别再只调 Prompt:用 Harness、测试与上下文打造可靠的编码 Agent

2026-09-25 16 预计阅读时间: 1 分钟
来源: cloud.google.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:13 分钟

自主编码 Agent 的能力,不只取决于模型有多聪明。真正决定它能否进入生产环境的,往往是模型外面的那一整套系统:它能读取哪些上下文、可以调用什么工具、如何验证结果、何时停止,以及哪些危险操作永远不能执行。这套系统就是 Agent Harness。

这也改变了工程师的工作重心:与其逐行编写和检查代码,不如把团队标准写进仓库、测试、静态检查器和工具权限中,然后审查 Agent 交付的 Pull Request、文档与运行结果。

Harness 才是 Agent 与普通聊天模型的分界线

可以把一个工程 Agent 简化成下面的组合:

Agent = LLM + Harness + Domain Knowledge

LLM 负责理解、规划和生成;Harness 负责把模型放进一个可以完成工作的环境。它通常包含:

  • 上下文检索:读取仓库文档、接口定义、历史决策和当前文件。
  • 工具调用:搜索代码、修改文件、执行测试、查询云资源。
  • 循环控制:决定失败后是否重试、最多运行多少轮、何时结束。
  • 记忆管理:保留关键诊断信息,并在上下文过长时压缩旧记录。
  • 结果验证:运行测试、Lint、类型检查和安全扫描。
  • 权限边界:阻止删除目录、清空数据库或未经批准的远程推送。

例如,“今天要不要带雨衣”并不是模型仅凭参数就能可靠回答的问题。Harness 需要识别天气查询意图,调用实时天气工具,再把地点、降水概率和时间等结果交给模型。编码任务也是如此:模型不知道当前分支有哪些文件、测试为何失败,更不知道团队对 API 兼容性的要求,除非 Harness 能把这些信息提供给它。

因此,Agent 失败时反复改写 Prompt 通常只是局部补丁。可复用的改进应该落到模型之外,成为所有任务都能继承的工程能力。

把干预左移:从“再试一次”变成自动约束

所谓“左移”,就是把发现和纠正错误的动作提前到更便宜、更稳定的位置。针对编码 Agent,可以把干预手段看成一条逐步增强的链路:

临时 Prompt → 仓库文档 → Lint/类型检查 → 单元测试 → 集成测试 → 上游评测

如果 Agent 总是使用错误的启动命令,不要每次提醒它,而应把命令写入 AGENTS.md 或项目文档。如果它反复破坏公共接口,就补充契约测试。如果它经常生成不符合规范的代码,就把规范做成格式化器、Lint 规则或 CI 检查。

一个可直接改造的 AGENTS.md 可以从下面开始:

# Repository Instructions

## Validation
- Run `python -m unittest discover -s tests` after every code change.
- Run `ruff check .` before declaring the task complete.
- Do not suppress failing tests or remove assertions.

## Architecture
- Keep HTTP handlers thin.
- Put business logic under `src/services/`.
- Public API changes require a compatibility test.

## Safety
- Never run recursive deletion commands.
- Never push to a remote repository.
- Do not read `.env`, credential files, or directories outside this repository.

## Completion criteria
- Tests pass.
- The diff contains only task-related changes.
- The final report lists changed files, validation commands, and remaining risks.

这种文档不是一篇写给人看的长说明,而是 Agent 可检索的持久记忆。内容应短、明确,并尽量引用确定性的命令。文档还可以按模块拆分,让 Agent 只加载当前任务相关的部分,降低长上下文中间信息被忽略的风险。

一个可运行的闭环 Harness:修改、测试、反馈、停止

下面的示例只使用 Python 标准库,演示闭环 Harness 的核心结构。为了保证任何人都能直接运行,propose_patch 使用确定性逻辑模拟模型给出的补丁;接入真实模型时,只需把这个函数替换成 SDK 或 API 调用。

将代码保存为 harness_demo.py,然后运行 python harness_demo.py:

from __future__ import annotations

import shutil
import subprocess
import sys
from pathlib import Path

ROOT = Path("agent_demo").resolve()
MAX_ITERATIONS = 5


def create_demo_project() -> None:
    if ROOT.exists():
        shutil.rmtree(ROOT)
    ROOT.mkdir()

    (ROOT / "calc.py").write_text(
        "def add(a, b):\n"
        "    return a - b\n",
        encoding="utf-8",
    )
    (ROOT / "test_calc.py").write_text(
        "import unittest\n"
        "from calc import add\n\n"
        "class CalcTest(unittest.TestCase):\n"
        "    def test_add(self):\n"
        "        self.assertEqual(add(2, 3), 5)\n\n"
        "if __name__ == '__main__':\n"
        "    unittest.main()\n",
        encoding="utf-8",
    )


def run_tests() -> tuple[bool, str]:
    result = subprocess.run(
        [
            sys.executable,
            "-m",
            "unittest",
            "discover",
            "-s",
            str(ROOT),
            "-p",
            "test_*.py",
        ],
        cwd=ROOT,
        text=True,
        capture_output=True,
        timeout=30,
    )
    logs = (result.stdout + "\n" + result.stderr)[-6000:]
    return result.returncode == 0, logs


