Skip to content

Harness 机制总装:把七道防护装进一个入口 ​

1. 本节产出 ​

一个完整的 AgentHarness:把前面六章的防护(超时、重试、熔断、循环检测、权限、审计)统一装配,业务代码只需调用一个方法,所有治理自动生效。附完整的 Guard 链顺序与配置。

2. 前置依赖 ​

3. 为什么需要「总装」这一步 ​

前面六章各做了一个防护,如果直接用会变成这样:

java
// 反面教材:治理逻辑散落在业务代码里
audit.logStart();
try {
    timeoutGuard.check();
    loopDetector.check();
    circuitBreaker.check();
    sandbox.check();
    result = loop.run();
} catch (...) { ... }
finally { audit.logEnd(); }

问题:

问题后果
顺序靠人记新人加一个防护,加错位置
容易漏某个新入口忘了加循环检测 → 又一个烧钱事故
难以测试无法统一验证「所有防护都生效」
无法复用第二个 Agent 要重新抄一遍

Harness 的定位:把「跑一个 Agent」这件事的治理部分全部收口到一个组件,业务只关心「目标是什么、用哪些工具」。

4. 核心原理 ​

4.1 Harness 的分层 ​

┌────────────────────────────────────────────┐
│  业务层:agentHarness.run(request)          │
├────────────────────────────────────────────┤
│  AgentHarness(总入口)                     │
│   ├─ 校验:请求合法性、预算、权限            │
│   ├─ 装配 Guard 链                          │
│   ├─ 驱动循环                               │
│   └─ 收尾:审计、指标、状态落库              │
├────────────────────────────────────────────┤
│  Guard 链(有序)                           │
│   1. 预算守卫    BudgetGuard                │
│   2. 循环守卫    LoopGuard                  │
│   3. 超时守卫    TimeoutGuard               │
│   4. 熔断守卫    CircuitBreakerGuard        │
│   5. 权限沙箱    PermissionSandbox          │
│   6. 审批网关    ApprovalGateway            │
│   7. 审计记录    AuditGuard                 │
├────────────────────────────────────────────┤
│  执行层:LLM 调用 / 工具执行                 │
└────────────────────────────────────────────┘

4.2 Guard 链的顺序原则 ​

原则:越便宜、越能提前终止的检查放越前面。

顺序Guard理由
1预算纯粹的内存判断,零成本;超了后面全不用做
2循环检测内存判断,零成本;检测到就不必调模型
3超时内存判断
4熔断内存判断(已统计好的状态)
5权限需要查注册表,很轻
6审批可能暂停,放在执行前最后一步
7审计要记录最终的输入与结果,包在最内层

为什么审计在最内层:它要记录「真正被执行的东西」。放外层会记录到被前面 Guard 拦截的请求——那些也应该记录,但应该记为「被拦截」,而不是「已执行」。

实际实现:审计做两层——外层记录请求进入,内层记录实际执行。

4.3 用 Advisor 还是自己写链? ​

方案优点缺点
Spring AI Advisor框架原生、与 ChatClient 集成好、顺序可控只覆盖 LLM 调用,不覆盖工具执行与循环
自研 Guard 链覆盖全流程(循环、工具、审批)要自己写

结论:两者都要,各管一段:

  • Advisor 管「单次 LLM 调用」的横切:审计、成本、安全过滤;
  • Harness Guard 链管「整个运行」的治理:预算、循环、超时、熔断、权限、审批。

这是本课程 agent-spring-harness 的核心架构决策。

4.4 一次运行的完整时序 ​

run(request)
  │
  ├─ 审计:记录开始
  ├─ 状态:创建/加载 AgentState
  │
  └─ for each iteration:
       ├─ 预算守卫    → 超预算 → 停止
       ├─ 循环守卫    → 检测到 → 强制结束
       ├─ 超时守卫    → 总超时 → 停止
       │
       ├─ LLM 调用(Advisor 链:审计 → 记忆 → 成本)
       │    └─ 熔断 + 重试 + 超时
       │
       ├─ 解析 Action
       │
       ├─ 如果是 FINISH → 结束
       │
       ├─ 权限沙箱    → 白名单/角色/参数校验
       ├─ 审批网关    → 需要审批 → 暂停保存
       │
       ├─ 工具执行(熔断 + 超时 + 幂等)
       │    └─ Observation
       │
       ├─ 追加到 transcript
       ├─ 上下文压缩检查
       └─ 落检查点
  │
  └─ 收尾:审计结束、指标上报、状态置为终态

5. 代码走查 ​

5.1 Harness 主类 ​

java
// ch03-agent/src/main/java/com/aitech/agent/AgentHarness.java
@Service
public class AgentHarness {

    private final List<Guard> guards;          // 有序注入
    private final ReactLoop loop;
    private final AgentStateRepository repo;
    private final AuditGuard audit;

