測(cè)工作臺(tái):從軌跡回放到回歸對(duì)比的落地實(shí)踐)
Agent 評(píng)測(cè)是 Agent 工程化落地中最容易被低估的一環(huán)。很多團(tuán)隊(duì)能快速搭出一個(gè)能跑的 Agent卻很難回答三個(gè)基礎(chǔ)問(wèn)題新版提示詞真的比舊版好嗎同一個(gè)任務(wù)連續(xù)運(yùn)行二十次成功率是多少失敗的回合到底是模型理解錯(cuò)了、工具調(diào)用錯(cuò)了還是任務(wù)定義本身有歧義。Agent Review Studio 這類 local-first agent evaluation workbench解決的就是這件事把評(píng)測(cè)集組織、運(yùn)行軌跡記錄、指標(biāo)計(jì)算和結(jié)果審查整合到一個(gè)本地工作臺(tái)中讓 Agent 的行為變化可復(fù)現(xiàn)、可對(duì)比、可人工核查。這篇文章不假定你已經(jīng)擁有完整的評(píng)測(cè)平臺(tái)也不綁定某個(gè)具體 Agent 框架。我會(huì)從評(píng)測(cè)工作臺(tái)解決的底層問(wèn)題開(kāi)始再給出一個(gè)可以在本地跑通的最小實(shí)現(xiàn)內(nèi)容包括評(píng)測(cè)集定義、運(yùn)行器、軌跡存儲(chǔ)、指標(biāo)計(jì)算和結(jié)果審查。讀完以后你可以把同一套思路遷移到自己的 Agent 項(xiàng)目里底層不管是自研模型調(diào)用、LangChain 這類編排框架還是企業(yè)內(nèi)部封裝好的 SDK都只影響適配層不影響工作臺(tái)的整體設(shè)計(jì)。1. Agent 評(píng)測(cè)為什么不能只靠普通測(cè)試腳本1.1 Agent 行為和普通函數(shù)測(cè)試有本質(zhì)差別傳統(tǒng)單元測(cè)試有一個(gè)隱含前提相同的輸入會(huì)產(chǎn)生穩(wěn)定輸出。對(duì)一個(gè)純函數(shù)來(lái)說(shuō)入?yún)⒋_定、執(zhí)行環(huán)境確定返回值就可以被精確斷言。Agent 不具備這個(gè)特性。Agent 的輸入是自然語(yǔ)言任務(wù)執(zhí)行過(guò)程是模型推理、工具調(diào)用、結(jié)果觀察的循環(huán)最后的輸出既受模型版本和溫度參數(shù)影響也受上下文長(zhǎng)度、工具返回內(nèi)容、網(wǎng)絡(luò)延遲甚至并發(fā)順序影響。同樣是幫我查一下本月訂單金額并生成匯總模型第一次可能直接調(diào)用查詢工具第二次可能先問(wèn)用戶要具體日期范圍第三次可能選擇了錯(cuò)誤的時(shí)間字段。三個(gè)回合的最終結(jié)果可能完全不同但每個(gè)回合內(nèi)部都有完整推理鏈路。只用斷言結(jié)果正確與否的測(cè)試腳本無(wú)法回答為什么會(huì)失敗失敗在哪一步是不是工具返回格式變了這類問(wèn)題。所以 Agent 評(píng)測(cè)的核心對(duì)象不是單個(gè)輸出值而是執(zhí)行過(guò)程本身。評(píng)測(cè)工作臺(tái)必須能記錄每一輪推理、每一次工具調(diào)用、每一個(gè)中間觀察結(jié)果把黑盒輸出變成可審查的白盒軌跡。軌跡回放能力是評(píng)測(cè)工作臺(tái)和普通測(cè)試腳本最本質(zhì)的區(qū)別。1.2 local-first 解決的是數(shù)據(jù)主權(quán)和可復(fù)現(xiàn)問(wèn)題local-first 這個(gè)詞在 Agent 評(píng)測(cè)場(chǎng)景下有兩層含義。第一層是數(shù)據(jù)不出本機(jī)。Agent 運(yùn)行軌跡里通常包含用戶問(wèn)題、業(yè)務(wù)字段、工具返回的原始數(shù)據(jù)這些內(nèi)容很可能涉及敏感信息。如果評(píng)測(cè)平臺(tái)是云端 SaaS軌跡上傳意味著業(yè)務(wù)數(shù)據(jù)離開(kāi)本地網(wǎng)絡(luò)很多團(tuán)隊(duì)在合規(guī)上無(wú)法接受。本地優(yōu)先的工作臺(tái)把 SQLite 文件、軌跡目錄和評(píng)測(cè)報(bào)告都放在本機(jī)評(píng)測(cè)數(shù)據(jù)的所有權(quán)和使用權(quán)都留在團(tuán)隊(duì)自己手里。第二層是環(huán)境可控。云端評(píng)測(cè)平臺(tái)往往黑盒管理模型版本、參數(shù)和依賴版本出現(xiàn)問(wèn)題后很難回溯。本地工作臺(tái)可以把模型版本、提示詞版本、工具定義版本、依賴鎖文件一起記錄成一次評(píng)測(cè)快照。出問(wèn)題時(shí)按快照重建環(huán)境就能復(fù)現(xiàn)不需要猜測(cè)線上環(huán)境發(fā)生了什么變化。需要說(shuō)明的是local-first 不等于完全離線。評(píng)測(cè)運(yùn)行器仍然需要調(diào)用模型 API只是評(píng)測(cè)框架本身、存儲(chǔ)、審查界面都在本地運(yùn)行。文章后面說(shuō)的架構(gòu)也是這種形態(tài)框架本地化模型遠(yuǎn)端調(diào)用。1.3 評(píng)測(cè)工作臺(tái)的五個(gè)能力層次從功能角度看一個(gè)可用的 Agent 評(píng)測(cè)工作臺(tái)至少包含五層能力評(píng)測(cè)集管理組織和版本化用例集合支持分類、標(biāo)簽、預(yù)期結(jié)果描述。運(yùn)行器批量執(zhí)行評(píng)測(cè)任務(wù)統(tǒng)一注入環(huán)境變量、模型參數(shù)和工具配置。軌跡存儲(chǔ)把一次運(yùn)行的所有步驟按時(shí)間順序落盤形成可回放證據(jù)。指標(biāo)計(jì)算在軌跡基礎(chǔ)上計(jì)算成功率、工具調(diào)用有效率、延遲、Token 消耗等。審查與對(duì)比把多次運(yùn)行結(jié)果放在一起比較定位差異和回歸。這五層缺了任何一層評(píng)測(cè)都會(huì)退回成跑一遍看日志。下面各部分會(huì)按這個(gè)層次順序展開(kāi)實(shí)現(xiàn)。2. 先理解評(píng)測(cè)工作臺(tái)的核心概念2.1 評(píng)測(cè)集Suite和用例Case評(píng)測(cè)集是若干評(píng)測(cè)用例的集合。一個(gè)用例代表一個(gè)要驗(yàn)證的任務(wù)場(chǎng)景包含任務(wù)輸入、任務(wù)描述、預(yù)期行為、標(biāo)簽和可選的環(huán)境配置。id: case-001 name: 查詢訂單狀態(tài)并回復(fù)用戶 description: 用戶想知道訂單 20240315001 當(dāng)前處于什么狀態(tài) input: message: 我的訂單 20240315001 現(xiàn)在到哪一步了 expected: type: contains values: - 已發(fā)貨 - 運(yùn)輸中 - 已完成 tags: - order - retrieval difficulty: easy這里的expected不是硬編碼最終答案而是描述什么結(jié)果可以接受。常見(jiàn)判斷方式有四類判斷方式適用場(chǎng)景說(shuō)明exact match結(jié)構(gòu)化輸出、JSON 字段輸出必須精確相等適合工具返回校驗(yàn)contains / regex自然語(yǔ)言回復(fù)判斷關(guān)鍵信息是否出現(xiàn)JSON Schema 校驗(yàn)工具參數(shù)、結(jié)構(gòu)化結(jié)果校驗(yàn)字段類型和必填項(xiàng)不關(guān)心具體值LLM-as-judge / 人工審查開(kāi)放性任務(wù)由模型或人判斷結(jié)果質(zhì)量需要額外控制偏差在最小實(shí)現(xiàn)里可以先支持前三種把 fourth 種留到后面擴(kuò)展。2.2 軌跡Trace是評(píng)測(cè)的第一手證據(jù)軌跡是一次評(píng)測(cè)運(yùn)行從開(kāi)始到結(jié)束的完整事件序列。一個(gè)最小軌跡應(yīng)該包含以下字段{ runId: run-20250315-001, caseId: case-001, startedAt: 2025-03-15T10:00:00.000Z, finishedAt: 2025-03-15T10:02:31.000Z, status: completed, steps: [ { seq: 1, type: model, input: 我的訂單 20240315001 現(xiàn)在到哪一步了, output: 我需要查詢訂單狀態(tài)先調(diào)用訂單查詢工具。, model: gpt-4o-mini, temperature: 0, tokensIn: 120, tokensOut: 45, durationMs: 800 }, { seq: 2, type: tool_call, name: query_order, arguments: {\orderId\: \20240315001\}, result: {\status\: \shipped\, \updated\: \2025-03-15T08:30:00Z\}, durationMs: 210 } ] }軌跡的價(jià)值在于事后審查。當(dāng)最終結(jié)果失敗時(shí)審查者需要知道模型在哪一步產(chǎn)生了錯(cuò)誤判斷工具返回了什么引起誤解后續(xù)步驟是否基于錯(cuò)誤信息繼續(xù)執(zhí)行。沒(méi)有軌跡指標(biāo)就是沒(méi)有根據(jù)的數(shù)字有了軌跡指標(biāo)才能被追責(zé)和復(fù)盤。2.3 指標(biāo)分為結(jié)果指標(biāo)和過(guò)程指標(biāo)指標(biāo)不能只看最終成功率。一個(gè) Agent 可能最后輸出了正確答案但中間調(diào)用了八次工具、繞了很多彎路也可能最終失敗但失敗原因是外部 API 超時(shí)而非 Agent 邏輯錯(cuò)誤。所以要把指標(biāo)分成兩類指標(biāo)類別指標(biāo)名稱計(jì)算方法說(shuō)明結(jié)果指標(biāo)Success Rate成功用例數(shù) / 總用例數(shù)最直觀的總體質(zhì)量指標(biāo)結(jié)果指標(biāo)Task Score按完成度打分 0-100適合部分完成也算分的情況過(guò)程指標(biāo)Tool Success Rate工具成功調(diào)用數(shù) / 總調(diào)用數(shù)反映工具使用能力過(guò)程指標(biāo)Avg Steps總步驟數(shù) / 運(yùn)行次數(shù)步驟多不一定差但可以反映繞路過(guò)程指標(biāo)Avg Latency總耗時(shí) / 運(yùn)行次數(shù)和成本一起決定可用性過(guò)程指標(biāo)Token Usage總 Token 數(shù)或單次平均直接對(duì)應(yīng) API 成本過(guò)程指標(biāo)Abort Rate超時(shí)或異常終止數(shù) / 總運(yùn)行數(shù)反映穩(wěn)定性真實(shí)項(xiàng)目里結(jié)果指標(biāo)決定功能是否達(dá)標(biāo)過(guò)程指標(biāo)決定 Agent 是否值得上線。一個(gè)步驟少、延遲低、成本可控的 Agent即使成功率略低也往往更容易被用戶接受。2.4 回歸對(duì)比把單次運(yùn)行變成趨勢(shì)Agent 評(píng)測(cè)最大的陷阱是拿一次運(yùn)行結(jié)果下結(jié)論。同樣的提示詞溫度為零時(shí)也可能因?yàn)榉谴_定性采樣產(chǎn)生不同輸出工具服務(wù)波動(dòng)也會(huì)影響結(jié)果。正確做法是對(duì)同一套評(píng)測(cè)集反復(fù)運(yùn)行多次把結(jié)果當(dāng)成分布來(lái)看?;貧w對(duì)比指兩件事一是同一個(gè) Agent 版本在多次運(yùn)行之間的穩(wěn)定性對(duì)比二是新舊版本在相同評(píng)測(cè)集上的效果差異。工作臺(tái)應(yīng)該把兩次運(yùn)行的用例結(jié)果對(duì)齊逐條顯示這個(gè)用例舊版成功、新版失敗或新舊都成功但新版多用了兩步。這種差異視圖是決定是否發(fā)布新版本的主要依據(jù)。3. 環(huán)境準(zhǔn)備搭建一個(gè)本地可跑通的腳手架3.1 技術(shù)棧選擇與版本要求下面示例用于說(shuō)明思路實(shí)際項(xiàng)目可以按團(tuán)隊(duì)熟悉程度替換。核心原則是運(yùn)行器用你熟悉的后端語(yǔ)言存儲(chǔ)用本地文件或 SQLite審查界面用最簡(jiǎn)單的方式呈現(xiàn)。推薦一個(gè)低成本的組合組件推薦選擇備選方案選擇理由運(yùn)行器Node.js TypeScriptPython FastAPI類型約束對(duì)軌跡數(shù)據(jù)結(jié)構(gòu)很有幫助存儲(chǔ)SQLitebetter-sqlite3JSON Lines 文件單文件、零運(yùn)維、適合本地評(píng)測(cè)集定義YAMLJSON可讀性好適合寫(xiě)注釋審查界面本地 Express 服務(wù) 靜態(tài) HTMLReact/Vue最小實(shí)現(xiàn)避免構(gòu)建復(fù)雜度命令行入口Commander直接 npm script方便在本地和 CI 復(fù)用環(huán)境要求很簡(jiǎn)單Node.js 18 以上npm 或 pnpm一臺(tái)能訪問(wèn)模型 API 的開(kāi)發(fā)機(jī)。不需要部署數(shù)據(jù)庫(kù)不需要申請(qǐng)額外的評(píng)測(cè)平臺(tái)賬號(hào)。注意如果你要接不同模型 API請(qǐng)先確認(rèn)項(xiàng)目使用模型的版本、baseURL 和鑒權(quán)方式。模型版本不固定評(píng)測(cè)結(jié)果的可復(fù)現(xiàn)性會(huì)大打折扣。3.2 目錄結(jié)構(gòu)設(shè)計(jì)一個(gè)最小項(xiàng)目可以按功能拆分目錄agent-review-studio/ ├── package.json ├── tsconfig.json ├── data/ │ └── eval.db # SQLite 數(shù)據(jù)庫(kù)本地自動(dòng)生成 ├── suites/ │ ├── order-basic.yaml # 評(píng)測(cè)集定義 │ └── order-edge.yaml ├── src/ │ ├── cli.ts # 命令行入口 │ ├── types.ts # 軌跡、用例、指標(biāo)類型 │ ├── runner.ts # 評(píng)測(cè)運(yùn)行器 │ ├── store.ts # SQLite 持久化 │ ├── metrics.ts # 指標(biāo)計(jì)算 │ ├── adapter.ts # Agent 適配層隔離不同框架 │ └── server.ts # 本地審查服務(wù) └── reports/ # 生成的報(bào)告目錄這里的adapter.ts是關(guān)鍵。評(píng)測(cè)工作臺(tái)不應(yīng)該關(guān)心 Agent 內(nèi)部是 LangChain 還是自研實(shí)現(xiàn)它只要求適配層暴露一個(gè)統(tǒng)一方法接收消息和歷史上下文返回本輪輸出、工具調(diào)用列表和 token 消耗。評(píng)測(cè)集、指標(biāo)、存儲(chǔ)全部建立在統(tǒng)一接口之上替換底層框架只需要重寫(xiě)適配層。3.3 SQLite 表結(jié)構(gòu)設(shè)計(jì)評(píng)測(cè)數(shù)據(jù)核心是四張表用例、運(yùn)行、步驟、指標(biāo)。CREATE TABLE cases ( id TEXT PRIMARY KEY, suite TEXT NOT NULL, name TEXT NOT NULL, input TEXT NOT NULL, expected TEXT NOT NULL, tags TEXT DEFAULT [], created_at TEXT DEFAULT (datetime(now)) ); CREATE TABLE runs ( id TEXT PRIMARY KEY, case_id TEXT NOT NULL, status TEXT NOT NULL, -- running / completed / failed / aborted model TEXT, prompt_version TEXT, started_at TEXT, finished_at TEXT, error TEXT ); CREATE TABLE steps ( id INTEGER PRIMARY KEY AUTOINCREMENT, run_id TEXT NOT NULL, seq INTEGER NOT NULL, type TEXT NOT NULL, -- model / tool_call / tool_result / error name TEXT, input TEXT, output TEXT, duration_ms INTEGER, tokens_in INTEGER, tokens_out INTEGER, created_at TEXT DEFAULT (datetime(now)) ); CREATE TABLE metrics ( run_id TEXT PRIMARY KEY, success INTEGER, task_score REAL, tool_success_rate REAL, avg_latency_ms REAL, total_tokens INTEGER, steps INTEGER );表結(jié)構(gòu)的設(shè)計(jì)要點(diǎn)是把運(yùn)行的原始軌跡和計(jì)算出的指標(biāo)分開(kāi)存儲(chǔ)。軌跡是證據(jù)指標(biāo)是結(jié)論。證據(jù)不能因?yàn)橹蟾牧怂惴ǘ鴣G失所以任何指標(biāo)計(jì)算都應(yīng)該是從軌跡重新推導(dǎo)而不是在軌跡上原地修改。這樣后面優(yōu)化指標(biāo)算法時(shí)歷史運(yùn)行記錄還能重新計(jì)算不需要重跑評(píng)測(cè)。4. 實(shí)現(xiàn)一個(gè)最小評(píng)測(cè)閉環(huán)4.1 定義評(píng)測(cè)任務(wù)和預(yù)期結(jié)果在suites/order-basic.yaml中定義兩個(gè)用例一個(gè)驗(yàn)證正常查詢一個(gè)驗(yàn)證無(wú)效訂單處理suite: order-basic description: 訂單查詢基礎(chǔ)能力評(píng)測(cè) cases: - id: order-001 name: 查詢已發(fā)貨訂單 input: message: 我的訂單 20240315001 現(xiàn)在到哪一步了 expected: type: contains values: [已發(fā)貨, 運(yùn)輸中, 已完成] tags: [order, happy-path] - id: order-002 name: 查詢不存在的訂單 input: message: 查一下訂單 999999 的物流狀態(tài) expected: type: contains values: [不存在, 沒(méi)有找到, 無(wú)效] tags: [order, edge-case]這里隱藏了一個(gè) Agent 評(píng)測(cè)的常見(jiàn)誤區(qū)預(yù)期結(jié)果描述的是用戶可接受的信息而不是模型逐字輸出。如果寫(xiě)死values: [已發(fā)貨](méi)當(dāng)模型回復(fù)您的訂單已經(jīng)發(fā)出正在運(yùn)輸途中時(shí)正確信息因?yàn)榇朕o不同被判失敗。評(píng)測(cè)集要站在用戶價(jià)值角度設(shè)計(jì)而不是站在字符串匹配角度設(shè)計(jì)。4.2 實(shí)現(xiàn)評(píng)測(cè)運(yùn)行器運(yùn)行器負(fù)責(zé)讀取評(píng)測(cè)集、逐條調(diào)用 Agent 適配層、收集軌跡。下面是一個(gè)最小實(shí)現(xiàn)// src/runner.ts import { readFile } from fs/promises; import yaml from yaml; import { RunResult, Step, Suite } from ./types; import { runAgent, AgentAdapterOptions } from ./adapter; export async function runSuite(filePath: string, adapter: AgentAdapterOptions) { const raw await readFile(filePath, utf-8); const suite yaml.parse(raw) as Suite; const results: RunResult[] []; for (const testCase of suite.cases) { const steps: Step[] []; const startedAt new Date().toISOString(); let status: RunResult[status] completed; let error: string | undefined; try { // 調(diào)用適配層執(zhí)行 Agent接收逐步回調(diào)以記錄軌跡 await runAgent( { message: testCase.input.message, suite: suite.suite, caseId: testCase.id, }, adapter, (step) steps.push(step) ); } catch (e) { status failed; error e instanceof Error ? e.message : String(e); } results.push({ runId: ${testCase.id}-${Date.now()}, caseId: testCase.id, status, startedAt, finishedAt: new Date().toISOString(), steps, error, }); } return results; }這段代碼的關(guān)鍵在于runAgent的第三步回調(diào)。很多初版評(píng)測(cè)框架只會(huì)收集最終輸出丟掉了中間步驟。正確的做法是讓適配層在每個(gè)事件發(fā)生時(shí)就上報(bào)一次運(yùn)行器只負(fù)責(zé)按順序追加。這樣軌跡的完整性由運(yùn)行器保證不屬于自定義 Agent 邏輯的一部分。4.3 保存軌跡和計(jì)算結(jié)果運(yùn)行完成后需要把軌跡寫(xiě)入 SQLite并計(jì)算指標(biāo)。寫(xiě)入順序很重要先寫(xiě)運(yùn)行主記錄再寫(xiě)步驟再寫(xiě)指標(biāo)。任何一步失敗都應(yīng)該讓整次運(yùn)行標(biāo)記為 failed避免出現(xiàn)有指標(biāo)沒(méi)軌跡的殘缺數(shù)據(jù)。import Database from better-sqlite3; export function persistRun(db: Database, result: RunResult) { const insertRun db.prepare( INSERT INTO runs (id, case_id, status, error, started_at, finished_at) VALUES (?, ?, ?, ?, ?, ?) ); const insertStep db.prepare( INSERT INTO steps (run_id, seq, type, name, input, output, duration_ms, tokens_in, tokens_out) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ); const insertMetric db.prepare( INSERT INTO metrics (run_id, success, task_score, tool_success_rate, avg_latency_ms, total_tokens, steps) VALUES (?, ?, ?, ?, ?, ?, ?) ); const tx db.transaction(() { insertRun.run( result.runId, result.caseId, result.status, result.error ?? null, result.startedAt, result.finishedAt ); for (const step of result.steps) { insertStep.run( result.runId, step.seq, step.type, step.name ?? null, step.input ?? null, step.output ?? null, step.durationMs ?? null, step.tokensIn ?? null, step.tokensOut ?? null ); } const metrics computeMetrics(result); insertMetric.run( result.runId, metrics.success ? 1 : 0, metrics.taskScore, metrics.toolSuccessRate, metrics.avgLatencyMs, metrics.totalTokens, metrics.steps ); }); tx(); }使用db.transaction的目的是保證一批數(shù)據(jù)要么全部寫(xiě)入要么全部不寫(xiě)入。否則一旦運(yùn)行到第三步失敗數(shù)據(jù)庫(kù)里就會(huì)留存一條沒(méi)有步驟記錄的 runs 數(shù)據(jù)后續(xù)統(tǒng)計(jì)時(shí)會(huì)污染成功率。4.4 生成審查報(bào)告本地工作臺(tái)的最小審查形態(tài)可以是一份靜態(tài) HTML 報(bào)告或者一個(gè)本地 Web 服務(wù)。最簡(jiǎn)單方式是讓 CLI 在執(zhí)行完評(píng)測(cè)后輸出一個(gè)reports/report.html里面按用例列出每次運(yùn)行的成功狀態(tài)和軌跡摘要。!-- 生成報(bào)告的簡(jiǎn)化模板實(shí)際由代碼動(dòng)態(tài)渲染 -- ul li strongorder-001/strongcompleted details summary查看軌跡/summary p第 1 步模型調(diào)用輸入用戶消息輸出我需要查詢訂單。/p p第 2 步工具調(diào)用 query_order參數(shù) {orderId:20240315001}。/p p第 3 步工具返回 {status:shipped}。/p /details /li /ul報(bào)告不是給機(jī)器看的是給人審查的。所以每一條用例都要有展開(kāi)軌跡的入口并且標(biāo)記該用例使用的預(yù)期判斷方式。這個(gè)階段的報(bào)告可以很粗糙但它必須讓審查者能回答為什么判定成功或?yàn)槭裁磁卸ㄊ ?. 關(guān)鍵模塊設(shè)計(jì)指標(biāo)計(jì)算、軌跡審查和回歸對(duì)比5.1 指標(biāo)計(jì)算模塊指標(biāo)計(jì)算不應(yīng)該散落在插入數(shù)據(jù)的邏輯里而應(yīng)該獨(dú)立成一個(gè)純函數(shù)模塊。這樣歷史軌跡可以重新計(jì)算指標(biāo)不同指標(biāo)算法可以并行比較。// src/metrics.ts import { RunResult, Metrics } from ./types; export function computeMetrics(result: RunResult): Metrics { const steps result.steps; const toolCalls steps.filter((s) s.type tool_call); const toolSuccess toolCalls.filter((s) s.output !s.output.startsWith(ERROR)); let totalTokens 0; let totalLatency 0; for (const step of steps) { totalTokens (step.tokensIn ?? 0) (step.tokensOut ?? 0); totalLatency step.durationMs ?? 0; } return { success: result.status completed, taskScore: result.status completed ? 100 : 0, toolSuccessRate: toolCalls.length ? toolSuccess.length / toolCalls.length : 1, avgLatencyMs: steps.length ? totalLatency / steps.length : 0, totalTokens, steps: steps.length, }; }現(xiàn)在computeMetrics里的 success 只判斷狀態(tài)沒(méi)有做預(yù)期結(jié)果的匹配。真實(shí)實(shí)現(xiàn)里應(yīng)該在運(yùn)行器判斷完 expected 之后把匹配結(jié)果傳進(jìn)來(lái)。這一步留給你的實(shí)際項(xiàng)目擴(kuò)展關(guān)鍵是保持軌跡數(shù)據(jù)不隨指標(biāo)變化的原則。5.2 軌跡審查頁(yè)面軌跡審查頁(yè)面至少有三種視圖列表視圖按套件和用例列出所有運(yùn)行記錄綠綠紅紅一眼看出失敗集中在哪個(gè)用例。時(shí)間線視圖單個(gè)運(yùn)行記錄的步驟按時(shí)間展開(kāi)模型推理、工具調(diào)用、工具結(jié)果依次排列任何一步耗時(shí)異常都容易發(fā)現(xiàn)。對(duì)比視圖同一用例的新舊兩次運(yùn)行并排展示高亮模型推理差異、工具參數(shù)差異和結(jié)果差異。時(shí)間線視圖最容易暴露問(wèn)題。例如一個(gè) Agent 在工具調(diào)用失敗后反復(fù)重試時(shí)間線會(huì)連續(xù)出現(xiàn)五次相同的tool_call這比任何指標(biāo)都直觀。如果工具報(bào)錯(cuò)是 401說(shuō)明權(quán)限配置有問(wèn)題如果是參數(shù)校驗(yàn)失敗說(shuō)明 Agent 對(duì)參數(shù)理解有誤。審查頁(yè)面應(yīng)該讓這些信息第一眼可見(jiàn)而不是藏在日志里。5.3 回歸對(duì)比和門禁判斷回歸對(duì)比的核心是找出從好變壞的用例。工作臺(tái)可以把新舊兩次運(yùn)行的數(shù)據(jù)按case_id對(duì)齊生成一張差異表case_id舊版結(jié)果新版結(jié)果舊版步驟數(shù)新版步驟數(shù)差異判斷order-001successsuccess34步驟增加可接受order-002successfailed2-回歸必須修復(fù)order-003failedsuccess-3修復(fù)項(xiàng)確認(rèn)有效性這里的門禁可以簡(jiǎn)單到一句話如果存在任何success - failed的用例且該用例標(biāo)簽不在豁免列表中則本次評(píng)測(cè)不通過(guò)。這個(gè)規(guī)則應(yīng)該寫(xiě)進(jìn) CI 腳本防止新版 Agent 或者新提示詞在修復(fù) A 場(chǎng)景時(shí)弄壞 B 場(chǎng)景。6. 運(yùn)行驗(yàn)證從命令行到本地 Web 工作臺(tái)6.1 啟動(dòng)流程按以下順序操作可以跑通最小閉環(huán)# 1. 初始化項(xiàng)目并安裝依賴 npm install # 2. 初始化數(shù)據(jù)庫(kù) npm run init-db # 3. 將模型 API 密鑰寫(xiě)入本地環(huán)境變量 export MODEL_API_KEYyour_key_here # 4. 運(yùn)行評(píng)測(cè)集 npm run eval -- --suite suites/order-basic.yaml # 5. 啟動(dòng)本地審查服務(wù) npm run review這里要特別提醒API 密鑰不要寫(xiě)進(jìn)評(píng)測(cè)集 YAML也不要提交到 git 倉(cāng)庫(kù)。評(píng)測(cè)集是團(tuán)隊(duì)共享的版本化文件密鑰應(yīng)該通過(guò)環(huán)境變量或本地的.env.local注入并且這個(gè)文件必須加入.gitignore。6.2 預(yù)期結(jié)果和驗(yàn)證方式第一次運(yùn)行結(jié)束后你應(yīng)該能看到類似下面的輸出suite: order-basic cases: 2 passed: 1 failed: 1 success rate: 50.0% tool success rate: 100.0% avg latency: 820ms total tokens: 1840 report: reports/report.html但這只是第一步。要確認(rèn)評(píng)測(cè)真的有效至少還需要驗(yàn)證三件事失敗的用例點(diǎn)擊軌跡后能看到具體的失敗步驟而不是只看到一個(gè) failed 狀態(tài)。把同一個(gè)用例故意改成錯(cuò)誤預(yù)期運(yùn)行結(jié)果應(yīng)該從成功變失敗證明預(yù)期判斷邏輯有效。連續(xù)運(yùn)行三次同一評(píng)測(cè)集觀察成功率波動(dòng)范圍。如果波動(dòng)超過(guò) 20 個(gè)百分點(diǎn)說(shuō)明用例設(shè)計(jì)或評(píng)測(cè)環(huán)境本身不穩(wěn)定需要先解決穩(wěn)定性問(wèn)題。6.3 學(xué)習(xí)環(huán)境和生產(chǎn)環(huán)境的區(qū)別上面這套流程適合在本地快速驗(yàn)證想法。進(jìn)入團(tuán)隊(duì)協(xié)作或生產(chǎn)環(huán)境后需要額外補(bǔ)上幾塊項(xiàng)目學(xué)習(xí)環(huán)境生產(chǎn)環(huán)境存儲(chǔ)本地 SQLite 文件SQLite 文件加入版本管理或備份機(jī)制評(píng)測(cè)集手工編輯 YAML評(píng)測(cè)集變更走 MR 流程記錄版本運(yùn)行結(jié)果本地命令行在 CI 中運(yùn)行結(jié)果上傳為構(gòu)建產(chǎn)物模型配置環(huán)境變量手動(dòng)指定固定模型版本、采樣參數(shù)寫(xiě)入配置快照人工審查本地 HTML 報(bào)告審查結(jié)果可注釋、可標(biāo)記、可追蹤數(shù)據(jù)備份不必須定期備份數(shù)據(jù)庫(kù)設(shè)置保留策略生產(chǎn)環(huán)境最關(guān)鍵的差異化動(dòng)作是可追溯。一次發(fā)布對(duì)應(yīng)的評(píng)測(cè)結(jié)果必須能關(guān)聯(lián)到具體的代碼版本、評(píng)測(cè)集版本、模型版本和運(yùn)行時(shí)間。缺失任何一個(gè)出現(xiàn)線上問(wèn)題時(shí)都無(wú)法回查。7. 常見(jiàn)問(wèn)題與排查鏈路7.1 評(píng)測(cè)任務(wù)大面積失敗現(xiàn)象同一個(gè)評(píng)測(cè)集大部分用例都返回 failed。排查順序先看錯(cuò)誤信息。如果所有失敗都報(bào) 401、403通常是 API Key 無(wú)效或沒(méi)有對(duì)應(yīng)模型權(quán)限。再看超時(shí)配置。如果失敗集中在某個(gè)工具調(diào)用步驟且每條軌跡都在同一工具處中斷優(yōu)先懷疑工具本身報(bào)錯(cuò)或網(wǎng)絡(luò)不通。然后看預(yù)期判斷方式。如果失敗用例的軌跡正常完成但被判定失敗檢查expected.type是否和實(shí)際輸出匹配。例如模型回復(fù)您的訂單已發(fā)出而預(yù)期寫(xiě)死了已發(fā)貨就會(huì)誤判。最后檢查評(píng)測(cè)集本身。如果用例輸入格式和 Agent 訓(xùn)練數(shù)據(jù)差異過(guò)大失敗是正常的需要調(diào)整用例設(shè)計(jì)。對(duì)應(yīng)處理建議問(wèn)題現(xiàn)象常見(jiàn)原因檢查方式處理建議全部用例 401API Key 無(wú)效或無(wú)權(quán)限查看運(yùn)行器日志的第一個(gè)錯(cuò)誤重新配置環(huán)境變量并確認(rèn)模型白名單全部用例超時(shí)模型或工具響應(yīng)過(guò)慢查看軌跡中耗時(shí)最長(zhǎng)的步驟增加單步超時(shí)時(shí)間或排查工具性能完成但判失敗expected 寫(xiě)得太死打印實(shí)際輸出對(duì)比預(yù)期配置改用 contains 或人工審查判斷結(jié)果時(shí)好時(shí)壞采樣參數(shù)或環(huán)境不穩(wěn)定連續(xù)運(yùn)行 5 次統(tǒng)計(jì)分布固定溫度記錄模型版本擴(kuò)大樣本量7.2 軌跡丟失或結(jié)果對(duì)不上現(xiàn)象metrics 表里有運(yùn)行記錄但 steps 表為空或者審查頁(yè)面顯示的成功數(shù)和 CLI 輸出不一致。排查鏈路檢查寫(xiě)入順序。如果insertMetric不在insertRun和insertStep的同一個(gè)事務(wù)里先寫(xiě)指標(biāo)后寫(xiě)步驟就容易出現(xiàn)只有指標(biāo)沒(méi)有軌跡的半截?cái)?shù)據(jù)。檢查異常處理。運(yùn)行器 catch 到異常后是否仍然執(zhí)行了持久化邏輯。如果異常發(fā)生在步驟還沒(méi)有回調(diào)時(shí)直接跳到失敗分支軌跡為空是正常的但如果你期望保留異常發(fā)生前已經(jīng)記錄的部分步驟需要在 catch 里顯式維護(hù)steps數(shù)組。檢查統(tǒng)計(jì)口徑。CLI 輸出的 success rate 是基于 runs 表統(tǒng)計(jì)還是基于 metrics 表統(tǒng)計(jì)。兩個(gè)來(lái)源結(jié)果不一致時(shí)說(shuō)明代碼中有第二個(gè)統(tǒng)計(jì)入口應(yīng)統(tǒng)一成一個(gè)函數(shù)。7.3 指標(biāo)偏高或偏低的可信度問(wèn)題現(xiàn)象本地跑成功率 90%但線上表現(xiàn)明顯更差。原因通常不在評(píng)測(cè)代碼而在評(píng)測(cè)設(shè)計(jì)評(píng)測(cè)集和調(diào)優(yōu)數(shù)據(jù)重疊。如果評(píng)測(cè)用例來(lái)自 Agent 調(diào)優(yōu)時(shí)見(jiàn)過(guò)的數(shù)據(jù)結(jié)果虛高屬于數(shù)據(jù)泄漏。樣本量太小。10 個(gè)用例只跑一次成功率 90% 意味著只有一個(gè) fail置信度非常低。評(píng)測(cè)集難度失衡。大量簡(jiǎn)單用例撐高整體成功率掩蓋了困難場(chǎng)景的問(wèn)題。處理方式是分層看指標(biāo)按 tag 統(tǒng)計(jì)成功率單獨(dú)篩出edge-case類用例的結(jié)果而不是只看總體成功率。同時(shí)在報(bào)告中展示每個(gè)用例的單次結(jié)果不合并成一個(gè)數(shù)字。7.4 本地環(huán)境與 CI 結(jié)果不一致現(xiàn)象本地運(yùn)行全部通過(guò)CI 里同一評(píng)測(cè)集大量失敗。這是 Agent 評(píng)測(cè)最常見(jiàn)的環(huán)境問(wèn)題之一。排查順序?qū)Ρ饶P桶姹?。CI 里是否用了不同的 model 名稱或 default 版本導(dǎo)致推理行為不一致。對(duì)比環(huán)境變量。CI 里是否缺少M(fèi)ODEL_API_KEY或使用了不同 key 導(dǎo)致速率限制。對(duì)比網(wǎng)絡(luò)和依賴。CI 是否能訪問(wèn)模型 APIpackage-lock.json是否提交依賴版本是否已鎖定。對(duì)比并發(fā)設(shè)置。本地順序執(zhí)行和 CI 并發(fā)執(zhí)行可能觸發(fā)速率限制或超時(shí)失敗位置通常分布在tool_call步驟。預(yù)防做法是在評(píng)測(cè)配置中記錄環(huán)境指紋模型版本、溫度、top_p、依賴版本、Node 版本、關(guān)鍵環(huán)境變量的 hash。評(píng)測(cè)報(bào)告第一頁(yè)就展示這些信息本地和 CI 不一致時(shí)先比對(duì)指紋。8. 評(píng)測(cè)工作臺(tái)的最佳實(shí)踐和擴(kuò)展方向8.1 設(shè)計(jì)評(píng)測(cè)集的檢查清單一個(gè)評(píng)測(cè)集在合并進(jìn)工程之前應(yīng)該逐條確認(rèn)每個(gè)用例是否描述了一個(gè)完整用戶場(chǎng)景而不是一個(gè)孤立的模型指令。預(yù)期結(jié)果是否站在用戶可接受信息的角度描述而不是模型逐字輸出。是否覆蓋 happy path、edge case、異常輸入、權(quán)限不足、工具失敗五類基礎(chǔ)場(chǎng)景。用例輸入中是否包含真實(shí)業(yè)務(wù)敏感數(shù)據(jù)如果是是否做了脫敏。每個(gè)用例是否有明確的維護(hù)人需求變更時(shí)由誰(shuí)更新評(píng)測(cè)集。評(píng)測(cè)集是否在獨(dú)立目錄下版本化管理和代碼變更同步提交。8.2 發(fā)布前評(píng)測(cè)檢查清單Agent 版本發(fā)布前建議依次確認(rèn)評(píng)測(cè)集版本已固定和本次發(fā)布意圖匹配。在固定模型版本和采樣參數(shù)下完成至少三輪完整評(píng)測(cè)。新舊版本回歸對(duì)比中不存在非豁免的success - failed用例。失敗的用例都有軌跡可供人工審查且每一條失敗原因已經(jīng)被歸類。指標(biāo)統(tǒng)計(jì)同時(shí)包含結(jié)果指標(biāo)和過(guò)程指標(biāo)避免只匯報(bào)成功率。評(píng)測(cè)數(shù)據(jù)已備份報(bào)告已歸檔對(duì)應(yīng)代碼 commit 可追溯。8.3 擴(kuò)展方向從最小工作臺(tái)繼續(xù)往下走有三個(gè)值得投入的方向。第一個(gè)是引入更豐富的判斷方式。除了字符串匹配可以增加 LLM-as-judge讓一個(gè)獨(dú)立模型按評(píng)分標(biāo)準(zhǔn)判斷結(jié)果質(zhì)量并將評(píng)判斷模型的結(jié)果也作為一條軌跡保存方便進(jìn)一步審查。第二個(gè)是支持評(píng)測(cè)集和數(shù)據(jù)集的版本化。用 Git 管理 YAML 評(píng)測(cè)集是第一步第二步是給每個(gè)用例增加expected的變更歷史讓為什么這個(gè)用例被改弱了有據(jù)可查。第三個(gè)是把評(píng)測(cè)工作臺(tái)接入日常開(kāi)發(fā)流。本地運(yùn)行只是調(diào)試手段真正能防止 Agent 退化的機(jī)制是 CI 門禁每次提示詞變更或 Agent 邏輯變更都自動(dòng)觸發(fā)一輪回歸評(píng)測(cè)把差別結(jié)果直接貼到 MR 里。做到這一步評(píng)測(cè)工作臺(tái)就不再是一個(gè)輔助工具而是 Agent 工程化質(zhì)量體系的一部分。Agent 評(píng)測(cè)的本質(zhì)是給看起來(lái)智能的系統(tǒng)建立可驗(yàn)證的行為邊界。Agent Review Studio 這類 local-first 工作臺(tái)提供了一個(gè)正確的方向讓評(píng)測(cè)數(shù)據(jù)留在本地讓軌跡成為證據(jù)讓差異可以被看見(jiàn)。先把最小閉環(huán)跑起來(lái)再用指標(biāo)、軌跡和回歸對(duì)比慢慢逼近真實(shí)業(yè)務(wù)的質(zhì)量要求這才是評(píng)測(cè)體系能長(zhǎng)期運(yùn)轉(zhuǎn)的方式。