ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI搜索评测实战:用BYOK与开源组件建立自己的质量度量体系

AI搜索评测实战:用BYOK与开源组件建立自己的质量度量体系 AI 搜索的质量评测向来比普通搜索评测更难组织。普通搜索可以用点击率、停留时长做反馈而 AI 搜索输出的是融合了检索片段与大模型生成的完整答案光知道“用户有没有点”远远不够。最近有一类以 “Show HN: Measure your AI search with BYOK and OSS (free)” 为代表的开源项目尝试把这个过程变成可操作、可复现、可回归的工程流程。它解决的问题很直接自带模型 API KeyBYOKBring Your Own Key用开源组件OSSOpen 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.txt3.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(titleAI Search BYOK Evaluator) app.post(/v1/evaluate, response_modelEvalResult) 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_idreq.case_id, hit_ratehit, mrrmrr, ndcgndcg, faithfulness_scorefaithfulness, answer_relevancy_scorerelevance, )这里把检索相关判断用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, start1): 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, reverseTrue) 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()], reverseTrue)[: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: fBearer {api_key}}, json{ model: model, messages: [{role: user, content: prompt}], temperature: 0, }, timeout30, ) 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_URLhttps://api.example.com/v1 LLM_MODELjudge-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 启动服务和发送请求启动 FastAPIuvicorn 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 就是 falseMRR 和 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, encodingutf-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, encodingutf-8) as f: for item in results: f.write(json.dumps(item, ensure_asciiFalse) \n) asyncio.run(main())注意不要把并发数设得过大。很多模型供应商对单 Key 有速率限制压力集中在单个测试账号上时容易出现 429。建议给批量脚本增加semaphore控制并发上限。4.4 多模型、多检索器对比落地一段时间后评测就变成了横向对比工具。实际使用中可以建立一张对比表被测系统数据集Hit Rate3MRRNDCG3FaithfulnessAnswer 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 在知识库上快速生成初版评测集人工标注质量最高但起步成本也高。一个可接受的启动方式是先用半自动方式生成初版再人工修正。初版构建路径从检索日志或用户反馈中取高频问题整理成查询列表。对每个查询直接在现有 AI 搜索系统执行一次检索取前三到五个结果。根据文档标题、摘要做快速判断标注相关性等级。使用一个较强的模型生成参考答案再由人在知识库原文中厘清是否违背事实。这个流程并不是一次性的。线上条件允许时可以从真实搜索会话中提取“最终点击了哪个结果”或“用户是否对回答点了反馈”定期反哺数据集。没有真实流量反馈的冷启动阶段人工抽样审核仍是不可缺失的兜底手段。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_url5网络、端口与限流查看 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 搜索的优化才能从“感觉变好了”走向“知道为什么变好了”。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进