Skip to content
Merged
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
8 changes: 4 additions & 4 deletions packages/uipath-agent-framework/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "uipath-agent-framework"
version = "0.0.5"
version = "0.0.6"
description = "Python SDK that enables developers to build and deploy Microsoft Agent Framework agents to the UiPath Cloud Platform"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"agent-framework-core>=1.0.0b260212",
"agent-framework-orchestrations>=1.0.0b260212",
"agent-framework-core>=1.0.0rc1",
"agent-framework-orchestrations>=1.0.0b260219",
"aiosqlite>=0.20.0",
"openinference-instrumentation-agent-framework>=0.1.0",
"uipath>=2.8.41, <2.9.0",
Expand All @@ -24,7 +24,7 @@ maintainers = [
]

[project.optional-dependencies]
anthropic = ["anthropic>=0.43.0", "agent-framework-anthropic>=1.0.0b260212"]
anthropic = ["anthropic>=0.43.0", "agent-framework-anthropic>=1.0.0b260219"]

[project.entry-points."uipath.middlewares"]
register = "uipath_agent_framework.middlewares:register_middleware"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ requires-python = ">=3.11"
dependencies = [
"uipath",
"uipath-agent-framework",
"agent-framework-core>=1.0.0b260212",
"agent-framework-orchestrations>=1.0.0b260212",
"agent-framework-core>=1.0.0rc1",
"agent-framework-orchestrations>=1.0.0b260219",
]

[dependency-groups]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ requires-python = ">=3.11"
dependencies = [
"uipath",
"uipath-agent-framework",
"agent-framework-core>=1.0.0b260212",
"agent-framework-orchestrations>=1.0.0b260212",
"agent-framework-core>=1.0.0rc1",
"agent-framework-orchestrations>=1.0.0b260219",
]

[dependency-groups]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ requires-python = ">=3.11"
dependencies = [
"uipath",
"uipath-agent-framework",
"agent-framework-core>=1.0.0b260212",
"agent-framework-orchestrations>=1.0.0b260212",
"agent-framework-core>=1.0.0rc1",
"agent-framework-orchestrations>=1.0.0b260219",
]

[dependency-groups]
Expand Down
20 changes: 18 additions & 2 deletions packages/uipath-agent-framework/samples/hitl-workflow/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
# HITL Workflow

A customer support workflow with human-in-the-loop approval for sensitive operations. A triage agent routes requests to billing or returns specialists. Both `transfer_funds` and `issue_refund` tools require human approval before executing.
A customer support workflow with human-in-the-loop approval for sensitive operations. A triage agent routes requests to specialists: billing handles fund transfers, returns handles refunds. The `transfer_funds`, `get_customer_order`, and `issue_refund` tools all require human approval before executing.

## Agent Graph

```mermaid
flowchart TB
__start__(__start__)
triage(triage)
orders_agent(orders_agent)
billing_agent(billing_agent)
returns_agent(returns_agent)
__end__(__end__)
__start__ --> |input|triage
triage --> orders_agent
triage --> billing_agent
triage --> returns_agent
orders_agent --> billing_agent
orders_agent --> returns_agent
orders_agent --> triage
billing_agent --> orders_agent
billing_agent --> returns_agent
billing_agent --> triage
returns_agent --> orders_agent
returns_agent --> billing_agent
returns_agent --> triage
orders_agent --> |output|__end__
billing_agent --> |output|__end__
returns_agent --> |output|__end__
```
Expand All @@ -32,8 +40,16 @@ uipath auth

## Run

Try a refund request (triggers `get_customer_order` + `issue_refund`, both require approval):

```
uipath run agent '{"messages": [{"contentParts": [{"data": {"inline": "I need a refund for order #12345, the item was defective"}}], "role": "user"}]}'
```

Or a fund transfer (triggers `transfer_funds`, requires approval):

```
uipath run agent '{"messages": [{"contentParts": [{"data": {"inline": "I need a refund for order #12345"}}], "role": "user"}]}'
uipath run agent '{"messages": [{"contentParts": [{"data": {"inline": "Transfer $500 from account ACC-001 to account ACC-002"}}], "role": "user"}]}'
```

