Solon v4.1.0

Agent 节点:Prompt、Session 与输出契约

</> markdown
2026年9月18日 上午11:51:06

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) 会:

  1. 从 Context 获取当前 AgentSession
  2. 从当前 TeamTraceTeamProtocol 准备 Prompt;
  3. 调用 Agent;
  4. 将成员结果加入 TeamTrace;
  5. 若配置了 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 拓扑才真正具有可维护的数据语义。