Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies = [
"jsonpath-ng>=1.7.0",
"mcp==1.26.0",
"langchain-mcp-adapters==0.2.1",
"deepagents>=0.4.1",
]

classifiers = [
Expand Down
19 changes: 19 additions & 0 deletions src/uipath_langchain/agent/deep/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Deep agent support for UiPath coded agents."""

from deepagents import CompiledSubAgent, SubAgent
from deepagents.backends import (
BackendProtocol,
FilesystemBackend,
)
from deepagents.backends.protocol import BackendFactory

from .agent import create_deep_agent

__all__ = [
"BackendFactory",
"BackendProtocol",
"CompiledSubAgent",
"FilesystemBackend",
"SubAgent",
"create_deep_agent",
]
50 changes: 50 additions & 0 deletions src/uipath_langchain/agent/deep/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Deep agent builder."""

from collections.abc import Sequence
from typing import Any

from deepagents import CompiledSubAgent, SubAgent
from deepagents import create_deep_agent as _create_deep_agent
from deepagents.backends import BackendProtocol
from deepagents.backends.protocol import BackendFactory
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import BaseTool
from langgraph.graph.state import CompiledStateGraph


def create_deep_agent(
model: BaseChatModel,
system_prompt: str = "",
tools: Sequence[BaseTool] = (),
subagents: Sequence[SubAgent | CompiledSubAgent] = (),
backend: BackendProtocol | BackendFactory | None = None,
) -> CompiledStateGraph[Any, Any, Any, Any]:
"""Create a deep agent.

Deep agents provide built-in capabilities for:
- Planning (write_todos, read_todos)
- Filesystem operations (read_file, write_file, edit_file, ls, glob, grep)
- Sub-agent delegation (task)
- Auto-summarization for long conversations

Args:
model: A BaseChatModel instance.
system_prompt: Instructions for the agent.
tools: Custom tools to provide to the agent.
subagents: Optional list of subagent configurations. Each entry is a
``SubAgent`` (name, description, system_prompt, and optional tools/model/middleware)
or a ``CompiledSubAgent`` (name, description, and a pre-built runnable).
backend: Storage backend for filesystem operations. Can be a
``BackendProtocol`` instance, a factory callable, or ``None``
(uses the default in-state backend).

Returns:
Compiled LangGraph agent ready for execution.
"""
return _create_deep_agent(
model=model,
system_prompt=system_prompt,
tools=list(tools),
subagents=list(subagents),
backend=backend,
)
3 changes: 2 additions & 1 deletion src/uipath_langchain/chat/bedrock.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ def get_client(self):
config=botocore.config.Config(
retries={
"total_max_attempts": 1,
}
},
read_timeout=600,
),
)
client.meta.events.register(
Expand Down
1 change: 0 additions & 1 deletion src/uipath_langchain/chat/hitl.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ def requires_approval(
args_schema: type | None = None,
return_direct: bool = False,
) -> BaseTool | Callable[..., BaseTool]:

def decorator(fn: Callable[..., Any]) -> BaseTool:
_created_tool: list[BaseTool] = []

Expand Down
1 change: 1 addition & 0 deletions tests/agent/deep/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tests for Deep agent."""
83 changes: 83 additions & 0 deletions tests/agent/deep/test_create_deep_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Tests for create_deep_agent."""

import tempfile
from unittest.mock import MagicMock, patch

import pytest
from langchain_core.language_models import BaseChatModel
from langchain_core.tools import tool
from langgraph.graph.state import CompiledStateGraph
from langgraph.prebuilt import ToolNode

from uipath_langchain.agent.deep.agent import create_deep_agent


def _make_mock_model() -> MagicMock:
"""Create a mock model with the attributes the upstream code reads."""
model = MagicMock(spec=BaseChatModel)
# upstream reads model.profile for summarization defaults
model.profile = None
return model


@tool
def _sample_tool(query: str) -> str:
"""A sample tool for testing."""
return query


class TestCreateDeepAgent:
"""Test the create_deep_agent function."""

@pytest.fixture
def mock_model(self) -> MagicMock:
return _make_mock_model()

def test_deep_agent_with_tools(self, mock_model: MagicMock) -> None:
"""Custom tool is registered alongside built-in deep agent tools."""
result = create_deep_agent(
mock_model, system_prompt="test", tools=[_sample_tool]
)
assert isinstance(result, CompiledStateGraph)
tools_node = result.nodes["tools"].bound
assert isinstance(tools_node, ToolNode)
tool_names = set(tools_node.tools_by_name.keys())
assert "_sample_tool" in tool_names

def test_deep_agent_without_tools(self, mock_model: MagicMock) -> None:
"""Built-in deep agent tools are present even with no custom tools."""
result = create_deep_agent(mock_model, system_prompt="test", tools=[])
assert isinstance(result, CompiledStateGraph)
tools_node = result.nodes["tools"].bound
assert isinstance(tools_node, ToolNode)
tool_names = set(tools_node.tools_by_name.keys())
assert "write_todos" in tool_names

def test_deep_agent_with_backend(self, mock_model: MagicMock) -> None:
"""Compiles without error when a FilesystemBackend is provided."""
from uipath_langchain.agent.deep import FilesystemBackend

with tempfile.TemporaryDirectory() as tmp:
backend = FilesystemBackend(root_dir=tmp)
result = create_deep_agent(
mock_model, system_prompt="test", backend=backend
)
assert isinstance(result, CompiledStateGraph)

def test_deep_agent_converts_sequences_to_lists(
self, mock_model: MagicMock
) -> None:
"""Tuples for tools and subagents are converted to lists."""
with patch(
"uipath_langchain.agent.deep.agent._create_deep_agent"
) as mock_upstream:
mock_upstream.return_value = MagicMock(spec=CompiledStateGraph)
create_deep_agent(
mock_model,
system_prompt="test",
tools=(_sample_tool,),
subagents=(),
)
_, kwargs = mock_upstream.call_args
assert isinstance(kwargs["tools"], list)
assert isinstance(kwargs["subagents"], list)
Loading