Skip to content

Commit 3a14ae0

Browse files
feat: enhance escalation traces (#522)
1 parent fab29b2 commit 3a14ae0

4 files changed

Lines changed: 95 additions & 11 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.5.29"
3+
version = "0.5.30"
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"

src/uipath_langchain/agent/tools/escalation_tool.py

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from uipath.eval.mocks import mockable
2222
from uipath.platform import UiPath
2323
from uipath.platform.action_center.tasks import TaskRecipient, TaskRecipientType
24-
from uipath.platform.common import CreateEscalation
24+
from uipath.platform.common import CreateEscalation, UiPathConfig
2525
from uipath.runtime.errors import UiPathErrorCode
2626

2727
from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
@@ -107,6 +107,48 @@ def _get_user_email(user: Any) -> str | None:
107107
return getattr(user, "emailAddress", None)
108108

109109

110+
def _parse_task_data(
111+
data: dict[str, Any],
112+
input_schema: dict[str, Any],
113+
output_schema: dict[str, Any] | None = None,
114+
) -> dict[str, Any]:
115+
"""
116+
Filter action center task data based on input/output schemas.
117+
118+
When output_schema is None, returns only fields not present in input_schema.
119+
When output_schema is provided, returns only fields defined in output_schema.
120+
121+
Args:
122+
data: Raw task data from action center
123+
input_schema: JSON schema defining the input fields
124+
output_schema: Optional JSON schema defining expected output fields
125+
126+
Returns:
127+
Filtered dictionary containing only relevant output fields
128+
"""
129+
filtered_fields: dict[str, Any] = {}
130+
131+
if output_schema is None:
132+
input_field_names = set()
133+
if "properties" in input_schema:
134+
input_field_names = set(input_schema["properties"].keys())
135+
136+
for field_name, field_value in data.items():
137+
if field_name not in input_field_names:
138+
filtered_fields[field_name] = field_value
139+
140+
else:
141+
output_field_names = set()
142+
if "properties" in output_schema:
143+
output_field_names = set(output_schema["properties"].keys())
144+
145+
for field_name, field_value in data.items():
146+
if field_name in output_field_names:
147+
filtered_fields[field_name] = field_value
148+
149+
return filtered_fields
150+
151+
110152
def create_escalation_tool(
111153
resource: AgentEscalationResourceConfig,
112154
) -> StructuredTool:
@@ -161,25 +203,31 @@ async def escalate():
161203

162204
# Extract task info before validation
163205
task_id = result.id
206+
task_url = f"{UiPathConfig.base_url}/actions_/tasks/{task_id}"
164207
assigned_to = _get_user_email(result.assigned_to_user)
165208

166-
escalation_action = result.action
167-
escalation_output = result.data or {}
209+
outcome = result.action
210+
escalation_output = _parse_task_data(
211+
result.data,
212+
input_schema=input_model.model_json_schema(),
213+
output_schema=EscalationToolOutput.model_json_schema(),
214+
)
168215

169216
outcome_str = (
170-
channel.outcome_mapping.get(escalation_action)
171-
if channel.outcome_mapping and escalation_action
217+
channel.outcome_mapping.get(outcome)
218+
if channel.outcome_mapping and outcome
172219
else None
173220
)
174-
outcome = (
221+
escalation_action = (
175222
EscalationAction(outcome_str) if outcome_str else EscalationAction.CONTINUE
176223
)
177224

178225
return {
179-
"action": outcome,
226+
"action": escalation_action,
180227
"output": escalation_output,
181-
"outcome": escalation_action,
228+
"outcome": outcome,
182229
"task_id": task_id,
230+
"task_url": task_url,
183231
"assigned_to": assigned_to,
184232
}
185233

@@ -215,7 +263,7 @@ async def escalation_wrapper(
215263
)
216264

217265
return {
218-
**result["output"],
266+
"output": result["output"],
219267
"outcome": result["outcome"],
220268
"task_id": result["task_id"],
221269
"assigned_to": result["assigned_to"],

tests/agent/tools/test_escalation_tool.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from uipath_langchain.agent.tools.escalation_tool import (
1818
_get_user_email,
19+
_parse_task_data,
1920
create_escalation_tool,
2021
resolve_asset,
2122
resolve_recipient_value,
@@ -692,3 +693,38 @@ async def test_wrapper_handles_missing_assigned_to_user(
692693

693694
assert result["task_id"] == 99999
694695
assert result["assigned_to"] is None
696+
697+
698+
class TestParseTaskData:
699+
"""Test output task data is filtered correctly."""
700+
701+
def test_filters_input_fields_when_no_output_schema(self):
702+
"""Test that input fields are excluded when output_schema is None."""
703+
data = {"input_field": "value1", "output_field": "value2"}
704+
input_schema = {"properties": {"input_field": {"type": "string"}}}
705+
706+
result = _parse_task_data(data, input_schema, output_schema=None)
707+
708+
assert result == {"output_field": "value2"}
709+
assert "input_field" not in result
710+
711+
def test_includes_only_output_fields_when_output_schema_provided(self):
712+
"""Test that only output schema fields are included."""
713+
data = {"field1": "a", "field2": "b", "field3": "c"}
714+
input_schema = {"properties": {"field1": {"type": "string"}}}
715+
output_schema = {
716+
"properties": {"field1": {"type": "string"}, "field2": {"type": "string"}}
717+
}
718+
719+
result = _parse_task_data(data, input_schema, output_schema)
720+
721+
assert result == {"field1": "a", "field2": "b"}
722+
assert "field3" not in result
723+
724+
def test_handles_missing_properties_in_schemas(self):
725+
"""Test behavior when schemas lack 'properties' key."""
726+
data = {"field": "value"}
727+
728+
# No properties key in schemas
729+
result = _parse_task_data(data, {}, None)
730+
assert result == {"field": "value"}

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)