什么是Harness Engineering?——最小的Agent Harness
·
Harness Engineering 背景介绍
1. 概念起源
"Harness"(套具/挽具) 这个词最早来自:
| 领域 | 含义 |
|---|---|
|
马具 |
控制马的装备,让马的力量被正确引导 |
|
电气工程 |
测试套具(Test Harness)- 连接被测系统与测试设备的中间层 |
|
软件测试 |
自动测试框架,负责执行测试用例并收集结果 |
在 AI Agent 领域,Harness 沿用了同样的隐喻:
Harness = 控制 AI 能力的"挽具",让 AI 的输出被正确引导和执行
2. 发展脉络
阶段一:纯 API 调用(2022-2023)
用户 → LLM API → 返回文本 → 结束
- 模型直接输出结果
- 无法执行真实操作
- 应用场景有限
阶段二:工具调用 Function Calling(2023)
用户 → LLM API → 决定调用工具 → Harness 执行 → 返回结果 → 结束
- 模型开始具备"调用工具"的能力
- 但缺乏循环和状态管理
阶段三:完整 Agent Harness(2023-至今)
用户 → [模型思考 → Harness 执行 → 模型思考 → Harness 执行 → ...] → 完成
- 循环直到任务完成
- 完整的上下文管理
- 多工具协作
- 错误恢复机制
为什么需要 Harness?
核心问题:模型"不是"万能的
| 模型能做的 | 模型实际不能做的 |
|---|---|
|
理解自然语言 |
真正读取你的文件 |
|
生成代码 |
真正运行代码 |
|
推理和规划 |
真正访问互联网 |
|
写回答 |
真正修改数据库 |
Harness 的价值: 把"说"和"做"分离,模型负责决策,Harness 负责执行。
模型只负责"思考和决策",Harness 负责真正执行、管理上下文、控制循环。
┌─────────────────────────────────────────────────────────────┐
│ Harness │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 工具注册表 (TOOL_FUNCTIONS) │ │
│ │ - read_file() - calculate() - ... │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ 调用工具 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Agent Loop (循环) │ │
│ │ 1. 发消息给模型 │ │
│ │ 2. 模型决定: 调用工具 or 给出答案 │ │
│ │ 3. 如果调用工具 → Harness 执行 → 返回结果 │ │
│ │ 4. 循环回到第1步 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌───────────────┐
│ AI Model │
│ (Claude) │
│ "思考和决策" │
└───────────────┘
以下是代码示例:
"""
最小 Agent Harness 示例
核心思想:模型只负责"思考和决定调用什么工具",
harness 负责真正执行工具、管理上下文、控制循环。
"""
import json
from anthropic import Anthropic
client = Anthropic() # 需要设置 ANTHROPIC_API_KEY 环境变量
# ---------- 1. 工具定义 ----------
# harness 里真正干活的函数。模型自己不能读文件、不能算数,
# 它只能"请求"harness 帮它做,这就是工具存在的意义。
def read_file(path: str) -> str:
"""读取一个文本文件的内容。"""
try:
with open(path, "r", encoding="utf-8") as f:
return f.read()
except Exception as e:
return f"读取失败: {e}"
def calculate(expression: str) -> str:
"""计算一个数学表达式,比如 '2 * (3 + 4)'。"""
try:
# 生产环境别用 eval,这里只是演示。用 ast 或专用库更安全。
result = eval(expression, {"__builtins__": {}}, {})
return str(result)
except Exception as e:
return f"计算失败: {e}"
# 工具的注册表:把函数名映射到真正的 Python 函数
TOOL_FUNCTIONS = {
"read_file": read_file,
"calculate": calculate,
}
# 工具的 schema:告诉模型有哪些工具、怎么调用、参数是什么。
# 这段描述写得好不好,直接影响模型用不用得对工具。
TOOL_SCHEMAS = [
{
"name": "read_file",
"description": "读取指定路径的文本文件内容",
"input_schema": {
"type": "object",
"properties": {
"path": {"type": "string", "description": "文件路径"}
},
"required": ["path"],
},
},
{
"name": "calculate",
"description": "计算一个数学表达式并返回结果",
"input_schema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "要计算的表达式,如 '2 * (3 + 4)'",
}
},
"required": ["expression"],
},
},
]
# ---------- 2. Agent Loop ----------
# 这是 harness 的心脏。
def run_agent(user_task: str, max_turns: int = 10) -> str:
# messages 就是"上下文"。harness 的核心工作之一
# 就是决定这个列表里放什么、怎么增长。
messages = [{"role": "user", "content": user_task}]
for turn in range(max_turns):
# (a) 把当前上下文 + 工具列表发给模型,让它决定下一步
response = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
tools=TOOL_SCHEMAS,
messages=messages,
)
# (b) 把模型这一轮的输出加回上下文(关键:保持对话连续)
messages.append({"role": "assistant", "content": response.content})
# (c) 判断模型是想调用工具,还是已经给出最终答案
if response.stop_reason != "tool_use":
# 模型没有要求调工具 → 认为任务完成,返回文本
final_text = "".join(
block.text for block in response.content
if block.type == "text"
)
return final_text
# (d) 模型要求调用一个或多个工具 → harness 真正执行它们
tool_results = []
for block in response.content:
if block.type == "tool_use":
fn = TOOL_FUNCTIONS[block.name]
print(f" [调用工具] {block.name}({block.input})")
result = fn(**block.input) # 真正执行
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id, # 对应上模型的请求 id
"content": result,
})
# (e) 把工具执行结果作为一条 user 消息喂回去,进入下一轮
messages.append({"role": "user", "content": tool_results})
return "达到最大轮次,未完成任务。"
# ---------- 3. 跑起来 ----------
if __name__ == "__main__":
answer = run_agent("帮我算一下 (128 + 256) * 3 等于多少")
print("\n最终回答:", answer)
1. 工具定义(Lines 13-70)
TOOL_FUNCTIONS = {
"read_file": read_file,
"calculate": calculate,
}
为什么需要工具?
- 模型本身不能真正读取文件、做计算
- 它只能"请求"Harness 帮它做
- 工具 = 模型与真实世界交互的桥梁
TOOL_SCHEMAS = [ { "name": "read_file", "description": "读取指定路径的文本文件内容", "input_schema": {...} }, ... ]Schema 的重要性: 描述写得越好,模型用工具越准确。
2. Agent Loop(Lines 73-118)
这是 Harness 的心脏:
for turn in range(max_turns):
# (a) 发消息给模型,让它决定下一步
response = client.messages.create(...)
# (b) 把模型输出加回上下文
messages.append({"role": "assistant", "content": response.content})
# (c) 判断是调用工具还是给出答案
if response.stop_reason != "tool_use":
return final_text # 完成!
# (d) Harness 真正执行工具
for block in response.content:
if block.type == "tool_use":
result = fn(**block.input) # 执行!
# (e) 把结果喂回去,进入下一轮
messages.append({"role": "user", "content": tool_results})
3. 运行示例(Lines 122-125)
answer = run_agent("帮我算一下 (128 + 256) * 3 等于多少")
执行流程:
模型收到任务 → "我需要调用 calculate 工具"
Harness 执行 → calculate("(128 + 256) * 3") → "1152"
模型收到结果 → 给出最终回答:"答案是 1152"
总结
Harness Engineering 的兴起是 LLM 应用成熟的必然结果:
- 从玩具到生产力 - LLM 需要真正做事,而不只是说话
- 安全与可控 - 把危险操作隔离在 Harness 层
- 可靠性保障 - 模型可能出错,Harness 需要兜底
- 工程化需求 - 可测试、可监控、可迭代
更多推荐



所有评论(0)