99
1010import logging
1111import sys
12- from collections .abc import AsyncIterator , Iterator
12+ from collections .abc import AsyncIterator , Iterator , Mapping
1313from contextlib import suppress
1414from importlib import import_module
1515from typing import Any , Callable
2222from opentelemetry .util .genai .types import Error
2323
2424from ._attributes import (
25+ BUILD_TASK_TOOL_MODULE ,
26+ BUILD_TASK_TOOL_NAME ,
2527 CREATE_DEEP_AGENT_MODULE ,
2628 CREATE_DEEP_AGENT_NAME ,
2729 FRAMEWORK_NAME ,
3739 GRAPH_REGISTRY_ATTR ,
3840 GRAPH_VERSION_ATTR ,
3941 LANGGRAPH_REACT_AGENT_METADATA_KEY ,
42+ METADATA_LS_AGENT_TYPE ,
43+ METADATA_SUBAGENT_DESCRIPTION ,
4044 SPAN_KIND_ENTRY ,
45+ SUBAGENT_TYPE ,
4146)
4247from ._utils import (
4348 active_span_is_entry_or_agent ,
4449 config_from_call ,
50+ config_with_langgraph_react_metadata ,
4551 create_graph_metadata ,
4652 detect_deepagents_version ,
4753 entry_attributes ,
6571_MISSING = object ()
6672_top_level_original : Any = _MISSING
6773_top_level_patched = False
74+ _is_subagent_task_patched = False
6875
6976
7077def instrument_entry_patch (handler : ExtendedTelemetryHandler ) -> None :
@@ -94,6 +101,7 @@ def instrument_entry_patch(handler: ExtendedTelemetryHandler) -> None:
94101 )
95102 return
96103 _sync_top_level_create_deep_agent ()
104+ _instrument_subagent_task_tool ()
97105 _is_entry_patched = True
98106
99107
@@ -111,6 +119,7 @@ def uninstrument_entry_patch() -> None:
111119 with suppress (Exception ):
112120 module = import_module (CREATE_DEEP_AGENT_MODULE )
113121 unwrap (module , CREATE_DEEP_AGENT_NAME )
122+ _uninstrument_subagent_task_tool ()
114123 _restore_top_level_create_deep_agent ()
115124 _is_entry_patched = False
116125
@@ -175,6 +184,134 @@ def _create_deep_agent_wrapper(
175184 return graph
176185
177186
187+ def _instrument_subagent_task_tool () -> None :
188+ """Patch deepagents subagent task construction to mark nested graphs."""
189+ global _is_subagent_task_patched # noqa: PLW0603
190+ if _is_subagent_task_patched :
191+ return
192+ try :
193+ wrap_function_wrapper (
194+ BUILD_TASK_TOOL_MODULE ,
195+ BUILD_TASK_TOOL_NAME ,
196+ _build_task_tool_wrapper ,
197+ )
198+ except ModuleNotFoundError as exc :
199+ if exc .name == "deepagents" or exc .name == BUILD_TASK_TOOL_MODULE :
200+ _logger .debug (
201+ "deepagents subagent middleware is not installed; "
202+ "SubAgent task patch skipped."
203+ )
204+ return
205+ raise
206+ except AttributeError :
207+ _logger .debug (
208+ "%s.%s not found; SubAgent task patch skipped." ,
209+ BUILD_TASK_TOOL_MODULE ,
210+ BUILD_TASK_TOOL_NAME ,
211+ )
212+ return
213+ _is_subagent_task_patched = True
214+
215+
216+ def _uninstrument_subagent_task_tool () -> None :
217+ global _is_subagent_task_patched # noqa: PLW0603
218+ if not _is_subagent_task_patched :
219+ return
220+ with suppress (Exception ):
221+ module = import_module (BUILD_TASK_TOOL_MODULE )
222+ unwrap (module , BUILD_TASK_TOOL_NAME )
223+ _is_subagent_task_patched = False
224+
225+
226+ def _build_task_tool_wrapper (
227+ wrapped : Callable [..., Any ],
228+ _instance : Any ,
229+ args : tuple [Any , ...],
230+ kwargs : dict [str , Any ],
231+ ) -> Any :
232+ _mark_subagent_specs (_subagent_specs_from_call (args , kwargs ))
233+ return wrapped (* args , ** kwargs )
234+
235+
236+ def _subagent_specs_from_call (
237+ args : tuple [Any , ...],
238+ kwargs : Mapping [str , Any ],
239+ ) -> Any :
240+ if args :
241+ return args [0 ]
242+ return kwargs .get ("subagents" )
243+
244+
245+ def _mark_subagent_specs (subagents : Any ) -> None :
246+ for spec in subagents or ():
247+ try :
248+ name = spec .get ("name" ) if isinstance (spec , dict ) else None
249+ description = (
250+ spec .get ("description" ) if isinstance (spec , dict ) else None
251+ )
252+ runnable = spec .get ("runnable" ) if isinstance (spec , dict ) else None
253+ if not name or runnable is None :
254+ continue
255+ spec ["runnable" ] = _mark_subagent_runnable (
256+ runnable ,
257+ name = str (name ),
258+ description = str (description ) if description else None ,
259+ )
260+ except Exception : # noqa: BLE001
261+ _logger .debug ("Failed to mark deepagents SubAgent graph" , exc_info = True )
262+
263+
264+ def _mark_subagent_runnable (
265+ runnable : Any ,
266+ * ,
267+ name : str ,
268+ description : str | None ,
269+ ) -> Any :
270+ metadata = create_graph_metadata (runnable , name = name )
271+ metadata .setdefault (METADATA_LS_AGENT_TYPE , SUBAGENT_TYPE )
272+ metadata .setdefault (LANGGRAPH_REACT_AGENT_METADATA_KEY , True )
273+ if description :
274+ metadata .setdefault (METADATA_SUBAGENT_DESCRIPTION , description )
275+
276+ registry = {name : description } if description else {}
277+ _mark_graph (runnable , metadata , registry )
278+ proxy = _SubagentRunnableProxy (runnable , metadata )
279+ _mark_graph (proxy , metadata , registry )
280+ return proxy
281+
282+
283+ class _SubagentRunnableProxy :
284+ """Proxy that injects deepagents metadata before nested SubAgent calls."""
285+
286+ __slots__ = ("_metadata" , "_runnable" )
287+
288+ def __init__ (self , runnable : Any , metadata : Mapping [str , Any ]) -> None :
289+ self ._runnable = runnable
290+ self ._metadata = dict (metadata )
291+
292+ def __getattr__ (self , name : str ) -> Any :
293+ return getattr (self ._runnable , name )
294+
295+ def invoke (self , value : Any , config : Any = None , ** kwargs : Any ) -> Any :
296+ return self ._runnable .invoke (
297+ value ,
298+ config_with_langgraph_react_metadata (config , self ._metadata ),
299+ ** kwargs ,
300+ )
301+
302+ async def ainvoke (
303+ self ,
304+ value : Any ,
305+ config : Any = None ,
306+ ** kwargs : Any ,
307+ ) -> Any :
308+ return await self ._runnable .ainvoke (
309+ value ,
310+ config_with_langgraph_react_metadata (config , self ._metadata ),
311+ ** kwargs ,
312+ )
313+
314+
178315def _mark_graph (
179316 graph : Any ,
180317 metadata : dict [str , Any ],
@@ -353,7 +490,7 @@ def _call_sync_with_entry(
353490 invocation , token = _start_entry (
354491 graph , method_name , metadata , registry , args , kwargs
355492 )
356- args , kwargs = inject_langgraph_react_metadata (args , kwargs )
493+ args , kwargs = inject_langgraph_react_metadata (args , kwargs , metadata )
357494 try :
358495 result = original (* args , ** kwargs )
359496 except Exception as exc :
@@ -375,7 +512,7 @@ async def _call_async_with_entry(
375512 invocation , token = _start_entry (
376513 graph , method_name , metadata , registry , args , kwargs
377514 )
378- args , kwargs = inject_langgraph_react_metadata (args , kwargs )
515+ args , kwargs = inject_langgraph_react_metadata (args , kwargs , metadata )
379516 try :
380517 result = await original (* args , ** kwargs )
381518 except Exception as exc :
@@ -397,7 +534,7 @@ def _call_stream_with_entry(
397534 invocation , token = _start_entry (
398535 graph , method_name , metadata , registry , args , kwargs
399536 )
400- args , kwargs = inject_langgraph_react_metadata (args , kwargs )
537+ args , kwargs = inject_langgraph_react_metadata (args , kwargs , metadata )
401538 last_chunk = None
402539 try :
403540 for chunk in original (* args , ** kwargs ):
@@ -421,7 +558,7 @@ async def _call_astream_with_entry(
421558 invocation , token = _start_entry (
422559 graph , method_name , metadata , registry , args , kwargs
423560 )
424- args , kwargs = inject_langgraph_react_metadata (args , kwargs )
561+ args , kwargs = inject_langgraph_react_metadata (args , kwargs , metadata )
425562 last_chunk = None
426563 try :
427564 async for chunk in original (* args , ** kwargs ):
0 commit comments