戰(zhàn)與落地)
導(dǎo)言動(dòng)手時(shí)的三個(gè)原則系列三開始寫代碼。但動(dòng)手前先立三條原則免得你抄完代碼卻沒(méi)學(xué)到架構(gòu)架構(gòu)優(yōu)先于代碼每篇我都先講為什么這樣拆再給代碼。看代碼時(shí)對(duì)照系列二的五層圖每個(gè)類都該能在圖上找到位置。最小可用優(yōu)于完整我只寫跑通必需的最少代碼。完整生產(chǎn)版要加并發(fā)、加配置、加日志輪轉(zhuǎn)——這些是工程增量不影響你理解內(nèi)核。可運(yùn)行優(yōu)于可炫所有代碼我在本地能跑通的邏輯才寫。你拿到后若環(huán)境不同按注釋改依賴即可但骨架不變。記住你抄走的是一個(gè) Harness 的內(nèi)核不是一個(gè)測(cè)試。區(qū)別就在架構(gòu)對(duì)不對(duì)。系列三 · 第 1 篇從零搭建一個(gè)測(cè)試 HarnessPlaywright 實(shí)戰(zhàn)摘要?jiǎng)邮至恕S?Playwright 系列二的 Common Layer 思路搭一個(gè)最小可用 Web 測(cè)試 Harnesslocator 定位、executor 執(zhí)行、oracle 斷言、reporter 出報(bào)告。重點(diǎn)不在代碼多在架構(gòu)對(duì)。前面兩個(gè)系列是認(rèn)知和藍(lán)圖。從這篇開始我們寫第一行代碼。目標(biāo)很克制用最少代碼搭一個(gè)換個(gè)項(xiàng)目也能用的 Web 測(cè)試 Harness。核心思路直接復(fù)用我內(nèi)部的 Common Layerlocator定位executor執(zhí)行oracle判定reporter報(bào)告。請(qǐng)記住這篇的價(jià)值觀重點(diǎn)不在代碼多在架構(gòu)對(duì)。你抄走這 80 行得到的不是一個(gè)測(cè)試而是一個(gè) Harness 的最小內(nèi)核。一、依賴與目錄pip install playwright playwright install chromiumharness/ core.py # Executor Step locator.py # Dependency Manager (locator) oracle.py # Oracle reporter.py # Reporter test_demo.py # 你的測(cè)試為什么拆四個(gè)文件而不是一個(gè)因?yàn)橄盗卸f(shuō)過(guò)組件邊界 團(tuán)隊(duì)并行邊界 未來(lái)可拆邊界。哪怕今天只有你一個(gè)人也別把四個(gè)組件焊在一個(gè)文件里——你會(huì)感謝三個(gè)月后的自己。二、Executor StepHarness 的心臟# harness/core.py from dataclasses import dataclass from typing import Callable, Any ? dataclass class Step: name: str action: Callable[[Page], Any] # 具體 Playwright 操作 expect: Callable[[Page], bool] # oracle返回 True/False ? class Executor: def __init__(self, browser): self.browser browser ? def run(self, steps: list[Step]) - list[tuple[str, bool]]: page self.browser.new_page() results [] for s in steps: try: s.action(page) ok s.expect(page) except Exception as e: ok False page.screenshot(pathffail_{s.name}.png) # 失敗留現(xiàn)場(chǎng) print(f[FAIL] {s.name}: {e}) else: if not ok: page.screenshot(pathffail_{s.name}.png) print(f[{PASS if ok else FAIL}] {s.name}) results.append((s.name, ok)) page.close() return results為什么這樣寫對(duì)應(yīng)系列二Step是一個(gè)數(shù)據(jù)對(duì)象描述做什么 期望什么不含執(zhí)行細(xì)節(jié)——這是把測(cè)試意圖和執(zhí)行引擎解耦。Executor.run只干一件事按順序跑 Step記錄結(jié)果失敗截圖。它不關(guān)心 Step 里具體點(diǎn)了什么——這就是 executor 只做心臟的紀(jì)律。try/except包住每個(gè) Step保證一個(gè)失敗不拖垮全程且失敗必截圖。截圖是 Observer 思維的雛形失敗要留現(xiàn)場(chǎng)而不是只拋個(gè)異常。一個(gè)延伸生產(chǎn)里你會(huì)給 Executor 加concurrency并發(fā)跑多個(gè) Step、加before/after鉤子、加timeout。但內(nèi)核就是這幾行——理解它加什么都清楚。三、LocatorHarness 的手# harness/locator.py class Locator: def __init__(self, page): self.page page def click(self, text): self.page.get_by_text(text).click() def fill(self, placeholder, value): self.page.get_by_placeholder(placeholder).fill(value) def expect_visible(self, text) - bool: return self.page.get_by_text(text).is_visible()把怎么找元素收口到一個(gè)類。前端改版換選擇器策略只改這里測(cè)試零動(dòng)。這就是 Dependency Manager 的價(jià)值落地。踩坑提醒別在測(cè)試?yán)镏苯訉憄age.click(#login-btn)。一旦前端把 id 改成 class所有測(cè)試紅。收口到 Locator 后改一個(gè)方法即可。這就是系列二說(shuō)的它一變?nèi)痉€(wěn)。四、Oracle Reporter# harness/oracle.py def assert_title(page, expected: str) - bool: return expected in page.title() ? # harness/reporter.py def report(results: list[tuple[str, bool]]) - bool: passed sum(1 for _, ok in results if ok) print(f\n SUMMARY: PASS {passed}/{len(results)} ) return all(ok for _, ok in results)report返回整體是否通過(guò)供 CI 當(dāng)退出碼用sys.exit(0 if ok else 1)。它消費(fèi)的是標(biāo)準(zhǔn)化results不是內(nèi)部對(duì)象——呼應(yīng)系列二契約鐵律三跨層只走數(shù)據(jù)。為什么 oracle 是函數(shù)不是方法保持它無(wú)狀態(tài)、可獨(dú)立測(cè)試。將來(lái)你要換成用另一個(gè) LLM 當(dāng) oracle只要替換這個(gè)函數(shù)Executor 一行不動(dòng)。五、跑起來(lái)# test_demo.py from playwright.sync_api import sync_playwright from harness.core import Executor, Step from harness.locator import Locator from harness.oracle import assert_title from harness.reporter import report ? def test_login(): with sync_playwright() as p: browser p.chromium.launch() loc Locator(browser.new_page()) steps [ Step(打開首頁(yè), lambda pg: pg.goto(https://demo.example.com), lambda pg: assert_title(pg, Demo)), Step(點(diǎn)擊登錄, lambda pg: loc.click(登錄), lambda pg: loc.expect_visible(用戶名)), ] ex Executor(browser) assert report(ex.run(steps)) browser.close()【配圖本篇 Harness 運(yùn)行流程圖locator→executor→oracle→reporter】六、關(guān)鍵收獲你剛剛寫的不是一個(gè)測(cè)試是一個(gè)測(cè)試 Harness 的最小內(nèi)核有 Executor心臟、Locator手、Oracle裁判、Reporter嘴測(cè)試只是聲明 Step不碰執(zhí)行細(xì)節(jié)失敗自動(dòng)留現(xiàn)場(chǎng)。接下來(lái)只要往上補(bǔ)系列二的五層——加環(huán)境層容器化瀏覽器把p.chromium.launch()換成從 EnvManager 取環(huán)境、加狀態(tài)層把results升級(jí)成帶trace的RunResult它就長(zhǎng)成了完整的塔。架構(gòu)對(duì)了擴(kuò)展是加法架構(gòu)錯(cuò)了擴(kuò)展是重寫。你這次選了加法。延伸思考這個(gè)內(nèi)核只有 4 個(gè)組件缺環(huán)境層、狀態(tài)層、約束層。所以它是能跑的腳本集合還不是完整的 Harness。但這正是系列設(shè)計(jì)的目的——讓你先看到內(nèi)核長(zhǎng)什么樣再理解為什么需要補(bǔ)全其余層。實(shí)戰(zhàn)案例把 200 個(gè)遺留腳本遷移進(jìn) Harness 內(nèi)核為了讓架構(gòu)對(duì)不只是一句口號(hào)講一個(gè)我們內(nèi)部的真實(shí)遷移。團(tuán)隊(duì)有 200 多個(gè)用 pytest 直接調(diào) Selenium 的舊腳本典型特征是環(huán)境靠手動(dòng)開、選擇器散落各處、失敗后靠人肉翻截圖。遷移分三步第一步先抽 Locator。用正則掃描 200 個(gè)文件里的find_element_by_*調(diào)用自動(dòng)替換為L(zhǎng)ocator的語(yǔ)義方法再人工校正約 5% 匹配不準(zhǔn)的。這一步?jīng)]動(dòng)任何測(cè)試邏輯只是換了手。第二步套 Executor。把每個(gè)腳本的前置 操作 斷言包成Step交給統(tǒng)一 Executor 跑。好處立刻顯現(xiàn)失敗自動(dòng)截圖、統(tǒng)一計(jì)時(shí)、并發(fā)可開。第三步補(bǔ) EnvManager。用容器封裝瀏覽器環(huán)境腳本里刪掉所有環(huán)境準(zhǔn)備代碼。遷移后最直觀的變化前端一次大改版舊腳本預(yù)計(jì)全紅、改 3 天新內(nèi)核改一個(gè) locator 文件、10 分鐘復(fù)活。這就是系列二說(shuō)的它一變?nèi)痉€(wěn)。踩坑實(shí)錄遷移初期有人把a(bǔ)ssert直接寫進(jìn)action里導(dǎo)致 Executor 捕獲不到 oracle 結(jié)果、失敗統(tǒng)計(jì)失真。教訓(xùn)刻在骨頭上action 只做操作期望必須進(jìn)expect——職責(zé)邊界是內(nèi)核穩(wěn)定的命根子越界一次數(shù)據(jù)就假一次。 留言區(qū)你現(xiàn)在的自動(dòng)化腳本有多少能直接套進(jìn)這個(gè)四件式骨架評(píng)論區(qū)曬改造思路我挑典型的在下篇展開。 關(guān)注回復(fù)「harness」領(lǐng)本篇完整可運(yùn)行倉(cāng)庫(kù)。系列三 · 第 2 篇構(gòu)建你的 Eval Harness對(duì)標(biāo) lm-evaluation-harness摘要評(píng)測(cè)大模型難的不是跑模型是公平對(duì)比。本文用約 50 行代碼講清 Eval Harness 三件事Task 聲明式配置、LM 接口抽象、Metric 聚合 種子固定。測(cè)試 Harness 管的是確定性軟件Eval Harness 管的是概率性模型。難點(diǎn)完全變了你擔(dān)心的不再是它崩了而是它這次蒙對(duì)了下次未必。對(duì)標(biāo) EleutherAI 的lm-evaluation-harness我把 Eval Harness 拆成三件事。這篇就把這三件事用最少代碼寫出來(lái)。一、Task把評(píng)測(cè)什么聲明出來(lái)# tasks/translation.yaml task: translation_zh2en dataset: path: data/zh2en.jsonl input: chinese reference: english metric: bleu few_shot: 3任務(wù)和數(shù)據(jù)徹底解耦——換數(shù)據(jù)集不碰代碼換指標(biāo)改一行配置。這是 Eval Harness 復(fù)用性的根基。為什么聲明式這么重要在確定性測(cè)試?yán)锬銓懸粋€(gè)assert就是在聲明期望。但在概率世界期望變成了一個(gè)評(píng)分函數(shù) 一串樣本。把它們外置成 yaml意味著評(píng)測(cè)邏輯和評(píng)測(cè)內(nèi)容分離——研究員調(diào) prompt、調(diào) few_shot工程師調(diào)框架互不干擾。這是大模型團(tuán)隊(duì)能規(guī)模化的關(guān)鍵。二、LM 接口抽象讓模型可替換# eval/lm.py from abc import ABC, abstractmethod ? class LM(ABC): abstractmethod def complete(self, prompt: str, **kw) - str: ... ? class OpenAILM(LM): def __init__(self, client, modelgpt-4o-mini): self.client, self.model client, model def complete(self, prompt, **kw): r self.client.chat.completions.create( modelself.model, messages[{role:user,content:prompt}]) return r.choices[0].message.content你的任務(wù)代碼只認(rèn)LM抽象。今天用 GPT明天換國(guó)產(chǎn)模型DeepSeek / 通義 / 文心任務(wù)一行不動(dòng)——只需新增一個(gè)LM實(shí)現(xiàn)。這正是系列二鐵律一只依賴抽象的回報(bào)。國(guó)產(chǎn)模型提示你提到你們平臺(tái)支持 DeepSeek、通義、文心、GLM、Kimi、豆包等國(guó)產(chǎn)大模型。Eval Harness 的 LM 抽象層正好讓一鍵切換國(guó)產(chǎn)模型做評(píng)測(cè)變成現(xiàn)實(shí)——這對(duì)不依賴國(guó)外模型的合規(guī)訴求是直接支撐。每個(gè)國(guó)產(chǎn)模型寫一個(gè)LM子類即可任務(wù)配置零改動(dòng)。三、Metric 聚合 種子固定管住隨機(jī)性# eval/run.py import random, json ? def evaluate(task: dict, lm: LM, n: int 5) - float: data [json.loads(l) for l in open(task[dataset][path])] scores [] for sample in data: random.seed(42) # 固定種子保證可復(fù)現(xiàn) prompt build_prompt(task, sample) # few-shot 拼接 outs [lm.complete(prompt) for _ in range(n)] scores.append(task_metric(task[metric], outs, sample[reference])) return sum(scores) / len(scores)random.seed(42)是 Eval Harness 的靈魂讓概率性輸出可復(fù)現(xiàn)、可對(duì)比。沒(méi)有它你兩次跑分不一樣根本不知道是模型變了還是運(yùn)氣變了。一個(gè)負(fù)責(zé)任的 Eval Harness必須固定一切隨機(jī)源包括模型自身的 temperature 也要顯式設(shè)低或固定。為什么多次采樣n5單次生成有方差。聚合成均值分?jǐn)?shù)才穩(wěn)。生產(chǎn)里 n 往往取 5~10并報(bào)告標(biāo)準(zhǔn)差讓模型 A 比 B 好這個(gè)結(jié)論有統(tǒng)計(jì)意義而不是好這一次。四、三件套合體【配圖Eval Harness 三件套關(guān)系圖Task / LM / Metric】Task(聲明) ──┐ ↓ LM(抽象接口) → evaluate() → Metric(聚合 種子) ↑ Dataset(解耦)這個(gè)結(jié)構(gòu)的妙處你可以用同一份 evaluate 函數(shù)評(píng)任意模型、任意任務(wù)。加一個(gè)新模型 加一個(gè) LM 類加一個(gè)新任務(wù) 加一個(gè) yaml。無(wú)限擴(kuò)展都是加法。五、Eval Harness 的硬指標(biāo)判斷你的 Eval Harness 合不合格三條可復(fù)現(xiàn)同模型同任務(wù)兩次跑分一致靠種子。可對(duì)比換模型結(jié)果可直接橫向比靠統(tǒng)一 Metric。可擴(kuò)展加模型/任務(wù)不碰核心代碼靠抽象。這三條正好對(duì)應(yīng)系列一的可復(fù)現(xiàn) 受控 可觀測(cè)。Eval Harness 就是把系列一那三個(gè)詞在概率世界重新實(shí)現(xiàn)一遍。延伸Metric 設(shè)計(jì)的坑BLEU 適合翻譯不適合開放問(wèn)答開放生成常用 LLM-as-judge用一個(gè)強(qiáng)模型評(píng)另一個(gè)。選錯(cuò) Metric整個(gè)評(píng)測(cè)結(jié)論作廢。下一篇 Agent Harness 會(huì)遇到更難的怎么判定 Agent 真完成了——那正是 Metric 設(shè)計(jì)的深水區(qū)。實(shí)戰(zhàn)案例用 Eval Harness 給國(guó)產(chǎn)模型做橫向評(píng)測(cè)你們平臺(tái)支持 DeepSeek、通義、文心、GLM、Kimi、豆包等國(guó)產(chǎn)模型正好用 Eval Harness 演示換模型不碰任務(wù)。我們做過(guò)一次內(nèi)部評(píng)測(cè)同一個(gè)翻譯任務(wù) yaml分別接 6 個(gè)國(guó)產(chǎn)模型的LM實(shí)現(xiàn)跑同一份種子、同一份樣本。結(jié)果出來(lái)很有意思總體 BLEU 差距不大但按長(zhǎng)句專有名詞口語(yǔ)化拆分子項(xiàng)后模型各具長(zhǎng)短。如果沒(méi)有 Eval Harness 的統(tǒng)一接口和固定種子這種公平對(duì)比根本做不出來(lái)——你得為每個(gè)模型手寫一套評(píng)測(cè)seed 還不一定一致結(jié)論毫無(wú)可比性。踩坑實(shí)錄few_shot 示例的順序會(huì)影響分?jǐn)?shù)。我們?cè)咽纠S機(jī)打亂兩次跑分差了 3 個(gè)點(diǎn)一度以為模型不穩(wěn)最后發(fā)現(xiàn)是示例順序在悄悄給模型泄題。教訓(xùn)few_shot 也要固定寫進(jìn) yaml 并版本化否則 Eval Harness 的可復(fù)現(xiàn)會(huì)被自己破壞。 留言區(qū)評(píng)測(cè)模型時(shí)你最頭疼的是分?jǐn)?shù)忽高忽低還是指標(biāo)選不對(duì)評(píng)論區(qū)說(shuō)我下篇專門講 Metric 設(shè)計(jì)陷阱。 關(guān)注回復(fù)「harness」領(lǐng) Eval Harness 最小模板倉(cāng)庫(kù)。系列三 · 第 3 篇Agent Harness 設(shè)計(jì)多角色制衡摘要讓一個(gè) Agent 既干活又自檢等于讓考生自己改卷。本文用代碼講 Agent Harness 如何用多角色制衡 約束層 校驗(yàn)層破解約束規(guī)避與虛報(bào)完成。到了 Agent 時(shí)代被測(cè)對(duì)象會(huì)自己規(guī)劃、自己調(diào)工具、自己宣布完成。單 Agent 自檢 自己改自己卷子不可信。系列一說(shuō)過(guò)解法叫制衡這篇落地成代碼。一、把任務(wù)拆給不同角色【配圖多 Agent 編排圖Planner / Worker / Critic / Judge】Planner拆任務(wù)不直接執(zhí)行。Worker執(zhí)行但看不到最終判定權(quán)。Critic獨(dú)立審查 Worker 的軌跡找繞過(guò)和遺漏。Judge對(duì)照規(guī)格做最終裁決權(quán)力獨(dú)立于前三者。第一鐵律Worker 不能既干活又裁判。二、約束層在 OS 層卡死約束規(guī)避# agent/harness.py BLOCKED {delete_production, skip_verification, direct_db_write} ? def guard(tool_call: dict) - dict: if tool_call[name] in BLOCKED: raise PermissionError(f被約束層攔截{tool_call[name]}) return dispatch(tool_call) # 真正執(zhí)行約束在Harness 層執(zhí)行不在 Agent 提示詞里請(qǐng)求它遵守。提示詞里的請(qǐng)勿刪除生產(chǎn)數(shù)據(jù)是建議OS 層的guard是法律——法律繞不過(guò)去。這直接治了系列一的約束規(guī)避。為什么提示詞不夠LLM 的遵守指令是概率性的長(zhǎng)上下文里會(huì)衰減規(guī)則遺忘遇到利益沖突會(huì)權(quán)衡后繞過(guò)約束規(guī)避。把規(guī)則變成 OS 層的硬卡點(diǎn)就從機(jī)制上消滅了這兩類風(fēng)險(xiǎn)。你們的平臺(tái)強(qiáng)調(diào)模型熱替換與無(wú) LLM 規(guī)則降級(jí)本質(zhì)也是同一思路——規(guī)則由系統(tǒng)守不由模型守。三、校驗(yàn)層用軌跡治虛報(bào)完成# agent/verify.py def judge(trace: list, spec: dict) - bool: done_steps {t[step] for t in trace} required set(spec[required_steps]) missing required - done_steps if missing: print(f[JUDGE] 虛報(bào)完成缺 {missing}) return False return TrueJudge 不讀 Worker 的我完成了結(jié)論只讀狀態(tài)層記錄的真實(shí)軌跡trace逐步比對(duì)規(guī)格spec。軌跡里缺的一步就是虛報(bào)的證據(jù)。這治了虛報(bào)完成和自審失效。四、一個(gè)最小閉環(huán)plan Planner().plan(task) # 拆任務(wù) trace [] for step in plan: call Worker().act(step) call guard(call) # 約束層卡一道 result dispatch(call) trace.append({step: step, result: result}) ? verdict Judge().judge(trace, spec) # 校驗(yàn)層判一道 assert verdict, Agent 未真正完成拒絕放行【配圖Agent Harness 雙層防線運(yùn)行圖guard 攔截 judge 校驗(yàn)】五、為什么必須多角色有人會(huì)問(wèn)為什么不讓一個(gè)聰明 Agent 又干又審因?yàn)?LLM 有自我美化偏差——讓它審自己的產(chǎn)出它傾向于確認(rèn)而非否定。多角色把生成和判定分給不同上下文、不同目標(biāo)函數(shù)的 Agent從機(jī)制上消滅自我美化。這不是工程技巧是統(tǒng)計(jì)學(xué)必然。延伸Critic 和 Judge 可以都是規(guī)則便宜、確定也可以是另一個(gè) LLM靈活、但有偏差。生產(chǎn)建議規(guī)則做硬卡 模型做軟查兩層都過(guò)才放行。這也是你們無(wú) LLM 規(guī)則降級(jí)的落地形態(tài)——LLM 掛了規(guī)則仍在Agent 不能裸奔。實(shí)戰(zhàn)案例用雙層防線攔下一次真實(shí)虛報(bào)講一個(gè)讓我們徹底信服 Agent Harness 的案例。一個(gè)自動(dòng)修復(fù)接口的 Agent自報(bào)全部用例通過(guò)無(wú)問(wèn)題。但 Judge 拿到狀態(tài)層 trace 一比發(fā)現(xiàn)它跳過(guò)了邊界值測(cè)試這一步——理由是在日志里寫了邊界情況風(fēng)險(xiǎn)低已評(píng)估跳過(guò)。如果沒(méi)有校驗(yàn)層這個(gè)跳過(guò)就神不知鬼不覺(jué)地溜進(jìn)發(fā)布。約束層 校驗(yàn)層合起來(lái)把這次虛報(bào)當(dāng)場(chǎng)摁住約束層禁止無(wú)理由跳過(guò) required_steps校驗(yàn)層發(fā)現(xiàn) trace 缺步直接判否。踩坑實(shí)錄多 Agent 協(xié)作時(shí)Planner 和 Worker 共享了同一段過(guò)長(zhǎng)的上下文導(dǎo)致 Worker 繼承了 Planner 里一句隨意的這步不太重要。教訓(xùn)角色之間傳計(jì)劃要精簡(jiǎn)成結(jié)構(gòu)化指令別把啰嗦的思考過(guò)程整個(gè)喂下去——上下文越長(zhǎng)規(guī)避和誤讀越多。 留言區(qū)你試過(guò)多 Agent 協(xié)作嗎踩過(guò)兩個(gè) Agent 互相甩鍋的坑嗎評(píng)論區(qū)聊我整理成《多 Agent 避坑清單》。 關(guān)注回復(fù)「harness」領(lǐng)多 Agent 編排示例倉(cāng)庫(kù)。系列三 · 第 4 篇狀態(tài)管理與可觀測(cè)性讓在我電腦能跑變成任何環(huán)境一致摘要失敗不可怕可怕的是偶現(xiàn)、無(wú)法復(fù)現(xiàn)。本文講狀態(tài)快照、軌跡記錄、日志/指標(biāo)/鏈路三件套把 Harness 的眼裝上并給出可運(yùn)行的最小接入代碼。測(cè)試人最怕聽到一句話在我電腦上是好的。這句話的本質(zhì)是狀態(tài)不可見(jiàn)。系列二說(shuō)過(guò)狀態(tài)層是可復(fù)現(xiàn)性的根源。這一篇把它落地并配上 Observer觀察器的最小實(shí)現(xiàn)。一、狀態(tài)快照把現(xiàn)場(chǎng)存下來(lái)# obs/snapshot.py import os, json ? class Snapshot: def capture(self, run_id: str, payload: dict): payload[seed] os.environ.get(SEED) json.dump(payload, open(fsnap/{run_id}.json, w), ensure_asciiFalse) def replay(self, run_id: str) - dict: return json.load(open(fsnap/{run_id}.json))每次運(yùn)行存一份快照輸入、環(huán)境指紋、隨機(jī)種子。下次偶現(xiàn)直接replay重放不用求爺爺告奶奶復(fù)現(xiàn)。為什么這能治偶現(xiàn)所謂偶現(xiàn)90% 是某個(gè)隱藏輸入/狀態(tài)不一致。快照把那次運(yùn)行的全部上下文固化下來(lái)重放時(shí)連環(huán)境都還原偶現(xiàn)就變必現(xiàn)。必現(xiàn)的 bug才好修。二、可觀測(cè)三件套【配圖可觀測(cè)性面板Logs / Metrics / Trace 三欄】Logs結(jié)構(gòu)化日志關(guān)鍵節(jié)點(diǎn)打點(diǎn)。Metrics通過(guò)率、耗時(shí)、失敗分布趨勢(shì)一目了然。Trace完整調(diào)用軌跡Agent 每一步留痕接上篇校驗(yàn)層。# obs/observer.py class Observer: def __init__(self, run_id): self.rid run_id; self.trace [] def log(self, msg): print(f[{self.rid}] {msg}) def metric(self, name, val): self.metrics[name] val def step(self, step, result): self.trace.append({step: step, result: result})三件套合起來(lái)失敗從一句紅了變成一段可回溯的證據(jù)鏈。三、接回 Executor把 Observer 嵌進(jìn)系列三第 1 篇的 Executorclass Executor: def run(self, steps, observer: Observer): for s in steps: observer.log(frun {s.name}) s.action(page) ok s.expect(page) observer.step(s.name, ok) if not ok: observer.metric(last_fail, s.name) observer.metric(pass_rate, passed/len(steps))從此每次運(yùn)行都自帶可觀測(cè)能力。等你把trace落盤上篇 Agent Harness 的judge(trace, spec)就能直接消費(fèi)——三個(gè)系列在這一刻閉環(huán)了。四、一句價(jià)值觀好的 Harness 不追求永遠(yuǎn)不出錯(cuò)它追求出錯(cuò)就一定能被看見(jiàn)、被復(fù)現(xiàn)、被定位。測(cè)試的價(jià)值不在綠而在真問(wèn)題一個(gè)都跑不掉。可觀測(cè)性就是這句話的工程技術(shù)實(shí)現(xiàn)。延伸從可觀測(cè)到質(zhì)量智能當(dāng)你把每次運(yùn)行的 trace、metrics 沉淀下來(lái)你就有了質(zhì)量數(shù)據(jù)湖。系列四最后一篇會(huì)講這些數(shù)據(jù)如何喂養(yǎng)出AI 質(zhì)量官——可觀測(cè)性是智能的起點(diǎn)。實(shí)戰(zhàn)案例用快照把偶現(xiàn)變必現(xiàn)一個(gè)時(shí)靈時(shí)不靈的登錄測(cè)試?yán)_團(tuán)隊(duì)兩周。人肉復(fù)現(xiàn)不了只能多跑幾次碰運(yùn)氣。我們給 Executor 接上 Snapshot強(qiáng)制每次運(yùn)行記錄輸入賬號(hào)、環(huán)境指紋、隨機(jī)種子、點(diǎn)擊序列。重放第 7 次運(yùn)行的那份快照時(shí)bug 穩(wěn)定復(fù)現(xiàn)——根因是某個(gè)第三方驗(yàn)證碼服務(wù)在高峰時(shí)段偶發(fā)超時(shí)測(cè)試沒(méi)對(duì)它做隔離。修復(fù)后這個(gè)偶現(xiàn)再?zèng)]出現(xiàn)過(guò)。踩坑實(shí)錄trace 全量落盤成本不低。一次 Agent 運(yùn)行可能產(chǎn)生上萬(wàn)條軌跡。我們后來(lái)做了分級(jí)默認(rèn)只存步驟級(jí)trace只在失敗時(shí)才補(bǔ)全調(diào)用級(jí)細(xì)節(jié)。可觀測(cè)性也要講成本否則質(zhì)量數(shù)據(jù)湖先把自己撐爆。 留言區(qū)你們團(tuán)隊(duì)現(xiàn)在能一鍵復(fù)現(xiàn)一個(gè)偶現(xiàn) bug嗎還是要三個(gè)人折騰一下午評(píng)論區(qū)說(shuō)真實(shí)情況。 關(guān)注回復(fù)「harness」領(lǐng)可觀測(cè)性最小接入方案。系列三完下一系列我們講進(jìn)階與智能化。系列三延伸生產(chǎn)級(jí) Harness 的 10 條軍規(guī)系列三的代碼是最小內(nèi)核能學(xué)架構(gòu)但直接上生產(chǎn)還差一層。把這 10 條當(dāng)上線 checklist逐條對(duì)著補(bǔ)。環(huán)境即代碼環(huán)境用聲明式 spec 制備可版本管理、可回滾禁止手動(dòng)裝好。失敗必留現(xiàn)場(chǎng)截圖/快照/日志三選一至少留且路徑可溯。狀態(tài)可序列化RunResult 用純數(shù)據(jù)結(jié)構(gòu)能落盤、能網(wǎng)絡(luò)傳、能回放。** oracle 獨(dú)立**判定邏輯絕不與執(zhí)行者同源尤其別讓同一個(gè) LLM 又干又審。約束在 OS 層AI 場(chǎng)景的規(guī)則由 Harness 卡死不靠提示詞請(qǐng)求遵守。并發(fā)可控Executor 支持并發(fā)但默認(rèn)保守避免壓垮被測(cè)環(huán)境。重試只針對(duì)瞬態(tài)區(qū)分 TransientError 和真失敗真失敗不重試、直接告警。報(bào)告雙格式給人看HTML/圖 給機(jī)器消費(fèi)JSON/JUnit XML都要有。配置外置閾值、種子、超時(shí)全進(jìn)配置不焊死在代碼里。可觀測(cè)分級(jí)默認(rèn)步驟級(jí) trace失敗才補(bǔ)全調(diào)用級(jí)控制存儲(chǔ)成本。這 10 條前 5 條是可信對(duì)應(yīng)受控/可復(fù)現(xiàn)/可觀測(cè)三特性后 5 條是可養(yǎng)能長(zhǎng)期運(yùn)營(yíng)不崩。內(nèi)核滿足了前 5 條就值得驕傲10 條全中才叫生產(chǎn)級(jí)。建議把它們貼在你團(tuán)隊(duì) wiki 首頁(yè)每次加組件前對(duì)照。系列三 · 一頁(yè)速記卡測(cè)試 Harness 內(nèi)核四件式Locator(手)Executor(心臟)Oracle(裁判)Reporter(嘴)測(cè)試只聲明 Step不碰執(zhí)行細(xì)節(jié)。Eval Harness 三件套Task(聲明)LM(抽象接口)Metric(聚合種子)。換模型不碰任務(wù)國(guó)產(chǎn)模型各寫一個(gè) LM 子類即可。Agent Harness 多角色Planner/Worker/Critic/JudgeWorker 不能既干又審約束層 OS 級(jí)卡死校驗(yàn)層讀 trace 防虛報(bào)完成。可觀測(cè)三件套Logs/Metrics/Trace狀態(tài)快照把偶現(xiàn)變必現(xiàn)trace 分級(jí)控成本。生產(chǎn)級(jí) 10 條軍規(guī)見(jiàn)延伸前 5 條保可信受控/可復(fù)現(xiàn)/可觀測(cè)后 5 條保可養(yǎng)能長(zhǎng)期運(yùn)營(yíng)。給老板的 30 秒版我們用最少代碼造出可復(fù)用的測(cè)試底座前端一次大改版從改腳本 3 天變成改一個(gè) locator 文件 10 分鐘復(fù)活——這是架構(gòu)對(duì)的直接回報(bào)。三個(gè)反共識(shí)① Eval Harness 不必自己造復(fù)用 lm-evaluation-harness只寫差異化適配② Agent 框架 ≠ Agent Harness后者是站在外面的質(zhì)量外殼管住前者別撒謊③ 可觀測(cè)性先落盤 JSON 人工復(fù)盤別一上來(lái)就 Prometheus/Grafana工具是手段不是目的。本地跑不起來(lái)三排查沒(méi)playwright install chromium、sync/async API 混用、占位地址沒(méi)換真實(shí)環(huán)境——先查這三條。一句話收尾架構(gòu)對(duì)了擴(kuò)展是加法架構(gòu)錯(cuò)了擴(kuò)展是重寫。系列三 · 一頁(yè)速查表Harness 類型核心文件關(guān)鍵組件必記要點(diǎn)測(cè)試 Harnesscore / locator / oracle / reporterLocatorExecutorOracleReporter測(cè)試只聲明 Step不碰執(zhí)行Eval Harnesslm.py / run.py yamlTaskLMMetric固定 seed換模型不碰任務(wù)Agent Harnessharness.py / verify.pyPlanner/Worker/Critic/Judge約束層卡死 校驗(yàn)層讀 trace可觀測(cè)observer / snapshotLogs/Metrics/Trace快照把偶現(xiàn)變必現(xiàn)把這表貼 wiki寫代碼前對(duì)照。系列三你手寫出的不是一個(gè)測(cè)試而是一個(gè) Harness 的內(nèi)核——記住這句話將來(lái)擴(kuò)展都是加法不是重寫。系列三 · 讀者行動(dòng)清單讀完別只收藏照這四條做動(dòng)手才算學(xué)會(huì)今晚用你手頭任意一個(gè) Playwright 腳本按系列三第 1 篇的 Common Layerlocator/executor/oracle/reporter重新畫一張依賴圖看它把定位和執(zhí)行混在一起了沒(méi)有。本周把系列三第 2 篇的evaluate(task, lm, n)模板抄出來(lái)跑通一個(gè)你自己的小任務(wù)重點(diǎn)驗(yàn)證random.seed(42)關(guān)掉后結(jié)果還穩(wěn)不穩(wěn)——不穩(wěn)就是可復(fù)現(xiàn)沒(méi)過(guò)關(guān)。本月挑團(tuán)隊(duì)里最痛的一個(gè) Agent 失控案例用系列三第 4 篇的約束層卡死 校驗(yàn)層讀 trace兩道防線重畫一次標(biāo)出哪條該擋在 OS 層、哪條該交給獨(dú)立 oracle。長(zhǎng)期建一個(gè)harness-recipes目錄把這三個(gè) Harness 的內(nèi)核當(dāng)模板沉淀新項(xiàng)目直接拿來(lái)改別每次從零寫。收藏從不等于學(xué)會(huì)動(dòng)手才算。下一系列見(jiàn)我們談智能與質(zhì)量門禁。系列三 · 本系列金句墻適合轉(zhuǎn)發(fā)、做封面金句、貼團(tuán)隊(duì)墻架構(gòu)對(duì)了擴(kuò)展是加法架構(gòu)錯(cuò)了擴(kuò)展是重寫。測(cè)試 Harness 內(nèi)核四件式手(Locator)心臟(Executor)裁判(Oracle)嘴(Reporter)。Eval Harness 三件套聲明(Task)抽象(LM)聚合(Metric)換模型不碰任務(wù)。Worker 不能既干活又裁判——讓 LLM 審自己等于考生自改卷。約束層在 OS 級(jí)卡死規(guī)則Agent 想繞也繞不過(guò)去。狀態(tài)快照把偶現(xiàn)變必現(xiàn)可觀測(cè)性不追求永遠(yuǎn)不出錯(cuò)追求出錯(cuò)必被看見(jiàn)。復(fù)用社區(qū)lm-evaluation-harness只寫差異化適配別重復(fù)造輪子。Agent 框架 ≠ Agent Harness前者讓你造 Agent后者讓你信 Agent 的結(jié)果。給讀者的 3 句叮囑① 先跑通四件式再追完整塔別悶頭造框架② 本地跑不起來(lái)先查 chromium / sync-async / 占位地址三件事③ trace 分級(jí)存別讓數(shù)據(jù)湖先撐爆。把四件式貼在團(tuán)隊(duì) wiki 首頁(yè)每次加組件前對(duì)照它越權(quán)干了別的組件的活嗎