## Debug
Expand Down
67 changes: 53 additions & 14 deletions packages/uipath-agent-framework/samples/hitl-workflow/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from agent_framework.orchestrations import HandoffBuilder

from uipath_agent_framework.chat import UiPathOpenAIChatClient, requires_approval
from uipath_agent_framework.chat import UiPathOpenAIChatClient
from uipath_agent_framework.chat.tools import requires_approval


@requires_approval
Expand All @@ -18,6 +19,26 @@ def transfer_funds(from_account: str, to_account: str, amount: float) -> str:
return f"Transferred ${amount:.2f} from {from_account} to {to_account}"


@requires_approval
def get_customer_order(order_id: str) -> str:
"""Retrieve customer info and order details by order ID. Requires human approval.

Args:
order_id: The order ID to look up

Returns:
Customer and order information
"""
return (
f"Order {order_id}:\n"
f" Customer: Jane Doe (jane.doe@example.com)\n"
f" Product: Wireless Headphones (SKU-4521)\n"
f" Amount: $79.99\n"
f" Status: Delivered\n"
f" Date: 2026-02-15"
)


@requires_approval
def issue_refund(order_id: str, amount: float, reason: str) -> str:
"""Issue a refund for an order. Requires human approval.
Expand All @@ -39,20 +60,34 @@ def issue_refund(order_id: str, amount: float, reason: str) -> str:
name="triage",
description="Routes customer requests to the right specialist.",
instructions=(
"You are a customer support triage agent. Determine what the "
"customer needs help with and hand off to the right agent:\n"
"You are a customer support triage agent. "
"Route the customer to the right agent immediately without asking questions:\n"
"- Order lookups and customer info -> orders_agent\n"
"- Billing issues (payments, transfers) -> billing_agent\n"
"- Returns and refunds -> returns_agent\n"
"Hand off on the first message. Do not ask clarifying questions."
),
)

orders = client.as_agent(
name="orders_agent",
description="Looks up customer info and order details.",
instructions=(
"You are an order lookup specialist. "
"When a customer asks about an order, immediately call get_customer_order "
"with the order ID they provided. Do not ask for additional information."
),
tools=[get_customer_order],
)

billing = client.as_agent(
name="billing_agent",
description="Handles billing, payments, and fund transfers.",
instructions=(
"You are a billing specialist. Help customers with payments "
"and transfers. Use the transfer_funds tool when needed — "
"it will require human approval before executing."
"You are a billing specialist. "
"When a customer requests a payment or transfer, immediately call "
"transfer_funds with the details provided. Do not ask for confirmation "
"or additional information — the tool has built-in human approval."
),
tools=[transfer_funds],
)
Expand All @@ -61,22 +96,26 @@ def issue_refund(order_id: str, amount: float, reason: str) -> str:
name="returns_agent",
description="Handles product returns and refund requests.",
instructions=(
"You are a returns specialist. Help customers process returns "
"and issue refunds. Use the issue_refund tool — it will "
"require human approval before executing."
"You are a returns specialist. "
"When a customer requests a refund, first call get_customer_order to look up "
"the order details, then immediately call issue_refund with the order ID, "
"the full order amount, and the reason the customer gave. "
"Do not ask the customer for information you can look up. "
"The tools have built-in human approval so just call them directly."
),
tools=[issue_refund],
tools=[get_customer_order, issue_refund],
)

workflow = (
HandoffBuilder(
name="customer_support",
participants=[triage, billing, returns],
participants=[triage, orders, billing, returns],
)
.with_start_agent(triage)
.add_handoff(triage, [billing, returns])
.add_handoff(billing, [returns, triage])
.add_handoff(returns, [billing, triage])
.add_handoff(triage, [orders, billing, returns])
.add_handoff(orders, [billing, returns, triage])
.add_handoff(billing, [orders, returns, triage])
.add_handoff(returns, [orders, billing, triage])
.build()
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ requires-python = ">=3.11"
dependencies = [
"uipath",
"uipath-agent-framework",
"agent-framework-core>=1.0.0b260212",
"agent-framework-core>=1.0.0rc1",
]

[dependency-groups]
Expand All @@ -18,3 +18,4 @@ dev = [

[tool.uv]
prerelease = "allow"

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ requires-python = ">=3.11"
dependencies = [
"uipath",
"uipath-agent-framework",
"agent-framework-core>=1.0.0b260212",
"agent-framework-core>=1.0.0rc1",
]

[dependency-groups]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,10 @@ def __getattr__(name):
from .anthropic import UiPathAnthropicClient

return UiPathAnthropicClient
if name == "requires_approval":
from .hitl import requires_approval

return requires_approval
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


__all__ = [
"UiPathOpenAIChatClient",
"UiPathAnthropicClient",
"requires_approval",
]
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from __future__ import annotations

import logging
from collections.abc import Sequence
from typing import Any

import httpx
Expand Down Expand Up @@ -103,7 +102,7 @@ def __init__(self, model: str = "gpt-4o-mini", **kwargs: Any):
**kwargs,
)

def _prepare_tools_for_openai(self, tools: Sequence[Any]) -> dict[str, Any]:
def _prepare_tools_for_openai(self, tools: Any) -> dict[str, Any]:
"""Prepare tools for the OpenAI Chat Completions API.

