Harness 工程:用 LLM 当评委,自动生成、评测、择优一条
大模型每次回答都不一样,质量也参差不齐。与其碰运气,不如让它多答几遍,再叫另一个 AI 当评委打分------Harness 就是这个思路。
前言
最近在做一个 AI 代码生成的场景,遇到了一个很头疼的问题:同样的 prompt,大模型有时候给得很漂亮的答案,有时候却答非所问。
你不可能每次都人肉去审代码------太慢了,也不工程化。
于是我开始研究怎么让 AI 替我做评测,发现了 LLM as Judge + Best-of-N Sampling 这套 Harness 模式。这篇文章我把核心原理和完整代码都整理出来,你复制粘贴改个 prompt 就能跑。
你将会收获:
- 🎯 理解什么是 Harness 工程,为什么能对抗 LLM 幻觉
- 🔧 Best-of-N Sampling 的实现原理(并行生成多个候选)
- ⚖️ LLM as Judge 的评审 prompt 怎么设计
- 🏗️ 一个完整的"生成→评测→择优"流水线代码
- 🧠 代码架构拆解,每个函数的作用都讲清楚
技术栈:Node.js、OpenAI SDK(兼容任何 OpenAI 格式 API)。
一、用大白话讲:Harness 是什么?
没有 Harness 的时候
你问 AI 一个问题,拿到一个答案。这个答案可能是对的,也可能是"一本正经地胡说八道"(幻觉)。你没法控制,只能碰运气。
有了 Harness 之后
你同时问 AI 同一个问题 N 次 ,拿到 N 个答案。然后再请另一个 AI 当评委 ,给每个答案打分,最后挑出最高分的那个。
┌───────────┐ ┌───────────┐ ┌───────────┐
│ 阶段 1 │ │ 阶段 2 │ │ 阶段 3 │
│ 生成 │ ──→ │ 评测 │ ──→ │ 择优 │
│ N个候选 │ │ LLM打分 │ │ 选最高分 │
└───────────┘ └───────────┘ └───────────┘
Harness 本意是"马具"------套上缰绳的马不会乱跑。同样,套上流水线的 LLM 不会胡说,它会在规则里稳定产出高质量结果。
三个核心思想
| 思想 | 大白话 | 解决什么问题 |
|---|---|---|
| Best-of-N Sampling | 同样的问题问 N 次 | AI 回答有随机性,多次采样覆盖更多可能性 |
| LLM as Judge | 让 AI 当评委打分 | 替代人工评审,实现闭环自动化 |
| Harness 抽象 | 生成→评测→择优解耦 | 三段各自独立,改一处不影响全局 |
这是 ReAct Agent 思维框架的一个工程化分支------让 LLM 在做判断时有结构化的反馈回路。
二、完整代码:从零写一个 Harness
下面是一个完整可运行的项目------让它自动生成数组去重函数,自己评测,选出最优版。
环境准备
bash
npm init -y
npm install openai dotenv
.env 文件(兼容阿里百炼、DeepSeek 等任何 OpenAI 格式 API):
env
OPENAI_API_KEY=你的API密钥
MODEL_NAME=qwen-plus
OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
完整代码
js
import OpenAI from 'openai';
import { config } from 'dotenv';
config();
// 初始化客户端(兼容任何 OpenAI 格式的 API)
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL,
});
// ===== 底层工具:统一封装 LLM 调用 =====
const askLLM = async (prompt) => {
const res = await client.chat.completions.create({
model: process.env.MODEL_NAME,
messages: [{ role: 'user', content: prompt }]
});
return res.choices[0].message.content;
};
// ===== 阶段 1:生成 ------ Best-of-N Sampling =====
const generateCandidates = (prompt, n = 3) => {
// Array.from 创建 n 个独立的 LLM 调用,Promise.all 并行执行
const tasks = Array.from({ length: n }, () => askLLM(prompt));
return Promise.all(tasks);
};
// ===== 阶段 2:评测 ------ LLM as Judge =====
async function judge(code) {
// 评审 prompt 设计:角色 + 任务 + 输出约束
const prompt = `
你是一个严格的代码评审,请判断下面代码是否正确实现"数组去重函数"
要求:
-只返回一个数字评分(0-100)
-不要解释
代码:
${code}
`;
const res = await askLLM(prompt);
const score = parseFloat(res); // 字符串转数字
return isNaN(score) ? 0 : score; // 解析失败兜底给 0 分
}
async function evaluateAll(candidates) {
const results = [];
for (const code of candidates) {
const score = await judge(code); // 逐个打分
results.push({ code, score });
}
return results;
}
// ===== 阶段 3:择优 ------ 选最高分 =====
function pickBest(evaluated) {
return evaluated.reduce(
(best, c) => (c.score > best.score ? c : best),
{ score: 0 } // 初始分数 0
);
}
// ===== Harness 主流程:串联三阶段 =====
async function harness(prompt) {
console.log('生成多个候选者....\n');
// 1. 生成 N 个候选代码
const candidates = await generateCandidates(prompt, 3);
console.log('候选结果:');
candidates.forEach((c, i) => {
console.log(`\n---- Candidate ${i + 1} ----\n${c}`);
});
// 2. LLM 逐个评分
console.log(`\nEvaluate Candidates...\n`);
const evaluated = await evaluateAll(candidates);
console.log('评估结果:');
evaluated.forEach((c, i) => {
console.log(`\n---- Candidate ${i + 1} ----\n${c.code}\n-> 分数: ${c.score}`)
});
// 3. 选出最优
const best = pickBest(evaluated);
return best.code;
}
// 跑起来!
harness("请使用javascript 实现一个数组去重函数");
三、代码架构逐层拆解
scss
harness() ← 编排层,掌控整个流水线
├── generateCandidates() ← 阶段1:生成(并行)
│ └── askLLM() × N
├── evaluateAll() ← 阶段2:评测(串行打分)
│ └── judge() × N
└── pickBest() ← 阶段3:择优(O(n) reduce)
askLLM ------ 统一封装
js
const askLLM = async (prompt) => {
const res = await client.chat.completions.create({ ... });
return res.choices[0].message.content; // 取回模型生成的文本
};
- 所有 LLM 调用走同一个函数,改 API Key / 模型地址只改一处
res.choices是候选回复数组(通常就 1 个),[0].message.content是实际文本
generateCandidates ------ 并行生成 N 个候选
js
const generateCandidates = (prompt, n = 3) => {
const tasks = Array.from({ length: n }, () => askLLM(prompt));
return Promise.all(tasks);
};
逐行拆解:
| 代码 | 作用 |
|---|---|
Array.from({ length: n }, ...) |
创建长度为 n 的数组,每个元素是一个 askLLM 调用 |
() => askLLM(prompt) |
映射函数,同步调用,n 个请求同时发出 |
Promise.all(tasks) |
等所有请求完成,返回结果数组 |
⚠️ 关键点 :
Promise.all是并行的,不是串行。n=3 时,耗时 ≈ 最慢那次的延迟,不是三次之和。
Best-of-N Sampling 为什么有效?
LLM 每次生成有随机性(受 temperature 等采样参数影响)。同一个 prompt 问 3 次:
- 第 1 次可能用了
Set去重(简洁) - 第 2 次可能用
filter + indexOf(兼容性好) - 第 3 次可能写出
reduce方案(炫技但有 bug)
三个答案互相覆盖,给你选择的空间。如果只问一次,你可能拿到第 3 个有 bug 的就直接用了。
judge ------ LLM 当评委
js
async function judge(code) {
const prompt = `
你是一个严格的代码评审,请判断下面代码是否正确实现"数组去重函数"
要求:只返回一个数字评分(0-100),不要解释
代码:${code}
`;
const res = await askLLM(prompt);
const score = parseFloat(res);
return isNaN(score) ? 0 : score;
};
评审 prompt 的设计要点:
| 要点 | 做法 | 为什么 |
|---|---|---|
| 角色设定 | "你是一个严格的代码评审" | 告诉 LLM 它的身份 |
| 任务明确 | "判断代码是否正确实现数组去重" | 评价标准唯一 |
| 输出约束 | "只返回数字,不要解释" | 方便程序解析,不要废话 |
| 容错处理 | isNaN(score) ? 0 : score |
LLM 万一不听话,不崩程序 |
pickBest ------ 选最高分
js
function pickBest(evaluated) {
return evaluated.reduce(
(best, c) => (c.score > best.score ? c : best),
{ score: 0 }
);
}
纯 JS,不需要调 LLM。reduce 一轮遍历选出最高分。
四、这个模式为什么能解决实际问题?
| 场景 | 单次 LLM 调用 | Harness 模式 |
|---|---|---|
| 代码生成 | 可能出 bug | 3 个候选互相覆盖,选最优 |
| 文本质量 | 质量随机 | LLM 评委把关 |
| 评审成本 | 需要人肉看 | AI 自动打分 |
| 落地可靠性 | 碰运气 | 流水线保证输出的是"最好的那个" |
这套模式不只适用于代码生成------文本翻译、文案撰写、数学解题、分类标注......任何 LLM 输出不稳定的场景,套上 Harness 都能提升质量下限。
五、常见坑
坑 1:评分 prompt 没约束格式
js
// ❌ 错误:prompt 只让评分,没说格式
const prompt = `请给下面的代码评分:${code}`;
// ✅ 正确:约束输出格式,方便解析
const prompt = `请给下面的代码打分,只返回一个 0-100 的数字,不要解释:${code}`;
坑 2:忘了 parseFloat 容错
js
// ❌ 错误:直接 return Number(res),万一返回 "85分" 就 NaN
return Number(res);
// ✅ 正确:兜底给 0
const score = parseFloat(res);
return isNaN(score) ? 0 : score;
坑 3:误以为 generateCandidates 是串行
js
// 这段不是串行!Promise.all 是并行的
const tasks = Array.from({ length: n }, () => askLLM(prompt));
return Promise.all(tasks); // n 个请求同时发出
六、一句话总结
Harness = Best-of-N Sampling(多问几次)+ LLM as Judge(AI 打分)+ 选最高分。给 LLM 套上缰绳,它就不会乱跑。
代码骨架(精简版):
js
const candidates = await Promise.all( // 1. 并行生成 N 个
Array.from({ length: 3 }, () => askLLM(prompt))
);
const evaluated = await evaluateAll(candidates); // 2. LLM 逐个评分
const best = pickBest(evaluated); // 3. reduce 选最优
结尾
这套模式看着简单,但工程化之后非常实用------改个 prompt 就能扩展到任何 LLM 输出不稳定的场景。
源码在 hjf-ai/interview/ai/harness/q1/ 目录下,.env 配好自己的 API Key 就能跑。
希望这篇文章对你有帮助!有问题欢迎在评论区交流,求个点赞收藏 🔥
写于 2026 年 8 月 · AI 工程化实践笔记