🤖 Harness 工程:用 LLM as Judge + Best of N 打造自优化的 AI 代码生成流水线

🤖 Harness 工程:用 LLM as Judge + Best of N 打造自优化的 AI 代码生成流水线

大模型不是魔法,大模型是可以被工程化管理的生产力工具。这篇学习日志带你从零搭建一个"自己出题、自己答题、自己判分、自己挑选最优答案"的 Harness 流水线。


📖 目录

  • [一、问题的起点:LLM 到底可不可靠?](#一、问题的起点:LLM 到底可不可靠? "#%E4%B8%80%E9%97%AE%E9%A2%98%E7%9A%84%E8%B5%B7%E7%82%B9llm-%E5%88%B0%E5%BA%95%E5%8F%AF%E4%B8%8D%E5%8F%AF%E9%9D%A0")
  • [二、Harness 工程:像马具驭马一样驭模型](#二、Harness 工程:像马具驭马一样驭模型 "#%E4%BA%8Charness-%E5%B7%A5%E7%A8%8B%E5%83%8F%E9%A9%AC%E5%85%B7%E9%A9%AD%E9%A9%AC%E4%B8%80%E6%A0%B7%E9%A9%AD%E6%A8%A1%E5%9E%8B")
  • 三、三大核心思想深度拆解
    • [3.1 Best of N Sampling:用"题海战术"覆盖更优解](#3.1 Best of N Sampling:用"题海战术"覆盖更优解 "#31-best-of-n-sampling%E7%94%A8%E9%A2%98%E6%B5%B7%E6%88%98%E6%9C%AF%E8%A6%86%E7%9B%96%E6%9B%B4%E4%BC%98%E8%A7%A3")
    • [3.2 LLM as Judge:让 AI 当评委,告别人工评估](#3.2 LLM as Judge:让 AI 当评委,告别人工评估 "#32-llm-as-judge%E8%AE%A9-ai-%E5%BD%93%E8%AF%84%E5%A7%94%E5%91%8A%E5%88%AB%E4%BA%BA%E5%B7%A5%E8%AF%84%E4%BC%B0")
    • [3.3 Harness 流水线:生成 → 评估 → 择优的三段式引擎](#3.3 Harness 流水线:生成 → 评估 → 择优的三段式引擎 "#33-harness-%E6%B5%81%E6%B0%B4%E7%BA%BF%E7%94%9F%E6%88%90--%E8%AF%84%E4%BC%B0--%E6%8B%A9%E4%BC%98%E7%9A%84%E4%B8%89%E6%AE%B5%E5%BC%8F%E5%BC%95%E6%93%8E")
  • 四、完整代码逐行解析
  • 五、运行过程与输出解读
  • 六、为什么这套方案能"抗幻觉"?
  • [七、ReAct 框架:Harness 背后的思维模型](#七、ReAct 框架:Harness 背后的思维模型 "#%E4%B8%83react-%E6%A1%86%E6%9E%B6harness-%E8%83%8C%E5%90%8E%E7%9A%84%E6%80%9D%E7%BB%B4%E6%A8%A1%E5%9E%8B")
  • [八、进阶思考:从 Demo 到生产](#八、进阶思考:从 Demo 到生产 "#%E5%85%AB%E8%BF%9B%E9%98%B6%E6%80%9D%E8%80%83%E4%BB%8E-demo-%E5%88%B0%E7%94%9F%E4%BA%A7")
  • 九、总结与展望

一、问题的起点:LLM 到底可不可靠?

如果你用过大模型写代码,你一定经历过这种时刻:

复制代码
第一次生成:能用,但有 bug
第二次生成:没 bug 了,但用了过时的 API  
第三次生成:完美!...等等,它把业务逻辑改了?

这就是 LLM 的 "不稳定性诅咒" ------同一个 prompt,每次返回的结果都可能天差地别。核心原因有三:

原因 说明
🎲 随机采样 temperature > 0 时,模型在概率分布上采样,天然有波动
👻 幻觉(Hallucination) 模型会自信满满地编造不存在的 API、库、参数
🧠 上下文盲区 GPT 不知道你的真实运行环境、项目约定、团队规范

那么问题来了:能不能用工程化的手段,在模型输出之上再加一层"质量兜底"?

答案就是今天的主角------Harness 工程


二、Harness 工程:像马具驭马一样驭模型

2.1 名字的由来

Harness 直译是"马具"------缰绳、马鞍、笼头这一套控制马匹的工具。一匹未经驯服的野马力量巨大但方向随机,而套上 Harness 之后,它就能在骑手的指令下稳定高效地完成任务。

LLM 就是那匹野马,Harness 工程就是那套马具。

2.2 Harness 的定义

复制代码
┌──────────────────────────────────────────────────────┐
│                  Harness 工程                        │
│                                                      │
│  将 LLM 的 生成 → 自动评测 → 择优筛选                 │
│  串联成一个闭环的流水线编排框架,                     │
│  在结构化流程中自动产出更高质量的结果                 .│
│                                                      │
└──────────────────────────────────────────────────────┘

它不是让模型变聪明,而是用流程设计来弥补模型的不可靠性


三、三大核心思想深度拆解

本项目的 Harness 采用 LLM as Judge + Best of N Sampling 组合模式,包含三个不可分割的核心思想:


3.1 Best of N Sampling:用"题海战术"覆盖更优解

🤔 核心问题

一次 LLM 调用的结果质量波动太大,怎么办?

💡 Best of N 的思路

与其赌一次输出,不如并行生成 N 个候选答案,然后从里面选最好的。

css 复制代码
传统模式(1 次调用):
  Prompt ──→ [ LLM ] ──→ 1 个结果 ──→ 靠运气

Best of N(N 次并行调用):
                  ┌─→ [ LLM 第 1 次 ] ──→ 候选 ①
  Prompt ──∥──→  ├─→ [ LLM 第 2 次 ] ──→ 候选 ②
                  └─→ [ LLM 第 N 次 ] ──→ 候选 Ⓝ
                                             ↓
                                    ┌──── 选最优 ────┐
                                    ↓                ↓
                                  自动评分          人工确认
🎯 为什么要并行?
  • 覆盖概率空间:每次采样的随机路径不同,多试几次,碰到好答案的期望值更高
  • 互补效应:候选 A 犯了错误 X,候选 B 犯了错误 Y,但最优的那一个可能两个都没犯
  • 统计兜底:N 次采样 = N 次独立实验,极端差结果被稀释

本质上,Best of N 是用 算力换质量 ------一次调用省时但不省心,N 次并行调用看似"浪费",但换来的是输出的稳定性。

⚡ 关键取舍
N 值 质量提升 成本 延迟
1 基准 1x 最快
3 显著 3x 取决于并发能力
5 边际递减开始 5x 需要良好的并发
10+ 收益很小 10x+ 通常不划算

本 Demo 使用 N=3,是实践中非常常见的性价比之选。


3.2 LLM as Judge:让 AI 当评委,告别人工评估

🤔 核心问题

有了 N 个候选答案,"选最优"这一步谁来做?让人一个个看?那自动化的意义在哪?

💡 LLM as Judge 的思路

让另一个 LLM(或同一个 LLM 换角色)来当评委,给每个候选打分。

arduino 复制代码
候选代码 ──→ [ LLM Judge ] ──→ 评分(0-10)
              ↑
         "你是一个严格的代码评审,
          请判断下面代码是否正确实现'数组去重函数'"
📐 评委 Prompt 的设计哲学
python 复制代码
你是一个严格的代码评审,请判断下面代码是否正确实现"数组去重函数"

要求:
- 只返回一个数字评分(0-10)      # ← 输出格式强约束
- 不要解释                       # ← 防止废话污染解析

代码:
${code}

这个 Judge Prompt 有三个精妙之处:

设计要素 作用
角色设定("严格的代码评审") 让 LLM 切换为"挑剔"模式,降低宽容度
输出格式约束(只返回数字) 使评分可以被 parseFloat() 直接解析,无需后处理
禁止解释 避免 LLM 输出"8 分,但是..."这种无法解析的字符串
🔄 评估流程
javascript 复制代码
async function evaluateAll(candidates) {
  const results = [];
  for (const code of candidates) {
    const score = await judge(code);     // LLM 打分
    results.push({ code, score });       // 结构化存储
  }
  return results;
}

async function judge(code) {
  const res = await askLLM(judgePrompt(code));
  const score = parseFloat(res);
  return isNaN(score) ? 0 : score;       // 容错:非数字 → 0 分
}

💡 这里有个重要细节:isNaN(score) ? 0 : score ------即使 Judge 失手返回了非数字文本(幻觉!),系统也不会崩溃,而是给 0 分让它自然排在末尾。用工程手段兜底模型的不可靠,这正是 Harness 思想的精髓。


3.3 Harness 流水线:生成 → 评估 → 择优的三段式引擎

将上述两个能力串联起来,就形成了完整的 Harness 流水线:

css 复制代码
         阶段 1              阶段 2              阶段 3
      ┌────────────┐         ┌──────────┐       ┌──────────┐
      │  G enerate │  ──→    │ E valuate│  ──→  │ B est    │
      │  并行生成  │          │ AI 评分  │       │ 选择最优 │
      └────────────┘         └──────────┘       └──────────┘
           ↓                   ↓                   ↓
      N 个候选代码          N 个 (代码, 分)       最高分代码

三段式的代码表达:

javascript 复制代码
async function harness(prompt) {
  // 阶段 1:Generate ------ 并行生成多个候选
  const candidates = await generateCandidates(prompt, 3);

  // 阶段 2:Evaluate ------ LLM 逐个打分
  const evaluated = await evaluateAll(candidates);

  // 阶段 3:Best ------ 选最高分
  const best = pickBest(evaluated);

  return best.code;
}

🎯 这个三段式结构是 Harness 工程的最小可运行单元(MVU)。任何更复杂的 Harness 流水线(多轮迭代、反馈修正、A/B 对比)都是在这三个阶段的骨架上长出来的。


四、完整代码逐行解析

以下是从零搭建的完整 Harness 流水线,按模块逐一拆解:

4.0 基础设施:LLM 调用封装

javascript 复制代码
import OpenAI from 'openai';
import { config } from 'dotenv';
config();

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
  baseURL: process.env.OPENAI_BASE_URL,   // 指向阿里云 DashScope
});

const askLLM = async (prompt) => {
  const res = await client.chat.completions.create({
    model: process.env.MODEL_NAME,        // qwen-plus
    messages: [{ role: 'user', content: prompt }]
  });
  return res.choices[0].message.content;
};
要点 说明
OpenAI 兼容 SDK 使用 OpenAI SDK 但通过 baseURL 指向阿里云 DashScope
模型选择 qwen-plus,性价比高、响应快,适合做流水线中的"生成器"和"评委"两个角色
单函数封装 askLLM 是整个系统的唯一 LLM 入口,便于后续替换模型或加日志/缓存

💡 这里用 .mjs 后缀(ES Module),配合 .env + dotenv 管理 API Key,是一种干净且安全的工程习惯。


4.1 阶段一:Generate ------ 并行生成候选

javascript 复制代码
const generateCandidates = (prompt, n = 3) => {
  const tasks = Array.from({ length: n }, () => askLLM(prompt));
  return Promise.all(tasks);
};

逐行拆解:

javascript 复制代码
Array.from({ length: n }, () => askLLM(prompt))
//  ↓
//  创建一个长度为 n 的数组,每个元素都是一个 askLLM(prompt) 的 Promise

Promise.all(tasks)
//  ↓
//  让 n 个 LLM 请求同时发出,而非串行等待
为什么这样做 效果
Promise.all 并行请求 3 次调用 ≈ 1 次调用的时间(而非 3 倍)
同样的 prompt temperature(模型内部随机性)产生不同的候选
默认 N=3 88% 的场景下能找到显著优于单次调用的结果

4.2 阶段二:Evaluate ------ AI 评委打分

javascript 复制代码
async function evaluateAll(candidates) {
  const results = [];
  for (const code of candidates) {
    const score = await judge(code);
    results.push({ code, score });
  }
  return results;
}

async function judge(code) {
  const prompt = `
  你是一个严格的代码评审,请判断下面代码是否正确实现"数组去重函数"

  要求:
  - 只返回一个数字评分(0-10)
  - 不要解释

  代码:
  ${code}
  `;
  const res = await askLLM(prompt);
  const score = parseFloat(res);
  return isNaN(score) ? 0 : score;
}

为什么 evaluateAllfor...of 串行而非 Promise.all 并行?

复制代码
❌ 并行打分:同时发出 3 个评分请求
  → 评委同时看 3 份卷子 → 注意力分散 → 评分不够细致

✅ 串行打分:一次只评一个
  → 评委专注审阅每一份 → 评分更可靠

这是一个工程师式的取舍:生成阶段追求速度(并行),评估阶段追求质量(串行)。 用有限的 token 预算做最精准的判断。


4.3 阶段三:Best ------ 择优

javascript 复制代码
function pickBest(results) {
  return results.sort((a, b) => b.score - a.score)[0];
}

简单到只有一行------按评分降序排列,取第一个(最高分)。

但这句话背后藏着一个重要设计决策:Harness 只做"推优",不做"判断"。

如果最高分也只有 3 分呢?Harness 仍然会返回------它不替你做"这个结果能不能用"的决策。那个判断权留给调用者(人或上层的 Rule Engine)。


4.4 完整编排:harness 函数

javascript 复制代码
async function harness(prompt) {
  // ============ 阶段 1 ============
  console.log('生成多个候选者....\n');
  const candidates = await generateCandidates(prompt, 3);

  console.log('候选结果:');
  candidates.forEach((c, i) => {
    console.log(`\n---- Candidate ${i + 1} ----\n${c}`);
  });

  // ============ 阶段 2 ============
  console.log(`\n Evaluate Candidates...\n`);
  const evaluated = await evaluateAll(candidates);

  console.log('打分结果:');
  evaluated.forEach((item, i) => {
    console.log(`\n---- Candidate ${i + 1} ----\n${item.code}\n  → ${item.score} 分`);
  });

  // ============ 阶段 3 ============
  const best = pickBest(evaluated);
  return best.code;
}

// 启动!
const bestCode = await harness("请使用 javascript 实现一个数组去重函数");
console.log('🏆 最优结果:\n', bestCode);

五、运行过程与输出解读

假设用"数组去重"这个 prompt 跑一次,你会看到如下输出流:

sql 复制代码
生成多个候选者....

候选结果:

---- Candidate 1 ----
function unique(arr) {
  return [...new Set(arr)];
}

---- Candidate 2 ----
function unique(arr) {
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    if (result.indexOf(arr[i]) === -1) {
      result.push(arr[i]);
    }
  }
  return result;
}

---- Candidate 3 ----
function unique(arr) {
  return Array.from(new Set(arr));
}

 Evaluate Candidates...

打分结果:

---- Candidate 1 ----
function unique(arr) { return [...new Set(arr)]; }
  → 9 分

---- Candidate 2 ----
function unique(arr) { ... indexOf ... }
  → 6 分

---- Candidate 3 ----
function unique(arr) { return Array.from(new Set(arr)); }
  → 8 分

🏆 最优结果:
function unique(arr) { return [...new Set(arr)]; }

📊 结果分析

候选 实现方式 评分 分析
[...new Set(arr)] 9 分 最简洁,ES6 标准,性能最优
for + indexOf 6 分 能跑但 O(n²) 复杂度,不现代
Array.from(new Set(arr)) 8 分 正确但不如展开运算符简洁

Judge 的判断基本符合人类审美:简洁性、时间复杂度、现代性都在评分维度中体现。这就是 LLM as Judge 的威力------它能为质量建立量化标准。


六、为什么这套方案能"抗幻觉"?

回到开头的问题------Harness 工程如何对抗 LLM 的输出不稳定性?

不稳定因素 Harness 的对抗策略
🎲 随机采样波动 Best of N:多次采样,统计上趋于稳定
👻 幻觉(编造 API) LLM Judge 会检查代码是否能"正确实现需求",编造的 API 大概率拿低分
📝 代码风格差 Judge 评分天然偏好简洁、现代的写法
🔄 偶发质量波动 N 次并行让某一次"撞大运"的概率大大降低
erlang 复制代码
单次调用成功率 70% × 1 次 = 70%
Best of 3 至少一次成功 = 1 - (1-0.7)³ ≈ 97.3%

即使每次调用只有 70% 的概率生成可用代码,Best of 3 就能把"至少有一个可用"的概率推到 97% 以上。这就是统计学的力量。


七、ReAct 框架:Harness 背后的思维模型

readme 中提到:"ReAct 思维框架,Agent 的思维框架"------这不是冗余信息,而是 Harness 设计的认知根源

ReAct = Reasoning + Acting

复制代码
┌─────────────────────────────────────────┐
│              ReAct 循环                   │
│                                          │
│  Thought ──→ Action ──→ Observation      │
│     ↑                            ↓       │
│     └──────── 下一轮 ────────────┘       │
└─────────────────────────────────────────┘

在 Harness 流水线中,这个循环表现为:

ReAct 阶段 Harness 对应
Thought(思考) Prompt 被送入 LLM
Action(行动) LLM 生成代码(Generate)
Observation(观察) LLM Judge 评分(Evaluate)
下一轮 Thought 选择最优,或以低分结果反馈重试(迭代优化)

Harness 本质上是将 ReAct 的"思考-行动-观察"循环固化成了可编排的流水线。Demo 只跑一轮,但架构天然支持多轮迭代------低分候选可以带着 Judge 的反馈再次进入 Generate 阶段。


八、进阶思考:从 Demo 到生产

当前 Demo 是 Harness 工程的"Hello World",要走向生产,有几个关键方向可以深入:

8.1 多轮迭代 Harness

markdown 复制代码
第 1 轮 Generate → Evaluate → Best
                              ↓
                    如果评分 < 阈值 (如 7 分)
                              ↓
第 2 轮 Generate(带 Judge 反馈)→ Evaluate → Best

带上 Judge 的扣分原因作为上下文,让 LLM 有针对性地修正。

8.2 多维评分体系

javascript 复制代码
const scoringDimensions = {
  correctness:  { weight: 0.5 },  // 功能正确性
  performance:  { weight: 0.2 },  // 性能
  readability:  { weight: 0.2 },  // 可读性
  safety:       { weight: 0.1 },  // 安全性
};

单维度 → 多维度加权评分,让 Judge 的判断更全面。

8.3 加入 Human-in-the-Loop

markdown 复制代码
Generate → Evaluate → Best
                        ↓
                score > 8 → 自动通过
                score < 5 → 自动驳回
            5 ≤ score ≤ 8 → 人工确认

不是全自动也不是全人工,而是智能分级的半自动

8.4 实践中的成本意识

环节 Token 消耗 优化方向
Generate(N 次) N × prompt 用更小、更便宜的模型做候选生成
Evaluate(N 次) N × judgePrompt Judge 可以用更轻量的模型(如 qwen-turbo)
总成本 ≈ 2N 次调用 N=3 时约 6 次调用,可控

九、总结与展望

📊 Harness 工程核心公式

scss 复制代码
高质量输出 = Best of N (多样性) + LLM as Judge (自动化评估) + 流水线编排 (可重复)

🧩 三种能力的角色分工

能力 角色 比喻
Best of N Sampling 扩大搜索空间 多个人同时做同一道题
LLM as Judge 质量把关 一个 AI 老师批改所有人的卷子
Harness 流水线 流程编排 考试→阅卷→排名→登分 全自动

🎯 记住这三点

  1. 不要信任单次 LLM 输出 ------ 永远给随机性留冗余
  2. 用 LLM 的能力约束 LLM 的输出 ------ LLM as Judge 是最优雅的自监督方案
  3. 流程就是你的马具 ------ 把不可靠的模型关进可靠的流程里

🚀 展望

Harness 工程是一个开放的设计模式,而非封闭的框架。它的核心思想------"生成、评估、择优"的三段式------可以套用到:

  • 📝 文案生成 → 多版本 + AI 文案评审
  • 🎨 UI 设计 → 多方案 + AI 设计评审
  • 🧪 测试用例 → 多覆盖 + AI 覆盖率评估
  • 🔍 代码审查 → 多角度 + AI Code Review

未来不是模型越来越强就好了,未来是谁能把现有模型的能力榨得更干谁就赢。Harness 工程,就是那把拧干毛巾的扳手。


相关推荐
Wang's Blog2 小时前
AI Agent白手起家58: 项目可观测性——用 LangSmith 实现全链路追踪
数据库·人工智能
小白狮ww2 小时前
小模型「扛」住自由运镜:InSpatio-World 开源实时 4D 模拟器
人工智能·ai
Alkaid20772 小时前
【手搓 Agent 第2.1关】搭建 Agent 进阶能力:RAG(上)
人工智能·agent
AI_paid_community2 小时前
如何使用 Claude 在 AI 时代快速入局新的行业?(经验贴)
前端·javascript·后端
程序员韩星2 小时前
从模型直连到统一 AI 网关:多模型 API、Codex 与 Claude Code 接入实践
前端·程序员·ai编程
躺不平的理查德2 小时前
OpenCV Mat 备忘录
人工智能·opencv·计算机视觉
想要成为糕糕手2 小时前
🐎 从“幻觉”到“可控”:手把手构建一个 LLM 自优化流水线 Harness
前端·llm·agent
Days20502 小时前
剪纸风城市文旅宣传海报提示词分享
人工智能·ai作画·gpt-image
只会迪霸戈2 小时前
系统验收测试从3天缩短到3小时:用 TRAE Work 跑完30+接口并自动生成验收报告
人工智能