Harness 工程:用 LLM 当评委,自动生成、评测、择优一条龙

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 工程化实践笔记

相关推荐
ocean21031 小时前
八股刷题之:变体树
面试·面试题·面试真题·变体树·八股精·深度拓展·高效刷题
何时梦醒2 小时前
第三篇:React 组件化开发 — 函数即组件的哲学与实践
react.js·面试·typescript
胡萝卜术2 小时前
编译期与运行期的双重防线:从 TypeScript 类型之争到 LLM 输出的自动化择优
前端·设计模式·面试
用户852495071842 小时前
Harness 工程:用工程化手段驾驭 LLM,而不是迷信一次回答
面试
Dr.kangder2 小时前
嵌入式面试总结(一)——嵌入式系统实时性
面试·职场和发展·架构·嵌入式·虚拟化
何时梦醒2 小时前
TypeScript 类型系统核心:type 与 interface 全方位深度对比(附实战案例)
前端·面试·typescript
书源2 小时前
AI 能写代码之后,前端工程师的价值在哪里?
前端·面试·程序员
吃饱了得干活3 小时前
Java并发安全:看这一篇就懂了!
java·后端·面试
早点睡9753 小时前
向量检索原理入门:从 ANN 到 HNSW
后端·面试