ByteNoteByteNote
DeepSeek Harness Web 访问接缝解析
字

字节笔记本

2026年10月6日 · 约 7 分钟读完

DeepSeek Harness Web 访问接缝解析

API中转
¥120

DeepSeek Harness 是一个开源的 agent 框架,它的做法是把智能体需要的每一类外部能力收拢成一条能力接缝:能力词汇集中定义在一处,具体实现可以随时替换,模型与上层代码感知不到变化。本文拆解其中负责联网的一条:Web 访问接缝。它包含搜索与抓取两个操作,挂在同一个 ctx.web 服务上,实现分散在几组包里。

DeepSeek Harness Web 访问接缝架构

一条接缝,两个操作

搜索与抓取不共享任何请求结构,也没有共享业务逻辑,却被刻意放进同一个中间层。合并的收益有三点:提供方选择策略只有一个归属地;中止与错误只有一套词汇;产品层面只暴露一个配置入口,回答这个 harness 究竟如何联网。代价是服务上永远存在 search 与 fetch 两条平行的方法对,文档明确说明这是有意为之,不是漏掉的抽象。

角色拆分横跨三类包:dsh-web 负责服务定义,持有 ctx.web 与提供方注册表;dsh-web-search-exa、dsh-web-search-perplexity、dsh-web-search-deepseek 与 dsh-web-fetch-http 是具体提供方;dsh-tool-web 是唯一消费方,模型看到的 web_search 与 web_fetch 两个工具,其名字、参数 schema、提示词指引和结果呈现全部住在这里。提供方注册进接缝的是能力,也就是 WebSearchProvider 或 WebFetchProvider,而不是工具。

Web 是一个可选能力,不在 agent 循环主链路上,所以它的词汇不进核心文档。换一个搜索后端,模型提问的方式不变;换一个抓取后端,模型递交 URL 的方式也不变。

搜索:模型只管问,接缝负责封顶

模型侧的搜索参数只有一个 query。maxResults 是消费方拥有的上限,来自 dsh-tool-web 的配置,默认值为 8,原样穿过接缝,并在返回路上强制执行:提供方如果超量返回,接缝就把 sources 截断到上限,同时把 truncated 置为真。支持结果数控制的提供方,可以在请求层提前应用这个上限来省成本和延迟,但接缝的截断无论如何都会执行。

归一化的搜索结果有三件套:content 是可选的提供方生成答案或摘要,Exa 与 DeepSeek 不返回,Perplexity 会返回;sources 是可移植的引用列表;truncated 标记是否被接缝裁剪过。单个来源只有 url 是必填字段,title、snippet、publishedAt 都是可选,理由很直接:不是每个提供方都返回这些字段,强迫适配器编造就等于让接缝说谎,Perplexity 的引用甚至可能只有 URL。缺标题时,展示层用域名兜底。

抓取:404 也是结果,不是错误

抓取请求同样克制,只有一个 url 字段。超时、格式、提示词、内容抽取这些控制被刻意排除在请求之外:取消是执行时的直接参数,而呈现与更高层的模型关注点,本就不该混进安全检索。

最有意思的取舍在状态码上:HTTP 状态属于被抓资源的状态,不自动等于失败。成功取回一个 404 或 500 的页面,返回的是带状态码和有界解码正文的正常结果;结果里的 url 是允许重定向之后的最终地址。WebError 只留给无法安全取回或无法表示资源的情况。

正文是一个封闭判别联合 WebFetchBody,目前只有 html 与 text 两种类型,归 dsh-web 所有:提供方负责解码分类,消费方负责渲染,新增类型是跨包协同变更,不是插件能私自扩展的点。消费方的分支处理都以 assertNever 收尾,新增一个未处理的类型,会让所有消费方立即编译失败。即便两个字段完全相同,每个分支也保持独立的对象字面量,方便某个分支日后长出自己的字段。

执行期选择:永不先到先得

提供方的 available() 是廉价的本地检查,只看凭据在不在、配置可不可解析,禁止发起网络请求。它是执行期选择的输入,不是健康检查系统;选择失败会以结构化 WebError 浮出,错误码与错误消息里带着可分支处理的细节,比如缺失的 id 或歧义的候选集合。

执行期选择规则与错误码归属

选择规则与注册顺序、配置加载顺序、热更新顺序都无关,只有六种情况:

  • 配置了 id 且该提供方已注册并可用,就用它;
  • 配置的 id 未注册,报 WEB_PROVIDER_CONFIGURED_MISSING;
  • 配置的 id 注册了但不可用,报 WEB_PROVIDER_CONFIGURED_UNAVAILABLE;
  • 没配置 id 且恰好有一个可用提供方,自动选中它;
  • 没配置 id 且有多个可用提供方,报 WEB_PROVIDER_AMBIGUOUS,绝不先到先得;
  • 没配置 id 且没有可用提供方,报 WEB_PROVIDER_UNAVAILABLE。

错误码:开放集合,按归属分两组

WebError 继承自核心错误基类 HarnessError,code 是开放字符串而非封闭联合:提供方可以抛自己的错误码而不用修改 dsh-web,消费方则必须容忍未知码。错误码按归属分两组。

第一组是接缝中性码,由共享运行时契约抛出,包括提供方不可用、配置 id 缺失、配置 id 不可用、选择歧义、重复注册(这是注册期的编程错误)、中止,以及兜底的 WEB_PROVIDER_ERROR,提供方自身的失败都从这里浮出,域名解析失败、连接被拒、TLS 握手出错这类传输故障也在其中。第二组是抓取传输码,归 dsh-web-fetch-http 所有:非法 URL、被封锁 URL、重定向被拒、体积超限、超时、内容类型不支持。换成别的抓取后端实现,不必抛这组码。

服务面与本地抓取的安全边界

WebRuntime 负责注册搜索与抓取提供方:重复的 id 直接抛 WEB_DUPLICATE_PROVIDER,注册返回一个清理函数,随调用方的生命周期自动释放;search 与 fetch 都在真正调用时才解析提供方,套用上面的选择规则。

内置的本地抓取后端把边界划得很清楚:只接受 HTTP 与 HTTPS;拒绝带凭据的 URL;限制重定向次数、字节数、字符数和总时长;每一个同源重定向跳都重新校验;正文由后端解码,呈现归工具层。文档同时给了一句醒目的警告:本地后端不阻断私网目标,在能触达敏感内网的环境里不要启用 web_fetch。

这条接缝的价值在于边界干净:模型只需要会说搜这个、抓那个,上限、截断、错误分类、提供方选择这些琐碎但关键的决策全部留在接缝里。想给 DeepSeek Harness 换或加一个联网后端,照着 WebSearchProvider 与 WebFetchProvider 两个接口实现再注册即可,模型与提示词一行都不用改。

相关文章

分享: