任何一个 Agent 跑起来之后,你很快就会撞上一个尴尬场景:它聊起天来头头是道,但一问"今天实时热搜是什么",它只能编;让它"算一下斐波那契数列第 50 项",它开始一本正经地胡说;让它"把这段数据画成图",它只能给你描述一张根本不存在的图。说白了,大模型只有"口才",没有"手"和"眼"。而真正让 Agent 从聊天机器人变成干活系统的,是外挂工具。在众多工具里,Retrieval、精确计算和代码执行这三类能力是刚需,对应到具体实现上,我这些年用得最顺手的三件套就是:SearxNG、WolframAlpha 与代码解释器。这篇内容不是我抄文档的笔记,而是我带着这三个工具在真实 Agent 项目里反复调试后沉淀下来的拆解。会聊到它们各自适合干什么、怎么接入、怎么编排,以及大量只在踩坑时才说得清的细节。无论你是用现成框架搭 Agent,还是想自己从零写工具调用,这份东西都能帮你少走半个月弯路。1. Agent 工具箱的定位:光有大模型口才还不够先搞清楚一个问题:为什么 Agent 一定要配工具箱?大模型本身有海量训练知识,但也因此有天然的三个短板:时间截止训练数据、无法实时获取外部信息;生成式模型不擅长精确数值计算,乘法都可能错;只能输出文本,无法真正操作系统、读写文件、运行代码。工具就是补短板用的。但补的时候要讲究搭配,不是工具越多越好,而是让每种工具负责它最擅长的能力边界。我偏好把工具按能力类型分成三类:信息获取类:负责"查事实、查动态、查详情",代表就是搜索工具。知识与计算类:负责"精确推导、单位换算、数学求解",代表是 WolframAlpha。执行操作类:负责"写代码、跑脚本、处理文件、生成图表",代表是代码解释器。三者的关系不是替代,而是互补。搜索能搜到一大堆网页,但搜不出积分结果;WolframAlpha 能告诉你精确答案,但没法帮你把答案持续计算一百遍;代码解释器什么都能算,但你得自己给它写对算法和逻辑。把三者组合起来,Agent 才真正具备"检索线索 → 精确推理 → 动手验证"的完整能力闭环。1.1 三个工具各自解决什么问题维度SearxNGWolframAlpha代码解释器能力本质元搜索聚合知识引擎与符号计算通用代码执行环境主要用途找网页、找最新信息、找事实线索数学计算、单位换算、实体知识、科学与工程数据写任意代码、处理数据、生成图、文件转码输出特点网页列表、摘要、链接结构化答案、步骤、图表文本输出、文件产物、可视化需要联网是,实时是,调 API通常隔离网络,更安全适合场景开放性事实查询精确、可验证的定量问题需要跑逻辑或生成文件的任务这是我在工具选型时的初始判断表。实际使用中,三者的边界经常有重叠。比如代码解释器理论上也能拉取数据,但联网权限通常被关掉,所以拉数据还得靠搜索类工具;WolframAlpha 也能画图,但自定义能力远不如代码解释器。清晰定位,后面编排才不会乱。1.2 工具调用的底层逻辑:Function Calling 与协议Agent 调用工具,技术上并不是真的"会按按钮",而是通过一层约定。主流方式是Function Calling:把工具描述、参数 schema 作为 JSON 发给模型,模型根据任务选择调用哪个工具并生成符合 schema 的参数,然后你的程序实际执行该工具,把结果再塞回对话上下文让模型继续推理。在较新的 Agent 框架里,这套东西已经具体化为类似MCP(Model Context Protocol)的协议,让工具以标准服务的形式暴露给 Agent。但不管协议怎么变,思维方式不变:给每个工具写清楚"什么时候用、什么时候别用"的描述。给每个工具定义严格的输入参数格式。工具的返回值必须结构完整,方便模型二次读取。我见过不少 Agent 翻车,不是因为模型笨,而是工具描述写得太含糊。比如把 SearxNG 描述成"搜索工具",模型就不知道该传什么关键词、该怎么处理结果。后面我会给实际可用的工具描述模板。2. SearxNG:自托管的元搜索聚合器,让 Agent 有一双"实时眼睛"先说信息获取这块。很多人的第一反应是:直接调某搜索引擎的 API 不行吗?可以,但有几个现实问题:免费额度有限、商用要付费、接口权限经常变、有些区域不可用,而且会把 Agent 的检索行为绑定到单一服务商。我在项目里最终选的是SearxNG——一个开源的自托管元搜索引擎。它不是自己爬网页,而是同时查询上游多个搜索来源,再把结果聚合去重后返回给你。自托管意味着你拥有完整的控制权:可以统一监控、可以按需禁用某些上游、可以缓存结果,最重要的是可以只对 Agent 开放内网接口,不用让每个请求都算在外部付费账户头上。对于高频检索的 Agent 场景,这笔账算下来非常划算。2.1 Docker 部署 SearxNG:最小可行配置SearxNG 的部署开销很小。跑在公司服务器或者自己的 Linux 机器上都行,最省事的方式是 Docker Compose。下面是我实际线下验证过的最小配置:services: searxng: image: searxng/searxng:latest container_name: searxng ports: - "8888:8080" environment: - SEARXNG_BASE_URL=http://localhost:8888 - SEARXNG_SECRET=请改成随机长字符串 volumes: - ./searxng-data:/etc/searxng restart: unless-stopped启动后访问http://localhost:8888能看到界面即成功。这里两个细节:SEARXNG_SECRET务必换成一个强随机串,否则服务会拒绝启动或者暴露敏感配置。我习惯用openssl rand -hex 32生成。容器内端口对宿主是 8080,映射到外部 8888 只是示例,按你自己的端口规划改。之后通过修改searxng-data/settings.yml可以调整上游引擎、限流策略、日志级别。默认的配置已经聚合了谷歌、必应等来源,但某些来源可能请求频繁被拒,所以要在配置里按需启用。提示:不要直接暴露到公网还不用认证。SearxNG 本身没有完善的用户认证机制,建议通过防火墙或网关注掉外部访问,只给 Agent 所在的内网放行,避免被刷成公共代理。2.2 用 JSON 格式接入 Agent 工具SearxNG 对机器调用非常友好,只要在请求 URL 里加上format=json,就会返回 JSON。一个典型的查询请求长这样:curl 'http://localhost:8888/search?q=Agent+tool+integration&format=json&categories=general'返回结构大致如下:{ "results": [ { "title": "Agent Tool Integration Guide", "url": "https://example.com/guide", "publishedDate": null, "content": "This guide explains how to integrate external tools..." } ], "number_of_results": 1000, "query": "Agent tool integration" }实际接入时,我不建议直接把整个 JSON 原样塞给模型。里面有一些无关字段和噪声,Token 消耗很快。更好的做法是做一层浅加工,只提取关键字段,压缩成干净的字符串列表:async def search_web(query: str, max_results: int = 5) -> str: """调用 SearxNG 并提取标题、链接、摘要""" params = {"q": query, "format": "json", "categories": "general"} async with httpx.AsyncClient(timeout=10) as client: resp = await client.get("http://localhost:8888/search", params=params) data = resp.json() rows = [] for item in data.get("results", [])[:max_results]: title = item.get("title") or "" url = item.get("url") or "" content = (item.get("content") or "")[:250] rows.append(f"- {title}\n 链接: {url}\n 摘要: {content}") return "\n".join(rows) if rows else "没有搜索到有效结果"这个函数返回的是一个干净文本块,模型读起来很舒适。如果直接把原始 JSON 丢给它,模型经常会被无关字段带偏,我甚至见过模型引用infobox里的非官方摘要当结论。预处理这层不能省。2.3 为什么选 SearxNG 而不是直接调多个 Search API直接调单一搜索引擎官方 API,优点是大厂结果质量高、稳定,缺点也很明显:配额与成本:免费层请求数量有限,Agent 任务动不动要搜索几十次,配额很快就会见底。一旦转成付费,单次请求成本需要精打细算。单点问题:如果该搜索引擎调整策略、某个地区不可用、或者开放平台抽风,Agent 的整个检索链路就断了。隐私与合规:Agent 每次搜索都会把用户的完整意图发到第三方服务。自托管 SearxNG 后,查询只落在自己的上游出口,至少中间少了一个集中记录。这是很多企业在内网部署 Agent 时的硬性要求。我当然不是说你完全不需要官方 API。如果你有预算,并且需求就是某一家检索结果质量特别高的垂直场景,那直接调 API 也没问题。但对于我这种"家常便饭"式的 Agent 检索需求,SearxNG 既省钱又灵活,还能统一做缓存和限流,性价比高出一大截。3. WolframAlpha:为 Agent 补上精确计算与符号推理的短板如果说搜索给 Agent 的是"眼界",那 WolframAlpha 给的就是"算力"——不是 GPU 那种算力,而是符号计算和结构化知识。很多问题,比如"一个物体的动量是多少""根号下 1234567 等于多少""3 英尺加 40 厘米是多少米",让大模型直接做,它不会老老实实告诉你说自己不确定,而是会流畅地编一个近似答案。这时候让 Agent 求助于 WolframAlpha,它通过查询计算服务获得精确答案,再把答案交还给模型复述,准确率瞬间拉满。WolframAlpha 的底层是 Wolfram 语言和庞大的知识库。它能求微积分、解方程、画函数图像、查元素周期表、换算单位、计算天气统计……这些是大模型无法稳定内置的能力。而且它的查询支持自然语言,意思是"temperature in Shanghai"这种短语它能看懂,甚至 "integrate x^2" 也可以直接处理。3.1 两种 API 的选择:Short Answers 还是 Full ResultsWolframAlpha 的开放 API 主要分两类(更细的说分为 Short Answers API、Full Results API 以及较新的 LLM-based API)。做一个快速对比。Short Answers API适合"只想要一句话答案"的场景:curl 'https://api.wolframalpha.com/v1/result?appid=你的APPID&i=integrate%20x%5E2'返回:x^3/3就这么干净。没有步骤,没有解释,就是一个可复用的精确结果。拿来当 Agent 的计算器再合适不过。Full Results API返回一个 XML(或 JSON)结构,包含输入解析、计算结果、图表、甚至分步骤推理。适合要给用户展示过程,或者答案本身很复杂、需要让模型进一步决策的场景。curl 'https://api.wolframalpha.com/v2/query?appid=你的APPID&input=integrate%20x%5E2&format=plaintext'返回里会有:plaintext: | x^3/3 |我实际项目里的经验是:如果你只关心"数值或符号结果对不对",用 Short Answers 就够了,响应更快、解析成本更低。但如果你需要 Agent 解释"为什么是这个答案"、或者要展示图表,就得上 Full Results。我把这两种 API 分别封装成两个工具让 Agent 自己选——短答案版用于快速计算,完整版用于需要证据链的场景。3.2 如何引导 Agent 正确构造 WolframAlpha 查询WolframAlpha 虽然能理解自然语言,但和"搜索引擎"的理解逻辑是两回事。搜索是模糊匹配、给一堆链接,WolframAlpha 是严格解析输入并对应到它内部的符号表达式。用户说"帮我算一下这个函数的导数,f 等于 x 平方加一",这种长句子直接丢给 WolframAlpha 常常解析失败。让 Agent 先把问题压缩成短查询,比如:derivative of x^2+1。这个压缩能力不需要我们写复杂代码,写在工具描述里,让模型自己学会即可。我用的工具描述模板长这样:工具名: wolfram_query 用途: 当回答中涉及精确数学公式、单位换算、科学常数、统计计算时使用。 输入: query,应当是一个短表达式,例如: - "integrate x^2" - "2 meters in feet" - "density of iron" - "sqrt(1234567)" 约束: 不要将自然语言长句塞入;如果问题是概念性解释,使用搜索工具。这里最关键的约束是:不要拿 WolframAlpha 做"解释概念"的事。比如"什么是导数"这个问题,它即使能返回一些定义,也不如搜索来的丰富。Agent 有时候会误用工具,所以我们必须在描述里把"什么时候不该用它"写清楚。还有一点,要处理查询解析失败的返回。WolframAlpha 在听不懂时通常会返回类似 "Wolfram|Alpha didn't understand your query" 的提示。这时候你的工具层要有容错逻辑:捕获这个错误,把返回值转换成"本次查询未识别,建议换个表达方式",再让模型重新构造查询,而不是直接把原始错误甩给用户。3.3 几个打动我的实际案例拿它的单位换算来说。我问普通人"3 英里每小时等于多少米每秒",他可能还要打开计算器换算半天,但 WolframAlpha 一两秒就给出精确答案:1.34112 m/s。这种确定性计算能力,是生成式模型给不了的。另一个让我印象深刻的场景是化学相关。学生问 Agent"100 克碳酸钙和足量盐酸反应能生成多少摩尔二氧化碳"。这种问题本质是化学计量计算,大模型能记住公式,但代入具体数字时经常换算错误。Agent 在识别到这种需要精确系数的问题时,转而调用 WolframAlpha 的0.1 mol CaCO3 + HCl查询,得到的摩尔数就完全可信了。所以,把 WolframAlpha 的定位理解为"精确知识计算器"更准确。它不适合检索,但凡是"算得准"的问题,它是最可信的。4. 代码解释器:给 Agent 一个可以"动手实验"的沙箱搜索负责找信息,WolframAlpha 负责算已知的公式,但现实世界的问题哪有那么规矩?数据要清洗、逻辑要反复验证、图表要生成、批量任务要跑循环。这时候最锋利的工具是代码解释器——也就是让 Agent 能写 Python 代码、传给一个沙箱环境执行、再把结果收回来继续推理。这东西是你 Agent 的"瑞士军刀"。它能做的远不止算数:处理 CSV / Excel / JSON 数据,筛选排序聚合。用 matplotlib 画折线图、柱状图、热力图。调用正则、文件解析、网页内容解析等标准库做文本处理。把不同格式的数据文件转来转去:html 转 pdf、md 转 docx、图片改尺寸。代码解释器是整个三件套里身手最灵活的一个,也因此最难缠——代码是 Agent 写的,能不能跑通、跑完之后怎么把产物传给下一步,都要你做好设计。4.1 沙箱的两种实现方式:子进程与容器"代码解释器"本质上就是一个 Python 运行环境,但绝不能把 Agent 生成的代码直接扔到你的主进程里跑。轻则把工作目录搞得乱七八糟,重则删掉库文件、读走敏感信息。我强烈建议单独开一个隔离环境。最简单的方案是subprocess + 虚拟环境。在本地用一个 venv 隔离依赖,执行方式像这样:import subprocess def run_code(code: str, timeout: int = 30): with open("agent_script.py", "w", encoding="utf-8") as f: f.write(code) result = subprocess.run( ["venv/bin/python", "agent_script.py"], capture_output=True, text=True, timeout=timeout, cwd="./sandbox_workspace", ) return { "stdout": result.stdout[-4000:], "stderr": result.stderr[-2000:], "exit_code": result.returncode, }但这样只能挡住"手滑"级别的风险,挡不住恶意代码。如果 Agent 接收的是公开数据甚至用户上传文件,那代码可能被诱导做危险操作。更稳妥的方案是Docker 容器沙箱:每次执行新任务,启动一个一次性容器,任务结束直接销毁。docker run --rm --network=none --memory=256m --cpus=1 \ -v ./workspace:/workspace -w /workspace \ python:3.11-slim python /workspace/agent_script.py这里我用了--network=none切断网络,这是安全基准之一。大部分代码解释器任务不需要联网,联网应该走 SearxNG 工具,而不是让沙箱里的代码随便请求。--memory和--cpus限制资源占用,避免一段死循环代码拖垮整台服务器。4.2 会话内状态管理:让 Agent 记住上一次的结果代码解释器最容易踩的坑是:模型以为环境是连续的,实际上每次执行可能都是全新状态。比如 Agent 第一步"加载数据到 df",第二步"打印 df 的前三行",如果第二步脚本是全新的,df根本不存在。解决方案有两种:让每一步代码生成都自包含。在提示词里要求 Agent"每次生成的代码必须包含必要的 import、定义和文件读取",不要依赖上一次的变量。用工作目录持久化中间文件。脚本读取和写入同一个工作目录,比如/workspace/data.csv,第二步脚本重新pd.read_csv再处理。这样即使变量不保留,文件还在。我用的是第二种为主,因为更直观。代码解释器的工具描述里明确写一句"工作目录是 /workspace,上一次任务生成的临时文件都在里面",模型就会习惯性地把中间结果落盘到目录里,然后下一步再读出来。这避免了多次执行之间的记忆问题,也让用户事后能看到中间文件。4.3 安全边界与资源配额设计给 Agent 开放代码执行能力,就是把一个会编程的"实习生"请进你的系统,你要给他画好活动范围。我整理的硬性边界,直接抄作业:超时控制:单次执行不超过 30 秒,超时直接 kill。我设过 60 秒,结果 Agent 写了个死循环查半天都没响应,体验很糟,后来统一改 30 秒。内存限制:Docker 里用--memory=256m,跑数据量大的任务可以放宽到 512m,但别超过 1g。** CPU 限制**:--cpus=1足够,太多并发容易把服务器挤爆。网络隔离:默认--network=none。如果某个任务确实需要联网,单独开放白名单域名。文件操作白名单:限制只能读取/workspace,不允许访问系统目录。标准输出截断:stdout超过 4000 字符后截断,防止模型上下文被刷爆。注意:代码解释器是"能动手"的工具,也是风险最高的工具。即便关了网络,恶意代码也可能通过读文件、大量文件 IO 来搞破坏,所以不要把非可信来源的数据直接喂给它,也别让它运行在拥有敏感数据权限的环境里。最理想是单独的虚拟机或专用容器,权限最小化。5. 三件套的协同编排:从问题到答案的完整流程三个工具单独都能干活,但它们各自的强项,只有在配合起来时才最有威力。我常用一个任务来测试 Agent 的编排能力:"统计某个城市近 30 天的平均日照时长,并画一张趋势图。"这个任务信息是动态的、计算是精确的、输出是可视化的,单靠任何一个工具都完不成。下面是编排流程。5.1 任务拆解与工具路由Agent 接到问题后,第一步不是直奔代码解释器,而是先做计划。我在系统提示词里做了这样的工具路由逻辑:先检索:"近 30 天日照时长"不是模型训练数据里能直接查到的,所以先用 SearxNG 搜天气数据或历史日照记录,找到可引用的来源,尽量拿到数字或数据表。再计算:如果数据里有复杂的换算要求(比如把天文学的日照公式系数代进来),就用 WolframAlpha 验算公式是否正确。最后执行:把搜集到的数据整理成脚本,用代码解释器跑出趋势图。对应到 Agent 实现上,就是让模型按 ReAct 模式循环:思考 → 调用工具 → 观察结果 → 再思考。5.2 上下文传递的设计工具之间传递信息,关键路径有三个:搜索 → 代码:搜索得到的数据往往是文本,不能直接拿去画图。需要 Agent 自己做一个信息抽取的中间步骤,把"搜索到的一些数"转成结构化的数组。我在系统提示词里写了一句话:当你收集到数值数据,先用代码解释器的 json.dumps 把中间结果写成/workspace/sunshine_data.json,供后续脚本读取。WolframAlpha → 代码:如果查询的结果是某个公式系数(比如某个常数),代码解释器脚本里要能引用这个数值。正确做法也是把它写进临时文件或者直接内嵌到生成的 Python 代码里;我发现直接把数值内嵌到代码里更不容易出错,省得读文件的代码平白多出错机会。代码 → 用户:代码解释器返回的stdout里只有文字描述,图片需要落盘到/workspace/output.png。你的工具层要负责把文件转成链接或 base64,才能真正让用户看到图。我在代码解释器执行后,会检查/workspace下是否有新增的图片文件,自动追加到回复内容里。这个流程跑通后,你会感觉到 Agent 不再是一个"回答问题"的机器,而是一个"完成项目"的助手:它先自己做功课查资料,再动手分析,最后把成果交付到你手上。6. 实战中的坑与优化:来自数十次真实调用的教训每个工具单独跑的时候都挺好,一旦组合起来,麻烦事就排队上门。我在几次 Agent 调试中踩了不少坑,挑几个最有代表性的讲。6.1 搜索结果的"脏数据"污染问题SearxNG 聚合的结果质量并不稳定。默认返回里经常夹杂一些 SEO 垃圾站点、内容农场、甚至页面乱码。给模型喂这种数据,它可能在推理过程中被带偏,引用一个根本不存在的"专家结论"。我的优化手段:结果置信度过滤:优先抓含数字、日期、官方域名的结果,排掉 URL 里带明显垃圾特征(如/out/、partner)的。摘要截取前 250 字符:搜索引擎摘要中前面通常是页面自述,后面经常是无关噪声。短摘要可以有效过滤掉大部分文本垃圾。模型二次确认:让 Agent 在引用搜索内容时,必须附上来源链接。如果后续推理中的结论和链接内容对不上,触发重新检索。这个优化不是让检索变慢,而是让检索后的信息更"干净",模型拿到的结论可信度明显提升。6.2 WolframAlpha 用自然语言长句提交导致的失败第一次接入时,我图省事,直接把用户问题原封不动转给 WolframAlpha,结果一半请求都返回 "didn't understand your query"。"帮我算一下从 2020 年 1 月 1 日到今天一共有几天"这种带背景的长句子,WolframAlpha 根本听不懂,得改写成days since 2020-01-01。优化方式是在工具函数里加"查询重写"提示:如果请求方返回未识别,让 Agent 用英文短查询重试。目前我用的是一个简单的二次试错循环:def wolfram_query_with_retry(query: str): resp = call_wolfram(query) if "didn't understand" in resp: better_query = llm_shorten_query(query) # 让模型压缩查询词 return call_wolfram(better_query) return resp这个循环往往一次重试就成功,代价极低,但成功率提升显著。6.3 代码解释器的路径混乱与依赖缺失Agent 生成代码时,经常会出现路径硬编码错误。比如它写open("data.csv"),但执行环境的工作目录是/workspace,用户上传的文件却在/workspace/uploads。一旦路径没对齐,Agent 就会对着一个 FileNotFoundError 循环尝试,非常浪费时间。我的解决方法是:在代码解释器工具描述里放一个标准开卡模板,要求 Agent 每个脚本开头都这样写:import os, sys BASE = "/workspace" # 所有相对路径必须基于 BASE # 如果文件在子目录,比如 uploads, 请写 os.path.join(BASE, "uploads", "data.csv")同时工具层在返回报错时,额外做一个自动告警:如果stderr里出现 "No such file" 或 "ModuleNotFoundError",就给模型追加一条提示:"工作目录为 /workspace,请先ls /workspace查看已有文件,再用绝对路径操作。"这个微小的反馈机制,能省掉我过去一大半的排错时间。依赖缺失是另一个高频问题。沙箱是精简 Python 镜像,默认没有 pandas、matplotlib。所以我在代码解释器工具描述里明确写了可选依赖清单:pandas, numpy, matplotlib, openpyxl, requests(虽然网络隔离,requests 可能用不上),并且做了一个启动时自动 pip install 最小集。如果 Agent 尝试 import 一个未安装的库,我会让返回信息里包含"可选装以下库:",引导它改用已提供的库。6.4 编排顺序:让工具按成本从小到大调用这是我个人的核心心得。工具调用是有成本的:SearxNG 查询:几秒,单位成本低。WolframAlpha 查询:几秒到十几秒,且 API 配额有限。代码解释器执行:可快可慢,但每次执行都可能消耗 token 来接收输出,修正错误还要再跑。所以我在提示词里强制约束工具的调用顺序:优先搜索获取线索,能用 WolframAlpha 短答案解决就别上代码解释器,代码解释器只在"需要处理多步骤数据/可视化/批量计算"时调用。这能极大减少 token 消耗和任务失败率。实际测试中,一个复杂问题如果一开始就上代码解释器,经常出现"数据都没拿到就急着画图"的空跑;按成本次序调用后,同样的任务平均减少 2-3 次无谓的工具调用。工具不在多,在于能找到各自的定位,并且配合得当。SearxNG、WolframAlpha 与代码解释器这三件套,一个管"看",一个管"算",一个管"做",正好覆盖了 Agent 日常任务里最高频的三种需求。如果你刚开始搭 Agent 工具箱,我建议先把这三样跑通,再考虑加语音、加数据库操作之类的扩展。最后再分享一个我在实际维护中的习惯:给每个工具加一层非常薄的日志记录,记录每次调用的参数、耗时和返回码。它不能直接提升 Agent 能力,但在你排查"为什么 Agent 给了一个奇怪回答"的时候,日志是最有效的事后证据。工具栈越复杂,日志越值钱。
任何一个 Agent 跑起来之后,你很快就会撞上一个尴尬场景:它聊起天来头头是道,但一问"今天实时热搜是什么",它只能编;让它"算一下斐波那契数列第 50 项",它开始一本正经地胡说;让它"把这段数据画成图",它只能给你描述一张根本不存在的图。说白了,大模型只有"口才",没有"手"和"眼"。而真正让 Agent 从聊天机器人变成干活系统的,是外挂工具。在众多工具里,Retrieval、精确计算和代码执行这三类能力是刚需,对应到具体实现上,我这些年用得最顺手的三件套就是:SearxNG、WolframAlpha 与代码解释器。这篇内容不是我抄文档的笔记,而是我带着这三个工具在真实 Agent 项目里反复调试后沉淀下来的拆解。会聊到它们各自适合干什么、怎么接入、怎么编排,以及大量只在踩坑时才说得清的细节。无论你是用现成框架搭 Agent,还是想自己从零写工具调用,这份东西都能帮你少走半个月弯路。1. Agent 工具箱的定位:光有大模型口才还不够先搞清楚一个问题:为什么 Agent 一定要配工具箱?大模型本身有海量训练知识,但也因此有天然的三个短板:时间截止训练数据、无法实时获取外部信息;生成式模型不擅长精确数值计算,乘法都可能错;只能输出文本,无法真正操作系统、读写文件、运行代码。工具就是补短板用的。但补的时候要讲究搭配,不是工具越多越好,而是让每种工具负责它最擅长的能力边界。我偏好把工具按能力类型分成三类:信息获取类:负责"查事实、查动态、查详情",代表就是搜索工具。知识与计算类:负责"精确推导、单位换算、数学求解",代表是 WolframAlpha。执行操作类:负责"写代码、跑脚本、处理文件、生成图表",代表是代码解释器。三者的关系不是替代,而是互补。搜索能搜到一大堆网页,但搜不出积分结果;WolframAlpha 能告诉你精确答案,但没法帮你把答案持续计算一百遍;代码解释器什么都能算,但你得自己给它写对算法和逻辑。把三者组合起来,Agent 才真正具备"检索线索 → 精确推理 → 动手验证"的完整能力闭环。1.1 三个工具各自解决什么问题维度SearxNGWolframAlpha代码解释器能力本质元搜索聚合知识引擎与符号计算通用代码执行环境主要用途找网页、找最新信息、找事实线索数学计算、单位换算、实体知识、科学与工程数据写任意代码、处理数据、生成图、文件转码输出特点网页列表、摘要、链接结构化答案、步骤、图表文本输出、文件产物、可视化需要联网是,实时是,调 API通常隔离网络,更安全适合场景开放性事实查询精确、可验证的定量问题需要跑逻辑或生成文件的任务这是我在工具选型时的初始判断表。实际使用中,三者的边界经常有重叠。比如代码解释器理论上也能拉取数据,但联网权限通常被关掉,所以拉数据还得靠搜索类工具;WolframAlpha 也能画图,但自定义能力远不如代码解释器。清晰定位,后面编排才不会乱。1.2 工具调用的底层逻辑:Function Calling 与协议Agent 调用工具,技术上并不是真的"会按按钮",而是通过一层约定。主流方式是Function Calling:把工具描述、参数 schema 作为 JSON 发给模型,模型根据任务选择调用哪个工具并生成符合 schema 的参数,然后你的程序实际执行该工具,把结果再塞回对话上下文让模型继续推理。在较新的 Agent 框架里,这套东西已经具体化为类似MCP(Model Context Protocol)的协议,让工具以标准服务的形式暴露给 Agent。但不管协议怎么变,思维方式不变:给每个工具写清楚"什么时候用、什么时候别用"的描述。给每个工具定义严格的输入参数格式。工具的返回值必须结构完整,方便模型二次读取。我见过不少 Agent 翻车,不是因为模型笨,而是工具描述写得太含糊。比如把 SearxNG 描述成"搜索工具",模型就不知道该传什么关键词、该怎么处理结果。后面我会给实际可用的工具描述模板。2. SearxNG:自托管的元搜索聚合器,让 Agent 有一双"实时眼睛"先说信息获取这块。很多人的第一反应是:直接调某搜索引擎的 API 不行吗?可以,但有几个现实问题:免费额度有限、商用要付费、接口权限经常变、有些区域不可用,而且会把 Agent 的检索行为绑定到单一服务商。我在项目里最终选的是SearxNG——一个开源的自托管元搜索引擎。它不是自己爬网页,而是同时查询上游多个搜索来源,再把结果聚合去重后返回给你。自托管意味着你拥有完整的控制权:可以统一监控、可以按需禁用某些上游、可以缓存结果,最重要的是可以只对 Agent 开放内网接口,不用让每个请求都算在外部付费账户头上。对于高频检索的 Agent 场景,这笔账算下来非常划算。2.1 Docker 部署 SearxNG:最小可行配置SearxNG 的部署开销很小。跑在公司服务器或者自己的 Linux 机器上都行,最省事的方式是 Docker Compose。下面是我实际线下验证过的最小配置:services: searxng: image: searxng/searxng:latest container_name: searxng ports: - "8888:8080" environment: - SEARXNG_BASE_URL=http://localhost:8888 - SEARXNG_SECRET=请改成随机长字符串 volumes: - ./searxng-data:/etc/searxng restart: unless-stopped启动后访问http://localhost:8888能看到界面即成功。这里两个细节:SEARXNG_SECRET务必换成一个强随机串,否则服务会拒绝启动或者暴露敏感配置。我习惯用openssl rand -hex 32生成。容器内端口对宿主是 8080,映射到外部 8888 只是示例,按你自己的端口规划改。之后通过修改searxng-data/settings.yml可以调整上游引擎、限流策略、日志级别。默认的配置已经聚合了谷歌、必应等来源,但某些来源可能请求频繁被拒,所以要在配置里按需启用。提示:不要直接暴露到公网还不用认证。SearxNG 本身没有完善的用户认证机制,建议通过防火墙或网关注掉外部访问,只给 Agent 所在的内网放行,避免被刷成公共代理。2.2 用 JSON 格式接入 Agent 工具SearxNG 对机器调用非常友好,只要在请求 URL 里加上format=json,就会返回 JSON。一个典型的查询请求长这样:curl 'http://localhost:8888/search?q=Agent+tool+integration&format=json&categories=general'返回结构大致如下:{ "results": [ { "title": "Agent Tool Integration Guide", "url": "https://example.com/guide", "publishedDate": null, "content": "This guide explains how to integrate external tools..." } ], "number_of_results": 1000, "query": "Agent tool integration" }实际接入时,我不建议直接把整个 JSON 原样塞给模型。里面有一些无关字段和噪声,Token 消耗很快。更好的做法是做一层浅加工,只提取关键字段,压缩成干净的字符串列表:async def search_web(query: str, max_results: int = 5) -> str: """调用 SearxNG 并提取标题、链接、摘要""" params = {"q": query, "format": "json", "categories": "general"} async with httpx.AsyncClient(timeout=10) as client: resp = await client.get("http://localhost:8888/search", params=params) data = resp.json() rows = [] for item in data.get("results", [])[:max_results]: title = item.get("title") or "" url = item.get("url") or "" content = (item.get("content") or "")[:250] rows.append(f"- {title}\n 链接: {url}\n 摘要: {content}") return "\n".join(rows) if rows else "没有搜索到有效结果"这个函数返回的是一个干净文本块,模型读起来很舒适。如果直接把原始 JSON 丢给它,模型经常会被无关字段带偏,我甚至见过模型引用infobox里的非官方摘要当结论。预处理这层不能省。2.3 为什么选 SearxNG 而不是直接调多个 Search API直接调单一搜索引擎官方 API,优点是大厂结果质量高、稳定,缺点也很明显:配额与成本:免费层请求数量有限,Agent 任务动不动要搜索几十次,配额很快就会见底。一旦转成付费,单次请求成本需要精打细算。单点问题:如果该搜索引擎调整策略、某个地区不可用、或者开放平台抽风,Agent 的整个检索链路就断了。隐私与合规:Agent 每次搜索都会把用户的完整意图发到第三方服务。自托管 SearxNG 后,查询只落在自己的上游出口,至少中间少了一个集中记录。这是很多企业在内网部署 Agent 时的硬性要求。我当然不是说你完全不需要官方 API。如果你有预算,并且需求就是某一家检索结果质量特别高的垂直场景,那直接调 API 也没问题。但对于我这种"家常便饭"式的 Agent 检索需求,SearxNG 既省钱又灵活,还能统一做缓存和限流,性价比高出一大截。3. WolframAlpha:为 Agent 补上精确计算与符号推理的短板如果说搜索给 Agent 的是"眼界",那 WolframAlpha 给的就是"算力"——不是 GPU 那种算力,而是符号计算和结构化知识。很多问题,比如"一个物体的动量是多少""根号下 1234567 等于多少""3 英尺加 40 厘米是多少米",让大模型直接做,它不会老老实实告诉你说自己不确定,而是会流畅地编一个近似答案。这时候让 Agent 求助于 WolframAlpha,它通过查询计算服务获得精确答案,再把答案交还给模型复述,准确率瞬间拉满。WolframAlpha 的底层是 Wolfram 语言和庞大的知识库。它能求微积分、解方程、画函数图像、查元素周期表、换算单位、计算天气统计……这些是大模型无法稳定内置的能力。而且它的查询支持自然语言,意思是"temperature in Shanghai"这种短语它能看懂,甚至 "integrate x^2" 也可以直接处理。3.1 两种 API 的选择:Short Answers 还是 Full ResultsWolframAlpha 的开放 API 主要分两类(更细的说分为 Short Answers API、Full Results API 以及较新的 LLM-based API)。做一个快速对比。Short Answers API适合"只想要一句话答案"的场景:curl 'https://api.wolframalpha.com/v1/result?appid=你的APPID&i=integrate%20x%5E2'返回:x^3/3就这么干净。没有步骤,没有解释,就是一个可复用的精确结果。拿来当 Agent 的计算器再合适不过。Full Results API返回一个 XML(或 JSON)结构,包含输入解析、计算结果、图表、甚至分步骤推理。适合要给用户展示过程,或者答案本身很复杂、需要让模型进一步决策的场景。curl 'https://api.wolframalpha.com/v2/query?appid=你的APPID&input=integrate%20x%5E2&format=plaintext'返回里会有:plaintext: | x^3/3 |我实际项目里的经验是:如果你只关心"数值或符号结果对不对",用 Short Answers 就够了,响应更快、解析成本更低。但如果你需要 Agent 解释"为什么是这个答案"、或者要展示图表,就得上 Full Results。我把这两种 API 分别封装成两个工具让 Agent 自己选——短答案版用于快速计算,完整版用于需要证据链的场景。3.2 如何引导 Agent 正确构造 WolframAlpha 查询WolframAlpha 虽然能理解自然语言,但和"搜索引擎"的理解逻辑是两回事。搜索是模糊匹配、给一堆链接,WolframAlpha 是严格解析输入并对应到它内部的符号表达式。用户说"帮我算一下这个函数的导数,f 等于 x 平方加一",这种长句子直接丢给 WolframAlpha 常常解析失败。让 Agent 先把问题压缩成短查询,比如:derivative of x^2+1。这个压缩能力不需要我们写复杂代码,写在工具描述里,让模型自己学会即可。我用的工具描述模板长这样:工具名: wolfram_query 用途: 当回答中涉及精确数学公式、单位换算、科学常数、统计计算时使用。 输入: query,应当是一个短表达式,例如: - "integrate x^2" - "2 meters in feet" - "density of iron" - "sqrt(1234567)" 约束: 不要将自然语言长句塞入;如果问题是概念性解释,使用搜索工具。这里最关键的约束是:不要拿 WolframAlpha 做"解释概念"的事。比如"什么是导数"这个问题,它即使能返回一些定义,也不如搜索来的丰富。Agent 有时候会误用工具,所以我们必须在描述里把"什么时候不该用它"写清楚。还有一点,要处理查询解析失败的返回。WolframAlpha 在听不懂时通常会返回类似 "Wolfram|Alpha didn't understand your query" 的提示。这时候你的工具层要有容错逻辑:捕获这个错误,把返回值转换成"本次查询未识别,建议换个表达方式",再让模型重新构造查询,而不是直接把原始错误甩给用户。3.3 几个打动我的实际案例拿它的单位换算来说。我问普通人"3 英里每小时等于多少米每秒",他可能还要打开计算器换算半天,但 WolframAlpha 一两秒就给出精确答案:1.34112 m/s。这种确定性计算能力,是生成式模型给不了的。另一个让我印象深刻的场景是化学相关。学生问 Agent"100 克碳酸钙和足量盐酸反应能生成多少摩尔二氧化碳"。这种问题本质是化学计量计算,大模型能记住公式,但代入具体数字时经常换算错误。Agent 在识别到这种需要精确系数的问题时,转而调用 WolframAlpha 的0.1 mol CaCO3 + HCl查询,得到的摩尔数就完全可信了。所以,把 WolframAlpha 的定位理解为"精确知识计算器"更准确。它不适合检索,但凡是"算得准"的问题,它是最可信的。4. 代码解释器:给 Agent 一个可以"动手实验"的沙箱搜索负责找信息,WolframAlpha 负责算已知的公式,但现实世界的问题哪有那么规矩?数据要清洗、逻辑要反复验证、图表要生成、批量任务要跑循环。这时候最锋利的工具是代码解释器——也就是让 Agent 能写 Python 代码、传给一个沙箱环境执行、再把结果收回来继续推理。这东西是你 Agent 的"瑞士军刀"。它能做的远不止算数:处理 CSV / Excel / JSON 数据,筛选排序聚合。用 matplotlib 画折线图、柱状图、热力图。调用正则、文件解析、网页内容解析等标准库做文本处理。把不同格式的数据文件转来转去:html 转 pdf、md 转 docx、图片改尺寸。代码解释器是整个三件套里身手最灵活的一个,也因此最难缠——代码是 Agent 写的,能不能跑通、跑完之后怎么把产物传给下一步,都要你做好设计。4.1 沙箱的两种实现方式:子进程与容器"代码解释器"本质上就是一个 Python 运行环境,但绝不能把 Agent 生成的代码直接扔到你的主进程里跑。轻则把工作目录搞得乱七八糟,重则删掉库文件、读走敏感信息。我强烈建议单独开一个隔离环境。最简单的方案是subprocess + 虚拟环境。在本地用一个 venv 隔离依赖,执行方式像这样:import subprocess def run_code(code: str, timeout: int = 30): with open("agent_script.py", "w", encoding="utf-8") as f: f.write(code) result = subprocess.run( ["venv/bin/python", "agent_script.py"], capture_output=True, text=True, timeout=timeout, cwd="./sandbox_workspace", ) return { "stdout": result.stdout[-4000:], "stderr": result.stderr[-2000:], "exit_code": result.returncode, }但这样只能挡住"手滑"级别的风险,挡不住恶意代码。如果 Agent 接收的是公开数据甚至用户上传文件,那代码可能被诱导做危险操作。更稳妥的方案是Docker 容器沙箱:每次执行新任务,启动一个一次性容器,任务结束直接销毁。docker run --rm --network=none --memory=256m --cpus=1 \ -v ./workspace:/workspace -w /workspace \ python:3.11-slim python /workspace/agent_script.py这里我用了--network=none切断网络,这是安全基准之一。大部分代码解释器任务不需要联网,联网应该走 SearxNG 工具,而不是让沙箱里的代码随便请求。--memory和--cpus限制资源占用,避免一段死循环代码拖垮整台服务器。4.2 会话内状态管理:让 Agent 记住上一次的结果代码解释器最容易踩的坑是:模型以为环境是连续的,实际上每次执行可能都是全新状态。比如 Agent 第一步"加载数据到 df",第二步"打印 df 的前三行",如果第二步脚本是全新的,df根本不存在。解决方案有两种:让每一步代码生成都自包含。在提示词里要求 Agent"每次生成的代码必须包含必要的 import、定义和文件读取",不要依赖上一次的变量。用工作目录持久化中间文件。脚本读取和写入同一个工作目录,比如/workspace/data.csv,第二步脚本重新pd.read_csv再处理。这样即使变量不保留,文件还在。我用的是第二种为主,因为更直观。代码解释器的工具描述里明确写一句"工作目录是 /workspace,上一次任务生成的临时文件都在里面",模型就会习惯性地把中间结果落盘到目录里,然后下一步再读出来。这避免了多次执行之间的记忆问题,也让用户事后能看到中间文件。4.3 安全边界与资源配额设计给 Agent 开放代码执行能力,就是把一个会编程的"实习生"请进你的系统,你要给他画好活动范围。我整理的硬性边界,直接抄作业:超时控制:单次执行不超过 30 秒,超时直接 kill。我设过 60 秒,结果 Agent 写了个死循环查半天都没响应,体验很糟,后来统一改 30 秒。内存限制:Docker 里用--memory=256m,跑数据量大的任务可以放宽到 512m,但别超过 1g。** CPU 限制**:--cpus=1足够,太多并发容易把服务器挤爆。网络隔离:默认--network=none。如果某个任务确实需要联网,单独开放白名单域名。文件操作白名单:限制只能读取/workspace,不允许访问系统目录。标准输出截断:stdout超过 4000 字符后截断,防止模型上下文被刷爆。注意:代码解释器是"能动手"的工具,也是风险最高的工具。即便关了网络,恶意代码也可能通过读文件、大量文件 IO 来搞破坏,所以不要把非可信来源的数据直接喂给它,也别让它运行在拥有敏感数据权限的环境里。最理想是单独的虚拟机或专用容器,权限最小化。5. 三件套的协同编排:从问题到答案的完整流程三个工具单独都能干活,但它们各自的强项,只有在配合起来时才最有威力。我常用一个任务来测试 Agent 的编排能力:"统计某个城市近 30 天的平均日照时长,并画一张趋势图。"这个任务信息是动态的、计算是精确的、输出是可视化的,单靠任何一个工具都完不成。下面是编排流程。5.1 任务拆解与工具路由Agent 接到问题后,第一步不是直奔代码解释器,而是先做计划。我在系统提示词里做了这样的工具路由逻辑:先检索:"近 30 天日照时长"不是模型训练数据里能直接查到的,所以先用 SearxNG 搜天气数据或历史日照记录,找到可引用的来源,尽量拿到数字或数据表。再计算:如果数据里有复杂的换算要求(比如把天文学的日照公式系数代进来),就用 WolframAlpha 验算公式是否正确。最后执行:把搜集到的数据整理成脚本,用代码解释器跑出趋势图。对应到 Agent 实现上,就是让模型按 ReAct 模式循环:思考 → 调用工具 → 观察结果 → 再思考。5.2 上下文传递的设计工具之间传递信息,关键路径有三个:搜索 → 代码:搜索得到的数据往往是文本,不能直接拿去画图。需要 Agent 自己做一个信息抽取的中间步骤,把"搜索到的一些数"转成结构化的数组。我在系统提示词里写了一句话:当你收集到数值数据,先用代码解释器的 json.dumps 把中间结果写成/workspace/sunshine_data.json,供后续脚本读取。WolframAlpha → 代码:如果查询的结果是某个公式系数(比如某个常数),代码解释器脚本里要能引用这个数值。正确做法也是把它写进临时文件或者直接内嵌到生成的 Python 代码里;我发现直接把数值内嵌到代码里更不容易出错,省得读文件的代码平白多出错机会。代码 → 用户:代码解释器返回的stdout里只有文字描述,图片需要落盘到/workspace/output.png。你的工具层要负责把文件转成链接或 base64,才能真正让用户看到图。我在代码解释器执行后,会检查/workspace下是否有新增的图片文件,自动追加到回复内容里。这个流程跑通后,你会感觉到 Agent 不再是一个"回答问题"的机器,而是一个"完成项目"的助手:它先自己做功课查资料,再动手分析,最后把成果交付到你手上。6. 实战中的坑与优化:来自数十次真实调用的教训每个工具单独跑的时候都挺好,一旦组合起来,麻烦事就排队上门。我在几次 Agent 调试中踩了不少坑,挑几个最有代表性的讲。6.1 搜索结果的"脏数据"污染问题SearxNG 聚合的结果质量并不稳定。默认返回里经常夹杂一些 SEO 垃圾站点、内容农场、甚至页面乱码。给模型喂这种数据,它可能在推理过程中被带偏,引用一个根本不存在的"专家结论"。我的优化手段:结果置信度过滤:优先抓含数字、日期、官方域名的结果,排掉 URL 里带明显垃圾特征(如/out/、partner)的。摘要截取前 250 字符:搜索引擎摘要中前面通常是页面自述,后面经常是无关噪声。短摘要可以有效过滤掉大部分文本垃圾。模型二次确认:让 Agent 在引用搜索内容时,必须附上来源链接。如果后续推理中的结论和链接内容对不上,触发重新检索。这个优化不是让检索变慢,而是让检索后的信息更"干净",模型拿到的结论可信度明显提升。6.2 WolframAlpha 用自然语言长句提交导致的失败第一次接入时,我图省事,直接把用户问题原封不动转给 WolframAlpha,结果一半请求都返回 "didn't understand your query"。"帮我算一下从 2020 年 1 月 1 日到今天一共有几天"这种带背景的长句子,WolframAlpha 根本听不懂,得改写成days since 2020-01-01。优化方式是在工具函数里加"查询重写"提示:如果请求方返回未识别,让 Agent 用英文短查询重试。目前我用的是一个简单的二次试错循环:def wolfram_query_with_retry(query: str): resp = call_wolfram(query) if "didn't understand" in resp: better_query = llm_shorten_query(query) # 让模型压缩查询词 return call_wolfram(better_query) return resp这个循环往往一次重试就成功,代价极低,但成功率提升显著。6.3 代码解释器的路径混乱与依赖缺失Agent 生成代码时,经常会出现路径硬编码错误。比如它写open("data.csv"),但执行环境的工作目录是/workspace,用户上传的文件却在/workspace/uploads。一旦路径没对齐,Agent 就会对着一个 FileNotFoundError 循环尝试,非常浪费时间。我的解决方法是:在代码解释器工具描述里放一个标准开卡模板,要求 Agent 每个脚本开头都这样写:import os, sys BASE = "/workspace" # 所有相对路径必须基于 BASE # 如果文件在子目录,比如 uploads, 请写 os.path.join(BASE, "uploads", "data.csv")同时工具层在返回报错时,额外做一个自动告警:如果stderr里出现 "No such file" 或 "ModuleNotFoundError",就给模型追加一条提示:"工作目录为 /workspace,请先ls /workspace查看已有文件,再用绝对路径操作。"这个微小的反馈机制,能省掉我过去一大半的排错时间。依赖缺失是另一个高频问题。沙箱是精简 Python 镜像,默认没有 pandas、matplotlib。所以我在代码解释器工具描述里明确写了可选依赖清单:pandas, numpy, matplotlib, openpyxl, requests(虽然网络隔离,requests 可能用不上),并且做了一个启动时自动 pip install 最小集。如果 Agent 尝试 import 一个未安装的库,我会让返回信息里包含"可选装以下库:",引导它改用已提供的库。6.4 编排顺序:让工具按成本从小到大调用这是我个人的核心心得。工具调用是有成本的:SearxNG 查询:几秒,单位成本低。WolframAlpha 查询:几秒到十几秒,且 API 配额有限。代码解释器执行:可快可慢,但每次执行都可能消耗 token 来接收输出,修正错误还要再跑。所以我在提示词里强制约束工具的调用顺序:优先搜索获取线索,能用 WolframAlpha 短答案解决就别上代码解释器,代码解释器只在"需要处理多步骤数据/可视化/批量计算"时调用。这能极大减少 token 消耗和任务失败率。实际测试中,一个复杂问题如果一开始就上代码解释器,经常出现"数据都没拿到就急着画图"的空跑;按成本次序调用后,同样的任务平均减少 2-3 次无谓的工具调用。工具不在多,在于能找到各自的定位,并且配合得当。SearxNG、WolframAlpha 与代码解释器这三件套,一个管"看",一个管"算",一个管"做",正好覆盖了 Agent 日常任务里最高频的三种需求。如果你刚开始搭 Agent 工具箱,我建议先把这三样跑通,再考虑加语音、加数据库操作之类的扩展。最后再分享一个我在实际维护中的习惯:给每个工具加一层非常薄的日志记录,记录每次调用的参数、耗时和返回码。它不能直接提升 Agent 能力,但在你排查"为什么 Agent 给了一个奇怪回答"的时候,日志是最有效的事后证据。工具栈越复杂,日志越值钱。