Reference
Manual attributes, project routing, auto-instrumentation, OpenTelemetry integration, and subagent tracing.
This reference covers the Judgment tracing APIs used for manual attributes, project routing, auto-instrumentation, OpenTelemetry integration, and subagent tracing. The complete Python and TypeScript APIs are in the Tracer SDK Reference.
Manual attributes
Tracer.observe records function arguments and return values automatically.
Setting them again inside an observed function overwrites the captured values
unless automatic capture is disabled. Python's Tracer.span() and TypeScript's
Tracer.span() and Tracer.with() do not capture I/O, so set input and output
manually when you use those APIs.
Use the attribute setters below for queryable metadata that is not already part
of the function's input or output. The SDK's session and customer setters also
propagate those documented IDs to child spans. For custom context that must
cross process boundaries, use the lower-level
Python baggage API or the baggage APIs in
the TypeScript SDK reference. The
judgment.* namespace is reserved for documented Judgment attributes.
@Tracer.observe(span_type="agent", span_name="handle")
def handle(user_id: str, question: str) -> str:
Tracer.set_attribute("user_id", user_id)
Tracer.set_attributes({"channel": "web", "plan": "pro"})
Tracer.set_customer_user_id(user_id)
return run(question)const handle = Tracer.observe(async function handle(
userId: string, question: string
): Promise<string> {
Tracer.setAttribute("user_id", userId);
Tracer.setAttributes({ channel: "web", plan: "pro" });
Tracer.setCustomerUserId(userId);
return run(question);
}, { spanType: "agent", spanName: "handle" });In Python, use
Tracer.scoped_context()
when you need to apply session, customer, or custom attributes before a span
starts. Spans created inside the block inherit those values.
LLM metadata
Use recordLLMMetadata() on the active llm span:
Tracer.recordLLMMetadata({
model: "gpt-5.6-luna",
provider: "openai",
output_tokens: 150,
});The helper accepts model, provider, non_cached_input_tokens,
output_tokens, cache_read_input_tokens, cache_creation_input_tokens,
and total_cost_usd. It stores them as:
judgment.llm.modeljudgment.llm.providerjudgment.usage.non_cached_input_tokensjudgment.usage.output_tokensjudgment.usage.cache_read_input_tokensjudgment.usage.cache_creation_input_tokensjudgment.usage.total_cost_usd
Use recordLLMMetadata() for these fields when possible. You can also write the
documented judgment.* attributes directly. Judgment can calculate cost from
the model and token data, so only set total_cost_usd when the provider returns
it.
Project routing
Use separate projects for development, tests, staging, and production.
Services in one distributed trace must use the same project and different
service.name values. Log the project name and environment at startup, and
keep the API key out of the logs. To test locally, point the tracer at a
separate test project.
For environment-specific projects, derive the project name from the deployment
environment and pass it to a single Tracer.init() call.
import os
env = os.environ["APP_ENV"]
Tracer.init(project_name=f"{env} - my_agent")In TypeScript, pass the computed value as projectName to the single
Tracer.init() call.
Auto-instrumentation
Auto-instrumentation records model calls without wrapping each one.
Python uses wrap(). It tracks all
LLM API calls, including token usage, cost, and streaming, for sync and async
clients. See Model Providers
for supported providers.
TypeScript needs all three of the following:
- An instrumentation file that the runtime preloads before the app starts.
- OpenTelemetry instrumentations registered on
TracerbeforeTracer.init(). - A CommonJS build, because the instrumentations patch modules at require time.
import { Tracer } from "judgeval";
import { OpenAIInstrumentation } from "@opentelemetry/instrumentation-openai";
Tracer.registerOTELInstrumentation(new OpenAIInstrumentation());
await Tracer.init({ projectName });Import this file first in your entrypoint (import "./instrumentation";), or
preload it with your runtime's require/import flag.
Activating spans
In TypeScript, the SDK tracks the active span in its own context rather than OpenTelemetry's global context. Two consequences follow:
Tracer.startSpanreturns a span but does not make it active. Nothing you run afterward parents under it, andsetSessionIdfinds no current span and silently does nothing.- Activating a span with raw OpenTelemetry calls (
context.with,trace.setSpan) writes to a context the SDK never reads. Integration spans then start their own traces instead of joining yours.
Create roots with Tracer.observe or
Tracer.getOTELTracer().startActiveSpan, which activate the span in the
SDK's context. After wiring, send one request and confirm the integration's
spans share your root's trace ID.
Python has the same lifecycle distinction between starting and activating a
span. Tracer.start_span() returns a span that you must manage yourself. Use
Tracer.observe, Tracer.span(), or Tracer.start_as_current_span() when work
should run with that span active.
OpenTelemetry integration
Judgment tracing is standard OpenTelemetry. It works with existing OTEL tooling, semantic conventions, collectors, and exporters. To feed Judgment from an existing OTEL setup, add Judgment's span processor to your provider:
tracer_provider.add_span_processor(tracer.get_span_processor())Set the usual resource attributes (service.name, service.version, and
deployment.environment) per the
OpenTelemetry Resource specification.
Attribute mappers
Spans that do not come from the judgeval SDK may use different attribute
names. OpenTelemetry's GenAI conventions use gen_ai.response.model and the
Vercel AI SDK uses ai.response.model, while Judgment reads
judgment.llm.model. When the names do not
match, fields such as cost, sessions, and inputs/outputs show up empty even
though the data is on the span.
An attribute mapper copies those values onto Judgment's keys at ingest, so your instrumentation stays as it is. A mapper is a list of source key to target key pairs:
| Source key | Target key |
|---|---|
gen_ai.system | judgment.llm.provider |
gen_ai.response.model | judgment.llm.model |
gen_ai.usage.input_tokens | judgment.usage.non_cached_input_tokens |
gen_ai.usage.output_tokens | judgment.usage.output_tokens |
To create one, open your project in the Judgment platform and go to Configure → Mappers. It applies to spans ingested from that point on and runs after Judgment's built-in mappers for known providers, so you only need a mapper for attributes those do not already recognize.
By default, the source key is deleted after its value is copied, and the copy is skipped if the target key already holds a value. Turn off Remove source to keep both keys, or turn on Override to write over the existing value.
Mappers can also be managed from your editor or the Judgment agent with the MCP server.
Subagent tracing
Use a forked trace when one agent delegates a meaningful chunk of work to a subagent and the subagent's work is evaluated on its own. The parent trace keeps a lightweight invocation span; the subagent runs in its own linked trace. Otherwise, keep subagent work inside the parent trace.
@Tracer.observe(span_type="agent", fork=True)
def research_subagent(task: str) -> str:
...In TypeScript, pass { spanType: "agent", fork: true }. For lower-level
control in Python, see
Tracer.start_linked_trace().