跳到主要内容

02 · 工具集

本章讲 agent 手里的五样工具:各自怎么实现、派发时的分支逻辑、以及为什么 visit 是工程含量最高的一支。读完你能讲清每个工具吃什么、吐什么、坑在哪。

全景:五个工具

工具在 react_agent.py:31-38 注册进 TOOL_MAP,模型按 SYSTEM_PROMPT 里的签名调用。

工具名干什么底层依赖文件
search批量 Google 网页搜索,返回前若干结果的标题+摘要Serper APItool_search.py
google_scholar批量学术搜索,带引用数/发表信息Serper /scholartool_scholar.py
visit抓网页 → LLM 摘要成「证据+总结」Jina Reader + 摘要 LLMtool_visit.py
PythonInterpreter沙箱里执行 PythonSandboxFusiontool_python.py
parse_file解析本地 PDF/DOCX/音视频等qwen_agent 文件解析 + VideoAgenttool_file.py

所有工具都继承 qwen_agent 的 BaseTool,用 @register_tool(..., allow_overwrite=True) 注册,核心方法都是 call(params) -> str

3.1 search / scholar:最简单的一对

它要解决的小问题: 给模型一个「谷歌一下」的能力,而且一次能搜多条 query。

Search.call 接受一个 query 数组,串行逐条搜再用 \n=======\n 拼接(tool_search.py:113-130)。单条搜索走 Serper(google_search_with_serp, tool_search.py:38-105),有个贴心细节:

# tool_search.py:39-56 —— 中文 query 自动切到中国区
if contains_chinese_basic(query):
payload = {"q": query, "location": "China", "gl": "cn", "hl": "zh-cn"}
else:
payload = {"q": query, "location": "United States", "gl": "us", "hl": "en"}

返回时把每条结果格式化成 序号. [标题](链接)\n发布日期\n来源\n摘要(tool_search.py:83-102),这样模型看到的是干净的 Markdown 列表。

Scholar 几乎一样,只是打 Serper 的 /scholar 端点,并额外抽取 citedBy(引用数)、publicationInfopdfUrl(tool_scholar.py:60-88)。区别:Scholar 用 ThreadPoolExecutor 并发搜多条(tool_scholar.py:106-108),而 search 是串行——一个小小的不一致。

3.2 visit:工程含量最高的一支

它要解决的小问题: 网页动辄几万字,不能整页塞回上下文。得先抓下来,再压缩成与当前目标相关的证据

思路: 分两步——(1) 用 Jina Reader 把 URL 抓成干净文本;(2) 拿一个独立的摘要 LLM,按 EXTRACTOR_PROMPT 把长文提炼成 {rational, evidence, summary} 三段 JSON。

图示(怎么读:从左到右是 visit 内部两阶段流水线):

url ──► html_readpage_jina ──► 长文本 ──► 截到 95K token ──► 摘要LLM(EXTRACTOR_PROMPT)
(Jina, 最多重试8次) │

回填给主循环 ◄── "Evidence: … / Summary: …" ◄── 解析出 evidence+summary

关键实现点:

  • 抓取靠 Jina。 jina_readpagehttps://r.jina.ai/{url},最多重试 3 次(tool_visit.py:132-167);外面又套 html_readpage_jina 最多试 8 次(tool_visit.py:169-177)。

  • 先截断再摘要。 抓来的内容先 truncate_to_tokens(content, 95000)(用 tiktokencl100k_base 编码,tool_visit.py:23-32),避免撑爆摘要模型。

  • 摘要用另一个 LLM,不是主模型。 call_serverAPI_KEY / API_BASE / SUMMARY_MODEL_NAME 环境变量,调一个 OpenAI 兼容端点(tool_visit.py:99-129)。这是「主模型思考、副模型压缩」的分工。

  • 摘要失败层层降级。 若摘要太短,循环里逐步把内容截更短再重试(0.7× 递减,最后砍到 25000 字,tool_visit.py:202-221);解析 JSON 失败也重试 3 次(tool_visit.py:226-232);全失败则返回一句「网页无法处理」的兜底文本(tool_visit.py:234-237)。这套多级容错是 visit 最见功力的地方,详见 03

  • 多 URL 串行 + 900 秒总闸。 传数组时逐个抓,总耗时超 900 秒后剩下的直接返回兜底文本(tool_visit.py:83-94)。

最终回填格式固定为(tool_visit.py:239-241):

The useful information in {url} for user goal {goal} as follows:
Evidence in page: {evidence}
Summary: {summary}

3.3 PythonInterpreter:唯一走沙箱的工具

它要解决的小问题: 让模型能算数/处理数据,又不能让它在你机器上乱跑代码。

思路: 代码不在本地执行,而是丢给 SandboxFusion(字节开源的代码沙箱)远程跑。端点从环境变量 SANDBOX_FUSION_ENDPOINT 读,可配多个(逗号分隔,tool_python.py:20-25)。

call 里每次随机挑一个端点,最多重试 8 次(tool_python.py:76-109),把 stdout/stderr 拼成结果字符串返回。超时判断很朴素:执行时间 ≥ timeout-1 秒就当超时(tool_python.py:89-90)。

为什么它的调用格式特殊? 回顾 01 §3.5:主循环发现 tool_call 里含 "python" 就走单独分支,直接从 <code></code> 里抠代码传进来(react_agent.py:162-167)——绕开 JSON,因为代码塞 JSON 太容易转义炸。工具描述里明写了这个约定(prompt.py:11-25)。

3.4 parse_file:解析本地文件

它要解决的小问题: 用户上传了 PDF/表格/音视频,得先把它们变成模型能读的文本。

FileParser.callasync 的(tool_file.py:112-141),按扩展名分流:非 .mp3 走通用文件解析器 SingleFileParser,音视频走 VideoAgent。解析后若总 token 超 DEFAULT_MAX_INPUT_TOKENS 就调 compress 压一遍(tool_file.py:93-96)。因为是 async,主循环里得用 asyncio.run(...) 包一层调用(react_agent.py:233-237)。

3.5 工具派发的坑:custom_call_tool

派发逻辑在 react_agent.py:228-247。有几处值得注意:

  • 参数被套了一层。 tool_args["params"] = tool_args(react_agent.py:230)——把参数字典塞进自己的 params 键,以适配 qwen_agent BaseTool 的取参约定。

  • parse_file 特判文件根目录。 只有它显式传 file_root_path="./eval_data/file_corpus"(react_agent.py:236),对应 README 里「把文件放这个目录」的约定。

  • 找不到工具返回错误字符串,不抛异常(react_agent.py:246-247),交由主循环当作普通观察回填——模型有机会看到「工具不存在」并改正。

代码地图

主题文件路径符号名
网页搜索inference/tool_search.pySearch.google_search_with_serp
学术搜索inference/tool_scholar.pyScholar.google_scholar_with_serp
抓网页inference/tool_visit.pyVisit.jina_readpagehtml_readpage_jina
网页摘要成证据inference/tool_visit.pyVisit.readpage_jinaVisit.call_server
摘要提示词inference/prompt.pyEXTRACTOR_PROMPT
Python 沙箱inference/tool_python.pyPythonInterpreter.call
文件解析inference/tool_file.pyFileParser.callfile_parser
工具派发inference/react_agent.pyMultiTurnReactAgent.custom_call_tool