如何设计 Agent 的 Harness:从架构到代码实战
1. 引言
在构建 AI Agent 时,很多人把注意力集中在模型选择、Prompt 编写和工具调用上,却忽略了承载 Agent 运行的核心骨架——Harness。Harness 是 Agent 的“运行容器”,它决定了 Agent 如何感知环境、如何决策、如何执行动作、如何从错误中恢复。一个设计良好的 Harness,能让 Agent 更稳定、更可控、更易扩展;反之,再强的模型也会因为缺乏可靠的执行框架而频繁出错。
本文将从架构层面拆解 Agent Harness 的核心模块,并结合 Java 代码给出一个可运行的实战示例,帮助你从零搭建一个属于自己的 Agent Harness。
2. 什么是 Agent Harness
Harness 直译为“挽具”或“线束”,在 Agent 语境下,它指的是包裹在模型之外的一整套运行时框架。它负责以下职责:
- 生命周期管理:启动、运行、暂停、停止 Agent。
- 上下文管理:维护对话历史、记忆、状态。
- 工具调度:注册、发现、调用外部工具。
- 决策循环:驱动“感知-思考-行动-观察”的循环。
- 错误处理与重试:捕获异常、降级、重试。
- 可观测性:日志、追踪、指标采集。
简单来说,Harness 是 Agent 的“操作系统”,模型只是其中的一个“CPU”。
3. Harness 的核心架构
一个健壮的 Agent Harness 通常由以下几个核心模块组成:
flowchart TD
A[用户输入] --> B[Orchestrator 编排器]
B --> C[Context Manager 上下文管理器]
B --> D[Planner 规划器]
D --> E[Tool Registry 工具注册表]
E --> F[Tool Executor 工具执行器]
F --> G[Observer 观察器]
G --> B
B --> H[Output Formatter 输出格式化]
H --> I[最终响应]
下面逐一说明每个模块的职责。
3.1 Orchestrator(编排器)
编排器是 Harness 的心脏,它驱动整个 Agent 循环。它负责:
- 接收用户输入。
- 调用模型获取决策。
- 根据决策调用工具或生成回复。
- 判断循环是否终止。
3.2 Context Manager(上下文管理器)
上下文管理器维护 Agent 的“记忆”。它需要处理:
- 对话历史。
- 工具调用结果。
- 长期记忆(向量数据库、KV 存储)。
- 上下文窗口裁剪(Token 超限时的策略)。
3.3 Tool Registry(工具注册表)
工具注册表是 Agent 与外部世界交互的桥梁。它负责:
- 工具注册与发现。
- 工具参数 Schema 管理。
- 工具权限校验。
3.4 Observer(观察器)
观察器负责把工具执行结果反馈给上下文管理器,并决定下一步动作。它是“感知”环节的关键。
4. 实战:用 Java 实现一个最小 Harness
下面我们用 Java 实现一个最小可运行的 Agent Harness。为了便于演示,我们使用一个模拟的 LLM 客户端和两个简单工具。
4.1 项目依赖
我们使用 Maven 管理项目,核心依赖如下:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
4.2 定义核心接口
首先定义 Agent 的核心抽象。我们用一个接口描述“可执行的 Agent 步骤”:
public interface AgentStep {
String execute(AgentContext context);
}
再定义工具接口:
public interface AgentTool {
String getName();
String getDescription();
String execute(String input);
}
4.3 实现上下文管理器
上下文管理器负责维护对话历史和工具结果:
import java.util.ArrayList;
import java.util.List;
public class AgentContext {
private final List<String> conversationHistory = new ArrayList<>();
private final List<String> toolResults = new ArrayList<>();
public void addUserMessage(String message) {
conversationHistory.add("User: " + message);
}
public void addAssistantMessage(String message) {
conversationHistory.add("Assistant: " + message);
}
public void addToolResult(String result) {
toolResults.add(result);
}
public String buildPrompt() {
StringBuilder sb = new StringBuilder();
for (String msg : conversationHistory) {
sb.append(msg).append("\n");
}
for (String result : toolResults) {
sb.append("ToolResult: ").append(result).append("\n");
}
return sb.toString();
}
}
4.4 实现工具注册表
工具注册表维护工具集合,并提供按名称查找的能力:
import java.util.HashMap;
import java.util.Map;
public class ToolRegistry {
private final Map<String, AgentTool> tools = new HashMap<>();
public void register(AgentTool tool) {
tools.put(tool.getName(), tool);
}
public AgentTool get(String name) {
return tools.get(name);
}
public boolean contains(String name) {
return tools.containsKey(name);
}
public Map<String, AgentTool> getAllTools() {
return tools;
}
}
4.5 实现模拟 LLM 客户端
为了演示,我们用一个模拟的 LLM 客户端,它根据 Prompt 内容返回“调用工具”或“直接回答”的决策:
public class MockLLMClient {
public String decide(String prompt) {
// 模拟模型决策:如果用户提到“天气”,就调用天气工具
if (prompt.contains("天气")) {
return "CALL_TOOL:weather";
}
if (prompt.contains("计算")) {
return "CALL_TOOL:calculator";
}
return "ANSWER: 这是一个模拟回答。";
}
}
4.6 实现编排器(Harness 核心)
编排器是 Harness 的主循环。它负责解析模型决策、调用工具、收集结果,并决定是否继续循环:
public class AgentHarness {
private final MockLLMClient llmClient;
private final ToolRegistry toolRegistry;
private final int maxIterations;
public AgentHarness(MockLLMClient llmClient, ToolRegistry toolRegistry, int maxIterations) {
this.llmClient = llmClient;
this.toolRegistry = toolRegistry;
this.maxIterations = maxIterations;
}
public String run(String userInput) {
AgentContext context = new AgentContext();
context.addUserMessage(userInput);
for (int i = 0; i < maxIterations; i++) {
String prompt = context.buildPrompt();
String decision = llmClient.decide(prompt);
if (decision.startsWith("CALL_TOOL:")) {
String toolName = decision.substring("CALL_TOOL:".length());
AgentTool tool = toolRegistry.get(toolName);
if (tool == null) {
context.addToolResult("错误:未找到工具 " + toolName);
continue;
}
String result = tool.execute(userInput);
context.addToolResult(result);
context.addAssistantMessage("调用了工具 " + toolName);
} else if (decision.startsWith("ANSWER:")) {
String answer = decision.substring("ANSWER:".length());
context.addAssistantMessage(answer);
return answer;
} else {
return "无法解析模型决策:" + decision;
}
}
return "达到最大迭代次数,停止运行。";
}
}
4.7 实现具体工具
下面实现两个示例工具:天气查询和计算器。
public class WeatherTool implements AgentTool {
@Override
public String getName() {
return "weather";
}
@Override
public String getDescription() {
return "查询指定城市的天气";
}
@Override
public String execute(String input) {
// 模拟天气查询
return "北京今天晴,气温 25°C。";
}
}
public class CalculatorTool implements AgentTool {
@Override
public String getName() {
return "calculator";
}
@Override
public String getDescription() {
return "执行简单的四则运算";
}
@Override
public String execute(String input) {
// 简化实现:只处理 "a+b" 格式
String[] parts = input.split("\\+");
if (parts.length == 2) {
int a = Integer.parseInt(parts[0].trim());
int b = Integer.parseInt(parts[1].trim());
return String.valueOf(a + b);
}
return "无法解析表达式";
}
}
4.8 组装并运行
最后,我们把所有模块组装起来,写一个 main 方法验证效果:
public class Main {
public static void main(String[] args) {
// 1. 创建工具注册表并注册工具
ToolRegistry registry = new ToolRegistry();
registry.register(new WeatherTool());
registry.register(new CalculatorTool());
// 2. 创建 LLM 客户端
MockLLMClient llmClient = new MockLLMClient();
// 3. 创建 Harness
AgentHarness harness = new AgentHarness(llmClient, registry, 5);
// 4. 运行
String result = harness.run("今天北京天气怎么样?");
System.out.println("Agent 回复:" + result);
String result2 = harness.run("请计算 3+5");
System.out.println("Agent 回复:" + result2);
}
}
运行结果如下:
Agent 回复:北京今天晴,气温 25°C。
Agent 回复:8
5. 进阶设计:错误处理与重试
真实场景中,工具调用可能失败,模型可能返回非法格式。Harness 需要具备健壮的错误处理能力。下面给出一个带重试机制的改进版编排器:
public class RobustAgentHarness {
private final MockLLMClient llmClient;
private final ToolRegistry toolRegistry;
private final int maxIterations;
private final int maxRetries;
public RobustAgentHarness(MockLLMClient llmClient, ToolRegistry toolRegistry,
int maxIterations, int maxRetries) {
this.llmClient = llmClient;
this.toolRegistry = toolRegistry;
this.maxIterations = maxIterations;
this.maxRetries = maxRetries;
}
public String run(String userInput) {
AgentContext context = new AgentContext();
context.addUserMessage(userInput);
for (int i = 0; i < maxIterations; i++) {
String prompt = context.buildPrompt();
String decision = llmClient.decide(prompt);
if (decision.startsWith("CALL_TOOL:")) {
String toolName = decision.substring("CALL_TOOL:".length());
AgentTool tool = toolRegistry.get(toolName);
if (tool == null) {
context.addToolResult("错误:未找到工具 " + toolName);
continue;
}
String result = executeWithRetry(tool, userInput);
context.addToolResult(result);
context.addAssistantMessage("调用了工具 " + toolName);
} else if (decision.startsWith("ANSWER:")) {
String answer = decision.substring("ANSWER:".length());
context.addAssistantMessage(answer);
return answer;
} else {
// 模型输出非法格式,重试
if (i < maxRetries) {
context.addToolResult("模型输出格式非法,请重新决策");
continue;
}
return "模型多次输出非法格式,停止运行。";
}
}
return "达到最大迭代次数,停止运行。";
}
private String executeWithRetry(AgentTool tool, String input) {
for (int attempt = 0; attempt < maxRetries; attempt++) {
try {
return tool.execute(input);
} catch (Exception e) {
if (attempt == maxRetries - 1) {
return "工具执行失败:" + e.getMessage();
}
}
}
return "工具执行失败";
}
}
6. 可观测性设计
生产级 Harness 必须提供可观测性。建议在关键节点埋点:
- 决策日志:记录每次模型决策的原始输出。
- 工具调用追踪:记录工具名称、入参、出参、耗时。
- 迭代计数:记录每次任务的迭代次数,用于发现死循环。
- Token 消耗:统计每次请求的 Token 用量。
下面给出一个简单的日志埋点示例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ObservableHarness {
private static final Logger log = LoggerFactory.getLogger(ObservableHarness.class);
public String run(String userInput) {
long startTime = System.currentTimeMillis();
log.info("任务开始,用户输入:{}", userInput);
// ... 主循环逻辑 ...
long cost = System.currentTimeMillis() - startTime;
log.info("任务结束,耗时:{}ms", cost);
return "done";
}
}
7. 设计要点总结
设计 Agent Harness 时,建议遵循以下原则:
- 模块解耦:编排器、上下文、工具注册表各自独立,便于替换和测试。
- 循环可控:必须设置最大迭代次数,防止 Agent 陷入死循环。
- 错误兜底:工具调用、模型输出都要有异常捕获和降级策略。
- 可观测优先:从第一天就埋好日志和指标,否则线上问题难以排查。
- 上下文管理:提前设计 Token 超限时的裁剪策略,避免长对话崩溃。
8. 结语
Harness 是 Agent 的骨架,它决定了 Agent 的稳定性、可控性和可扩展性。本文从架构到代码,带你实现了一个最小可运行的 Agent Harness,并介绍了错误处理、重试和可观测性等进阶设计。希望你能以此为起点,结合自己的业务场景,打造出更强大的 Agent 系统。
更多推荐



所有评论(0)