Skip to content

Commit 3d99b95

Browse files
committed
Normalize integer output enums for Google ADK
1 parent 0e479b2 commit 3d99b95

3 files changed

Lines changed: 63 additions & 3 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ to include examples, links to docs, or any other relevant information.
6969
the workflow sandbox.
7070
- `contrib.google_adk_agents`: agents with an `output_schema` no longer fail every workflow task
7171
when calling the model. The schema type is now sent to the model activity as its JSON schema.
72+
Integer-valued output enums are normalized to strings to match Google GenAI.
7273
- `GoogleAdkPlugin` now passes OpenTelemetry modules through the workflow sandbox so ADK 2.9
7374
graph workflows can load their context support during execution.
7475
- `contrib.deepagents`: prevent duplicate input messages after continue-as-new.

‎temporalio/contrib/google_adk_agents/_model.py‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from collections.abc import AsyncGenerator, Callable
22
from dataclasses import dataclass
33
from datetime import timedelta
4+
from enum import Enum
45

56
from google.adk.models import BaseLlm, LLMRegistry
67
from google.adk.models.llm_request import LlmRequest
@@ -100,14 +101,23 @@ def _with_serializable_response_schema(llm_request: LlmRequest) -> LlmRequest:
100101
(for example a Pydantic model class), which the payload converter cannot
101102
serialize. google-genai and ADK's LiteLlm both turn such a type into its
102103
JSON schema before calling the model, so sending the JSON schema instead
103-
is equivalent.
104+
is equivalent. Integer-valued enums are normalized to string enums to
105+
match google-genai's enum handling.
104106
"""
105107
schema = llm_request.config.response_schema
106108
if schema is None or isinstance(schema, (dict, types.Schema)):
107109
return llm_request
110+
response_schema = TypeAdapter(schema).json_schema()
111+
if (
112+
isinstance(schema, type)
113+
and issubclass(schema, Enum)
114+
and any(isinstance(member.value, int) for member in schema)
115+
):
116+
response_schema["type"] = "string"
117+
response_schema["enum"] = [str(member.value) for member in schema]
108118
request = llm_request.model_copy()
109119
request.config = llm_request.config.model_copy(
110-
update={"response_schema": TypeAdapter(schema).json_schema()}
120+
update={"response_schema": response_schema}
111121
)
112122
return request
113123

‎tests/contrib/google_adk_agents/test_google_adk_agents.py‎

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from abc import ABC, abstractmethod
2323
from collections.abc import AsyncGenerator
2424
from datetime import timedelta
25+
from enum import Enum, IntEnum
2526
from typing import Any
2627

2728
import pytest
@@ -1179,6 +1180,26 @@ class CityWeather(BaseModel):
11791180
temperature_c: float
11801181

11811182

1183+
class NumericChoice(IntEnum):
1184+
FIRST = 10
1185+
SECOND = 20
1186+
1187+
1188+
class IntegerChoice(Enum):
1189+
FIRST = 1
1190+
SECOND = 2
1191+
1192+
1193+
class MixedChoice(Enum):
1194+
FIRST = 1
1195+
SECOND = "other"
1196+
1197+
1198+
class StringChoice(str, Enum):
1199+
FIRST = "first"
1200+
SECOND = "second"
1201+
1202+
11821203
class OutputSchemaModel(TestModel):
11831204
def responses(self) -> list[LlmResponse]:
11841205
return [
@@ -1250,7 +1271,7 @@ async def test_agent_with_output_schema(client: Client):
12501271
assert result == {"city": "Paris", "temperature_c": 17.5}
12511272

12521273

1253-
@pytest.mark.parametrize("schema", [CityWeather, list[CityWeather]])
1274+
@pytest.mark.parametrize("schema", [CityWeather, list[CityWeather], StringChoice])
12541275
def test_output_schema_type_sent_as_json_schema(schema: Any) -> None:
12551276
request = LlmRequest(
12561277
model="gemini-2.0-flash",
@@ -1269,6 +1290,34 @@ def test_output_schema_type_sent_as_json_schema(schema: Any) -> None:
12691290
assert serialized["config"]["response_schema"] == TypeAdapter(schema).json_schema()
12701291

12711292

1293+
@pytest.mark.parametrize(
1294+
("schema", "expected_values"),
1295+
[
1296+
(NumericChoice, ["10", "20"]),
1297+
(IntegerChoice, ["1", "2"]),
1298+
(MixedChoice, ["1", "other"]),
1299+
],
1300+
)
1301+
def test_output_schema_integer_enum_is_serializable(
1302+
schema: type[Enum], expected_values: list[str]
1303+
) -> None:
1304+
request = LlmRequest(
1305+
model="gemini-2.0-flash",
1306+
config=types.GenerateContentConfig(),
1307+
)
1308+
request.set_output_schema(schema)
1309+
1310+
converted = _with_serializable_response_schema(request)
1311+
1312+
assert request.config.response_schema is schema
1313+
converter = GoogleAdkPlugin()._configure_data_converter(None).payload_converter
1314+
payloads = converter.to_payloads([converted])
1315+
restored = converter.from_payloads(payloads, [LlmRequest])[0]
1316+
response_schema = types.Schema.model_validate(restored.config.response_schema)
1317+
assert response_schema.type == types.Type.STRING
1318+
assert response_schema.enum == expected_values
1319+
1320+
12721321
def test_json_output_schema_left_unchanged() -> None:
12731322
request = LlmRequest(
12741323
model="gemini-2.0-flash",

0 commit comments

Comments
 (0)