Agent 节点:Prompt、Session 与输出契约
Graph 连接只定义控制流。要让 Agent 节点可靠协作,还必须明确四条数据通道:
| 通道 | 作用 |
|---|---|
Prompt | 定义本次 Agent 要处理什么 |
FlowContext | 保存 Graph 共享业务数据和执行状态 |
AgentSession | 保存消息历史,并持有 Agent 使用的 FlowContext |
outputKey | 把 Agent 最终文本回填到 Context 的指定键 |
1、直接 Agent 节点的执行语义
在 TeamAgent 内部 Graph 中可以直接添加 Agent:
spec.addActivity(researcher)
.linkAdd(reviewer.name());
执行时 Agent.run(FlowContext, Node) 会:
- 从 Context 获取当前
AgentSession; - 从当前
TeamTrace和TeamProtocol准备 Prompt; - 调用 Agent;
- 将成员结果加入 TeamTrace;
- 若配置了
outputKey,由具体 Agent 将文本写回 Session Context。
这正是直接节点适合 TeamAgent Graph 的原因。
2、控制流不是数据流
researcher -> reviewer
只保证 reviewer 在 researcher 之后执行,不保证 reviewer 自动看见研究结果。
在 TeamProtocols.NONE 下,默认成员 Prompt 来自团队原始 working memory 的副本;前序成员输出进入 TeamTrace records 和自己的 outputKey,但不会自动追加到后序成员 Prompt。
可选的显式交接方式:
Context 模板
Agent reviewer = SimpleAgent.of(chatModel)
.name("reviewer")
.role("审核员")
.instruction("审核以下研究结果:\n#{research_result}")
.outputKey("review_result")
.build();
显式适配 Activity
spec.addActivity("review_step")
.task((ctx, node) -> {
AgentSession session = ctx.getAs(Agent.KEY_SESSION);
String research = ctx.getAs("research_result");
String review = reviewer.prompt("请审核:\n" + research)
.session(session)
.call()
.getContent();
ctx.put("review_result", review);
});
自定义 TeamProtocol
当多个节点都需要统一拼接协作历史时,可以自定义 prepareAgentPrompt(...),集中定义团队 Prompt 策略。不要在每个 Agent 中依赖隐含约定。
3、outputKey 的精确含义
.outputKey("research_result")
对 SimpleAgent 而言,它把最终 AssistantMessage.getContent() 写入 Session Context。对 TeamAgent 而言,它写入团队 finalAnswer;没有显式最终答案时回退到最后一条 Agent 记录。
outputKey 不会:
- 保存完整响应对象;
- 自动变成下一节点 Prompt;
- 自动创建结构化业务对象;
- 自动完成并行结果合并。
需要完整响应、结构化解析或业务校验时,应使用显式 Activity。
4、独立 Graph 的安全适配模式
普通独立 Graph 没有 TeamTrace 为 Agent 准备 Prompt,推荐固定使用以下模式:
FlowContext context = FlowContext.of("risk-job-001");
AgentSession session = InMemoryAgentSession.of(context);
context.put("input.question", "分析订单 SO-1001 的风险");
Graph graph = Graph.create("risk_graph", spec -> {
spec.addStart("start").linkAdd("risk_agent_step");
spec.addActivity("risk_agent_step")
.task((ctx, node) -> {
AgentSession current = ctx.getAs(Agent.KEY_SESSION);
String question = ctx.getAs("input.question");
String result = riskAgent.prompt(question)
.session(current)
.call()
.getContent();
ctx.put("risk.result", result);
})
.linkAdd("end");
spec.addEnd("end");
});
FlowEngine.newInstance().eval(graph, context);
不要仅因 Agent 实现 NamedTaskComponent 就在独立 Graph 中假设:
spec.addActivity(riskAgent); // 不会自动读取 input.question
5、Session 必须绑定同一个 Context
正确:
FlowContext context = FlowContext.of("job-001");
AgentSession session = InMemoryAgentSession.of(context);
错误:
FlowContext context = FlowContext.of("job-001");
AgentSession session = InMemoryAgentSession.of("job-001");
即使 ID 相同,第二种写法仍创建两个不同的 Context。Agent 的 outputKey 会写入 Session 自己的 Context,外层 Graph 读取不到。
此外,不应依赖 Agent.run 找不到 Session 时创建的临时 Session;该 Session 同样拥有独立 Context。
6、命名和输出契约
建议为每个节点写明:
节点 ID:risk_agent
Prompt 来源:input.risk_request
输出键:analysis.risk
失败键:error.risk
最终输出:output.final
并遵守:
- Agent 名称稳定且在 Graph 内唯一;
- 并行 Agent 使用不同
outputKey; - Prompt 来源可追踪;
- Context 中的业务结果与 TeamTrace 中的协作记录分开;
- 最终业务结果由明确节点写入固定键。
7、选择直接节点还是适配节点?
| 场景 | 推荐 |
|---|---|
| TeamAgent + 预置协议 | 直接 Agent 节点 |
| TeamAgent + NONE,输入只来自原 Prompt | 直接 Agent 节点 |
| TeamAgent + NONE,输入依赖前序 Context | 模板或显式适配节点 |
| 独立业务 Graph | 显式适配节点 |
| 需要结构化解析、校验、降级 | 显式适配节点 |
把这些契约确定下来后,Graph 拓扑才真正具有可维护的数据语义。