Skip to content

Commit f0ecbde

Browse files
committed
feat: add uipath_langchain.agent.deep with optional deepagents extra
1 parent 87852f5 commit f0ecbde

11 files changed

Lines changed: 481 additions & 3 deletions

File tree

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.10.11"
3+
version = "0.10.12"
44
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"
@@ -53,8 +53,12 @@ bedrock = [
5353
fireworks = [
5454
"uipath-langchain-client[fireworks]>=1.10.0,<1.11.0",
5555
]
56+
deep = [
57+
"deepagents==0.4.11",
58+
]
5659
all = [
5760
"uipath-langchain-client[all]>=1.10.0,<1.11.0",
61+
"deepagents==0.4.11",
5862
]
5963

6064
[project.entry-points."uipath.middlewares"]
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Advanced (deep) agent support, built on the optional `deepagents` package.
2+
3+
Install the optional extra to use this module:
4+
5+
pip install 'uipath-langchain[deep]'
6+
7+
The `deepagents` types re-exported here (``SubAgent``, ``CompiledSubAgent``,
8+
``BackendProtocol``, ``BackendFactory``) are loaded lazily so importing this
9+
package without the extra installed does not crash — only attribute access
10+
will raise ``ImportError`` with the install hint.
11+
"""
12+
13+
from .agent import create_advanced_agent, create_advanced_agent_graph
14+
from .prompts import get_advanced_agent_meta_prompt
15+
from .types import AdvancedAgentGraphState
16+
from .utils import create_state_with_input
17+
18+
_INSTALL_HINT = (
19+
"deepagents is required for advanced agents. "
20+
"Install with: pip install 'uipath-langchain[deep]'"
21+
)
22+
23+
24+
def __getattr__(name: str):
25+
if name in ("SubAgent", "CompiledSubAgent"):
26+
try:
27+
import deepagents
28+
29+
return getattr(deepagents, name)
30+
except ImportError as exc:
31+
raise ImportError(_INSTALL_HINT) from exc
32+
if name == "BackendProtocol":
33+
try:
34+
from deepagents.backends import BackendProtocol
35+
36+
return BackendProtocol
37+
except ImportError as exc:
38+
raise ImportError(_INSTALL_HINT) from exc
39+
if name == "BackendFactory":
40+
try:
41+
from deepagents.backends.protocol import BackendFactory
42+
43+
return BackendFactory
44+
except ImportError as exc:
45+
raise ImportError(_INSTALL_HINT) from exc
46+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
47+
48+
49+
__all__ = [
50+
"AdvancedAgentGraphState",
51+
"BackendFactory",
52+
"BackendProtocol",
53+
"CompiledSubAgent",
54+
"SubAgent",
55+
"create_advanced_agent",
56+
"create_advanced_agent_graph",
57+
"create_state_with_input",
58+
"get_advanced_agent_meta_prompt",
59+
]
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
"""Advanced (deep) agent builder.
2+
3+
Thin UiPath wrapper around the `deepagents` library. The deepagents dependency
4+
is optional — install with `pip install 'uipath-langchain[deep]'` to use this
5+
module.
6+
"""
7+
8+
from collections.abc import Callable, Sequence
9+
from typing import TYPE_CHECKING, Any
10+
11+
from langchain.agents.structured_output import ResponseFormat
12+
from langchain_core.language_models import BaseChatModel
13+
from langchain_core.messages import HumanMessage
14+
from langchain_core.tools import BaseTool
15+
from langgraph.graph import END, START
16+
from langgraph.graph.state import CompiledStateGraph, StateGraph
17+
from pydantic import BaseModel
18+
19+
from .types import AdvancedAgentGraphState
20+
from .utils import create_state_with_input
21+
22+
if TYPE_CHECKING:
23+
from deepagents import CompiledSubAgent, SubAgent
24+
from deepagents.backends import BackendProtocol
25+
from deepagents.backends.protocol import BackendFactory
26+
27+
28+
_INSTALL_HINT = (
29+
"deepagents is required for advanced agents. "
30+
"Install with: pip install 'uipath-langchain[deep]'"
31+
)
32+
33+
34+
def _import_deepagents() -> Any:
35+
try:
36+
import deepagents # noqa: F401
37+
from deepagents import create_deep_agent
38+
39+
return create_deep_agent
40+
except ImportError as exc:
41+
raise ImportError(_INSTALL_HINT) from exc
42+
43+
44+
def create_advanced_agent(
45+
model: BaseChatModel,
46+
system_prompt: str = "",
47+
tools: Sequence[BaseTool] = (),
48+
subagents: "Sequence[SubAgent | CompiledSubAgent]" = (),
49+
backend: "BackendProtocol | BackendFactory | None" = None,
50+
response_format: ResponseFormat[Any] | None = None,
51+
) -> CompiledStateGraph[Any, Any, Any, Any]:
52+
"""Create an advanced agent.
53+
54+
Advanced agents provide built-in capabilities for:
55+
- Planning (write_todos, read_todos)
56+
- Filesystem operations (read_file, write_file, edit_file, ls, glob, grep)
57+
- Sub-agent delegation (task)
58+
- Auto-summarization for long conversations
59+
60+
Args:
61+
model: A BaseChatModel instance.
62+
system_prompt: Instructions for the agent.
63+
tools: Custom tools to provide to the agent.
64+
subagents: Optional list of subagent configurations. Each entry is a
65+
``SubAgent`` (name, description, system_prompt, and optional tools/model/middleware)
66+
or a ``CompiledSubAgent`` (name, description, and a pre-built runnable).
67+
backend: Storage backend for filesystem operations. Can be a
68+
``BackendProtocol`` instance, a factory callable, or ``None``
69+
(uses the default in-state backend).
70+
response_format: Structured output format for the agent response.
71+
72+
Returns:
73+
Compiled LangGraph agent ready for execution.
74+
75+
Raises:
76+
ImportError: If the ``deepagents`` package is not installed. Install
77+
with ``pip install 'uipath-langchain[deep]'``.
78+
"""
79+
create_deep_agent = _import_deepagents()
80+
return create_deep_agent(
81+
model=model,
82+
system_prompt=system_prompt,
83+
tools=list(tools),
84+
subagents=list(subagents),
85+
backend=backend,
86+
response_format=response_format,
87+
)
88+
89+
90+
def create_advanced_agent_graph(
91+
model: BaseChatModel,
92+
tools: Sequence[BaseTool],
93+
system_prompt: str,
94+
backend: "BackendProtocol | BackendFactory | None",
95+
response_format: ResponseFormat[Any] | None,
96+
input_schema: type[BaseModel] | None,
97+
output_schema: type[BaseModel],
98+
build_user_message: Callable[[dict[str, Any]], str],
99+
) -> StateGraph[Any, Any, Any, Any]:
100+
"""Build an advanced agent wrapped in a parent graph that handles I/O transformation.
101+
102+
The advanced agent only understands messages as input and produces
103+
structured_response as output. The wrapper graph bridges the gap:
104+
105+
START -> transform_input -> advanced_agent -> transform_output -> END
106+
107+
Args:
108+
model: Chat model for the advanced agent.
109+
tools: Tools available to the advanced agent.
110+
system_prompt: Combined system + meta prompt.
111+
backend: Filesystem backend for the advanced agent.
112+
response_format: Structured output format.
113+
input_schema: Resolved input Pydantic model (or None).
114+
output_schema: Resolved output Pydantic model.
115+
build_user_message: Callable that converts input arguments dict to a user message string.
116+
117+
Raises:
118+
ImportError: If the ``deepagents`` package is not installed. Install
119+
with ``pip install 'uipath-langchain[deep]'``.
120+
"""
121+
inner_graph = create_advanced_agent(
122+
model=model,
123+
tools=tools,
124+
system_prompt=system_prompt,
125+
backend=backend,
126+
response_format=response_format,
127+
)
128+
129+
wrapper_state = create_state_with_input(input_schema)
130+
131+
internal_fields = set(AdvancedAgentGraphState.model_fields.keys())
132+
133+
def transform_input(state: BaseModel) -> dict[str, Any]:
134+
state_data = state.model_dump()
135+
input_data = {k: v for k, v in state_data.items() if k not in internal_fields}
136+
input_args = (
137+
input_schema.model_validate(input_data).model_dump()
138+
if input_schema is not None
139+
else {}
140+
)
141+
user_text = build_user_message(input_args)
142+
return {"messages": [HumanMessage(content=user_text, id="user-input")]}
143+
144+
def transform_output(state: BaseModel) -> dict[str, Any]:
145+
structured = getattr(state, "structured_response", {})
146+
return output_schema.model_validate(structured).model_dump()
147+
148+
wrapper: StateGraph[Any, Any, Any, Any] = StateGraph(
149+
wrapper_state, input_schema=input_schema, output_schema=output_schema
150+
)
151+
wrapper.add_node("transform_input", transform_input)
152+
wrapper.add_node("advanced_agent", inner_graph)
153+
wrapper.add_node("transform_output", transform_output)
154+
wrapper.add_edge(START, "transform_input")
155+
wrapper.add_edge("transform_input", "advanced_agent")
156+
wrapper.add_edge("advanced_agent", "transform_output")
157+
wrapper.add_edge("transform_output", END)
158+
159+
return wrapper
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Meta prompt for advanced agent capabilities.
2+
3+
Describes what tools an advanced agent has access to (todos, subagents, filesystem)
4+
so the model knows how to leverage them. Workflow-specific goals live in agent.json.
5+
"""
6+
7+
_ADVANCED_AGENT_META_PROMPT_TEMPLATE = """\
8+
You are an advanced agent with powerful capabilities that standard agents lack. \
9+
Use them to produce high-quality output efficiently. Break work into parts, delegate, persist, and assemble.
10+
11+
**Planning — `write_todos` / `read_todos`**
12+
Start by creating a todo list with `write_todos`. Each todo should be a concrete, completable task. \
13+
Use `read_todos` to check progress and stay on track. Mark todos done as you complete them. \
14+
Never skip planning.
15+
16+
**Delegation — `task` tool**
17+
You have subagents. Delegate independent, self-contained tasks using the `task` tool. Each subagent \
18+
runs independently with its own context and tools. Give subagents specific, detailed \
19+
instructions including all necessary context, background information, and expected output \
20+
format — they cannot see your conversation history or files. \
21+
If work needs context from other subagents (like synthesis or final assembly), do it yourself. \
22+
Launch multiple subagents in parallel when tasks are independent.
23+
24+
**Filesystem — `write_file` / `edit_file` / `read_file`**
25+
You have a virtual filesystem (root: `/`). Persist work to files — save results from each \
26+
task to a dedicated file (e.g., `/task-01.md`). \
27+
Use `edit_file` to update or append to existing files as work progresses."""
28+
29+
30+
def get_advanced_agent_meta_prompt() -> str:
31+
"""Return the advanced agent capabilities meta prompt."""
32+
return _ADVANCED_AGENT_META_PROMPT_TEMPLATE
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""State types for the advanced (deep) agent wrapper graph."""
2+
3+
from typing import Annotated, Any
4+
5+
from langchain_core.messages import AnyMessage
6+
from langgraph.graph.message import add_messages
7+
from pydantic import BaseModel
8+
9+
10+
class AdvancedAgentGraphState(BaseModel):
11+
"""Graph state for the advanced agent wrapper."""
12+
13+
messages: Annotated[list[AnyMessage], add_messages] = []
14+
structured_response: dict[str, Any] = {}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Utilities for the advanced (deep) agent wrapper graph."""
2+
3+
from typing import cast
4+
5+
from pydantic import BaseModel
6+
7+
from .types import AdvancedAgentGraphState
8+
9+
10+
def create_state_with_input(
11+
input_schema: type[BaseModel] | None,
12+
) -> type[AdvancedAgentGraphState]:
13+
"""Create combined state by merging AdvancedAgentGraphState with the input schema.
14+
15+
Mirrors the shallow agent's create_state_with_input pattern:
16+
dynamic multi-inheritance + model_rebuild() for Pydantic resolution.
17+
"""
18+
if input_schema is None:
19+
return AdvancedAgentGraphState
20+
CompleteState = type(
21+
"CompleteAdvancedAgentGraphState",
22+
(AdvancedAgentGraphState, input_schema),
23+
{},
24+
)
25+
cast(type[BaseModel], CompleteState).model_rebuild()
26+
return CompleteState

tests/agent/deep/__init__.py

Whitespace-only changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""Smoke test for create_advanced_agent.
2+
3+
Verifies create_advanced_agent forwards its arguments to deepagents.create_deep_agent.
4+
We don't exercise the deepagents internals (those are tested by the deepagents
5+
package itself); we only validate UiPath's pass-through.
6+
"""
7+
8+
from unittest.mock import MagicMock, patch
9+
10+
import pytest
11+
12+
pytest.importorskip("deepagents")
13+
14+
from uipath_langchain.agent.deep import create_advanced_agent # noqa: E402
15+
16+
17+
def test_create_advanced_agent_forwards_to_deepagents() -> None:
18+
sentinel_graph = MagicMock(name="compiled_deep_agent")
19+
fake_create_deep_agent = MagicMock(return_value=sentinel_graph)
20+
model = MagicMock()
21+
22+
with patch(
23+
"uipath_langchain.agent.deep.agent._import_deepagents",
24+
return_value=fake_create_deep_agent,
25+
):
26+
graph = create_advanced_agent(
27+
model=model, system_prompt="sys", tools=[], subagents=[]
28+
)
29+
30+
assert graph is sentinel_graph
31+
fake_create_deep_agent.assert_called_once()
32+
kwargs = fake_create_deep_agent.call_args.kwargs
33+
assert kwargs["model"] is model
34+
assert kwargs["system_prompt"] == "sys"
35+
assert kwargs["tools"] == []
36+
assert kwargs["subagents"] == []
37+
assert kwargs["backend"] is None
38+
assert kwargs["response_format"] is None

0 commit comments

Comments
 (0)