☰
AI搜索评测实战:用BYOK与开源组件建立自己的质量度量体系
2026/9/30 12:21:59 网站建设 项目流程

AI 搜索的质量评测,向来比普通搜索评测更难组织。普通搜索可以用点击率、停留时长做反馈,而 AI 搜索输出的是融合了检索片段与大模型生成的完整答案,光知道“用户有没有点”远远不够。最近有一类以 “Show HN: Measure your AI search with BYOK and OSS (free)” 为代表的开源项目,尝试把这个过程变成可操作、可复现、可回归的工程流程。它解决的问题很直接:自带模型 API Key(BYOK,Bring Your Own Key),用开源组件(OSS,Open Source Software)搭建评测链路,在不把业务数据交给第三方评测平台的前提下,持续度量 AI 搜索在检索准确性、生成质量和用户体感上的真实水平。

这类项目的价值不在于把“检索准确率”做成一个大而全的指标面板,而是先解决一个更基础的问题:每次改知识库、换模型、调 prompt、调 embedding 之后,AI 搜索到底是变好了还是变差了。如果没有一套可以重复执行的测量机制,任何改动都只能依赖几轮人工抽查,结果往往不稳定,也不容易定位是哪一层出了问题。

本文从一个实践者视角拆解这类“测 AI 搜索”的项目:按什么逻辑理解它,如何自建一套带 BYOK 能力的评估服务,需要哪些数据、代码和指标,运行之后如何看结果,以及最常见的问题为什么出现。文中所给代码是说明思路的最小示例,落到你自己的项目时,需要把包名、路径、模型服务商和字段设计替换成实际环境。

1. 先搞清楚:AI 搜索到底要测什么

1.1 AI 搜索不是“搜索 + 聊天”,而是多层系统

如果把 AI 搜索理解成“把用户问题丢给大模型直接回答”,评测就只剩下答案好坏一个维度,这会导致一个严重问题:系统表现差时,无法判断是知识库缺内容、检索没召回、还是模型生成跑偏。真实 AI 搜索通常分多步完成:

  • 用户输入查询后,先对查询做改写、扩展或意图识别。
  • 在知识库、文档库、数据库或网页索引中做召回。
  • 召回结果经过重排,选出最相关的若干片段。
  • 将片段组装成上下文,和查询一起交给大模型。
  • 模型生成答案,部分系统还会附上引用来源。

问题因此产生:最终答案错误,可能是因为知识库里没有相关内容,也可能是因为相关内容没有被召回,还可能是因为上下文拼接顺序错误,或者模型在上下文正确的情况下仍然产生了幻觉。

测量 AI 搜索,第一步就是把这条链路拆开。按层度量,才能拿到可指导优化的结论。若只测一个综合分,改动后分数下降都不知道该去调哪一层。

1.2 从检索、生成到体验,质量维度各不相同

实际评测中,通常把指标分成三组。

第一组是检索质量指标,用来回答“正确文档有没有被捞上来、排得够不够靠前”。常用指标有:

  • Hit Rate @ k:前 k 条结果中是否出现相关知识片段。
  • MRR:第一个正确答案的排名有多靠前。
  • NDCG @ k:结果排序是否符合人工标注的相关性等级。
  • Recall @ k:应召回的相关文档中被召回的占比。

第二组是生成质量指标,用来回答“模型给出的最终答案是否忠于上下文、是否满足了用户问题”。这类指标通常由另一个大模型来评估,或者使用有标准答案的测试集做比对。常见维度包括:

  • 忠实性(Faithfulness):答案是否完全基于给定上下文,不编造不存在的信息。
  • 答案相关性(Answer Relevancy):答案是否回答了用户的原始问题。
  • 完整性:多个知识点是否都覆盖到了。

第三组是体验和成本指标,包括:

  • 无回答率:系统最终没有生成任何有用回复的比例。
  • 引用正确率:答案中引用来源是否和结论真正对应。
  • 首 token 延迟、整体耗时。
  • 每次查询消耗的 token 量和估算费用。

一套偏工程的评测系统,至少要把前两组指标自动化。第三组中部分指标需要真实流量埋点,不适合只靠离线数据集完成,可以作为线上补充。

1.3 为什么用 BYOK 架构来测量更容易落地

