踐)
在實(shí)際的 LLM Coding 評測和 Agent 開發(fā)中有一個(gè)很反直覺的現(xiàn)象模型權(quán)重完全沒動(dòng)提示詞也沒改只是把模型外層的執(zhí)行腳手架從“單次生成答案”改成“生成代碼 - 運(yùn)行測試 - 返回報(bào)錯(cuò) - 讓模型修改”的閉環(huán)許多不同模型的編碼指標(biāo)會同時(shí)上升。標(biāo)題里的 “Only the harness changed” 說的就是這個(gè)場景。harness 在這里不是模型參數(shù)也不是 prompt 模板而是包裹在模型之外負(fù)責(zé)輸入構(gòu)造、工具調(diào)用、代碼執(zhí)行、結(jié)果反饋和重試策略的一整套工程系統(tǒng)。這篇文章會圍繞 harness 展開先說明它是什么為什么能影響 15 個(gè)模型的編碼表現(xiàn)再一步步實(shí)現(xiàn)一個(gè)最小可復(fù)現(xiàn)的 coding harness并用它批量評估多個(gè) LLM。適合正在做模型評測、編碼 Agent、AI 編程工具鏈的工程師閱讀。讀完以后你可以把這套方案復(fù)制到自己的項(xiàng)目里用同一套 harness 橫向?qū)Ρ炔煌P鸵材芾斫鉃槭裁础皳Q模型不如換 harness”。1. Harness 到底是什么模型之外的執(zhí)行與評測系統(tǒng)1.1 從一個(gè)“模型集體變強(qiáng)”的現(xiàn)象說起如果只看結(jié)果很多人會以為某個(gè)新版本模型“一夜之間變強(qiáng)了”。但標(biāo)題里的現(xiàn)象并不是模型參數(shù)升級而是推理時(shí)的運(yùn)行方式變了??梢赃@樣理解同樣是做一張數(shù)學(xué)卷子之前要求直接寫答案現(xiàn)在允許在草稿紙上演算、檢查、改正最后再上交。草稿紙沒有改變考生大腦里的知識但確實(shí)提高了最終得分。LLM 也一樣。同一個(gè)模型在同樣的權(quán)重下如果外部系統(tǒng)允許它先執(zhí)行代碼、看到報(bào)錯(cuò)、再修改答案它的 coding 指標(biāo)就可能明顯提升。這就是 harness 的作用它不是一個(gè)更大的模型也不是更長的 prompt而是把“推理一次”擴(kuò)展成“推理、執(zhí)行、反饋、再推理”的完整循環(huán)。這里要特別說明不同評測集、不同模型供應(yīng)商、不同任務(wù)難度下harness 帶來的提升幅度并不一樣。不要把這個(gè)現(xiàn)象理解成“任何模型都能被 harness 救活”。模型本身的能力仍然是上限harness 負(fù)責(zé)把已經(jīng)存在但未被充分利用的能力釋放出來。1.2 Harness 在 LLM Coding 場景中的技術(shù)定位在 LLM coding 場景里harness 通常包含幾個(gè)核心部分輸入構(gòu)造把題目、代碼模板、歷史錯(cuò)誤、測試結(jié)果組裝成模型能理解的上下文。動(dòng)作執(zhí)行讓模型有機(jī)會運(yùn)行代碼、執(zhí)行命令、讀取文件而不是只輸出靜態(tài)文本。環(huán)境反饋把執(zhí)行后的 stdout、stderr、退出碼、測試斷言結(jié)果返回給模型。迭代控制決定最大嘗試次數(shù)、何時(shí)停止、何時(shí)切換策略。結(jié)果評估用隱藏測試、靜態(tài)檢查、人工規(guī)則判斷最終答案是否真正正確。模型、prompt、harness 是三個(gè)不同層面的東西。用一張表可以看得更清楚層面決定因素典型改動(dòng)影響范圍模型權(quán)重和架構(gòu)換模型、微調(diào)基礎(chǔ)語言理解、代碼生成能力Prompt輸入文本和示例改任務(wù)描述、Few-shot 示例模型對當(dāng)前任務(wù)的理解方式Harness外部執(zhí)行和反饋循環(huán)增加測試執(zhí)行、工具調(diào)用、重試模型能否利用執(zhí)行結(jié)果自我修正很多團(tuán)隊(duì)在優(yōu)化 coding 效果時(shí)第一反應(yīng)是換更大模型或?qū)懜?prompt 示例。實(shí)際上如果模型生成的代碼經(jīng)常只差一個(gè)邊界條件harness 里加一輪“跑測試再修復(fù)”通常更便宜、更穩(wěn)定。1.3 Harness 與 Agent 的關(guān)系A(chǔ)gent 是 harness 的高級形態(tài)但二者不是一回事。最小可用的 coding harness 可以只有四步生成、執(zhí)行、反饋、重試。Agent 則在此基礎(chǔ)上增加了更復(fù)雜的規(guī)劃、記憶、多工具選擇和長期目標(biāo)維護(hù)。換句話說Agent 框架本質(zhì)上是一套加強(qiáng)版 harness。理解 harness 的底層邏輯再去看 LangChain、自研 Agent、MCP 等工具時(shí)會更容易抓住核心它們都在解決“模型如何觀察外部世界、如何行動(dòng)、如何根據(jù)反饋調(diào)整”的問題。所以這篇文章先從一個(gè)最小 harness 開始。它不需要復(fù)雜的 Agent 框架也能實(shí)驗(yàn)出“只改 harness 就能提升多個(gè)模型”的效果。2. 為什么只改 Harness 就能提升多個(gè) LLM 的編碼能力2.1 單次生成的盲區(qū)模型看不到運(yùn)行結(jié)果不接 harness 時(shí)LLM 編碼是單向過程用戶給題目模型給代碼結(jié)束。如果代碼有語法錯(cuò)誤、邊界條件錯(cuò)誤、邏輯缺陷模型完全沒有機(jī)會看見。它只能依賴訓(xùn)練時(shí)見過的相似問題模式來推斷。這種模式適合“短答案生成”但不適合“程序必須可運(yùn)行”的場景。一個(gè)函數(shù)能否通過所有測試用例不取決于生成時(shí)看起來是否合理而取決于在真實(shí) Python 環(huán)境中的執(zhí)行結(jié)果。模型無法運(yùn)行代碼就無法獲得這一關(guān)鍵信息。2.2 反饋回路讓“試錯(cuò)”成為推理的一部分接入 harness 后模型的每次錯(cuò)誤都不是終點(diǎn)而是下一步推理的輸入。舉個(gè)例子模型第一次生成def two_sum(nums, target): for i in range(len(nums)): for j in range(len(nums)): if i ! j and nums[i] nums[j] target: return [i, j]用樣例測試[2, 7, 11, 15], 9可以通過但用邊界測試[3, 2, 4], 6會失敗因?yàn)檎_答案是[1, 2]而雙重循環(huán)可能先返回[0, 0]這種非法組合。如果不執(zhí)行測試模型永遠(yuǎn)不知道這個(gè)問題。加了 harness 后反饋信息里會包含斷言失敗assert res [1, 2] failed: expected [1, 2], got [0, 0]模型看到這個(gè)反饋后通常會在下一次生成時(shí)修正邏輯讓內(nèi)層循環(huán)從i 1開始。這個(gè)過程非常接近人類工程師調(diào)試代碼。2.3 工具調(diào)用擴(kuò)展了模型的信息邊界更強(qiáng)的 harness 不只是運(yùn)行代碼還可以讓模型讀取文件、執(zhí)行 shell 命令、查詢 API 文檔、搜索代碼倉庫。模型訓(xùn)練數(shù)據(jù)里沒有的依賴版本、內(nèi)部 API 簽名、最新框架用法都可以通過工具調(diào)用即時(shí)獲取。這也解釋了為什么 15 個(gè)模型都能從 harness 改動(dòng)中受益它們不需要共享同一套秘密技巧只需要共用同一個(gè)能執(zhí)行、能反饋、能搜索的外部系統(tǒng)。每個(gè)模型都會根據(jù)自己的能力從反饋中提取信息最終整體通過率上升。2.4 更合理的評判標(biāo)準(zhǔn)交付前先驗(yàn)證很多模型在 benchmark 上得分高但實(shí)際交付時(shí)仍然需要工程師修改。原因之一是評測只檢查“模型輸出是否看起來對”而不檢查“代碼是否真的能運(yùn)行通過”。harness 改變了評判方式不是模型生成完就交卷而是生成后先進(jìn)入沙箱執(zhí)行測試測試通過后才算完成。如果失敗就繼續(xù)反饋和重試。這樣最終產(chǎn)物是經(jīng)過執(zhí)行驗(yàn)證的代碼而不是一段沒有運(yùn)行過的字符串。對于 Coding 類任務(wù)這種“執(zhí)行優(yōu)先”的評判思路比單純比較生成文本相似度可靠得多。3. 動(dòng)手實(shí)現(xiàn)一個(gè)最小可復(fù)現(xiàn)的 Coding Harness3.1 場景與工作機(jī)制我們要實(shí)現(xiàn)一個(gè)最小但有完整閉環(huán)的 harness。它接收一個(gè)編程題目和若干測試用例調(diào)用任意 OpenAI 兼容接口的 LLM 生成 Python 代碼在本地沙箱中執(zhí)行測試。如果測試失敗就把錯(cuò)誤信息返回給模型讓模型繼續(xù)修復(fù)最多重試若干次。技術(shù)棧選擇Python 3.10 以上openaiPython SDK用于調(diào)用 OpenAI 兼容接口PyYAML用于讀取配置文件subprocesstempfile用于隔離運(yùn)行模型生成的代碼學(xué)習(xí)環(huán)境下使用subprocess加超時(shí)已經(jīng)足夠。生產(chǎn)環(huán)境要換成 Docker 容器或云沙箱不要直接在本機(jī)執(zhí)行不受信任的模型代碼。3.2 環(huán)境準(zhǔn)備與依賴安裝先創(chuàng)建虛擬環(huán)境并安裝依賴python -m venv .venv source .venv/bin/activate pip install openai pyyamlOpenAI 兼容接口的模型可以通過環(huán)境變量配置export OPENAI_API_KEY替換為你的 API Key export OPENAI_BASE_URLhttps://api.deepseek.com/v1這里使用OPENAI_BASE_URL的好處是可以平滑切換到任意兼容 OpenAI 協(xié)議的網(wǎng)關(guān)。模型名在配置文件中單獨(dú)指定例如deepseek-chat、qwen-plus、gpt-4o-mini等。實(shí)際運(yùn)行時(shí)要把密鑰和域名替換成自己環(huán)境對應(yīng)的值。3.3 數(shù)據(jù)模型與配置文件為了讓評測可重復(fù)先用配置文件管理模型參數(shù)和 sandbox 參數(shù)# config.yaml model: deepseek-chat temperature: 0.2 max_tokens: 2048 max_attempts: 3 sandbox_timeout: 10 test_timeout: 5 truncate_feedback: 2000字段含義參數(shù)默認(rèn)值示例作用modeldeepseek-chat指定要調(diào)用的模型名temperature0.2控制生成隨機(jī)性越低越穩(wěn)定max_tokens2048限制單次輸出長度max_attempts3允許模型修復(fù)代碼的最大輪數(shù)sandbox_timeout10每次運(yùn)行測試腳本的超時(shí)時(shí)間單位秒truncate_feedback2000截?cái)喾祷亟o模型的錯(cuò)誤信息長度避免上下文膨脹任務(wù)文件用 JSON 保存題目和測試用例{ id: two-sum, instruction: 實(shí)現(xiàn)函數(shù) two_sum(nums, target)返回兩個(gè)下標(biāo)組成的列表使兩個(gè)數(shù)之和等于 target。, entry_point: two_sum, tests: [ {args: [[2, 7, 11, 15], 9], expected: [0, 1]}, {args: [[3, 2, 4], 6], expected: [1, 2]}, {args: [[0, 4, 3, 0], 0], expected: [0, 3]} ] }這里測試用例不僅僅是樣例。[0, 4, 3, 0], 0這種邊界測試會暴露“返回相同下標(biāo)”的錯(cuò)誤是評測 harness 必須覆蓋的場景。3.4 核心代碼實(shí)現(xiàn)先定義數(shù)據(jù)類# harness.py import argparse import json import os import subprocess import sys import tempfile import textwrap from dataclasses import dataclass from openai import OpenAI import yaml dataclass class TestCase: args: list expected: object dataclass class Task: id: str instruction: str entry_point: str tests: list[TestCase] dataclass class HarnessConfig: model: str temperature: float 0.2 max_tokens: int 2048 max_attempts: int 3 sandbox_timeout: int 10 test_timeout: int 5 truncate_feedback: int 2000加載任務(wù)和配置def load_task(path: str) - Task: with open(path, r, encodingutf-8) as f: data json.load(f) tests [TestCase(argst[args], expectedt[expected]) for t in data[tests]] return Task( iddata[id], instructiondata[instruction], entry_pointdata[entry_point], teststests, ) def load_config(path: str) - HarnessConfig: with open(path, r, encodingutf-8) as f: data yaml.safe_load(f) return HarnessConfig(**data)構(gòu)造測試腳本。這里把用戶代碼和斷言拼成一個(gè)獨(dú)立腳本通過subprocess執(zhí)行def build_test_script(user_code: str, task: Task) - str: parts [user_code, ] for idx, t in enumerate(task.tests): args_repr , .join(json.dumps(arg) for arg in t.args) expected_repr json.dumps(t.expected, ensure_asciiFalse) parts.append(fres {task.entry_point}({args_repr})) parts.append( fassert res {expected_repr}, fcase {idx} failed: expected {expected_repr}, got repr(res) ) parts.append(print(PASS)) parts.append(print(ALL_PASS)) return \n.join(parts) def run_tests(user_code: str, task: Task, timeout: int) - dict: test_script build_test_script(user_code, task) with tempfile.TemporaryDirectory() as tmpdir: try: proc subprocess.run( [sys.executable, -I, -c, test_script], capture_outputTrue, textTrue, timeouttimeout, cwdtmpdir, ) except subprocess.TimeoutExpired: return { passed: False, stdout: , stderr: TimeoutExpired: 代碼運(yùn)行超時(shí), } if proc.returncode 0 and ALL_PASS in proc.stdout: return {passed: True, stdout: proc.stdout, stderr: proc.stderr} stderr proc.stderr or proc.stdout return {passed: False, stdout: proc.stdout, stderr: stderr}從模型輸出中提取代碼。為保證兼容性允許模型輸出 Markdown 代碼塊import re def extract_code(raw_output: str) - str: pattern r(?:python)?\s*(.*?) match re.search(pattern, raw_output, re.S) if match: return match.group(1).strip() return raw_output.strip()核心重試循環(huán)def solve_task(client: OpenAI, config: HarnessConfig, task: Task) - dict: messages [ { role: system, content: 你是一個(gè) Python 編程助手。只輸出可運(yùn)行的 Python 代碼不要添加多余解釋。, }, {role: user, content: task.instruction}, ] attempts [] for attempt in range(1, config.max_attempts 1): resp client.chat.completions.create( modelconfig.model, messagesmessages, temperatureconfig.temperature, max_tokensconfig.max_tokens, ) raw_output resp.choices[0].message.content or code extract_code(raw_output) result run_tests(code, task, config.sandbox_timeout) attempts.append( { attempt: attempt, code: code, passed: result[passed], stderr: result[stderr][-config.truncate_feedback :], } ) if result[passed]: return {status: solved, attempts: attempts, final_code: code} feedback f第 {attempt} 次運(yùn)行失敗請修復(fù)代碼\n{result[stderr][-config.truncate_feedback:]} messages.append({role: assistant, content: code}) messages.append({role: user, content: feedback}) return {status: failed, attempts: attempts, final_code: None}最后是命令行入口def main(): parser argparse.ArgumentParser() parser.add_argument(--task, requiredTrue, help任務(wù) JSON 文件路徑) parser.add_argument(--config, defaultconfig.yaml, helpharness 配置文件路徑) args parser.parse_args() config load_config(args.config) task load_task(args.task) client OpenAI( api_keyos.getenv(OPENAI_API_KEY), base_urlos.getenv(OPENAI_BASE_URL), ) result solve_task(client, config, task) print(json.dumps(result, ensure_asciiFalse, indent2)) if __name__ __main__: main()這段代碼有幾個(gè)關(guān)鍵點(diǎn)需要解釋模型輸出先經(jīng)過extract_code再進(jìn)入沙箱執(zhí)行避免模型在代碼塊外添加解釋導(dǎo)致運(yùn)行失敗。每次失敗后把模型自己的代碼和報(bào)錯(cuò)一起追加到對話歷史中讓模型在同一上下文里繼續(xù)修改。truncate_feedback防止巨型 traceback 撐爆上下文。-I參數(shù)讓 Python 以隔離模式運(yùn)行忽略當(dāng)前 PYTHONPATH 影響降低環(huán)境干擾。3.5 運(yùn)行與驗(yàn)證保存好config.yaml、task.json、harness.py后執(zhí)行python harness.py --task task.json --config config.yaml如果一切正常會輸出類似下面的 JSON{ status: solved, attempts: [ { attempt: 1, passed: false, stderr: assert res [1, 2] failed: expected [1, 2], got [0, 0] }, { attempt: 2, passed: true, stderr: } ], final_code: def two_sum(nums, target):\n seen {}\n for i, n in enumerate(nums):\n if target - n in seen:\n return [seen[target - n], i]\n seen[n] i\n }注意status是solved只代表模型通過了任務(wù)里配置的測試用例。它不能證明代碼在所有情況下都正確。這個(gè)區(qū)分很重要后面專門講。4. 用同一套 Harness 評估 15 個(gè) LLM 的流程設(shè)計(jì)4.1 為什么必須使用同一套 Harness如果你要橫向?qū)Ρ榷鄠€(gè)模型必須讓 harness 完全一致。因?yàn)椴煌闹卦嚧螖?shù)、不同的測試用例、不同的沙箱超時(shí)都會直接影響最終得分。有些模型在 1 次嘗試內(nèi)表現(xiàn)不佳但如果在 3 次嘗試中能通過最終排名就會不一樣?!爸桓?harness 就提升 15 個(gè) LLM”的場景里提升幅度并不只屬于某一個(gè)模型。所有模型共享同一個(gè)改進(jìn)后的執(zhí)行反饋機(jī)制。如果你在評測時(shí)給 A 模型 5 次重試給 B 模型 1 次重試那結(jié)果沒有可比性。統(tǒng)一內(nèi)容至少包括同一個(gè)任務(wù)集和測試用例。同一個(gè)max_attempts。同一個(gè)temperature。同一個(gè)沙箱超時(shí)時(shí)間。同一個(gè)反饋截?cái)嚅L度。同一個(gè)系統(tǒng)提示詞。任何一項(xiàng)不一致都應(yīng)視為不同 harness而不是不同模型的真實(shí)對比。4.2 評測集與判定規(guī)則評測集不能只有公開樣例還要包含隱藏測試用例。最簡單的方式是像前面task.json一樣把每個(gè)任務(wù)拆成“展示給模型的 instruction”和“模型看不到的 tests”兩部分。模型自己不能提前看到全部測試否則它可能生成專門針對測試用例的硬編碼。更完整的評測集應(yīng)該覆蓋基礎(chǔ)功能正常輸入。邊界條件空數(shù)組、最小值、最大值、重復(fù)元素。異常輸入不符合題目約束的數(shù)據(jù)是否被合理處理。性能約束大數(shù)據(jù)量下是否超時(shí)。不同難度的題目混合在一起能看出模型在不同復(fù)雜度下的表現(xiàn)差異。如果只有一個(gè)簡單樣例15 個(gè)模型可能全都滿分無法區(qū)分 harness 的作用。4.3 批量評估與指標(biāo)輸出批量評估腳本可以循環(huán)遍歷任務(wù)文件和模型列表。為了控制成本建議把配置參數(shù)單獨(dú)抽取出來python evaluate.py \ --task-dir tasks/ \ --models deepseek-chat qwen-plus gpt-4o-mini \ --config config.yaml \ --concurrency 4 \ --output report.jsonl評估腳本內(nèi)部可以這樣組織def evaluate_one(client, config, task, model): result solve_task(client, config, task) return { task_id: task.id, model: model, status: result[status], attempt_count: len(result[attempts]), token_usage_estimate: estimate_tokens(result[attempts]), } def evaluate_all(client, config, tasks, models): records [] for model in models: for task in tasks: cfg HarnessConfig(**{**config.__dict__, model: model}) records.append(evaluate_one(client, cfg, task, model)) return records收集完記錄后可以統(tǒng)計(jì)每個(gè)模型的首次通過率、最終通過率和平均嘗試次數(shù)指標(biāo)計(jì)算方式首次通過率第 1 次嘗試即通過的任務(wù)數(shù) / 總?cè)蝿?wù)數(shù)最終通過率在max_attempts內(nèi)通過的任務(wù)數(shù) / 總?cè)蝿?wù)數(shù)平均嘗試次數(shù)所有已通過任務(wù)的嘗試次數(shù)平均值平均耗時(shí)單任務(wù)從開始到結(jié)束的墻鐘時(shí)間平均值注意這里展示的表格結(jié)構(gòu)是通用指標(biāo)設(shè)計(jì)不是任何特定模型的真實(shí)分?jǐn)?shù)。你在自己的評測集上得到的結(jié)果可以作為內(nèi)部選型依據(jù)。4.4 成本與并發(fā)控制批量評估 15 個(gè)模型時(shí)token 消耗會迅速增加。每個(gè)失敗嘗試都會額外消耗一次模型調(diào)用和一次反饋上下文??刂瞥杀究梢詮膸追矫嫒胧窒拗苖ax_attempts一般 3 到 5 次足夠。對相同請求做緩存相同模型、相同任務(wù)、相同重試不必重復(fù)調(diào)用。使用并發(fā)時(shí)控制請求速率避免觸發(fā)限流。記錄每次調(diào)用的 prompt token 和 completion token便于核算成本。并發(fā)不是越高越好。限流和超時(shí)會導(dǎo)致評測結(jié)果不穩(wěn)定最終浪費(fèi)更多重試。更穩(wěn)妥的做法是先單線程跑通一個(gè)小樣本確認(rèn)流程穩(wěn)定后再加大并發(fā)。5. Harness 關(guān)鍵參數(shù)與調(diào)優(yōu)5.1 重試次數(shù)、溫度與最大 token這幾個(gè)參數(shù)直接影響評測結(jié)果和成本。參數(shù)取值范圍參考調(diào)小的影響調(diào)大的影響max_attempts1 - 5成本低但模型沒有自糾機(jī)會成本高可能在簡單錯(cuò)誤上反復(fù)打轉(zhuǎn)temperature0 - 0.7輸出穩(wěn)定但容易重復(fù)相同錯(cuò)誤探索更多但可能引入隨機(jī)錯(cuò)誤max_tokens512 - 4096代碼較長時(shí)容易被截?cái)噍敵龈暾赡苌啥嘤鄡?nèi)容sandbox_timeout5 - 30 秒快速失敗但誤殺復(fù)雜計(jì)算減少誤殺但死循環(huán)會拖慢評測在實(shí)際調(diào)優(yōu)中不要一開始就追求高重試次數(shù)。先把max_attempts設(shè)為 1看模型在不自糾時(shí)的基線水平再逐步增加到 3 和 5觀察通過率提升和成本增長曲線。如果 3 次到 5 次的通過率提升很小說明繼續(xù)增加重試的意義不大。5.2 測試用例生成策略測試用例來源有三種人工編寫質(zhì)量最高但覆蓋不全成本高。從題目約束自動(dòng)生成用枚舉、隨機(jī)、邊界值生成覆蓋性好。讓另一個(gè) LLM 生成速度快但可能出現(xiàn)錯(cuò)誤斷言。更推薦先人工寫題目和核心邊界再用腳本補(bǔ)充隨機(jī)測試。模型生成的測試用例可以作為補(bǔ)充但必須經(jīng)過人工審查。絕不能把 LLM 生成的測試直接當(dāng)成 ground truth否則可能出現(xiàn)“錯(cuò)誤測試認(rèn)定正確代碼”的情況。5.3 反饋信息的長度控制多輪重試時(shí)上下文會越來越長。如果每次都把完整 traceback 塞給模型幾輪之后 prompt 會膨脹模型容易丟失最早的任務(wù)描述。推薦做法只保留最后一次異常的最后 1000 到 2000 字符。把重復(fù)出現(xiàn)的相同錯(cuò)誤去重避免模型反復(fù)看同一個(gè) traceback。在反饋中明確告訴模型當(dāng)前是第幾次嘗試避免它生成“這是第一次”的回答。如果上下文仍然過長可以把歷史反饋壓縮成“之前嘗試過的修改摘要”。5.4 沙箱安全與資源限制模型生成的代碼不可信。學(xué)習(xí)環(huán)境里用subprocess和超時(shí)只是為了快速驗(yàn)證思路生產(chǎn)環(huán)境必須做更強(qiáng)的隔離。生產(chǎn)環(huán)境至少做到使用 Docker 容器運(yùn)行模型代碼限制 CPU、內(nèi)存和網(wǎng)絡(luò)。設(shè)置timeout和內(nèi)存上限避免死循環(huán)和內(nèi)存耗盡。模型代碼沒有網(wǎng)絡(luò)訪問權(quán)限除非任務(wù)明確需要。所有執(zhí)行結(jié)果先落盤再?zèng)Q定是否返回給模型。不要用宿主機(jī)的高權(quán)限賬號運(yùn)行任意生成代碼。注意如果你的 harness 會執(zhí)行模型生成的代碼安全性是第一優(yōu)先級。寧可犧牲一點(diǎn)執(zhí)行速度也不要讓不可信代碼直接接觸宿主機(jī)。6. 常見問題與排錯(cuò)路徑6.1 模型輸出不是合法代碼現(xiàn)象run_tests報(bào)錯(cuò) NameError 或者無法解析??赡茉蚰P蜎]有按“只輸出代碼”的要求返回。模型把代碼放在 Markdown 代碼塊中但extract_code沒匹配到。模型返回了 JSON 包裝的字符串。檢查方式打印raw_output確認(rèn)模型實(shí)際返回內(nèi)容。處理建議增強(qiáng)解析函數(shù)。支持去掉python標(biāo)記如果模型返回 JSON先嘗試解析code字段如果仍然失敗可以讓模型輸出固定格式例如BEGIN_CODE ... END_CODE6.2 同一個(gè)錯(cuò)誤反復(fù)重試現(xiàn)象max_attempts用完但每次錯(cuò)誤相同??赡茉蚍答佇畔⒈唤?cái)嗟酵耆珱]有錯(cuò)誤細(xì)節(jié)。temperature0導(dǎo)致模型每次都輸出相同結(jié)果。模型沒有真正“看到”反饋因?yàn)橄㈨樞蚧蚪巧O(shè)置錯(cuò)誤。檢查方式查看attempts中每一輪返回給模型的stderr。處理建議把temperature提高到 0.2 到 0.4確認(rèn)反饋中保留最后一行錯(cuò)誤類型和斷言信息如果錯(cuò)誤信息過長只保留后半段。6.3 公開測試通過但隱藏測試失敗現(xiàn)象harness 輸出solved但人工復(fù)測時(shí)發(fā)現(xiàn)代碼不正確??赡茉驕y試用例太少模型恰好通過了所有用例。模型學(xué)會了針對測試樣例硬編碼。測試用例只有正常輸入沒有邊界。檢查方式把模型生成的final_code放到更大的隱藏測試集上執(zhí)行。處理建議評測集必須分離公開樣例和隱藏測試。最終指標(biāo)以隱藏測試為準(zhǔn)一次評測同時(shí)加入邊界、隨機(jī)、性能三類用例。6.4 API 調(diào)用超時(shí)或限流現(xiàn)象批量評估時(shí)大量請求報(bào)錯(cuò)??赡茉虿l(fā)過高觸發(fā) API 限流。部分模型響應(yīng)慢超過客戶端默認(rèn)超時(shí)。網(wǎng)絡(luò)抖動(dòng)。檢查方式查看client.chat.completions.create拋出的異常類型和錯(cuò)誤碼。處理建議加入指數(shù)退避重試降低并發(fā)數(shù)為每次調(diào)用設(shè)置合理 timeout對相同請求做緩存避免重復(fù)調(diào)用。6.5 沙箱卡死與資源耗盡現(xiàn)象單個(gè)任務(wù)執(zhí)行時(shí)間特別長甚至整機(jī)變卡。可能原因模型生成死循環(huán)代碼。代碼創(chuàng)建超大列表或遞歸無終止。subprocess的timeout沒有覆蓋到子進(jìn)程內(nèi)部再創(chuàng)建的子進(jìn)程。檢查方式檢查 sandbox 日志確認(rèn)是否出現(xiàn)TimeoutExpired。處理建議在subprocess.run中設(shè)置嚴(yán)格 timeout生產(chǎn)環(huán)境用 Docker 的--memory和--pids-limit限制資源不要相信模型生成的代碼天然安全。排錯(cuò)路徑可以按這個(gè)順序走先檢查輸入任務(wù)文件、測試數(shù)據(jù)、配置格式。再檢查解析模型輸出是否正確提取。再檢查執(zhí)行沙箱能否運(yùn)行最簡單的腳本。再檢查反饋模型是否收到了完整報(bào)錯(cuò)。再檢查限制超時(shí)、token、并發(fā)是否導(dǎo)致假失敗。最后檢查評測集測試用例本身是否可靠。7. 生產(chǎn)環(huán)境 Harness 的最佳實(shí)踐與擴(kuò)展方向7.1 從評測腳本走向在線編碼 Agent最小 harness 是評測工具但它的核心結(jié)構(gòu)可以直接擴(kuò)展成生產(chǎn)級的編碼 Agent。區(qū)別在于生產(chǎn)系統(tǒng)需要更多工程能力支持多文件倉庫模型可以讀取目錄、打開文件、修改文件。支持工具協(xié)議模型能調(diào)用格式檢查、靜態(tài)分析、測試運(yùn)行、git 操作。支持持久記憶保存模型已經(jīng)嘗試過的方案避免重復(fù)犯錯(cuò)。支持人工介入在自動(dòng)修復(fù)多次失敗后把任務(wù)轉(zhuǎn)給工程師處理。支持版本回滾模型對代碼庫的所有修改都記錄 diff異常時(shí)可以回滾。這些能力本質(zhì)上都是“在模型外面加 harness”。模型還是那個(gè)模型但外部系統(tǒng)越完善它能完成的任務(wù)越復(fù)雜。7.2 評測前檢查清單把下面這份清單當(dāng)作可復(fù)用模板每次開始新的評測前過一遍測試用例是否包含隱藏用例而不是只給模型公開樣例。所有模型是否使用完全相同的max_attempts、溫度、超時(shí)和系統(tǒng)提示詞。反饋信息是否保留異常類型和關(guān)鍵斷言而不是只返回“失敗”。錯(cuò)誤信息是否做了長度截?cái)喾乐股舷挛呐蛎洝I诚涫欠裨O(shè)置了超時(shí)、內(nèi)存和 CPU 限制。是否記錄每輪 attempt 的原始輸出、最終代碼和錯(cuò)誤日志。是否固定依賴版本和模型版本保證結(jié)果可復(fù)現(xiàn)。是否使用passk多次采樣而不是只跑一次。是否區(qū)分“通過公開測試”和“通過隱藏測試”兩個(gè)指標(biāo)。是否把隨機(jī)種子、并發(fā)數(shù)、API 端點(diǎn)寫入實(shí)驗(yàn)記錄。這十項(xiàng)做完你的評測結(jié)果才有橫向?qū)Ρ葍r(jià)值。7.3 擴(kuò)展方向MCP、RAG 與更豐富的工具層再往前走h(yuǎn)arness 可以連接 MCP 協(xié)議、向量檢索、內(nèi)部文檔庫和外部 API。模型在編碼時(shí)不再只依靠訓(xùn)練記憶而是能實(shí)時(shí)獲取依賴文檔、歷史代碼、團(tuán)隊(duì)規(guī)范和安全策略。例如harness 里增加一個(gè)search_code工具模型在寫某個(gè)函數(shù)前先搜索項(xiàng)目里已有的相似實(shí)現(xiàn)再生成代碼。另一個(gè)read_docs工具讓模型在不確定某個(gè) SDK 參數(shù)時(shí)先查官方文檔。這些能力都會影響編碼結(jié)果而它們?nèi)匀粚儆凇澳P椭獾南到y(tǒng)”。學(xué)習(xí)建議是先把這個(gè)最小 harness 跑通再逐步加入一個(gè)工具、一個(gè)記憶模塊觀察每次改動(dòng)對多個(gè)模型的影響。你會慢慢理解為什么“只改 harness”能提升 15 個(gè)模型——因?yàn)?harness 改變的不是參數(shù)而是模型與外部世界互動(dòng)的方式。這種工程能力比單純追新模型版本更可控也更值得長期投入。