ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Harness Engineering:如何让AI代码生成过程真正可控

Harness Engineering:如何让AI代码生成过程真正可控 最近社区里被聊得最多的一个现象是AI 编码工具越来越强代码生成已经不是瓶颈让代码生成过程真正可控反而成了瓶颈。一个很常见的场景是你让 AI 助手帮你写一个“读取目录下 CSV 并生成统计报告”的脚本它很快就给出了一段可运行的 Python 代码本地测试也没有问题。但合并到主干后却发现这个脚本在一个不相关的环境里被调度执行了 200 次。原因不是模型写错了代码而是它运行在一个几乎没有行动边界、没有权限约束、没有审计记录的“自由环境”里。这个问题不是靠换一个更大的模型能解决的。真正解决问题的是 Harness Engineering也就是给模型戴上缰绳、划定边界、装好仪表盘和行车记录仪的系统工程。最近 DeepSeek Harness、Codex Harness、Agent Harness 等方向讨论热度很高很多读者私信问Harness 是不是又一个 Agent 框架它和 Agent 到底什么关系中小团队怎么用 Harness 来保证生成代码可控这篇文章会分三个层次展开先讲清楚概念Harness Engineering 是什么它和 Agent 的核心区别在哪里。再拆解实践生成可控代码必须做好的几类工程手段包括工具白名单、工作区隔离、沙箱执行、结构化输出、日志审计、插件扩展。最后给出一套最小可运行的 Harness 原型示例以及本地部署、插件开发和排错思路。看完这篇文章你会对“如何约束 AI 代码生成”形成一套可执行的框架而不是只知道几个概念名词。1. Harness Engineering 到底是什么“Harness” 的英文原意是马具、缰绳也常被翻译为“安全带”或“线束”。在 LLM 应用工程里它指的是围绕模型推理循环建立的一整套外部工程装置。单纯从模型视角看一个 LLM 的完整工作过程是用户输入 Prompt模型结合上下文内容逐 Token 生成输出。这个循环本身非常简单复杂的是外部系统怎么控制它、约束它、观测它。Harness 就是连接模型和真实世界的中间层它通常包含工具调用注册与白名单管理。工作区与文件访问限制。命令执行时的沙箱隔离。模型输出格式校验与安全过滤。完整调用链路的审计日志。插件扩展机制。为什么要加这一层我们可以对比两个流程。没有 Harness 时的典型流程是用户 Prompt - 模型直接调用工具 - 生成代码 - 结果交给使用者这个流程的问题很明显模型一旦产生工具调用所有的系统权限就等于交给了模型。模型读到的上下文如果被污染或者模型本身产生了幻觉就可能调用一个它根本不该调用的接口。而且这个过程中没有完整的日志出了事故也无法追踪。有 Harness 之后的流程是用户 Prompt - Harness 解析意图 - 查询允许的操作范围 - 模型在约束内调用工具 - 输出校验 - 审计日志 - 回滚机制同样是生成代码多了一个中间层之后模型能看到什么、能调用什么、能在哪一层沙箱中执行全部由工程策略决定而不是由模型自由决定。所以我认为 Harness 工程的核心判断可以概括为让代码生成可控的关键不在于找更强的模型而在于把模型放进一个设计过边界和安全策略的 Harness 中。2026 年再做 Agent 相关项目如果只关注模型能力而忽略 Harness 建设大概率会在上线后遇到权限失控、审计缺失和回滚困难的问题。2. Agent 与 Harness一个管“聪明”一个管“听话”很多读者会把 Agent 和 Harness 搞混原因也正常。因为你在使用一些开源 Agent 框架时会看到配置里既有工具、又有记忆、又有执行器看起来像是一个完整的“指挥系统”大家就把这套系统直接叫 Agent。但从工程视角划分两者关注的问题完全不同。对比维度AgentHarness核心问题让模型能自主规划、选择工具、反思改进让模型只能在允许范围内规划和执行关注点任务分解、推理策略、工具选择算法工具约束、沙箱边界、日志审计、输出校验抽象层级算法与行为层工程与基础设施层典型机制ReAct 循环、Plan-and-Execute、反思白名单、工作区、Schema 校验、审计追踪负责方向提升任务完成质量降低不可控风险一个常见的类比是Agent 是驾驶员Harness 是道路安全系统。驾驶员再厉害也需要红绿灯、车道线、限速牌和行车记录仪。反过来一个驾驶技术一般的驾驶员只要道路规则明确事故概率也会大幅下降。在真实开发里这个区别会直接影响排错方向。如果你的 Agent 经常选错工具那是推理策略问题需要调整 Agent 的规划逻辑。如果你的 Agent 总是调用到危险工具、读取到不该读取的文件、或者产生没有日志的意外操作那不是模型不够聪明而是 Harness 没设计好。从团队协作角度看两者也应该由不同角色关注算法工程师可能更关注 Agent 的推理效果而平台工程师需要关注 Harness 的可靠性、可观测性和安全边界。很多中小团队一开始只有一个人兼职做这两件事所以更容易混淆。但只要把概念边界搞清楚后续的配置、排错和扩展才会有正确方向。3. 可控代码生成的五大核心原则要把 Harness 真正落地到代码生成场景下面五个原则是必须考虑的。它们不是可选的优化项而是可控性的底线。3.1 原则一限制行动空间模型的动作空间应该是一个“最小集合”。凡是没显式允许的工具调用默认都禁止凡是不需要的文件系统路径默认都不可访问凡是不能确定安全性的网络请求默认都拦截。落地提示把所有工具调用收敛到注册表里显式列出 allow 和 deny 列表。不要让模型直接执行任意 Shell 命令除非你确认这条命令只能在沙箱里运行。3.2 原则二隔离工作区AI 生成代码时应该只读写一个被严格隔离的工作区目录。密钥、生产配置、数据库连接串、其他项目的代码都不应该出现在模型的上下文和可用路径里。落地提示在 Harness 层做路径解析与校验任何文件读写都必须经过工作区路径检查。发现目标路径跳出工作区时直接拒绝操作。3.3 原则三强制输出验证模型生成代码后不能直接当作有效代码使用。要先做格式校验、Schema 校验、静态检查、危险模式识别再决定是否进入下一步流程。落地提示输出验证可以拆成多层结构层看是否符合格式内容层看是否包含危险表达式行为层看是否调用了不合法接口最后再跑测试套件。3.4 原则四全程可观测每一次模型输入、每一次工具调用、每一个生成结果都必须记录进审计日志。没有日志就没有排查依据没有日志就无法回答“为什么模型会做这个操作”这个最基本的问题。落地提示日志要包含调用时间、调用方、工具名、参数摘要、调用结果、耗时、错误信息。生产环境里日志需要集中管理和定期归档。3.5 原则五可灰度可回滚AI 生成的代码不应该直接进入生产环境。它需要先落到评审分支经过静态扫描、单元测试、人工审核才能合并。一旦发现问题能够快速回滚到上一个稳定版本。落地提示在 Harness 设计阶段就预留快照和回滚接口否则等出了事故再补就来不及了。4. 六种实用的 Harness 落地实践原则要落地必须变成具体的工程实践。下面这六种做法是当前 Harness 工程里最常用、也是和“生成可控代码”关系最紧密的六个方向。4.1 工具注册表与白名单机制避免让模型直接调用系统命令这是可控性的起点。所有模型可调用的工具都应该在 Harness 启动时注册进一个工具注册表。注册表里需要包含工具名、参数 Schema、执行入口、权限要求、白名单标记、审计策略。具体做法是列出模型在任务中可能需要的所有操作例如读取文件、列出目录、运行测试、搜索代码。逐项判断这个操作是否必要是否可以用更小权限的替代方案把允许的操作加入白名单把可能造成破坏的操作加入拒绝列表。运行时在 call_tool 入口统一做一次检查绕过白名单的调用直接拒绝。这层机制解决的是模型“能做什么”的问题。如果工具注册表设计得好模型无法访问白名单之外的任何能力天然就能挡住大量随意操作。4.2 工作区与会话隔离AI 编码工具通常需要一个工作区用来存放生成的文件、临时文件、测试产物。Harness 需要保证模型只能在这个工作区内部写文件不能跳出去写别的目录。具体实现时可以在文件读写接口上加一层路径解析函数把相对路径转换成绝对路径再检查转换后的路径是否位于工作区根目录之下。所有结果路径必须经过这层校验否则拒绝操作。会话隔离则是指不同任务、不同用户、不同项目的上下文与文件不能互相干扰。一个会话中生成的文件另一个会话默认不可见。这样能避免模型在并发的多个任务中串号也能减少上下文污染带来的错误输入。4.3 沙箱执行如果模型需要执行命令比如运行测试或编译代码那么命令执行必须发生在沙箱内。沙箱的技术选型可以包括容器运行时、用户态隔离、进程资源限制等。这里有三个重点不要给沙箱挂载宿主机的真实敏感目录。不要把宿主机的 Docker Socket 挂载给 Agent否则 Agent 拥有创建特权容器的能力等于突破了隔离边界。沙箱内的网络权限也需要控制尽量只放行白名单域名。沙箱的意义是即使模型生成的代码被执行了它也只能影响沙箱内部无法扩散到真实系统。4.4 结构化输出与 Schema 校验很多模型调用失败的案例不是模型能力不足而是输出格式不稳定导致的。Harness 应该在系统层面强制模型输出结构化结果然后进行一次严格的 Schema 校验。常见做法是Prompt 中明确要求返回 JSON 或 YAML 格式。Harness 侧定义生成结果的 JSON Schema包含字段、类型、必填项约束。模型输出后先解析为结构体再做格式与类型校验。校验不通过时可以进行有限次重试或者将错误信息反馈给模型修正。这一步能解决“模型看起来生成了代码但实际上整个结构是乱的”这类问题。4.5 日志审计与复盘审计日志是最容易被忽略、但又是最重要的可控性保障。当前实际项目中好的审计日志应包括请求时间与请求 ID。Prompt 摘要与完整内容的归档位置。工具调用的参数快照。工具返回的结果摘要。输出校验是否通过以及失败原因。生成代码的去向与版本信息。有了日志之后任何一次工具调用异常都可以回放任何一次生成结果导致的线上问题都能找到原因。这是模型调试和事故复盘的基础。4.6 插件系统成熟的 Harness 通常提供插件机制允许团队在不修改核心代码的前提下扩展事件钩子。例如在模型准备调用工具之前做一个自定义安全检查在生成结果之后自动跑一次团队内部的规范扫描。插件系统的价值不是增加功能而是把团队的纪律约束注入到 Harness 的每一个关键节点。你可以在事件总线上挂载自定义动作on_tool_call工具调用前拦截。on_generated处理模型输出。on_error异常时执行自定义降级策略。这样核心 Harness 保持稳定而团队的个性化规则可以通过插件持续迭代。5. 最小可运行的 Harness 原型为了把上面的概念落到实践下面给出一个最小可运行的 Harness 原型。它不是一个成熟开源项目的官方 API而是用于学习“约束、工具、校验、日志”这四件事如何组合的最小示例。5.1 原型设计目标这个原型要演示三层控制工具白名单未被注册的工具无法调用。工作区路径限制所有文件写入必须位于工作区内。输出校验包含危险表达式的生成代码会被拒绝。5.2 目录结构demo-harness/ ├── demo_harness.py ├── harness_config.yaml ├── schemas/ │ └── generated_code.json ├── plugins/ │ └── example_plugin.py ├── workspace/ └── logs/5.3 配置文件harness_config.yamlharness: name: demo-dev-harness workspace: ./workspace log_dir: ./logs tools: allowed: - list_files - exec_command denied: - drop_database - delete_production llm: provider: local_model_or_remote_api temperature: 0.2 max_tokens: 1000 output: require_test_pass: true allow_fuzzy_schema: false这份配置的重点是tools.allowed和tools.denied两个列表。实际项目中这两个列表通常会由安全团队或项目负责人维护而不是由普通开发者随意修改。5.4 核心代码demo_harness.py demo_harness.py 一个用于教学的最小 Harness 原型。 演示了工具白名单、工作区路径限制、输出内容校验三层控制。 真实生产环境请使用成熟的 Harness 框架并配合沙箱、审计和权限体系。 import argparse import json import os import re from pathlib import Path from typing import Any, Callable, Dict, List try: import yaml except ImportError: raise SystemExit(missing dependency: pip install pyyaml) def load_config(path: str) - Dict[str, Any]: 从 YAML 文件读取 Harness 配置。 with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) class Harness: def __init__(self, config: Dict[str, Any]): self.config config self.workspace Path(config[harness][workspace]).resolve() self.workspace.mkdir(parentsTrue, exist_okTrue) self.log_dir Path(config[harness][log_dir]).resolve() self.log_dir.mkdir(parentsTrue, exist_okTrue) self._allowed_tools set(config[tools][allowed]) self._denied_tools set(config[tools][denied]) self._tool_funcs: Dict[str, Callable] {} def register_tool(self, name: str, func: Callable) - None: 只有白名单内的工具允许注册。 if name in self._denied_tools: raise PermissionError(ftool {name} is in denied list) if name not in self._allowed_tools: raise PermissionError(ftool {name} is not in allowed list) self._tool_funcs[name] func def call_tool(self, name: str, args: Dict[str, Any]) - Any: 统一工具调用入口执行白名单检查并记录审计日志。 if name not in self._allowed_tools or name not in self._tool_funcs: self.audit(denied, {tool: name, args: args}) raise PermissionError(ftool {name} not allowed or not registered) if name in self._denied_tools: self.audit(denied, {tool: name, args: args}) raise PermissionError(ftool {name} is denied) self.audit(call, {tool: name, args: args}) return self._tool_funcs[name](**args) def audit(self, action: str, detail: Dict[str, Any]) - None: 将关键事件写入审计日志。 record json.dumps( {action: action, detail: detail}, ensure_asciiFalse, ) log_file self.log_dir / harness.log with open(log_file, a, encodingutf-8) as f: f.write(record \n) def resolve_in_workspace(self, filename: str) - Path: 强制所有文件路径都落在工作区内部。 target (self.workspace / filename).resolve() if not target.is_relative_to(self.workspace): raise PermissionError(fwrite path {target} is outside workspace) target.parent.mkdir(parentsTrue, exist_okTrue) return target def validate_output(self, generated_code: str) - bool: 最小输出校验拒绝明显危险表达式。 dangerous_patterns [ rsubprocess\.Popen\(.*shellTrue, rimport\sos\.system, rrm\s-rf\s/, rDROP\sTABLE, ] for pattern in dangerous_patterns: if re.search(pattern, generated_code, re.IGNORECASE): self.audit(rejected, {reason: dangerous_pattern, pattern: pattern}) return False return True def tool_list_files(path: str .) - List[str]: 示例工具列出目录内容。 entries os.listdir(path) return entries[:20] def tool_exec_command(command: str) - str: 示例工具执行简单命令。 生产环境不要直接这样实现应该借助沙箱和进程隔离。 if rm in command or drop in command.lower(): raise PermissionError(command rejected by tool-level policy) import subprocess result subprocess.run( command, shellTrue, capture_outputTrue, textTrue, timeout10 ) return result.stdout[:2000] def main(): parser argparse.ArgumentParser(descriptiondemo harness) parser.add_argument(--config, defaultharness_config.yaml) parser.add_argument(--task, defaultlist files in workspace) args parser.parse_args() config load_config(args.config) harness Harness(config) # 注册工具 harness.register_tool(list_files, tool_list_files) harness.register_tool(exec_command, tool_exec_command) print( demo harness ) print(task:, args.task) print(allowed tools:, harness._allowed_tools) # 1. 正常工具调用 result harness.call_tool(list_files, {path: harness.workspace}) print(tool result:, result) # 2. 模拟一次白名单之外的调用应该被拒绝 try: harness.call_tool(drop_database, {sql: DROP TABLE users}) except PermissionError as exc: print(denied as expected:, exc) # 3. 模拟一次输出校验这段代码不会被真正执行只是验证校验逻辑 generated_code import subprocess subprocess.Popen(dangerous_command, shellTrue) if not harness.validate_output(generated_code): print(generated code rejected by output validator) else: print(generated code passed) if __name__ __main__: main()这个原型的核心逻辑集中在三个地方register_tool保证工具必须来自白名单。call_tool做统一入口拦截并记录审计日志。validate_output对生成代码做危险模式过滤。5.5 插件示例plugins/example_plugin.py plugins/example_plugin.py 一个简化的 Harness 插件示例演示事件钩子的扩展方式。 真实插件系统通常基于事件总线这里只展示核心思路。 from typing import Dict class ExamplePlugin: def __init__(self, harness): self.harness harness def on_tool_call(self, tool_name: str, args: Dict[str, str]): 在工具调用前执行自定义策略。 self.harness.audit(plugin_tool_call, {tool: tool_name}) if tool_name exec_command: command args.get(command, ) if secret in command.lower() or env in command: raise PermissionError(plugin blocked sensitive command) def on_generated(self, code: str) - str: 对模型生成结果做团队规范检查。 if len(code) 10: raise ValueError(generated code is too short, please regenerate) if TODO in code: self.harness.audit(plugin_todo_warning, {message: generated code contains TODO}) return code这个插件示例想表达的是插件机制可以把团队的纪律注入到 Harness 的关键节点而不需要修改核心代码。5.6 运行与验证准备环境cd demo-harness pip install pyyaml运行原型python demo_harness.py --config harness_config.yaml --task 生成一个 CSV 统计脚本预期输出大致是 demo harness task: 生成一个 CSV 统计脚本 allowed tools: {list_files, exec_command} tool result: [文件名列表...] denied as expected: tool drop_database not allowed or not registered generated code rejected by output validator验证要点有三个list_files能正常返回说明白名单工具可以调用。drop_database被拒绝说明白名单拦截生效。包含危险表达式的生成结果被校验器拒绝说明输出校验生效。同时可以在logs/harness.log里查看审计记录{action: call, detail: {tool: list_files, args: {path: /.../workspace}}} {action: denied, detail: {tool: drop_database, args: {sql: DROP TABLE users}}} {action: rejected, detail: {reason: dangerous_pattern, pattern: subprocess\\.Popen\\(.*shellTrue}}这个示例的价值在于它把 Harness 的最小闭环跑通了一遍配置、注册、调用、拦截、审计、校验。你可以把这个原型继续扩展成支持更多工具、更多校验规则、更多日志输出的工程原型。6. 本地部署与插件开发技巧6.1 为什么很多团队选择本地部署 Harness数据隐私和安全边界是最主要原因。如果把上下文、代码、内部 API 描述都发送到外部模型服务很多企业无法接受。本地部署可以做到模型调用保持在本地网络敏感数据不出内网。工具执行环境完全由企业控制方便对接内部系统。可以更精细地做审计避免第三方平台留存数据。当然本地部署也意味着要自己处理模型资源、依赖管理、升级维护等问题。对于还没有完整运维能力的团队建议先从单机原型开始不要一上来就搭建整套分布式平台。6.2 通用部署路径从社区常见实践看本地部署 Harness 通常有三条路径下载预编译产物适合只想快速体验的场景。源码构建适合需要深度定制和插件开发的团队。Docker 部署适合需要快速复现环境、方便迁移的场景。源码构建时最容易遇到的问题往往不是功能代码而是依赖版本。尤其是基于 Node.js 生态的项目不同版本的 pnpm、Node、lockfile 格式之间兼容性差异较大经常出现在pnpm install或者启动 Web 控制台时卡住的情况。如果遇到类似“卡在 pnpm dsh web”这样的问题通用排查顺序是检查 Node 版本是否满足项目要求。检查包管理器版本是否和 lockfile 匹配。检查依赖源是否可达必要时切换镜像源。清理缓存和临时目录后重试。尽量避免在生产环境直接使用最新版本先确认社区内是否已有兼容性报告。6.3 一个 Docker Compose 部署模板下面是一个通用的 Docker Compose 模板它演示的是“Harness 服务 工作区 日志目录”的部署形态具体镜像、端口和内容按照你的实际项目替换# docker-compose.yml通用模板按实际项目调整 services: harness-api: build: . ports: - 8080:8080 volumes: - ./workspace:/app/workspace - ./logs:/app/logs environment: LOG_LEVEL: debug SANDBOX_ENABLED: true MAX_CONTEXT_TOKENS: 16000 restart: unless-stopped部署时要注意几个点工作区目录和日志目录必须挂载持久化卷否则容器重启后数据丢失。环境变量中的SANDBOX_ENABLED建议一直保持true。如果 Harness 需要调用本地模型服务需要通过 extra_hosts 或网络配置保证容器能访问内网模型地址。6.4 插件开发套路插件开发通常是团队扩展 Harness 能力的首选方式。通用套路是找到 Harness 提供的事件钩子文档确认你要注入的是哪个阶段。编写插件类实现对应事件方法。在 Harness 配置中声明插件路径启动时扫描并注册。为插件编写单独的日志前缀方便与核心日志区分。先在小规模任务上验证再放开到全量任务。插件不是越多越好。每增加一个插件就增加
返回列表