    public AgentResult run(AgentRequest req) {
        audit.logStart(req);
        AgentState state = repo.load(req.runId())
                .orElseGet(() -> AgentState.start(req));

        try {
            while (state.iteration() < req.limits().maxIterations()) {

                // 1. Guard 链:任一守卫返回 STOP 则结束
                for (Guard g : guards) {
                    GuardResult r = g.check(state, req);
                    if (r.stop()) {
                        return finish(state, r.reason(), r.status());
                    }
                }

                // 2. 决策
                Action action = loop.decide(state);

                if (action.step() == Action.Step.FINISH) {
                    return finish(state, action.finalAnswer(), Status.DONE);
                }

                // 3. 工具执行(权限 + 审批 + 熔断 + 超时都在里面)
                ToolResult tool = toolExecutor.execute(action, state, req);

                if (tool.needApproval()) {
                    state = state.waitingApproval(action);
                    repo.checkpoint(state);
                    return AgentResult.needApproval(state.runId());
                }

                // 4. 推进状态
                state = state.append(action, tool.observation(), tool.tokens());
                repo.checkpoint(state);
            }
            return finish(state, null, Status.MAX_ITERATIONS);
        } finally {
            audit.logEnd(state);
            metrics.record(state);
        }
    }
}

5.2 Guard 接口与实现 ​

java
// ch03-agent/src/main/java/com/aitech/agent/guard/Guard.java
public interface Guard {

    GuardResult check(AgentState state, AgentRequest req);

    /** 顺序:越小越先执行 */
    int order();
}

public record GuardResult(boolean stop, String reason, AgentResult.Status status) {
    public static GuardResult pass() { return new GuardResult(false, null, null); }
    public static GuardResult stop(String reason, AgentResult.Status s) {
        return new GuardResult(true, reason, s);
    }
}
java
// 预算守卫(order = 10)
@Component
public class BudgetGuard implements Guard {

    @Override
    public GuardResult check(AgentState s, AgentRequest req) {
        if (s.tokenUsed() > req.limits().budgetTokens()) {
            return GuardResult.stop("达到 Token 预算", Status.BUDGET_EXCEEDED);
        }
        if (elapsed(s) > req.limits().totalTimeout()) {
            return GuardResult.stop("达到总超时", Status.TIMEOUT);
        }
        return GuardResult.pass();
    }

    @Override public int order() { return 10; }
}
java
// 循环守卫(order = 20)
@Component
public class LoopGuard implements Guard {

    @Override
    public GuardResult check(AgentState s, AgentRequest req) {
        String reason = detector.check(s.history());
        return reason == null ? GuardResult.pass()
                : GuardResult.stop(reason, Status.LOOP_DETECTED);
    }

    @Override public int order() { return 20; }
}

5.3 装配(顺序由 order 决定) ​

java
// ch03-agent/src/main/java/com/aitech/agent/config/HarnessConfig.java
@Configuration
public class HarnessConfig {

    @Bean
    public List<Guard> guards(List<Guard> all) {
        return all.stream()
                .sorted(Comparator.comparingInt(Guard::order))
                .toList();
    }
}

用 Spring 的集合注入 + 排序,新增一个 Guard 只要加 @Component 并指定 order,不用改 Harness——这是开闭原则的直接应用。

5.4 配置清单 ​

yaml
agent:
  limits:
    simple:    { max-iterations: 5,  budget-tokens: 20000,  timeout: 60s }
    standard:  { max-iterations: 10, budget-tokens: 80000,  timeout: 180s }
    research:  { max-iterations: 20, budget-tokens: 300000, timeout: 600s }
  guard:
    budget: true
    loop: true
    timeout: true
    circuit-breaker: true
    permission: true
    approval: true
    audit: true

6. 跑起来 ​

bash
git checkout ch03-13-harness
mvn -q test -Dtest=AgentHarnessIntegrationTest

全防护演练(本节核心验收):

bash
# 逐项注入故障,验证每道守卫都生效
for fault in budget-exceed loop timeout circuit-break unknown-tool \
             need-approval sensitive-args; do
  echo "=== $fault ==="
  curl -s -X POST http://localhost:8080/api/agent/run \
    -d "{\"goal\":\"测试\",\"fault\":\"$fault\"}" | jq .status
done

期望输出:

=== budget-exceed ===    "BUDGET_EXCEEDED"
=== loop ===             "LOOP_DETECTED"
=== timeout ===          "TIMEOUT"
=== circuit-break ===    "DONE"          (降级到备用厂商完成任务)
=== unknown-tool ===     "DONE"          (工具被拒但任务用其他方式完成)
=== need-approval ===    "NEED_APPROVAL"
=== sensitive-args ===   "DONE"          (参数被拒但任务继续)
检查项通过标准
七道守卫全生效每种故障被正确拦截或降级
顺序正确日志显示 budget → loop → timeout → cb → permission → approval
业务代码简洁调用方只有一行 harness.run(req)
可扩展新增 Guard 只需加组件,不改 Harness
全绿集成测试覆盖所有守卫

7. 生产避坑 ​

  1. Guard 顺序要按「成本递增」排列。便宜的检查(内存判断)放前面,能提前终止就别浪费后面的资源。顺序错了不会报错,但会让「超预算的任务还跑了好几轮」——这正是我们要避免的。
  2. 不要把所有治理都塞进 Advisor。Advisor 只覆盖 LLM 调用,管不了循环、工具执行和审批。用一个 Harness Guard 链管运行级治理,Advisor 管调用级横切,两者职责分开。
  3. 新增 Guard 必须能自动接入。如果每加一个防护都要改 Harness 主类,半年后这个类会变得臃肿且没人敢动。用 Spring 集合注入 + order 排序,加防护只加一个文件。

8. 延伸与锚点 ​