# 架构说明 ## 当前交付状态 当前正式 Release 为 `0.2.16`。该版本适配 DSH `0.2.1-alpha.5`:开发运行时升级到 alpha.5,兼容矩阵加入 alpha.5。`@deepseek-ai/dsh-web` 的 provider 注册接口在 alpha.3 至 alpha.5 间保持一致,插件运行代码无需因该接口变化修改。仓库门禁覆盖 771 项测试、构建、类型检查、仓库与分发检查,以及 Windows/Ubuntu × Node.js 22.19/24 的 CI 矩阵。 历史运行修复已进入正式分发包:GitHub 仓库根页与 blob Markdown 的专用路由、同资源 HTTP → HTTPS 升级的正文归属修正包含在 `0.2.13` 及之后版本。原 HTTPS 输入的历史整理拒绝仍未归因,issue #3 保持开放;后续成功调用不作为该问题关闭依据。DSH Store 的独立供应链审阅与准入仍由 Store 维护者决定。 本文件描述当前架构。各验证报告按其记录的版本和资产单独生效,后续版本不自动继承运行结论。 ## 项目定位 `dsh-web-search-fetch` 是运行在 DSH 进程中的 Cordis 插件。插件向 `ctx.web` 注册搜索和网页提取 provider,两者的 ID 均为 `aggregated`,分别供原生 `web_search` 和 `web_fetch` 工具使用。 插件直接调用 HTTP API,不启动外部服务,不读取 `ctx.web` 的私有注册表,也不注册额外的模型可见工具。 ## 模块职责 | 模块 | 职责 | | --- | --- | | `src/index.ts` | 配置 schema、依赖注入、凭证解析与 provider 注册 | | `src/search-pipeline.ts` | 搜索客户端组合、服务适配与重排接入 | | `src/search-aggregate.ts` | 并发搜索、额度检查、URL 去重、RRF 融合与截断 | | `src/search-quality.ts` | 官方文档意图、相关性约束与内容别名识别 | | `src/settlement-window.ts` | 多路结果充分后的宽限期与取消信号 | | `src/fetch-pipeline.ts` | HTTP 提取器组合与动态服务装配 | | `src/fetch-aggregate.ts` | URL 分流、动态提取、结果整理或兼容选择、错误选择 | | `src/fetch-document/` | HTML 转换、内容块对齐、去重、互补、来源和差异输出 | | `src/request-budget.ts` | 操作配置快照、服务筛选、请求计数、总限时和取消 | | `src/engines/` | 独立服务的客户端、请求构造与响应映射 | | `src/engines/additional/` | 13 个扩展服务的共享传输、适配与条件缓存 | | `src/breaker/` | 限流冷却、额度熔断与状态事件 | | `src/settings-surface.ts` | 设置字段、填写位与引用回落规则 | | `src/credential-ref.ts` | 按操作读取凭证引用名 | | `client/index.js` | Web 设置卡片与浏览器端保存逻辑 | 服务协议层不注册 provider。纯请求构造和响应映射与 HTTP 传输分离,使用本地测试服务验证协议。 ## 自动调用范围 默认 `controls.preset: auto`。插件在每次 provider 操作开始时读取请求文本:搜索请求使用查询语句,提取请求使用目标 URL。决策层先把请求分为 `fast`、`balanced` 或 `deep`,再统一进入服务筛选;用户不需要手动切换模式。 | 决策结果 | 派发上限 | | --- | --- | | `fast` | 三路 | | `balanced` | 五路 | | `deep` | 已选服务范围 | `search.decisionModel` 支持 `Kev-4b`、`SemIf`、`diffusiongemma`、阿里 `decision-model-preview` 和 TypeSafe `jev-latest`。前四个使用各自平台当前提供的免费调用,TypeSafe 为付费模型;费用以服务方为准。硅基流动三款使用 `SILICONFLOW_API_KEY`,阿里使用 `DASHSCOPE_API_KEY`,TypeSafe 使用 `TYPESAFE_API_KEY`。默认模型为 `decision-model-preview`:五题路由样例中三题符合预期、延迟约 94~217 毫秒。决策请求默认限时 2,000 毫秒并计入插件请求预算。 本机五题冒烟检查(样本过少,不能据此排名;普通搜索、文档提取、对比调研、新闻核验、静态 JSON)中:`decision-model-preview` 五题三中,单题 94~217 毫秒;TypeSafe `jev-latest` 走本机 DSH 代理后五题四中,414~760 毫秒;`Kev-4b` 三题命中,204~346 毫秒;`SemIf` 两题命中但普通请求偏深,155~358 毫秒;`diffusiongemma` 三题命中,332~739 毫秒。TypeSafe 直连返回 HTTP 451,代理链路可用。 自动预设将外部决策模型作为必要依赖。决策失败、超时或凭证缺失时,本次返回明确错误,不派发搜索或提取服务。自动档调整派发数量和结果充分后的等待时间,不自动开启服务、不提高预算、不改变 URL 分流和正文整理。模型、判定结果、置信度和实际选择的服务数量写入操作说明。 ## 搜索 1. 当日首次查询时,检查支持额度查询的服务,目前仅知乎提供此检查。 2. 跳过熔断或缺少凭证的服务,按预设、参与列表和数量上限派发,最多 16 路。 3. 按单个服务预算等待结果。失败或超时的服务不参与融合。 4. 归一化 URL,去除锚点及 `utm_*` 参数,合并重复来源。 5. 使用 RRF 融合排名,默认取前 20 条作为重排候选。 6. 可选调用硅基流动重排,失败时保留 RRF 顺序。 7. 按 `maxResults` 截断;重排池之外的来源保持 RRF 顺序。 RRF 得分为各服务中 `1 / (k + rank)` 的累加,排名从 1 开始,默认 `k = 60`。同一服务内的重复 URL 只计一次。合并时保留已有标题和日期,摘要采用较长值。同分来源按首次出现顺序排列。 URL 归一化默认保留非跟踪查询参数及非根路径尾斜杠,避免合并不同资源。例外为已核实的 MDN HTTP/HTTPS、旧语言与推广参数,以及 TypeScript Handbook 的 `.html` 别名。其他尾斜杠变体仅在标题与至少 80 字符的摘要一致时合并;同一服务内的重复结果仍只计一次票。不同语言和版本的文档不直接合并。空结果属于成功响应;全部服务失败或被跳过时返回 `WEB_PROVIDER_ERROR`。 对于明确的官方文档请求,识别 Python、TypeScript、Node.js、JavaScript/Promise、React、Rust 和 Go 的文档域名;显式 `site:` 优先。未指定 `site:` 时,最多追加两次限定域名检索,与基础查询并发并共用 HTTP 预算。同一服务的补充检索合并为一组排名,不增加服务票数。相关官方结果在重排前进入候选池、重排后保持优先;域名本身不代表内容相关或真实。 仅自动预设启用充分结果结束条件:搜索至少三家服务成功、有足够相关且去重的候选,官方意图还需相关官方结果;正文整理至少三家服务返回同标题、共享长段落且各有不少于 1,200 字符段落正文。`fast`/`balanced` 宽限 500 毫秒、派发后至少等待 1,200 毫秒;`deep` 宽限 1,500 毫秒、至少等待 2,500 毫秒。宽限结束取消未完成渠道,不计为服务故障,结果标记 `truncated` 并说明可能遗漏。未满足条件时继续等待单路或操作总限时。固定预设保持原等待方式。 ### Kimi 搜索档位 Kimi 默认使用 `POST /v1/tools/search_pro`;`engines.kimi.searchMode: basic` 改为 `POST /v1/tools/search` 并关闭整页正文。一次查询只请求一个档位,最多 20 条结果;摘要附加来源、等级和相关正文片段,合计最多 6,000 字符。提取使用 `/v1/tools/fetch`。 ### 智谱传输通道 `anthropic` 通道发送 Anthropic 兼容 Messages 请求,声明 `web_search_20250305` 服务端工具。客户端按 SSE 流读取,完整结果块到达后停止读取。非 SSE 响应、流解析失败或未完整结束的流可触发一次非流式请求;两次请求共用原始时间预算。正常结束且没有结果块时返回空结果。 `tools-api` 通道使用 `/paas/v4/web_search`,查询最多保留 70 个 Unicode 码点。该限制不适用于 `anthropic` 通道。两条通道统一映射为来源列表,传输选择按操作读取。 翻译层接受标准 Anthropic 结果块与智谱 `tool_result` 布局,包括 Python-repr 风格字符串。解析不执行字符串中的代码。智谱仅接入搜索,不包含阅读器。 ## 网页提取 ### URL 分流 | 条件 | 行为 | | --- | --- | | 已启用的 AIHOT、命中受支持的专用 API 路径 | 直接读取 Markdown / JSON API | | `staticRouting: false` | 其他 URL 全部交给 DSH HTTP 提取器 | | URL 无法解析或非 HTTP(S) | 交给 HTTP 提取器执行 URL 校验 | | 命中静态域名或其子域 | HTTP 提取 | | GitHub 仓库/blob URL,匹配已启用、已选中、凭证可用且未熔断的 GitHub 服务 | 动态提取;blob 的 `.md` 等扩展名不视为原始静态文件 | | 命中静态扩展名表 | HTTP 提取 | | 其他 URL | Firecrawl / AnySearch 与已启用、适用于该 URL 的其他提取服务并发 | 域名匹配按标签边界判断,例如 `example.com` 包含 `docs.example.com`,不包含 `otherexample.com`。分流只检查 URL,不发起 Content-Type 预探测。两种路径之间不自动回退。 GitHub 仓库根页通过 README API 读取正文。同一仓库的 README 文件地址归属请求的仓库根页;返回其他仓库地址时,正文整理仍拒绝将其作为目标页。 HTTP 路径复用 `@deepseek-ai/dsh-web-fetch-http` 的校验与限制:默认响应最多 5,000,000 字节、正文最多 100,000 字符、限时 30 秒、最多 5 次重定向。动态路径由第三方服务提取网页,不继承这些本地 HTTP 限制。 ### 正文整理 动态路径最多同时启动 10 个普通网页提取服务;适用的 GitHub URL 增加一路,最多 11 路,实际派发遵守参与列表和数量上限。默认单路预算 45 秒,操作总限时 50 秒。空正文和纯空白正文不参与整理。 默认 `controls.mode: organize`。HTML 转为 Markdown,移除明确的导航与页面控件;按标题路径、章节出现次数和共同内容块对齐。标题比较忽略页内定位符,普通段落比较忽略空白及加粗、行内代码格式,输出保留原文。结构较完整的同标题正文提供排列参照,其余服务补充内容。跨服务相同内容块去重;不同章节中至少 32 字符的相同块也可合并来源,同一原文内的重复内容保留。每块附带提取来源,数字或否定词变体保留在差异区,不裁定真伪。标题、语言、返回目标或段落顺序不一致时保留独立片段。登录和挑战页不加入主正文;仅有此类页面时返回错误。 默认输出上限 100,000 字符,按完整内容块省略并标注截断;不裁断代码围栏或表格。每份输入最多 1,000,000 字符、2,000 个内容块,超限报错。JSON 对象/数组及识别出的 XML 内容保持原样,超出输出预算时报错。AIHOT 专用读取和静态 HTTP 路径保持宿主原始格式。规则与限制见[正文整理](./docs/content-organization.md)。 明确版本标记不同的内容保留为独立片段。引用式链接与脚注按整份 Markdown 原文隔离,避免定义名称冲突;此路径仅合并完全相同的原文。来源说明记录接收时间;没有成功状态时,仅目标 URL 或其允许的 HTTPS 升级地址的非 2xx 正文可进入带预算和来源的整理输出。 正文整理允许同主机、同路径/查询、默认端口且不含用户信息的 HTTP → HTTPS 升级,并返回最终地址;有正常 HTTPS 正文时,原 HTTP 内容按不同目标隔离。其他地址变化及 HTTPS 降级仍拒绝。无可用正文时,错误附服务 ID 和不含 URL/凭证/正文的拒绝原因。 `controls.mode: select` 保留旧版单份正文评分。仅此模式使用 `fetch.engineWeights`:Firecrawl 默认 `3`、AnySearch 和扩展服务默认 `2`,同分按派发顺序选择。 目标网页的非 2xx 状态可随有效正文返回。全部服务失败时,优先保留 `WEB_BLOCKED_URL`、`WEB_INVALID_URL`、`WEB_FETCH_TOO_LARGE`、`WEB_UNSUPPORTED_CONTENT_TYPE`、`WEB_FETCH_TIMEOUT` 等具体错误。调用方取消使用 `WEB_ABORTED`。 ## 熔断与超时 搜索和提取各自持有一个熔断器,状态不跨 provider、进程或重启共享。 | 信号 | 行为 | | --- | --- | | `rate-limit` | 使用 `Retry-After` 或默认 60 秒冷却;重复信号采用退避,上限 1 小时 | | `quota-exhausted` | 暂停到宿主时区次日零点 | | `insufficient-credits` | 暂停到宿主时区次日零点 | 恢复在下一次状态读取时判断,无后台任务。服务方账期不一定与宿主时区一致;余额不足在次日恢复尝试后仍可能再次熔断。一般网络错误和认证错误不触发额度熔断。 编排在时间预算到期时丢弃对应结果,并通过取消信号通知客户端。可协作的客户端会停止网络请求;忽略信号的客户端可能继续执行,但迟到结果不会进入返回值。 操作总限时取消未完成的路,返回已完成结果并标记截断;无可用结果才返回超时错误。调用方取消仍优先返回 `WEB_ABORTED`。所有插件 API 请求拒绝重定向;静态 HTTP 路径沿用宿主策略。 ## 凭证与设置 AnySearch 官方 `/v1/search` 与 `/v1/extract`、Firecrawl 官方 `/v2/search` 与 `/v2/scrape`、Tavily 官方 `/search` 与 `/extract` 在缺少凭证时允许匿名额度请求,不发送 Authorization;Tavily 另发送 `x-tavily-access-mode: keyless`。自定义端点仍要求凭证。服务就绪过滤允许官方匿名路径参与派发。匿名额度属于额度型服务,不加入免费接口预设;已有密钥使用原账户,不自动切换匿名请求。AnySearch 匿名错误正文可能含自动生成凭证,不进入错误消息、日志或自动重试。 新安装 bundle 默认只开启上述三家远程服务,其他远程服务与重排均关闭,静态 HTTP 抓取保留。schema 未改变旧配置的兼容初始值,已保存的显式开关设置优先于安装默认;旧版未设置开关的服务采用新版默认。 配置只持有引用名,实际值由 `ctx.credentials.resolve()` 按操作解析。搜索引用以及提取的回落引用也按操作读取,避免设置保存后继续使用旧名称。 Web 设置域的命名空间为 Loader entry ID `web-search-fetch`。schema 的 `.volatile()` 标记决定可服务字段;保存将新值提交到运行中的引用,不重新挂载插件。端点和调优参数由配置文件管理。 浏览器卡片覆盖 18 项搜索/提取/重排服务和 18 个凭证位,另有五款决策模型选择,通过凭证服务读取状态、写入值,通过设置域保存服务总开关、扩展服务启用状态、智谱通道和 `controls` 字段。保存只提交发生变化的路径,保留未修改的端点与其他配置。空白草稿不写入凭证。浏览器模块独立于 Host 模块,两侧填写位及 README 服务清单由测试核对。AnySearch 和 Firecrawl 显式使用不同搜索/提取引用时,一次填写可更新两个引用;豆包两把凭证独立管理。外部请求与日志边界见[安全说明](./SECURITY.md)。 设置界面复用 DSH 原生组件和主题变量,由宿主窗口统一滚动。调用参数默认收起,服务配置位于首屏;预设和正文处理方式使用分段选择,高级数值使用 `SettingsValueField`。数值草稿保留用户输入,无效值阻止保存。离开设置页会清除未保存草稿;若保存已开始,保留已提交操作的凭证引用快照,操作结束后再次清理草稿。已被服务端接受的写入不回滚。 ### 扩展服务与缓存 `engines` 是按 ID 索引的扩展配置。条目存在且未设 `enabled: false` 时创建客户端。运行中配置变化时重建扩展客户端列表,不重复注册 provider。凭证值每次操作重新解析;Bing RSS 和 AIHOT 不调用凭证解析器。 共享 HTTP 传输拒绝重定向和含用户名、密码的 API 端点。文本响应在读取时限制为 1,000,000 字节;通用 JSON 请求尚未使用相同的读取上限。Bing RSS 拒绝 DOCTYPE,并先校验 XML;不解析搜索 HTML。 仅 AIHOT 使用条件 GET 缓存,按完整请求区分,普通接口默认 60 秒,重置接口默认至少 10 分钟、最多 32 项。缓存遵循 `max-age`、`no-cache` 和 `no-store`;过期可携带 ETag,304 复用已有内容,失败不返回过期数据。结果保留原文链接、来源和署名信息。专用读取优先于通用 URL 分流,直接返回 Markdown 或完整 JSON,不参与正文评分;过大响应报错,不截断结构化数据。读取只处理显式请求的一页,不自动翻页或同步。429 / 503 的 Retry-After 在实例内阻止继续请求其他 API 路径。 服务总开关保存在 `controls.serviceEnabled`,按操作读取。搜索及动态提取在派发前过滤关闭的服务;专用 API 读取、重排和静态 HTTP 路径同样检查总开关。关闭 HTTP 不触发其他提取服务。Firecrawl 的旧扩展开关仍只控制搜索,总开关默认保留其旧版可用提取行为。 ## 扩展与验证 新增服务在 `src/engines/` 实现客户端和纯协议映射,再由 pipeline 适配。搜索返回 DSH 来源列表,提取返回 `WebFetchResult`。限流和额度信号使用结构化分类,不根据消息猜测。 新增设置字段需同步 schema、投影、卡片和测试。新增模型工具会改变当前双工具接口,应作为独立接口变更讨论。 测试覆盖协议映射、客户端、SSE、聚合排序、提取选择、熔断、引用和卡片保存逻辑,使用本地模拟服务或固定夹具。本地测试不证明外部账户权限、当前响应质量或真实界面效果。开发命令见[贡献指南](./CONTRIBUTING.md)。