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
2 changes: 1 addition & 1 deletion packages/uipath-google-adk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -17,3 +18,4 @@ dev = [

[tool.uv]
override-dependencies = ["opentelemetry-sdk>=1.39.0,<1.40.0"]

Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
66 changes: 38 additions & 28 deletions packages/uipath-google-adk/src/uipath_google_adk/runtime/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down
90 changes: 90 additions & 0 deletions packages/uipath-google-adk/tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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."""

Expand Down
2 changes: 1 addition & 1 deletion packages/uipath-google-adk/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.