Skip to content

Commit 36f580f

Browse files
committed
fix: 修复复杂类型的工具调用
Change-Id: Ia93a08e93126a9b451acac605a5bc3a4acd0fda5 Signed-off-by: OhYee <oyohyee@oyohyee.com>
1 parent 3d57315 commit 36f580f

File tree

7 files changed

+2714
-48
lines changed

7 files changed

+2714
-48
lines changed

agentrun/integration/google_adk/tool_adapter.py

Lines changed: 213 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,180 @@
22
33
将标准工具定义转换为 Google ADK 函数格式。"""
44

5-
from typing import List, Optional
5+
from typing import Any, Dict, List, Optional
66

77
from agentrun.integration.utils.adapter import ToolAdapter
88
from agentrun.integration.utils.canonical import CanonicalTool
99

1010

11+
def _json_schema_to_google_schema(
12+
schema: Dict[str, Any],
13+
root_schema: Optional[Dict[str, Any]] = None,
14+
) -> Any:
15+
"""将 JSON Schema 转换为 Google ADK Schema
16+
17+
Google ADK 不支持 $ref 和 $defs,所以需要内联所有引用。
18+
19+
Args:
20+
schema: JSON Schema 定义
21+
root_schema: 根 schema,用于解析 $ref 引用
22+
23+
Returns:
24+
Google ADK types.Schema 对象
25+
"""
26+
from google.genai import types
27+
28+
if not schema or not isinstance(schema, dict):
29+
return types.Schema(type=types.Type.OBJECT)
30+
31+
if root_schema is None:
32+
root_schema = schema
33+
34+
# 处理 $ref 引用
35+
if "$ref" in schema:
36+
ref = schema["$ref"]
37+
resolved = _resolve_schema_ref(ref, root_schema)
38+
if resolved:
39+
return _json_schema_to_google_schema(resolved, root_schema)
40+
return types.Schema(type=types.Type.OBJECT)
41+
42+
schema_type = schema.get("type")
43+
description = schema.get("description")
44+
45+
# 类型映射
46+
type_mapping = {
47+
"string": types.Type.STRING,
48+
"integer": types.Type.INTEGER,
49+
"number": types.Type.NUMBER,
50+
"boolean": types.Type.BOOLEAN,
51+
"array": types.Type.ARRAY,
52+
"object": types.Type.OBJECT,
53+
}
54+
55+
google_type = type_mapping.get(
56+
str(schema_type or "object"), types.Type.OBJECT
57+
)
58+
59+
# 处理数组类型
60+
if schema_type == "array":
61+
items_schema = schema.get("items")
62+
items = None
63+
if items_schema:
64+
items = _json_schema_to_google_schema(items_schema, root_schema)
65+
return types.Schema(
66+
type=google_type,
67+
description=description,
68+
items=items,
69+
)
70+
71+
# 处理对象类型
72+
if schema_type == "object":
73+
props = schema.get("properties", {})
74+
required = schema.get("required", [])
75+
76+
google_props = {}
77+
for prop_name, prop_schema in props.items():
78+
google_props[prop_name] = _json_schema_to_google_schema(
79+
prop_schema, root_schema
80+
)
81+
82+
return types.Schema(
83+
type=google_type,
84+
description=description,
85+
properties=google_props if google_props else None,
86+
required=required if required else None,
87+
)
88+
89+
# 基本类型
90+
return types.Schema(
91+
type=google_type,
92+
description=description,
93+
)
94+
95+
96+
def _resolve_schema_ref(
97+
ref: str, root_schema: Dict[str, Any]
98+
) -> Optional[Dict[str, Any]]:
99+
"""解析 JSON Schema $ref 引用
100+
101+
Args:
102+
ref: $ref 字符串,如 "#/$defs/MyType" 或 "#/definitions/MyType"
103+
root_schema: 根 schema,包含 $defs 或 definitions
104+
105+
Returns:
106+
解析后的 schema,如果无法解析则返回 None
107+
"""
108+
if not ref or not ref.startswith("#/"):
109+
return None
110+
111+
# 解析路径,如 "#/$defs/MyType" -> ["$defs", "MyType"]
112+
path_parts = ref[2:].split("/")
113+
current = root_schema
114+
115+
for part in path_parts:
116+
if not isinstance(current, dict) or part not in current:
117+
return None
118+
current = current[part]
119+
120+
return current if isinstance(current, dict) else None
121+
122+
123+
def _create_custom_function_tool_class():
124+
"""创建自定义 FunctionTool 类
125+
126+
延迟创建以避免在模块导入时依赖 google.adk。
127+
"""
128+
from google.adk.tools.base_tool import BaseTool
129+
130+
class CustomFunctionTool(BaseTool):
131+
"""自定义 Google ADK 工具类
132+
133+
允许手动指定 FunctionDeclaration,避免 Pydantic 模型的 $ref 问题。
134+
继承自 google.adk.tools.BaseTool 以确保与 ADK Agent 兼容。
135+
"""
136+
137+
def __init__(
138+
self,
139+
func,
140+
declaration,
141+
):
142+
# 调用父类 __init__,传递 name 和 description
143+
super().__init__(
144+
name=declaration.name,
145+
description=declaration.description or "",
146+
)
147+
self._func = func
148+
self._declaration = declaration
149+
150+
def _get_declaration(self):
151+
return self._declaration
152+
153+
async def run_async(self, *, args: Dict[str, Any], tool_context):
154+
"""异步执行工具函数"""
155+
return self._func(**args)
156+
157+
return CustomFunctionTool
158+
159+
160+
# 缓存类,避免重复创建
161+
_CustomFunctionToolClass = None
162+
163+
164+
def _get_custom_function_tool_class():
165+
"""获取 CustomFunctionTool 类(延迟加载)"""
166+
global _CustomFunctionToolClass
167+
if _CustomFunctionToolClass is None:
168+
_CustomFunctionToolClass = _create_custom_function_tool_class()
169+
return _CustomFunctionToolClass
170+
171+
11172
class GoogleADKToolAdapter(ToolAdapter):
12173
"""Google ADK 工具适配器 / Google ADK Tool Adapter
13174
14175
实现 CanonicalTool → Google ADK 函数的转换。
15-
Google ADK 直接使用 Python 函数作为工具。"""
176+
由于 Google ADK 不支持复杂的 JSON Schema(包含 $ref 和 $defs),
177+
此适配器会手动构建 FunctionDeclaration 并使用自定义工具类。
178+
"""
16179

