给自己的 Web AI 接入联网检索:Tavily + 后端上下文注入 + 前端来源展示
很多网站现在都会接一个 AI 助手:用户输入问题,后端转发给大模型,前端流式展示回答。
这个流程本身不复杂,但用一段时间后很快会遇到一个问题:
AI 对“最新信息”不敏感。
比如问它某个库最新版本怎么用、某个产品最近更新了什么、某个 API 文档是否变了,模型往往只能根据训练数据回答。哪怕回答看起来很流畅,也可能已经过时。
所以我最近给自己的工具站 FuShengTool 的 AI 助手加了一版“联网检索”能力。整体目标不是做一个复杂的搜索引擎,而是先完成一个可靠的 MVP:
- 用户在前端打开“联网检索”开关;
- 后端用搜索 API 查询相关资料;
- 把搜索结果整理成上下文,注入给大模型;
- 模型基于检索结果回答;
- 前端展示本次回答参考了哪些来源。
这篇文章记录一下整体设计思路。
1. 为什么不让前端直接调搜索 API?
最直接的想法是:前端调用 Tavily、Brave Search、Bing Search 之类的 API,然后把结果发给模型。
但这在真实产品里不合适。
原因主要有三个:
1.1 API Key 不能暴露到浏览器
搜索 API 通常需要 Key。只要 Key 放到前端,就可能被用户在 DevTools 里看到,后续被滥用、刷额度、甚至带来账单风险。
所以第一条原则是:
搜索 API Key 只放后端。
前端只告诉后端:这次用户是否开启联网检索。
1.2 后端更适合做安全控制
联网检索不只是“搜一下”这么简单。后面如果要支持读取网页正文,就会涉及 SSRF 风险。
比如用户传入:
http://localhost:xxxxhttp://127.0.0.1- 内网 IP
- 云厂商 metadata 地址
这些都不能让后端随便请求。
所以 URL 校验、私网地址拦截、超时、最大结果数、最大正文长度,都应该放在后端。
1.3 后端更方便做缓存和限流
搜索 API 是有成本的。一个用户每问一句都联网查,成本很快就会上去。
后端可以做:
- 每个用户每小时最大联网次数;
- 同一个 query 短时间缓存;
- 默认只返回 3-5 条结果;
- 深度检索单独作为高级模式。
这些都不适合完全交给前端控制。
2. 第一版为什么选 Tavily?
搜索 API 有很多选择,比如:
- Tavily
- Brave Search API
- Bing Web Search API
- Serper / SerpAPI
- 自建 SearXNG
我第一版倾向 Tavily,主要是因为它是偏 AI 场景设计的搜索 API,返回内容更适合喂给大模型。
传统搜索 API 通常更像搜索引擎结果页:标题、链接、摘要。它当然也能用,但如果想进一步读网页正文,就要自己再做抓取、清洗、截断、排序。
Tavily 的优势是接入成本比较低,Python SDK 也比较直接:
fromtavilyimportTavilyClient client=TavilyClient(api_key="...")response=client.search(query="某个问题",search_depth="basic",max_results=5,)当然,这不代表只能用 Tavily。工程上最好做一层抽象,比如:
search_web(query) -> SearchBundle这样未来要换 Brave、Bing 或自建代理,不需要重写聊天主流程。
3. 后端整体流程
后端聊天接口原本大概是:
用户消息 -> 后端 -> 大模型 API -> SSE 流式返回 -> 前端展示加上联网检索后,流程变成:
用户消息 -> 判断是否开启联网检索 -> 调用搜索 API -> 归一化搜索结果 -> 构造“联网检索上下文” -> 插入到 messages 前面 -> 请求大模型 -> 同时把来源通过 SSE 发给前端重点是:不要把搜索结果直接混在用户消息里,而是作为一段明确的系统上下文,例如:
以下是本次联网检索结果。回答时请优先依据这些来源; 如果来源不足,请明确说明不确定,不要编造。然后列出:
[1] 标题 URL: ... 摘要: ...这样模型更容易知道哪些内容是检索结果,哪些内容是用户原始问题。
4. 为什么要把来源返回给前端?
联网检索最怕的问题是:
用户不知道 AI 到底参考了什么。
如果 AI 回答里只是说“根据最新资料”,但不给来源,那可信度会很弱。
所以前端需要展示来源卡片,至少包含:
- 标题
- URL
- 站点名
- 摘要
- 发布时间(如果有)
这样用户可以点开核对,也能看出回答依据来自哪里。
在流式聊天里,可以通过自定义 SSE 事件把来源先发给前端,比如:
event: portal_web_search data: {"query":"...","sources":[...]}前端收到后,把来源挂到当前 assistant 消息上。这样回答还在流式生成时,用户就能看到“已联网检索”。
5. 前端交互怎么做?
第一版不建议把所有参数暴露给用户,比如search_depth、max_results、include_raw_content等。
普通用户不关心这些。
前端只需要一个简单开关:
[联网检索]开启后,发送请求时带上:
{"webSearchEnabled":true}后端自己决定用什么搜索深度、返回几条、是否走缓存。
后续如果要增强,可以加三档:
- 快速检索:结果少,速度快,成本低;
- 标准检索:默认模式;
- 深入检索:结果更多,可能更慢、更贵。
但第一版一个开关就足够。
6. 安全和成本控制
联网检索能力上线前,至少要考虑这些限制:
6.1 API Key 只放后端
环境变量示例:
TAVILY_API_KEY=你的 Key不要传给前端,不要存到浏览器。
6.2 限制最大结果数
第一版建议:
max_results = 5 search_depth = basic不要默认 advanced,否则成本会更高。
6.3 超时控制
搜索和网页读取都要有超时。
比如:
- 搜索超时:5-10 秒;
- 单网页读取超时:5-8 秒;
- 总检索流程超时:10-15 秒。
用户体验上,联网检索慢一点可以接受,但不能无限卡住。
6.4 SSRF 防护
如果后续支持读取用户传入 URL 或搜索结果正文,需要禁止访问:
- localhost
- 127.0.0.1
- 0.0.0.0
- 内网 IP
- link-local 地址
- 云 metadata 地址
- 带用户名密码的 URL
这部分一定要在服务端做。
7. 第一版可以先不做什么?
为了尽快上线 MVP,有些功能可以先不做:
- 不做全网爬虫;
- 不做 Crawl 整站抓取;
- 不做复杂 Research 报告;
- 不开放所有 Tavily 参数;
- 不让用户自定义搜索 API Key;
- 不让前端直接访问搜索服务商。
先把核心链路跑通:
Search -> Context -> Answer -> Sources这就已经能显著提升 AI 助手处理最新问题的能力。
8. 总结
给网站 AI 加联网检索,不只是“调一个搜索接口”。真正要考虑的是一条完整产品链路:
- 前端怎么让用户控制是否联网;
- 后端怎么安全地调用搜索 API;
- 搜索结果怎么整理成模型能理解的上下文;
- 来源怎么展示给用户;
- 成本、限流、安全怎么兜住。
我的建议是:第一版先做轻量、可控、可替换。
用 Tavily 这类 AI 搜索 API 快速做 MVP,同时把搜索能力封装成后端模块。等需求稳定后,再考虑引入网页正文提取、Research 模式、MCP 工具化或多搜索源 fallback。
对于很多自建工具站来说,这已经是从“普通 AI 聊天”走向“能查资料的 AI 助手”的关键一步。
体验地址: https://web.fushengtool.com/chat
