AutoHarness 实战指南:用树搜索与沙箱为 LLM 代理自动生成代码 Harness

让大语言模型(LLM)乖乖听话并不容易。在代理架构中,模型经常会生成不合法的动作或破坏环境的代码,过去我们靠手工写规则去约束它,费时费力且难以覆盖所有边界情况。AutoHarness 这个 Rust 库直接换了个思路:用树搜索结合 Thompson 采样,自动为 LLM 代理合成代码 harness,把约束代码的生成和优化变成一个自动迭代的过程。根据实际测试,平均只需 14.5 次迭代就能达到 100% 的合法动作率。

为什么我们需要自动生成代码 Harness?

Harness 的核心作用是给 LLM 代理套上一层安全且合法的动作约束。手工写这些约束代码经常跟不上业务变化,而 AutoHarness 提供了一套自动化方案,让小模型加上合适的 harness,效果甚至能超过没有 harness 的大模型。

AutoHarness 支持三种 harness 模式,分别对应不同的约束场景:

  • 过滤器:在动作执行前进行拦截。如果 LLM 生成了一步非法的棋,过滤器直接把它挡在门外,不让环境状态被污染。
  • 验证器:侧重于对状态本身进行校验。环境状态发生改变后,验证器检查当前局面是否符合游戏规则或业务逻辑。
  • 策略 harness:不只是拦截或验证,它直接参与动作的生成与推荐,引导 LLM 走向更合法的策略空间。

这三种模式覆盖了从简单拦截到复杂策略引导的全链路需求。我一开始以为自动生成的约束代码会很死板,容易误杀正常动作,但看了它在 145 场 TextArena 游戏中跑出的 100% 合法动作率,这种基于树搜索的动态生成方案确实管用。

怎么在五分钟内把 AutoHarness 跑起来?

部署开发工具最怕的就是环境依赖地狱,AutoHarness 在安装这块做得比较干脆,提供了一键脚本和标准的 Cargo 包引入方式。

对于 macOS Intel (x86_64) 用户,直接在终端跑一键安装脚本就行:

curl -fsSL https://raw.githubusercontent.com/gyc567/AutoHarness/main/install/install.sh | bash

如果 GitHub 的 raw 链接拉取速度慢,可以换用 jsDelivr CDN,下载速度会快很多:

curl -fsSL https://cdn.jsdelivr.net/gh/gyc567/AutoHarness@main/install/install.sh | bash

跑完脚本后,执行 autoharness --version 验证是否安装成功。默认情况下,二进制文件会放在 ~/.local/bin/autoharness。记得把这个路径加到系统环境变量里,否则终端会提示找不到命令:

export PATH="$HOME/.local/bin:$PATH"

如果你不想用脚本,或者你用的是需要自行编译的 Linux x86_64 和 Windows x86_64 平台,可以通过克隆仓库手动安装。macOS Apple Silicon (ARM) 暂时没有原生包,但能使用 x86_64 兼容版正常运行。

手动安装的命令如下:

git clone https://github.com/gyc567/AutoHarness.git
cd AutoHarness/install
chmod +x install.sh
./install.sh

安装脚本支持几个常用参数,需要卸载时执行 ./install.sh uninstall,忘了怎么用就敲 ./install.sh --help

如果你是在 OpenCode 或 CloudCode 环境里干活,可以直接复制粘贴下面这句话,让系统自动启动并设计 Harness 工程系统:

现在用 AutoHarness 这个 CLI:https://github.com/gyc567/AutoHarness 对本项目进行设计 Harness 工程系统。

对于 Rust 开发者,还可以直接在项目的 Cargo.toml 文件里引入依赖:

[dependencies]
autoharness = "0.1.0"

在 Rust 项目里怎么集成 AutoHarness?

集成 AutoHarness 的关键在于定义好状态、动作和评估器,然后把它们喂给合成引擎。引擎会根据你提供的初始代码片段,不断变异和搜索,最终吐出一份优化好的 harness 代码。

先来看一段完整的基础用法代码。这段代码模拟了一个棋盘游戏环境,定义了游戏状态和四种移动动作:

