Skip to content

Commit 71b218c

Browse files
feat: support coded deepagents
1 parent d6b9227 commit 71b218c

24 files changed

Lines changed: 904 additions & 17 deletions

samples/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ This sample demonstrates a simple LangGraph agent that performs basic arithmetic
66
## [Chat agent](chat-agent)
77
This sample shows how to build an AI assistant using LangGraph and Tavily search for movie research and recommendations.
88

9+
## [Coded DeepAgent](coded-deepagent)
10+
This sample demonstrates a task-mode coded DeepAgent using the UiPath runtime workspace contract, typed input/output, a tool, and a review subagent.
11+
912
## [Company research agent](company-research-agent)
1013
This sample demonstrates how to create an agent that researches companies and develops outreach strategies using web search capabilities.
1114

samples/coded-deepagent/README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Coded DeepAgent
2+
3+
This sample demonstrates a task-mode coded agent built with the UiPath
4+
DeepAgents contract.
5+
6+
The graph uses `create_uipath_deep_agent_graph`, which tags the graph for the
7+
UiPath LangGraph runtime. At runtime, UiPath creates a workspace, injects its
8+
path into LangGraph config, and hydrates that workspace through job
9+
attachments. Task-mode DeepAgents persist workspace changes when the run
10+
successfully completes or suspends. The sample does not configure a custom
11+
bucket backend.
12+
13+
## What It Shows
14+
15+
- Typed coded-agent input and output with Pydantic models.
16+
- A standard LangChain tool used by the main DeepAgent.
17+
- A DeepAgents subagent used for risk review.
18+
- Runtime-provided workspace persistence through the UiPath contract.
19+
20+
## Files
21+
22+
- `graph.py`: task-mode coded DeepAgent graph.
23+
- `input.json`: sample input payload.
24+
- `langgraph.json`: LangGraph entrypoint.
25+
- `uipath.json`: UiPath task-mode runtime configuration.
26+
- `agent.mermaid`: high-level graph diagram.
27+
28+
## Requirements
29+
30+
- UiPath runtime credentials for `UiPathChat`.
31+
- Access to the configured model, `gpt-4o-2024-08-06`.
32+
33+
## Run
34+
35+
```bash
36+
cd samples/coded-deepagent
37+
uv sync
38+
uipath run agent "$(cat input.json)"
39+
```
40+
41+
The agent writes `/launch/brief.md` and `/launch/risks.md` in the DeepAgents
42+
workspace and returns those paths in `workspace_files`.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
flowchart TB
2+
__start__(__start__)
3+
transform_input(transform_input)
4+
advanced_agent(advanced_agent)
5+
risk_reviewer([risk_reviewer subagent])
6+
workspace[(runtime workspace)]
7+
transform_output(transform_output)
8+
__end__(__end__)
9+
10+
__start__ --> transform_input
11+
transform_input --> advanced_agent
12+
advanced_agent -. delegates review .-> risk_reviewer
13+
advanced_agent -. writes files .-> workspace
14+
advanced_agent --> transform_output
15+
transform_output --> __end__

