Skip to content

Commit 49b6782

Browse files
authored
Merge branch 'main' into feat/a2a-preserve-session-identity
2 parents 5c1106d + 38a30a4 commit 49b6782

File tree

3 files changed

+99
-28
lines changed

3 files changed

+99
-28
lines changed

src/google/adk/cli/cli_deploy.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -771,25 +771,27 @@ def to_agent_engine(
771771
)
772772
agent_config['description'] = description
773773

774-
if not requirements_file:
774+
requirements_txt_path = os.path.join(agent_src_path, 'requirements.txt')
775+
if requirements_file:
776+
if os.path.exists(requirements_txt_path):
777+
click.echo(
778+
f'Overwriting {requirements_txt_path} with {requirements_file}'
779+
)
780+
shutil.copyfile(requirements_file, requirements_txt_path)
781+
elif 'requirements_file' in agent_config:
782+
if os.path.exists(requirements_txt_path):
783+
click.echo(
784+
f'Overwriting {requirements_txt_path} with'
785+
f' {agent_config["requirements_file"]}'
786+
)
787+
shutil.copyfile(agent_config['requirements_file'], requirements_txt_path)
788+
else:
775789
# Attempt to read requirements from requirements.txt in the dir (if any).
776-
requirements_txt_path = os.path.join(agent_src_path, 'requirements.txt')
777790
if not os.path.exists(requirements_txt_path):
778791
click.echo(f'Creating {requirements_txt_path}...')
779792
with open(requirements_txt_path, 'w', encoding='utf-8') as f:
780793
f.write('google-cloud-aiplatform[adk,agent_engines]')
781794
click.echo(f'Created {requirements_txt_path}')
782-
agent_config['requirements_file'] = agent_config.get(
783-
'requirements',
784-
requirements_txt_path,
785-
)
786-
else:
787-
if 'requirements_file' in agent_config:
788-
click.echo(
789-
'Overriding requirements in agent engine config with '
790-
f'{requirements_file}'
791-
)
792-
agent_config['requirements_file'] = requirements_file
793795
agent_config['requirements_file'] = f'{temp_folder}/requirements.txt'
794796

795797
env_vars = {}

src/google/adk/tools/_gemini_schema_util.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,35 @@ def _to_snake_case(text: str) -> str:
7474
return text
7575

7676

77+
def _sanitize_schema_type(
78+
schema: dict[str, Any], preserve_null_type: bool = False
79+
) -> dict[str, Any]:
80+
if not schema:
81+
schema["type"] = "object"
82+
if isinstance(schema.get("type"), list):
83+
types_no_null = [t for t in schema["type"] if t != "null"]
84+
nullable = len(types_no_null) != len(schema["type"])
85+
if "array" in types_no_null:
86+
non_null_type = "array"
87+
else:
88+
non_null_type = types_no_null[0] if types_no_null else "object"
89+
if nullable:
90+
schema["type"] = [non_null_type, "null"]
91+
else:
92+
schema["type"] = non_null_type
93+
elif schema.get("type") == "null" and not preserve_null_type:
94+
schema["type"] = ["object", "null"]
95+
96+
schema_type = schema.get("type")
97+
is_array = schema_type == "array" or (
98+
isinstance(schema_type, list) and "array" in schema_type
99+
)
100+
if is_array:
101+
schema.setdefault("items", {"type": "string"})
102+
103+
return schema
104+
105+
77106
def _dereference_schema(schema: dict[str, Any]) -> dict[str, Any]:
78107
"""Resolves $ref pointers in a JSON schema."""
79108

@@ -113,7 +142,7 @@ def _resolve_refs(sub_schema: Any) -> Any:
113142

114143

115144
def _sanitize_schema_formats_for_gemini(
116-
schema: dict[str, Any],
145+
schema: dict[str, Any], preserve_null_type: bool = False
117146
) -> dict[str, Any]:
118147
"""Filters the schema to only include fields that are supported by JSONSchema."""
119148
supported_fields: set[str] = set(_ExtendedJSONSchema.model_fields.keys())
@@ -135,8 +164,12 @@ def _sanitize_schema_formats_for_gemini(
135164
field_value
136165
)
137166
elif field_name in list_schema_field_names:
167+
should_preserve = field_name in ("any_of", "one_of")
138168
snake_case_schema[field_name] = [
139-
_sanitize_schema_formats_for_gemini(value) for value in field_value
169+
_sanitize_schema_formats_for_gemini(
170+
value, preserve_null_type=should_preserve
171+
)
172+
for value in field_value
140173
]
141174
elif field_name in dict_schema_field_names and field_value is not None:
142175
snake_case_schema[field_name] = {
@@ -158,11 +191,7 @@ def _sanitize_schema_formats_for_gemini(
158191
elif field_name in supported_fields and field_value is not None:
159192
snake_case_schema[field_name] = field_value
160193

161-
# If the schema is empty, assume it has the type of object
162-
if not snake_case_schema:
163-
snake_case_schema["type"] = "object"
164-
165-
return snake_case_schema
194+
return _sanitize_schema_type(snake_case_schema, preserve_null_type)
166195

167196

168197
def _to_gemini_schema(openapi_schema: dict[str, Any]) -> Schema:

tests/unittests/tools/test_gemini_schema_util.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,15 @@ def test_to_gemini_schema_array_string_types(self):
6666
"nullable_string": {"type": ["string", "null"]},
6767
"nullable_number": {"type": ["null", "integer"]},
6868
"nullable_object": {"type": ["object", "null"]},
69+
"object_nullable": {"type": "null"},
6970
"multi_types_nullable": {"type": ["string", "null", "integer"]},
7071
"only_null": {"type": "null"},
7172
"empty_default_object": {},
73+
"empty_list_type": {"type": []},
74+
"multi_type_with_array_nullable": {
75+
"type": ["string", "array", "null"]
76+
},
77+
"multi_type_with_array_nonnullable": {"type": ["integer", "array"]},
7278
},
7379
}
7480
gemini_schema = _to_gemini_schema(openapi_schema)
@@ -88,18 +94,38 @@ def test_to_gemini_schema_array_string_types(self):
8894
assert gemini_schema.properties["nullable_object"].type == Type.OBJECT
8995
assert gemini_schema.properties["nullable_object"].nullable
9096

