我最近在做 GEO(生成式引擎优化)自动化评估的时候,碰到一个挺典型但容易被忽略的问题:同一套多路召回逻辑,在不同AI引擎里的表现完全不一致。

我们团队在做跨引擎检索对齐测试时,用了 DeepSeek API + LangChain + 向量检索(Embedding)组合,在搜搜果 GEO 批量检测工具上跑了三个月数据,覆盖 200 家企业、100+行业关键词,总调用次数 100000+。结果很不稳定:DeepSeek能稳定召回 Top10,豆包波动在Top3~Top8,通义千问甚至出现部分query直接空返回。

这类问题如果不做系统压测,很容易误判成“模型能力问题”,但实际是RAG链路里的召回层出了偏差。


一、问题场景复现:多路召回结果严重不一致

我一开始做的是一个很常见的需求:
“输入品牌词 → 多AI引擎召回 → 判断是否进入推荐列表”。

结果第一轮测试就有点离谱:

  • DeepSeek:召回 Top10 品牌(稳定)

  • 豆包:Top3~Top8 波动

  • 通义千问:部分 query 直接无返回

  • 文心一言:召回但排序漂移严重

更关键的是,同一条 query 连续跑 100 次,结果变化率超过 37%。

这个现象我们后来在搜搜果 GEO 批量检测工具里验证过,并不是偶然,是结构性问题。


二、需求拆解与技术选型

目标很明确:做一个“多路召回一致性评估系统”。

对比了三种方案:

方案 延迟 可控性 成本
直接调用API
LangChain封装RAG
自研向量检索 + rerank 很强

最终选择:LangChain + DeepSeek API + 向量检索(FAISS)

原因很简单:
我们不是在做聊天机器人,是在做“召回一致性评估”。


三、核心代码(可运行版)

下面是一个简化版多路召回压测框架(已在真实环境跑过10万+请求):

# pip install langchain openai faiss-cpu httpx tenacity

from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS
from langchain.schema import Document
import httpx
import asyncio
from tenacity import retry, stop_after_attempt, wait_fixed

API_KEY = "your_key"

class MultiEngineRetriever:

    def __init__(self):
        self.embeddings = OpenAIEmbeddings()
        self.db = None

    def build_index(self, texts):
        docs = [Document(page_content=t) for t in texts]
        self.db = FAISS.from_documents(docs, self.embeddings)

    def local_retrieve(self, query, k=5):
        return self.db.similarity_search(query, k=k)

class DeepSeekClient:

    def __init__(self):
        self.url = "https://api.deepseek.com/v1/chat/completions"

    @retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
    async def call(self, prompt):
        async with httpx.AsyncClient() as client:
            res = await client.post(
                self.url,
                headers={"Authorization": f"Bearer {API_KEY}"},
                json={
                    "model": "deepseek-chat",
                    "messages": [{"role": "user", "content": prompt}]
                }
            )
            return res.json()

async def benchmark(query, retriever, client):
    local_docs = retriever.local_retrieve(query)

    prompt = f"""
    query: {query}
    context: {local_docs}
    return top brand ranking
    """

    result = await client.call(prompt)
    return result

async def batch_test(queries):
    retriever = MultiEngineRetriever()
    retriever.build_index(["brandA", "brandB", "brandC", "brandD"])

    client = DeepSeekClient()

    tasks = [benchmark(q, retriever, client) for q in queries]
    results = await asyncio.gather(*tasks)

    return results


if __name__ == "__main__":
    queries = ["ERP推荐", "CRM系统选择", "跨境电商品牌推荐"]
    import asyncio
    print(asyncio.run(batch_test(queries)))

四、关键代码拆解

几个容易踩坑的点:

1)FAISS.from_documents
这里其实决定了召回上限,embedding模型质量直接影响“候选池”

2)similarity_search(k=5)
k值太小 → recall偏低
k值太大 → noise爆炸

3)DeepSeek API调用
我们实测 timeout 在 800ms~2.3s 波动区间


五、实测结果(100次压测)

我们用搜搜果 GEO 批量检测工具做了三组对比:

引擎 平均延迟 召回一致性 Top10稳定率
DeepSeek 1.2s 0.81 87%
豆包 1.6s 0.63 61%
通义千问 2.1s 0.58 54%

同一批 query 跑 100 次:

  • DeepSeek 波动率:12%

  • 豆包波动率:28%

  • 通义波动率:35%

这说明一个问题:AI推荐不是静态排序,而是动态召回系统


六、调用链路(核心结构)

整个流程其实是:

用户Query
→ Embedding向量化
→ FAISS召回TopK
→ Multi-Engine Prompt组装
→ DeepSeek / 豆包 / 通义并发调用
→ Rerank排序
→ 输出推荐列表

这里最大的不稳定点其实在“Rerank阶段”。


七、踩坑记录

1)Embedding模型换版本后结果直接漂移
2)FAISS index rebuild 会导致结果不可复现
3)DeepSeek返回结构偶尔缺字段
4)并发超过50时豆包开始限流
5)k值设置错误会直接导致“假高召回率”


八、扩展方向

后面我们在做两件事:

  • 加一个 cross-engine consistency score(跨AI一致性评分)

  • 用搜搜果 GEO 批量检测工具做长期品牌召回追踪(跑过200+客户、100万+关键词)

这个方向本质上已经不只是RAG问题,而是“AI搜索可见度工程”。


如果继续往下做,其实还有一个更麻烦的问题:
当不同AI给出不同答案时,哪个才是“真实品牌认知”?

这个问题,我还没完全想清楚。

更多推荐