fiddler-langchain
Fiddler observability for LangChain V1 agents built with langchain.agents.create_agent.
Installation
pip install fiddler-langchain
Quick Start
Runnable example: see
examples/travel_agent.pyfor a complete, traced single-agent quickstart.
Call FiddlerLangChainInstrumentor.instrument() once after creating your FiddlerClient. Every agent created with langchain.agents.create_agent() is then traced automatically. Use the optional name argument to label agents in traces; if omitted, the agent name is left empty.
import langchain.agents
from fiddler_otel import FiddlerClient
from fiddler_langchain import FiddlerLangChainInstrumentor
client = FiddlerClient(
api_key='YOUR_API_KEY',
application_id='YOUR_APPLICATION_ID',
url='https://your-instance.fiddler.ai',
)
instrumentor = FiddlerLangChainInstrumentor(client=client)
instrumentor.instrument()
agent = langchain.agents.create_agent(
model='openai:gpt-4o-mini',
tools=[...],
name='my_agent',
)
result = agent.invoke({'messages': [{'role': 'user', 'content': 'Hello!'}]})
Alternative (manual middleware): You can instead pass middleware=[FiddlerAgentMiddleware(client=client, agent_name="my_agent")] to each create_agent() call and skip the instrumentor. Use the instrumentor when you want a single instrument() to trace all agents.
Trace Hierarchy
Each invocation produces a clean, flat hierarchy with no noisy Chain wrappers:
[Span] my_agent (Agent root - TYPE=agent)
└── [Span] gpt-4o-mini (LLM call - TYPE=llm)
└── [Span] hotel_search (Tool call - TYPE=tool)
└── [Span] gpt-4o-mini (LLM call - TYPE=llm)
Note on
span_type=agentfor the root span: The root span usesspan_type=agentto accurately represent that it is an agent invocation. It carriesagent_name,agent_id, andconversation_idbut has empty LLM and tool fields (model_name,llm_output,tool_name, etc.) because it is a container span for the full agent lifecycle - not an LLM or tool call itself. The legacychainspan type is deprecated and no longer used for agent root spans.
Multi-turn Conversations
from fiddler_langchain import set_conversation_id
import uuid
set_conversation_id(str(uuid.uuid4()))
agent.invoke({'messages': [...]})
LLM Context
Attach contextual metadata to LLM spans by calling set_llm_context before the agent runs.
The instrumentation reads this value from the model's metadata at invocation time.
import langchain.agents
from langchain_openai import ChatOpenAI
from fiddler_langchain import FiddlerLangChainInstrumentor, set_llm_context
from fiddler_otel import FiddlerClient
client = FiddlerClient(api_key='...', application_id='...', url='...')
FiddlerLangChainInstrumentor(client=client).instrument()
model = ChatOpenAI(model='gpt-4o-mini')
set_llm_context(model, 'User preference: concise answers')
agent = langchain.agents.create_agent(model=model, tools=[...], name='my_agent')
Session and Span Attributes
Add custom business metadata to your traces without modifying agent code.
Session attributes
add_session_attributes(key, value) injects key-value pairs onto every span in the current invocation. Call it before invoking the agent. Attributes appear as fiddler.session.user.{key} on all spans and are propagated from parent to child spans.
from fiddler_langchain import add_session_attributes, set_conversation_id
import uuid
set_conversation_id(str(uuid.uuid4()))
add_session_attributes('user_id', 'alice@example.com')
add_session_attributes('environment', 'production')
add_session_attributes('cost_center', 'travel_desk')
agent.invoke({'messages': [{'role': 'user', 'content': 'Hello!'}]})
Span attributes
add_span_attributes(node, **kwargs) attaches attributes to a specific component (model, tool, or retriever). Only spans created for that component carry these attributes. They appear as fiddler.span.user.{key}.
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from fiddler_langchain import add_span_attributes
model = ChatOpenAI(model='gpt-4o-mini')
add_span_attributes(model, department='AI_Engineering', agent_role='flight_assistant')
@tool
def book_flight(from_airport: str, to_airport: str) -> str:
"""Book a flight between two airports."""
return f'Booked {from_airport} -> {to_airport}'
add_span_attributes(book_flight, department='third_party_flight', reward_points='10.2')
agent = langchain.agents.create_agent(model=model, tools=[book_flight], name='my_agent')
| Helper | Scope | Span attribute format |
|---|---|---|
add_session_attributes(key, value) |
All spans in the invocation | fiddler.session.user.{key} |
add_span_attributes(model_or_tool, **kwargs) |
Spans for that model/tool/retriever | fiddler.span.user.{key} |
Retriever Instrumentation
The LangChain V1 middleware API does not expose a dedicated retriever hook. Following the
same convention used in fiddler-langgraph, retrievers are treated as tools.
Wrap your retriever with @tool (or create_retriever_tool) and pass it to create_agent.
The instrumentation's tool hook captures the retriever call automatically as a
TYPE=tool span - with the query as tool_input and the retrieved documents as tool_output.
import langchain.agents
from langchain_core.tools import tool
from fiddler_langchain import FiddlerLangChainInstrumentor
from fiddler_otel import FiddlerClient
client = FiddlerClient(api_key='...', application_id='...', url='...')
FiddlerLangChainInstrumentor(client=client).instrument()
retriever = vector_store.as_retriever()
@tool
def search_docs(query: str) -> str:
"""Search company documents for relevant information."""
return str(retriever.invoke(query))
agent = langchain.agents.create_agent(
model='openai:gpt-4o-mini',
tools=[search_docs, ...],
name='rag_agent',
)
The resulting trace looks like:
[Span] rag_agent (Agent root - TYPE=agent)
└── [Span] gpt-4o-mini (LLM call - TYPE=llm)
└── [Span] search_docs (Retriever as Tool - TYPE=tool)
└── [Span] gpt-4o-mini (LLM call - TYPE=llm)
Multi-Agent Setup
With the instrumentor, a single instrument() call patches create_agent so every agent is traced. Pass name='...' to each create_agent() to label agents in traces. When a supervisor delegates work to sub-agents via tools, the entire flow now appears as a single trace for that user request: the supervisor is the root span, delegation tools are tool spans under it, and each sub-agent root span is a child of the corresponding delegation tool span. All spans share the same conversation_id.
import uuid
import langchain.agents
from langchain_openai import ChatOpenAI
from fiddler_langchain import FiddlerLangChainInstrumentor, set_conversation_id
from fiddler_otel import FiddlerClient
client = FiddlerClient(api_key='...', application_id='...', url='...')
instrumentor = FiddlerLangChainInstrumentor(client=client)
instrumentor.instrument()
# Sub-agents - invoked via supervisor tools
flight_agent = langchain.agents.create_agent(
model=ChatOpenAI(), tools=[book_flight], name='flight_assistant'
)
hotel_agent = langchain.agents.create_agent(
model=ChatOpenAI(), tools=[search_hotel, book_hotel], name='hotel_assistant'
)
# Supervisor - delegates to sub-agents via @tool wrappers
supervisor = langchain.agents.create_agent(
model=ChatOpenAI(),
tools=[delegate_to_flight_assistant, delegate_to_hotel_assistant],
name='supervisor',
)
# Link the whole flow under one conversation + one trace
set_conversation_id(str(uuid.uuid4()))
supervisor.invoke({'messages': [{'role': 'user', 'content': 'Book a flight and a hotel.'}]})
The resulting trace looks like:
[Span] supervisor (root - TYPE=agent)
├── [Span] gpt-4o-mini (LLM - TYPE=llm)
├── [Span] delegate_to_flight_assistant (Tool - TYPE=tool)
│ └── [Span] flight_assistant (Agent - TYPE=agent)
│ ├── [Span] gpt-4o-mini (LLM - TYPE=llm)
│ └── [Span] book_flight (Tool - TYPE=tool)
└── [Span] delegate_to_hotel_assistant (Tool - TYPE=tool)
└── [Span] hotel_assistant (Agent - TYPE=agent)
├── [Span] gpt-4o-mini (LLM - TYPE=llm)
├── [Span] search_hotel (Tool - TYPE=tool, retriever-as-tool)
└── [Span] book_hotel (Tool - TYPE=tool)
Local JSONL Capture
To capture all spans to a local JSONL file without sending to Fiddler (useful for debugging):
client = FiddlerClient(
api_key='...',
application_id='...',
url='...',
jsonl_capture_enabled=True,
jsonl_file_path='trace_data.jsonl',
)
Or via environment variables:
FIDDLER_JSONL_ENABLED=true \
FIDDLER_JSONL_FILE=trace_data.jsonl \
python my_agent.py
Each line in the output file is a JSON object containing all span attributes:
trace_id, span_id, parent_span_id, span_type, agent_name, conversation_id,
model_name, model_provider, llm_input_system, llm_input_user, llm_output,
llm_context, llm_token_count_input/output/total, gen_ai_input_messages,
gen_ai_output_messages, tool_name, tool_input, tool_output, tool_definitions.
Async Agents
The instrumentation fully supports async agents via awrap_model_call and
awrap_tool_call. Use agent.ainvoke() instead of agent.invoke() - no additional
configuration is needed:
import asyncio
import langchain.agents
from langchain_openai import ChatOpenAI
from fiddler_langchain import FiddlerLangChainInstrumentor
from fiddler_otel import FiddlerClient
client = FiddlerClient(api_key='...', application_id='...', url='...')
FiddlerLangChainInstrumentor(client=client).instrument()
agent = langchain.agents.create_agent(
model=ChatOpenAI(model='gpt-4o-mini'),
tools=[...],
name='my_agent',
)
async def main():
result = await agent.ainvoke({'messages': [{'role': 'user', 'content': 'Hello!'}]})
print(result)
asyncio.run(main())
The instrumentation automatically uses the async lifecycle hooks (awrap_model_call,
awrap_tool_call) when the agent is invoked asynchronously, producing the same span
hierarchy as the sync path.
Error Handling
If an LLM call or tool call raises an exception, the instrumentation catches it, marks the
corresponding span with StatusCode.ERROR, re-raises the exception so normal error
handling in your application is unaffected, and still cleanly closes the root agent span.
try:
result = agent.invoke({'messages': [{'role': 'user', 'content': 'Hello!'}]})
except Exception as e:
# The failing LLM or tool span is already marked ERROR in Fiddler
# The root agent span is also closed - no dangling spans
raise
This means:
- Partial traces are never lost — all spans up to the point of failure are recorded
- The failing span carries
status_code=ERRORand the exception message - The root agent span is always closed, regardless of whether the invocation succeeded or failed
Relationship to fiddler-langgraph
| Package | Framework | Instrumentation |
|---|---|---|
fiddler-langgraph |
LangGraph (StateGraph.compile()) |
Callback handler |
fiddler-langchain |
LangChain V1 (create_agent) |
FiddlerLangChainInstrumentor (auto) or FiddlerAgentMiddleware (manual) |
Metadata
Release files for fiddler-langchain 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fiddler_langchain-1.2.0.tar.gz | 20.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fiddler_langchain-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 39.7 kB
Release files / fiddler_langchain-1.2.0.tar.gz
| Download URL | fiddler_langchain-1.2.0.tar.gz |
|---|---|
| Size | 20.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eb26d2e18db4e5c68fef74258af9df5aba7de3600e4a8153adffa1b1a8bba8cc
|
|
BLAKE2b-256 checksum How to use checksums |
a794704804a0e897fbe3e72292aea240125447c38ff92b7f88040031b5656ac8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / fiddler_langchain-1.2.0-py3-none-any.whl
| Download URL | fiddler_langchain-1.2.0-py3-none-any.whl |
|---|---|
| Size | 18.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0c0d944ca87b044038676240aa99b600c735dd811d18a83c603e1bf68dddcae8
|
|
BLAKE2b-256 checksum How to use checksums |
5d4cc45504b32656ab3fd46c2bc283e438cebac63cc4524f47c89021adeb671f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency log