diff --git a/packages/uipath-google-adk/pyproject.toml b/packages/uipath-google-adk/pyproject.toml index e728c4c7..8c3625be 100644 --- a/packages/uipath-google-adk/pyproject.toml +++ b/packages/uipath-google-adk/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "uipath-google-adk" -version = "0.0.4" +version = "0.0.5" description = "Python SDK that enables developers to build and deploy Google ADK agents to the UiPath Cloud Platform" readme = "README.md" requires-python = ">=3.11" diff --git a/packages/uipath-google-adk/samples/multi-agent/pyproject.toml b/packages/uipath-google-adk/samples/multi-agent/pyproject.toml index b5a668ca..72572b32 100644 --- a/packages/uipath-google-adk/samples/multi-agent/pyproject.toml +++ b/packages/uipath-google-adk/samples/multi-agent/pyproject.toml @@ -2,6 +2,7 @@ name = "multi-agent" version = "0.0.1" description = "Google ADK multi-agent example with sub-agents and tools" +authors = [{ name = "John Doe" }] readme = "README.md" requires-python = ">=3.11" dependencies = [ diff --git a/packages/uipath-google-adk/samples/quickstart-agent/README.md b/packages/uipath-google-adk/samples/quickstart-agent/README.md index 08def611..c0e71f6e 100644 --- a/packages/uipath-google-adk/samples/quickstart-agent/README.md +++ b/packages/uipath-google-adk/samples/quickstart-agent/README.md @@ -19,7 +19,7 @@ flowchart TB ## Run ``` -uipath run agent "What's the weather in San Francisco?" +uipath run agent '{"messages": [{"contentParts": [{"data": {"inline": "What is the weather in San Francisco?"}}], "role": "user"}]}' ``` ## Debug diff --git a/packages/uipath-google-adk/samples/quickstart-agent/pyproject.toml b/packages/uipath-google-adk/samples/quickstart-agent/pyproject.toml index 2fa77ef4..630b33f0 100644 --- a/packages/uipath-google-adk/samples/quickstart-agent/pyproject.toml +++ b/packages/uipath-google-adk/samples/quickstart-agent/pyproject.toml @@ -2,6 +2,7 @@ name = "quickstart-agent" version = "0.0.1" description = "Quickstart Google ADK agent example" +authors = [{ name = "John Doe" }] readme = "README.md" requires-python = ">=3.11" dependencies = [ @@ -17,3 +18,4 @@ dev = [ [tool.uv] override-dependencies = ["opentelemetry-sdk>=1.39.0,<1.40.0"] + diff --git a/packages/uipath-google-adk/samples/typed-agent/pyproject.toml b/packages/uipath-google-adk/samples/typed-agent/pyproject.toml index c174bc13..81866b3f 100644 --- a/packages/uipath-google-adk/samples/typed-agent/pyproject.toml +++ b/packages/uipath-google-adk/samples/typed-agent/pyproject.toml @@ -2,6 +2,7 @@ name = "typed-agent" version = "0.0.1" description = "Google ADK agent with strongly-typed input/output schemas" +authors = [{ name = "John Doe" }] readme = "README.md" requires-python = ">=3.11" dependencies = [ diff --git a/packages/uipath-google-adk/src/uipath_google_adk/runtime/runtime.py b/packages/uipath-google-adk/src/uipath_google_adk/runtime/runtime.py index 122177ab..eff399b2 100644 --- a/packages/uipath-google-adk/src/uipath_google_adk/runtime/runtime.py +++ b/packages/uipath-google-adk/src/uipath_google_adk/runtime/runtime.py @@ -433,7 +433,14 @@ def _create_success_result(self, output: Any) -> UiPathRuntimeResult: serialized_output = serialize_defaults(output) if not isinstance(serialized_output, dict): - serialized_output = {"result": serialized_output} + serialized_output = { + "messages": [ + { + "role": "assistant", + "contentParts": [{"data": {"inline": serialized_output}}], + } + ] + } return UiPathRuntimeResult( output=serialized_output, diff --git a/packages/uipath-google-adk/src/uipath_google_adk/runtime/schema.py b/packages/uipath-google-adk/src/uipath_google_adk/runtime/schema.py index 365f0dd1..052114fa 100644 --- a/packages/uipath-google-adk/src/uipath_google_adk/runtime/schema.py +++ b/packages/uipath-google-adk/src/uipath_google_adk/runtime/schema.py @@ -9,7 +9,6 @@ from google.adk.tools.base_tool import BaseTool from google.adk.tools.base_toolset import BaseToolset from pydantic import BaseModel, TypeAdapter -from uipath.core.chat import UiPathConversationMessage from uipath.runtime.schema import ( UiPathRuntimeEdge, UiPathRuntimeGraph, @@ -193,50 +192,61 @@ def get_entrypoints_schema(agent: BaseAgent) -> dict[str, Any]: return schema -def _conversation_messages_schema() -> dict[str, Any]: - """Generate JSON schema for list[UiPathConversationMessage].""" - adapter = TypeAdapter(list[UiPathConversationMessage]) - return adapter.json_schema() - - -def _default_input_schema() -> dict[str, Any]: - """Default input schema using UiPath conversation message format.""" - messages_schema = _conversation_messages_schema() - schema: dict[str, Any] = { +def _conversation_message_item_schema() -> dict[str, Any]: + """Minimal message schema: only role and contentParts required, contentParts items only need data.inline.""" + return { "type": "object", "properties": { - "messages": { + "role": {"type": "string"}, + "contentParts": { "type": "array", - "items": messages_schema["items"], - "title": "Messages", - "description": "UiPath conversation messages", - } + "items": { + "type": "object", + "properties": { + "mimeType": {"type": "string"}, + "data": { + "type": "object", + "properties": { + "inline": {}, + }, + "required": ["inline"], + }, + "citations": {"type": "array", "items": {"type": "object"}}, + }, + "required": ["data"], + }, + }, + "toolCalls": {"type": "array", "items": {"type": "object"}}, + "interrupts": {"type": "array", "items": {"type": "object"}}, }, - "required": ["messages"], + "required": ["role", "contentParts"], } - if "$defs" in messages_schema: - schema["$defs"] = messages_schema["$defs"] - return schema -def _default_output_schema() -> dict[str, Any]: - """Default output schema using UiPath conversation message format.""" - messages_schema = _conversation_messages_schema() - schema: dict[str, Any] = { +def _default_messages_schema() -> dict[str, Any]: + """Default messages schema using minimal UiPath conversation message format.""" + return { "type": "object", "properties": { "messages": { "type": "array", - "items": messages_schema["items"], + "items": _conversation_message_item_schema(), "title": "Messages", "description": "UiPath conversation messages", } }, "required": ["messages"], } - if "$defs" in messages_schema: - schema["$defs"] = messages_schema["$defs"] - return schema + + +def _default_input_schema() -> dict[str, Any]: + """Default input schema using UiPath conversation message format.""" + return _default_messages_schema() + + +def _default_output_schema() -> dict[str, Any]: + """Default output schema using UiPath conversation message format.""" + return _default_messages_schema() def get_agent_graph(agent: BaseAgent) -> UiPathRuntimeGraph: diff --git a/packages/uipath-google-adk/tests/test_schema.py b/packages/uipath-google-adk/tests/test_schema.py index a7dff907..ad745b1c 100644 --- a/packages/uipath-google-adk/tests/test_schema.py +++ b/packages/uipath-google-adk/tests/test_schema.py @@ -2,11 +2,13 @@ from unittest.mock import MagicMock +import jsonschema # type: ignore[import-untyped] import pytest from google.adk.agents import BaseAgent, LlmAgent from pydantic import BaseModel from uipath_google_adk.runtime.schema import ( + _default_input_schema, get_agent_graph, get_entrypoints_schema, resolve_output_schema, @@ -135,6 +137,94 @@ def test_output_schema_takes_precedence_over_output_key(self): assert "response" not in schema["output"]["properties"] +class TestDefaultMessagesSchemaValidation: + """Tests that the default messages schema accepts minimal input.""" + + def test_minimal_message_with_inline_data(self): + """Minimal message: only role and contentParts with data.inline.""" + schema = _default_input_schema() + data = { + "messages": [ + { + "role": "user", + "contentParts": [ + {"data": {"inline": "What is the weather in San Francisco?"}} + ], + } + ] + } + jsonschema.validate(data, schema) + + def test_message_with_all_optional_fields(self): + """Message with all optional fields populated.""" + schema = _default_input_schema() + data = { + "messages": [ + { + "role": "user", + "contentParts": [ + { + "mimeType": "text/plain", + "data": {"inline": "Hello"}, + "citations": [], + } + ], + "toolCalls": [], + "interrupts": [], + } + ] + } + jsonschema.validate(data, schema) + + def test_missing_role_fails(self): + """Message without role should fail validation.""" + schema = _default_input_schema() + data = { + "messages": [ + { + "contentParts": [{"data": {"inline": "Hello"}}], + } + ] + } + with pytest.raises(jsonschema.ValidationError): + jsonschema.validate(data, schema) + + def test_missing_content_parts_fails(self): + """Message without contentParts should fail validation.""" + schema = _default_input_schema() + data = {"messages": [{"role": "user"}]} + with pytest.raises(jsonschema.ValidationError): + jsonschema.validate(data, schema) + + def test_content_part_missing_data_fails(self): + """Content part without data should fail validation.""" + schema = _default_input_schema() + data = { + "messages": [ + { + "role": "user", + "contentParts": [{"mimeType": "text/plain"}], + } + ] + } + with pytest.raises(jsonschema.ValidationError): + jsonschema.validate(data, schema) + + def test_data_missing_inline_fails(self): + """Data without inline should fail validation.""" + schema = _default_input_schema() + data = { + "messages": [ + { + "role": "user", + "contentParts": [{"data": {}}], + } + ] + } + with pytest.raises(jsonschema.ValidationError): + jsonschema.validate(data, schema) + + class TestResolveOutputSchema: """Tests for resolve_output_schema validation.""" diff --git a/packages/uipath-google-adk/uv.lock b/packages/uipath-google-adk/uv.lock index ae4b5268..62dcfb14 100644 --- a/packages/uipath-google-adk/uv.lock +++ b/packages/uipath-google-adk/uv.lock @@ -3599,7 +3599,7 @@ wheels = [ [[package]] name = "uipath-google-adk" -version = "0.0.4" +version = "0.0.5" source = { editable = "." } dependencies = [ { name = "google-adk" },