use autoharness::core::{State, Action, Harness, HarnessType};
use autoharness::engine::{CodeSynthesisEngine, SynthesisConfig, Evaluator};
use autoharness::sandbox::{SandboxExecutor, SandboxConfig};

// 定义你的状态
#[derive(Debug, Clone, serde::Serialize)]
struct GameState {
    board: Vec<Vec<i32>>,
    score: i32,
}

impl State for GameState {
    fn to_prompt(&self) -> String {
        format!("Board: {:?}, Score: {}", self.board, self.score)
    }

    fn validate(&self) -> autoharness::core::Result<()> {
        Ok(())
    }
}

// 定义你的动作
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
enum GameAction {
    MoveUp,
    MoveDown,
    MoveLeft,
    MoveRight,
}

impl Action for GameAction {
    fn to_string(&self) -> String {
        format!("{:?}", self)
    }

    fn from_string(s: &str) -> autoharness::core::Result<Self> {
        match s {
            "MoveUp" => Ok(GameAction::MoveUp),
            "MoveDown" => Ok(GameAction::MoveDown),
            "MoveLeft" => Ok(GameAction::MoveLeft),
            "MoveRight" => Ok(GameAction::MoveRight),
            _ => Err(autoharness::core::HarnessError::action_parse("Unknown action")),
        }
    }
}

在这段代码里,GameState 实现了 State trait。它的 to_prompt 方法负责把当前棋盘和分数格式化成字符串,方便 LLM 理解当前环境。GameAction 实现了 Action trait,负责动作的序列化和反序列化。如果传进来的字符串不是预定义的四种动作,from_string 会直接抛出解析错误。

接下来是评估器和引擎的调用逻辑。评估器是整个系统的裁判,它负责给生成的 harness 代码打分:

// 创建自定义评估器
struct GameEvaluator;

impl Evaluator for GameEvaluator {
    fn evaluate(&self, code: &str) -> autoharness::engine::Result<f64> {
        // 评估 harness 代码
        // 返回 0.0 到 1.0 之间的分数
        if code.contains("is_legal_action") {
            Ok(0.8)
        } else {
            Ok(0.2)
        }
    }
}

// 合成 harness
fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = SynthesisConfig::new()
        .with_max_iterations(20)
        .with_convergence_threshold(0.95);

    let mut engine = CodeSynthesisEngine::new(config);
    let evaluator = GameEvaluator;

    let initial_code = r#"
        def is_legal_action(state, action):
            # TODO: 实现验证逻辑
            return True
    "#;

    let optimized_code = engine.synthesize(initial_code, &evaluator)?;
    println!("优化的 harness:\n{}", optimized_code);

    Ok(())
}

这里的 GameEvaluator 逻辑很直白:如果生成的代码里包含 is_legal_action 字样,给 0.8 分,否则只给 0.2 分。引擎会以这个分数为导向,利用树搜索不断调整代码,直到分数达到配置的收敛阈值 0.95,或者跑满 20 次迭代。

AutoHarness 内部的树搜索和沙箱机制是怎么运转的?

AutoHarness 的高效和安全,靠的是引擎模块的树搜索策略和沙箱模块的硬性隔离。搜索算法负责找对方向,沙箱负责兜底防爆破,两者缺一不可。

整个架构由四个核心模块构成:

  • Core 模块:定义了 StateActionHarness 等基础数据模型和接口。
  • Engine 模块:核心的代码合成引擎,内部集成了树搜索算法。
  • Sandbox 模块:提供安全的代码执行环境,所有的代码变异都会在这里跑。
  • Feedback 模块:收集沙箱的执行反馈并整合给引擎,指导下一轮搜索。

核心接口与合成引擎

Core 模块里定义了三个关键 trait。State 需要实现序列化、克隆以及线程安全传递,核心方法包括 to_promptvalidateAction 同样需要满足序列化和线程安全,核心是 to_stringfrom_string 这对转换方法。Harness trait 则是所有约束类型的统一接口,必须实现 harness_typeevaluatepropose_actions

CodeSynthesisEngine 是搜索过程的总调度。它内部持有一棵搜索树 (SearchTree)、配置项 (SynthesisConfig) 和统计信息 (SynthesisStats)。调用 synthesize 方法时,传入初始代码和评估器,引擎就会开始跑树搜索。搜索结束后,可以通过 get_best_code 方法拿到历史最优的代码节点。

