Skip to content
掌握度

CodeAgent CLI 项目深挖 · 单期模块。总览见 需求分析 与 整体设计方案。

给 Agent 加两个联网工具——web_search(搜实时信息)和 web_fetch(抓网页正文),背后是「多 provider 抽象 + SSRF 安全策略 + HTML 正文提取」三层设计。

联网搜索实现 ​

两条工具链。

链路一:web_search。

  1. 先判断是否 shouldPreferStepSearch——当前模型是 step-3.7-flash 且 StepSearch MCP 就绪时,优先转调 MCP 的 web_search,失败再回退;
  2. 否则走 SearchProviderFactory.create 选一个搜索 provider(智谱 / SerpAPI / SearXNG 三家);
  3. provider.search(query, topK) 执行,返回带 position(编号,方便 LLM 引用)/ title / url / snippet / source(域名)的结果;
  4. 格式化回给 LLM。

链路二:web_fetch。

  1. 安全校验:NetworkPolicy.checkUrl 先查 URL(scheme 白名单 http/https + 主机黑名单防 SSRF),再 acquire 申请限流配额;
  2. WebFetcher.fetch 拿 HTML(5MB 上限、30 秒超时);
  3. HtmlExtractor.extract 用 Jsoup 做「极简版 readability」,把 HTML 提取成正文 Markdown;
  4. 按 max_chars 截断,格式化返回。

【主动抛深度点 1:多 provider 抽象 + 工厂自动选择】 搜索 provider 用 SearchProvider 接口统一(name / isReady / search),三家实现:智谱(国内首选,和 GLM 推理共用 key)、SerpAPI(商业聚合、开箱即用)、SearXNG(开源自托管、免费)。工厂的自动选择优先级是:有 GLM_API_KEY → 智谱,有 SERPAPI_KEY → SerpAPI,有 SEARXNG_URL → SearXNG,都没有 → 占位 provider(isReady=false,由调用方提示用户配 key)。加新 provider(Brave/Tavily/Exa)只要实现接口,调用方零改动——这和上一期多模型适配是同一个策略模式思路。

【主动抛深度点 2:NetworkPolicy 的 SSRF 防护】 web_fetch 最大的安全风险是 SSRF(服务端请求伪造)——Agent 可能被诱导去抓 http://localhost:6379 这种内网地址。所以 checkHost 做了防护:字面量黑名单(localhost / 0.0.0.0)+ DNS 解析后检查真实 IP(loopback / site-local / link-local / any-local 全部拒绝,)。再加一个 token bucket 限流(60 秒 30 次),防止 Agent 死循环狂发请求。


搜索 provider 怎么选的?为什么要三家? ​

工厂 SearchProviderFactory 自动选择:按 GLM_API_KEY → SERPAPI_KEY → SEARXNG_URL 的优先级,也可以显式 SEARCH_PROVIDER 指定。三家对应三种需求:智谱国内可用、SerpAPI 国际付费、SearXNG 自托管免费。核心是让用户按成本 / 数据合规 / 离线需求自由切换,而不绑定单一搜索引擎。

web_fetch 怎么防 SSRF? ​

NetworkPolicy.checkUrl 两层:scheme 白名单只放 http/https;主机校验分两步——先字面量黑名单(localhost、0.0.0.0),再 InetAddress.getAllByName 做真实 DNS 解析,检查解析出来的 IP 是不是 loopback / site-local / link-local / any-local,是就拒绝。这防的是「域名解析到内网地址」这种绕过。文档里也诚实说明:面向严苛企业环境还要补 DNS rebinding 防护和 CIDR 白名单。

HTML 怎么转成正文 Markdown? ​

HtmlExtractor 用 Jsoup 实现「极简版 readability」:先清掉噪音标签(script/style/nav/footer 等)和噪音 class(sidebar/menu/ad 等),再给每个元素打分(文本密度、标签权重),选出「主正文元素」,最后递归渲染成 Markdown(标题/列表/表格)。本质是正文提取,不是完整 HTML 转 Markdown。

遇到 JS 渲染或防爬墙的页面怎么办? ​

这期明确不重试。FetchResult 里有 bodyEmpty 和 hint 字段,抓到了 HTML 但提取不出正文时,返回「可能是 JS 渲染或防爬墙,本期范围内不再重试」的提示,让 LLM 知道这是已知边界、不要反复重试。真正解决要等后面 Chrome DevTools MCP(浏览器渲染)那期。这体现了「每期划清边界」的工程态度。

step_search 转调是什么? ​

一个特判优化:当当前模型是 step-3.7-flash 且 StepSearch MCP 工具就绪时,内置 web_search / web_fetch 会优先转调 MCP 版的工具(StepSearch 的搜索结果质量更好),失败再回退到内置的 SearchProvider / WebFetcher。shouldPreferStepSearch 就是那个开关,putIfStepToolAccepts 负责把 topK/maxChars 参数适配到 MCP 工具自己的参数名。

源码锚点(读码定位用,面试不主动讲) ​

  • ToolRegistry.java:847
  • ToolRegistry.java:910
  • ToolRegistry.java:914-921
  • ToolRegistry.java:935-936
  • NetworkPolicy.java:98

本站仅供个人学习使用,请勿外传