---
title: "Agent 节点：Prompt、Session 与输出契约"
---

Graph 连接只定义控制流。要让 Agent 节点可靠协作，还必须明确四条数据通道：

| 通道 | 作用 |
|---|---|
| `Prompt` | 定义本次 Agent 要处理什么 |
| `FlowContext` | 保存 Graph 共享业务数据和执行状态 |
| `AgentSession` | 保存消息历史，并持有 Agent 使用的 FlowContext |
| `outputKey` | 把 Agent 最终文本回填到 Context 的指定键 |

### 1、直接 Agent 节点的执行语义

在 TeamAgent 内部 Graph 中可以直接添加 Agent：

```java
spec.addActivity(researcher)
        .linkAdd(reviewer.name());
```

执行时 `Agent.run(FlowContext, Node)` 会：

1. 从 Context 获取当前 `AgentSession`；
2. 从当前 `TeamTrace` 和 `TeamProtocol` 准备 Prompt；
3. 调用 Agent；
4. 将成员结果加入 TeamTrace；
5. 若配置了 `outputKey`，由具体 Agent 将文本写回 Session Context。

这正是直接节点适合 TeamAgent Graph 的原因。

### 2、控制流不是数据流

```java
researcher -> reviewer
```

只保证 reviewer 在 researcher 之后执行，不保证 reviewer 自动看见研究结果。

在 `TeamProtocols.NONE` 下，默认成员 Prompt 来自团队原始 working memory 的副本；前序成员输出进入 TeamTrace records 和自己的 `outputKey`，但不会自动追加到后序成员 Prompt。

可选的显式交接方式：

#### Context 模板

```java
Agent reviewer = SimpleAgent.of(chatModel)
        .name("reviewer")
        .role("审核员")
        .instruction("审核以下研究结果：\n#{research_result}")
        .outputKey("review_result")
        .build();
```

#### 显式适配 Activity

```java
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 的精确含义

```java
.outputKey("research_result")
```

对 `SimpleAgent` 而言，它把最终 `AssistantMessage.getContent()` 写入 Session Context。对 `TeamAgent` 而言，它写入团队 `finalAnswer`；没有显式最终答案时回退到最后一条 Agent 记录。

`outputKey` 不会：

- 保存完整响应对象；
- 自动变成下一节点 Prompt；
- 自动创建结构化业务对象；
- 自动完成并行结果合并。

需要完整响应、结构化解析或业务校验时，应使用显式 Activity。

### 4、独立 Graph 的安全适配模式

普通独立 Graph 没有 TeamTrace 为 Agent 准备 Prompt，推荐固定使用以下模式：

```java
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 中假设：

```java
spec.addActivity(riskAgent); // 不会自动读取 input.question
```

### 5、Session 必须绑定同一个 Context

正确：

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

错误：

```java
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、命名和输出契约

建议为每个节点写明：

```text
节点 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 拓扑才真正具有可维护的数据语义。