Skip to content

Commit 4877a0b

Browse files
feat: add content_tokens and argument_properties to agent model (#1004)
1 parent 33b153c commit 4877a0b

4 files changed

Lines changed: 546 additions & 1 deletion

File tree

src/uipath/agent/models/agent.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@ class AgentGuardrailActionType(str, Enum):
9292
UNKNOWN = "unknown" # fallback branch discriminator
9393

9494

95+
class AgentToolArgumentPropertiesVariant(str, Enum):
96+
"""Agent tool argument properties variant enumeration."""
97+
98+
DYNAMIC = "dynamic"
99+
ARGUMENT = "argument"
100+
STATIC = "static"
101+
TEXT_BUILDER = "textBuilder"
102+
103+
104+
class TextTokenType(str, Enum):
105+
"""Text token type enumeration."""
106+
107+
SIMPLE_TEXT = "simpleText"
108+
VARIABLE = "variable"
109+
EXPRESSION = "expression"
110+
111+
95112
class BaseCfg(BaseModel):
96113
"""Base configuration model with common settings."""
97114

@@ -108,6 +125,59 @@ class ExampleCall(BaseCfg):
108125
output: str = Field(..., alias="output")
109126

110127

128+
class TextToken(BaseCfg):
129+
"""Text token model."""
130+
131+
type: TextTokenType
132+
raw_string: str = Field(alias="rawString")
133+
134+
135+
class BaseAgentToolArgumentProperties(BaseCfg):
136+
"""Base tool argument properties model."""
137+
138+
variant: AgentToolArgumentPropertiesVariant
139+
is_sensitive: bool = Field(alias="isSensitive")
140+
141+
142+
class AgentToolStaticArgumentProperties(BaseAgentToolArgumentProperties):
143+
"""Static tool argument properties model."""
144+
145+
variant: Literal[AgentToolArgumentPropertiesVariant.STATIC] = Field(
146+
default=AgentToolArgumentPropertiesVariant.STATIC, frozen=True
147+
)
148+
value: Optional[Any]
149+
150+
151+
class AgentToolArgumentArgumentProperties(BaseAgentToolArgumentProperties):
152+
"""Agent argument argument properties model."""
153+
154+
variant: Literal[AgentToolArgumentPropertiesVariant.ARGUMENT] = Field(
155+
default=AgentToolArgumentPropertiesVariant.ARGUMENT,
156+
frozen=True,
157+
)
158+
argument_path: str = Field(alias="argumentPath")
159+
160+
161+
class AgentToolTextBuilderArgumentProperties(BaseAgentToolArgumentProperties):
162+
"""Agent text builder argument properties model."""
163+
164+
variant: Literal[AgentToolArgumentPropertiesVariant.TEXT_BUILDER] = Field(
165+
default=AgentToolArgumentPropertiesVariant.TEXT_BUILDER,
166+
frozen=True,
167+
)
168+
tokens: List[TextToken]
169+
170+
171+
AgentToolArgumentProperties = Annotated[
172+
Union[
173+
AgentToolStaticArgumentProperties,
174+
AgentToolArgumentArgumentProperties,
175+
AgentToolTextBuilderArgumentProperties,
176+
],
177+
Field(discriminator="variant"),
178+
]
179+
180+
111181
class BaseResourceProperties(BaseCfg):
112182
"""Base resource properties model."""
113183

@@ -216,6 +286,9 @@ class AgentMcpTool(BaseCfg):
216286
description: str = Field(..., alias="description")
217287
input_schema: Dict[str, Any] = Field(..., alias="inputSchema")
218288
output_schema: Optional[Dict[str, Any]] = Field(None, alias="outputSchema")
289+
argument_properties: Dict[str, AgentToolArgumentProperties] = Field(
290+
{}, alias="argumentProperties"
291+
)
219292

220293

221294
class AgentMcpResourceConfig(BaseAgentResourceConfig):
@@ -360,6 +433,9 @@ class AgentProcessToolResourceConfig(BaseAgentToolResourceConfig):
360433
properties: AgentProcessToolProperties
361434
settings: AgentToolSettings = Field(default_factory=AgentToolSettings)
362435
arguments: Dict[str, Any] = Field(default_factory=dict)
436+
argument_properties: Dict[str, AgentToolArgumentProperties] = Field(
437+
{}, alias="argumentProperties"
438+
)
363439

364440

365441
class AgentIntegrationToolParameter(BaseCfg):
@@ -426,6 +502,9 @@ class AgentInternalToolResourceConfig(BaseAgentToolResourceConfig):
426502
arguments: Optional[Dict[str, Any]] = Field(default_factory=dict)
427503
is_enabled: Optional[bool] = Field(None, alias="isEnabled")
428504
output_schema: Dict[str, Any] = Field(..., alias="outputSchema")
505+
argument_properties: Dict[str, AgentToolArgumentProperties] = Field(
506+
{}, alias="argumentProperties"
507+
)
429508

430509

431510
class AgentUnknownToolResourceConfig(BaseAgentToolResourceConfig):
@@ -677,6 +756,7 @@ class AgentMessage(BaseCfg):
677756

678757
role: Literal[AgentMessageRole.SYSTEM, AgentMessageRole.USER]
679758
content: str
759+
content_tokens: Optional[List[TextToken]] = Field(None, alias="contentTokens")
680760

681761
@field_validator("role", mode="before")
682762
@classmethod
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
"""Text token utilities for building prompts from tokenized content."""
2+
3+
import json
4+
from typing import Any
5+
6+
from uipath.agent.models.agent import TextToken, TextTokenType
7+
8+
9+
def build_string_from_tokens(
10+
tokens: list[TextToken],
11+
input_arguments: dict[str, Any],
12+
tool_names: list[str] | None = None,
13+
escalation_names: list[str] | None = None,
14+
context_names: list[str] | None = None,
15+
) -> str:
16+
"""Build a string from text tokens with variable replacement.
17+
18+
Args:
19+
tokens: List of text tokens to join
20+
input_arguments: Dictionary of input arguments for variable replacement
21+
tool_names: Optional list of tool names for tool.* variable resolution
22+
escalation_names: Optional list of escalation names for escalation.* variable resolution
23+
context_names: Optional list of context names for context.* variable resolution
24+
"""
25+
parts: list[str] = []
26+
27+
for token in tokens:
28+
if token.type == TextTokenType.SIMPLE_TEXT:
29+
parts.append(token.raw_string)
30+
elif token.type == TextTokenType.EXPRESSION:
31+
parts.append(token.raw_string)
32+
elif token.type == TextTokenType.VARIABLE:
33+
resolved_value = _process_variable_token(
34+
token.raw_string,
35+
input_arguments,
36+
tool_names,
37+
escalation_names,
38+
context_names,
39+
)
40+
parts.append(resolved_value)
41+
else:
42+
parts.append(token.raw_string)
43+
44+
return "".join(parts)
45+
46+
47+
def _process_variable_token(
48+
raw_string: str,
49+
input_arguments: dict[str, Any],
50+
tool_names: list[str] | None = None,
51+
escalation_names: list[str] | None = None,
52+
context_names: list[str] | None = None,
53+
) -> str:
54+
"""Process a variable token and return its resolved value.
55+
56+
Returns:
57+
The resolved variable value or original string if unresolved
58+
"""
59+
if not raw_string or not raw_string.strip():
60+
return raw_string
61+
62+
if raw_string.lower() == "input":
63+
return json.dumps(input_arguments, ensure_ascii=False)
64+
65+
dot_index = raw_string.find(".")
66+
if dot_index < 0:
67+
return raw_string
68+
69+
prefix = raw_string[:dot_index].lower()
70+
path = raw_string[dot_index + 1 :]
71+
72+
if prefix == "input":
73+
value = safe_get_nested(input_arguments, path)
74+
return serialize_argument(value) if value is not None else raw_string
75+
elif prefix == "output":
76+
return path
77+
elif prefix == "tools":
78+
found_name = _find_resource_name(path, tool_names)
79+
return found_name if found_name else raw_string
80+
elif prefix == "escalations":
81+
found_name = _find_resource_name(path, escalation_names)
82+
return found_name if found_name else raw_string
83+
elif prefix == "contexts":
84+
found_name = _find_resource_name(path, context_names)
85+
return found_name if found_name else raw_string
86+
87+
return raw_string
88+
89+
90+
def _find_resource_name(name: str, resource_names: list[str] | None) -> str | None:
91+
"""Find a resource name in the list.
92+
93+
Args:
94+
name: The name to search for
95+
resource_names: List of resource names to search in
96+
97+
Returns:
98+
The matching resource name, or None if not found
99+
"""
100+
if not resource_names:
101+
return None
102+
103+
name_lower = name.lower()
104+
return next(
105+
(
106+
resource_name
107+
for resource_name in resource_names
108+
if resource_name.lower() == name_lower
109+
),
110+
None,
111+
)
112+
113+
114+
def safe_get_nested(data: dict[str, Any], path: str) -> Any:
115+
"""Get nested dictionary value using dot notation (e.g., "user.email")."""
116+
keys = path.split(".")
117+
current = data
118+
119+
for key in keys:
120+
if isinstance(current, dict) and key in current:
121+
current = current[key]
122+
else:
123+
return None
124+
125+
return current
126+
127+
128+
def serialize_argument(
129+
value: str | int | float | bool | list[Any] | dict[str, Any] | None,
130+
) -> str:
131+
"""Serialize value for interpolation: primitives as-is, collections as JSON."""
132+
if value is None:
133+
return ""
134+
if isinstance(value, (list, dict, bool)):
135+
return json.dumps(value, ensure_ascii=False)
136+
return str(value)

tests/agent/models/test_agent.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,41 @@ def test_agent_with_all_tool_types_loads(self):
4242
"name": "Agent with All Tools",
4343
"metadata": {"isConversational": False, "storageVersion": "22.0.0"},
4444
"messages": [
45-
{"role": "System", "content": "You are an agentic assistant."},
45+
{
46+
"role": "System",
47+
"content": "You are an agentic assistant.",
48+
"contentTokens": [
49+
{
50+
"type": "simpleText",
51+
"rawString": "You are an agentic assistant.",
52+
}
53+
],
54+
},
4655
{
4756
"role": "User",
4857
"content": "Use the provided tools. Execute {{task}} the number of {{times}}.",
58+
"contentTokens": [
59+
{
60+
"type": "simpleText",
61+
"rawString": "Use the provided tools. Execute ",
62+
},
63+
{
64+
"type": "variable",
65+
"rawString": "input.task",
66+
},
67+
{
68+
"type": "simpleText",
69+
"rawString": " the number of ",
70+
},
71+
{
72+
"type": "variable",
73+
"rawString": "input.times",
74+
},
75+
{
76+
"type": "simpleText",
77+
"rawString": ".",
78+
},
79+
],
4980
},
5081
],
5182
"inputSchema": {
@@ -229,6 +260,13 @@ def test_agent_with_all_tool_types_loads(self):
229260
"properties": {"output": {"type": "string"}},
230261
},
231262
"settings": {},
263+
"argumentProperties": {
264+
"task": {
265+
"variant": "argument",
266+
"argumentPath": "$['task']",
267+
"isSensitive": False,
268+
}
269+
},
232270
"properties": {
233271
"processName": "Basic RPA Process",
234272
"folderPath": "TestFolder/Complete Solution 30 Sept",
@@ -274,6 +312,18 @@ def test_agent_with_all_tool_types_loads(self):
274312
},
275313
"required": ["timezone"],
276314
},
315+
"argumentProperties": {
316+
"timezone": {
317+
"variant": "textBuilder",
318+
"tokens": [
319+
{
320+
"type": "simpleText",
321+
"rawString": "Europe/London",
322+
},
323+
],
324+
"isSensitive": False,
325+
},
326+
},
277327
},
278328
{
279329
"name": "convert_time",

0 commit comments

Comments
 (0)