Skip to content
Judgment Labs
Esc
↑↓navigate↵open⌘Jpreview
On this page

Observations

Record each model call your agent makes as an observation: the JSON format and the span attribute that carries it.

An observation records one model call your agent makes: everything the model was given and what it produced. Judgment groups observations into conversations and shows them, exactly as the model saw them, in the Conversation view. Supported integrations record observations automatically. For any other model call, send the observation yourself.

Send an observation

Set the observation on the model call’s span. It travels as JSON text under judgment.conversation.observation. One span holds one observation; setting it again replaces it.

Tracer.set_conversation_observation takes a typed ConversationObservation, so your type checker flags fields the format doesn’t define before Judgment rejects them.

from judgeval import Tracer
from judgeval.trace import ConversationMessage, ConversationObservation

@Tracer.observe(span_type="llm", span_name="support_reply")
def reply(history: list[ConversationMessage]) -> str:
    response = my_model.generate(history)
    observation: ConversationObservation = {
        "model": "my-model-v2",
        "input": [
            {"type": "message", "role": "system", "content": [{"type": "text", "text": "You help customers with orders."}]},
            *history,
        ],
        "output": [
            {"type": "message", "role": "assistant", "content": [{"type": "text", "text": response.text}]},
        ],
        "finish_reason": "stop",
    }
    Tracer.set_conversation_observation(observation)
    return response.text

Tracer.setConversationObservation takes a typed ConversationObservation, so the compiler flags fields the format doesn’t define before Judgment rejects them.

import { Tracer, type ConversationMessage, type ConversationObservation } from "judgeval";

const reply = Tracer.observe(async (history: ConversationMessage[]) => {
  const response = await myModel.generate(history);
  const observation: ConversationObservation = {
    model: "my-model-v2",
    input: [
      { type: "message", role: "system", content: [{ type: "text", text: "You help customers with orders." }] },
      ...history,
    ],
    output: [
      { type: "message", role: "assistant", content: [{ type: "text", text: response.text }] },
    ],
    finish_reason: "stop",
  };
  Tracer.setConversationObservation(observation);
  return response.text;
}, { spanType: "llm", spanName: "support_reply" });
import json
from opentelemetry import trace

span = trace.get_current_span()
span.set_attribute("judgment.conversation.observation", json.dumps(observation))

Observation

Field Type Meaning
input Item list, required Everything the model was given, in order.
output Item list What the model produced, when recorded.
conversation_id String The thread of context the call belongs to. Defaults to the span’s judgment.session_id. One of the two is required.
customer_id String The customer the call ran for. Defaults to the span’s judgment.customer_id.
user_id String The user the call ran for. Defaults to the span’s judgment.customer_user_id.
tools Tool list The tools the model could call: name, and optional description, parameters (JSON Schema of the arguments), and metadata.
model String The model that ran the call.
finish_reason String Why the model stopped, as the provider reported it.
usage Usage What the call consumed. Defaults to the span’s judgment.usage.* attributes.
error Error A recorded model-call failure.
metadata Object of JSON values Any other facts about this call.

Set conversation_id (or the session ID) to the same value on every call that continues the same context. See Session guidance.

Items

input and output are flat, ordered lists of items. An assistant turn with text and two tool calls is one message item followed by two tool_call items.

type Fields
message role (system, user, or assistant), content (part list)
tool_call name, arguments (any JSON), optional call_id
tool_result output (part list), optional call_id (the call it answers), optional error
reasoning text
custom kind (a name such as acme:handoff), data (any JSON)

Parts

type Fields
text text
image, audio, file source: {"type": "url", "url": ...} or {"type": "base64", "data": ...}; optional media_type, such as image/png
json value (any JSON)
custom kind, data (any JSON)

Use custom items and parts for data observations do not define yet.

Usage

Every field is optional. Leave out what you don’t know; don’t send zero.

Field Meaning
non_cached_input_tokens Input tokens charged at the normal rate.
non_cached_input_image_tokens Image input tokens charged at the normal rate.
cache_read_input_tokens Input tokens read from the prompt cache.
cache_creation_input_tokens Input tokens written to the prompt cache.
output_tokens Generated tokens, excluding reasoning.
output_image_tokens Generated image tokens.
reasoning_tokens Tokens spent reasoning.
total_cost_usd The call’s cost in USD.

Each field means the same as the matching judgment.usage.* attribute in Attribute keys.

Errors

error on the observation records a failed model call. error on a tool_result records a failed tool. Both take optional type, message, and stacktrace; {} records a failure without details. Judgment never infers a failure from span status or a missing output.

Rules

  • Send the whole window. input holds every item the model was given, including history from earlier calls. Judgment stores each distinct item once per conversation, so resent history costs little storage. It still counts toward the span’s size.
  • Keep per-call facts off items. An item must be exactly what is resent on later calls. Finish reason, usage, and latency belong on the observation.
  • No unknown fields. Every object is closed. A field this page does not define rejects the whole observation.

Validation and limits

An observation that does not fit this format is not stored. Judgment sets judgment.conversation.warnings on its span, listing each problem by field path. The span and its trace are stored as usual.

Limit Value
IDs, names, kinds, models, finish reasons, media types 1 to 512 characters
Span attributes, including the observation 8 MiB per span. See Ingest contract.

Was this page helpful?