Skip to content

Commit 8c70b09

Browse files
committed
fix: narrow DeerFlow observability integration
1 parent ba044f3 commit 8c70b09

55 files changed

Lines changed: 944 additions & 581 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

instrumentation-loongsuite/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
| [loongsuite-instrumentation-crewai](./loongsuite-instrumentation-crewai) | crewai >= 0.80.0 | No | development
1212
| [loongsuite-instrumentation-dashscope](./loongsuite-instrumentation-dashscope) | dashscope >= 1.0.0 | No | development
1313
| [loongsuite-instrumentation-deepagents](./loongsuite-instrumentation-deepagents) | deepagents >= 0.6.0, < 0.7.0 | No | development
14-
| [loongsuite-instrumentation-deerflow](./loongsuite-instrumentation-deerflow) | deerflow | No | development
14+
| [loongsuite-instrumentation-deerflow](./loongsuite-instrumentation-deerflow) | deerflow-harness >= 2, < 3 (official source distribution) | No | development
1515
| [loongsuite-instrumentation-dify](./loongsuite-instrumentation-dify) | dify | No | development
1616
| [loongsuite-instrumentation-google-adk](./loongsuite-instrumentation-google-adk) | google-adk >= 0.1.0 | No | development
1717
| [loongsuite-instrumentation-hermes-agent](./loongsuite-instrumentation-hermes-agent) | openai >= 1.0.0 | No | development
@@ -30,4 +30,4 @@
3030
| [loongsuite-instrumentation-vita](./loongsuite-instrumentation-vita) | vita >= 0.0.1 | No | development
3131
| [loongsuite-instrumentation-webarena](./loongsuite-instrumentation-webarena) | webarena >= 0.0.1 | No | development
3232
| [loongsuite-instrumentation-widesearch](./loongsuite-instrumentation-widesearch) | widesearch >= 0.1.0 | No | development
33-
| [loongsuite-instrumentation-wildtool](./loongsuite-instrumentation-wildtool) | openai >= 1.0.0 | No | development
33+
| [loongsuite-instrumentation-wildtool](./loongsuite-instrumentation-wildtool) | openai >= 1.0.0 | No | development

instrumentation-loongsuite/loongsuite-instrumentation-deepagents/CHANGELOG.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10-
### Changed
11-
12-
- Mark DeepAgents graphs with the explicit `deepagents` agent flavor so their
13-
`model` nodes remain the sole ReAct `STEP` boundary when multiple compatible
14-
instrumentors are enabled.
15-
1610
## Version 0.7.0 (2026-07-03)
1711

1812
### Added