def propose_patch(test_logs: str) -> tuple[str, str]:
    """模拟模型输出:(相对路径, 新文件内容)。

    实际项目可在这里把任务、相关文件和 test_logs 发给模型,
    但仍应要求模型返回结构化补丁,而不是任意 shell 命令。
    """
    if "AssertionError" in test_logs:
        return "calc.py", "def add(a, b):\n    return a + b\n"
    raise RuntimeError("No safe patch can be proposed from the diagnostics")


def guarded_write(relative_path: str, content: str) -> None:
    target = (ROOT / relative_path).resolve()
    if ROOT not in target.parents:
        raise PermissionError(f"Refusing to write outside workspace: {target}")
    if target.suffix != ".py":
        raise PermissionError(f"Unsupported file type: {target.suffix}")
    target.write_text(content, encoding="utf-8")


def main() -> None:
    create_demo_project()

    for iteration in range(1, MAX_ITERATIONS + 1):
        passed, logs = run_tests()
        print(f"\n--- iteration {iteration} ---")
        print(logs)

        if passed:
            print("Verified: all tests pass.")
            return

        path, content = propose_patch(logs)
        guarded_write(path, content)
        print(f"Applied candidate patch to {path}")

    raise SystemExit("Stopped: iteration limit reached")


if __name__ == "__main__":
    main()

这个小程序包含了生产 Harness 最重要的几个控制点:

  1. 测试是退出条件,不是让模型自行声称“已经修好”。
  2. 失败日志会回到下一轮,形成可观察的修复闭环。
  3. 最多执行五轮,避免无限循环和费用失控。
  4. 文件写入受工作区约束,阻止路径穿越。
  5. 测试命令由 Harness 固定,模型不能替换成删除测试或跳过验证的命令。

真实系统还应保存每轮输入、工具调用、补丁、测试结果和令牌成本。对于 Shell 工具,优先使用命令白名单或结构化工具参数,而不是把模型生成的字符串直接交给 shell=True。

长任务不是一个大 Prompt,而是一串可审查的短循环

语言迁移、大型重构或云基础设施调整,很难依靠一次生成可靠完成。更稳健的方式是缩小每一轮的状态空间:

  • 先生成迁移计划和受影响文件清单。
  • 每次只修改一个模块或一种接口。
  • 每个阶段都产生小型、可审查的 Pull Request。
  • 使用测试和独立审计步骤验证结果。
  • 只有连续多次成功后,才扩大 Agent 的任务范围。

这是一种逐步建立信任的过程。团队不应因为 Agent 偶尔完成过一次大型任务,就立即授予它生产部署权限。自动化范围应该与可观测性、回滚能力和验证强度一起增长。

当任务需要数十次文件检查、编辑和测试时,低延迟、成本可控的模型可能比每一步都调用重型推理模型更实用。来源讨论采用“模型、Harness、知识”三层栈,并以快速模型、编排 Harness 和模块化领域知识为例。这里的重点不是绑定某个产品,而是根据循环频率选择模型,把真正可积累的投资放在工具与上下文上。

团队经验应该升级 Agent,而不是困在个人 Prompt 里

一名 React 架构师加入团队后,他对渲染性能和组件边界的判断,可以沉淀为前端检查清单、性能预算、示例组件和自动测试;数据库专家的经验则可以变成迁移规则、查询分析工具和权限策略。

这样一来,Agent 不只是某位开发者的助手,而是团队知识的集中执行者。新成员贡献的最佳实践会提升整个系统的“能力属性”,而不是只存在于个人聊天记录中。

不过,不要因此急着从零开发庞大的专有 Harness。文件读取、代码搜索、命令执行、上下文压缩和工具拦截等基础能力,成熟框架通常已经提供。自己实现一个小型 Harness 有助于理解循环、工具和记忆,但生产投入更值得放在以下位置:

  • 高质量、可发现的仓库文档;
  • 稳定且具有明确输入输出的 CLI;
  • 快速、确定性的测试与静态检查;
  • 最小权限和高风险操作拦截;
  • 面向真实任务的回归评测集;
  • 可审计的执行轨迹和成本指标。

落地前的检查清单

在扩大自主编码范围前,可以逐项确认:

  • Agent 是否能从仓库自行找到构建、测试和架构约束?
  • 成功是否由测试和检查器判定,而不是由模型自我评价?
  • 是否设置了迭代次数、时间、令牌和费用上限?
  • 工具权限是否遵循最小权限原则?
  • 删除、部署、数据库写入和远程推送是否需要人工批准?
  • 每次失败是否会转化为文档、测试、工具或评测的永久改进?
  • 是否能快速替换模型,而不用重写全部工作流?

可靠的编码 Agent 不是“一个更会写代码的聊天框”,而是一套把概率推理包裹在确定性工程约束中的系统。模型会持续变化,但清晰的上下文、稳定的工具、自动验证和安全边界仍会保值。把这些能力建设好,团队才能从“Prompt and pray”走向可审查、可复现、可逐步放权的自主工程流程。


相关推荐