17180
def get_registered_tool(self, name: str) -> Optional[CanonicalTool]:
18181
"""根据名称获取最近注册的工具定义 / Google ADK Tool Adapter"""
@@ -21,5 +184,51 @@ def get_registered_tool(self, name: str) -> Optional[CanonicalTool]:
21184
def from_canonical(self, tools: List[CanonicalTool]):
22185
"""将标准格式转换为 Google ADK 工具 / Google ADK Tool Adapter
23186
24-
Google ADK 通过函数的类型注解推断参数,需要动态创建带注解的函数。"""
25-
return self.function_tools(tools)
187+
为每个工具创建自定义的 FunctionTool,手动指定参数 schema,
188+
以避免 Pydantic 模型产生的 $ref 问题。
189+
"""
190+
from google.genai import types
191+
192+
result = []
193+
194+
for tool in tools:
195+
# 记录工具定义
196+
self._registered_tools[tool.name] = tool
197+
198+
# 从 parameters schema 构建 Google ADK Schema
199+
parameters_schema = tool.parameters or {
200+
"type": "object",
201+
"properties": {},
202+
}
203+
204+
google_schema = _json_schema_to_google_schema(
205+
parameters_schema, parameters_schema
206+
)
207+
208+
# 创建 FunctionDeclaration
209+
declaration = types.FunctionDeclaration(
210+
name=tool.name,
211+
description=tool.description or "",
212+
parameters=google_schema,
213+
)
214+
215+
# 创建包装函数
216+
def make_wrapper(canonical_tool: CanonicalTool):
217+
def wrapper(**kwargs):
218+
if canonical_tool.func is None:
219+
raise NotImplementedError(
220+
f"Tool function for '{canonical_tool.name}' "
221+
"is not implemented."
222+
)
223+
return canonical_tool.func(**kwargs)
224+
225+
return wrapper
226+
227+
wrapper_func = make_wrapper(tool)
228+
229+
# 创建自定义工具
230+
CustomFunctionTool = _get_custom_function_tool_class()
231+
custom_tool = CustomFunctionTool(wrapper_func, declaration)
232+
result.append(custom_tool)
233+
234+
return result

0 commit comments

Comments
 (0)