引擎自适应优化的关键在于 Thompson 采样。它能在探索未知代码空间和利用已知高分代码之间找到平衡。这种机制让引擎不必盲目穷举,平均跑 14.5 次就能收敛。

沙箱执行的安全底线

让 LLM 生成的代码直接在宿主机上跑是非常危险的。AutoHarness 的 SandboxExecutor 把所有代码执行都关在隔离进程里。

SandboxConfig 提供了非常细致的资源控制选项:

pub struct SandboxConfig {
    pub memory_limit_mb: u64,          // 默认: 256
    pub time_limit_ms: u64,            // 默认: 5000
    pub max_file_descriptors: u32,     // 默认: 64
    pub max_output_size: usize,        // 默认: 10MB
    pub enable_network: bool,          // 默认: false
    pub working_directory: Option<PathBuf>,
    pub environment_variables: HashMap<String, String>,
}

我以前跑 LLM 生成的代码没限制内存,结果一段死循环分配内存的代码直接让整个测试服务器 OOM 宕机。AutoHarness 默认只给 256MB 内存和 5 秒执行时间,文件描述符限制在 64 个,网络访问默认关闭。这些默认值就是安全底线。除了资源限制,沙箱还实现了系统调用过滤,只放行必要的系统调用,超时进程会被强制干掉,执行前还会做输入验证。

如何根据业务场景调整合成与沙箱配置?

默认配置能应付大多数情况,但如果你跑的是复杂逻辑或者对收敛精度要求极高,就得手动调参。配置分合成引擎配置和沙箱配置两块,调参的核心是在效果和耗时之间找平衡。

合成引擎参数调优

基础的引擎配置比较保守,适合跑常规任务:

use autoharness::engine::SynthesisConfig;

let config = SynthesisConfig::new()
    .with_max_iterations(20)
    .with_convergence_threshold(0.95)
    .with_max_depth(10);

如果业务规则极其复杂,基础配置跑不出满分 harness,可以上高级配置:

use autoharness::engine::SynthesisConfig;

let config = SynthesisConfig::new()
    .with_max_iterations(50)
    .with_convergence_threshold(0.99)
    .with_max_depth(15)
    .with_mutations_per_node(5)
    .with_exploration_constant(2.0)
    .with_adaptive_sampling(true)
    .with_target_iterations(30)
    .with_min_improvement(0.005)
    .with_max_nodes(2000);

高级配置把最大迭代次数拉到了 50,收敛阈值逼到 0.99。max_depth 设为 15 意味着搜索树可以扎得更深,适合挖掘边界条件复杂的逻辑。每个节点的变异次数从默认的 3 次提到 5 次,探索常数设为 2.0,鼓励引擎多去试错。同时开启了自适应采样,目标迭代数设为 30。为了防止搜索树无限膨胀,节点数上限卡在 2000 个,最小改进阈值设为 0.005,如果优化幅度太小就提前停掉,节省算力。

沙箱环境定制

沙箱的配置同样可以根据生成代码的预期行为来调整。比如有些代码需要处理较大的数据集,256MB 内存可能不够用:

use autoharness::sandbox::SandboxConfig;

let config = SandboxConfig::new()
    .with_memory_limit(512)
    .with_time_limit(10000)
    .with_max_file_descriptors(128)
    .with_max_output_size(20 * 1024 * 1024)  // 20MB
    .with_network(false);

这里把内存放宽到 512MB,执行时间给到 10 秒,文件描述符翻倍到 128 个,最大输出体积允许到 20MB。网络依然保持关闭,生成 harness 代码不需要联网。

项目怎么实现代码自主改进和测试验证?

代码写完不是终点,AutoHarness 引入了 GOAL.md 模式来实现项目代码的自主改进,同时配备了严格的测试和质量打分机制。

跑测试套件直接用标准 Rust 命令:

cargo test

如果你想单独跑合成逻辑或沙箱逻辑的测试,可以指定测试名称:

cargo test test_synthesis
cargo test test_sandbox

