Skip to content

Commit e63e2a7

Browse files
xuanyang15copybara-github
authored andcommitted
fix: add the missing required tool parameters for Anthropic models
Fixes #1692 PiperOrigin-RevId: 795125801
1 parent 9ba8eec commit e63e2a7

File tree

2 files changed

+213
-12
lines changed

2 files changed

+213
-12
lines changed

src/google/adk/models/anthropic_llm.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -216,25 +216,31 @@ def _update_type_string(value_dict: dict[str, Any]):
216216
def function_declaration_to_tool_param(
217217
function_declaration: types.FunctionDeclaration,
218218
) -> anthropic_types.ToolParam:
219+
"""Converts a function declaration to an Anthropic tool param."""
219220
assert function_declaration.name
220221

221222
properties = {}
222-
if (
223-
function_declaration.parameters
224-
and function_declaration.parameters.properties
225-
):
226-
for key, value in function_declaration.parameters.properties.items():
227-
value_dict = value.model_dump(exclude_none=True)
228-
_update_type_string(value_dict)
229-
properties[key] = value_dict
223+
required_params = []
224+
if function_declaration.parameters:
225+
if function_declaration.parameters.properties:
226+
for key, value in function_declaration.parameters.properties.items():
227+
value_dict = value.model_dump(exclude_none=True)
228+
_update_type_string(value_dict)
229+
properties[key] = value_dict
230+
if function_declaration.parameters.required:
231+
required_params = function_declaration.parameters.required
232+
233+
input_schema = {
234+
"type": "object",
235+
"properties": properties,
236+
}
237+
if required_params:
238+
input_schema["required"] = required_params
230239

231240
return anthropic_types.ToolParam(
232241
name=function_declaration.name,
233242
description=function_declaration.description or "",
234-
input_schema={
235-
"type": "object",
236-
"properties": properties,
237-
},
243+
input_schema=input_schema,
238244
)
239245

240246

tests/unittests/models/test_anthropic_llm.py

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from google.adk import version as adk_version
2121
from google.adk.models import anthropic_llm
2222
from google.adk.models.anthropic_llm import Claude
23+
from google.adk.models.anthropic_llm import function_declaration_to_tool_param
2324
from google.adk.models.llm_request import LlmRequest
2425
from google.adk.models.llm_response import LlmResponse
2526
from google.genai import types
@@ -96,6 +97,200 @@ def test_supported_models():
9697
assert models[1] == r"claude-.*-4.*"
9798

9899