如果打开一个公开的 AI 评估工具,直接把知识库和查询上传上去,由平台自带模型完成打分,流程虽然方便,但会产生几个问题:

  • 业务数据经第三方平台处理后,数据边界难以说清。
  • 模型供应商、模型版本由平台控制,被评估的不是“你的真实线上模型”,结果存在偏差。
  • 指标逻辑黑盒,分数波动时不好追溯。
  • 免费额度只是引流,等评测规模上来后可能产生不明费用或平台绑定。

BYOK 架构把模型调用层还给你:评测系统只负责编排评测用例、调用你指定的推理接口、收集结果、计算指标。模型 Key 由你提供,甚至评测系统本身由你自托管。这样评估数据不必经过外部平台,模型参数和服务商完全可控,成本和调用日志也能自己核算。开源(OSS)+ 免费起步,则是把整个管道暴露在明处,你可以读代码、改指标、接自己的存储。

注意:BYOK 并不是“零成本”。开源软件免费,大模型 API 的 token 消耗、向量数据库的存储和人工标注时间仍然需要预算。对“free”的正确理解是首次试用门槛很低,而不是全链路不花钱。

2. BYOK 模式的测量体系:自托管、自持 Key、数据不出内网

2.1 BYOK 在 AI 搜索评测场景里的真正含义

BYOK 原本多用于云原生加密和 SaaS 集成领域,指用户使用自己的密钥。落到 AI 搜索评测时,Key 不单指大模型 API Key,还包括知识库连接串、向量库访问凭据和评估系统的管理员账号。整个评测体系的信任模型由“把数据交给平台”变为“自己持有全部凭据”。

这个设计对很多团队是刚需。企业知识库、客服记录、内部产品文档经常不具备对外传输条件。只要评估过程调用的是外部大模型,文本数据就仍会离网,这点要通过企业安全评审确认。真正的 BYOK 应该在评测服务内部提前处理好脱敏、最小化字段传输和数据擦除策略。

也可以选用可私有化部署的模型,让“Key”指向内网模型网关。评测服务的对外接口保持不变,只是底层模型供应商不同,这正体现了 BYOK 最大的优点:模型是插件,不是绑定。

2.2 一次离线评估请求的完整链路

一次针对某个 AI 搜索系统的离线评估,请求通常需要包含足够多的结构信息,而不是只交一个问题。一个比较中性的设计是把评估请求定义成:

{ "case_id": "case-001", "query": "如何配置 Nginx 反向代理 WebSocket?", "retrieved_ids": ["doc-nginx-websocket-01", "doc-general-websocket"], "retrieved_scores": [0.91, 0.72], "context_ids": ["doc-nginx-websocket-01"], "answer": "要配置 Nginx 反向代理 WebSocket,需要设置 Upgrade 和 Connection 两个头……", "reference_answer": "在 location 中配置 proxy_set_header Upgrade ...", "reference_ids": ["doc-nginx-websocket-01"] }

字段含义分别是:

  • query:评估用例中的查询。
  • retrieved_ids:被测系统实际召回的文档 ID,顺序要保留。
  • retrieved_scores:召回排序分数,可选,用于观察“分差是否合理”。
  • context_ids:最终送进大模型的上下文文档 ID。
  • answer:被测系统最终生成的答案。
  • reference_answer:人工或半自动准备的参考答案。
  • reference_ids:与查询真正相关的文档 ID。

评测服务拿到这个结构,先计算检索层指标,再将 query、answer、reference_answer 拼成 prompt,交给用户自带 Key 的模型做判定,最后汇总成报告。输入结构中同时出现retrieved_ids与context_ids,是为了区分“检索到了”和“选进上下文了”。这两者差异往往能揭示重排模块的问题。

2.3 为什么开源组件适合做评估底座

评测系统使用的底层组件越封闭,越难做二次开发。多数同类型开源项目会把这些能力组合在一起:

  • 向量数据库:存放知识片段,作为检索基准环境。
  • 评测集存储:SQLite 或 Postgres 这类常规数据库即可。
  • 评估调度:用 Python 脚本或队列任务控制批量执行。
  • 指标计算库:例如 RAGAS 或自研指标脚本。
  • 可视化面板:Grafana、Streamlit 或简单 HTML 报表。

