在体系中的位置:2.2 说工具是智能体的"手脚",这一节专门讲这双手脚怎么长、怎么管、怎么安全用。工具是智能体从"只会说话"到"能干活"的转折点,也是多智能体里最容易出安全事故的地方。
一个事实先摆:模型再聪明,它也只能产出文本。要让智能体"查天气、跑代码、发邮件",必须靠工具把文本意图翻译成对外部系统的真实调用。AgentScope 把工具做成一等公民,而不是事后补丁。
工具本质上是一个有清晰签名和文档字符串的函数。模型看到工具的 docstring,就知道"什么时候该调、传什么参"。所以写工具时 docstring 比实现还重要——模型靠它决策。
AgentScope 2.0 用 @tool 装饰器声明工具,再塞进 Toolkit 交给智能体。下面给一个规整例子。
from agentscope.tools import tool from agentscope.toolkit import Toolkit @tool def search_papers(query: str, top_k: int = 3) -> str: """在内部文献库检索相关论文。 Args: query: 检索关键词 top_k: 返回条数,默认 3 Returns: 检索到的论文标题与摘要拼接文本 """ # 真实场景调用向量库;演示返回占位 return f"找到 {top_k} 篇关于「{query}」的论文:\n1. AgentScope 综述\n2. 多智能体容错" tk = Toolkit() tk.register_tool(search_papers) # 把 tk 交给 ReActAgent,模型会根据用户问题决定是否调 search_papers
运行说明:docstring 里的 Args/Returns 是模型决策依据。参数类型注解(query: str)会被框架用来做输入校验——模型传错类型,框架在调用前就拦下,而不是等外部 API 报错。这是工具安全的第一道闸。
工具能执行任意代码(尤其"让模型写代码运行"这类高级玩法),风险显而易见:一个被诱导的提示词可能让智能体执行 rm -rf 之类。AgentScope 的工具在沙箱里执行,限制对宿主文件系统的访问。
代价:沙箱有序列化与隔离开销,高频工具调用要测延迟。但相比安全,这代价通常值得。下面这张图把"无沙箱 vs 有沙箱"的边界画清楚。

当工具多起来,散着定义难维护。Toolkit 把工具聚成一包,可整体交给智能体,也可多个智能体共享同一包。共享意味着"改一处工具定义,所有用它的智能体生效"。
# 多个智能体共享同一 Toolkit shared_tools = Toolkit() shared_tools.register_tool(search_papers) shared_tools.register_tool(get_weather) # 2.2 定义的天气工具 researcher = ReActAgent(name="研究员", sys_prompt="...", model=model, toolkit=shared_tools) assistant = ReActAgent(name="助理", sys_prompt="...", model=model, toolkit=shared_tools) # 两智能体都能用检索和天气工具,维护点唯一
背景:遇到没有现成工具的复杂计算,期望智能体临时写代码解决。
操作:提供一个"执行 Python"工具(在沙箱内),模型遇到非常规计算时生成代码交给它跑,拿到结果继续推理。
结果:智能体具备了"按需造工具"的能力,覆盖面远超预定义工具集。
解读:这正是工具沙箱价值最大化的场景——代码是模型现写的,不隔离几乎必然出事。沙箱把"灵活"和"安全"这对矛盾在这里和解。
变式:沙箱太宽仍有风险(比如读走敏感文件),生产要严格限制沙箱的文件与网络权限,并按需审计工具调用日志。灵活不是无边界。
类型注解只是第一道闸。业务上常要更细的约束,比如数值范围、枚举取值。把校验写进工具体内,非法输入直接返回 ERROR 消息,而不是打爆下游系统——这同时呼应 3.6 的"故障即消息"。
from agentscope.tools import tool @tool def transfer_fund(from_id: str, to_id: str, amount: float) -> str: """账户间转账,金额必须为正且不超过单笔上限。""" if amount <= 0: return "ERROR: 转账金额必须为正数" if amount > 50000: return "ERROR: 超过单笔上限 50000" if from_id == to_id: return "ERROR: 转出与转入账户不能相同" # 实际:调用账务服务完成转账 return f"OK: {from_id} 向 {to_id} 转账 {amount:.2f} 成功" # 'ERROR: 超过单笔上限 50000'
运行说明:三个非法分支分别返回带原因的 ERROR 文本,模型或上游智能体据此协商(重试、改参数或转人工),而非让异常穿透框架。工具体内部的"前置校验"比依赖外部系统报错 cheaper 也更可控——错误在边界就被表达成消息。
固定 Toolkit 适合静态场景。若智能体能力要随对话阶段变化(如客服先只能"查",核验身份后才"退款"),可在运行时动态注册/注销工具,避免早期暴露高危能力。
from agentscope.toolkit import Toolkit tk = Toolkit() tk.register_tool(search_papers) # 初始阶段只暴露查询 def grant_refund(agent_id: str): """身份核验通过后,动态挂载退款工具。""" if verify_identity(agent_id): # 伪代码:核验逻辑 tk.register_tool(refund_tool) # 此刻才暴露退款能力 return "OK: 已开通退款权限" return "ERROR: 身份核验未通过" # 'ERROR: 身份核验未通过' # 退款工具始终不暴露
运行说明:工具能力随权限升级才出现,模型在"未核验"阶段根本没有退款工具可用,从机制上杜绝了越权调用。这和 4.2 说的"权限最小化"是一体两面——不仅接口要白名单,暴露时机也要按需。代价是编排层要维护"能力随状态变化"的逻辑,但安全收益明显。
Toolkit 聚合复用工具,多智能体共享时维护点唯一。⚠️ 给智能体"执行 Python"这类高级工具,沙箱必须收紧文件/网络权限并审计日志。灵活不等于无边界,沙箱太宽一样会出事。
💡 写工具时先写好 docstring 的 Args/Returns,再写实现。模型靠 docstring 决策调不调、传什么,docstring 糊弄,工具基本不会被正确触发。