feat(adapters): OpenAI Agents SDK, and Pydantic AI verified as already covered - #146
Merged
Merged
Conversation
…y covered Issue #96 asked which frameworks need an adapter next, and said to pick by adoption. Measured rather than assumed, and the measurement changed the plan from two adapters to one adapter and one verification. Adoption, PyPI downloads per month with stars as a second signal: LangGraph 70.3M already covered OpenAI Agents 39.7M 29.0k stars uncovered <- built here Strands 37.1M 7.0k Claude Agent SDK 32.9M 8.0k CrewAI 30.8M 57.7k Google ADK 21.6M already covered Pydantic AI 137M/13M 19.5k already covered, see below OpenAI Agents SDK: a first-party adapter The SDK ships agents.tracing.TracingProcessor, which runs in the agent's own process. So this is the LangChain shape, not the transcription shape: records carry no origin block, because absence means self and self is the truth. Routing a first-party observation through agentrust-trace-adapters would force third-party-control-plane onto a record about the operator's own execution, which is a worse description rather than a safer one. enforcement_mode defaults to declared. Guardrails exist and can stop a run, but they are the operator's own code, not a policy engine evaluating a bundle, and recording a tripwire as enforcement is the overclaim that value exists to prevent. Payloads stay out. FunctionSpanData carries input and output and GenerationSpanData carries the whole message list; none of it is hashed into the transcript. A test runs a real agent with a secret in the tool input and asserts it appears nowhere in the record. What goes in is identity: which tools ran, in what order, and which agents handed off to which, because in a multi-agent run the tool order alone does not say who was holding the run. The processor keys everything by trace_id. The SDK runs agents concurrently, and a record whose transcript describes two runs while its subject names one is wrong rather than incomplete. Pydantic AI: no adapter needed It instruments through OpenTelemetry rather than a bespoke callback, and emits the GenAI conventions integrations/otel-genai already maps. Rather than assert that, a new interop test runs a real released agent with a tool call and maps its actual spans. Three findings recorded rather than smoothed over. It does not emit gen_ai.tool.type, so the transcript carries None there; worth knowing before somebody compares transcripts across frameworks. It emits gen_ai.agent.call.id where the conventions say gen_ai.agent.id, which is exactly the drift UNMAPPED_ATTRIBUTES exists to surface, and costs nothing because both are already unmapped. And it emits tool arguments, tool results and both message lists BY DEFAULT, all four already excluded as payloads. A real framework shipping them on by default is the argument for that exclusion, not against it, and there is now a test asserting the framework really does emit them. Both interop tests run against pinned released packages with no network: the OpenAI one implements the SDK's own Model interface for a scripted two-turn run rather than borrowing a fixture from its test suite, which is not in the wheel. Both get their own CI job so a failure is legible in the checks list. 51 tests across the two directories. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak
This was referenced Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses #96. It asked to pick the next adapters by adoption. I measured rather than assumed, and the measurement changed the plan from two adapters to one adapter and one verification.
Adoption
PyPI downloads per month, with GitHub stars as an independent second signal.
OpenAI Agents SDK: first-party
The SDK ships
agents.tracing.TracingProcessor, which runs in the agent's own process. That is the LangChain shape, not the transcription shape, so records carry nooriginblock: absence meansself, and self is the truth.Routing a first-party observation through
agentrust-trace-adapterswould forcethird-party-control-planeonto a record about the operator's own execution. A worse description, not a safer one.enforcement_modedefaults todeclared. Guardrails exist and can stop a run, but they are the operator's own code, not a policy engine evaluating a bundle. Recording a tripwire as enforcement is the overclaim that value exists to prevent.Payloads stay out.
FunctionSpanDatacarriesinput/output,GenerationSpanDatacarries the whole message list. A test runs a real agent with a secret in the tool input and asserts it appears nowhere in the record. What goes in is identity: which tools ran, in what order, and which agents handed off to which — because in a multi-agent run the tool order alone does not say who was holding it.The processor keys everything by
trace_id. The SDK runs agents concurrently, and a record whose transcript describes two runs while its subject names one is wrong rather than incomplete.Pydantic AI: no adapter needed, and here is the proof
It instruments through OpenTelemetry, not a bespoke callback, and emits the GenAI conventions
otel-genaialready maps. Rather than assert that, the new interop test runs a real released agent with a tool call and maps its actual spans.Three findings, recorded rather than smoothed over:
gen_ai.tool.type, so the transcript carriesNonethere. Worth knowing before someone compares transcripts across frameworks and finds a field populated in one and not another.gen_ai.agent.call.idwhere the conventions saygen_ai.agent.id. Exactly the driftUNMAPPED_ATTRIBUTESexists to surface, and it costs nothing since both are already unmapped.Interop tests use only what a user can reach
Both run against pinned released packages with no network. The OpenAI one implements the SDK's own
Modelinterface for a scripted two-turn run rather than borrowing a fixture from its test suite, which is not shipped in the wheel.Both get their own CI job so a failure is legible in the checks list rather than buried.
Also worth noting
Scope item 1 of #96 ("LangGraph is not mentioned anywhere") is already closed by
test_langgraph_interop.py. Details on the issue.51 tests across the two directories. Ruff gate clean.
🤖 Generated with Claude Code
https://claude.ai/code/session_014NL8o3PXq6kfs2SdmBv6ak