91-
assert gemini_schema.properties["multi_types_nullable"].any_of == [
92-
Schema(type=Type.STRING),
93-
Schema(type=Type.INTEGER),
94-
]
97+
assert gemini_schema.properties["object_nullable"].type == Type.OBJECT
98+
assert gemini_schema.properties["object_nullable"].nullable
99+
100+
assert gemini_schema.properties["multi_types_nullable"].type == Type.STRING
95101
assert gemini_schema.properties["multi_types_nullable"].nullable
96102

97-
assert gemini_schema.properties["only_null"].type is None
103+
assert gemini_schema.properties["only_null"].type == Type.OBJECT
98104
assert gemini_schema.properties["only_null"].nullable
99105

106+
assert gemini_schema.properties["multi_types_nullable"].type == Type.STRING
107+
assert gemini_schema.properties["multi_types_nullable"].nullable
108+
100109
assert gemini_schema.properties["empty_default_object"].type == Type.OBJECT
101110
assert gemini_schema.properties["empty_default_object"].nullable is None
102111

112+
assert gemini_schema.properties["empty_list_type"].type == Type.OBJECT
113+
assert not gemini_schema.properties["empty_list_type"].nullable
114+
115+
assert (
116+
gemini_schema.properties["multi_type_with_array_nullable"].type
117+
== Type.ARRAY
118+
)
119+
assert gemini_schema.properties["multi_type_with_array_nullable"].nullable
120+
121+
assert (
122+
gemini_schema.properties["multi_type_with_array_nonnullable"].type
123+
== Type.ARRAY
124+
)
125+
assert not gemini_schema.properties[
126+
"multi_type_with_array_nonnullable"
127+
].nullable
128+
103129
def test_to_gemini_schema_nested_objects(self):
104130
openapi_schema = {
105131
"type": "object",
@@ -144,6 +170,20 @@ def test_to_gemini_schema_nested_array(self):
144170
gemini_schema = _to_gemini_schema(openapi_schema)
145171
assert gemini_schema.items.properties["name"].type == Type.STRING
146172

173+
def test_to_gemini_schema_array_without_items_gets_default(self):
174+
openapi_schema = {"type": "array"}
175+
gemini_schema = _to_gemini_schema(openapi_schema)
176+
assert gemini_schema.type == Type.ARRAY
177+
assert not gemini_schema.nullable
178+
assert gemini_schema.items.type == Type.STRING
179+
180+
def test_to_gemini_schema_nullable_array_without_items_gets_default(self):
181+
openapi_schema = {"type": ["array", "null"]}
182+
gemini_schema = _to_gemini_schema(openapi_schema)
183+
assert gemini_schema.type == Type.ARRAY
184+
assert gemini_schema.nullable
185+
assert gemini_schema.items.type == Type.STRING
186+
147187
def test_to_gemini_schema_any_of(self):
148188
openapi_schema = {
149189
"anyOf": [{"type": "string"}, {"type": "integer"}],
@@ -200,7 +240,7 @@ def test_to_gemini_schema_nested_dict(self):
200240
},
201241
}
202242
gemini_schema = _to_gemini_schema(openapi_schema)
203-
# Since metadata is neither properties nor item, it will call to_gemini_schema recursively.
243+
# Since metadata is not properties nor item, it will call to_gemini_schema recursively.
204244
assert isinstance(gemini_schema.properties["metadata"], Schema)
205245
assert (
206246
gemini_schema.properties["metadata"].type == Type.OBJECT
@@ -544,7 +584,7 @@ def test_sanitize_schema_formats_for_gemini_nullable(self):
544584
"properties": {
545585
"case_id": {
546586
"description": "The ID of the case.",
547-
"title": "Case ID",
587+
"title": "Case Id",
548588
"type": "string",
549589
},
550590
"next_page_token": {
@@ -567,7 +607,7 @@ def test_sanitize_schema_formats_for_gemini_nullable(self):
567607
"properties": {
568608
"case_id": {
569609
"description": "The ID of the case.",
570-
"title": "Case ID",
610+
"title": "Case Id",
571611
"type": "string",
572612
},
573613
"next_page_token": {

0 commit comments

Comments
 (0)