资讯动态

Harness实践:让大模型代码生成变得可控可校验

发布时间:2026/9/2 10:09:37 来源:尧图企业网站定制
不知道你有没有碰到过这种场景让大模型生成一段代码它给出的结果看起来结构完整、注释齐全但一旦放进真实项目里就会出现格式不匹配、语法报错、依赖缺失甚至整体风格与你团队规范完全不一致。如果只是单次问答这种问题影响不大但如果是在自动化代码生成、代码评审、数据管道脚本生成等工程场景里这就会变成巨大的稳定性隐患。我最近在推进一个内部代码生成项目时对这个问题体会很深。项目初期我们把大量精力花在优化提示词上反复调整请你生成一个函数请使用 Python 编写之类的描述结果生成质量依然不稳定。后来团队把视角从怎么把模型问好切换成怎么在模型外面搭一套工程约束框架事情才开始出现转机。这套约束框架就是本文要聊的 Harness 实践。本文会围绕 Harness 的概念、核心原理和可落地的代码生成约束模式展开并通过三个可运行的 Python 示例逐步演示如何把一段裸的大模型输出改造成可控、可校验、可重试的工程化代码。文章适合正在做 AI 应用落地的后端工程师也适合对 AI 工程化感兴趣、希望把模型能力真正接进业务系统的同学。1. 背景与核心概念1.1 为什么生成代码容易生成可控代码很难大模型生成代码本身并不难难的是让它在工程环境里稳定产出符合预期的代码。这里有一个容易被忽略的事实大模型本质上是语言模型它学习的是文本分布而不是工程约束。它知道一段 Python 代码大概长什么样但它并不天然知道你所在项目的目录结构、依赖版本、命名规范、日志规范和异常处理要求。举个例子你让模型生成一个 HTTP 接口它可能给你返回requests.get的实现但团队标准是使用httpx.AsyncClient。你让它写一个数据库查询它可能默认使用 SQLAlchemy 的方式但项目里统一使用的是 MyBatis 或原生psycopg。这类偏好在提示词里写一次两次可以但没法保证每次生成都稳定遵循。更麻烦的是模型输出具有随机性。同一段提示词多次调用可能得到不同结果。这在对话场景中是灵活在生产场景中就是不稳定。可控代码的核心诉求正是通过模型外部的约束机制把这种随机性压缩到可接受的范围。1.2 Harness 是什么Harness 在英文里有马具、挽具的含义引申为约束装置、控制框架。在 AI 工程领域Harness 通常指围绕模型调用构建的一整套工程化约束层它负责把模型的输入、输出、校验、反馈、重试、记录等环节整合成一条可控链路。用更通俗的话说没有 Harness 时模型是直接裸露在业务代码里的返回什么我们就用什么有了 Harness 后模型的每次输出都会经过 约束 → 校验 → 反馈 → 修正 的闭环只有满足预设条件的输出才会进入业务系统。近年来围绕 DeepSeek、Codex 等模型的社区工具中也出现了大量被命名为 harness 的项目。它们做的事情通常是封装模型 API、提供外部工具调用能力、约束模型输出格式、执行结果校验与自动修复。这也从一个侧面说明Harness 已经成为大模型应用从能跑走向可控的重要工程模式。1.3 Harness 与 Agent 的区别很多人会把 Harness 和 Agent 混在一起实际上两者解决的是不同层面的问题。Agent 的核心是自主决策。它根据目标自己规划步骤、选择工具、决定何时结束强调模型的推理和行动能力。Harness 的核心是约束与控制。它强调的是在模型外部设定边界让输出结果可预期、可校验。用我自己的理解来对比维度HarnessAgent关注点输出的正确性与可控性目标的规划与执行控制权框架主导模型按预设路径输出模型主导框架提供工具支持适用场景代码生成、结构化输出、批量任务自主问答、复杂任务拆解、多工具协作风险偏好偏向低风险宁可重试也不放过异常偏向高灵活度允许探索性路径对结果的要求强校验结果需要满足规则或 Schema弱校验更关注任务完成度实际工程中两者并不是互斥的。比较稳妥的做法是先建立 Harness 的约束和校验闭环再在闭环之上逐步引入 Agent 的自主能力。直接在裸调用上构建 Agent往往会出现Agent 看似聪明但错误难以追踪的情况。2. 环境准备与版本说明在开始写代码之前先把环境准备说明白。本文所有示例都基于 Python 3.10 开发使用jsonschema库做 JSON Schema 校验。模型调用部分会先以模拟客户端的方式演示生产环境可以替换成你自己团队内部的模型网关地址或任意模型 API。2.1 运行环境操作系统Windows 10/11、macOS、Linux 均可。Python 版本3.10 或以上。示例代码中使用了list[dict]这类类型注解语法低版本需要改成List[dict]。包管理工具pip 或 poetry 均可。如果你使用的是 Python 3.8 或 3.9需要把类型注解调整为兼容写法同时安装from __future__ import annotations。建议直接使用 Python 3.10代码更简洁。2.2 依赖安装创建一个新的虚拟环境并安装以下依赖python -m venv .venv source .venv/bin/activate # Windows 下使用 .venv\Scripts\activate pip install jsonschemajsonschema库用于校验模型返回的 JSON 是否符合结构要求。如果你只是做代码语法校验Python 内置的ast模块就够用了不需要额外安装依赖。2.3 示例项目结构为了便于阅读这里规划一个简单的项目结构harness_practice/ ├── harness_mini.py # 实战一最小校验型 Harness ├── harness_json.py # 实战二结构化输出 Harness ├── harness_execute.py # 实战三可执行代码沙箱 Harness └── common.py # 公共工具函数下面三个实战案例会对应这三个文件建议按顺序看完因为后一个案例会复用前一个案例中的函数。3. 可控代码的核心链路拆解在写具体代码之前先拆解 Harness 的核心链路。理解了这条链路后面再去看代码就会轻松很多。一套典型的代码生成 Harness至少包含三个环节约束输入、校验输出、反馈闭环。3.1 约束输入提示词与输出格式设计很多人以为提示词只是给模型讲清楚要做什么但在 Harness 里提示词还承担着输出格式约束的作用。我比较推荐的做法是在提示词中明确要求模型使用 Markdown 代码块包裹代码并且不允许输出解释性文字。这样便于后续用正则表达式把代码提取出来。更严格的场景还可以要求模型输出 JSON并给出 JSON Schema 作为参考。举个例子你是一个严谨的 Python 代码生成助手。 必须输出如下格式 python 完整可运行的 Python 代码不允许输出任何多余解释。这种提示词写法看起来简单但能显著减少后续解析的麻烦。与之配套的是在提示词中尽量给出具体需求背景、输入输出示例、约束条件而不是只丢一句话给模型。 ### 3.2 校验输出语法、Schema、静态规则 模型给出输出后Harness 不能直接信任必须做程序化校验。校验的层级由浅到深可以分为三种 - 格式校验模型输出是否为合法的代码块或 JSON。 - 语法校验代码能否通过 AST 解析或编译器检查。 - 语义校验代码是否满足业务规则比如函数名是否符合规范、是否引用了不允许的依赖、是否包含危险操作。 格式校验通常用正则或 JSON 解析库语法校验用 Python 的 ast 模块就能实现语义校验属于工程定制部分需要根据团队规范编写规则。 ### 3.3 反馈闭环错误信息回填与重试策略 校验失败后不能直接把失败结果丢掉而是要把失败原因以结构化方式反馈给模型让模型在下一轮生成中修正。 这也是 Harness 中闭环的关键含义。比如语法错误发生在第 3 行就把第 3 行存在 SyntaxError作为新提示词的一部分传给模型JSON 缺少字段就把缺失字段告诉我模型并要求其重新输出。 重试次数必须有限制。我通常设置为 3 次到 5 次。超过次数后直接抛出异常防止模型无限循环消耗资源。重试失败不算 bug而是一个应该被记录和告警的业务事件。 ## 4. 实战一最小校验型 Harness 原型 先从最简单的原型开始。这个 Harness 可以完成三件事提取模型输出中的 Python 代码块、用 ast 校验语法、失败后把错误回灌给模型重试。 ### 4.1 定义模型客户端接口 为了便于测试我们先定义一个统一接口并提供一个模拟客户端。这个客户端第一次调用时故意返回语法错误的代码第二次返回正确的代码这样可以直观看到重试闭环的效果。 python # harness_mini.py from typing import Protocol class LLMClient(Protocol): def generate(self, prompt: str) - str: 根据提示词生成文本。 ... class FakeLLMClient: def __init__(self): self.count 0 def generate(self, prompt: str) - str: self.count 1 if self.count 1: return python\ndef add(a, b)\n return a b\n return python\ndef add(a, b):\n return a b\n这里把模型客户端抽象成Protocol是为了将来接入真实模型时不需要改动 Harness 主逻辑。FakeLLMClient演示了首次失败、重试成功的过程。4.2 实现代码块解析与语法校验接下来写两个工具函数。第一个从 Markdown 文本中提取 Python 代码块第二个用ast.parse做语法校验。# harness_mini.py import ast import re def extract_code_from_markdown(text: str) - str: 解析模型输出中的 python 代码块如果没有代码块则返回去除首尾空白的原始文本。 pattern re.compile(r(?:python)?\s*\n(.*?), re.S) match pattern.search(text) if match: return match.group(1) return text.strip() def validate_python_code(code: str) - tuple[bool, str]: 使用 ast 模块做语法校验返回 (是否通过, 错误信息)。 try: ast.parse(code) return True, except SyntaxError as e: line_no e.lineno if e.lineno is not None else 未知 return False, f第 {line_no} 行: {e.msg}extract_code_from_markdown中用到的正则r(?:python)?\s*\n(.*?)匹配以 包裹的代码块并支持python语言标记。re.S表示让.能匹配换行符。validate_python_code把代码交给ast.parse。如果语法错误Python 会抛出SyntaxError我们从中提取行号和错误信息这比直接把完整异常文本丢给模型更友好。4.3 编写重试主循环核心 Harness 类就写在这里。它负责拼装提示词、调用模型、解析代码、校验语法并在失败时把错误信息反馈给模型。# harness_mini.py class CodeGenHarness: def __init__(self, client: LLMClient, max_retries: int 3): self.client client self.max_retries max_retries def run(self, user_prompt: str) - str: system_prompt ( 你是一个严谨的 Python 代码生成助手。\n 必须输出如下格式\n python\n 完整可运行的 Python 代码\n \n 不允许输出任何多余解释。 ) full_prompt f{system_prompt}\n\n用户需求{user_prompt} for attempt in range(1, self.max_retries 1): raw self.client.generate(full_prompt) code extract_code_from_markdown(raw) ok, error validate_python_code(code) if ok: return code # 把错误信息回灌给模型要求其修复 fix_prompt ( f你上一次生成的代码存在语法错误\n{error}\n f错误代码\npython\n{code}\n\n f请重新生成修复后的完整代码。 ) full_prompt fix_prompt raise RuntimeError(f经过 {self.max_retries} 次尝试仍未生成合法 Python 代码)这段代码的核心逻辑集中在run方法中。每一次尝试都完成生成 → 解析 → 校验的循环失败后构造新的修复提示词。注意修复提示词里同时携带了错误信息和错误代码这比只告诉模型你错了有效得多。4.4 运行与结果说明在__main__中跑一次完整流程# harness_mini.py if __name__ __main__: client FakeLLMClient() harness CodeGenHarness(client, max_retries3) result harness.run(写一个两数相加的函数) print(最终生成代码) print(result) print(f模型调用次数{client.count})预期输出最终生成代码 def add(a, b): return a b 模型调用次数2第一次生成的代码缺少冒号导致SyntaxErrorHarness 把错误反馈给模型第二次生成正确代码并返回。这就是一个最基础的 Harness 闭环。5. 实战二结构化输出 Harness代码生成之外另一个常见场景是让模型输出 JSON 配置、接口定义、测试用例等结构化内容。这时候Harness 的校验重点从语法正确性变成 Schema 符合性。5.1 为什么用 JSON SchemaJSON Schema 是一种用于描述 JSON 数据结构的规范。它定义了一个 JSON 对象应该包含哪些字段、字段类型是什么、哪些字段必填、枚举值有哪些等。在可控代码生成中JSON Schema 的价值在于让模型的输出从看起来像 JSON变成严格符合业务预期。比如我们需要模型返回一个分页查询结果直接返回 JSON 是不够的必须保证page是正整数、status是枚举值、name是非空字符串。这些约束都可以写进 Schema。5.2 实现 JSON 输出校验闭环下面的代码实现了一个通用 JSON Schema Harness# harness_json.py import json from typing import Any try: from jsonschema import validate, ValidationError except ImportError: raise ImportError(缺少依赖请先执行 pip install jsonschema) class JsonHarness: def __init__(self, client, schema: dict[str, Any], max_retries: int 3): self.client client self.schema schema self.max_retries max_retries def _parse_json(self, raw: str) - Any: try: return json.loads(raw) except json.JSONDecodeError as e: return e def run(self, prompt: str) - Any: system_prompt ( 你是一个结构化输出助手。\n 必须只输出一个合法的 JSON 对象不允许包含 Markdown 代码块\n fJSON 必须满足以下 Schema\n{json.dumps(self.schema, ensure_asciiFalse, indent2)}\n ) full_prompt f{system_prompt}\n用户需求{prompt} for attempt in range(1, self.max_retries 1): raw self.client.generate(full_prompt) parsed self._parse_json(raw) if isinstance(parsed, json.JSONDecodeError): fix_prompt ( f你上一次输出不是合法 JSON{parsed.msg}。\n 请重新输出一个合法的 JSON 对象。 ) full_prompt fix_prompt continue try: validate(instanceparsed, schemaself.schema) return parsed except ValidationError as e: path /.join(str(p) for p in e.path) or $ fix_prompt ( f你上一次输出的 JSON 不符合 Schema{e.message}\n f错误路径{path}\n 请重新输出符合 Schema 的 JSON 对象。 ) full_prompt fix_prompt raise RuntimeError(f经过 {self.max_retries} 次尝试仍未获得符合 Schema 的 JSON)与第一个示例相比这个 Harness 新增了两种失败分支JSON 解析失败和 Schema 校验失败。两种分支都需要把错误信息转化为下一次生成的提示词形成闭环。5.3 运行与结果说明下面用一个演示客户端模拟模型输出 JSON# harness_json.py if __name__ __main__: class DemoClient: def generate(self, prompt: str) - str: # 生产环境中替换为真实模型调用 return {name: 用户模块, status: active, page: 1} schema { type: object, properties: { name: {type: string}, status: {type: string, enum: [active, inactive]}, page: {type: integer, minimum: 1} }, required: [name, status, page] } harness JsonHarness(DemoClient(), schema) result harness.run(解析一段配置) print(result)运行后输出{name: 用户模块, status: active, page: 1}如果模型返回的 JSON 缺失page字段Harness 会捕获ValidationError并把错误路径$和错误信息传回给模型要求其补齐字段。这个机制在批量数据处理、接口参数生成、配置项生成等场景中非常实用。6. 实战三把 Harness 扩展到可执行代码沙箱语法校验通过不代表代码真的能运行。对于自动化代码生成平台来说更进一步的 Harness 是执行验证——在受控环境中实际运行模型生成的代码用真实运行结果作为反馈。6.1 从语法校验到运行校验语法校验只能保证代码符合语言规则无法发现运行时错误比如变量未定义、函数调用参数错误、类型不匹配等。要想让模型生成的代码具备真正的可执行性必须增加运行时校验环节。这一步的关键是受控。模型生成的代码不可信不能在宿主环境直接运行否则可能出现恶意操作或意外资源消耗。6.2 在临时目录中执行模型代码下面这个示例使用临时目录加子进程的方式执行模型生成的代码并通过超时限制防止死循环# harness_execute.py import subprocess import tempfile from pathlib import Path class ExecuteHarness: def __init__(self, client, max_retries: int 3, timeout: int 10): self.client client self.max_retries max_retries self.timeout timeout def run(self, requirement: str) - str: system_prompt ( 你是一名可以生成可执行 Python 脚本的助手。\n 只输出 Python 代码使用如下格式包裹\n python\n代码\n\n 代码中不要包含任何交互输入不要包含无限循环。 ) prompt f{system_prompt}\n需求{requirement} for attempt in range(1, self.max_retries 1): raw self.client.generate(prompt) code extract_code_from_markdown(raw) ok, error validate_python_code(code) if not ok: prompt f语法错误{error}\n请重新生成完整代码。 continue with tempfile.TemporaryDirectory() as tmp: script Path(tmp) / gen_code.py script.write_text(code, encodingutf-8) try: proc subprocess.run( [python, str(script)], capture_outputTrue, textTrue, timeoutself.timeout, ) except subprocess.TimeoutExpired: prompt ( 代码执行超时请检查是否存在死循环或长时间阻塞。\n 请重新生成更高效的代码。 ) continue if proc.returncode 0: return code, proc.stdout prompt ( f代码运行报错\n{proc.stderr[:500]}\n 请分析错误并重新生成修复后的完整代码。 ) raise RuntimeError(f经过 {self.max_retries} 次尝试代码仍无法通过执行校验)这个类的写法和前面的 Harness 保持一致但在校验阶段调用了subprocess.run。如果模型代码能正常运行并返回状态码 0就认为代码通过校验。6.3 安全边界提示这里必须再次强调直接执行模型生成的代码存在严重安全风险。模型可能生成删除文件、读写系统目录、下载远程脚本等危险操作。即使没有任何恶意意图代码也可能因为资源消耗导致机器卡顿。生产环境中至少应该做到下面两点使用容器运行生成的代码比如 Docker配合内存、CPU、网络访问限制。在独立的低权限用户下运行禁止访问业务系统内部网络和服务。如果你所在团队还没有完善的安全隔离条件建议先不要在宿主环境启用执行验证只保留语法校验。7. 常见问题与排查思路在落地 Harness 时团队最常遇到的问题有下面几类。我把问题现象、常见原因和解决思路整理成表格方便排查时对照。问题现象常见原因解决思路多次重试仍返回非法代码提示词没有强制约束输出格式模型持续输出解释文本在提示词中明确写出只输出代码块不允许额外解释并在解析前清理多余文本重试循环一直不结束错误信息没有正确回灌给模型或者回灌的信息不完整检查闭环中的修复提示词是否包含上一次错误信息和错误代码片段JSON 解析总失败模型返回的内容包含 Markdown 代码块、注释或前后缀先提取 JSON 区域再进行json.loads而不是直接解析完整输出语法通过但运行时崩溃缺少依赖、运行环境不一致或代码存在隐藏逻辑错误在沙箱中固定依赖版本增加运行时校验并回传 stderr 给模型子进程执行模型代码把机器卡死模型生成了死循环或资源消耗型代码设置timeout限制 CPU 和内存优先使用容器隔离输出结果不稳定同一需求多次生成结果差异大缺少系统提示词采样参数温度过高对工程化生成任务使用较低温度并在提示词中增加格式和风格约束遇到问题时我的排查顺序通常是先看模型原始输出确认是不是解析环节出了问题再看提示词是否足够明确最后才怀疑模型能力。很多时候问题根源并不在模型本身而是外层工程没有把约束做扎实。8. 最佳实践与工程建议8.1 提示词与格式约束设计提示词是 Harness 的第一道关卡。我建议把系统提示词拆成三个部分角色定义、输出格式约束、需求内容。角色定义让模型知道它是什么身份输出格式约束告诉它怎么组织答案需求内容才是每次动态变化的部分。在有条件的情况下尽量使用 JSON Schema 描述输出结构。相比自然语言描述Schema 更精确也更容易被程序化校验。模型输出经过校验之后再进入业务逻辑能显著减少运行时错误。8.2 安全与权限治理Harness 让代码生成更可控但不是绝对安全。凡是涉及模型生成内容执行、部署、数据访问的场景都要额外关注安全边界。执行权限最小化使用独立低权限账号运行生成代码。网络访问隔离默认禁止生成代码访问外网按需放行白名单域名。敏感信息保护提示词和模型输出都可能包含敏感信息日志中需要脱敏处理。变更审批涉及数据库操作、生产环境配置的生成代码必须走人工审批流程。8.3 可观测性与回归Harness 的价值在于每一次生成行为都有迹可循。建议把每次调用的输入提示词、模型原始输出、校验结果、重试次数、最终输出都记录下来。这些数据既用于问题回溯也用于评估模型生成质量。回归测试同样重要。你可以准备一组标准需求定期运行 Harness记录通过率和失败原因。任何一次提示词调整或模型版本升级都应该跑一遍回归用例确保生成质量没有回退。8.4 从 Harness 走向 Agent 的演进路线对大多数团队来说直接做 Agent 风险很高。更务实的路线是先把 Harness 做到成熟再在可靠闭环上引入 Agent 的自主能力。举例来说第一版系统可以是固定 Harness 固定提示词让模型生成代码并校验第二版可以加入多轮修复闭环第三版才让 Agent 在多个工具之间自主选择。每一步都以可观测、可回滚为前提不要一步跨到全自主。9. 总结与学习路线本文从为什么代码生成难以可控这个问题出发介绍了 Harness 的核心概念、与 Agent 的边界并通过三个实战示例演示了不同层级的可控代码实践。如果你正在建设代码生成平台我的建议是不要急着追求 Agent 化而是先把 Harness 的约束 → 校验 → 反馈闭环做扎实。从 5 到 10 个高频代码生成场景入手为每个场景定义输出 Schema、校验规则和重试模板逐步沉淀出属于你自己团队的 Harness 工具包。这条路看起来慢但长期收益最高也最容易让业务方建立信任。下一步可以继续深入的方向包括如何针对特定框架编写语义校验规则、如何在 Harness 中接入单元测试结果、如何设计多模型路由与降级策略以及如何让 Harness 在持续集成流水线中发挥作用。欢迎在评论区分享你团队在可控代码生成上的经验和问题一起探讨更好的工程实践。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价