很多网站现在都会接一个 AI 助手:用户输入问题,后端转发给大模型,前端流式展示回答。

这个流程本身不复杂,但用一段时间后很快会遇到一个问题:

AI 对“最新信息”不敏感。

比如问它某个库最新版本怎么用、某个产品最近更新了什么、某个 API 文档是否变了,模型往往只能根据训练数据回答。哪怕回答看起来很流畅,也可能已经过时。

所以我最近给自己的工具站 FuShengTool 的 AI 助手加了一版“联网检索”能力。整体目标不是做一个复杂的搜索引擎,而是先完成一个可靠的 MVP:

  1. 用户在前端打开“联网检索”开关;
  2. 后端用搜索 API 查询相关资料;
  3. 把搜索结果整理成上下文,注入给大模型;
  4. 模型基于检索结果回答;
  5. 前端展示本次回答参考了哪些来源。

这篇文章记录一下整体设计思路。

1. 为什么不让前端直接调搜索 API?

最直接的想法是:前端调用 Tavily、Brave Search、Bing Search 之类的 API,然后把结果发给模型。

但这在真实产品里不合适。

原因主要有三个:

1.1 API Key 不能暴露到浏览器

搜索 API 通常需要 Key。只要 Key 放到前端,就可能被用户在 DevTools 里看到,后续被滥用、刷额度、甚至带来账单风险。

所以第一条原则是:

搜索 API Key 只放后端。

前端只告诉后端:这次用户是否开启联网检索。

1.2 后端更适合做安全控制

联网检索不只是“搜一下”这么简单。后面如果要支持读取网页正文,就会涉及 SSRF 风险。

比如用户传入:

  • http://localhost:xxxx
  • http://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 也比较直接:

from tavily import TavilyClient

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_depthmax_resultsinclude_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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