这些组件在各自领域都相对成熟。作为实践者,不需要从零实现向量检索,也不建议过早引入重量级平台。先跑通 50 条用例的离线评测,再逐步扩展。

2.4 不要混淆对象存储 OSS 与 Open Source Software

看标题热搜词时,很多人会被另一个“OSS”带偏。网络上有大量“fastadmin 上传到阿里云 OSS”或“OSS 计费”相关内容,那个 OSS 是对象存储产品。而“Measure your AI search with BYOK and OSS (free)”这类开源项目语境里的 OSS,更多指 Open Source Software,也就是开源软件。理解一篇技术材料前,先确认它属于哪个技术社区,否则很容易把云存储的计费和开源评估工具的开源许愿混为一谈。

3. 动手搭一套最小 BYOK AI 搜索评估服务

3.1 环境准备和前置依赖

学习阶段建议在本机完成,不急着上生产。准备工作相对简单:

  • Python 3.10 或更高版本,用于写评测服务。
  • 一个可访问的大模型 API,以及对应 Key。可以选择你所在环境能够正常访问的模型供应商服务,也可以使用本机部署的模型网关。
  • 一个简单文档集和向量库。如果还不了解向量检索,可以先不接真实向量库,用静态 JSON 模拟召回结果。
  • 四个 Python 依赖:fastapi、uvicorn、requests或openai风格 SDK、numpy。

安装命令:

python -m venv venv source venv/bin/activate # Windows 下使用 venv\Scripts\activate pip install fastapi "uvicorn[standard]" requests numpy python-dotenv

如果原始项目依赖不明确,落地前要先确认你实际使用的模型 SDK 版本。OpenAI SDK 0.x 和 1.x 的调用方式差异较大。这里示例统一用 HTTP 请求的方式描述,便于替换成不同供应商的 SDK。

3.2 目录结构参考

一个便于学习的最小评估服务可以这样组织:

ai-search-evaluator/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 入口 │ ├── metrics.py # 检索指标计算 │ ├── llm_judge.py # 大模型判定逻辑 │ └── schemas.py # 请求和响应模型 ├── datasets/ │ └── eval_cases.json # 评测用例 ├── .env # BYOK Key 配置 └── requirements.txt

3.3 用 FastAPI 写一个评估入口

评测接口需要接收上一节定义的评估结构。用 Pydantic 模型定义EvalCaseRequest:

# app/schemas.py from typing import List, Optional from pydantic import BaseModel class EvalCaseRequest(BaseModel): case_id: str query: str retrieved_ids: List[str] = [] retrieved_scores: Optional[List[float]] = [] context_ids: List[str] = [] answer: Optional[str] = "" reference_answer: Optional[str] = "" reference_ids: List[str] = [] class EvalResult(BaseModel): case_id: str hit_rate: bool mrr: float ndcg: float faithfulness_score: float answer_relevancy_score: float

接口设计尽量简单,一个请求只评估一个用例,便于并发和失败重试。

# app/main.py from fastapi import FastAPI from app.schemas import EvalCaseRequest, EvalResult from app.metrics import compute_hit_rate, compute_mrr, compute_ndcg from app.llm_judge import judge_faithfulness, judge_relevancy app = FastAPI(title="AI Search BYOK Evaluator") @app.post("/v1/evaluate", response_model=EvalResult) async def evaluate_case(req: EvalCaseRequest): hit = compute_hit_rate(req.retrieved_ids, req.reference_ids) mrr = compute_mrr(req.retrieved_ids, req.reference_ids) ndcg = compute_ndcg(req.retrieved_ids, req.reference_ids) faithfulness = await judge_faithfulness(req.query, req.context_ids, req.answer) relevance = await judge_relevancy(req.query, req.answer, req.reference_answer) return EvalResult( case_id=req.case_id, hit_rate=hit, mrr=mrr, ndcg=ndcg, faithfulness_score=faithfulness, answer_relevancy_score=relevance, )

这里把检索相关判断用reference_ids计算,生成相关判断则同时使用检索到的上下文和模型生成的答案。只返回分数还不够,评测报告中最好同时保留输入和中间结果,便于出问题时回放。