100+
function_declaration_test_cases = [
101+
(
102+
"function_with_no_parameters",
103+
types.FunctionDeclaration(
104+
name="get_current_time",
105+
description="Gets the current time.",
106+
),
107+
anthropic_types.ToolParam(
108+
name="get_current_time",
109+
description="Gets the current time.",
110+
input_schema={"type": "object", "properties": {}},
111+
),
112+
),
113+
(
114+
"function_with_one_optional_parameter",
115+
types.FunctionDeclaration(
116+
name="get_weather",
117+
description="Gets weather information for a given location.",
118+
parameters=types.Schema(
119+
type=types.Type.OBJECT,
120+
properties={
121+
"location": types.Schema(
122+
type=types.Type.STRING,
123+
description="City and state, e.g., San Francisco, CA",
124+
)
125+
},
126+
),
127+
),
128+
anthropic_types.ToolParam(
129+
name="get_weather",
130+
description="Gets weather information for a given location.",
131+
input_schema={
132+
"type": "object",
133+
"properties": {
134+
"location": {
135+
"type": "string",
136+
"description": (
137+
"City and state, e.g., San Francisco, CA"
138+
),
139+
}
140+
},
141+
},
142+
),
143+
),
144+
(
145+
"function_with_one_required_parameter",
146+
types.FunctionDeclaration(
147+
name="get_stock_price",
148+
description="Gets the current price for a stock ticker.",
149+
parameters=types.Schema(
150+
type=types.Type.OBJECT,
151+
properties={
152+
"ticker": types.Schema(
153+
type=types.Type.STRING,
154+
description="The stock ticker, e.g., AAPL",
155+
)
156+
},
157+
required=["ticker"],
158+
),
159+
),
160+
anthropic_types.ToolParam(
161+
name="get_stock_price",
162+
description="Gets the current price for a stock ticker.",
163+
input_schema={
164+
"type": "object",
165+
"properties": {
166+
"ticker": {
167+
"type": "string",
168+
"description": "The stock ticker, e.g., AAPL",
169+
}
170+
},
171+
"required": ["ticker"],
172+
},
173+
),
174+
),
175+
(
176+
"function_with_multiple_mixed_parameters",
177+
types.FunctionDeclaration(
178+
name="submit_order",
179+
description="Submits a product order.",
180+
parameters=types.Schema(
181+
type=types.Type.OBJECT,
182+
properties={
183+
"product_id": types.Schema(
184+
type=types.Type.STRING, description="The product ID"
185+
),
186+
"quantity": types.Schema(
187+
type=types.Type.INTEGER,
188+
description="The order quantity",
189+
),
190+
"notes": types.Schema(
191+
type=types.Type.STRING,
192+
description="Optional order notes",
193+
),
194+
},
195+
required=["product_id", "quantity"],
196+
),
197+
),
198+
anthropic_types.ToolParam(
199+
name="submit_order",
200+
description="Submits a product order.",
201+
input_schema={
202+
"type": "object",
203+
"properties": {
204+
"product_id": {
205+
"type": "string",
206+
"description": "The product ID",
207+
},
208+
"quantity": {
209+
"type": "integer",
210+
"description": "The order quantity",
211+
},
212+
"notes": {
213+
"type": "string",
214+
"description": "Optional order notes",
215+
},
216+
},
217+
"required": ["product_id", "quantity"],
218+
},
219+
),
220+
),
221+
(
222+
"function_with_complex_nested_parameter",
223+
types.FunctionDeclaration(
224+
name="create_playlist",
225+
description="Creates a playlist from a list of songs.",
226+
parameters=types.Schema(
227+
type=types.Type.OBJECT,
228+
properties={
229+
"playlist_name": types.Schema(
230+
type=types.Type.STRING,
231+
description="The name for the new playlist",
232+
),
233+
"songs": types.Schema(
234+
type=types.Type.ARRAY,
235+
description="A list of songs to add to the playlist",
236+
items=types.Schema(
237+
type=types.Type.OBJECT,
238+
properties={
239+
"title": types.Schema(type=types.Type.STRING),
240+
"artist": types.Schema(type=types.Type.STRING),
241+
},
242+
required=["title", "artist"],
243+
),
244+
),
245+
},
246+
required=["playlist_name", "songs"],
247+
),
248+
),
249+
anthropic_types.ToolParam(
250+
name="create_playlist",
251+
description="Creates a playlist from a list of songs.",
252+
input_schema={
253+
"type": "object",
254+
"properties": {
255+
"playlist_name": {
256+
"type": "string",
257+
"description": "The name for the new playlist",
258+
},
259+
"songs": {
260+
"type": "array",
261+
"description": "A list of songs to add to the playlist",
262+
"items": {
263+
"type": "object",
264+
"properties": {
265+
"title": {"type": "string"},
266+
"artist": {"type": "string"},
267+
},
268+
"required": ["title", "artist"],
269+
},
270+
},
271+
},
272+
"required": ["playlist_name", "songs"],
273+
},
274+
),
275+
),
276+
]
277+
278+
279+
@pytest.mark.parametrize(
280+
"_, function_declaration, expected_tool_param",
281+
function_declaration_test_cases,
282+
ids=[case[0] for case in function_declaration_test_cases],
283+
)
284+
async def test_function_declaration_to_tool_param(
285+
_, function_declaration, expected_tool_param
286+
):
287+
"""Test function_declaration_to_tool_param."""
288+
assert (
289+
function_declaration_to_tool_param(function_declaration)
290+
== expected_tool_param
291+
)
292+
293+
99294
@pytest.mark.asyncio
100295
async def test_generate_content_async(
101296
claude_llm, llm_request, generate_content_response, generate_llm_response

0 commit comments

Comments
 (0)