instrumentation-loongsuite/loongsuite-instrumentation-deepagents/src/opentelemetry/instrumentation/deepagents/internal/patch.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616
1717
DeepAgents builds on ``langchain.agents.create_agent`` and returns
1818
``create_agent(...).with_config(...)``. That final ``with_config`` call creates
19-
a new graph object and drops the flavor that LangChain instrumentation places
20-
on the original graph. This module marks the returned graph as ``deepagents``
21-
and injects that flavor into call-time config.
19+
a new graph object and drops the marker that LangChain instrumentation places
20+
on the original graph. This module restores the marker on the returned graph
21+
and injects the marker into call-time config, mirroring LangGraph's lightweight
22+
metadata handoff.
2223
"""
2324

2425
from __future__ import annotations
@@ -39,8 +40,6 @@
3940

4041
CREATE_DEEP_AGENT_MODULE = "deepagents.graph"
4142
CREATE_DEEP_AGENT_NAME = "create_deep_agent"
42-
AGENT_FLAVOR_METADATA_KEY = "_loongsuite_agent_flavor"
43-
DEEPAGENTS_AGENT_FLAVOR = "deepagents"
4443
REACT_AGENT_METADATA_KEY = "_loongsuite_react_agent"
4544
DEEPAGENTS_METADATA_KEY = "_loongsuite_deepagents_agent"
4645
GRAPH_METHODS_WRAPPED_ATTR = "_loongsuite_deepagents_methods_wrapped"
@@ -182,7 +181,6 @@ def _restore_top_level_create_deep_agent() -> None:
182181

183182
def _mark_graph(graph: Any) -> None:
184183
with suppress(Exception):
185-
setattr(graph, AGENT_FLAVOR_METADATA_KEY, DEEPAGENTS_AGENT_FLAVOR)
186184
setattr(graph, REACT_AGENT_METADATA_KEY, True)
187185
setattr(graph, DEEPAGENTS_METADATA_KEY, True)
188186

@@ -243,7 +241,6 @@ def _restore_graph_methods(graph: Any) -> None:
243241
for attr_name in (
244242
GRAPH_ORIGINAL_METHODS_ATTR,
245243
GRAPH_METHODS_WRAPPED_ATTR,
246-
AGENT_FLAVOR_METADATA_KEY,
247244
REACT_AGENT_METADATA_KEY,
248245
DEEPAGENTS_METADATA_KEY,
249246
):
@@ -316,8 +313,6 @@ def _inject_react_metadata(config: Any) -> Any:
316313
config = ensure_config(config)
317314
config = {**config}
318315
metadata = dict(config.get("metadata") or {})
319-
metadata[AGENT_FLAVOR_METADATA_KEY] = DEEPAGENTS_AGENT_FLAVOR
320-
metadata.setdefault(REACT_AGENT_METADATA_KEY, True)
321316
metadata.setdefault(DEEPAGENTS_METADATA_KEY, True)
322317
config["metadata"] = metadata
323318
return config

instrumentation-loongsuite/loongsuite-instrumentation-deepagents/tests/test_deepagents_spans.py

Lines changed: 0 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@
2828
from langchain_core.outputs import ChatGeneration, ChatResult
2929
from langchain_core.tools import tool
3030

31-
from opentelemetry.instrumentation.langchain.internal._agent_flavor import (
32-
AGENT_FLAVOR_METADATA_KEY,
33-
DEEPAGENTS_AGENT_FLAVOR,
34-
LANGCHAIN_CREATE_AGENT_FLAVOR,
35-
)
36-
3731

3832
class _SequenceToolCallingModel(BaseChatModel):
3933
def __init__(self, responses: list[AIMessage]):
@@ -162,148 +156,6 @@ def _assert_kind_under_step(
162156
return matches
163157

164158

165-
def test_langchain_create_agent_model_node_creates_single_step(
166-
instrument, span_exporter
167-
):
168-
"""LangChain 1.x ``create_agent`` uses ``model``, not ``agent``, nodes."""
169-
from langchain.agents import create_agent # noqa: PLC0415
170-
171-
agent = create_agent(
172-
model=_SequenceToolCallingModel(
173-
[AIMessage(content="Plain LangChain final answer.")]
174-
),
175-
tools=[lookup_city],
176-
name="plain_langchain_agent",
177-
)
178-
179-
assert (
180-
getattr(agent, AGENT_FLAVOR_METADATA_KEY)
181-
== LANGCHAIN_CREATE_AGENT_FLAVOR
182-
)
183-
assert not hasattr(agent, "_loongsuite_deepagents_agent")
184-
185-
result = agent.invoke({"messages": [{"role": "user", "content": "hello"}]})
186-
187-
assert result["messages"][-1].content == "Plain LangChain final answer."
188-
spans = span_exporter.get_finished_spans()
189-
agent_span = _assert_single_agent(spans, "plain_langchain_agent")
190-
step_spans = _spans_by_kind(spans, "STEP")
191-
assert len(step_spans) == 1
192-
_assert_step_is_under_agent(agent_span, step_spans[0])
193-
194-
195-
def test_langchain_create_agent_tool_loop_creates_two_steps(
196-
instrument, span_exporter
197-
):
198-
from langchain.agents import create_agent # noqa: PLC0415
199-
200-
agent = create_agent(
201-
model=_SequenceToolCallingModel(
202-
[
203-
AIMessage(
204-
content="",
205-
tool_calls=[
206-
{
207-
"name": "lookup_city",
208-
"args": {"query": "where"},
209-
"id": "plain_call_1",
210-
}
211-
],
212-
),
213-
AIMessage(content="Plain answer: Hangzhou."),
214-
]
215-
),
216-
tools=[lookup_city],
217-
name="plain_langchain_tool_agent",
218-
)
219-
220-
result = agent.invoke({"messages": [{"role": "user", "content": "where"}]})
221-
222-
assert result["messages"][-1].content == "Plain answer: Hangzhou."
223-
spans = span_exporter.get_finished_spans()
224-
_assert_single_agent(spans, "plain_langchain_tool_agent")
225-
step_spans = sorted(
226-
_spans_by_kind(spans, "STEP"),
227-
key=lambda span: span.attributes.get("gen_ai.react.round", 0),
228-
)
229-
assert len(step_spans) == 2
230-
assert [
231-
span.attributes.get("gen_ai.react.finish_reason")
232-
for span in step_spans
233-
] == ["tool_calls", "stop"]
234-
235-
236-
def test_langchain_create_agent_stream_creates_single_step(
237-
instrument,
238-
span_exporter,
239-
):
240-
from langchain.agents import create_agent # noqa: PLC0415
241-
242-
agent = create_agent(
243-
model=_SequenceToolCallingModel(
244-
[AIMessage(content="Plain streamed answer.")]
245-
),
246-
tools=[lookup_city],
247-
name="plain_langchain_stream_agent",
248-
)
249-
250-
chunks = list(
251-
agent.stream({"messages": [{"role": "user", "content": "hello"}]})
252-
)
253-
254-
assert chunks
255-
spans = span_exporter.get_finished_spans()
256-
agent_span = _assert_single_agent(spans, "plain_langchain_stream_agent")
257-
step_spans = _spans_by_kind(spans, "STEP")
258-
assert len(step_spans) == 1
259-
_assert_step_is_under_agent(agent_span, step_spans[0])
260-
261-
262-
@pytest.mark.asyncio
263-
async def test_langchain_create_agent_async_tool_loop_creates_two_steps(
264-
instrument,
265-
span_exporter,
266-
):
267-
from langchain.agents import create_agent # noqa: PLC0415
268-
269-
agent = create_agent(
270-
model=_SequenceToolCallingModel(
271-
[
272-
AIMessage(
273-
content="",
274-
tool_calls=[
275-
{
276-
"name": "lookup_city",
277-
"args": {"query": "where"},
278-
"id": "plain_async_call_1",
279-
}
280-
],
281-
),
282-
AIMessage(content="Async answer: Hangzhou."),
283-
]
284-
),
285-
tools=[lookup_city],
286-
name="plain_langchain_async_agent",
287-
)
288-
289-
result = await agent.ainvoke(
290-
{"messages": [{"role": "user", "content": "where"}]}
291-
)
292-
293-
assert result["messages"][-1].content == "Async answer: Hangzhou."
294-
spans = span_exporter.get_finished_spans()
295-
_assert_single_agent(spans, "plain_langchain_async_agent")
296-
step_spans = sorted(
297-
_spans_by_kind(spans, "STEP"),
298-
key=lambda span: span.attributes.get("gen_ai.react.round", 0),
299-
)
300-
assert len(step_spans) == 2
301-
assert [
302-
span.attributes.get("gen_ai.react.finish_reason")
303-
for span in step_spans
304-
] == ["tool_calls", "stop"]
305-
306-
307159
def test_deepagents_root_span_is_agent_and_single_step(
308160
instrument, span_exporter
309161
):
@@ -314,7 +166,6 @@ def test_deepagents_root_span_is_agent_and_single_step(
314166

315167
assert getattr(agent, "_loongsuite_react_agent") is True
316168
assert getattr(agent, "_loongsuite_deepagents_agent") is True
317-
assert getattr(agent, AGENT_FLAVOR_METADATA_KEY) == DEEPAGENTS_AGENT_FLAVOR
318169

319170
result = agent.invoke({"messages": [{"role": "user", "content": "hello"}]})
320171

instrumentation-loongsuite/loongsuite-instrumentation-deepagents/tests/test_instrumentor.py

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
from opentelemetry.instrumentation.deepagents import DeepAgentsInstrumentor
2020
from opentelemetry.instrumentation.deepagents.internal.patch import (
21-
AGENT_FLAVOR_METADATA_KEY,
22-
DEEPAGENTS_AGENT_FLAVOR,
2321
DEEPAGENTS_METADATA_KEY,
2422
GRAPH_METHODS_WRAPPED_ATTR,
2523
GRAPH_ORIGINAL_METHODS_ATTR,
@@ -101,18 +99,10 @@ def test_uninstrument_restores_wrapped_graph_methods():
10199
_wrap_graph_methods(graph)
102100

103101
assert getattr(graph, GRAPH_METHODS_WRAPPED_ATTR) is True
104-
assert (
105-
getattr(graph, AGENT_FLAVOR_METADATA_KEY)
106-
== DEEPAGENTS_AGENT_FLAVOR
107-
)
108102
assert getattr(graph, REACT_AGENT_METADATA_KEY) is True
109103
assert getattr(graph, DEEPAGENTS_METADATA_KEY) is True
110104

111105
invoke_config = graph.invoke("hello")
112-
assert (
113-
invoke_config["metadata"][AGENT_FLAVOR_METADATA_KEY]
114-
== DEEPAGENTS_AGENT_FLAVOR
115-
)
116106
assert invoke_config["metadata"][DEEPAGENTS_METADATA_KEY] is True
117107

118108
stream_iter = graph.stream("hello")
@@ -124,29 +114,19 @@ def test_uninstrument_restores_wrapped_graph_methods():
124114

125115
child_graph = graph.with_config({"metadata": {"customer": "kept"}})
126116
assert getattr(child_graph, GRAPH_METHODS_WRAPPED_ATTR) is True
127-
assert (
128-
getattr(child_graph, AGENT_FLAVOR_METADATA_KEY)
129-
== DEEPAGENTS_AGENT_FLAVOR
130-
)
131117
assert getattr(child_graph, DEEPAGENTS_METADATA_KEY) is True
132118
child_config = child_graph.invoke("hello")
133119
assert child_config["metadata"]["customer"] == "kept"
134-
assert (
135-
child_config["metadata"][AGENT_FLAVOR_METADATA_KEY]
136-
== DEEPAGENTS_AGENT_FLAVOR
137-
)
138120
assert child_config["metadata"][DEEPAGENTS_METADATA_KEY] is True
139121

140122
uninstrument_create_deep_agent()
141123

142124
assert not hasattr(graph, GRAPH_METHODS_WRAPPED_ATTR)
143125
assert not hasattr(graph, GRAPH_ORIGINAL_METHODS_ATTR)
144-
assert not hasattr(graph, AGENT_FLAVOR_METADATA_KEY)
145126
assert not hasattr(graph, REACT_AGENT_METADATA_KEY)
146127
assert not hasattr(graph, DEEPAGENTS_METADATA_KEY)
147128
assert not hasattr(child_graph, GRAPH_METHODS_WRAPPED_ATTR)
148129
assert not hasattr(child_graph, GRAPH_ORIGINAL_METHODS_ATTR)
149-
assert not hasattr(child_graph, AGENT_FLAVOR_METADATA_KEY)
150130
assert not hasattr(child_graph, REACT_AGENT_METADATA_KEY)
151131
assert not hasattr(child_graph, DEEPAGENTS_METADATA_KEY)
152132
assert graph.invoke("hello") is None

instrumentation-loongsuite/loongsuite-instrumentation-deerflow/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- Treat user cancellation, early stream closure, and DeerFlow interrupted runs
1313
as non-error entry completions while retaining their interrupted status.
1414
- Clarify the initial sandbox, skill, and memory observability boundaries.
15+
- Document verified local, daemon, Docker, and embedded integration paths,
16+
including DeerFlow's exact `uv sync` behavior.
17+
- Keep DeerFlow trace-correlation helper failures from interrupting embedded
18+
client streams.
1519

1620
## Version 0.8.0 (2026-07-14)
1721

0 commit comments

Comments
 (0)