Solon v4.1.0

异常治理、事件、观测与测试

</> markdown
2026年9月18日 下午12:00:33

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

1、五层观察模型

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

导出定义:

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 不是完整审计日志

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

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

完整节点审计应通过 FlowInterceptorTeamInterceptor 写入外部日志。审计状态至少区分:

STARTED
COMPLETED
SUSPENDED
INTERRUPTED
FAILED

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

3、实时 AgentEvent

流式 TeamAgent 请求返回 AgentEvent

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 / NodeEndEventAgent.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、指标

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:

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");
    }
};

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

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

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

构建时加入:

.defaultInterceptorAdd(recorder)

断言业务结果和路径:

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 负责执行。