Skip to content

Commit b4bf0ff

Browse files
authored
Merge branch 'main' into feat/app-name-override
2 parents ce478ce + 4cee97f commit b4bf0ff

3 files changed

Lines changed: 202 additions & 26 deletions

File tree

src/google/adk/agents/llm_agent.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,18 +1089,14 @@ def validate_generate_content_config(
10891089
raise ValueError(
10901090
'Response schema must be set via LlmAgent.output_schema.'
10911091
)
1092-
if generate_content_config.http_options:
1093-
if generate_content_config.http_options.base_url:
1094-
raise ValueError(
1095-
'Base URL is a transport setting and must be set on the model or'
1096-
' its client, not via LlmAgent.generate_content_config.'
1097-
)
1098-
if generate_content_config.http_options.extra_body:
1099-
raise ValueError(
1100-
'Extra body is merged into the request body and can overwrite the'
1101-
' tools, system instruction and response schema rejected above.'
1102-
' Set it on the model or its client.'
1103-
)
1092+
if (
1093+
generate_content_config.http_options
1094+
and generate_content_config.http_options.base_url
1095+
):
1096+
raise ValueError(
1097+
'Base URL is a transport setting and must be set on the model or'
1098+
' its client, not via LlmAgent.generate_content_config.'
1099+
)
11041100
return generate_content_config
11051101

11061102
@override

tests/unittests/agents/test_llm_agent_fields.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -340,29 +340,18 @@ def test_validate_generate_content_config_http_options_base_url_throw():
340340
)
341341

342342

343-
def test_validate_generate_content_config_http_options_extra_body_throw():
344-
"""Tests that an extra request body cannot be set directly in config."""
345-
with pytest.raises(ValueError):
346-
_ = LlmAgent(
347-
name='test_agent',
348-
generate_content_config=types.GenerateContentConfig(
349-
http_options=types.HttpOptions(
350-
extra_body={'systemInstruction': {'parts': [{'text': 'hi'}]}}
351-
)
352-
),
353-
)
354-
355-
356343
def test_validate_generate_content_config_http_options_allowed():
357344
"""Tests that request-time http options remain settable in config."""
345+
extra_body = {'tool_config': {'function_calling_config': {'mode': 'AUTO'}}}
358346
agent = LlmAgent(
359347
name='test_agent',
360348
generate_content_config=types.GenerateContentConfig(
361-
http_options=types.HttpOptions(timeout=1000)
349+
http_options=types.HttpOptions(timeout=1000, extra_body=extra_body)
362350
),
363351
)
364352

365353
assert agent.generate_content_config.http_options.timeout == 1000
354+
assert agent.generate_content_config.http_options.extra_body == extra_body
366355

367356

