LangChain / LangGraph
Connect LangChain or LangGraph agents to Radicas via the OpenInference instrumentor — the first shim-required path.
Status: experimental — the strategy and the normalization maps are implemented, but the
bench deep-dive (fixture capture per docs/add-a-framework.md) has not validated them
end-to-end yet. The registry card openinference-langchain is a research-drafted stub.
One page covers both: classic LangChain AgentExecutor is legacy (LangChain itself points to
LangGraph for agents) and both emit under the same instrumentation scope, so for Radicas
they are one source.
Install
pip install "radicas[langchain]" # = radicas + openinference-instrumentation-langchainUse
import radicas
radicas.init(service="pricing-agent", feature="flight-pricing")
# instrument="auto" detects langchain/langgraph and activates
# LangChainInstrumentor().instrument() — or select explicitly:
# radicas.init(instrument=["langchain"]) # alias for openinference-langchain
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(model, tools)
agent.invoke({"messages": [("user", prompt)]})
radicas.shutdown()If the framework is installed but the instrumentor extra is not, explicit selection raises
MissingInstrumentorError naming radicas[langchain]; "auto" skips with a warning.
What it emits
- Scope name:
openinference.instrumentation.langchain— from the OpenInference package (covers LangGraph). - Namespace:
llm.*/openinference.*— NOT canonicalgen_ai.*; hence the shim. - Span names: chain/llm/tool spans named after LangChain components (e.g.
ChatAnthropic), not the canonical names — span-name mapping is pending fixture (the card'sspan_name_mapis intentionally empty until verified). - Coverage (registry card, drafted from research — to confirm against a fixture):
| Field | Covered |
|---|---|
gen_ai.request.model | yes — mapped from llm.model_name |
gen_ai.usage.input_tokens | yes — mapped from llm.token_count.prompt |
gen_ai.usage.output_tokens | yes — mapped from llm.token_count.completion |
| reasoning tokens | partial (provider-dependent; to verify) |
| cache tokens | expected, pending fixture |
| finish reasons | partial |
provider (gen_ai.system) | partial |
| tool tree | partial — chain/tool spans exist but not under the canonical names |
| content | partial |
Shim behavior
- Client-side adapter: yes. The SDK's
NormalizingSpanExporterapplies the card's attribute maps at export (llm.model_name→gen_ai.request.model, token counts) and stampsradicas.source_framework=openinference-langchain. Originals are kept. - Server-side OTTL: yes. The same maps run at Radicas ingest for non-SDK senders — SDK users simply arrive already-canonical.
Known limitations
- Not fixture-verified: token/span coverage above is the card's honest draft, not evidence.
- The alternative Traceloop route (
opentelemetry-instrumentation-langchain, near-gen_ai.*with older names) is still under evaluation; the deep-dive will pick the official route. - No canonical
invoke_agentroot until the span-name map is confirmed. The default feature is only stamped oninvoke_agent*roots, so it will not appear on these spans yet; a scopedwith radicas.feature(...)block DOES stamp every span inside it and works today.
Verify it works
SELECT model, input_tokens, output_tokens, estimated_cost_usd
FROM otel.llm_call ORDER BY ts DESC LIMIT 5;
-- which adapter fired:
SELECT DISTINCT SpanAttributes['radicas.source_framework'] FROM otel_traces
WHERE ScopeName = 'openinference.instrumentation.langchain';Bench example (agent runnable, telemetry pending):
agents/langchain-langgraph/python in radicas-integrations.