Native Instrumentation¶
Autobench native instrumentors collect ABP traces from supported SDKs without task-level
ctx.span() or ctx.metric() calls. They are optional adapters around public hooks or pinned,
reviewed patch points. Core benchmark, record, replay, and report imports do not require any of the
instrumented SDKs.
Install¶
Install one integration or the complete set:
pip install 'autobench[pydantic-ai]'
pip install 'autobench[openai]'
pip install 'autobench[openai-agents]'
pip install 'autobench[httpx]'
pip install 'autobench[instrumentation]'
The integration registry is lazy. Loading a YAML spec, replaying evidence, or running
autobench instrumentation doctor does not import an SDK that is not installed.
Automatic Discovery¶
Use instrument_all() when the benchmark should activate every built-in integration that is
installed and compatible in the current environment:
from autobench import Benchmark
benchmark = Benchmark("support-agent").instrument_all()
Unavailable or unsupported integrations are skipped by default and recorded on each run as
instrumentation.skipped diagnostic evidence. Use strict=True when the environment must support
the complete selected set:
benchmark = Benchmark("support-agent").instrument_all(
exclude={"httpx"},
strict=True,
)
Explicit settings take precedence over discovery, including an explicit false. A custom runtime
instrumentor with the same instrumentor ID also takes precedence, so automatic discovery does not
install a duplicate. Calling instrument_all() again replaces the previous automatic settings.
Live OpenRouter Trace¶
The live Pydantic AI example exercises automatic discovery across all three active layers:
uv sync --extra instrumentation
export OPENROUTER_API_KEY=...
export OPENROUTER_MODEL=openrouter:openai/gpt-5.6-luna
uv run python examples/pydantic_ai/openrouter_instrument_all.py \
--record /tmp/autobench-openrouter
The benchmark itself only opts in once:
benchmark = (
Benchmark("openrouter-shopping-agent")
.instrument(PydanticAI(assets=[INSTRUCTIONS]))
.instrument_all()
)
The explicit Pydantic AI instrumentor contributes tracked prompt metadata. Automatic discovery recognizes its instrumentor ID and does not install a duplicate, then adds the OpenAI client and HTTPX transport instrumentors. One real request therefore records agent, model, tool, output validation, stream, client request, and transport spans with their native parentage. It also records model identity, token usage, durations, HTTP method/host/path/status, score observations, asset versions, capture diagnostics, and replayable source provenance. HTTP bodies and credentials remain redacted by the default capture policy.
The full source is examples/pydantic_ai/openrouter_instrument_all.py. It deliberately contains no
manual ctx.span() or ctx.metric() calls so the resulting record demonstrates native collection
rather than hand-authored benchmark telemetry.
YAML¶
Instrumentation belongs to the named benchmark:
# yaml-language-server: $schema=schemas/0.2.0/benchmark_schema.json
benchmark:
support-agent:
dataset:
source: file://datasets/cases.yaml
run:
python: support_benchmark:run
variants:
baseline:
factors:
model.name: openrouter:openai/gpt-5.6-luna
instrumentation:
all:
exclude: [httpx]
strict: false
pydantic_ai: {}
openai: {}
httpx:
capture:
path: hash
request_headers: [x-request-id]
response_headers: [x-request-id]
request_body: false
response_body: false
max_body_bytes: 65536
Use false to retain a known integration in a shared spec without installing it:
instrumentation:
openai_agents: false
Unknown integration names and unknown settings fail validation. YAML never evaluates Python expressions.
The all block follows the same precedence rules as the Python builder. In this example HTTPX is
excluded from discovery but its explicit capture settings still install it; all explicit entries
remain authoritative.
Python¶
The fluent API accepts typed, serializable settings:
from autobench import (
Benchmark,
HTTPXCaptureSettings,
HTTPXInstrumentation,
OpenAIInstrumentation,
)
benchmark = Benchmark("streaming-chat").instrument(
OpenAIInstrumentation(),
HTTPXInstrumentation(
capture=HTTPXCaptureSettings(
path="hash",
response_headers=("x-request-id",),
)
),
)
experiment = benchmark.run()
It also accepts a custom Instrumentor instance. Runtime instances are installed for the whole
benchmark matrix and closed even when execution fails. They are intentionally not serialized into
the YAML spec:
benchmark.instrument(MyNativeInstrumentor(settings))
Duplicate instrumentor IDs are rejected before hooks are installed. This avoids ambiguous ownership when a typed setting and a custom instance configure the same integration.
Built-In Integrations¶
| Integration | Layer | Collection seam | Evidence |
|---|---|---|---|
| Pydantic AI | framework | public agent capability | agent/model/tool/validation spans, messages, structured output, usage, stream lifecycle |
| OpenAI Python | client | reviewed public client methods and stream types | chat, responses, embeddings, raw responses, model identity, direct usage, stream lifecycle |
| OpenAI Agents | framework | native trace processor | workflow, agent, generation, tool, handoff, guardrail, and custom spans |
| HTTPX | transport | public transport methods | request method/host/path policy, status, selected headers, body metadata, stream lifecycle |
Run compatibility diagnostics before a benchmark:
autobench instrumentation doctor
The Rich output shows availability, installed version, supported range, abstraction layer, mechanism, sync/async/streaming capabilities, span kinds, semantic families, capture defaults, and degradation diagnostics.
Layered Traces¶
Instrumentors compose instead of flattening one another. A Pydantic AI request using the OpenAI client over HTTPX can produce this parent chain:
task
agent
llm framework operation
OpenAI client operation
HTTP request
Transport spans do not emit token or cost usage. Framework aggregate usage and client direct usage retain different accounting scopes. Trace extraction selects one authoritative direct layer and keeps aggregate values as validation evidence, so enabling HTTPX cannot inflate LLM totals.
Streaming Lifecycle¶
A stream span does not end when an SDK returns an iterator. It remains open until the stream:
- completes normally;
- raises;
- is cancelled;
- is explicitly closed early;
- is abandoned when the instrumentor manager closes.
ABP records first-chunk evidence, item/chunk counts, partial state, and the final end reason. Native items, exceptions, iterator methods, and context-manager behavior pass through unchanged.
HTTP Privacy Defaults¶
HTTPX capture defaults are deliberately conservative:
- query-free path hash, not the raw path;
- no request or response headers unless named;
- authorization, cookies, API keys, tokens, passwords, and secrets always redacted;
- no request or response body capture;
- bounded capture when bodies are explicitly enabled;
- binary bodies represented by metadata and a digest, not embedded bytes.
path: full is an explicit opt-in. Query strings and URL user information are not recorded by the
path setting. Capture policies apply before evidence reaches a RunRecord.
Trace Diagnostics¶
Every native span records an InstrumentationScope: instrumentor and target versions, mechanism,
abstraction layer, and source convention. Source facts can be retained alongside canonical
Autobench semantic attributes. Unsupported library versions fail installation instead of silently
patching an unknown lifecycle.
Inspect recorded trace shape without importing task modules or optional SDKs:
autobench instrumentation trace runs/support-agent/exp_...
The command reports per-case span/root counts, partial traces, diagnostics, span-kind totals, and instrumentor composition.
Replay Without SDKs¶
RunRecords contain materialized ABP traces, not live provider objects. A reporting or optimization worker can replay and re-extract evidence with only Autobench installed:
from autobench import CompositeExtractor, SignalExtractor, SpanExtractor, UsageExtractor
from autobench.records.replay import load_run_record, replay_extraction
record = load_run_record(path, root_dir=run_dir)
derived = replay_extraction(
record,
CompositeExtractor(SignalExtractor(), SpanExtractor(), UsageExtractor()),
)
Extraction creates a derived record with lineage; it never mutates the original record.
ABP And OpenTelemetry¶
ABP is not an OpenTelemetry wrapper and has no OTel dependency. It is Autobench's evidence protocol for benchmark execution, semantic measurements, accounting scope, partial streams, replay, and optimization lineage. Native instrumentors use the same kinds of stable SDK hooks that mature OTel instrumentations validate, but emit ABP directly.
A future bridge can export ABP spans to OTLP systems such as Logfire or Datadog. That bridge will be an adapter: ABP remains the source evidence model, and importing Autobench will not require an OTel SDK or collector.
Protocol Stability¶
ABP protocol version 1 is the initial public serialized contract. Autobench 0.2.x preserves the
meaning of its signal, trace, scope, provenance, and accounting fields. Readers retain unknown
additive data through extension maps, while a breaking wire-format change requires a new protocol
version. Instrumentor patch points are compatibility-gated separately because provider SDK
lifecycles can change independently of ABP.
Examples¶
examples/abp_manual: explicit workflow spans plus method instrumentation.examples/abp_concurrent: concurrent sibling operations with task-local parentage.examples/pydantic_ai: tool use, retry, streaming, and structured output; OpenAI models add OpenAI and HTTPX layers.examples/abp_openai: offline official OpenAI streaming over an HTTPX mock transport.examples/abp_openai_agents: offline native OpenAI Agents trace-processor workflow.examples/abp_replay: trace extraction from recorded evidence without importing provider SDKs.