368357
def test_allow_transfer_by_default():
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Tests for vertex_ai_example_store."""
16+
17+
from types import SimpleNamespace
18+
from unittest import mock
19+
20+
from google.adk.examples.vertex_ai_example_store import VertexAiExampleStore
21+
import pytest
22+
23+
_STORE_NAME = "projects/p/locations/l/exampleStores/s"
24+
25+
26+
def _part(*, text=None, function_call=None, function_response=None):
27+
return SimpleNamespace(
28+
text=text,
29+
function_call=function_call,
30+
function_response=function_response,
31+
)
32+
33+
34+
def _expected_content(*, role, parts):
35+
return SimpleNamespace(content=SimpleNamespace(role=role, parts=parts))
36+
37+
38+
def _result(*, search_key="search key", expected_contents=(), score=1.0):
39+
return SimpleNamespace(
40+
similarity_score=score,
41+
example=SimpleNamespace(
42+
stored_contents_example=SimpleNamespace(
43+
search_key=search_key,
44+
contents_example=SimpleNamespace(
45+
expected_contents=list(expected_contents)
46+
),
47+
)
48+
),
49+
)
50+
51+
52+
@pytest.fixture
53+
def mock_example_stores():
54+
with mock.patch(
55+
"google.adk.dependencies.vertexai.example_stores"
56+
) as example_stores:
57+
yield example_stores
58+
59+
60+
@pytest.fixture
61+
def search_examples(mock_example_stores):
62+
return (
63+
mock_example_stores.ExampleStore.return_value.api_client.search_examples
64+
)
65+
66+
67+
def test_get_examples_searches_the_configured_store(
68+
mock_example_stores, search_examples
69+
):
70+
search_examples.return_value = SimpleNamespace(results=[])
71+
72+
VertexAiExampleStore(_STORE_NAME).get_examples("what is the weather?")
73+
74+
mock_example_stores.ExampleStore.assert_called_once_with(_STORE_NAME)
75+
search_examples.assert_called_once_with({
76+
"stored_contents_example_parameters": {
77+
"content_search_key": {
78+
"contents": [{
79+
"role": "user",
80+
"parts": [{"text": "what is the weather?"}],
81+
}],
82+
"search_key_generation_method": {"last_entry": {}},
83+
}
84+
},
85+
"top_k": 10,
86+
"example_store": _STORE_NAME,
87+
})
88+
89+
90+
def test_get_examples_returns_empty_list_without_results(search_examples):
91+
search_examples.return_value = SimpleNamespace(results=[])
92+
93+
assert VertexAiExampleStore(_STORE_NAME).get_examples("query") == []
94+
95+
96+
def test_get_examples_converts_text_part(search_examples):
97+
search_examples.return_value = SimpleNamespace(
98+
results=[
99+
_result(
100+
search_key="what is the weather?",
101+
expected_contents=[
102+
_expected_content(
103+
role="model", parts=[_part(text="it is sunny")]
104+
)
105+
],
106+
)
107+
]
108+
)
109+
110+
examples = VertexAiExampleStore(_STORE_NAME).get_examples("query")
111+
112+
assert len(examples) == 1
113+
assert examples[0].input.role == "user"
114+
assert [part.text for part in examples[0].input.parts] == [
115+
"what is the weather?"
116+
]
117+
assert len(examples[0].output) == 1
118+
assert examples[0].output[0].role == "model"
119+
assert [part.text for part in examples[0].output[0].parts] == ["it is sunny"]
120+
121+
122+
def test_get_examples_filters_results_below_similarity_threshold(
123+
search_examples,
124+
):
125+
search_examples.return_value = SimpleNamespace(
126+
results=[
127+
_result(search_key="too dissimilar", score=0.49),
128+
_result(search_key="similar enough", score=0.5),
129+
]
130+
)
131+
132+
examples = VertexAiExampleStore(_STORE_NAME).get_examples("query")
133+
134+
assert [example.input.parts[0].text for example in examples] == [
135+
"similar enough"
136+
]
137+
138+
139+
def test_get_examples_converts_function_call_part(search_examples):
140+
search_examples.return_value = SimpleNamespace(
141+
results=[
142+
_result(
143+
expected_contents=[
144+
_expected_content(
145+
role="model",
146+
parts=[
147+
_part(
148+
function_call=SimpleNamespace(
149+
name="get_weather", args={"city": "London"}
150+
)
151+
)
152+
],
153+
)
154+
],
155+
)
156+
]
157+
)
158+
159+
examples = VertexAiExampleStore(_STORE_NAME).get_examples("query")
160+
161+
function_call = examples[0].output[0].parts[0].function_call
162+
assert function_call.name == "get_weather"
163+
assert function_call.args == {"city": "London"}
164+
165+
166+
def test_get_examples_converts_function_response_part(search_examples):
167+
search_examples.return_value = SimpleNamespace(
168+
results=[
169+
_result(
170+
expected_contents=[
171+
_expected_content(
172+
role="user",
173+
parts=[
174+
_part(
175+
function_response=SimpleNamespace(
176+
name="get_weather",
177+
response={"temperature": 12},
178+
)
179+
)
180+
],
181+
)
182+
],
183+
)
184+
]
185+
)
186+
187+
examples = VertexAiExampleStore(_STORE_NAME).get_examples("query")
188+
189+
function_response = examples[0].output[0].parts[0].function_response
190+
assert function_response.name == "get_weather"
191+
assert function_response.response == {"temperature": 12}

0 commit comments

Comments
 (0)