samples/coded-deepagent/graph.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""Task-mode coded DeepAgent using the UiPath DeepAgents contract."""
2+
3+
from langchain_core.tools import tool
4+
from pydantic import BaseModel, Field
5+
6+
from uipath_langchain.chat import UiPathChat
7+
from uipath_langchain.deepagents import create_uipath_deep_agent_graph
8+
9+
10+
class BriefInput(BaseModel):
11+
"""Input for the product launch brief."""
12+
13+
product_name: str = Field(description="Name of the product or feature.")
14+
audience: str = Field(description="Primary customer or user audience.")
15+
objective: str = Field(description="Main launch objective.")
16+
constraints: list[str] = Field(
17+
default_factory=list,
18+
description="Important limits, requirements, or risks to account for.",
19+
)
20+
21+
22+
class BriefOutput(BaseModel):
23+
"""Structured launch brief returned by the agent."""
24+
25+
executive_summary: str
26+
launch_plan: list[str]
27+
risk_review: list[str]
28+
workspace_files: list[str]
29+
30+
31+
@tool
32+
def score_launch_readiness(audience: str, constraints: list[str]) -> str:
33+
"""Score launch readiness from simple deterministic planning signals."""
34+
score = 80
35+
if len(constraints) >= 3:
36+
score -= 10
37+
if any("compliance" in item.lower() for item in constraints):
38+
score -= 10
39+
if any("deadline" in item.lower() for item in constraints):
40+
score -= 5
41+
return f"Launch readiness for {audience}: {max(score, 35)}/100"
42+
43+
44+
MODEL = UiPathChat(model="gpt-4o-2024-08-06", temperature=0)
45+
46+
RISK_REVIEWER_PROMPT = """You are a launch risk reviewer.
47+
Review the proposed launch plan for practical delivery risks, compliance gaps,
48+
unclear ownership, and missing follow-up work. Return concise findings that the
49+
main agent can incorporate into the final brief."""
50+
51+
RISK_REVIEWER = {
52+
"name": "risk_reviewer",
53+
"description": "Reviews launch plans for execution risks and missing safeguards.",
54+
"system_prompt": RISK_REVIEWER_PROMPT,
55+
"model": MODEL,
56+
}
57+
58+
SYSTEM_PROMPT = """You are a product launch planning agent.
59+
60+
Use the planning tools provided by DeepAgents. Use score_launch_readiness to get
61+
a deterministic readiness signal. Delegate a plan review to risk_reviewer before
62+
finalizing the answer.
63+
64+
The UiPath runtime provides your DeepAgents filesystem through the tagged graph
65+
contract. Write these workspace files before producing the final answer:
66+
- /launch/brief.md with the final launch brief
67+
- /launch/risks.md with the risk review
68+
69+
Return structured output matching the schema. Include the workspace file paths
70+
you wrote in workspace_files."""
71+
72+
73+
def build_user_message(args: dict) -> str:
74+
constraints = args.get("constraints") or []
75+
constraint_lines = "\n".join(f"- {item}" for item in constraints) or "- None"
76+
return f"""Create a launch brief.
77+
78+
Product: {args["product_name"]}
79+
Audience: {args["audience"]}
80+
Objective: {args["objective"]}
81+
Constraints:
82+
{constraint_lines}
83+
"""
84+
85+
86+
graph = create_uipath_deep_agent_graph(
87+
model=MODEL,
88+
input_schema=BriefInput,
89+
output_schema=BriefOutput,
90+
system_prompt=SYSTEM_PROMPT,
91+
tools=[score_launch_readiness],
92+
subagents=[RISK_REVIEWER],
93+
build_user_message=build_user_message,
94+
)

samples/coded-deepagent/input.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"product_name": "Invoice Exception Copilot",
3+
"audience": "accounts payable operations managers",
4+
"objective": "prepare a pilot launch plan for three enterprise customers",
5+
"constraints": [
6+
"security review must complete before external rollout",
7+
"deadline is six weeks away",
8+
"support team needs enablement material"
9+
]
10+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"dependencies": ["."],
3+
"graphs": {
4+
"agent": "./graph.py:graph"
5+
},
6+
"env": ".env"
7+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[project]
2+
name = "coded-deepagent"
3+
version = "0.0.1"
4+
description = "Task-mode coded DeepAgent using the UiPath runtime workspace contract"
5+
authors = [{ name = "UiPath", email = "support@uipath.com" }]
6+
requires-python = ">=3.11"
7+
dependencies = [
8+
"uipath-langchain>=0.14.3, <0.15.0",
9+
"uipath>=2.13.6, <2.14.0",
10+
]
11+
12+
[dependency-groups]
13+
dev = [
14+
"uipath-dev",
15+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"runtimeOptions": {
3+
"isConversational": false
4+
}
5+
}

src/uipath_langchain/agent/advanced/agent.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
from deepagents import CompiledSubAgent, SubAgent
77
from deepagents import create_deep_agent as _create_deep_agent
88
from deepagents.backends import BackendProtocol
9-
from deepagents.backends.filesystem import FilesystemBackend
109
from deepagents.backends.protocol import BackendFactory
1110
from langchain.agents.structured_output import ResponseFormat
1211
from langchain_core.language_models import BaseChatModel
1312
from langchain_core.messages import HumanMessage
13+
from langchain_core.runnables.config import RunnableConfig
1414
from langchain_core.tools import BaseTool
1515
from langgraph.graph import END, START
1616
from langgraph.graph.state import CompiledStateGraph, StateGraph
@@ -23,6 +23,7 @@
2323
from .utils import (
2424
MEMORY_INDEX_VIRTUAL_PATH,
2525
create_state_with_input,
26+
is_workspace_filesystem_backend,
2627
resolve_input_attachments,
2728
)
2829

@@ -62,22 +63,24 @@ def create_advanced_agent_graph(
6263
input_schema: type[BaseModel] | None,
6364
output_schema: type[BaseModel],
6465
build_user_message: Callable[[dict[str, Any]], str],
66+
subagents: Sequence[SubAgent | CompiledSubAgent] = (),
6567
) -> StateGraph[Any, Any, Any, Any]:
6668
"""Wrap the advanced agent in a parent graph that maps typed I/O to/from messages.
6769
68-
With a ``FilesystemBackend``, attachment-shaped inputs are downloaded into the
69-
workspace and given a ``FilePath`` before the user message is built. A
70-
``FilesystemBackend`` also enables workspace memory: deepagents'
70+
With a filesystem workspace backend, attachment-shaped inputs are downloaded
71+
into the workspace and given a ``FilePath`` before the user message is built.
72+
Filesystem workspaces also enable workspace memory: deepagents'
7173
``MemoryMiddleware`` reads ``/memory/MEMORY.md`` from the backend each turn.
72-
Memory stays disabled for non-filesystem backends, which carry no workspace.
74+
Memory stays disabled for backends which carry no workspace.
7375
"""
7476
memory_sources = (
75-
[MEMORY_INDEX_VIRTUAL_PATH] if isinstance(backend, FilesystemBackend) else []
77+
[MEMORY_INDEX_VIRTUAL_PATH] if is_workspace_filesystem_backend(backend) else []
7678
)
7779

7880
inner_graph = create_advanced_agent(
7981
model=model,
8082
tools=tools,
83+
subagents=subagents,
8184
system_prompt=system_prompt,
8285
backend=backend,
8386
response_format=response_format,
@@ -90,7 +93,10 @@ def create_advanced_agent_graph(
9093
get_job_attachment_paths(input_schema) if input_schema is not None else []
9194
)
9295

93-
async def transform_input_async(state: BaseModel) -> dict[str, Any]:
96+
async def transform_input_async(
97+
state: BaseModel,
98+
config: RunnableConfig,
99+
) -> dict[str, Any]:
94100
state_data = state.model_dump()
95101
input_data = {k: v for k, v in state_data.items() if k not in internal_fields}
96102
input_args = (
@@ -100,7 +106,11 @@ async def transform_input_async(state: BaseModel) -> dict[str, Any]:
100106
)
101107
if attachment_paths:
102108
input_args = await resolve_input_attachments(
103-
backend, attachment_paths, input_args
109+
backend,
110+
attachment_paths,
111+
input_args,
112+
state=state,
113+
config=config,
104114
)
105115
user_text = build_user_message(input_args)
106116
return {"messages": [HumanMessage(content=user_text, id="user-input")]}
@@ -128,6 +138,7 @@ def create_conversational_advanced_agent_graph(
128138
tools: Sequence[BaseTool],
129139
system_prompt: str,
130140
backend: BackendProtocol | BackendFactory | None,
141+
subagents: Sequence[SubAgent | CompiledSubAgent] = (),
131142
) -> StateGraph[Any, Any, Any, Any]:
132143
"""Wrap the advanced agent in a parent graph that speaks the conversational contract.
133144
@@ -141,12 +152,13 @@ def create_conversational_advanced_agent_graph(
141152
from uipath_langchain.runtime.messages import UiPathChatMessagesMapper
142153

143154
memory_sources = (
144-
[MEMORY_INDEX_VIRTUAL_PATH] if isinstance(backend, FilesystemBackend) else []
155+
[MEMORY_INDEX_VIRTUAL_PATH] if is_workspace_filesystem_backend(backend) else []
145156
)
146157

147158
inner_graph = create_advanced_agent(
148159
model=model,
149160
tools=tools,
161+
subagents=subagents,
150162
system_prompt=system_prompt,
151163
backend=backend,
152164
memory=memory_sources,

src/uipath_langchain/agent/advanced/utils.py

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from deepagents.backends import BackendProtocol, FilesystemBackend
1111
from deepagents.backends.protocol import BackendFactory
1212
from jsonpath_ng import parse as jsonpath_parse # type: ignore[import-untyped]
13+
from langchain.tools import ToolRuntime
14+
from langchain_core.runnables.config import RunnableConfig
1315
from pydantic import BaseModel
1416
from uipath.platform import UiPath
1517
from uipath.platform.attachments import Attachment
@@ -25,12 +27,54 @@
2527
# persisted via WorkspaceHydrator) rather than the cross-run StoreBackend.
2628
MEMORY_DIR_NAME = "memory"
2729
MEMORY_INDEX_FILENAME = "MEMORY.md"
30+
WORKSPACE_FILESYSTEM_BACKEND_ATTR = "is_uipath_workspace_filesystem_backend"
2831

2932
# Virtual path handed to MemoryMiddleware as a source; the agent's virtual-mode
3033
# FilesystemBackend resolves it under the workspace root.
3134
MEMORY_INDEX_VIRTUAL_PATH = f"/{MEMORY_DIR_NAME}/{MEMORY_INDEX_FILENAME}"
3235

3336

37+
def is_workspace_filesystem_backend(
38+
backend: BackendProtocol | BackendFactory | None,
39+
) -> bool:
40+
"""Return whether a backend is backed by a runtime workspace filesystem."""
41+
return isinstance(backend, FilesystemBackend) or (
42+
callable(backend)
43+
and getattr(backend, WORKSPACE_FILESYSTEM_BACKEND_ATTR, False) is True
44+
)
45+
46+
47+
def _resolve_filesystem_backend(
48+
backend: BackendProtocol | BackendFactory | None,
49+
*,
50+
state: BaseModel | None = None,
51+
config: RunnableConfig | None = None,
52+
) -> FilesystemBackend:
53+
if isinstance(backend, FilesystemBackend):
54+
return backend
55+
if callable(backend) and is_workspace_filesystem_backend(backend):
56+
resolved = backend(
57+
ToolRuntime(
58+
state=state,
59+
context=None,
60+
config=config or {},
61+
stream_writer=lambda _: None,
62+
tool_call_id=None,
63+
store=None,
64+
)
65+
)
66+
if isinstance(resolved, FilesystemBackend):
67+
return resolved
68+
raise TypeError(
69+
"UiPath workspace backend factory must resolve to FilesystemBackend, "
70+
f"got {type(resolved).__name__}"
71+
)
72+
raise NotImplementedError(
73+
"Advanced agent with input attachments requires a FilesystemBackend, "
74+
f"got {type(backend).__name__}"
75+
)
76+
77+
3478
def create_state_with_input(
3579
input_schema: type[BaseModel] | None,
3680
) -> type[AdvancedAgentGraphState]:
@@ -59,17 +103,16 @@ async def resolve_input_attachments(
59103
backend: BackendProtocol | BackendFactory | None,
60104
attachment_paths: list[str],
61105
input_args: dict[str, Any],
106+
*,
107+
state: BaseModel | None = None,
108+
config: RunnableConfig | None = None,
62109
) -> dict[str, Any]:
63110
"""Download attachment-shaped inputs into the backend and add a ``FilePath``.
64111
65112
Each ticket is streamed to ``<backend.cwd>/<ID>_<name>`` and augmented with a
66-
``FilePath`` so the agent's file tools can open it. FilesystemBackend only.
113+
``FilePath`` so the agent's file tools can open it.
67114
"""
68-
if not isinstance(backend, FilesystemBackend):
69-
raise NotImplementedError(
70-
"Advanced agent with input attachments requires a FilesystemBackend, "
71-
f"got {type(backend).__name__}"
72-
)
115+
backend = _resolve_filesystem_backend(backend, state=state, config=config)
73116

74117
result = copy.deepcopy(input_args)
75118
client = UiPath()

0 commit comments

Comments
 (0)