From 44dcb028b93bea2e6a177e38cf00bd91c557c27e Mon Sep 17 00:00:00 2001 From: Kris <2893855659@qq.com> Date: Sat, 11 Jul 2026 22:08:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(toolkit):=20=E4=BF=AE=E5=A4=8D=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=B7=A5=E5=85=B7=E5=8F=82=E6=95=B0=E4=B8=8E=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E4=BF=A1=E6=81=AF=E5=B1=95=E7=A4=BA=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 为web_search和tavily_search添加runtime默认值None 2. 重构工具参数提取逻辑,跳过runtime参数并适配pydantic v2字段 3. 调整工具包导入顺序与格式化 4. 为外部工具调用添加上下文透传日志字段 --- .../yuxi/agents/toolkits/buildin/__init__.py | 24 +++++---- .../agents/toolkits/buildin/search/tools.py | 6 +-- .../package/yuxi/agents/toolkits/service.py | 53 ++++++++++++++----- 3 files changed, 56 insertions(+), 27 deletions(-) diff --git a/backend/package/yuxi/agents/toolkits/buildin/__init__.py b/backend/package/yuxi/agents/toolkits/buildin/__init__.py index 56854c9b..f3ce2be5 100644 --- a/backend/package/yuxi/agents/toolkits/buildin/__init__.py +++ b/backend/package/yuxi/agents/toolkits/buildin/__init__.py @@ -1,12 +1,14 @@ # buildin 工具包 -from .install_skill import install_skill -from .tools import ask_user_question, present_artifacts # Web 搜索与抓取工具(不依赖 LITE 模式,始终注册) -from . import search # noqa: F401 -from . import crawl # noqa: F401 +from . import ( + crawl, # noqa: F401 + search, # noqa: F401 +) +from .install_skill import install_skill # 会话通信工具仅在非 LITE 模式下注册 from .session_tools import _LITE_MODE as _session_lite_mode +from .tools import ask_user_question, present_artifacts if not _session_lite_mode: from .session_tools import get_agent_progress, get_session_history, list_sessions, send_to_session @@ -18,9 +20,11 @@ __all__ = [ ] if not _session_lite_mode: - __all__.extend([ - "get_agent_progress", - "get_session_history", - "list_sessions", - "send_to_session", - ]) + __all__.extend( + [ + "get_agent_progress", + "get_session_history", + "list_sessions", + "send_to_session", + ] + ) diff --git a/backend/package/yuxi/agents/toolkits/buildin/search/tools.py b/backend/package/yuxi/agents/toolkits/buildin/search/tools.py index 11d34916..428026ef 100644 --- a/backend/package/yuxi/agents/toolkits/buildin/search/tools.py +++ b/backend/package/yuxi/agents/toolkits/buildin/search/tools.py @@ -1,4 +1,5 @@ """web_search 与 tavily_search 工具注册。""" + import json from langgraph.prebuilt.tool_node import ToolRuntime @@ -9,7 +10,6 @@ from .chain import build_provider_chain from .models import SearchResult from .rate_limiter import rate_limiter - WEB_SEARCH_DESCRIPTION = """ 搜索互联网获取实时信息。 @@ -104,7 +104,7 @@ async def _execute_web_search(query: str, max_results: int, runtime: ToolRuntime async def web_search( query: str, max_results: int = 5, - runtime: ToolRuntime, + runtime: ToolRuntime = None, ) -> str: """搜索互联网,返回结构化结果列表。""" return await _execute_web_search(query, max_results, runtime) @@ -119,7 +119,7 @@ async def web_search( async def tavily_search( query: str, max_results: int = 5, - runtime: ToolRuntime, + runtime: ToolRuntime = None, ) -> str: """Tavily 网页搜索(兼容入口,内部委托 _execute_web_search)。""" return await _execute_web_search(query, max_results, runtime) diff --git a/backend/package/yuxi/agents/toolkits/service.py b/backend/package/yuxi/agents/toolkits/service.py index 39af703c..79d0fa4c 100644 --- a/backend/package/yuxi/agents/toolkits/service.py +++ b/backend/package/yuxi/agents/toolkits/service.py @@ -19,16 +19,35 @@ def _extract_tool_info(tool_obj) -> dict: if hasattr(tool_obj, "args_schema") and tool_obj.args_schema: schema = tool_obj.args_schema - if hasattr(schema, "schema"): - schema = schema.schema() - for arg_name, arg_info in schema.get("properties", {}).items(): - info["args"].append( - { - "name": arg_name, - "type": arg_info.get("type", ""), - "description": arg_info.get("description", ""), - } - ) + # 优先从 pydantic v2 ``model_fields`` 提取参数信息,避免对 + # ``ToolRuntime`` 等含 callable 字段(如 ``stream_writer``)的注入式 + # 参数生成完整 JSON schema 时抛出 ``PydanticInvalidForJsonSchema``。 + # ``runtime`` 字段由 langgraph 自动注入,不属于用户输入参数,跳过展示。 + model_fields = getattr(schema, "model_fields", None) + if model_fields: + for arg_name, field_info in model_fields.items(): + if arg_name == "runtime": + continue + annotation = field_info.annotation + info["args"].append( + { + "name": arg_name, + "type": getattr(annotation, "__name__", str(annotation)), + "description": field_info.description or "", + } + ) + elif hasattr(schema, "schema"): + schema_dict = schema.schema() + for arg_name, arg_info in schema_dict.get("properties", {}).items(): + if arg_name == "runtime": + continue + info["args"].append( + { + "name": arg_name, + "type": arg_info.get("type", ""), + "description": arg_info.get("description", ""), + } + ) return info @@ -146,15 +165,21 @@ async def resolve_configured_runtime_tools(context) -> list[Any]: if external_tool_names: async with pg_manager.get_async_session_context() as db: use_cases = create_use_cases_from_db(db) + # 透传 agent 上下文(uid 作为 caller_id,run_id 作为 correlation_id), + # 使 agent 调用外部工具时写入完整的可观测性字段 + thread_id = getattr(context, "thread_id", None) output = await use_cases.tool_service.build_runtime_tools( - BuildRuntimeToolsInput(slugs=external_tool_names), + BuildRuntimeToolsInput( + slugs=external_tool_names, + caller_id=getattr(context, "uid", None), + correlation_id=getattr(context, "run_id", None), + tags={"thread_id": thread_id} if thread_id else {}, + ), ) selected_tools.extend(output.items) selected_tool_names.update(tool.name for tool in output.items) # 构建失败的工具 slug 必须显式记录,避免用户配置的工具被静默丢失 if output.failed_slugs: - logger.warning( - f"Failed to build external runtime tools, skipped: {output.failed_slugs}" - ) + logger.warning(f"Failed to build external runtime tools, skipped: {output.failed_slugs}") return selected_tools