traceAI-langchain 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- traceai_langchain-0.2.2/PKG-INFO +326 -0
- traceai_langchain-0.2.2/README.md +292 -0
- traceai_langchain-0.2.2/pyproject.toml +52 -0
- {traceai_langchain-0.2.0 → traceai_langchain-0.2.2}/traceai_langchain/__init__.py +5 -0
- {traceai_langchain-0.2.0 → traceai_langchain-0.2.2}/traceai_langchain/_tracer.py +143 -19
- traceai_langchain-0.2.2/traceai_langchain/version.py +1 -0
- traceai_langchain-0.2.0/PKG-INFO +0 -214
- traceai_langchain-0.2.0/README.md +0 -195
- traceai_langchain-0.2.0/pyproject.toml +0 -29
- traceai_langchain-0.2.0/traceai_langchain/version.py +0 -1
- {traceai_langchain-0.2.0 → traceai_langchain-0.2.2}/traceai_langchain/_langgraph/__init__.py +0 -0
- {traceai_langchain-0.2.0 → traceai_langchain-0.2.2}/traceai_langchain/_langgraph/_attributes.py +0 -0
- {traceai_langchain-0.2.0 → traceai_langchain-0.2.2}/traceai_langchain/_langgraph/_instrumentor.py +0 -0
- {traceai_langchain-0.2.0 → traceai_langchain-0.2.2}/traceai_langchain/package.py +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: traceAI-langchain
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: OpenTelemetry instrumentation for LangChain
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Keywords: traceai,futureagi,opentelemetry,tracing,observability,llm,langchain
|
|
7
|
+
Author: Future AGI
|
|
8
|
+
Author-email: no-reply@futureagi.com
|
|
9
|
+
Requires-Python: >=3.10,<3.14
|
|
10
|
+
Classifier: Framework :: OpenTelemetry
|
|
11
|
+
Classifier: Framework :: OpenTelemetry :: Instrumentations
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Provides-Extra: langgraph
|
|
24
|
+
Requires-Dist: fi-instrumentation-otel (>=1.0.0)
|
|
25
|
+
Requires-Dist: langchain-core (>=0.2.43)
|
|
26
|
+
Requires-Dist: langgraph (>=0.2.0) ; extra == "langgraph"
|
|
27
|
+
Project-URL: Changelog, https://github.com/future-agi/traceAI/blob/dev/python/frameworks/langchain/CHANGELOG.md
|
|
28
|
+
Project-URL: Documentation, https://docs.futureagi.com/docs/integrations/traceai/langchain
|
|
29
|
+
Project-URL: Homepage, https://futureagi.com
|
|
30
|
+
Project-URL: Issues, https://github.com/future-agi/traceAI/issues
|
|
31
|
+
Project-URL: Repository, https://github.com/future-agi/traceAI/tree/dev/python/frameworks/langchain
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# LangChain & LangGraph OpenTelemetry Integration
|
|
35
|
+
|
|
36
|
+
## Overview
|
|
37
|
+
This integration provides comprehensive OpenTelemetry instrumentation for both LangChain and LangGraph frameworks. It enables detailed tracing and monitoring of applications built with these frameworks.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
### Install traceAI LangChain
|
|
42
|
+
```bash
|
|
43
|
+
pip install traceAI-langchain
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### For LangGraph support (optional)
|
|
47
|
+
```bash
|
|
48
|
+
pip install traceAI-langchain[langgraph]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Install LangChain OpenAI
|
|
52
|
+
```bash
|
|
53
|
+
pip install langchain-openai
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Environment Variables
|
|
57
|
+
Set up your environment variables to authenticate with FutureAGI.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import os
|
|
61
|
+
|
|
62
|
+
os.environ["FI_API_KEY"] = FI_API_KEY
|
|
63
|
+
os.environ["FI_SECRET_KEY"] = FI_SECRET_KEY
|
|
64
|
+
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## LangChain Quickstart
|
|
68
|
+
|
|
69
|
+
### Register Tracer Provider
|
|
70
|
+
Set up the trace provider to establish the observability pipeline:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from fi_instrumentation import register
|
|
74
|
+
from fi_instrumentation.fi_types import ProjectType
|
|
75
|
+
|
|
76
|
+
trace_provider = register(
|
|
77
|
+
project_type=ProjectType.OBSERVE,
|
|
78
|
+
project_name="langchain_app",
|
|
79
|
+
session_name="chat-bot"
|
|
80
|
+
)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Configure LangChain Instrumentation
|
|
84
|
+
Instrument the LangChain client to enable telemetry collection:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from traceai_langchain import LangChainInstrumentor
|
|
88
|
+
|
|
89
|
+
LangChainInstrumentor().instrument(tracer_provider=trace_provider)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Create LangChain Components
|
|
93
|
+
Set up your LangChain client with built-in observability:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from langchain_openai import ChatOpenAI
|
|
97
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
98
|
+
|
|
99
|
+
prompt = ChatPromptTemplate.from_template("{x} {y} {z}?").partial(x="why is", z="blue")
|
|
100
|
+
chain = prompt | ChatOpenAI(model_name="gpt-3.5-turbo")
|
|
101
|
+
|
|
102
|
+
def run_chain():
|
|
103
|
+
try:
|
|
104
|
+
result = chain.invoke({"y": "sky"})
|
|
105
|
+
print(f"Response: {result}")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print(f"Error executing chain: {e}")
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
run_chain()
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## LangGraph Instrumentation
|
|
116
|
+
|
|
117
|
+
LangGraph tracing is **automatic**: LangGraph runs each node, tool and LLM through
|
|
118
|
+
LangChain's callback system, so `LangChainInstrumentor` captures them with no
|
|
119
|
+
LangGraph-specific setup. You get:
|
|
120
|
+
|
|
121
|
+
- **Per-node spans** named after the graph node (`agent`, `tools`, `ask_human`, …)
|
|
122
|
+
- **Tool and LLM spans** nested under their node
|
|
123
|
+
- **Graph-node enrichment** — the node's own span carries `gen_ai.agent.graph.node_name` and `gen_ai.agent.graph.node_id`
|
|
124
|
+
- **Session grouping** — LangGraph's `configurable.thread_id` becomes `session.id` on every span (an explicit `using_session(...)` or `metadata={"session_id": ...}` wins). New in 0.2.1; with 0.2.0 or earlier, wrap the call in `using_session(thread_id)`.
|
|
125
|
+
- **HITL interrupts** traced correctly — an interrupted node/tool span stays `OK` (not `ERROR`) and is marked with `langgraph.interrupt`
|
|
126
|
+
|
|
127
|
+
> **`LangGraphInstrumentor` is a deprecated no-op** kept for backwards compatibility.
|
|
128
|
+
> You can remove any `LangGraphInstrumentor().instrument()` call — tracing happens
|
|
129
|
+
> through `LangChainInstrumentor`.
|
|
130
|
+
|
|
131
|
+
### LangGraph Quickstart
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from typing import Annotated, TypedDict
|
|
135
|
+
from fi_instrumentation import register
|
|
136
|
+
from fi_instrumentation.fi_types import ProjectType
|
|
137
|
+
from fi_instrumentation.instrumentation.context_attributes import using_session
|
|
138
|
+
from langgraph.graph import StateGraph, START, END
|
|
139
|
+
from langgraph.graph.message import add_messages
|
|
140
|
+
from traceai_langchain import LangChainInstrumentor
|
|
141
|
+
|
|
142
|
+
class MyState(TypedDict):
|
|
143
|
+
messages: Annotated[list, add_messages]
|
|
144
|
+
|
|
145
|
+
trace_provider = register(
|
|
146
|
+
project_type=ProjectType.OBSERVE,
|
|
147
|
+
project_name="langgraph_app",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Instrument LangChain — LangGraph is traced through it automatically.
|
|
151
|
+
LangChainInstrumentor().instrument(tracer_provider=trace_provider)
|
|
152
|
+
|
|
153
|
+
workflow = StateGraph(MyState)
|
|
154
|
+
workflow.add_node("agent", agent_node) # sync or async nodes both work
|
|
155
|
+
workflow.add_edge(START, "agent")
|
|
156
|
+
workflow.add_edge("agent", END)
|
|
157
|
+
app = workflow.compile()
|
|
158
|
+
|
|
159
|
+
# Group every span of a conversation under one session:
|
|
160
|
+
with using_session("thread-123"):
|
|
161
|
+
result = app.invoke(
|
|
162
|
+
{"messages": []}, {"configurable": {"thread_id": "thread-123"}}
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### LangGraph Span Attributes
|
|
167
|
+
|
|
168
|
+
Node, tool and LLM runs are standard LangChain spans (`gen_ai.span.kind` =
|
|
169
|
+
`CHAIN` / `TOOL` / `LLM` / `AGENT`) with these LangGraph additions:
|
|
170
|
+
|
|
171
|
+
- `gen_ai.agent.graph.node_name`, `gen_ai.agent.graph.node_id` — the graph node (on the node's own span)
|
|
172
|
+
- `langgraph_node`, `langgraph_step`, `langgraph_triggers`, `langgraph_path`, `langgraph_checkpoint_ns` — LangGraph's raw callback metadata
|
|
173
|
+
- `session.id` — from `config={"metadata": {"session_id": ...}}`, else `using_session(...)`, else LangGraph's `configurable.thread_id` (0.2.1 and later; a blank `thread_id` sets nothing). The choice is made per run: set an explicit session around the whole `invoke`/`stream`, because a session set for only part of a run (inside a node, or in an inner `with_config`) covers only the runs inside it, and the outer spans fall back to `thread_id`.
|
|
174
|
+
- `langgraph.interrupt` (attribute + event) on a HITL pause; a `langgraph.resume` event on resume
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Deep Agents
|
|
179
|
+
|
|
180
|
+
[Deep Agents](https://github.com/langchain-ai/deepagents) builds a LangGraph graph
|
|
181
|
+
with `create_deep_agent`. There is no Deep Agents instrumentor and no separate
|
|
182
|
+
package: `LangChainInstrumentor` traces the graph, its model calls, its built-in
|
|
183
|
+
tools and its `task` subagents. The cookbook is
|
|
184
|
+
[`examples/deep_agents.py`](examples/deep_agents.py); the compatibility test
|
|
185
|
+
(`tests/test_deepagents_compat.py`) runs that same script offline against a
|
|
186
|
+
scripted fake model and a loopback collector.
|
|
187
|
+
|
|
188
|
+
Tested versions, as printed by the compatibility test:
|
|
189
|
+
|
|
190
|
+
| Package | Version |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `deepagents` | 0.7.21 (PyPI wheel); 0.7.23 and 0.7.28 (latest on 2026-10-16) also pass |
|
|
193
|
+
| `langchain` | 1.4.3; 1.4.4 with deepagents 0.7.23 |
|
|
194
|
+
| `langchain-core` | 1.6.6 (the floor `deepagents` 0.7.21 requires); 1.6.9 and 1.6.12 (latest) also pass |
|
|
195
|
+
| `langgraph` | 1.2.12; 1.2.14 and 1.2.16 (latest) also pass |
|
|
196
|
+
| `langgraph-prebuilt` | as resolved with `langgraph` 1.2.12 (printed by the test), and 1.1.0 with `langgraph` 1.2.14; its `ToolNode` re-raises tool errors |
|
|
197
|
+
| `traceAI-langchain` | 0.2.1 (tested from this repository before release): the `thread_id` → `session.id` change is **not** in 0.2.0 |
|
|
198
|
+
| Python | 3.11.12 and 3.13.7; 3.13.15 with deepagents 0.7.28; 3.12 with deepagents 0.7.23 |
|
|
199
|
+
|
|
200
|
+
`traceAI-langchain`'s own lower bound (`langchain-core>=0.2.43`) is unchanged.
|
|
201
|
+
Deep Agents itself needs `langchain-core>=1.6.6` and `langgraph`.
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
pip install "traceAI-langchain>=0.2.1" "deepagents==0.7.21" langgraph
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
With `traceAI-langchain` 0.2.0 or earlier, `thread_id` is not copied to `session.id`: upgrade, or wrap the call in `using_session(thread_id)`.
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from deepagents import create_deep_agent
|
|
211
|
+
from deepagents.backends import StateBackend
|
|
212
|
+
from fi_instrumentation import TraceConfig, register
|
|
213
|
+
from fi_instrumentation.fi_types import ProjectType
|
|
214
|
+
from traceai_langchain import LangChainInstrumentor
|
|
215
|
+
|
|
216
|
+
# 1. register(), 2. instrument(), 3. build the agent.
|
|
217
|
+
trace_provider = register(project_type=ProjectType.OBSERVE, project_name="deep-agents-app")
|
|
218
|
+
LangChainInstrumentor().instrument(
|
|
219
|
+
tracer_provider=trace_provider,
|
|
220
|
+
# Filesystem tool arguments and results can contain file contents.
|
|
221
|
+
config=TraceConfig(hide_inputs=True, hide_outputs=True),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Pass a model explicitly: model=None selects the deprecated claude-sonnet-4-6 default.
|
|
225
|
+
agent = create_deep_agent(model=model, tools=[my_tool], backend=StateBackend())
|
|
226
|
+
result = agent.invoke(
|
|
227
|
+
{"messages": [{"role": "user", "content": "..."}]},
|
|
228
|
+
config={"configurable": {"thread_id": "t-1"}}, # becomes session.id (0.2.1+; with 0.2.0 use using_session)
|
|
229
|
+
)
|
|
230
|
+
trace_provider.force_flush()
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
What you get (all from `traceAI-langchain`; Deep Agents adds no keys):
|
|
234
|
+
|
|
235
|
+
- **LLM** spans with `gen_ai.request.model` and `gen_ai.usage.*` from the model's
|
|
236
|
+
`usage_metadata`. Token counts are only on LLM spans, so a trace's token total is
|
|
237
|
+
the sum of its model calls.
|
|
238
|
+
- **TOOL** spans named after the tool that ran. With the default `StateBackend`,
|
|
239
|
+
0.7.21 binds `ls`, `read_file`, `write_file`, `edit_file`, `delete`, `glob`,
|
|
240
|
+
`grep` and `task`, plus your tools. `execute` is bound only for a sandbox backend.
|
|
241
|
+
`write_todos` is not a default tool at this version; harness profiles can add tools
|
|
242
|
+
(the OpenAI Codex profile adds `write_todos` for `openai:gpt-5.x-codex` models).
|
|
243
|
+
- **Subagents**: the `task` TOOL span is the parent of the subagent's span (named
|
|
244
|
+
after the subagent, e.g. `general-purpose`, kind `CHAIN`) and of its model and
|
|
245
|
+
tool spans, all in one trace. Subagent spans also carry `lc_agent_name`.
|
|
246
|
+
- **Graph nodes** (`model`, `tools`, middleware nodes) are `CHAIN` spans; a node whose
|
|
247
|
+
name contains "agent" (e.g. `PatchToolCallsMiddleware.before_agent`) is reported as
|
|
248
|
+
`AGENT` by the existing name heuristic.
|
|
249
|
+
- **Session**: `configurable.thread_id` → `session.id` on every span, subagents included
|
|
250
|
+
(0.2.1 and later; see the session note above).
|
|
251
|
+
- **Errors**: a tool that raises ends its TOOL span `ERROR` with an `exception`
|
|
252
|
+
event (LangGraph's default tool error handler re-raises, so `invoke` raises too).
|
|
253
|
+
The status description is the exception message, without a traceback. Stacktraces
|
|
254
|
+
remain in exception events only when error details are visible. With core 1.2.0+,
|
|
255
|
+
`TraceConfig.hide_error_details` controls this: content-hide flags derive it by
|
|
256
|
+
default, and an explicit `False` keeps error details. With older core versions,
|
|
257
|
+
`hide_inputs=True` or `hide_outputs=True` also hides error details. Hidden failures
|
|
258
|
+
keep their exception type, but no status description, message, stacktrace or
|
|
259
|
+
provider-error output (body, response headers or request ids).
|
|
260
|
+
Cancelling an `astream` early closes every span; the root span ends `ERROR`
|
|
261
|
+
with a `GeneratorExit` exception event (asserted by the compatibility test).
|
|
262
|
+
- **CI**: the Deep Agents job installs the real `deepagents==0.7.21` wheel and runs
|
|
263
|
+
the compatibility, session and error-policy tests with the current core. It also
|
|
264
|
+
checks input/output privacy and sessions against published core 1.1.0.
|
|
265
|
+
|
|
266
|
+
Not emitted: cost, user id (set `using_attributes(user_id=...)` yourself),
|
|
267
|
+
retrieval (Deep Agents has no retriever tool), and a `gen_ai.provider.name`
|
|
268
|
+
(LangChain's raw `ls_provider` metadata is passed through as-is).
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Examples
|
|
273
|
+
|
|
274
|
+
### LangChain Examples
|
|
275
|
+
- `examples/chat_prompt_template.py` - Basic chat prompt usage
|
|
276
|
+
- `examples/rag.py` - Retrieval-augmented generation
|
|
277
|
+
- `examples/tool_calling_agent.py` - Agent with tools
|
|
278
|
+
- `examples/openai_chat_stream.py` - Streaming responses
|
|
279
|
+
|
|
280
|
+
### LangGraph Examples
|
|
281
|
+
- `examples/langgraph_simple_workflow.py` - Simple state machine workflow
|
|
282
|
+
- `examples/langgraph_agent_supervisor.py` - Multi-agent supervisor pattern
|
|
283
|
+
- `examples/langgraph_human_in_the_loop.py` - Human-in-the-loop interrupt workflow
|
|
284
|
+
|
|
285
|
+
### Deep Agents Example
|
|
286
|
+
- `examples/deep_agents.py` - `create_deep_agent` with a custom tool, built-in file tools and a `task` subagent (runs offline)
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## API Reference
|
|
291
|
+
|
|
292
|
+
### LangChainInstrumentor
|
|
293
|
+
|
|
294
|
+
```python
|
|
295
|
+
from traceai_langchain import LangChainInstrumentor
|
|
296
|
+
|
|
297
|
+
# Initialize and instrument
|
|
298
|
+
instrumentor = LangChainInstrumentor()
|
|
299
|
+
instrumentor.instrument(tracer_provider=trace_provider)
|
|
300
|
+
|
|
301
|
+
# Get current span
|
|
302
|
+
span = instrumentor.get_span(run_id)
|
|
303
|
+
|
|
304
|
+
# Get ancestor spans
|
|
305
|
+
ancestors = instrumentor.get_ancestors(run_id)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### LangGraphInstrumentor
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from traceai_langchain import LangGraphInstrumentor
|
|
312
|
+
|
|
313
|
+
# Deprecated no-op — LangGraph is traced automatically by LangChainInstrumentor.
|
|
314
|
+
# instrument() only logs a one-time deprecation notice; you can remove this call.
|
|
315
|
+
LangGraphInstrumentor().instrument(tracer_provider=trace_provider)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## Troubleshooting
|
|
321
|
+
|
|
322
|
+
### LangGraph not being traced
|
|
323
|
+
1. Make sure you instrumented **`LangChainInstrumentor`** — LangGraph is traced through it (`LangGraphInstrumentor` is a no-op).
|
|
324
|
+
2. Install the langgraph extra if you use LangGraph: `pip install traceAI-langchain[langgraph]`.
|
|
325
|
+
3. Check that `langgraph` is installed: `pip show langgraph`.
|
|
326
|
+
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# LangChain & LangGraph OpenTelemetry Integration
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
This integration provides comprehensive OpenTelemetry instrumentation for both LangChain and LangGraph frameworks. It enables detailed tracing and monitoring of applications built with these frameworks.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
### Install traceAI LangChain
|
|
9
|
+
```bash
|
|
10
|
+
pip install traceAI-langchain
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### For LangGraph support (optional)
|
|
14
|
+
```bash
|
|
15
|
+
pip install traceAI-langchain[langgraph]
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Install LangChain OpenAI
|
|
19
|
+
```bash
|
|
20
|
+
pip install langchain-openai
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Environment Variables
|
|
24
|
+
Set up your environment variables to authenticate with FutureAGI.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
os.environ["FI_API_KEY"] = FI_API_KEY
|
|
30
|
+
os.environ["FI_SECRET_KEY"] = FI_SECRET_KEY
|
|
31
|
+
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## LangChain Quickstart
|
|
35
|
+
|
|
36
|
+
### Register Tracer Provider
|
|
37
|
+
Set up the trace provider to establish the observability pipeline:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from fi_instrumentation import register
|
|
41
|
+
from fi_instrumentation.fi_types import ProjectType
|
|
42
|
+
|
|
43
|
+
trace_provider = register(
|
|
44
|
+
project_type=ProjectType.OBSERVE,
|
|
45
|
+
project_name="langchain_app",
|
|
46
|
+
session_name="chat-bot"
|
|
47
|
+
)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Configure LangChain Instrumentation
|
|
51
|
+
Instrument the LangChain client to enable telemetry collection:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from traceai_langchain import LangChainInstrumentor
|
|
55
|
+
|
|
56
|
+
LangChainInstrumentor().instrument(tracer_provider=trace_provider)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Create LangChain Components
|
|
60
|
+
Set up your LangChain client with built-in observability:
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from langchain_openai import ChatOpenAI
|
|
64
|
+
from langchain_core.prompts import ChatPromptTemplate
|
|
65
|
+
|
|
66
|
+
prompt = ChatPromptTemplate.from_template("{x} {y} {z}?").partial(x="why is", z="blue")
|
|
67
|
+
chain = prompt | ChatOpenAI(model_name="gpt-3.5-turbo")
|
|
68
|
+
|
|
69
|
+
def run_chain():
|
|
70
|
+
try:
|
|
71
|
+
result = chain.invoke({"y": "sky"})
|
|
72
|
+
print(f"Response: {result}")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"Error executing chain: {e}")
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
run_chain()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## LangGraph Instrumentation
|
|
83
|
+
|
|
84
|
+
LangGraph tracing is **automatic**: LangGraph runs each node, tool and LLM through
|
|
85
|
+
LangChain's callback system, so `LangChainInstrumentor` captures them with no
|
|
86
|
+
LangGraph-specific setup. You get:
|
|
87
|
+
|
|
88
|
+
- **Per-node spans** named after the graph node (`agent`, `tools`, `ask_human`, …)
|
|
89
|
+
- **Tool and LLM spans** nested under their node
|
|
90
|
+
- **Graph-node enrichment** — the node's own span carries `gen_ai.agent.graph.node_name` and `gen_ai.agent.graph.node_id`
|
|
91
|
+
- **Session grouping** — LangGraph's `configurable.thread_id` becomes `session.id` on every span (an explicit `using_session(...)` or `metadata={"session_id": ...}` wins). New in 0.2.1; with 0.2.0 or earlier, wrap the call in `using_session(thread_id)`.
|
|
92
|
+
- **HITL interrupts** traced correctly — an interrupted node/tool span stays `OK` (not `ERROR`) and is marked with `langgraph.interrupt`
|
|
93
|
+
|
|
94
|
+
> **`LangGraphInstrumentor` is a deprecated no-op** kept for backwards compatibility.
|
|
95
|
+
> You can remove any `LangGraphInstrumentor().instrument()` call — tracing happens
|
|
96
|
+
> through `LangChainInstrumentor`.
|
|
97
|
+
|
|
98
|
+
### LangGraph Quickstart
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from typing import Annotated, TypedDict
|
|
102
|
+
from fi_instrumentation import register
|
|
103
|
+
from fi_instrumentation.fi_types import ProjectType
|
|
104
|
+
from fi_instrumentation.instrumentation.context_attributes import using_session
|
|
105
|
+
from langgraph.graph import StateGraph, START, END
|
|
106
|
+
from langgraph.graph.message import add_messages
|
|
107
|
+
from traceai_langchain import LangChainInstrumentor
|
|
108
|
+
|
|
109
|
+
class MyState(TypedDict):
|
|
110
|
+
messages: Annotated[list, add_messages]
|
|
111
|
+
|
|
112
|
+
trace_provider = register(
|
|
113
|
+
project_type=ProjectType.OBSERVE,
|
|
114
|
+
project_name="langgraph_app",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Instrument LangChain — LangGraph is traced through it automatically.
|
|
118
|
+
LangChainInstrumentor().instrument(tracer_provider=trace_provider)
|
|
119
|
+
|
|
120
|
+
workflow = StateGraph(MyState)
|
|
121
|
+
workflow.add_node("agent", agent_node) # sync or async nodes both work
|
|
122
|
+
workflow.add_edge(START, "agent")
|
|
123
|
+
workflow.add_edge("agent", END)
|
|
124
|
+
app = workflow.compile()
|
|
125
|
+
|
|
126
|
+
# Group every span of a conversation under one session:
|
|
127
|
+
with using_session("thread-123"):
|
|
128
|
+
result = app.invoke(
|
|
129
|
+
{"messages": []}, {"configurable": {"thread_id": "thread-123"}}
|
|
130
|
+
)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### LangGraph Span Attributes
|
|
134
|
+
|
|
135
|
+
Node, tool and LLM runs are standard LangChain spans (`gen_ai.span.kind` =
|
|
136
|
+
`CHAIN` / `TOOL` / `LLM` / `AGENT`) with these LangGraph additions:
|
|
137
|
+
|
|
138
|
+
- `gen_ai.agent.graph.node_name`, `gen_ai.agent.graph.node_id` — the graph node (on the node's own span)
|
|
139
|
+
- `langgraph_node`, `langgraph_step`, `langgraph_triggers`, `langgraph_path`, `langgraph_checkpoint_ns` — LangGraph's raw callback metadata
|
|
140
|
+
- `session.id` — from `config={"metadata": {"session_id": ...}}`, else `using_session(...)`, else LangGraph's `configurable.thread_id` (0.2.1 and later; a blank `thread_id` sets nothing). The choice is made per run: set an explicit session around the whole `invoke`/`stream`, because a session set for only part of a run (inside a node, or in an inner `with_config`) covers only the runs inside it, and the outer spans fall back to `thread_id`.
|
|
141
|
+
- `langgraph.interrupt` (attribute + event) on a HITL pause; a `langgraph.resume` event on resume
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Deep Agents
|
|
146
|
+
|
|
147
|
+
[Deep Agents](https://github.com/langchain-ai/deepagents) builds a LangGraph graph
|
|
148
|
+
with `create_deep_agent`. There is no Deep Agents instrumentor and no separate
|
|
149
|
+
package: `LangChainInstrumentor` traces the graph, its model calls, its built-in
|
|
150
|
+
tools and its `task` subagents. The cookbook is
|
|
151
|
+
[`examples/deep_agents.py`](examples/deep_agents.py); the compatibility test
|
|
152
|
+
(`tests/test_deepagents_compat.py`) runs that same script offline against a
|
|
153
|
+
scripted fake model and a loopback collector.
|
|
154
|
+
|
|
155
|
+
Tested versions, as printed by the compatibility test:
|
|
156
|
+
|
|
157
|
+
| Package | Version |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `deepagents` | 0.7.21 (PyPI wheel); 0.7.23 and 0.7.28 (latest on 2026-10-16) also pass |
|
|
160
|
+
| `langchain` | 1.4.3; 1.4.4 with deepagents 0.7.23 |
|
|
161
|
+
| `langchain-core` | 1.6.6 (the floor `deepagents` 0.7.21 requires); 1.6.9 and 1.6.12 (latest) also pass |
|
|
162
|
+
| `langgraph` | 1.2.12; 1.2.14 and 1.2.16 (latest) also pass |
|
|
163
|
+
| `langgraph-prebuilt` | as resolved with `langgraph` 1.2.12 (printed by the test), and 1.1.0 with `langgraph` 1.2.14; its `ToolNode` re-raises tool errors |
|
|
164
|
+
| `traceAI-langchain` | 0.2.1 (tested from this repository before release): the `thread_id` → `session.id` change is **not** in 0.2.0 |
|
|
165
|
+
| Python | 3.11.12 and 3.13.7; 3.13.15 with deepagents 0.7.28; 3.12 with deepagents 0.7.23 |
|
|
166
|
+
|
|
167
|
+
`traceAI-langchain`'s own lower bound (`langchain-core>=0.2.43`) is unchanged.
|
|
168
|
+
Deep Agents itself needs `langchain-core>=1.6.6` and `langgraph`.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
pip install "traceAI-langchain>=0.2.1" "deepagents==0.7.21" langgraph
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
With `traceAI-langchain` 0.2.0 or earlier, `thread_id` is not copied to `session.id`: upgrade, or wrap the call in `using_session(thread_id)`.
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
from deepagents import create_deep_agent
|
|
178
|
+
from deepagents.backends import StateBackend
|
|
179
|
+
from fi_instrumentation import TraceConfig, register
|
|
180
|
+
from fi_instrumentation.fi_types import ProjectType
|
|
181
|
+
from traceai_langchain import LangChainInstrumentor
|
|
182
|
+
|
|
183
|
+
# 1. register(), 2. instrument(), 3. build the agent.
|
|
184
|
+
trace_provider = register(project_type=ProjectType.OBSERVE, project_name="deep-agents-app")
|
|
185
|
+
LangChainInstrumentor().instrument(
|
|
186
|
+
tracer_provider=trace_provider,
|
|
187
|
+
# Filesystem tool arguments and results can contain file contents.
|
|
188
|
+
config=TraceConfig(hide_inputs=True, hide_outputs=True),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Pass a model explicitly: model=None selects the deprecated claude-sonnet-4-6 default.
|
|
192
|
+
agent = create_deep_agent(model=model, tools=[my_tool], backend=StateBackend())
|
|
193
|
+
result = agent.invoke(
|
|
194
|
+
{"messages": [{"role": "user", "content": "..."}]},
|
|
195
|
+
config={"configurable": {"thread_id": "t-1"}}, # becomes session.id (0.2.1+; with 0.2.0 use using_session)
|
|
196
|
+
)
|
|
197
|
+
trace_provider.force_flush()
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
What you get (all from `traceAI-langchain`; Deep Agents adds no keys):
|
|
201
|
+
|
|
202
|
+
- **LLM** spans with `gen_ai.request.model` and `gen_ai.usage.*` from the model's
|
|
203
|
+
`usage_metadata`. Token counts are only on LLM spans, so a trace's token total is
|
|
204
|
+
the sum of its model calls.
|
|
205
|
+
- **TOOL** spans named after the tool that ran. With the default `StateBackend`,
|
|
206
|
+
0.7.21 binds `ls`, `read_file`, `write_file`, `edit_file`, `delete`, `glob`,
|
|
207
|
+
`grep` and `task`, plus your tools. `execute` is bound only for a sandbox backend.
|
|
208
|
+
`write_todos` is not a default tool at this version; harness profiles can add tools
|
|
209
|
+
(the OpenAI Codex profile adds `write_todos` for `openai:gpt-5.x-codex` models).
|
|
210
|
+
- **Subagents**: the `task` TOOL span is the parent of the subagent's span (named
|
|
211
|
+
after the subagent, e.g. `general-purpose`, kind `CHAIN`) and of its model and
|
|
212
|
+
tool spans, all in one trace. Subagent spans also carry `lc_agent_name`.
|
|
213
|
+
- **Graph nodes** (`model`, `tools`, middleware nodes) are `CHAIN` spans; a node whose
|
|
214
|
+
name contains "agent" (e.g. `PatchToolCallsMiddleware.before_agent`) is reported as
|
|
215
|
+
`AGENT` by the existing name heuristic.
|
|
216
|
+
- **Session**: `configurable.thread_id` → `session.id` on every span, subagents included
|
|
217
|
+
(0.2.1 and later; see the session note above).
|
|
218
|
+
- **Errors**: a tool that raises ends its TOOL span `ERROR` with an `exception`
|
|
219
|
+
event (LangGraph's default tool error handler re-raises, so `invoke` raises too).
|
|
220
|
+
The status description is the exception message, without a traceback. Stacktraces
|
|
221
|
+
remain in exception events only when error details are visible. With core 1.2.0+,
|
|
222
|
+
`TraceConfig.hide_error_details` controls this: content-hide flags derive it by
|
|
223
|
+
default, and an explicit `False` keeps error details. With older core versions,
|
|
224
|
+
`hide_inputs=True` or `hide_outputs=True` also hides error details. Hidden failures
|
|
225
|
+
keep their exception type, but no status description, message, stacktrace or
|
|
226
|
+
provider-error output (body, response headers or request ids).
|
|
227
|
+
Cancelling an `astream` early closes every span; the root span ends `ERROR`
|
|
228
|
+
with a `GeneratorExit` exception event (asserted by the compatibility test).
|
|
229
|
+
- **CI**: the Deep Agents job installs the real `deepagents==0.7.21` wheel and runs
|
|
230
|
+
the compatibility, session and error-policy tests with the current core. It also
|
|
231
|
+
checks input/output privacy and sessions against published core 1.1.0.
|
|
232
|
+
|
|
233
|
+
Not emitted: cost, user id (set `using_attributes(user_id=...)` yourself),
|
|
234
|
+
retrieval (Deep Agents has no retriever tool), and a `gen_ai.provider.name`
|
|
235
|
+
(LangChain's raw `ls_provider` metadata is passed through as-is).
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Examples
|
|
240
|
+
|
|
241
|
+
### LangChain Examples
|
|
242
|
+
- `examples/chat_prompt_template.py` - Basic chat prompt usage
|
|
243
|
+
- `examples/rag.py` - Retrieval-augmented generation
|
|
244
|
+
- `examples/tool_calling_agent.py` - Agent with tools
|
|
245
|
+
- `examples/openai_chat_stream.py` - Streaming responses
|
|
246
|
+
|
|
247
|
+
### LangGraph Examples
|
|
248
|
+
- `examples/langgraph_simple_workflow.py` - Simple state machine workflow
|
|
249
|
+
- `examples/langgraph_agent_supervisor.py` - Multi-agent supervisor pattern
|
|
250
|
+
- `examples/langgraph_human_in_the_loop.py` - Human-in-the-loop interrupt workflow
|
|
251
|
+
|
|
252
|
+
### Deep Agents Example
|
|
253
|
+
- `examples/deep_agents.py` - `create_deep_agent` with a custom tool, built-in file tools and a `task` subagent (runs offline)
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## API Reference
|
|
258
|
+
|
|
259
|
+
### LangChainInstrumentor
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from traceai_langchain import LangChainInstrumentor
|
|
263
|
+
|
|
264
|
+
# Initialize and instrument
|
|
265
|
+
instrumentor = LangChainInstrumentor()
|
|
266
|
+
instrumentor.instrument(tracer_provider=trace_provider)
|
|
267
|
+
|
|
268
|
+
# Get current span
|
|
269
|
+
span = instrumentor.get_span(run_id)
|
|
270
|
+
|
|
271
|
+
# Get ancestor spans
|
|
272
|
+
ancestors = instrumentor.get_ancestors(run_id)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### LangGraphInstrumentor
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
from traceai_langchain import LangGraphInstrumentor
|
|
279
|
+
|
|
280
|
+
# Deprecated no-op — LangGraph is traced automatically by LangChainInstrumentor.
|
|
281
|
+
# instrument() only logs a one-time deprecation notice; you can remove this call.
|
|
282
|
+
LangGraphInstrumentor().instrument(tracer_provider=trace_provider)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Troubleshooting
|
|
288
|
+
|
|
289
|
+
### LangGraph not being traced
|
|
290
|
+
1. Make sure you instrumented **`LangChainInstrumentor`** — LangGraph is traced through it (`LangGraphInstrumentor` is a no-op).
|
|
291
|
+
2. Install the langgraph extra if you use LangGraph: `pip install traceAI-langchain[langgraph]`.
|
|
292
|
+
3. Check that `langgraph` is installed: `pip show langgraph`.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "traceAI-langchain"
|
|
3
|
+
version = "0.2.2"
|
|
4
|
+
description = "OpenTelemetry instrumentation for LangChain"
|
|
5
|
+
authors = ["Future AGI <no-reply@futureagi.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
packages = [
|
|
9
|
+
{ include = "traceai_langchain" },
|
|
10
|
+
]
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
keywords = ["traceai", "futureagi", "opentelemetry", "tracing", "observability", "llm", "langchain"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: Apache Software License",
|
|
16
|
+
"Programming Language :: Python",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Framework :: OpenTelemetry",
|
|
24
|
+
"Framework :: OpenTelemetry :: Instrumentations",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Topic :: System :: Monitoring",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[tool.poetry.dependencies]
|
|
30
|
+
python = ">=3.10,<3.14"
|
|
31
|
+
fi-instrumentation-otel = ">=1.0.0"
|
|
32
|
+
langchain-core = ">=0.2.43"
|
|
33
|
+
langgraph = {version = ">=0.2.0", optional = true}
|
|
34
|
+
|
|
35
|
+
[tool.poetry.extras]
|
|
36
|
+
langgraph = ["langgraph"]
|
|
37
|
+
|
|
38
|
+
[tool.poetry.group.dev.dependencies]
|
|
39
|
+
pytest = "^8.0.0"
|
|
40
|
+
pytest-asyncio = "^0.23.0"
|
|
41
|
+
langgraph = ">=0.2.0"
|
|
42
|
+
packaging = ">=21.0"
|
|
43
|
+
|
|
44
|
+
[tool.poetry.urls]
|
|
45
|
+
Homepage = "https://futureagi.com"
|
|
46
|
+
Documentation = "https://docs.futureagi.com/docs/integrations/traceai/langchain"
|
|
47
|
+
Repository = "https://github.com/future-agi/traceAI/tree/dev/python/frameworks/langchain"
|
|
48
|
+
Changelog = "https://github.com/future-agi/traceAI/blob/dev/python/frameworks/langchain/CHANGELOG.md"
|
|
49
|
+
Issues = "https://github.com/future-agi/traceAI/issues"
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["poetry-core"]
|
|
52
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -70,6 +70,7 @@ class LangChainInstrumentor(BaseInstrumentor): # type: ignore
|
|
|
70
70
|
name="BaseCallbackManager.__init__",
|
|
71
71
|
wrapper=_BaseCallbackManagerInit(self._tracer),
|
|
72
72
|
)
|
|
73
|
+
self._protect_class = Protect
|
|
73
74
|
if Protect is not None:
|
|
74
75
|
self._original_protect = Protect.protect
|
|
75
76
|
wrap_function_wrapper(
|
|
@@ -83,6 +84,10 @@ class LangChainInstrumentor(BaseInstrumentor): # type: ignore
|
|
|
83
84
|
def _uninstrument(self, **kwargs: Any) -> None:
|
|
84
85
|
langchain_core.callbacks.BaseCallbackManager.__init__ = self._original_callback_manager_init # type: ignore
|
|
85
86
|
self._original_callback_manager_init = None # type: ignore
|
|
87
|
+
if self._protect_class is not None:
|
|
88
|
+
self._protect_class.protect = self._original_protect
|
|
89
|
+
self._original_protect = None
|
|
90
|
+
self._protect_class = None
|
|
86
91
|
self._tracer = None
|
|
87
92
|
self.fi_tracer = None
|
|
88
93
|
|