From cc416133f5555261685dccd14c784b4889fac477 Mon Sep 17 00:00:00 2001 From: Andrei Ancuta Date: Wed, 10 Dec 2025 18:16:37 +0200 Subject: [PATCH 1/7] feat: add content_tokens to message model --- src/uipath/agent/models/agent.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/uipath/agent/models/agent.py b/src/uipath/agent/models/agent.py index e4532c31a..a3a849d82 100644 --- a/src/uipath/agent/models/agent.py +++ b/src/uipath/agent/models/agent.py @@ -92,6 +92,14 @@ class AgentGuardrailActionType(str, Enum): UNKNOWN = "unknown" # fallback branch discriminator +class TextTokenType(str, Enum): + """Text token type enumeration.""" + + SIMPLE_TEXT = "simpleText" + VARIABLE = "variable" + EXPRESSION = "expression" + + class BaseCfg(BaseModel): """Base configuration model with common settings.""" @@ -108,6 +116,13 @@ class ExampleCall(BaseCfg): output: str = Field(..., alias="output") +class TextToken(BaseCfg): + """Text token model.""" + + type: TextTokenType + raw_string: str = Field(alias="rawString") + + class BaseResourceProperties(BaseCfg): """Base resource properties model.""" @@ -677,6 +692,7 @@ class AgentMessage(BaseCfg): role: Literal[AgentMessageRole.SYSTEM, AgentMessageRole.USER] content: str + content_tokens: Optional[List[TextToken]] = Field(None, alias="contentTokens") @field_validator("role", mode="before") @classmethod From 454049df62e75e169e0f4b1cd79f4b7a544e54f1 Mon Sep 17 00:00:00 2001 From: Andrei Ancuta Date: Wed, 10 Dec 2025 18:40:37 +0200 Subject: [PATCH 2/7] feat: add argument_properties to agent model --- src/uipath/agent/models/agent.py | 61 ++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/uipath/agent/models/agent.py b/src/uipath/agent/models/agent.py index a3a849d82..69174140f 100644 --- a/src/uipath/agent/models/agent.py +++ b/src/uipath/agent/models/agent.py @@ -92,6 +92,15 @@ class AgentGuardrailActionType(str, Enum): UNKNOWN = "unknown" # fallback branch discriminator +class AgentToolArgumentPropertiesVariant(str, Enum): + """Agent tool argument properties variant enumeration.""" + + DYNAMIC = "dynamic" + ARGUMENT = "argument" + STATIC = "static" + TEXT_BUILDER = "textBuilder" + + class TextTokenType(str, Enum): """Text token type enumeration.""" @@ -123,6 +132,52 @@ class TextToken(BaseCfg): raw_string: str = Field(alias="rawString") +class BaseAgentToolArgumentProperties(BaseCfg): + """Base tool argument properties model.""" + + variant: AgentToolArgumentPropertiesVariant + is_sensitive: bool = Field(alias="isSensitive") + + +class AgentToolStaticArgumentProperties(BaseAgentToolArgumentProperties): + """Static tool argument properties model.""" + + variant: Literal[AgentToolArgumentPropertiesVariant.STATIC] = Field( + default=AgentToolArgumentPropertiesVariant.STATIC, frozen=True + ) + value: Optional[Any] + + +class AgentToolArgumentArgumentProperties(BaseAgentToolArgumentProperties): + """Agent argument argument properties model.""" + + variant: Literal[AgentToolArgumentPropertiesVariant.ARGUMENT] = Field( + default=AgentToolArgumentPropertiesVariant.ARGUMENT, + frozen=True, + ) + argument_path: str = Field(alias="argumentPath") + + +class AgentToolTextBuilderArgumentProperties(BaseAgentToolArgumentProperties): + """Agent text builder argument properties model.""" + + variant: Literal[AgentToolArgumentPropertiesVariant.TEXT_BUILDER] = Field( + default=AgentToolArgumentPropertiesVariant.TEXT_BUILDER, + frozen=True, + ) + tokens: List[TextToken] + + +AgentToolArgumentProperties = Annotated[ + Union[ + AgentToolStaticArgumentProperties, + AgentToolArgumentArgumentProperties, + AgentToolTextBuilderArgumentProperties, + ], + Field(discriminator="variant"), +] + + class BaseResourceProperties(BaseCfg): """Base resource properties model.""" @@ -231,6 +286,9 @@ class AgentMcpTool(BaseCfg): description: str = Field(..., alias="description") input_schema: Dict[str, Any] = Field(..., alias="inputSchema") output_schema: Optional[Dict[str, Any]] = Field(None, alias="outputSchema") + argument_properties: Dict[str, AgentToolArgumentProperties] = Field( + {}, alias="argumentProperties" + ) class AgentMcpResourceConfig(BaseAgentResourceConfig): @@ -375,6 +433,9 @@ class AgentProcessToolResourceConfig(BaseAgentToolResourceConfig): properties: AgentProcessToolProperties settings: AgentToolSettings = Field(default_factory=AgentToolSettings) arguments: Dict[str, Any] = Field(default_factory=dict) + argument_properties: Dict[str, AgentToolArgumentProperties] = Field( + {}, alias="argumentProperties" + ) class AgentIntegrationToolParameter(BaseCfg): From 632bf676a2ee77607c526afb9692a8ab9d7c6136 Mon Sep 17 00:00:00 2001 From: Andrei Ancuta Date: Wed, 10 Dec 2025 18:43:12 +0200 Subject: [PATCH 3/7] feat: bump version --- pyproject.toml | 2 +- uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index cd11a5ad6..f8c2ec121 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath" -version = "2.4.10" +version = "2.4.11" description = "Python SDK and CLI for UiPath Platform, enabling programmatic interaction with automation services, process management, and deployment tools." readme = { file = "README.md", content-type = "text/markdown" } requires-python = ">=3.11" diff --git a/uv.lock b/uv.lock index 3cc0c1493..56bba6213 100644 --- a/uv.lock +++ b/uv.lock @@ -2486,7 +2486,7 @@ wheels = [ [[package]] name = "uipath" -version = "2.4.10" +version = "2.4.11" source = { editable = "." } dependencies = [ { name = "applicationinsights" }, From 949fb761110e3c755f39e78543ad709eb55e1350 Mon Sep 17 00:00:00 2001 From: Andrei Ancuta Date: Thu, 11 Dec 2025 15:19:20 +0200 Subject: [PATCH 4/7] feat: add new fields to test json --- tests/agent/models/test_agent.py | 52 +++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/tests/agent/models/test_agent.py b/tests/agent/models/test_agent.py index 27924b388..4cc2e0d40 100644 --- a/tests/agent/models/test_agent.py +++ b/tests/agent/models/test_agent.py @@ -42,10 +42,41 @@ def test_agent_with_all_tool_types_loads(self): "name": "Agent with All Tools", "metadata": {"isConversational": False, "storageVersion": "22.0.0"}, "messages": [ - {"role": "System", "content": "You are an agentic assistant."}, + { + "role": "System", + "content": "You are an agentic assistant.", + "contentTokens": [ + { + "type": "simpleText", + "rawString": "You are an agentic assistant.", + } + ], + }, { "role": "User", "content": "Use the provided tools. Execute {{task}} the number of {{times}}.", + "contentTokens": [ + { + "type": "simpleText", + "rawString": "Use the provided tools. Execute ", + }, + { + "type": "variable", + "rawString": "input.task", + }, + { + "type": "simpleText", + "rawString": " the number of ", + }, + { + "type": "variable", + "rawString": "input.times", + }, + { + "type": "simpleText", + "rawString": ".", + }, + ], }, ], "inputSchema": { @@ -229,6 +260,13 @@ def test_agent_with_all_tool_types_loads(self): "properties": {"output": {"type": "string"}}, }, "settings": {}, + "argumentProperties": { + "task": { + "variant": "argument", + "argumentPath": "$['task']", + "isSensitive": False, + } + }, "properties": { "processName": "Basic RPA Process", "folderPath": "TestFolder/Complete Solution 30 Sept", @@ -274,6 +312,18 @@ def test_agent_with_all_tool_types_loads(self): }, "required": ["timezone"], }, + "argumentProperties": { + "timezone": { + "variant": "textBuilder", + "tokens": [ + { + "type": "simpleText", + "rawString": "Europe/London", + }, + ], + "isSensitive": False, + }, + }, }, { "name": "convert_time", From 9834be958a1792451359fa8cf7ea734b833f30f7 Mon Sep 17 00:00:00 2001 From: Andrei Ancuta Date: Wed, 17 Dec 2025 13:37:16 +0200 Subject: [PATCH 5/7] feat: add text tokens utils --- src/uipath/agent/utils/text_tokens.py | 136 +++++++++++++ tests/agent/utils/test_text_tokens.py | 276 ++++++++++++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 src/uipath/agent/utils/text_tokens.py create mode 100644 tests/agent/utils/test_text_tokens.py diff --git a/src/uipath/agent/utils/text_tokens.py b/src/uipath/agent/utils/text_tokens.py new file mode 100644 index 000000000..fd1293ee2 --- /dev/null +++ b/src/uipath/agent/utils/text_tokens.py @@ -0,0 +1,136 @@ +"""Text token utilities for building prompts from tokenized content.""" + +import json +from typing import Any + +from uipath.agent.models.agent import TextToken, TextTokenType + + +def build_string_from_tokens( + tokens: list[TextToken], + input_arguments: dict[str, Any], + tool_names: list[str] | None = None, + escalation_names: list[str] | None = None, + context_names: list[str] | None = None, +) -> str: + """Build a string from text tokens with variable replacement. + + Args: + tokens: List of text tokens to join + input_arguments: Dictionary of input arguments for variable replacement + tool_names: Optional list of tool names for tool.* variable resolution + escalation_names: Optional list of escalation names for escalation.* variable resolution + context_names: Optional list of context names for context.* variable resolution + """ + parts: list[str] = [] + + for token in tokens: + if token.type == TextTokenType.SIMPLE_TEXT: + parts.append(token.raw_string) + elif token.type == TextTokenType.EXPRESSION: + parts.append(token.raw_string) + elif token.type == TextTokenType.VARIABLE: + resolved_value = _process_variable_token( + token.raw_string, + input_arguments, + tool_names, + escalation_names, + context_names, + ) + parts.append(resolved_value) + else: + parts.append(token.raw_string) + + return "".join(parts) + + +def _process_variable_token( + raw_string: str, + input_arguments: dict[str, Any], + tool_names: list[str] | None = None, + escalation_names: list[str] | None = None, + context_names: list[str] | None = None, +) -> str: + """Process a variable token and return its resolved value. + + Returns: + The resolved variable value or original string if unresolved + """ + if not raw_string or not raw_string.strip(): + return raw_string + + if raw_string.lower() == "input": + return json.dumps(input_arguments, ensure_ascii=False) + + dot_index = raw_string.find(".") + if dot_index < 0: + return raw_string + + prefix = raw_string[:dot_index].lower() + path = raw_string[dot_index + 1 :] + + if prefix == "input": + value = safe_get_nested(input_arguments, path) + return serialize_argument(value) if value is not None else raw_string + elif prefix == "output": + return path + elif prefix == "tools": + found_name = _find_resource_name(path, tool_names) + return found_name if found_name else raw_string + elif prefix == "escalations": + found_name = _find_resource_name(path, escalation_names) + return found_name if found_name else raw_string + elif prefix == "contexts": + found_name = _find_resource_name(path, context_names) + return found_name if found_name else raw_string + + return raw_string + + +def _find_resource_name(name: str, resource_names: list[str] | None) -> str | None: + """Find a resource name in the list. + + Args: + name: The name to search for + resource_names: List of resource names to search in + + Returns: + The matching resource name, or None if not found + """ + if not resource_names: + return None + + name_lower = name.lower() + return next( + ( + resource_name + for resource_name in resource_names + if resource_name.lower() == name_lower + ), + None, + ) + + +def safe_get_nested(data: dict[str, Any], path: str) -> Any: + """Get nested dictionary value using dot notation (e.g., "user.email").""" + keys = path.split(".") + current = data + + for key in keys: + if isinstance(current, dict) and key in current: + current = current[key] + else: + return None + + return current + + +def serialize_argument( + value: str | int | float | bool | list[Any] | dict[str, Any] | None, +) -> str: + """Serialize value for interpolation: primitives as-is, collections as JSON.""" + if value is None: + return "" + if isinstance(value, (list, dict, bool)): + return json.dumps(value, ensure_ascii=False) + return str(value) diff --git a/tests/agent/utils/test_text_tokens.py b/tests/agent/utils/test_text_tokens.py new file mode 100644 index 000000000..d967e95ce --- /dev/null +++ b/tests/agent/utils/test_text_tokens.py @@ -0,0 +1,276 @@ +"""Tests for text_tokens.py utils.""" + +import pytest + +from uipath.agent.models.agent import TextToken, TextTokenType +from uipath.agent.utils.text_tokens import ( + build_string_from_tokens, + safe_get_nested, + serialize_argument, +) + + +class TestBuildStringFromTokens: + """Test building strings from text tokens.""" + + def test_simple_text_tokens(self): + """Test simple text tokens are joined correctly.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Hello "), + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="World"), + ] + result = build_string_from_tokens(tokens, {}) + assert result == "Hello World" + + def test_with_input_variable_replacement(self): + """Test input.* variable tokens are replaced with input values.""" + tokens = [ + TextToken( + type=TextTokenType.SIMPLE_TEXT, rawString="What is the weather like in " + ), + TextToken(type=TextTokenType.VARIABLE, rawString="input.city"), + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="?"), + ] + result = build_string_from_tokens(tokens, {"city": "London"}) + assert result == "What is the weather like in London?" + + def test_with_output_variable(self): + """Test output.* variable tokens return the path.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Put the results in "), + TextToken(type=TextTokenType.VARIABLE, rawString="output.weather"), + ] + result = build_string_from_tokens(tokens, {}) + assert result == "Put the results in weather" + + def test_spec_example_without_tool_names(self): + """Test the spec example without tool names (tools.weather remains unresolved).""" + tokens = [ + TextToken( + type=TextTokenType.SIMPLE_TEXT, rawString="What is the weather like in " + ), + TextToken(type=TextTokenType.VARIABLE, rawString="input.city"), + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="?\nUse the "), + TextToken(type=TextTokenType.VARIABLE, rawString="tools.weather"), + TextToken( + type=TextTokenType.SIMPLE_TEXT, + rawString=" tool and put the results in ", + ), + TextToken(type=TextTokenType.VARIABLE, rawString="output.weather"), + ] + result = build_string_from_tokens(tokens, {"city": "London"}) + # tools.weather is not resolved (returns raw string), output.weather returns "weather" + assert ( + result + == "What is the weather like in London?\nUse the tools.weather tool and put the results in weather" + ) + + def test_spec_example_with_tool_names(self): + """Test the spec example with tool names resolves correctly.""" + tokens = [ + TextToken( + type=TextTokenType.SIMPLE_TEXT, rawString="What is the weather like in " + ), + TextToken(type=TextTokenType.VARIABLE, rawString="input.city"), + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="?\nUse the "), + TextToken(type=TextTokenType.VARIABLE, rawString="tools.weather"), + TextToken( + type=TextTokenType.SIMPLE_TEXT, + rawString=" tool and put the results in ", + ), + TextToken(type=TextTokenType.VARIABLE, rawString="output.weather"), + ] + result = build_string_from_tokens( + tokens, {"city": "London"}, tool_names=["weather"] + ) + assert ( + result + == "What is the weather like in London?\nUse the weather tool and put the results in weather" + ) + + def test_with_nested_input_object(self): + """Test nested input objects are replaced correctly.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="The person is "), + TextToken(type=TextTokenType.VARIABLE, rawString="input.person.age"), + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString=" years old"), + ] + result = build_string_from_tokens( + tokens, {"person": {"age": 25, "name": "John"}} + ) + assert result == "The person is 25 years old" + + def test_expression_token_copies_raw_string(self): + """Test expression tokens are copied as-is.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Result: "), + TextToken(type=TextTokenType.EXPRESSION, rawString="{{some.expression}}"), + ] + result = build_string_from_tokens(tokens, {}) + assert result == "Result: {{some.expression}}" + + def test_input_variable_returns_entire_input_as_json(self): + """Test 'input' variable returns entire input as JSON.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Input: "), + TextToken(type=TextTokenType.VARIABLE, rawString="input"), + ] + result = build_string_from_tokens(tokens, {"city": "London", "temp": 20}) + assert result == 'Input: {"city": "London", "temp": 20}' + + @pytest.mark.parametrize( + "raw_string,expected", + [ + ("", ""), + (" ", " "), + ("unknown", "unknown"), + ("unknown.path", "unknown.path"), + ], + ) + def test_unknown_variable_patterns_return_raw_string(self, raw_string, expected): + """Test unknown variable patterns return the raw string.""" + tokens = [TextToken(type=TextTokenType.VARIABLE, rawString=raw_string)] + result = build_string_from_tokens(tokens, {"some": "value"}) + assert result == expected + + def test_missing_input_value_returns_raw_string(self): + """Test missing input values return the raw variable string.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Value: "), + TextToken(type=TextTokenType.VARIABLE, rawString="input.missing"), + ] + result = build_string_from_tokens(tokens, {"other": "value"}) + assert result == "Value: input.missing" + + def test_tool_reference_resolves_to_tool_name(self): + """Test tools.* variable resolves to tool name.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Use the "), + TextToken(type=TextTokenType.VARIABLE, rawString="tools.weather"), + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString=" tool"), + ] + result = build_string_from_tokens( + tokens, {}, tool_names=["weather", "calculator"] + ) + assert result == "Use the weather tool" + + def test_tool_reference_case_insensitive(self): + """Test tools.* variable resolution is case-insensitive.""" + tokens = [ + TextToken(type=TextTokenType.VARIABLE, rawString="tools.WEATHER"), + ] + result = build_string_from_tokens(tokens, {}, tool_names=["weather"]) + assert result == "weather" + + def test_tool_reference_not_found_returns_raw_string(self): + """Test unknown tool reference returns raw string.""" + tokens = [ + TextToken(type=TextTokenType.VARIABLE, rawString="tools.unknown"), + ] + result = build_string_from_tokens(tokens, {}, tool_names=["weather"]) + assert result == "tools.unknown" + + def test_escalation_reference_resolves_to_escalation_name(self): + """Test escalations.* variable resolves to escalation name.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Escalate to "), + TextToken(type=TextTokenType.VARIABLE, rawString="escalations.support"), + ] + result = build_string_from_tokens(tokens, {}, escalation_names=["support"]) + assert result == "Escalate to support" + + def test_context_reference_resolves_to_context_name(self): + """Test contexts.* variable resolves to context name.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Search in "), + TextToken(type=TextTokenType.VARIABLE, rawString="contexts.docs"), + ] + result = build_string_from_tokens(tokens, {}, context_names=["docs"]) + assert result == "Search in docs" + + def test_multiple_resource_types_together(self): + """Test using multiple resource types in one prompt.""" + tokens = [ + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Use "), + TextToken(type=TextTokenType.VARIABLE, rawString="tools.weather"), + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString=" with "), + TextToken(type=TextTokenType.VARIABLE, rawString="contexts.docs"), + TextToken(type=TextTokenType.SIMPLE_TEXT, rawString=" or escalate to "), + TextToken(type=TextTokenType.VARIABLE, rawString="escalations.support"), + ] + result = build_string_from_tokens( + tokens, + {}, + tool_names=["weather"], + escalation_names=["support"], + context_names=["docs"], + ) + assert result == "Use weather with docs or escalate to support" + + +class TestSafeGetNested: + """Test nested dictionary access.""" + + def test_simple_key(self): + """Test accessing simple top-level key.""" + data = {"name": "Alice"} + assert safe_get_nested(data, "name") == "Alice" + + def test_nested_key(self): + """Test accessing nested keys with dot notation.""" + data = {"user": {"name": "Alice", "age": 30}} + assert safe_get_nested(data, "user.name") == "Alice" + assert safe_get_nested(data, "user.age") == 30 + + def test_missing_key(self): + """Test accessing missing keys returns None.""" + data = {"user": {"name": "Alice"}} + assert safe_get_nested(data, "user.missing") is None + assert safe_get_nested(data, "missing.key") is None + + def test_array_value(self): + """Test accessing array values.""" + data = {"items": [1, 2, 3]} + assert safe_get_nested(data, "items") == [1, 2, 3] + + def test_deeply_nested(self): + """Test deeply nested access.""" + data = {"level1": {"level2": {"level3": {"value": "deep"}}}} + assert safe_get_nested(data, "level1.level2.level3.value") == "deep" + + def test_null_intermediate_value(self): + """Test access through null intermediate value.""" + data = {"user": None} + assert safe_get_nested(data, "user.name") is None + + +class TestSerializeValue: + """Test value serialization.""" + + def test_string(self): + """Test serializing strings.""" + assert serialize_argument("hello") == "hello" + + def test_number(self): + """Test serializing numbers.""" + assert serialize_argument(42) == "42" + assert serialize_argument(3.14) == "3.14" + + def test_list(self): + """Test serializing lists.""" + assert serialize_argument([1, 2, 3]) == "[1, 2, 3]" + + def test_dict(self): + """Test serializing dictionaries.""" + result = serialize_argument({"key": "value"}) + assert '"key"' in result + assert '"value"' in result + + def test_none(self): + """Test serializing None returns empty string.""" + assert serialize_argument(None) == "" + + def test_boolean(self): + """Test serializing booleans (JSON-style lowercase).""" + assert serialize_argument(True) == "true" + assert serialize_argument(False) == "false" From a87616b0a0ea975fd081e1d51ed345afb9f19a79 Mon Sep 17 00:00:00 2001 From: Andrei Ancuta Date: Mon, 12 Jan 2026 11:01:03 +0200 Subject: [PATCH 6/7] fix: rawString -> raw_string --- tests/agent/utils/test_text_tokens.py | 91 ++++++++++++++------------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/tests/agent/utils/test_text_tokens.py b/tests/agent/utils/test_text_tokens.py index d967e95ce..abdbeec00 100644 --- a/tests/agent/utils/test_text_tokens.py +++ b/tests/agent/utils/test_text_tokens.py @@ -16,8 +16,8 @@ class TestBuildStringFromTokens: def test_simple_text_tokens(self): """Test simple text tokens are joined correctly.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Hello "), - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="World"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Hello "), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="World"), ] result = build_string_from_tokens(tokens, {}) assert result == "Hello World" @@ -26,10 +26,11 @@ def test_with_input_variable_replacement(self): """Test input.* variable tokens are replaced with input values.""" tokens = [ TextToken( - type=TextTokenType.SIMPLE_TEXT, rawString="What is the weather like in " + type=TextTokenType.SIMPLE_TEXT, + raw_string="What is the weather like in ", ), - TextToken(type=TextTokenType.VARIABLE, rawString="input.city"), - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="?"), + TextToken(type=TextTokenType.VARIABLE, raw_string="input.city"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="?"), ] result = build_string_from_tokens(tokens, {"city": "London"}) assert result == "What is the weather like in London?" @@ -37,8 +38,8 @@ def test_with_input_variable_replacement(self): def test_with_output_variable(self): """Test output.* variable tokens return the path.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Put the results in "), - TextToken(type=TextTokenType.VARIABLE, rawString="output.weather"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Put the results in "), + TextToken(type=TextTokenType.VARIABLE, raw_string="output.weather"), ] result = build_string_from_tokens(tokens, {}) assert result == "Put the results in weather" @@ -47,16 +48,17 @@ def test_spec_example_without_tool_names(self): """Test the spec example without tool names (tools.weather remains unresolved).""" tokens = [ TextToken( - type=TextTokenType.SIMPLE_TEXT, rawString="What is the weather like in " + type=TextTokenType.SIMPLE_TEXT, + raw_string="What is the weather like in ", ), - TextToken(type=TextTokenType.VARIABLE, rawString="input.city"), - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="?\nUse the "), - TextToken(type=TextTokenType.VARIABLE, rawString="tools.weather"), + TextToken(type=TextTokenType.VARIABLE, raw_string="input.city"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="?\nUse the "), + TextToken(type=TextTokenType.VARIABLE, raw_string="tools.weather"), TextToken( type=TextTokenType.SIMPLE_TEXT, - rawString=" tool and put the results in ", + raw_string=" tool and put the results in ", ), - TextToken(type=TextTokenType.VARIABLE, rawString="output.weather"), + TextToken(type=TextTokenType.VARIABLE, raw_string="output.weather"), ] result = build_string_from_tokens(tokens, {"city": "London"}) # tools.weather is not resolved (returns raw string), output.weather returns "weather" @@ -69,16 +71,17 @@ def test_spec_example_with_tool_names(self): """Test the spec example with tool names resolves correctly.""" tokens = [ TextToken( - type=TextTokenType.SIMPLE_TEXT, rawString="What is the weather like in " + type=TextTokenType.SIMPLE_TEXT, + raw_string="What is the weather like in ", ), - TextToken(type=TextTokenType.VARIABLE, rawString="input.city"), - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="?\nUse the "), - TextToken(type=TextTokenType.VARIABLE, rawString="tools.weather"), + TextToken(type=TextTokenType.VARIABLE, raw_string="input.city"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="?\nUse the "), + TextToken(type=TextTokenType.VARIABLE, raw_string="tools.weather"), TextToken( type=TextTokenType.SIMPLE_TEXT, - rawString=" tool and put the results in ", + raw_string=" tool and put the results in ", ), - TextToken(type=TextTokenType.VARIABLE, rawString="output.weather"), + TextToken(type=TextTokenType.VARIABLE, raw_string="output.weather"), ] result = build_string_from_tokens( tokens, {"city": "London"}, tool_names=["weather"] @@ -91,9 +94,9 @@ def test_spec_example_with_tool_names(self): def test_with_nested_input_object(self): """Test nested input objects are replaced correctly.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="The person is "), - TextToken(type=TextTokenType.VARIABLE, rawString="input.person.age"), - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString=" years old"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="The person is "), + TextToken(type=TextTokenType.VARIABLE, raw_string="input.person.age"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string=" years old"), ] result = build_string_from_tokens( tokens, {"person": {"age": 25, "name": "John"}} @@ -103,8 +106,8 @@ def test_with_nested_input_object(self): def test_expression_token_copies_raw_string(self): """Test expression tokens are copied as-is.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Result: "), - TextToken(type=TextTokenType.EXPRESSION, rawString="{{some.expression}}"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Result: "), + TextToken(type=TextTokenType.EXPRESSION, raw_string="{{some.expression}}"), ] result = build_string_from_tokens(tokens, {}) assert result == "Result: {{some.expression}}" @@ -112,8 +115,8 @@ def test_expression_token_copies_raw_string(self): def test_input_variable_returns_entire_input_as_json(self): """Test 'input' variable returns entire input as JSON.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Input: "), - TextToken(type=TextTokenType.VARIABLE, rawString="input"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Input: "), + TextToken(type=TextTokenType.VARIABLE, raw_string="input"), ] result = build_string_from_tokens(tokens, {"city": "London", "temp": 20}) assert result == 'Input: {"city": "London", "temp": 20}' @@ -129,15 +132,15 @@ def test_input_variable_returns_entire_input_as_json(self): ) def test_unknown_variable_patterns_return_raw_string(self, raw_string, expected): """Test unknown variable patterns return the raw string.""" - tokens = [TextToken(type=TextTokenType.VARIABLE, rawString=raw_string)] + tokens = [TextToken(type=TextTokenType.VARIABLE, raw_string=raw_string)] result = build_string_from_tokens(tokens, {"some": "value"}) assert result == expected def test_missing_input_value_returns_raw_string(self): """Test missing input values return the raw variable string.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Value: "), - TextToken(type=TextTokenType.VARIABLE, rawString="input.missing"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Value: "), + TextToken(type=TextTokenType.VARIABLE, raw_string="input.missing"), ] result = build_string_from_tokens(tokens, {"other": "value"}) assert result == "Value: input.missing" @@ -145,9 +148,9 @@ def test_missing_input_value_returns_raw_string(self): def test_tool_reference_resolves_to_tool_name(self): """Test tools.* variable resolves to tool name.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Use the "), - TextToken(type=TextTokenType.VARIABLE, rawString="tools.weather"), - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString=" tool"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Use the "), + TextToken(type=TextTokenType.VARIABLE, raw_string="tools.weather"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string=" tool"), ] result = build_string_from_tokens( tokens, {}, tool_names=["weather", "calculator"] @@ -157,7 +160,7 @@ def test_tool_reference_resolves_to_tool_name(self): def test_tool_reference_case_insensitive(self): """Test tools.* variable resolution is case-insensitive.""" tokens = [ - TextToken(type=TextTokenType.VARIABLE, rawString="tools.WEATHER"), + TextToken(type=TextTokenType.VARIABLE, raw_string="tools.WEATHER"), ] result = build_string_from_tokens(tokens, {}, tool_names=["weather"]) assert result == "weather" @@ -165,7 +168,7 @@ def test_tool_reference_case_insensitive(self): def test_tool_reference_not_found_returns_raw_string(self): """Test unknown tool reference returns raw string.""" tokens = [ - TextToken(type=TextTokenType.VARIABLE, rawString="tools.unknown"), + TextToken(type=TextTokenType.VARIABLE, raw_string="tools.unknown"), ] result = build_string_from_tokens(tokens, {}, tool_names=["weather"]) assert result == "tools.unknown" @@ -173,8 +176,8 @@ def test_tool_reference_not_found_returns_raw_string(self): def test_escalation_reference_resolves_to_escalation_name(self): """Test escalations.* variable resolves to escalation name.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Escalate to "), - TextToken(type=TextTokenType.VARIABLE, rawString="escalations.support"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Escalate to "), + TextToken(type=TextTokenType.VARIABLE, raw_string="escalations.support"), ] result = build_string_from_tokens(tokens, {}, escalation_names=["support"]) assert result == "Escalate to support" @@ -182,8 +185,8 @@ def test_escalation_reference_resolves_to_escalation_name(self): def test_context_reference_resolves_to_context_name(self): """Test contexts.* variable resolves to context name.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Search in "), - TextToken(type=TextTokenType.VARIABLE, rawString="contexts.docs"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Search in "), + TextToken(type=TextTokenType.VARIABLE, raw_string="contexts.docs"), ] result = build_string_from_tokens(tokens, {}, context_names=["docs"]) assert result == "Search in docs" @@ -191,12 +194,12 @@ def test_context_reference_resolves_to_context_name(self): def test_multiple_resource_types_together(self): """Test using multiple resource types in one prompt.""" tokens = [ - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString="Use "), - TextToken(type=TextTokenType.VARIABLE, rawString="tools.weather"), - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString=" with "), - TextToken(type=TextTokenType.VARIABLE, rawString="contexts.docs"), - TextToken(type=TextTokenType.SIMPLE_TEXT, rawString=" or escalate to "), - TextToken(type=TextTokenType.VARIABLE, rawString="escalations.support"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string="Use "), + TextToken(type=TextTokenType.VARIABLE, raw_string="tools.weather"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string=" with "), + TextToken(type=TextTokenType.VARIABLE, raw_string="contexts.docs"), + TextToken(type=TextTokenType.SIMPLE_TEXT, raw_string=" or escalate to "), + TextToken(type=TextTokenType.VARIABLE, raw_string="escalations.support"), ] result = build_string_from_tokens( tokens, From 73c0bb81dba10a5d40e432df72c194b0d1282dea Mon Sep 17 00:00:00 2001 From: Andrei Ancuta Date: Mon, 12 Jan 2026 11:14:43 +0200 Subject: [PATCH 7/7] feat: argument_properties for internal tools --- src/uipath/agent/models/agent.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/uipath/agent/models/agent.py b/src/uipath/agent/models/agent.py index 69174140f..916f24bcf 100644 --- a/src/uipath/agent/models/agent.py +++ b/src/uipath/agent/models/agent.py @@ -502,6 +502,9 @@ class AgentInternalToolResourceConfig(BaseAgentToolResourceConfig): arguments: Optional[Dict[str, Any]] = Field(default_factory=dict) is_enabled: Optional[bool] = Field(None, alias="isEnabled") output_schema: Dict[str, Any] = Field(..., alias="outputSchema") + argument_properties: Dict[str, AgentToolArgumentProperties] = Field( + {}, alias="argumentProperties" + ) class AgentUnknownToolResourceConfig(BaseAgentToolResourceConfig):