Extends the base implementation to convert plain callables to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Example::

from uipath_agent_framework.chat import requires_approval
from uipath_agent_framework.chat.tools import requires_approval

@requires_approval
def transfer_funds(from_account: str, to_account: str, amount: float) -> str:
Expand All @@ -22,18 +22,18 @@ def transfer_funds(from_account: str, to_account: str, amount: float) -> str:


@overload
def requires_approval(func: Callable[..., Any]) -> FunctionTool[Any]: ...
def requires_approval(func: Callable[..., Any]) -> FunctionTool: ...


@overload
def requires_approval(
func: None = None,
) -> Callable[[Callable[..., Any]], FunctionTool[Any]]: ...
) -> Callable[[Callable[..., Any]], FunctionTool]: ...


def requires_approval(
func: Callable[..., Any] | None = None,
) -> FunctionTool[Any] | Callable[[Callable[..., Any]], FunctionTool[Any]]:
) -> FunctionTool | Callable[[Callable[..., Any]], FunctionTool]:
"""Decorator that marks a tool function as requiring human approval.

When the agent calls a tool decorated with ``@requires_approval``,
Expand Down Expand Up @@ -61,7 +61,7 @@ def my_tool(arg: str) -> str: ...
if func is not None:
return tool(func, approval_mode="always_require")

def decorator(fn: Callable[..., Any]) -> FunctionTool[Any]:
def decorator(fn: Callable[..., Any]) -> FunctionTool:
return tool(fn, approval_mode="always_require")

return decorator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from __future__ import annotations

from collections.abc import Awaitable, Callable
from collections.abc import Awaitable, Callable, Mapping
from typing import Any
from uuid import uuid4

Expand Down Expand Up @@ -123,7 +123,10 @@ async def process(
input_value: Any = None
if context.arguments is not None:
try:
input_value = context.arguments.model_dump()
if isinstance(context.arguments, Mapping):
input_value = dict(context.arguments)
else:
input_value = context.arguments.model_dump()
except Exception:
input_value = str(context.arguments)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ async def get_latest(self, *, workflow_name: str) -> WorkflowCheckpoint | None:
conn = await self._storage._get_conn()
async with self._storage._lock:
cursor = await conn.execute(
"SELECT checkpoint_data FROM checkpoints WHERE workflow_name = ? ORDER BY timestamp DESC LIMIT 1",
"SELECT checkpoint_data FROM checkpoints WHERE workflow_name = ? ORDER BY timestamp DESC, rowid DESC LIMIT 1",
(workflow_name,),
)
row = await cursor.fetchone()
Expand Down
Loading