Skip to content

Commit c08f939

Browse files
authored
Merge pull request #15 from Serverless-Devs/sandbox-and-toolset
修复工具调用问题,增强 sandbox 集成能力
2 parents 8789245 + 285cde8 commit c08f939

20 files changed

+3888
-155
lines changed

agentrun/integration/builtin/sandbox.py

Lines changed: 1022 additions & 64 deletions
Large diffs are not rendered by default.

agentrun/integration/google_adk/tool_adapter.py

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

10172

11173
class GoogleADKToolAdapter(ToolAdapter):
12174
"""Google ADK 工具适配器 / Google ADK Tool Adapter
13175
14176
实现 CanonicalTool → Google ADK 函数的转换。
15-
Google ADK 直接使用 Python 函数作为工具。"""
177+
由于 Google ADK 不支持复杂的 JSON Schema(包含 $ref 和 $defs),
178+
此适配器会手动构建 FunctionDeclaration 并使用自定义工具类。
179+
"""
16180

17181
def get_registered_tool(self, name: str) -> Optional[CanonicalTool]:
18182
"""根据名称获取最近注册的工具定义 / Google ADK Tool Adapter"""
19-
return self._registered_tools.get(name)
183+
return self._registered_tools.get(normalize_tool_name(name))
20184

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

0 commit comments

Comments
 (0)