从 Brave Search 到 Tavily:一次 Open WebUI Web Search 稳定性取舍

内网部署了自有推理服务器和Open WebUI 前端界面。最近小伙伴在使用 Open WebUI 的 Web Search 功能时,遇到了一个很典型、但一开始并不直观的问题:
搜索经常失败,错误来自搜索服务端,而不是模型本身。

这篇文章简单记录一下:我遇到了什么问题、尝试过哪些解决手段、如何做技术评估,以及最终为什么换成了 Tavily。


问题是什么

最初使用的是 Brave Search API 作为 Open WebUI 的 Web Search 后端。

在日常使用中,多人使用的时候频繁遇到:

  • 搜索偶发失败
  • 返回 too many connections / rate limited
  • 同样的问题在“低负载”下也会出现

直觉上很奇怪:
我并没有显式做高并发搜索。(但我确实需要,且希望并发进行搜索,这样出来结果快一些)


真正的原因

后来发现问题尽管单个用户的并发进行了控制,但多个用户使用的时候却没有有效控制并发,所以问题并不在“用户并发”,而在 Open WebUI + LLM + Tool 的调用模型

  • 一次用户提问,可能触发 多次搜索
  • 多个用户同时请求
  • 推理链、重试、搜索改写都会放大请求数
  • Brave Search 本身对并发和连接数限制非常严格

结果就是:
LLM 无意中变成了并发制造机,而 Brave Search 并不适合这个调用模式。


我尝试过什么

在不修改 Open WebUI 关于单词搜索的并发设置的情况下,主要尝试了三类手段:

  1. 限制搜索并发
  • 在 Brave Search API 前加反向代理
  • 只对搜索请求做并发闸门
  • 可以缓解问题,但工程复杂度上升
  1. 降低 WebUI 搜索触发频率
  • 调低 tool 使用倾向
  • 减少一次回答里的搜索次数
  • 本质是止痛药,不是根治
  1. 评估不同搜索后端的“AI 友好度”
  • Brave
  • SerpAPI
  • Bing
  • Tavily

简单评估结论

Open WebUI 的使用场景 出发,而不是单纯 API 能力:

  • Brave Search
  • 成本低
  • 但并发和连接限制过紧
  • 不加代理就不稳定
  • SerpAPI
  • 规则清晰、工程友好
  • 成本高
  • 更像“传统搜索 API”
  • Bing Search API
  • 产品生命周期存在不确定性
  • 不值得继续投入
  • Tavily
  • 明确为 AI / RAG / Agent 场景设计
  • 对 LLM 扇出式调用更友好
  • 行为可预期,整体最省心

最终选择

我最终换成了 Tavily。

原因很简单:

我希望尽可能快一点,支持合理的并发,但又不想为一个“搜索工具”
去额外维护限流代理、并发闸门和异常兜底。而 Tavily 的并发支持比 Brave 更慷慨,且总体更面向 AI Agents。

在 Open WebUI 这种 “模型主导、工具被动调用” 的架构下,
搜索后端是否理解 AI 的调用模式,比价格和传统性能指标更重要。


一点总结

这次经历最大的收获不是“换了哪个搜索 API”,而是一个更普遍的判断:

不是所有 Web Search API 都适合直接挂在 LLM 后面用。

如果一个搜索服务是为“人类 + 页面浏览”设计的,
那在 LLM 场景下,你迟早会为并发、限流和不可预期的失败买单。

Tavily 至少在这个阶段,帮我省掉了这些精力。

OpenWebUI 訪問後端 LLM 報錯:Unexpected token ‘d’, “data: {“id”… is not valid JSON

這幾天在部署企業內部 AI, Open WebUI 用 Docker 部署在一台 Web Server 上,因為上面有多個服務,所以用了 nginx 反向代理,真正運行的端口在 3000,反向代理到 HTTPS。當我用 Open Webui 訪問 Inference Server上部署的後端 LLM 的時候,不管後端用什麼引擎,Open Webui都報錯:

Unexpected token 'd', "data: {"id"... is not valid JSON

一開始以為是後端 LLM 模型的設置問題,後來又以為是 Open WebUI 的設置問題。

折騰了挺久才知道其實是 web socket 設置的問題。


這個錯誤訊息的關鍵是:回來的串流內容長得像 SSE(每行前面有 data: …),但 Open WebUI 這邊把它當成「一次性 JSON」去 JSON.parse(),所以第一個字符是 d 就直接報錯了。

最後解決方法分兩部分,涉及 nginx 設置,也涉及 Open WebUI 的 docker compose 設置。

Docker Compose 文件裡面,需要確保如下環境變量:

environment:
  - WEBUI_URL=https://chat.example.com
  - CORS_ALLOW_ORIGIN=https://chat.example.com
  - WEBUI_SESSION_COOKIE_SECURE=true
  - WEBUI_COOKIE_SECURE=True

    Nginx 的設置裡面:

    upstream open-webui {
        keepalive 32;
        server localhost:3000;
    }
    server {
        server_name   chat.augwit.com;
    
        access_log /var/log/nginx/chat.example.com/access.log;
        error_log /var/log/nginx/chat.example.com/error.log;
    
    
        location /ws/ {
          proxy_pass http://open-webui;
          proxy_http_version 1.1;
    
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection $connection_upgrade;
    
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-Proto $scheme;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Real-IP $remote_addr;
    
          proxy_buffering off;
          proxy_read_timeout 3600;
          proxy_send_timeout 3600;
        }
    
        location / {
            proxy_pass   http://open-webui;
    
            proxy_http_version 1.1;
    
            # WebSocket 必需
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
    
            proxy_set_header Host $host;
            proxy_set_header X-NginX-Proxy true;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Forwarded-Host $host;
            proxy_set_header X-Forwarded-Port $server_port;
            proxy_set_header X-Forwarded-Uri $request_uri;
    
            # 串流 / SSE 常需要關掉 buffering,不然容易卡或解析怪
            proxy_buffering off;
    
            # 依需求調大超時(串流聊天很容易超過預設)
            proxy_read_timeout 3600;
            proxy_send_timeout 3600;
    
            client_max_body_size  1024m;
        }
    
        listen 443 ssl; # managed by Certbot
        ssl_certificate /etc/letsencrypt/live/chat.example.com/fullchain.pem; # managed by Certbot
        ssl_certificate_key /etc/letsencrypt/live/chat.example.com/privkey.pem; # managed by Certbot
        include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
        ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    
    }

    最近因為在不同語言環境下工作,所以這篇正好是中文繁體。