FlowContext:Agent Graph 的数据交换契约
Graph 负责控制流,FlowContext 负责业务数据。AgentSession、TeamTrace 和 Agent Trace 则分别承担会话与执行轨迹职责。
1、状态分层
| 对象 | 职责 | 典型内容 |
|---|---|---|
FlowContext | Graph 实例数据和恢复游标 | 输入、中间结果、路由键、FlowTrace |
AgentSession | Agent 会话边界 | 消息历史、FlowContext、pending 状态 |
TeamTrace | TeamAgent 协作状态 | 原始 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 保存顶层数据,但其中的普通 List、Map 或业务对象不会自动变得线程安全,读—改—写组合也不是原子操作。最稳妥的做法是分支各写独立键,汇聚节点统一读取。
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() 只表示根图最后记录的节点,不是完整历史,也不足以表达并行或嵌套图的全部恢复状态。持久化时应保存完整 FlowContext 和 FlowTrace。
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、事件监听器和外部连接都应重新构建。