Appearance
Harness 机制总装:把七道防护装进一个入口
1. 本节产出
一个完整的 AgentHarness:把前面六章的防护(超时、重试、熔断、循环检测、权限、审计)统一装配,业务代码只需调用一个方法,所有治理自动生效。附完整的 Guard 链顺序与配置。
2. 前置依赖
- 03-07 至 03-12 全部完成
- 03-05 状态机与持久化
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: true6. 跑起来
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. 生产避坑
- Guard 顺序要按「成本递增」排列。便宜的检查(内存判断)放前面,能提前终止就别浪费后面的资源。顺序错了不会报错,但会让「超预算的任务还跑了好几轮」——这正是我们要避免的。
- 不要把所有治理都塞进 Advisor。Advisor 只覆盖 LLM 调用,管不了循环、工具执行和审批。用一个 Harness Guard 链管运行级治理,Advisor 管调用级横切,两者职责分开。
- 新增 Guard 必须能自动接入。如果每加一个防护都要改 Harness 主类,半年后这个类会变得臃肿且没人敢动。用 Spring 集合注入 + order 排序,加防护只加一个文件。
8. 延伸与锚点
- 思考题:Harness 有了,用它做三个真实场景试试?(03B 完成,进入 03C 实战)
- 代码锚点:
git checkout ch03-13-harness - 下一课时:03-14 实战案例(一):智能运维助手
- 对应课件:L03-13 Harness 总装