AI Agent 调 SERP API 时,我想对比 5 家服务哪个对 Agent 帮助最大。下面流量染色 + A/B 测试方案。

1. 场景

  • 50 用户 / 月
  • 同一 query 分流到 5 家 SERP API
  • 测量指标:LLM 答案质量 / 延迟 / 成本
  • 决策:选哪家长期合作

2. 流量染色基础

每个请求带 tag,标识属于哪个实验组:

import hashlib

def get_group(user_id, experiment='serp_provider'):
    """根据 user_id 哈希分流"""
    h = hashlib.md5(f"{user_id}:{experiment}".encode()).hexdigest()
    bucket = int(h[:8], 16) % 100

    if bucket < 20:
        return 'A'  # 20%
    elif bucket < 40:
        return 'B'  # 20%
    elif bucket < 60:
        return 'C'  # 20%
    elif bucket < 80:
        return 'D'  # 20%
    else:
        return 'E'  # 20%

同一 user 永远在同一组,保证实验一致性。

3. 实验设计

3.1 实验组

SERP API 占比
A serpbase 20%
B SerpApi 20%
C Serper.dev 20%
D DataForSEO 20%
E Bright Data 20%

3.2 测量指标

3 类:

业务指标:

  • LLM 答案准确率(人工评估)
  • 幻觉率
  • 答案深度

性能指标:

  • SERP API 延迟
  • LLM 响应延迟
  • 端到端延迟

成本指标:

  • SERP API 成本
  • LLM token 成本
  • 总成本 / 1000 次 query

4. 完整代码

import hashlib
import requests
import time
import os

PROVIDERS = {
    'A': {
        'name': 'serpbase',
        'url': 'https://api.serpbase.dev/google/search',
        'key_env': 'SERPBASE_API_KEY',
        'header': 'X-API-Key',
    },
    'B': {
        'name': 'serpapi',
        'url': 'https://serpapi.com/search',
        'key_env': 'SERPAPI_API_KEY',
        'header': 'api_key',  # query string
    },
    # ... C / D / E
}

def get_group(user_id):
    h = hashlib.md5(f"{user_id}:serp_provider".encode()).hexdigest()
    return chr(ord('A') + int(h[:8], 16) % 5)

def call_provider(group, query):
    """按实验组调对应 SERP API"""
    p = PROVIDERS[group]
    api_key = os.environ[p['key_env']]

    if p['name'] == 'serpapi':
        # SerpApi 用 query string
        r = requests.get(
            p['url'],
            params={'api_key': api_key, 'q': query, 'num': 10},
            timeout=5
        )
    else:
        # 其他用 Header
        r = requests.post(
            p['url'],
            headers={p['header']: api_key},
            json={'q': query, 'num': 10},
            timeout=5
        )

    r.raise_for_status()
    return r.json()

# 主流程
def ab_search(user_id, query):
    group = get_group(user_id)
    start = time.time()
    serp = call_provider(group, query)
    latency = time.time() - start

    # 上报指标
    metrics.record(group=group, latency=latency, success=True)

    return serp, group

5. 指标收集

每个实验组上报指标:

import time
from prometheus_client import Counter, Histogram

search_total = Counter('ab_search_total', 'Total AB searches', ['group', 'provider'])
search_latency = Histogram('ab_search_latency', 'AB search latency', ['group', 'provider'])
search_errors = Counter('ab_search_errors', 'AB search errors', ['group', 'provider', 'error_type'])

def record_metrics(group, provider, latency, success=True, error_type=None):
    search_total.labels(group=group, provider=provider).inc()
    search_latency.labels(group=group, provider=provider).observe(latency)
    if not success:
        search_errors.labels(group=group, provider=provider, error_type=error_type).inc()

Grafana 看板对比 5 组。

6. 数据分析

跑 7 天后,聚合数据:

def analyze_ab_results():
    """分析 A/B 测试结果"""
    results = {}

    for group in 'ABCDE':
        # 累计成功率
        total = search_total.labels(group=group, provider=PROVIDERS[group]['name'])._value.get()
        # 累计延迟
        # ...

        results[group] = {
            'total': total,
            'p50_latency': ...,
            'p99_latency': ...,
            'error_rate': ...,
            'avg_cost': ...
        }

    return results

7. 7 天实测数据(项目跑过)

provider P50 延迟 错误率 成本/1000 答案准确率
A serpbase 1.4s 0.2% $0.30 91%
B SerpApi 2.8s 0.4% $0.80 90%
C Serper 1.6s 0.5% $0.20 85%
D DataForSEO 4.5s 2.4% $0.40 78%
E Bright Data 3.2s 1.0% $1.20 88%

serpbase (A) 综合最优

8. 流量放大

5% 流量看不清,放大到 20% / 组后:

  • 7 天收集 100K+ 请求 / 组
  • 95% 置信区间 < 0.5%
  • 决策可信

9. 灰度发布

A/B 测试确定选 A 后,灰度切流量:

阶段 旧 (B) 新 (A) 持续时间
第 1 周 90% 10% 7 天
第 2 周 70% 30% 7 天
第 3 周 30% 70% 7 天
第 4 周 0% 100% -

每阶段监控指标,有问题回滚。

10. 灰度代码

def get_provider(user_id):
    """灰度发布版本"""
    # 10% → A
    if get_rollout_group(user_id, 'serpbase_rollout', percentage=100):
        return 'A'
    return 'B'  # 默认旧版本

def get_rollout_group(user_id, experiment, percentage):
    h = hashlib.md5(f"{user_id}:{experiment}".encode()).hexdigest()
    bucket = int(h[:8], 16) % 100
    return bucket < percentage

通过调 percentage 参数控制灰度比例,平滑切流。

11. 回滚机制

灰度过程发现 A 有问题,立即回滚:

def get_provider(user_id):
    percentage = get_current_rollout_percentage()  # 从配置读

    if percentage < 100 and has_issue_detected('A'):
        # 自动回滚到 0
        percentage = 0
        alert('Auto rollback to 0%')

    if get_rollout_group(user_id, 'serpbase_rollout', percentage):
        return 'A'
    return 'B'

自动检测异常 + 回滚。

12. 实战数据

我项目跑 2 个月:

  • A/B 测试 7 天,流量染色分流
  • 5 组 × 7 天 × 1000 次/天 = 35000 次
  • 综合评分:serpbase 第一
  • 灰度 4 周,100% 切换
  • 0 故障

13. 工具支持

不需要自建,可用第三方:

  • LaunchDarkly:商业,功能全
  • GrowthBook:开源
  • Unleash:开源,自托管
  • 自建:Redis + 哈希分流(我项目用)

14. 总结

A/B 测试 + 流量染色 + 灰度发布:

  • 哈希分流(同 user 同组)
  • 5 组 × 7 天实测
  • 综合评分选 provider
  • 4 周灰度切流
  • 自动回滚

代码 GitHub 公开,clone 跑起来。

相关链接

本文示例以 serpbase 的接口为例,完整文档和接入指南在 serpbase.dev

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