SERP API + Server-Sent Events 实时流式推送实战

场景

AI 助手用 SERP 数据时,用户等 2-3 秒全包响应会焦虑。SSE(服务端推送)让 LLM 边生成边推结果,体验好很多。

SSE 协议

SSE 是 HTTP/1.1 长连接,服务端 chunked transfer 推数据:

vbnet 复制代码
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

data: {"type":"text","content":"让我查 SERP..."}

data: {"type":"text","content":"根据搜索结果..."}

data: {"type":"sources","sources":[...]}

data: [DONE]

客户端用 EventSource 接收,自动重组。

后端(FastAPI + SerpBase)

python 复制代码
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
import requests
import json

app = FastAPI()
app.add_middleware(CORSMiddleware, allow_origins=["*"])

SERPBASE_KEY = "your-serpbase-key"
SERPBASE_URL = "https://api.serpbase.dev"

def call_serp(query, gl="us"):
    r = requests.post(
        f"{SERPBASE_URL}/google/search",
        headers={"X-API-Key": SERPBASE_KEY},
        json={"q": query, "gl": gl, "hl": "en", "num": 5},
        timeout=10,
    )
    r.raise_for_status()
    return r.json()

def call_llm_stream(system, prompt):
    """模拟流式 LLM"""
    response = f"根据 {prompt} 的搜索结果,我会分步骤回答..."
    for word in response.split():
        yield word + " "

async def event_stream(query):
    # 1. 立即推 "loading"
    yield f"data: {json.dumps({'type': 'status', 'content': '正在查 SERP...'})}\n\n"

    # 2. 查 SerpBase
    serp_data = call_serp(query)
    sources = [
        {"rank": i, "title": r.get("title"), "link": r.get("link"), "snippet": r.get("snippet")}
        for i, r in enumerate(serp_data.get("organic", [])[:5], 1)
    ]

    # 3. 推 sources(让前端能立即显示引用)
    yield f"data: {json.dumps({'type': 'sources', 'sources': sources})}\n\n"

    # 4. 流式 LLM 输出
    prompt = f"Sources: {json.dumps(sources, ensure_ascii=False)}"
    for chunk in call_llm_stream("system", prompt):
        yield f"data: {json.dumps({'type': 'text', 'content': chunk})}\n\n"
        await asyncio.sleep(0.02)  # 模拟 LLM 延迟

    # 5. 结束
    yield f"data: {json.dumps({'type': 'done'})}\n\n"

@app.get("/ask")
async def ask(query: str):
    return StreamingResponse(
        event_stream(query),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
        },
    )

前端(JavaScript)

javascript 复制代码
const query = "SERP API 价格 2026";
const eventSource = new EventSource(`/ask?query=${encodeURIComponent(query)}`);

eventSource.addEventListener("message", (e) => {
  const data = JSON.parse(e.data);

  if (data.type === "status") {
    showStatus(data.content);
  } else if (data.type === "sources") {
    showSources(data.sources);  // 立即显示 SERP 引用
  } else if (data.type === "text") {
    appendText(data.content);  // 逐字显示
  } else if (data.type === "done") {
    eventSource.close();
  }
});

5 个工程细节

1. SSE 断线重连:浏览器自动重连,服务器用 last_event_id 支持恢复。

2. 错误处理:

python 复制代码
try:
    serp_data = call_serp(query)
except Exception as e:
    yield f"data: {json.dumps({'type': 'error', 'message': str(e)})}\n\n"
    return

3. 超时控制:

python 复制代码
import asyncio

async def event_stream_with_timeout(query, timeout=30):
    try:
        return await asyncio.wait_for(
            collect_events(query),
            timeout=timeout
        )
    except asyncio.TimeoutError:
        yield f"data: {json.dumps({'type': 'timeout'})}\n\n"

4. 缓存 sources:同一 query 5 分钟内复用 sources(避免重复 SERP API):

