资讯动态

工具系统 -- Agent 的能力扩展

发布时间:2026/8/10 8:18:54 来源:尧图企业网站定制
本文基于 AgentScope 2.0.4 源码示例项目为 myagentAgentScope FastAPI Postgres WebSocket。核心观点工具不是 RPC是 Agent 的能力扩展工具不是 RPC是 Agent 的能力扩展 – FunctionTool 包装的是函数但执行权在 Agent 手里。在第三篇文章中你看到了 Agent 的 ReAct 循环Agent 自己决定要不要调工具、调哪个工具、调几次。但那时我们没展开 – 工具到底是什么怎么定义一个工具Agent 怎么知道有哪些工具可用现在打开工具系统的黑盒。你将掌握工具定义、Toolkit、调用契约理解工具的本质工具不是 RPC是 Agent 的能力扩展掌握 FunctionTool 的定义函数 自动 schema 推导理解 Toolkit 如何组装、工具如何分组掌握 extra_agent_tools 注入per-user 工具理解工具中间件洋葱模型掌握工具调用契约Toolkit.call_tool 内部理解工具结果ToolResponse/ToolChunk/状态/截断前置知识文章 1-3 与 async 概念读过文章 1Agent 认知基础、文章 2asyncio 基础、文章 3ReAct 循环特别理解文章 3 的 Acting 阶段_batch_tool_calls / _acting_impl了解 Python 的async和生成器概念一、工具的本质工具不是 RPC很多人把 Agent 的工具调用类比成 RPC远程过程调用——“Agent 调一个函数跟调一个微服务差不多”。这个类比是错的会误导你理解工具系统。RPC 和 Agent 工具的根本区别在谁决定调用┌─ RPC ──────────────────────────────────────────────┐ │ │ │ 调用方 你的代码 │ │ service.get_weather(北京) │ │ # 你决定调哪个服务、传什么参数、怎么处理结果 │ │ # 服务是被动的等着被调 │ │ │ └────────────────────────────────────────────────────┘ ┌─ Agent 工具 ───────────────────────────────────────┐ │ │ │ 调用方 AgentLLM │ │ Agent: 我需要查天气 - 自己决定调 get_weather │ │ # Agent 决定调哪个工具、传什么参数、怎么用结果 │ │ # 工具是被动的但是否被调由 Agent 决定 │ │ │ └────────────────────────────────────────────────────┘RPC 是你编排调用Agent 工具是 Agent 编排调用。工具定义者你只负责提供能力 声明能力边界真正决定何时用、怎么用的是 Agent。与 REST endpoint 的根本差异REST endpoint Agent FunctionTool ─────────────── ──────────────── 路径 HTTP 方法 工具名 (name) app.post(/weather) FunctionTool(funcget_weather) 请求体 - 校验 - 处理 - 响应 LLM 生成参数 - 执行 - ToolChunk 调用方 客户端 (你) 调用方 Agent (LLM) 参数校验 手动 Pydantic 参数 schema 自动从函数签名推导 返回 JSON 返回 ToolChunk 流REST endpoint 是别人客户端来调用我FunctionTool 是我注册一个能力Agent 决定要不要用它。三类工具AgentScope 的工具系统有三类源码agentscope/tool/__init__.pyToolBase (抽象基类) │ ├── FunctionTool 包装一个 Python 函数自定义工具的主力 │ ├── MCPTool MCP 协议工具连外部 MCP server │ └── ToolGroup 工具分组一个组内多个工具可整体激活/停用FunctionTool把普通 Python 函数包装成 Agent 可调用的工具MCPTool通过 MCP 协议连接外部工具服务Model Context ProtocolToolGroup工具的分组容器支持整体激活/停用二、FunctionTool 详解FunctionTool 是自定义工具的主力。它是把一个普通 Python 函数翻译成 Agent 能理解的工具源码agentscope/tool/_adapters.py。签名FunctionTool(func,# 要包装的 Python 函数nameNone,# 工具名默认用函数名descriptionNone,# 工具描述默认从 docstring 提取is_concurrency_safeTrue,# 是否并发安全is_read_onlyFalse,# 是否只读is_state_injectedFalse,# 是否注入 agent statemiddlewaresNone,# 工具中间件)自动 schema 推导FunctionTool 的核心魔法是从函数签名自动推导参数 schema不用你手写# 你只需要写一个普通 Python 函数defcalculate(expression:str)-str:计算数学表达式。returnstr(eval_expression(expression))# FunctionTool 自动提取:# name calculate# description 计算数学表达式。# input_schema {type:object, properties:{expression:{type:string}}, ...}toolFunctionTool(funccalculate)这个input_schema会传给 LLM —— 告诉 LLM这个工具接受一个叫 expression 的字符串参数。LLM 生成工具调用时就按这个 schema 生成 JSON 参数。为什么 schema 重要LLM 靠 schema 理解工具怎么用。如果 schema 不清晰LLM 就会乱传参数。所以工具定义时name要语义化如get_weather不要func123description要说明用途和参数LLM 靠这个决定这个问题适不适合用这个工具参数类型要明确str/int 还是 object返回值FunctionTool 的函数返回ToolChunk增量或AsyncGenerator[ToolChunk]流式。AgentScope 的Toolkit.call_tool会累加这些 chunk 成完整的ToolResponse。三、Toolkit 组装get_toolkit工具的装配中心get_toolkit()是每次 chat turn 的工具装配中心源码agentscope/app/_service/_toolkit.py。它把多个来源的工具组装成一个Toolkitget_toolkit() 的工具来源按附加顺序: ───────────────────────────────────────────────────── 1. Workspace 内置 Bash / Read / Write / Grep / Glob / Edit 2. Planning 工具 TaskCreate / TaskList / TaskGet / TaskUpdate 3. 后台任务控制 ToolStop来自 BackgroundTaskManager 4. 定时任务 ScheduleCreate / View / Delete / List 5. 团队工具 TeamCreate / AgentCreate / TeamSay / TeamDelete 6. 自定义扩展 extra_factory 提供的工具 workspace 的 skills 和 MCPs每次 Agent 推理时get_toolkit把所有这些工具组装起来传给 Agent 的 toolkit。工具分组Toolkit用ToolGroup组织工具Toolkit ├── ToolGroup(basic) - [Bash, Read, Write, ...] ├── ToolGroup(planning) - [TaskCreate, ...] ├── ToolGroup(schedule) - [ScheduleCreate, ...] └── ToolGroup(team) - [TeamCreate, ...]工具组支持激活/停用activate/deactivate。停用的组里的工具Agent 调不到。这提供了一层按场景启用工具的灵活性。与 DI 容器的对比AgentScope ToolkitSpring DI 容器工具注册Bean 注册ToolGroup 分组按 profile 装配工具激活/停用Profile / Conditionalget_toolkit 组装ApplicationContext 装配四、extra_agent_toolsAgentToolFactory 签名extra_agent_tools是create_app的注入点接收一个工厂函数# 签名: (user_id, agent_id, session_id) - list[ToolBase]asyncdefcreate_tools(user_id,agent_id,session_id,**kwargs)-list[ToolBase]:...框架在每次 chat turn 调用它产出当前用户可用的工具列表。这就是per-user 工具注入 —— 不同用户可能拿到不同的工具。myagent 的 create_tools# 源码: src/myagent/tools/registry.pyasyncdefcreate_tools(user_id,agent_id,session_id,**kwargs):_obs_mw[ObservabilityMiddleware()]calculator_toolTrustedFunctionTool(funccalculate,namecalculator,description计算数学表达式...,middlewares_obs_mw,)asyncdefweather_handler(cityNone):returnawait_get_weather(city,user_iduser_id,storagestorage)weather_toolTrustedFunctionTool(funcweather_handler,nameget_weather,description查询指定城市的当前天气...,middlewares_obs_mw,)return[calculator_tool,weather_tool]注意weather_handler闭包捕获了user_id—— 这样天气工具就能根据当前用户自动定位城市。这就是 per-user 注入的价值工具能用上当前请求的上下文。五、工具中间件ToolMiddlewareBase工具中间件基于ToolMiddlewareBase用洋葱模型包裹工具调用源码agentscope/tool/_base.py。它和文章 3 讲过的 Agent 中间件不同 —— 工具中间件专门包裹单个工具的调用。myagent 的 ObservabilityMiddleware# 源码: src/myagent/observability.pyclassObservabilityMiddleware(ToolMiddlewareBase):asyncdefon_tool_call(self,tool,input_kwargs,next_handler):logger.info(tool_call_start name%s params%s,tool.name,input_kwargs)starttime.monotonic()try:asyncforchunkinnext_handler(**input_kwargs):yieldchunkexceptExceptionase:logger.error(tool_call_error name%s error%s,tool.name,e)raiseelse:logger.info(tool_call_end name%s duration_ms%.1f,tool.name,(time.monotonic()-start)*1000)这个中间件在每个工具调用前后打日志开始、结束含耗时、出错。这就是可观测性 —— 你在外面包一层不用改工具本身。TrustedFunctionTool 的权限检查工具执行前会调用check_permissions()决定是否允许。默认的FunctionTool.check_permissions()返回ASK需要用户确认# FunctionTool 默认: 需要用户确认asyncdefcheck_permissions(self,*_args,**_kwargs):returnPermissionDecision(behaviorPermissionBehavior.ASK,messageCustom function tools must be explicitly allowed.,)myagent 继承了 FunctionTool 并重写为自动放行# 源码: src/myagent/tools/registry.pyclassTrustedFunctionTool(FunctionTool):asyncdefcheck_permissions(self,*_args,**_kwargs):returnPermissionDecision(behaviorPermissionBehavior.ALLOW,messageTrusted tool auto-allowed.,)这个设计很关键如果自定义工具都要求用户确认聊天会频繁卡在权限确认上死锁。myagent 用TrustedFunctionTool自动放行自定义工具避免死锁。关于权限系统的完整解析留到文章 13。六、工具调用契约现在打开Toolkit.call_tool内部 —— 这是单个工具如何被调用的完整契约。注意文章 3 讲的是循环如何调度工具_batch_tool_calls / _acting_impl这里讲的是一个工具被调用时内部发生了什么。# 源码: agentscope/tool/_toolkit.py Toolkit.call_toolasyncdefcall_tool(self,tool_call,state):tool_responseToolResponse(idtool_call.id)# 1. 工具存在性检查available_toolsawaitself._get_available_tools(...)iftool_call.namenotinavailable_tools:chunkToolChunk(content[TextBlock(textfToolNotFoundError: ...)],stateToolResultState.ERROR)yieldchunkreturn# 2. 工具组激活检查# 工具在未激活的 group - ToolGroupInactiveError# 3. 参数解析kwargs_json_loads_with_repair(tool_call.input)# LLM 生成的参数 JSON 可能不合法需要修复解析# 4. state 注入iftool_func.is_state_injected:kwargs[_agent_state]state# 5. 返回值分派ifinspect.iscoroutinefunction(tool_func.__call__):resawaittool_func(**kwargs)else:restool_func(**kwargs)ifisinstance(res,ToolChunk):yieldreselifisinstance(res,AsyncGenerator):asyncforchunkinres:yieldchunkelifisinstance(res,Generator):forchunkinres:yieldchunk# 6. 异常处理# McpError / DeveloperOrientedException# - 包装成 ToolChunk(stateERROR)六个步骤存在性检查工具名在不在可用列表里。不在 - 返回ToolNotFoundError的 ToolChunk不让异常向上传播而是给 Agent 一个错误反馈让 Agent 决定怎么办。工具组激活检查工具在未激活的组里 -ToolGroupInactiveError提示 Agent 先激活组。参数解析_json_loads_with_repair解析 LLM 生成的参数 JSON。LLM 生成的 JSON 可能带多余字符、格式不严需要修复式解析。state 注入如果工具声明了is_state_injected把 agent state 注入参数工具可以读写 agent 状态。返回值分派根据工具函数类型同步/异步/生成器/异步生成器分派执行逐个 yield ToolChunk。异常处理工具函数执行时若抛异常call_tool会捕获并包装成 ERROR 状态的 ToolChunk给 Agent 反馈而不是让异常向上传播DeveloperOrientedException这类开发者异常除外会重新抛出。关键洞察工具调用契约的本质工具系统的设计原则是把执行异常转换为错误状态的 ToolChunk 反馈给 Agent而不是让异常向上传播导致流程崩溃。传统函数: 出错了 - raise Exception - 调用方 try/except Agent 工具: 出错了 - call_tool 捕获 - 返回 ToolChunk(stateERROR) - Agent 自己决定怎么处理这里有个细微差别工具函数内部可以抛异常但Toolkit.call_tool会拦截并把它包装成 ERROR 状态的 ToolChunk。所以从 Agent 的视角看它不会收到异常只会看到带错误信息的 ToolChunk。唯一的例外是DeveloperOrientedException框架开发者错误它会被重新抛出。Agent 不能像传统代码那样 try/except —— 它是 LLM需要看到错误信息然后决定下一步。所以工具系统把错误转成 ToolChunk 反馈给 Agent让 Agent 自主决策。七、工具结果ToolResponse / ToolChunkToolChunk 增量结果流式工具执行过程中逐步产生 ToolResponse 完整结果Toolkit.call_tool 累加所有 chunk 后生成工具可以返回单个ToolChunk也可以 yield 一串ToolChunk流式。Toolkit.call_tool内部会累加这些 chunk 到ToolResponse。ToolResultState工具结果有状态源码agentscope/messageToolResultState: SUCCESS 成功 ERROR 出错工具异常/参数错误 INTERRUPTED 被中断文章 3 讲过 _close_unfinished_tool_calls 会标记 INTERRUPTED结果截断工具结果可能很大比如读了一个大文件。AgentScope 的ContextConfig.tool_result_limit默认 50000 token控制工具结果的最大长度超出的截断防止撑爆 agent context。八、myagent 实战回顾 myagent 的完整工具链路源码src/myagent/tools/server.py create_app(extra_agent_toolscreate_tools, ...) # 注入工具工厂 └─ 每次 chat turn └─ get_toolkit(extra_factorycreate_tools) # 组装工具 └─ create_tools(user_id, agent_id, session_id) ├─ TrustedFunctionTool(calculate, calculator) └─ TrustedFunctionTool(weather_handler, get_weather) └─ 两个工具都带 ObservabilityMiddlewarecalculatorcalculate函数用 ast 安全解析数学表达式源码tools/calculator.pyget_weatherweather_handler闭包捕获 user_id调用高德天气 API源码tools/weather.pyTrustedFunctionTool重写 check_permissions 返回 ALLOW避免权限确认死锁ObservabilityMiddleware包裹两个工具记录调用耗时和状态九、常见陷阱陷阱 1: 工具 description 不清晰导致 LLM 误调症状LLM 不调某个工具或乱传参数。原因工具的 description 和参数 schema 不清晰。LLM 靠这些理解什么时候该用这个工具、参数怎么传。解决description 写清楚用途 参数说明。比如FunctionTool(funccalculate,namecalculator,description计算数学表达式支持加减乘除、幂、取模。参数 expression: 数学表达式字符串,)而不是description工具这种模糊描述。陷阱 2: 同步工具阻塞事件循环症状Agent 调用工具时所有用户请求卡住。原因工具函数是同步阻塞的如requests.get、time.sleep在 async 环境里阻塞了事件循环。解决工具函数用异步实现httpx.AsyncClient或用asyncio.to_thread跑同步代码。参考文章 2 的同步阻塞混入 async陷阱。陷阱 3: 工具结果过大撑爆 context症状Agent 对话越来越慢甚至报 context 超长错误。原因工具返回了超大结果读大文件、查大量数据撑爆了 agent context。解决设置ContextConfig.tool_result_limit限制工具结果长度工具函数自身也尽量只返回摘要而非全量数据。总结工具定义了什么能做Agent 决定做什么工具系统是 Agent 的能力扩展。它不是 RPC —— 是你注册能力Agent 决定用不用。核心组件FunctionTool把 Python 函数包装成工具自动推导 schemaToolkit组装多来源工具workspace/planning/schedule/team/extras/skills/mcpextra_agent_toolsper-user 工具注入工具中间件洋葱模型包裹工具调用观测/权限工具调用契约捕获执行异常为错误 ToolChunk 让 Agent 决策工具结果ToolChunk 流 ToolResponse 状态 截断理解工具系统你就理解了 Agent 能力的边界 —— 工具定义了什么能做Agent 决定做什么。下一篇文章将深入事件流模型 —— Agent 怎么用事件思考和沟通。你已经理解了工具如何被调用接下来看这些调用怎么变成前端可见的事件流。进一步探索并发安全、权限恢复、超大结果如果一个工具是纯计算calculator和查数据库的工具它们的is_read_only/is_concurrency_safe应该怎么设check_permissions返回 ASK 时前端要展示什么Agent 暂停后怎么恢复工具返回超大结果时除了截断还有什么策略如分层摘要、只返回链接

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价