Skip to content
Closed
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
3 changes: 3 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ This sample demonstrates a simple LangGraph agent that performs basic arithmetic
## [Chat agent](chat-agent)
This sample shows how to build an AI assistant using LangGraph and Tavily search for movie research and recommendations.

## [Coded DeepAgent](coded-deepagent)
This sample demonstrates a task-mode coded DeepAgent using the UiPath runtime workspace contract, typed input/output, a tool, and a review subagent.

## [Company research agent](company-research-agent)
This sample demonstrates how to create an agent that researches companies and develops outreach strategies using web search capabilities.

Expand Down
39 changes: 39 additions & 0 deletions samples/coded-deepagent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Coded DeepAgent

This sample demonstrates a task-mode coded agent built directly with the
standard `deepagents.create_deep_agent` API.

The sample does not use a UiPath-specific graph factory or attach UiPath
metadata. At runtime, UiPath detects the `ls_integration: deepagents` metadata
already present on the graph and applies the UiPath-owned runtime policy.

## What It Shows

- Standard DeepAgents message input and structured output.
- A standard LangChain tool used by the main DeepAgent.
- A DeepAgents subagent used for risk review.
- Automatic runtime detection through native DeepAgents metadata.

## Files

- `graph.py`: task-mode coded DeepAgent graph.
- `input.json`: sample input payload.
- `langgraph.json`: LangGraph entrypoint.
- `uipath.json`: UiPath task-mode runtime configuration.
- `agent.mermaid`: high-level graph diagram.

## Requirements

- UiPath runtime credentials for `UiPathChat`.
- Access to the configured model, `gpt-4o-2024-08-06`.

## Run

```bash
cd samples/coded-deepagent
uv sync
uipath run agent "$(cat input.json)"
```

The agent uses the filesystem tools supplied by the DeepAgents harness for its
working files and returns a structured launch brief.
15 changes: 15 additions & 0 deletions samples/coded-deepagent/agent.mermaid
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
flowchart TB
__start__(__start__)
transform_input(transform_input)
advanced_agent(advanced_agent)
risk_reviewer([risk_reviewer subagent])
workspace[(runtime workspace)]
transform_output(transform_output)
__end__(__end__)

__start__ --> transform_input
transform_input --> advanced_agent
advanced_agent -. delegates review .-> risk_reviewer
advanced_agent -. writes files .-> workspace
advanced_agent --> transform_output
transform_output --> __end__
66 changes: 66 additions & 0 deletions samples/coded-deepagent/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Task-mode coded DeepAgent using the standard DeepAgents API."""

from deepagents import create_deep_agent
from langchain_core.tools import tool
from pydantic import BaseModel

from uipath_langchain.chat import UiPathChat


class BriefOutput(BaseModel):
"""Structured launch brief returned by the agent."""

executive_summary: str
launch_plan: list[str]
risk_review: list[str]


@tool
def score_launch_readiness(audience: str, constraints: list[str]) -> str:
"""Score launch readiness from simple deterministic planning signals."""
score = 80
if len(constraints) >= 3:
score -= 10
if any("compliance" in item.lower() for item in constraints):
score -= 10
if any("deadline" in item.lower() for item in constraints):
score -= 5
return f"Launch readiness for {audience}: {max(score, 35)}/100"


MODEL = UiPathChat(model="gpt-4o-2024-08-06", temperature=0)

RISK_REVIEWER_PROMPT = """You are a launch risk reviewer.
Review the proposed launch plan for practical delivery risks, compliance gaps,
unclear ownership, and missing follow-up work. Return concise findings that the
main agent can incorporate into the final brief."""

RISK_REVIEWER = {
"name": "risk_reviewer",
"description": "Reviews launch plans for execution risks and missing safeguards.",
"system_prompt": RISK_REVIEWER_PROMPT,
"model": MODEL,
}


SYSTEM_PROMPT = """You are a product launch planning agent.

Use the planning tools provided by DeepAgents. Use score_launch_readiness to get
a deterministic readiness signal. Delegate a plan review to risk_reviewer before
finalizing the answer.

Use the DeepAgents filesystem to write these working files before producing the
final answer:
- /launch/brief.md with the final launch brief
- /launch/risks.md with the risk review