在项目维护层面,AutoHarness 用 GOAL.md 模式驱动代码自主改进。你可以跑 ./scripts/score.sh 脚本,看看当前项目的代码质量得分。目前项目的综合得分是满分 100 分:格式检查 20 分,clippy 静态检查 20 分,测试覆盖 25 分,文档 15 分,可维护性 20 分,安全检查 7 分(满分 10 分)。唯一扣分项在安全这块,可能是因为部分极端场景的边界处理还有提升空间。

项目里还维护了几个关键文件,GOAL.md 定义了改进目标,CLAUDE.md 是给 Agent 看的行动指南,template/GOAL.md 提供了模板,docs/goal-md/tutorial-cn/ 目录下有完整的中文教程索引和 5 分钟快速入门文档。

实用摘要 / 操作清单

  • 安装 AutoHarness 首选一键脚本:curl -fsSL https://cdn.jsdelivr.net/gh/gyc567/AutoHarness@main/install/install.sh | bash
  • 安装后记得把 ~/.local/bin 加入系统 PATH 变量。
  • Apple Silicon 芯片使用 x86_64 兼容版,Linux 和 Windows 需手动编译。
  • Rust 项目集成时,在 Cargo.toml 加入 autoharness = "0.1.0"
  • 必须实现 StateAction trait,为引擎提供环境状态和动作的序列化能力。
  • 编写 Evaluator 时,根据生成的 harness 代码特征返回 0.0 到 1.0 之间的分数。
  • 复杂任务调高 SynthesisConfigmax_depthmutations_per_node,别盲目拉高 max_iterations
  • 生成代码执行前,确保 SandboxConfig 中的内存和时间限制符合业务需求,网络权限非必要不开启。
  • cargo test 跑测试,用 ./scripts/score.sh 看代码质量打分。

一页速览

  • 工具定位:Rust 编写的 LLM 代理代码 harness 自动合成库。
  • 核心算法:树搜索 + Thompson 采样,平均 14.5 次迭代收敛。
  • 三种模式:过滤器、验证器、策略 harness。
  • 四大模块:Core、Engine、Sandbox、Feedback。
  • 安全机制:沙箱隔离、资源限制、系统调用过滤、强制超时。
  • 性能结论:小模型 + harness > 大模型无 harness。
  • 测试得分:代码质量综合评分 100/100。

常见问题 (FAQ)

1. AutoHarness 支持哪些操作系统和架构?
目前 macOS Intel (x86_64) 有原生支持,macOS Apple Silicon (ARM) 使用 x86_64 兼容版。Linux 和 Windows 的 x86_64 架构需要用户自行从源码编译。

2. 默认安装路径在哪?怎么让系统识别命令?
默认安装在 ~/.local/bin/autoharness。需要在 shell 配置文件中添加 export PATH="$HOME/.local/bin:$PATH",或者直接在当前终端执行该命令。

3. LLM 生成的代码直接跑在沙箱里安全吗?
比较安全。AutoHarness 的沙箱做了多重隔离,包括 256MB 默认内存限制、5 秒超时强制终止、系统调用过滤以及网络默认禁用,能挡住大部分恶意或异常代码。

4. 引擎跑多少次迭代才能生成可用的 harness?
根据项目测试数据,在 145 场 TextArena 游戏中,平均只需 14.5 次迭代就能达到 100% 的合法动作率。默认最大迭代次数设为 50 次。

5. 评估器返回的分数有什么要求?
自定义的 Evaluator 必须返回 0.0 到 1.0 之间的浮点数。分数越高代表 harness 代码质量越好,引擎会根据这个分数引导树搜索的方向。

6. 如果搜索过程耗时太长怎么办?
检查 SynthesisConfig 配置。可以降低 max_depthmutations_per_node,也可以调高 min_improvement 阈值,让引擎在优化幅度变小时提前停止搜索。

7. 项目怎么进行代码质量评估?
项目使用 GOAL.md 模式驱动改进。运行 ./scripts/score.sh 脚本即可查看代码质量评分,涵盖格式、clippy、测试、文档、可维护性和安全性六个维度。

8. 在 OpenCode/CloudCode 中怎么快速启动?
直接在工作区粘贴这句话:“现在用 AutoHarness 这个 CLI:https://github.com/gyc567/AutoHarness 对本项目进行设计 Harness 工程系统。”系统会自动接管后续流程。