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 系统。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