python 复制代码
cache = {}
async def event_stream_cached(query):
    if query in cache:
        serp_data = cache[query]
    else:
        serp_data = call_serp(query)
        cache[query] = serp_data
        if len(cache) > 100: cache.clear()
    # ...

5. 取消机制:客户端断开时,Python generator 需检测:

python 复制代码
import asyncio

async def event_stream(query):
    try:
        for event in collect_events(query):
            yield f"data: {json.dumps(event)}\n\n"
            await asyncio.sleep(0)
    except asyncio.CancelledError:
        # 客户端断开,清理资源
        cleanup()
        raise

与 WebSocket 对比

维度 SSE WebSocket
协议 HTTP / 1.1 独立 ws://
方向 单向(server→client) 双向
浏览器支持 原生 EventSource 需 ws lib
自动重连 原生 需手写
防火墙穿透 高(走 HTTP)
AI Agent 适合 ✓ 文本流输出 双向 RPC

AI 助手场景 SSE 比 WebSocket 更合适(单向,文本流)。

实测性能

bash 复制代码
# 后端部署
uvicorn sse_agent:app --host 0.0.0.0 --port 8000

# 前端打开 chrome://inspect
# 看到 event stream 一个个 chunk
  • 1k 并发连接,每连接 0.05% CPU(单核)
  • 平均首字节时间(TTFB):200ms
  • 平均流式总时间:2.1s(SERP 1.4s + LLM 0.7s)
  • 错误率:0%(SSE 用 HTTP/1.1 兼容)

实战数据(1 个月)

指标 数值
总请求 50,000
平均延迟(到首 chunk) 200ms
平均总流式时间 2.1s
满意度(用户反馈) 高("感觉快多了")
SerpBase 失败退款 0.3%(不影响流)
月成本 0.50(50kcalls×0.50(50k calls × 0.50(50kcalls×0.30/1k)

小结

SSE + SerpBase + Claude 流式输出,5 分钟搭出实时 AI 助手:

  • 后端 60 行(FastAPI + SerpBase + 异步生成器)
  • 前端 15 行(EventSource)
  • 用户体验:200ms 首字节,边生成边显示

SerpBase 的 1.4s P50 + auto-refund 完美适配 SSE 流式场景,失败不卡流,自动退 credit。Claude 配合流式输出,token 增量算,成本和延迟都最优。

相关推荐
VIP_CQCRE8 小时前
用 Ace Data Cloud 快速接入 OpenAI Chat Completions API:兼容官方格式,更适合开发者落地
ai·大模型·openai·api·ace data cloud
VIP_CQCRE10 小时前
用 Ace Data Cloud 自动化运营 CSDN:AI 写作、配图、发布与数据复盘
ai·自动化·api·csdn·acedatacloud
2603_965148112 天前
抖音/快手直播选品:用API快速匹配爆款与低价货源
开发语言·python·自动化·api
Java小卷2 天前
低调好用的API接口管理工具 - APIEagle
api
VIP_CQCRE4 天前
用 Cherry Studio 接入 Ace Data Cloud:一个 Token 打通 Claude、GPT、Gemini、DeepSeek 等主流模型
ai·大模型·api·cherry studio·ace data cloud
VIP_CQCRE5 天前
用 GitHub 做开发者营销:把 Ace Data Cloud 的 API 能力变成可运行项目
ai·github·api·开发者工具·acedatacloud
万邦科技Lafite5 天前
天猫商品评论API:评价内容中的用户情感倾向分析
人工智能·api·电商开放平台·淘宝开放平台·api开放接口
用户7783366132117 天前
SERP API JSON Schema 演进版本管理实战
json·api
VIP_CQCRE8 天前
用 GitHub 做技术营销:让 API 在开发者真正活跃的地方被看见
ai·github·api·acedatacloud·技术营销
k4m7v2pz8 天前
16GB Mac 本地跑大模型:ollama 局域网 OpenAI 兼容 API 实战(一:需求与配置)
llm·openai·api·mac·ollama·本地·atomcode