---
title: "异常治理、事件、观测与测试"
---



Agent Graph 同时包含确定性拓扑和不确定模型输出。生产治理不能只比较最终文本，而应分别观察结构、恢复位置、协作记录、Agent 执行和业务状态。

### 1、五层观察模型

| 层次 | 对象 | 主要内容 |
|---|---|---|
| 定义层 | `Graph` | 节点、连接、网关和静态拓扑 |
| 恢复层 | `FlowTrace` | 每张 Graph 的最后节点 |
| 协作层 | `TeamTrace` | 成员记录、协议状态、团队最终答案和指标 |
| Agent 执行层 | `SimpleTrace` / `ReActTrace` | Prompt、模型与工具过程、指标 |
| 业务层 | `FlowContext` | 路由条件、审批状态、中间结果和最终输出 |

导出定义：

```java
Graph graph = team.getGraph();
System.out.println(graph.toYaml());
System.out.println(graph.toJson());
System.out.println(graph.toPlantuml());
```

导出的是拓扑描述。Java 实例形式的 Agent 和 Lambda task 不会因此变成可无损重建的定义。

### 2、FlowTrace 不是完整审计日志

```java
FlowContext context = session.getContext();
System.out.println(context.trace().lastRecords());
```

FlowTrace 只保存每张 Graph 的最后节点，用于恢复定位；它不能回答完整路径、节点执行次数或并行时序。

完整节点审计应通过 `FlowInterceptor` 或 `TeamInterceptor` 写入外部日志。审计状态至少区分：

```text
STARTED
COMPLETED
SUSPENDED
INTERRUPTED
FAILED
```

节点 task 内调用 stop/interrupt 后，不能假设 `onNodeEnd` 一定触发；挂起前应显式记录 `SUSPENDED`。

### 3、实时 AgentEvent

流式 TeamAgent 请求返回 `AgentEvent`：

```java
team.prompt("评估迁移方案")
        .session(session)
        .stream()
        .doOnNext(event -> {
            System.out.println(event.getClass().getSimpleName());
        })
        .blockLast();
```

常见事件包括团队开始/结束、Agent 节点开始/结束、ReAct 推理、工具调用和 HITL 事件。它们的产生位置并不完全对称：

| 事件 | 产生位置 |
|---|---|
| `TeamStartEvent` | 具有 stream sink 的每次 `TeamAgent.call` 开始时 |
| `TeamEndEvent` | 发起 `.stream()` 的 `TeamRequest` 成功完成时 |
| AgentEvent 的 `NodeStartEvent` / `NodeEndEvent` | `Agent.run` 包裹的直接 Agent 节点 |
| 普通 Activity / Gateway 生命周期 | 使用 `FlowInterceptor.onNodeStart/onNodeEnd` 观察 |

因此，嵌套 TeamAgent 作为父团队的 Agent 节点执行时，可能出现子团队的 `TeamStartEvent`，但没有与之对称的子团队 `TeamEndEvent`；父图仍会为这个 TeamAgent 节点产生 AgentEvent 的 `NodeStartEvent` / `NodeEndEvent`。不要把 AgentEvent 的节点事件和 FlowInterceptor 的同名生命周期概念混为一谈。

注意：

- `AgentEvent` 主要服务流式请求；普通 `call()` 不等于外部自动收到事件流；
- AgentEvent 的 `NodeStartEvent` / `NodeEndEvent` 主要描述 Agent 节点，不是所有普通 Activity/Gateway 的通用审计；
- 并行事件可能交错，不能依赖到达顺序推断业务顺序。

框架没有通用内建 `branchId`。需要分支关联时，应使用稳定 Node ID、业务实例 ID、`runId`，或自行把分支 ID 写入事件 meta/外部日志。

### 4、指标

```java
TeamTrace trace = team.getTrace(session);
Metrics metrics = trace.getMetrics();

System.out.println("durationMs=" + metrics.getTotalDuration());
System.out.println("promptTokens=" + metrics.getPromptTokens());
System.out.println("thinkTokens=" + metrics.getThinkTokens());
System.out.println("completionTokens=" + metrics.getCompletionTokens());
System.out.println("totalTokens=" + metrics.getTotalTokens());
```

还应记录：

- Graph ID、实例 ID、Node ID、Agent 名称；
- 模型、工具名称和调用 UUID；
- 路由决定和置信度；
- 重试、降级、人工拒绝；
- 并发分支和幂等键。

