Skip to content

Adapter System

Adapters translate provider responses or framework events into Attest’s common Trace format. Choose one that records the evidence your assertions need: a provider response alone does not describe an entire agent workflow or prove a tool’s external effects.

TypeInputWhat it can establish
ProviderA completed provider response and caller-supplied contextLLM output, reported tokens, requested tool calls
FrameworkFramework callbacks, events, or run summariesThe execution details actually exposed by that integration
ManualSteps supplied by your codeWhatever you explicitly record
OpenTelemetryExisting spansWhat the spans and supported attributes contain

These integration types are not the cost tiers (TIER_1–TIER_3) or maturity levels below. An adapter does not make unsupported evidence appear. In particular, a requested tool call is not proof that the tool ran, and missing cost or timing information is not a measured zero.

Python provider adapters share BaseProviderAdapter.trace_from_response(). It builds a trace from an existing response; it does not automatically wrap every client call. Supply the input and timestamps around the actual request when you need them:

from attest import OpenAIAdapter
# response and messages come from your application's completed provider call.
trace = OpenAIAdapter(agent_id="support").trace_from_response(
response,
input_messages=messages,
started_at_ms=started,
ended_at_ms=ended,
)

The base implementation resolves timestamps, creates a TraceBuilder, extracts completion/model/token data and tool calls, and assembles output and metadata. Missing timestamps fall back to the current time; those fallbacks do not measure the original request duration. The caller can supply metadata such as cost_usd and latency_ms.

OpenAI capture supports Chat Completions and Responses API response shapes. An OpenAI-compatible service must return a supported response shape; endpoint compatibility alone is not a guarantee of trace parity.

Subclass BaseProviderAdapter from attest.adapters._base. Its required extraction hooks are:

HookReturn value
_extract_completion(response)Completion string
_extract_model(response, **metadata)Model string or None
_extract_total_tokens(response)Token count or None
_extract_tool_calls(response)List of dictionaries with name and optional args

Optional hooks include _extract_input(input_messages, **metadata), _build_output(response, completion_text, **metadata), _extract_token_split(response), and _extract_refusal(response). Default output is {"message": completion_text}. Return unknown usage as absent rather than inventing measurements.

Framework adapters have framework-specific entry points rather than one universal interception interface.

Python integrationEntry pointEvidence boundary
LangChainwith adapter.capture() as handler:; pass handler in callbacks, then read adapter.traceCaptures supplied callback events
Google ADKawait adapter.capture_async(runner, user_id, session_id, message) or GoogleADKAdapter.from_events(events)Converts the runner’s event stream
CrewAIwith adapter.capture() as cap: then cap.trace_from_crew_output(result, crew)Summary-only: final output, token usage, and latency; not per-tool/delegation evidence
LlamaIndexwith handler: then handler.build_trace(query=..., response=...)Captures supported instrumentation events

A LangChain example:

from attest import LangChainAdapter
adapter = LangChainAdapter(agent_id="support")
with adapter.capture() as handler:
executor.invoke({"input": "Check my order"}, config={"callbacks": [handler]})
trace = adapter.trace

Do not infer parallel execution, agent identity, or nested delegation merely from the framework’s feature list. Inspect the trace for the required agent_call/sub_trace structure and temporal fields before using those assertions. Use manual instrumentation or another capture path when an adapter cannot expose the necessary evidence.

Manual traces are useful for custom agents or isolated tests, without provider calls:

from attest import ManualAdapter
builder = ManualAdapter(agent_id="support").create_builder()
builder.set_input(query="Check order")
builder.add_tool_call("lookup_order", args={"order_id": "O-1042"})
builder.set_output(message="Order found")
trace = builder.build()

Python OTelAdapter.from_spans(otel_spans) accepts readable spans, while from_otel_span_dicts() accepts span dictionaries. They convert supported telemetry into traces; the existence of an OpenTelemetry integration does not guarantee that every observability vendor or attribute convention is supported.

The 1.1.0 engine capability inventory lists 10 Python and 7 TypeScript adapters. Maturity describes compatibility commitments, not assertion cost:

  • Stable: covered by parity and conformance suites; behavior changes are breaking changes.
  • Beta: shipped and tested, but interface or behavior can change in a minor release.
  • Experimental: no stability commitment; may change or be removed in any release.
IntegrationPythonTypeScriptMaturity
OpenAIOpenAIAdapterOpenAIStable
AnthropicAnthropicAdapterAnthropicStable
ManualManualAdapterManualStable
GeminiGeminiAdapterGeminiBeta
OllamaOllamaAdapterOllamaBeta
LangChain / LangChain.jsLangChainAdapterLangChain.jsBeta
OpenTelemetryOTelAdapterOpenTelemetryBeta
CrewAICrewAIAdapterNot shippedBeta
Google ADKGoogleADKAdapterNot shippedExperimental
LlamaIndexLlamaIndexInstrumentationHandlerNot shippedExperimental

Install the Python extra for the chosen integration:

Terminal window
uv add "attest-ai[openai]>=1.1.0"

Other extras are anthropic, gemini, ollama, langchain, crewai, google-adk, llamaindex, and otel. Manual capture is included in the base package. See the assertion pipeline for the evidence each kind of check needs.