3.4 检索质量指标的计算

检索指标基于排序列表和标准答案文档 ID 计算。

Hit Rate 是“前 k 个结果里有没有命中”,MRR 是“第一个命中的排名倒数”,NDCG 是“按相关性权重衰减的排序分”。下面是简化实现:

# app/metrics.py from typing import List import math def compute_hit_rate(retrieved_ids: List[str], reference_ids: List[str]): return len(set(retrieved_ids) & set(reference_ids)) > 0 def compute_mrr(retrieved_ids: List[str], reference_ids: List[str]) -> float: reference_set = set(reference_ids) for rank, doc_id in enumerate(retrieved_ids, start=1): if doc_id in reference_set: return 1.0 / rank return 0.0 def _dcg_at_k(scores: List[float], k: int) -> float: scores = scores[:k] return sum(score / math.log2(idx + 2) for idx, score in enumerate(scores)) def compute_ndcg( retrieved_ids: List[str], reference_ids: List[str], k: int = 5 ) -> float: binary = [1.0 if doc_id in set(reference_ids) else 0.0 for doc_id in retrieved_ids] dcg = _dcg_at_k(binary, k) ideal = sorted(binary, reverse=True) idcg = _dcg_at_k(ideal, k) return dcg / idcg if idcg > 0 else 0.0

直接对命中文档打 1 分,非命中打 0 分,只能表达“有无”。真实场景中,评估集最好对每个查询记录多个相关文档,并且标注相关性等级。例如等级 2 表示直接命中,等级 1 表示部分参考,等级 0 表示不相关。多级 NDCG 更容易暴露排序下降的问题。

下面引入多级相关性:

def compute_ndcg_multilevel(retrieved_ids: List[str], relevance_map: dict, k: int = 5) -> float: scores = [float(relevance_map.get(doc_id, 0.0)) for doc_id in retrieved_ids[:k]] dcg = sum(score / math.log2(idx + 2) for idx, score in enumerate(scores)) ideal_scores = sorted([float(v) for v in relevance_map.values()], reverse=True)[:k] idcg = sum(score / math.log2(idx + 2) for idx, score in enumerate(ideal_scores)) return dcg / idcg if idcg > 0 else 0.0

这里的relevance_map通过评测数据集传入:{"doc-a": 2, "doc-b": 1}。

3.5 用自带 Key 模型做生成质量判定

生成质量较难用 n-gram 相似度暴力判断,因为两个语义一致的句子字面差异可能很大。目前开源评测工具大多采用“大模型当评委”的思路:构造一个结构化的打分 prompt,把 query、answer、reference_answer、context 作为输入,要求模型输出 0 到 1 的分数,并给出简短理由。

# app/llm_judge.py import os import httpx JUDGE_PROMPT_TEMPLATE = """ 你是 AI 搜索质量评测员。请必须基于给定上下文判断答案是否忠实。 查询: {query} 上下文片段: {context} 模型答案: {answer} 请回答两个问题: 1. 答案是否完全基于上下文,没有编造事实?输出 0 或 1。 2. 答案是否直接回应查询?输出 0 或 1。 输出 JSON:{"faithfulness": 0/1, "relevancy": 0/1} 不要输出额外解释。 """ async def call_llm(prompt: str) -> str: api_key = os.environ["LLM_API_KEY"] base_url = os.environ.get("LLM_BASE_URL", "https://api.example.com/v1") model = os.environ.get("LLM_MODEL", "your-model") async with httpx.AsyncClient() as client: resp = await client.post( f"{base_url}/chat/completions", headers={"Authorization": f"Bearer {api_key}"}, json={ "model": model, "messages": [{"role": "user", "content": prompt}], "temperature": 0, }, timeout=30, ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"]

这里最核心的取舍是 temperature 设为 0。评测要可复现,如果 judge 模型本身随机性太大,相同输入跑两次结果不同,后续回归就失去意义。即使如此,大模型判定仍存在不确定性,最好对每个用例跑 2 到 3 次取结果作为参考,而不是只信单次输出。

3.6 Key 注入与配置管理

BYOK 的最小实现是环境变量,开发环境可以用.env文件:

LLM_API_KEY=你的模型服务商Key LLM_BASE_URL=https://api.example.com/v1 LLM_MODEL=judge-model-name EVAL_DATA_PATH=./datasets/eval_cases.json REPORT_OUTPUT_PATH=./reports/eval_result.jsonl

用python-dotenv加载:

from dotenv import load_dotenv load_dotenv()

这只是本地做法。生产环境不要直接把 Key 写入代码或镜像,应通过密钥管理服务和部署平台的环境变量注入,并给 Key 设置调用配额和阈值告警,防止某个评测脚本写错循环导致费用飙升。

生产部署之前,至少要做三件事:配置外置化、日志集中化、评测任务可中断重试。离线评估跑几十个用例时无所谓,跑到几万次时,网络抖动、限流、进程重启都会出现,任务必须能断点续跑。

4. 运行评估流程并理解输出

4.1 准备评测用例和被测结果

先从一个小数据集开始。真实的评测集至少包含 query、知识库里的标准答案文档 ID、参考答案。这里用一个用例作为演示:

[ { "case_id": "case-001", "query": "如何配置 Nginx 反向代理 WebSocket?", "reference_ids": ["doc-nginx-websocket-01"], "reference_answer": "需要配置 Upgrade 和 Connection 请求头,同时在 location 中开启 proxy_http_version 1.1。" } ]

评测服务不直接读取原始文档内容,而是依赖reference_ids与检索结果的交集计算排序指标。context 和 answer 可以由被测系统自行生成后提交到评测接口。

4.2 启动服务和发送请求

启动 FastAPI:

uvicorn app.main:app --host 0.0.0.0 --port 8000

用 curl 模拟一次评测请求:

curl -X POST http://127.0.0.1:8000/v1/evaluate \ -H "Content-Type: application/json" \ -d '{ "case_id": "case-001", "query": "如何配置 Nginx 反向代理 WebSocket?", "retrieved_ids": ["doc-nginx-websocket-01", "doc-general-websocket"], "context_ids": ["doc-nginx-websocket-01"], "answer": "配置 Nginx 反向代理 WebSocket 时需要设置 Upgrade 和 Connection 头,并将 HTTP 协议版本设为 1.1。", "reference_answer": "需要配置 Upgrade 和 Connection 请求头,同时开启 proxy_http_version 1.1。" }'

预期响应格式类似:

{ "case_id": "case-001", "hit_rate": true, "mrr": 1.0, "ndcg": 1.0, "faithfulness_score": 1.0, "answer_relevancy_score": 1.0 }

如果检索列表中没有标准答案,Hit Rate 就是 false,MRR 和 NDCG 会是 0。这说明问题大概率出在检索层,而不是生成层。

4.3 批量运行并输出报告

单条 curl 只能验证接口,批量评测要写脚本循环读取数据集,并把结果追加到 JSONL 文件。每一行保留原始请求、模型原始返回和计算结果,便于审计,而不是只保存最终分数。

import asyncio import json import httpx async def evaluate_one(client: httpx.AsyncClient, case: dict, base_url: str): resp = await client.post(f"{base_url}/v1/evaluate", json={ "case_id": case["case_id"], "query": case["query"], "retrieved_ids": case.get("retrieved_ids", []), "context_ids": case.get("context_ids", []), "answer": case.get("answer", ""), "reference_answer": case.get("reference_answer", ""), "reference_ids": case.get("reference_ids", []), }) return {"case": case, "result": resp.json()} async def main(): with open("./datasets/eval_cases.json", "r", encoding="utf-8") as f: cases = json.load(f) async with httpx.AsyncClient() as client: results = await asyncio.gather( *[evaluate_one(client, case, "http://127.0.0.1:8000") for case in cases] ) with open("./reports/eval_result.jsonl", "w", encoding="utf-8") as f: for item in results: f.write(json.dumps(item, ensure_ascii=False) + "\n") asyncio.run(main())

注意不要把并发数设得过大。很多模型供应商对单 Key 有速率限制,压力集中在单个测试账号上时容易出现 429。建议给批量脚本增加semaphore控制并发上限。

4.4 多模型、多检索器对比

落地一段时间后,评测就变成了横向对比工具。实际使用中可以建立一张对比表:

被测系统数据集Hit Rate@3MRRNDCG@3FaithfulnessAnswer Relevancy调用成本
基线:BM25 + 默认模型v1.0 评测集0.720.510.650.910.760.6 元/百次
实验:向量检索 + 默认模型v1.0 评测集0.780.600.700.920.780.7 元/百次
实验:向量检索 + 新模型v1.0 评测集0.780.600.700.940.851.8 元/百次

表格呈现方式比只报单个平均分更有说服力。它能帮你快速看出某个改动带来的收益到底落在检索层还是生成层。

5. 评测数据:从少量样例到可维护的评测集

5.1 相关性分级是检索评测的地基

很多团队刚开始做评测时,会给每个查询只标注一个“正确答案文档”。这种方式容易标注,但存在偏差:知识库中同一主题往往有多个文档、多个片段都算相关。如果只标一个,检索系统即使把其他合理文档排在前面也会被判为错误,评测结果会偏悲观。

更合理的方式是引入相关性分级:

  • 0:与查询无关,或者仅有时间背景重合。
  • 1:部分相关,能提供间接参考。
  • 2:直接相关,能回答查询的核心意图。

评测任务以 JSON 保存时,可以加一个relevance_map字段:

{ "case_id": "case-003", "query": "订单退款后优惠券是否退回?", "relevance_map": { "doc-order-refund-01": 2, "doc-coupon-policy-02": 1, "doc-user-guide-03": 0 } }

NDCG 多级计算可以让排序模型学到“部分相关的文档排在直接相关之前,只扣一点分;完全不相关的文档排在前面,要扣很多分”的约束。只靠 Hit Rate 无法表达这种细微差异。

5.2 在知识库上快速生成初版评测集

人工标注质量最高,但起步成本也高。一个可接受的启动方式是先用半自动方式生成初版,再人工修正。

初版构建路径:

  1. 从检索日志或用户反馈中取高频问题,整理成查询列表。
  2. 对每个查询,直接在现有 AI 搜索系统执行一次检索,取前三到五个结果。
  3. 根据文档标题、摘要做快速判断,标注相关性等级。
  4. 使用一个较强的模型生成参考答案,再由人在知识库原文中厘清是否违背事实。

这个流程并不是一次性的。线上条件允许时,可以从真实搜索会话中提取“最终点击了哪个结果”或“用户是否对回答点了反馈”,定期反哺数据集。没有真实流量反馈的冷启动阶段,人工抽样审核仍是不可缺失的兜底手段。

5.3 评测集也需要版本管理

数据集一旦进入团队协作阶段,就会产生一个容易被忽略的管理问题:没有版本历史,改过之后无法回溯“上一次分数为什么高”。评测集是最容易悄悄变化的资产。

建议至少记录:

  • 数据集版本号或 Git 提交号。
  • 用例总数和各相关性等级分布。
  • 最近一次更新人和更新原因。
  • 评测服务的代码提交号。
  • 被测模型的 vendor、model 名称与版本快照。

评测结果报告头部可以携带这些元信息:

{ "dataset_version": "2024-11-eval-v1", "evaluator_version": "git-abc123", "model_under_test": "your-search-system-v2.3", "judge_model": "judge-model-v1", "run_at": "2024-11-01T10:00:00Z" }

没有这些信息,三个月后你看到一份“准确率提升 5%”的报表,会无法判断提升来自算法改进,还是评测集被改得更容易了。

6. 常见问题:为什么你测出来的结果总是不对

6.1 检索结果为空或只有一条

现象:召回列表为空,或reference_ids中完全命中的文档不足。Hit Rate、MRR、NDCG 全部偏低。

可能原因:

  • 知识库还没索引完整。
  • 查询改写逻辑把短问题变成了无法匹配的复杂表达。
  • embedding 模型不匹配,同一个语义空间的文档没有被检索到。
  • 评估数据里的reference_ids来源于旧版本知识库,新库已经删除该文档。
  • top k 设置过小,例如候选只取 1 个,一旦第一条错了就没有挽救机会。

检查方式:单独打开向量库按 query 查一次,看返回 ID 是否与reference_ids对齐。对比知识库版本和数据集版本。

处理方法:提高 top k 到 5 或 10;确认查询改写是否必要;修复索引同步;扩充数据集时避免引用已下线文档。

