-
Notifications
You must be signed in to change notification settings - Fork 17
feat: Add Deep Agents integration #90
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rapids-bot
merged 41 commits into
NVIDIA:release/0.2
from
dagardner-nv:david-deepagents-int-p2
May 14, 2026
Merged
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
e1abda3
Add deepagents extra
dagardner-nv 92ca845
Import improvement
dagardner-nv d8912c9
WIP: First pass at deep agents integration
dagardner-nv 3ce8bc7
Use a nim model, remove unnecessary helper method
dagardner-nv 649e9f8
Rename helper method
dagardner-nv 2e2b9f0
Document constants, remove unneeded truncation
dagardner-nv 561eb93
Add protocol type hints
dagardner-nv 4d4435c
Cleanups to callbacks class, distinguish between hitl and other graph…
dagardner-nv 6a67371
Nitpicky formatting
dagardner-nv bc51450
Update attribs
dagardner-nv 601e771
Move the subscribed_events fixture to be shared
dagardner-nv 822bab7
WIP: [skip ci]
dagardner-nv 445bdff
Formatting [skip ci]
dagardner-nv aa3b66e
Fix linting errors
dagardner-nv a1eecc3
Add deepagents in ci
dagardner-nv e609300
Add return type hint per code rabbit suggestion
dagardner-nv bdbe385
Install dependencies in the CI check stage needed for ty tool
dagardner-nv 92e3908
Install OpenSSL via vcpkg for Windows-arm64
dagardner-nv 59e01c8
Merge branch 'main' of github.com:NVIDIA/NeMo-Flow into david-deepage…
dagardner-nv c6f0eff
Add more tests
dagardner-nv 9948131
Formatting
dagardner-nv 47b225e
Merge branch 'main' of github.com:NVIDIA/NeMo-Flow into david-deepage…
dagardner-nv 9b7e117
Apply suggestions from coderabbit
dagardner-nv b3aba0d
Merge branch 'main' of github.com:NVIDIA/NeMo-Flow into david-deepage…
dagardner-nv dd2e39c
Merge branch 'release/0.2' of github.com:NVIDIA/NeMo-Flow into david-…
dagardner-nv 05ac673
Record skills as tool scopes not marks
dagardner-nv e5faedb
Replace tool marks with scopes
dagardner-nv 5fe997f
Remove redundant backend.py
dagardner-nv cb3ef49
Remove overloads of wrap_tool_call and awrap_tool_call and therefore …
dagardner-nv 0606d75
WIP
dagardner-nv e175f04
Set to warning not debug, failing to pop due to a failure in _prepare…
dagardner-nv 9c49a65
Handle pydantic classes in _serialize_value
dagardner-nv 95bf464
Handle langgraph command and send types in prepare_outputs
dagardner-nv ab8a14b
Update lock
dagardner-nv cd2ec54
Remove unused methods
dagardner-nv 7016a3c
Formatting
dagardner-nv 869edaf
Expand e2e test to include a subagent
dagardner-nv 08870f7
Improve event validation
dagardner-nv 85577cc
Merge branch 'release/0.2' of github.com:NVIDIA/NeMo-Flow into david-…
dagardner-nv 342b367
Move documentation
dagardner-nv c478f91
Forgot to check in moved file
dagardner-nv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| <!-- | ||
| SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| SPDX-License-Identifier: Apache-2.0 | ||
| --> | ||
|
|
||
| # NeMo Flow Deep Agents Integration | ||
|
|
||
| Use the `nemo_flow.integrations.deepagents` package to add NeMo Flow | ||
| observability to Deep Agents applications through the LangChain and LangGraph | ||
| integration surfaces that Deep Agents builds on. | ||
|
|
||
| ## Setup | ||
|
|
||
| Install the Deep Agents integration extra in your application environment. | ||
|
|
||
| ::::{tab-set} | ||
| :sync-group: install-tool | ||
|
|
||
| :::{tab-item} uv | ||
| :selected: | ||
| :sync: uv | ||
|
|
||
| ```bash | ||
| uv add "nemo-flow[deepagents]" | ||
| ``` | ||
| ::: | ||
|
|
||
| :::{tab-item} pip | ||
| :sync: pip | ||
|
|
||
| ```bash | ||
| pip install "nemo-flow[deepagents]" | ||
| ``` | ||
| ::: | ||
|
|
||
| :::: | ||
|
|
||
| The example below uses the NVIDIA LangChain provider. Install that provider | ||
| extra too if you want to run the example as written: | ||
|
|
||
| ::::{tab-set} | ||
| :sync-group: install-tool | ||
|
|
||
| :::{tab-item} uv | ||
| :selected: | ||
| :sync: uv | ||
|
|
||
| ```bash | ||
| uv add "nemo-flow[deepagents,langchain-nvidia]" | ||
| ``` | ||
| ::: | ||
|
|
||
| :::{tab-item} pip | ||
| :sync: pip | ||
|
|
||
| ```bash | ||
| pip install "nemo-flow[deepagents,langchain-nvidia]" | ||
| ``` | ||
| ::: | ||
|
|
||
| :::: | ||
|
|
||
| ## Usage Example | ||
|
|
||
| ```python | ||
| import nemo_flow | ||
| from deepagents import create_deep_agent | ||
| from nemo_flow.integrations.deepagents import ( | ||
| NemoFlowDeepAgentsCallbackHandler, | ||
| add_nemo_flow_integration, | ||
| ) | ||
|
|
||
| agent = create_deep_agent( | ||
| **add_nemo_flow_integration( | ||
| model="nvidia:nvidia/nemotron-3-nano-30b-a3b", | ||
| tools=[], | ||
| skills=["/skills/research/"], | ||
| name="main-agent", | ||
| ) | ||
| ) | ||
|
|
||
| input_payload = { | ||
| "messages": [ | ||
| { | ||
| "role": "user", | ||
| "content": "Research recent GPU news", | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| with nemo_flow.scope.scope("deepagents-request", nemo_flow.ScopeType.Agent): | ||
| result = agent.invoke( | ||
| input_payload, | ||
| config={"callbacks": [NemoFlowDeepAgentsCallbackHandler()]}, | ||
| ) | ||
|
|
||
| final_message = result["messages"][-1] | ||
| print(f"Final response: {final_message.content}") | ||
| ``` | ||
|
|
||
| ## Observability | ||
|
|
||
| The integration composes the existing NeMo Flow LangChain and LangGraph hooks, | ||
| then emits Deep Agents-specific marks for configured skills, subagents, and | ||
| human-in-the-loop lifecycle events. | ||
|
|
||
| It captures: | ||
|
|
||
| - LangChain model and tool calls through NeMo Flow managed execution. | ||
| - LangGraph run scopes through callbacks. | ||
| - Human-in-the-loop interrupt and resume marks. | ||
| - Configured skills and subagent summaries at agent-run start. | ||
| - In-process dictionary-style subagents with the same NeMo Flow middleware, so | ||
| their model and tool calls are captured when Deep Agents invokes them. | ||
|
|
||
| Remote graphs or processes still need NeMo Flow instrumentation in that graph | ||
| or process to capture their internal model and tool calls. | ||
|
|
||
| Refer to [Export Observability Data](../../export-observability-data/about.md) | ||
| for details on exporting NeMo Flow observability data to third-party systems. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| """NeMo Flow integrations for Deep Agents.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from collections.abc import Mapping, Sequence | ||
| from typing import Any | ||
|
|
||
| from nemo_flow.integrations.deepagents.callbacks import NemoFlowDeepAgentsCallbackHandler | ||
| from nemo_flow.integrations.deepagents.middleware import NemoFlowDeepAgentsMiddleware | ||
|
|
||
|
|
||
| def add_nemo_flow_integration( | ||
| kwargs: Mapping[str, Any] | None = None, | ||
| *, | ||
| instrument_subagents: bool = True, | ||
| **overrides: Any, | ||
| ) -> dict[str, Any]: | ||
| """ | ||
| Receives the keyword arguments for ``create_deep_agent`` and returns them with NeMo Flow observability attached. | ||
|
|
||
| Use this helper as ``create_deep_agent(**add_nemo_flow_integration(...))``. | ||
| It injects Deep Agents-aware middleware at the top level, adds the same | ||
| middleware to dictionary-style custom subagents that do not inherit parent | ||
| middleware, and leaves any provided backend unchanged. | ||
| """ | ||
| observed = dict(kwargs or {}) | ||
| observed.update(overrides) | ||
|
|
||
| skills = _string_sequence(observed.get("skills")) | ||
| subagents = list(observed.get("subagents") or ()) | ||
| subagent_summaries = [_subagent_summary(subagent) for subagent in subagents] | ||
| backend = observed.get("backend") | ||
| backend_name = type(backend).__name__ if backend is not None else None | ||
|
|
||
| middleware = list(observed.get("middleware") or ()) | ||
| _append_middleware( | ||
| middleware, | ||
| NemoFlowDeepAgentsMiddleware( | ||
| agent_name=observed.get("name"), | ||
| skills=skills, | ||
| subagents=subagent_summaries, | ||
| backend_name=backend_name, | ||
| ), | ||
| ) | ||
| observed["middleware"] = middleware | ||
|
|
||
| if instrument_subagents and subagents: | ||
| observed["subagents"] = [_instrument_subagent(subagent) for subagent in subagents] | ||
|
|
||
| return observed | ||
|
|
||
|
|
||
| def _append_middleware(middleware: list[Any], new_middleware: NemoFlowDeepAgentsMiddleware) -> None: | ||
| if any(isinstance(item, NemoFlowDeepAgentsMiddleware) for item in middleware): | ||
| return | ||
| middleware.append(new_middleware) | ||
|
|
||
|
|
||
| def _instrument_subagent(subagent: Any) -> Any: | ||
| if not isinstance(subagent, dict): | ||
| return subagent | ||
|
|
||
| observed = dict(subagent) | ||
| middleware = list(observed.get("middleware") or ()) | ||
| _append_middleware( | ||
| middleware, | ||
| NemoFlowDeepAgentsMiddleware( | ||
| agent_name=observed.get("name"), | ||
| skills=_string_sequence(observed.get("skills")), | ||
| subagents=None, | ||
| ), | ||
| ) | ||
| observed["middleware"] = middleware | ||
| return observed | ||
|
|
||
|
|
||
| def _subagent_summary(subagent: Any) -> Mapping[str, Any]: | ||
| if isinstance(subagent, Mapping): | ||
| summary: dict[str, Any] = {"type": "subagent"} | ||
| for key in ("name", "description", "model", "graph_id", "url"): | ||
| value = subagent.get(key) | ||
| if value is not None: | ||
| summary[key] = value | ||
| if "skills" in subagent: | ||
| summary["skills"] = _string_sequence(subagent.get("skills")) | ||
| return summary | ||
|
|
||
| summary = {"type": type(subagent).__name__} | ||
| for attr in ("name", "description", "graph_id", "url"): | ||
| value = getattr(subagent, attr, None) | ||
| if value is not None: | ||
| summary[attr] = value | ||
| return summary | ||
|
|
||
|
|
||
| def _string_sequence(value: Any) -> Sequence[str] | None: | ||
| if value is None: | ||
| return None | ||
| if isinstance(value, str): | ||
| return [value] | ||
| if isinstance(value, Sequence): | ||
| return [str(item) for item in value] | ||
| return [str(value)] | ||
|
|
||
|
|
||
| __all__ = [ | ||
| "NemoFlowDeepAgentsCallbackHandler", | ||
| "NemoFlowDeepAgentsMiddleware", | ||
| "add_nemo_flow_integration", | ||
| ] |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.