Solon v4.1.0

FlowContext:Agent Graph 的数据交换契约

</> markdown
2026年9月18日 上午11:52:02

Graph 负责控制流,FlowContext 负责业务数据。AgentSessionTeamTrace 和 Agent Trace 则分别承担会话与执行轨迹职责。

1、状态分层

对象职责典型内容
FlowContextGraph 实例数据和恢复游标输入、中间结果、路由键、FlowTrace
AgentSessionAgent 会话边界消息历史、FlowContext、pending 状态
TeamTraceTeamAgent 协作状态原始 Prompt、成员记录、协议状态、最终答案、指标
AgentTrace单个 Agent 执行状态working memory、模型/工具过程、指标

关系可以理解为:

AgentSession
  ├─ ChatSession 消息历史
  └─ FlowContext
       ├─ 业务变量
       ├─ FlowTrace
       ├─ TeamTrace
       └─ SimpleTrace / ReActTrace

2、为 Context 建立键契约

建议按输入、中间状态、路由和输出分层:

public interface GraphKeys {
    String INPUT_QUESTION = "input.question";
    String ANALYSIS_TECH = "analysis.tech";
    String ANALYSIS_RISK = "analysis.risk";
    String ROUTE_LEVEL = "route.level";
    String OUTPUT_FINAL = "output.final";
}

Agent 与业务节点使用同一契约:

Agent riskAgent = SimpleAgent.of(chatModel)
        .name("risk_agent")
        .role("风险分析员")
        .instruction("分析风险并给出依据。")
        .outputKey(GraphKeys.ANALYSIS_RISK)
        .build();

3、确定性任务留给普通 Activity

spec.addActivity("load_order")
        .task((ctx, node) -> {
            ctx.put("order.id", "SO-1001");
            ctx.put("order.amount", 15000D);
            ctx.put(GraphKeys.ROUTE_LEVEL, "HIGH");
        })
        .linkAdd(riskAgent.name());

但 Context 中存在订单数据,不代表 riskAgent 会自动看见。需要显式引用:

Agent riskAgent = SimpleAgent.of(chatModel)
        .name("risk_agent")
        .role("风险分析员")
        .instruction("分析订单:编号=#{order.id},金额=#{order.amount},"
                + "初始等级=#{route.level}")
        .outputKey(GraphKeys.ANALYSIS_RISK)
        .build();

建议分工:

  • 查询、权限、金额计算、枚举校验:普通 Activity;
  • 语义理解、归纳、生成、工具推理:Agent;
  • 关键业务路径:Graph 网关;
  • 最终结果:明确写入固定输出键。

4、outputKey 只做回填

.outputKey("analysis.risk")

它只完成:

Agent 最终文本 -> FlowContext[analysis.risk]

后续普通 Activity 可直接 ctx.getAs(...);后续 Agent 则需要通过 Context 模板、自定义 TeamProtocol 或显式 agent.prompt(...) 消费该值。

5、独立 Graph 共用同一 Context

FlowContext context = FlowContext.of("risk-job-001");
AgentSession session = InMemoryAgentSession.of(context);

context.put(GraphKeys.INPUT_QUESTION, "分析订单 SO-1001 的风险");
FlowEngine.newInstance().eval(graph, context);

不要只让 ID 相同:

FlowContext context = FlowContext.of("risk-job-001");
AgentSession session = InMemoryAgentSession.of("risk-job-001");

这仍是两个 Context 实例。Agent 结果会写入 Session 自己的 Context,外部 Graph 无法读取。

6、并行分支的数据隔离

并行 Agent 应使用不同输出键:

analysis.tech
analysis.cost
analysis.risk

不要共同写:

analysis.result

当前默认实现 FlowContextDefault 使用并发 Map 保存顶层数据,但其中的普通 ListMap 或业务对象不会自动变得线程安全,读—改—写组合也不是原子操作。最稳妥的做法是分支各写独立键,汇聚节点统一读取。

7、Context 与 Trace 不混用

业务结果:

String result = session.getContext().getAs(GraphKeys.OUTPUT_FINAL);

团队协作记录:

TeamTrace trace = team.getTrace(session);
trace.getRecords().forEach(record ->
        System.out.println(record.getSource()));

Graph 诊断位置:

String lastNodeId = session.getContext().lastNodeId();

lastNodeId() 只表示根图最后记录的节点,不是完整历史,也不足以表达并行或嵌套图的全部恢复状态。持久化时应保存完整 FlowContextFlowTrace

8、快照边界

String json = session.getContext().toJson();

快照保存可序列化 Context 数据和 FlowTrace。AgentSession 等运行时对象不会作为业务数据保存;TeamTrace 中的配置、调用选项和 Session 引用也需要在恢复执行时重新注入。

恢复时:

FlowContext restored = FlowContext.fromJson(json);
AgentSession restoredSession = InMemoryAgentSession.of(restored);

ChatModel、Agent、TeamAgent、Graph、Driver、Executor、事件监听器和外部连接都应重新构建。