6.2 模型无响应、429 或余额不足

现象:批量评测跑到一半,大量请求报 429,或者某一个 Key 失败后整个脚本中断。

可能原因:并发过高触达服务商限流;账号余额不足;网络不通;评测任务缺少重试机制。

检查方式:查看服务商返回的 HTTP 状态码和错误码;观察同一时间点发起的请求数量;检查.env是否被正确加载。

处理建议:给批量脚本加上信号量限制最大并发;对 429、5xx 做指数退避重试;把失败用例单独写到failed_cases.jsonl,修复后从失败列表续跑,不要重新跑全量。

6.3 judge 模型给出的分数忽高忽低

现象:完全相同的输入,连续测两次,分数不一。

可能原因:judge prompt 不够结构化,模型自主发挥空间太大;temperature 没有调成 0;模型在长上下文里忽略了部分上下文;单次调用随机性被直接写入报告。

检查方式:查看模型原始返回,确认输出是否包含额外解释;对同一用例连跑 5 次统计分布。

处理建议:把输出约束成 JSON 字段;让模型先写简短判断理由再给分数;多次调用取中位数或投票结果;将一次跑完的原子结果缓存下来,回评时避免重复计费。

6.4 BYOK Key 配置错、泄露或费用失控

现象:请求能发出去但一直报鉴权失败;日志中打印了完整 Key;月底账单超出预期。

可能原因:环境变量名写错;调用了与模型不匹配的 Key;日志中间件打印了 Header;评测脚本循环没有上限导致重复调用;没有设置消息量配额。

处理建议:

  • 把 Key 写入服务端环境变量,不要写死在源码里。
  • 日志打印请求参数前过滤Authorization头。
  • 在模型供应商后台设置每日消费上限和告警阈值。
  • 利用评测系统的 report 中记录每次调用的 token 数,主动统计成本。
  • 怀疑泄露时立即“更换 Key”,而不是关闭后再启用。

6.5 排查优先级速查表

按系统化顺序排查比随机试错更高效:

优先级检查项快速验证方式
1输入数据是否正确检查 case 数据与格式
2路径和命名是否正确检查评测集目录、报告目录
3依赖版本是否匹配pip freeze 与 requirements 对比
4环境变量是否生效临时打印 Key 长度与 base_url
5网络、端口与限流查看 HTTP 状态码、响应耗时
6评测集是否过期核对 reference_ids 是否还在向量库中
7judge 模型输出是否稳定多次执行并对比 JSON 输出
8系统本身是否存在版本限制查阅组件 changelog 或 issue

从输入到依赖,再到运行环境和日志,通常能定位到 90% 的异常。不要一上来就怀疑模型能力,很多问题出在调用层和数据集本身。

7. 最佳实践:把“凭感觉”升级成“能回归”的评测机制

7.1 把评测跑进 CI/CD,形成回归拦截

离线评测最有价值的应用方式不是“上线前测一次”,而是“每次改动都能自动触发回归”。场景可以是:

  • 修改 prompt 模板后,推送代码自动跑冒烟集。
  • 修改知识库索引逻辑后,跑回归集比较关键指标。
  • 切换 embedding 模型、升级模型供应商 SDK 后,跑全量基准集。

在 CI 中执行时设置“断言线”:

# 伪代码示意,实际平台以自己的 CI pipeline 配置为准 - name: Run AI search evaluation run: python scripts/run_eval.py --dataset datasets/regression_v2.json - name: Check quality gate run: python scripts/check_quality_gate.py --metric-hit-rate 0.70 --metric-ndcg 0.60

质量门禁的意义不是追求分数无限上升,而是防止关键指标无理由下滑。建议把“必过线”先设到比现有分数低 5 到 10 个百分点,达到破坏性变化触发告警,而不是把门禁设得过高导致所有人都失去信心。

7.2 评测集分层:冒烟集、回归集、盲测集

面向不同目的,数据需要分层:

  • 冒烟集:20 到 50 条最典型的用例,30 秒内跑完,用于每次开发迭代快速反馈。
  • 回归集:300 到 1000 条覆盖不同文档类型和难度的用例,用于发布前验证。
  • 盲测集:不参与日常调参,只在发布后定期采样评测,用于观察真实效果漂移。

