Files
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
12 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "biggress/deepseekx", "files": {"docs/superpowers/specs/2026-05-23-search-orchestrator-design.md": {"e4978801c3566cde912cbba8b0dd249f3b996e20": {"t": 1786068572.6417894, "n": 7297, "h": "5740e03cc4143244", "c": "# DeepSeekX 联网搜索闭环设计\n\n> 日期:2026-05-23\n> 状态:Draft\n> 主题:方案 B - 两阶段调用 + 应用侧搜索编排\n\n---\n\n## 1. 背景\n\n当前 DeepSeekX 在 GUI 主链路中通过 `send_message_via_api` 调用 DeepSeek Chat Completions API,并依赖:\n\n- system prompt 中的“你拥有联网搜索能力”提示\n- 请求体中的 `web_search: {\"enable\": true}`\n\n来让模型自动完成实时信息检索。\n\n该方案存在明显不稳定性:\n\n- 模型有时直接返回最终答案\n- 模型有时直接回答自己无法联网\n- 模型有时只输出类似 `<search>SOXL stock price today</search>` 的中间协议文本,然后流程结束\n\n根因是:当前应用没有把“搜索”实现为本地可控的工具闭环,而只是依赖模型和上游 API 自行决定是否完成搜索。\n\n---\n\n## 2. 目标\n\n本方案的目标是将“联网搜索”改为应用侧可控流程,确保实时问题能够稳定走完:\n\n1. 判断是否需要搜索\n2. 生成搜索词\n3. 执行搜索\n4. 将搜索结果回填给模型\n5. 生成最终答案\n\n同时满足以下要求:\n\n- 默认对所有用户零配置可用\n- 提供一个内置免费搜索源\n- 在免费源失效时自动回退到第二层免费方案\n- 支持第三方搜索提供商,供用户在设置中切换\n- 不再将 `<search>` 等中间协议暴露为最终回答\n\n---\n\n## 3. 非目标\n\n本方案第一版不解决以下问题:\n\n- 多轮自动搜索和复杂 agent 循环\n- 搜索结果可信度评分系统\n- 网页正文深度抓取与全文阅读\n- 第三方 provider 的全量接入\n- 搜索结果来源卡片、富文本来源引用等高级 UI\n\n第一版只保证:一次判定、一次搜索、一次综合、一次收敛。\n\n---\n\n## 4. 方案概述\n\n采用两阶段模型调用和应用侧搜索编排:\n\n### 阶段一:Planner\n\n模型只负责判断:\n\n- 是否需要联网搜索\n- 如果需要,应该搜索什么关键词\n\nPlanner 只输出结构化结果,不直接产出最终答案。\n\n### 阶段二:Search Orchestrator\n\n应用收到搜索请求后自行执行搜索,优先走默认免费源:\n\n1. `PublicSearchProvider`\n2. `WebScrapeProvider` 兜底\n\n### 阶段三:Synthesizer\n\n应用将裁剪后的搜索结果回填给模型,由模型生成最终中文答复。\n\n---\n\n## 5. 高层架构\n\n```text\nUser Question\n |\n v\nsend_message_via_api\n |\n +--> Planner Completion\n | |\n | +--> action = answer ------> Final Response\n | |\n | +--> action = search\n | |\n | v\n | Search Router\n | |\n | +----------+-----------+\n | | |\n | v v\n | PublicSearchProvider WebScrapeProvider\n | | |\n | +----------+-----------+\n | |\n | v\n +------------> Synthesis Completion\n |\n v\n Final Response\n |\n v\n Session Persistence\n```\n\n---\n\n## 6. 核心数据流\n\n### 6.1 用户输入\n\n示例:\n\n```text\n帮我查询下 SOXL 的今天的价格\n```\n\n### 6.2 Planner 输出\n\nPlanner 强制输出 JSON,仅允许两种形态。\n\n直接回答:\n\n```json\n{\"action\":\"answer\",\"content\":\"...\"}\n```\n\n请求搜索:\n\n```json\n{\"action\":\"search\",\"query\":\"SOXL stock price today\",\"reason\":\"需要实时价格\"}\n```\n\n### 6.3 搜索执行\n\n应用执行搜索并得到统一结构:\n\n```json\n{\n \"provider\": \"builtin_free\",\n \"query\": \"SOXL stock price today\",\n \"results\": [\n {\n \"title\": \"...\",\n \"url\": \"...\",\n \"snippet\": \"...\",\n \"source\": \"...\",\n \"score\": 0.92\n }\n ],\n \"status\": \"success\"\n}\n```\n\n### 6.4 综合回答\n\n应用将问题与搜索结果摘要一起发给 Synthesizer:\n\n```text\n[User Question]\n帮我查询下 SOXL 的今天的价格\n\n[Search Results]\n1. Title: ...\n URL: ...\n Snippet: ...\n2. Title: ...\n URL: ...\n Snippet: ...\n```\n\nSynthesizer 必须输出最终可读中文答复,禁止再次输出 `<search>`。\n\n---\n\n## 7. 搜索提供商设计\n\n### 7.1 默认提供商:Built-in Free\n\n默认 provider 为 `builtin_free`,对所有用户零配置可用。\n\n它不是单一搜索实现,而是一个带回退策略的 provider:\n\n1. 优先:`PublicSearchProvider`\n2. 回退:`WebScrapeProvider`\n\n### 7.2 PublicSearchProvider\n\n职责:\n\n- 请求预置公共 `searxng` 风格实例\n- 获取通用网页搜索结果\n- 统一转成 `SearchResult[]`\n\n要求:\n\n- 短超时,避免界面长时间等待\n- 支持多个公共实例轮询或备用列表\n- 对失败、空结果、低质量结果给出明确状态\n\n### 7.3 WebScrapeProvider\n\n职责:\n\n- 当公共实例失败时抓取公开搜索结果页\n- 提取标题、链接、摘要\n- 作为兜底免费能力\n\n限制:\n\n- 解析逻辑脆弱\n- 不保证长期稳定\n- 第一版只做轻量结果抽取,不做复杂反爬对抗\n\n### 7.4 第三方 Provider\n\n支持用户选择第三方提供商,例如:\n\n- Tavily\n- Exa\n- SerpAPI\n- Firecrawl\n- Perplexity\n\n这些 provider 通过统一接口适配,在未配置 API Key 时不可激活,并自动回退为 `builtin_free`。\n\n---\n\n## 8. 配置设计\n\n### 8.1 应用级配置\n\n联网搜索默认作为应用级能力配置,放在现有设置体系中,而不是项目表中。\n\n建议新增字段:\n\n- `search_enabled`\n- `search_provider`\n- `search_timeout_ms`\n- `search_max_results`\n- `search_api_keys`\n\n其中:\n\n- `search_provider` 默认值为 `builtin_free`\n- `search_timeout_ms` 建议默认 8000-12000ms\n- `search_max_results` 建议默认 3-5\n\n### 8.2 项目级配置\n\n项目只保留轻量控制项:\n\n- `search_mode = inherit | disabled | force_enabled`\n\n含义:\n\n- `inherit`:使用全局搜索设置\n- `disabled`:该项目禁用联网搜索\n- `force_enabled`:即使全局关闭,该项目仍启用联网搜索\n\n---\n\n## 9. 会话与持久化设计\n\n### 9.1 用户可见消息\n\n最终 assistant 消息只保存最终答复文本。\n\n禁止将以下内容写为最终消息:\n\n- `<search>...</search>`\n- planner 原始 JSON\n- 原始网页抓取文本\n\n### 9.2 中间过程记录\n\n搜索步骤记录为 turn 内工具调用摘要,映射到现有 Turn 模型:\n\n- `tool_name = \"web_search\"`\n- `tool_input = query`\n- `tool_result.summary = provider + 结果数 + 错误摘要`\n\n这样可以复用现有 Turn UI 进行展示。\n\n### 9.3 调试信息\n\n默认不把完整搜索结果写入 session 主消息,避免 session 文件膨胀。\n\n如需调试,可保留轻量摘要:\n\n- 搜索 provider\n- 查询词\n- 结果数量\n- 失败原因\n\n---\n\n## 10. 提示词设计\n\n### 10.1 Planner Prompt\n\nPlanner 只做搜索决策,不做最终回答。\n\n要求:\n\n- 强制 JSON 输出\n- 不允许输出 `<search>` 标签\n- 不允许输出自然语言前奏\n- 只允许 `answer` 或 `search`\n\n### 10.2 Synthesizer Prompt\n\nSynthesizer 根据搜索结果生成最终用户可见回答。\n\n要求:\n\n- 使用简体中文\n- 直接回答用户问题\n- 如果结果中有时效信息,优先标注“截至某时点”\n- 不允许再次发起搜索\n- 不允许输出 `<search>` 或中间协议\n\n---\n\n## 11. 失败状态设计\n\n系统必须在单次请求中收敛,不允许悬挂或无限等待。\n\n定义以下失败状态:\n\n- `planner_failed`\n- `search_timeout`\n- `search_no_results`\n- `search_provider_unavailable`\n- `synthesis_failed`\n\n用户可见失败文案原则:\n\n- 简洁\n- 可理解\n- 可重试\n- 不暴露内部协议\n\n示例:\n\n- `这次联网搜索超时了,请稍后重试。`\n- `没有找到足够可靠的实时结果。`\n- `当前搜索服务暂时不可用,已回退为普通回答。`\n\n---\n\n## 12. 模块拆分\n\n建议按以下方式拆分:\n\n### Rust 后端\n\n- `src-tauri/src/search/mod.rs`\n - 搜索模块入口\n - 定义 trait 和通用类型\n- `src-tauri/src/search/router.rs`\n - provider 选择与回退逻辑\n- `src-tauri/src/search/settings.rs`\n - 搜索配置读取与默认值\n- `src-tauri/src/search/providers/builtin_free.rs`\n - 免费双回退 provider\n- `src-tauri/src/search/providers/public_instance.rs`\n - 公共实例 provider\n- `src-tauri/src/search/providers/web_scrape.rs`\n - 网页抓取 provider\n- `src-tauri/src/search/providers/third_party.rs`\n - 第三方 provider 适配\n- `src-tauri/src/api.rs`\n - 增加 `planner_completion()` 和 `synthesis_completion()`\n- `src-tauri/src/lib.rs`\n - 将 `send_message_via_api()` 改造为 orchestrator\n- `src-tauri/src/session.rs`\n - 搜索步骤写入 turn 结构\n\n### 前端\n\n- `src/components/SettingsDialog.tsx`\n - 新增搜索 provider 设置项\n- `src/components/ProjectSettings.tsx`\n - 新增 `search_mode`\n- `src/api.ts`\n - 新增搜索设置读写接口\n- `src/types.ts`\n - 新增 provider、settings、mode 类型\n\n---\n\n## 13. 实施顺序\n\n### Phase 1:后端最小闭环\n\n- 增加 `SearchPlan`、`SearchResult`、`SearchProvider`\n- 实现 `planner_completion()`\n- 实现 `synthesis_completion()`\n- 在 `send_message_via_api()` 中串起两阶段调用\n\n### Phase 2:默认免费搜索源\n\n- 实现 `PublicSearchProvider`\n- 实现 `WebScrapeProvider`\n- 封装 `builtin_free`\n\n### Phase 3:设置系统\n\n- 应用级搜索设置\n- 项目级 `search_mode`\n- 默认值与配置回退规则\n\n### Phase 4:会话和 UI\n\n- 搜索步骤写入 session\n- Turn 视图展示 `web_search`\n- 设置页增加 provider 选择\n\n### Phase 5:第三方 Provider\n\n- 先接入 Tavily\n- 再按统一接口逐步补其他 provider\n\n---\n\n## 14. 测试策略\n\n### 14.1 单元测试\n\n- Planner JSON 解析\n- `<search>关键词</search>` 兼容解析\n- provider 回退逻辑\n- 搜索结果裁剪和摘要格式\n\n### 14.2 集成测试\n\n- Planner 返回 `answer`\n- Planner 返回 `search`\n- 公共实例失败时自动走抓取兜底\n- Synthesizer 失败时返回确定性错误消息\n\n### 14.3 回归测试\n\n- `帮我查询下 SOXL 的今天的价格`\n- `今天上海天气怎么样`\n- `OpenAI 今天有什么新闻`\n- `帮我解释 Go 的 interface`\n\n回归验证重点:\n\n- 实时问题能触发搜索\n- 非实时问题不误触发搜索\n- 最终消息不再出现 `<search>`\n- session 中存在 `web_search` 工具调用摘要\n\n---\n\n## 15. 风险与取舍\n\n### 风险\n\n- 公共搜索实例稳定性不可控\n- 网页抓取受目标页面结构变化影响\n- 两阶段调用增加时延和 token 成本\n- 第三方 provider 接入后测试矩阵增大\n\n### 取舍\n\n- 选择两阶段调用,是以额外成本换确定性\n- 选择免费双回退,是以更高实现复杂度换零配置体验\n- 第三方 provider 延后接入,是为了优先修复现有“卡死在 `<search>`”的问题\n\n---\n\n## 16. 验收标准\n\n- 输入“帮我查询下 SOXL 的今天的价格”时,不再出现 `<search>...</search>` 作为最终回复\n- 默认安装后无需额外配置即可触发联网搜索\n- 公共实例失败时自动走抓取兜底\n- 第三方 provider 配置后可替换默认 provider\n- Turn 视图可显示一次 `web_search` 调用及摘要\n- 普通问题不强制进入两阶段调用\n- 任一失败场景都能在一次请求内返回确定性结果\n\n---\n\n## 17. 结论\n\n本方案通过“Planner -> Search Router -> Synthesizer”的应用侧闭环,将联网搜索从不稳定的提示词能力,升级为 DeepSeekX 可控、可回退、可扩展的核心能力。\n\n该方案优先解决当前用户最明显的问题:\n\n- 不再卡在 `<search>...</search>`\n- 实时问题具备更高成功率\n- 默认对所有用户零配置可用\n\n在此基础上,再逐步开放第三方搜索 provider,实现“默认免费可用 + 高级用户可扩展”的整体体验。\n"}}}}