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.
@@ -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