给 weclaw 接上 DeepSeek 联网搜索——一次从"听说"到上线的实战

给 weclaw 接上 DeepSeek 联网搜索——一次从"听说"到上线的实战

起因

"听说 DeepSeek 的 response 接口支持搜索,帮我验证一下。"

就这么一句话。weclaw(我们那个把微信、OpenAI 兼容 API 桥接到各家大模型的 agent 中台)一直在用 DeepSeek 的 chat/completions,但从没碰过所谓"response 接口"。于是先验证,再决定接不接。

第一步:它真的能搜,而且搜得很实

官方文档确认 DeepSeek 有 Responses API(POST /v1/responses,OpenAI 风格),deepseek-v4-flash 支持,内置 web_search 工具,服务端执行。

光看文档不算数,拿现有的 key 直接打一发:

POST https://api.deepseek.com/v1/responses  
{  
  "model": "deepseek-v4-flash",  
  "input": "OpenAI 2026 年最近发布的模型是哪个?给来源。",  
  "tools": [{"type": "web_search"}],  
  "stream": false  
}  

返回的 output[] 里清清楚楚:reasoning(思考)→ web_search_call(action.type=search,真发了查询词)→ web_search_call(action.type=open_page,真抓了 openai.comhelp.openai.com 等页面)→ message(phase=final_answer,带可核实的来源链接)。不是幻觉,是真去网上抓了页子的。

顺便摸清几个关键约束:

  • chat/completions 会拒绝 web_search 工具——报错 unknown variant 'web_search', expected 'function'。搜索是 Responses 独占。
  • 搜索结果是黑盒:标题、摘要、正文一律不返回,服务端直接塞进 LLM 上下文。annotations 恒为空;URL 只在 open_page 动作里出现,还没标题。
  • 只在 deepseek-v4-flash + api.deepseek.com 上能用(v4-pro 官方说 8 月初上,暂未)。

结论:能搜,值得接。

第二步:一个 helper,喂两条路径

weclaw 里 DeepSeek 有两条互相独立的调用路径:

  • admin/API 反代(api/server.go):网页后台聊天,非流式,裸字节透传上游。
  • 微信(agent/http_agent.go):非流式,固定 {model, messages}

两条路径都要能搜索,又不能各写一遍。于是抽一个共享 helper:

func CallResponsesSearch(ctx, endpoint, apiKey, headers, model, msgs, systemPrompt)  
    (answer string, sources []string, err error)  

它干三件事:把 endpoint 从 /chat/completions 切到 /responses、注入 web_search 工具、解析 output[] 取最终答案 + 来源 URL(去重、剥掉 DeepSeek 给 URL 追加的 #ws_call_id=... 追踪尾巴)。

触发方式选了最省事的 agent 级配置:AgentConfig 加一个 web_search bool,某个 agent 标成搜索 agent,就永远走 Responses。新增一个独立的 deepseek-search(别名 dss),不动原来日常用的 deepseek——避免把每次对话都降级到 v4-flash。

来源怎么展示?白嫖现成的渲染器

搜索结果只有 URL、没标题。本来打算写新前端,翻代码发现 renderMarkdown 早就自带"📎 参考链接"渲染:文本末尾 --- 后跟 N. [标题](url) 列表,它自动渲染成带圆圈编号的可点链接框。

于是 helper 只要把来源拼成这种脚注块追加到答案末尾:

答案是……  
  
---  
  
1. [](https://help.openai.com/...)  
2. [](https://www.theverge.com/...)  

前端零改动,自动出"📎 参考链接"框。这是这次最爽的一处复用。

admin 非流式?打包成 chat.completion

admin 后台是 await resp.json()choices[0].message.content,完全不走流式。那就在后端把 Responses 的结果重新打包成标准 chat.completion JSON 吐回去——前端解析逻辑一行不用改。搜索期间把按钮文案从 "Thinking" 换成 "🔍 搜索中…" 就行。

第三步:上线,和一个隐蔽的坑

部署后先测 admin 路径:问"OpenAI 2026 最近发布的模型"——20 多秒后返回标准 chat.completion,答案是真实的 GPT-5.6 / 2026-07-09,末尾带来源脚注。✅

然后微信发 dss 今天的新闻——没搜索。模型一本正经地回:"我知识截止到 2025 年,建议你打开联网搜索功能。"

翻日志,两个细节露了马脚:

  1. 创建 agent 的日志是 [agent] created HTTP agent,不是 [handler] created——说明走的是另一条建 agent 的代码路径
  2. 回复只花了 5.2 秒——真搜索一次要 20~30 秒,5 秒说明根本没搜,走的是普通 chat。

根因:HTTPAgent两个创建点——cmd/start.gocreateAgentByName(启动建默认 agent + 微信按需启动都用它)和 messaging/handler.go 的 reload 工厂(配置热重载时才装)。我第一次只改了后者,而微信 /dss 走的是前者,所以那个实例 webSearch=false,自然不搜。

补上 cmd/start.go 那处,重新部署,再测——成功。微信问"今天几号",老老实实去查了日历网站,回 "2026-08-05"(嗯,差一天,那是搜索内容本身的事,不是代码 bug),末尾带来源链接。

复盘:几个值得记的点

  • "听说 X 支持 Y"先验证再信。 这次验证直接推翻了"加个参数就行"的直觉——chat 接口根本不收 web_search,必须换 API 风格、改请求体结构。
  • 黑盒能力要摸清边界。 搜索结果不返回正文,意味着想做 Perplexity 式引用块还得外接搜索 API;但只做"答案 + 来源链接",现成就够。清楚边界才知道哪些能省、哪些省不了。
  • 多个工厂/创建点是最容易漏的坑。 加字段时 grep 一遍所有构造调用,比"我改了那处"靠谱得多。这次 [agent] vs [handler] 的日志前缀、和"5 秒 vs 30 秒"的时间差,是定位的两个关键线索。
  • 复用现成渲染器省巨多事。 renderMarkdown 那套脚注解析本来是给别的 agent 准备的,这次白嫖,来源展示零前端代码。

结果

weclaw 现在多了一个 deepseek-search agent:

  • 网页后台:选中它,问时间敏感的问题,答案末尾自动出"📎 参考链接"。
  • 微信:发 dss 你的问题,机器人真的去搜网,回复带来源。

一次搜索大约 2 分钱(flash 定价),token 比普通 chat 重一个量级,所以做成按需的 agent 而不是默认开。

从"听说支持搜索"到两条路径都能搜,一天搞定。最有成就感的不是代码量(其实没多少),而是那个"5 秒没搜"的坑被日志里的细节精准钓出来——这是调试手感最好的那种 bug。


weclaw · DeepSeek Responses API · 2026-08-06