如何设计 Agent 的 Harness:从架构到代码实战

如何设计 Agent 的 Harness:从架构到代码实战
1. 引言在构建 AI Agent 时很多人把注意力集中在模型选择、Prompt 编写和工具调用上却忽略了承载 Agent 运行的核心骨架——Harness。Harness 是 Agent 的“运行容器”它决定了 Agent 如何感知环境、如何决策、如何执行动作、如何从错误中恢复。一个设计良好的 Harness能让 Agent 更稳定、更可控、更易扩展反之再强的模型也会因为缺乏可靠的执行框架而频繁出错。本文将从架构层面拆解 Agent Harness 的核心模块并结合 Java 代码给出一个可运行的实战示例帮助你从零搭建一个属于自己的 Agent Harness。2. 什么是 Agent HarnessHarness 直译为“挽具”或“线束”在 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 groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency dependency groupIdorg.slf4j/groupId artifactIdslf4j-api/artifactId version2.0.9/version /dependency4.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 ListString conversationHistory new ArrayList(); private final ListString 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 MapString, 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 MapString, 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) { // 简化实现只处理 ab 格式 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(请计算 35); System.out.println(Agent 回复 result2); } }运行结果如下Agent 回复北京今天晴气温 25°C。 Agent 回复85. 进阶设计错误处理与重试真实场景中工具调用可能失败模型可能返回非法格式。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 系统。

最新新闻

日新闻

周新闻

月新闻