Return structured output matching the schema."""


graph = create_deep_agent(
model=MODEL,
system_prompt=SYSTEM_PROMPT,
response_format=BriefOutput,
tools=[score_launch_readiness],
subagents=[RISK_REVIEWER],
)
8 changes: 8 additions & 0 deletions samples/coded-deepagent/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"messages": [
{
"type": "human",
"content": "Create a pilot launch plan for Invoice Exception Copilot for accounts payable operations managers at three enterprise customers. The security review must complete before external rollout, the deadline is six weeks away, and the support team needs enablement material."
}
]
}
7 changes: 7 additions & 0 deletions samples/coded-deepagent/langgraph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"dependencies": ["."],
"graphs": {
"agent": "./graph.py:graph"
},
"env": ".env"
}
15 changes: 15 additions & 0 deletions samples/coded-deepagent/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "coded-deepagent"
version = "0.0.1"
description = "Task-mode coded DeepAgent using the UiPath runtime workspace contract"
authors = [{ name = "UiPath", email = "support@uipath.com" }]
requires-python = ">=3.11"
dependencies = [
"uipath-langchain>=0.14.3, <0.15.0",
"uipath>=2.13.6, <2.14.0",
]

[dependency-groups]
dev = [
"uipath-dev",
]
5 changes: 5 additions & 0 deletions samples/coded-deepagent/uipath.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"runtimeOptions": {
"isConversational": false
}
}
41 changes: 30 additions & 11 deletions src/uipath_langchain/agent/advanced/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
from deepagents import CompiledSubAgent, SubAgent
from deepagents import create_deep_agent as _create_deep_agent
from deepagents.backends import BackendProtocol
from deepagents.backends.filesystem import FilesystemBackend
from deepagents.backends.protocol import BackendFactory
from langchain.agents.structured_output import ResponseFormat
from langchain_core.language_models import BaseChatModel
from langchain_core.messages import HumanMessage
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.runnables.config import RunnableConfig
from langchain_core.tools import BaseTool
from langgraph.graph import END, START
from langgraph.graph.state import CompiledStateGraph, StateGraph
Expand All @@ -23,6 +23,7 @@
from .utils import (
MEMORY_INDEX_VIRTUAL_PATH,
create_state_with_input,
is_workspace_filesystem_backend,
resolve_input_attachments,
)

Expand Down Expand Up @@ -62,22 +63,25 @@ def create_advanced_agent_graph(
input_schema: type[BaseModel] | None,
output_schema: type[BaseModel],
build_user_message: Callable[[dict[str, Any]], str],
build_system_message: Callable[[dict[str, Any]], str] | None = None,
subagents: Sequence[SubAgent | CompiledSubAgent] = (),
) -> StateGraph[Any, Any, Any, Any]:
"""Wrap the advanced agent in a parent graph that maps typed I/O to/from messages.

With a ``FilesystemBackend``, attachment-shaped inputs are downloaded into the
workspace and given a ``FilePath`` before the user message is built. A
``FilesystemBackend`` also enables workspace memory: deepagents'
With a filesystem workspace backend, attachment-shaped inputs are downloaded
into the workspace and given a ``FilePath`` before the user message is built.
Filesystem workspaces also enable workspace memory: DeepAgents'
``MemoryMiddleware`` reads ``/memory/MEMORY.md`` from the backend each turn.
Memory stays disabled for non-filesystem backends, which carry no workspace.
Memory stays disabled for backends which carry no workspace.
"""
memory_sources = (
[MEMORY_INDEX_VIRTUAL_PATH] if isinstance(backend, FilesystemBackend) else []
[MEMORY_INDEX_VIRTUAL_PATH] if is_workspace_filesystem_backend(backend) else []
)

inner_graph = create_advanced_agent(
model=model,
tools=tools,
subagents=subagents,
system_prompt=system_prompt,
backend=backend,
response_format=response_format,
Expand All @@ -90,7 +94,10 @@ def create_advanced_agent_graph(
get_job_attachment_paths(input_schema) if input_schema is not None else []
)

async def transform_input_async(state: BaseModel) -> dict[str, Any]:
async def transform_input_async(
state: BaseModel,
config: RunnableConfig,
) -> dict[str, Any]:
state_data = state.model_dump()
input_data = {k: v for k, v in state_data.items() if k not in internal_fields}
input_args = (
Expand All @@ -100,10 +107,20 @@ async def transform_input_async(state: BaseModel) -> dict[str, Any]:
)
if attachment_paths:
input_args = await resolve_input_attachments(
backend, attachment_paths, input_args
backend,
attachment_paths,
input_args,
state=state,
config=config,
)
messages = []
if build_system_message is not None:
system_text = build_system_message(input_args)
if system_text:
messages.append(SystemMessage(content=system_text, id="system-input"))
user_text = build_user_message(input_args)
return {"messages": [HumanMessage(content=user_text, id="user-input")]}
messages.append(HumanMessage(content=user_text, id="user-input"))
return {"messages": messages}

def transform_output(state: BaseModel) -> dict[str, Any]:
structured = getattr(state, "structured_response", {})
Expand All @@ -128,6 +145,7 @@ def create_conversational_advanced_agent_graph(
tools: Sequence[BaseTool],
system_prompt: str,
backend: BackendProtocol | BackendFactory | None,
subagents: Sequence[SubAgent | CompiledSubAgent] = (),
) -> StateGraph[Any, Any, Any, Any]:
"""Wrap the advanced agent in a parent graph that speaks the conversational contract.