不要记录密钥、完整敏感 Prompt 或未经脱敏的工具结果。

### 5、失败分类和处置

| 失败类型 | 典型处置 |
|---|---|
| 模型超时/限流 | 有上限重试、切换模型、降级 |
| Agent 空输出/结构错误 | 校验失败进入人工或兜底分支 |
| 工具失败 | 按幂等性重试、补偿或挂起 |
| 路由无匹配 | 默认分支或人工审核 |
| Parallel 某分支失败 | 明确失败键，决定继续、降级或整体失败 |
| 恢复冲突 | 分布式锁/版本号拒绝重复执行 |
| Graph 定义不兼容 | 使用旧版本或迁移快照 |

重试必须放在正确边界。对模型读操作可以重试；对付款、删除、发布等副作用必须先建立幂等性。

### 6、使用确定性 Agent 测试拓扑

Graph 单元测试不应依赖真实 LLM 文风。可以创建返回固定值的 Agent：

```java
Agent analyst = new Agent() {
    @Override
    public String name() {
        return "analyst";
    }

    @Override
    public String role() {
        return "分析员";
    }

    @Override
    public AssistantMessage call(Prompt prompt, AgentSession session) {
        session.getContext().put("analysis.result", "HIGH");
        return ChatMessage.ofAssistant("HIGH");
    }
};
```

再使用拦截器记录节点路径：

```java
List<String> started = new CopyOnWriteArrayList<>();

TeamInterceptor recorder = new TeamInterceptor() {
    @Override
    public void onNodeStart(FlowContext context, Node node) {
        started.add(node.getId());
    }
};
```

构建时加入：

```java
.defaultInterceptorAdd(recorder)
```

断言业务结果和路径：

```java
Assertions.assertEquals("MANUAL",
        session.getContext().get("route.result"));
Assertions.assertTrue(started.contains("analyst"));
Assertions.assertTrue(started.contains("route"));
Assertions.assertTrue(started.contains("manual"));
Assertions.assertFalse(started.contains("auto"));
```

不要用 TeamTrace records 代替全部 Flow 节点路径；它主要记录 Agent 协作。

### 7、分层测试

#### Graph 单元测试

- 固定 Agent 或 Mock ChatModel；
- 验证节点集合、分支、Context 和最终状态；
- 不访问网络，稳定进入 CI。

#### Agent 能力测试

- 单独测试 Prompt、工具和结构化输出；
- 可使用真实模型；
- 做结构或语义断言，不比较整段文本。

#### 端到端验收测试

- 使用真实模型和外部服务；
- 独立配置密钥、配额、超时和重试；
- 不作为每次提交都必须通过的快速测试。

### 8、专项测试矩阵

至少覆盖：

- Exclusive 命中、默认和低置信度人工分支；
- Inclusive 零个、一个和多个分支；
- Parallel 分支集合、汇聚和非固定完成顺序；
- 循环结束条件和最大轮次；
- Graph pending、同 Session 恢复和 JSON 恢复；
- `stop()` 与 `interrupt()` 的差异；
- ReAct HITL 按调用 UUID 审批；
- File/Redis Session 保存后重新构造的闭环；
- 子 TeamTrace 和子图 FlowTrace；
- 节点异常、工具失败和外部副作用幂等性。

### 9、生产检查表

#### Graph

- Graph ID 在共享执行树和注册空间内唯一；
- Node ID 在图内唯一；
- Start、End 和连接完整；
- Exclusive 只有一个默认边；
- Inclusive 至少能命中一个分支；
- Parallel split/join 严格配对；
- 循环有轮次、Token 和耗时上限。

#### Agent

- Agent 名称稳定；
- Prompt 输入来源明确；
- 并行输出键隔离；
- 结构化输出经过程序校验；
- 工具权限不只依赖自然语言；
- 敏感动作接入 HITL。

#### Context 与恢复

- 使用业务实例 ID；
- 不保存密钥；
- 快照加密、脱敏和过期；
- 恢复时重建运行时组件；
- 同一实例禁止并发恢复；
- 保存定义版本和幂等键。

Graph 与 Agent 结合后，仍要保持职责清晰：Agent 负责智能，Graph 负责结构，FlowContext 负责业务数据，AgentSession 负责会话边界，FlowEngine 负责执行。