不要因为“要评测系统”就只准备一个超大文件,任何变更都跑全量,这样的结果反馈周期太长,反而没人执行。分层的核心是让不同场景拿到不同粒度的反馈。

7.3 评估成本也要纳入决策

大模型评测的隐藏成本通常体现在 judge 调用上。一组数据规模很大时,成本往往不是被测模型产生的,而是“评委模型”对答案逐条打分的 token 消耗。

在准备阶段可以用量级估算框定预算:

数据集规模每条用例 judge token 约数单价约数粗略总费
1 万条短期评估集800 tokens/条按你实际模型价格填写按实际服务商价格计算
100 条长期回归集800 tokens/条按你实际模型价格填写价格较低
新增人工抽查 20%同上按你实际模型价格填写额外计入人工复核工时

实际落地时建议:优先用本地小模型做初筛,只把“边界含糊”的用例提升到强模型二审,用更低的成本保证大多数用例的稳定性。

7.4 一份可复用的发布前评测检查清单

发布前遇到这份清单可以逐项打勾,避免只测了一个平均数就上线的冲动:

  • 数据集版本号是否已更新。
  • 测试集是否包含正常查询、模糊查询、缺文档查询三类情况。
  • 被测系统版本和参数是否记录。
  • 检索路径和最终生成路径是否在日志中可区分。
  • judge 模型是否固定版本、temperature 是否为 0。
  • 是否保存了每个用例的原始模型输出,而不只是平均分。
  • 失败用例是否已经重试,重试后是否排除到统计之外。
  • 成本账单是否在可控阈值内。
  • 与上一个版本的核心指标对比是否通过质量门禁。
  • 异常用例是否已有归属人跟进。

8. 从“免费尝鲜”到“生产测量”:接下来还能补什么

8.1 从离线指标走向在线观察

离线评测打的是“能不能答对”,线上生产还要关注“用户真正遇到什么”。建议在离线评估稳定后,增加一组线上数据采集:

  • 无结果率:检索为空或最终没有生成答案的会话比例。
  • 引用点击率:用户是否点击答案引用的文档。
  • 用户反馈率:点赞、点踩、纠错的数据量。
  • 首 token 时延和整体生成耗时。

这些数据的价值在于和离线指标互相校验。离线评测集覆盖不全的边界场景,可以通过真实流量抽样补充进下一版评测集,形成数据回流。

8.2 从检索评测延伸到 Agent 行为评测

若 AI 搜索逐步发展为多轮 Agent,比如先反问用户条件再做检索,或者调用数据库工具后再总结,那评测对象就不再是单个 query,而是一组会话。此时要增加:

  • 工具调用是否合规有效。
  • 最终答案是否依赖上次检索结果。
  • 多轮上下文是否存在遗忘。
  • 用户打断、澄清、中途换主题时,系统是否回到正确路径。

这类评测数据采集成本更高。建议先用少量回放日志做人工标注,再逐步引入评测模型辅助判断,不要一开始就设计一套复杂评分体系。

8.3 安全合规测量不能省

带 BYOK 的评测体系在模型调用层缓解了“Key 归属”问题,但文本数据仍然可能出网。做生产化之前最好与安全团队确认:

  • 查询文本在评测时是否包含敏感字段。
  • 数据脱敏规则在进入评测链路前是否已经执行。
  • 日志持久化时间与删除策略。
  • 自动化评测触发权限与对外暴露接口的访问控制。

AI 搜索评分提升得再快,都不值得以数据安全作为代价。测量能力只有建立在可控的数据边界上,才能长期运行下去。

把开源、自带 Key、免费起步这些特征放在一起时,最有价值的地方其实不是“不用花钱”,而是一个方向:测量 AI 搜索质量的主动权可以完全握在自己手里。评测集自己管、评测逻辑自己改、模型自己选,分数变化时能追回每一层链路。在这套体系稳定跑起来之前,别急着追求复杂炫酷的看板,先把一条用例、一个接口、一份报告跑通,再从 50 条数据扩展到每天自动执行的回归集。这样一轮一轮积累下来,AI 搜索的优化才能从“感觉变好了”走向“知道为什么变好了”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询