Expand All @@ -141,12 +159,13 @@ def create_conversational_advanced_agent_graph(
from uipath_langchain.runtime.messages import UiPathChatMessagesMapper

memory_sources = (
[MEMORY_INDEX_VIRTUAL_PATH] if isinstance(backend, FilesystemBackend) else []
[MEMORY_INDEX_VIRTUAL_PATH] if is_workspace_filesystem_backend(backend) else []
)

inner_graph = create_advanced_agent(
model=model,
tools=tools,
subagents=subagents,
system_prompt=system_prompt,
backend=backend,
memory=memory_sources,
Expand Down
55 changes: 49 additions & 6 deletions src/uipath_langchain/agent/advanced/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from deepagents.backends import BackendProtocol, FilesystemBackend
from deepagents.backends.protocol import BackendFactory
from jsonpath_ng import parse as jsonpath_parse # type: ignore[import-untyped]
from langchain.tools import ToolRuntime
from langchain_core.runnables.config import RunnableConfig
from pydantic import BaseModel
from uipath.platform import UiPath
from uipath.platform.attachments import Attachment
Expand All @@ -25,12 +27,54 @@
# persisted via WorkspaceHydrator) rather than the cross-run StoreBackend.
MEMORY_DIR_NAME = "memory"
MEMORY_INDEX_FILENAME = "MEMORY.md"
WORKSPACE_FILESYSTEM_BACKEND_ATTR = "is_uipath_workspace_filesystem_backend"

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


def is_workspace_filesystem_backend(
backend: BackendProtocol | BackendFactory | None,
) -> bool:
"""Return whether a backend is backed by a runtime workspace filesystem."""
return isinstance(backend, FilesystemBackend) or (
callable(backend)
and getattr(backend, WORKSPACE_FILESYSTEM_BACKEND_ATTR, False) is True
)


def _resolve_filesystem_backend(
backend: BackendProtocol | BackendFactory | None,
*,
state: BaseModel | None = None,
config: RunnableConfig | None = None,
) -> FilesystemBackend:
if isinstance(backend, FilesystemBackend):
return backend
if callable(backend) and is_workspace_filesystem_backend(backend):
resolved = backend(
ToolRuntime(
state=state,
context=None,
config=config or {},
stream_writer=lambda _: None,
tool_call_id=None,
store=None,
)
)
if isinstance(resolved, FilesystemBackend):
return resolved
raise TypeError(
"UiPath workspace backend factory must resolve to FilesystemBackend, "
f"got {type(resolved).__name__}"
)
raise NotImplementedError(
"Advanced agent with input attachments requires a FilesystemBackend, "
f"got {type(backend).__name__}"
)


def create_state_with_input(
input_schema: type[BaseModel] | None,
) -> type[AdvancedAgentGraphState]:
Expand Down Expand Up @@ -59,17 +103,16 @@ async def resolve_input_attachments(
backend: BackendProtocol | BackendFactory | None,
attachment_paths: list[str],
input_args: dict[str, Any],
*,
state: BaseModel | None = None,
config: RunnableConfig | None = None,
) -> dict[str, Any]:
"""Download attachment-shaped inputs into the backend and add a ``FilePath``.

Each ticket is streamed to ``<backend.cwd>/<ID>_<name>`` and augmented with a
``FilePath`` so the agent's file tools can open it. FilesystemBackend only.
``FilePath`` so the agent's file tools can open it.
"""
if not isinstance(backend, FilesystemBackend):
raise NotImplementedError(
"Advanced agent with input attachments requires a FilesystemBackend, "
f"got {type(backend).__name__}"
)
backend = _resolve_filesystem_backend(backend, state=state, config=config)

result = copy.deepcopy(input_args)
client = UiPath()
Expand Down
1 change: 1 addition & 0 deletions src/uipath_langchain/deepagents/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Internal DeepAgents runtime support for UiPath."""
Loading
Loading