Solon v4.1.0

嵌套 Agent、TeamAgent 与子图

</> markdown
2026年9月18日 上午11:56:57

复杂系统不应堆在一个超大 Graph 中。可以组合三类执行单元:

主 Graph
  ├─ 单 Agent
  ├─ TeamAgent
  └─ Graph.asTask() 子图

1、TeamAgent 作为 Agent 节点

1.1 父 TeamAgent Graph 直接嵌套子 TeamAgent

TeamAgent 本身实现 Agent,因此可成为父 TeamAgent Graph 的 Activity。父 TeamTrace 和 TeamProtocol 会为子团队准备本次 Prompt:

TeamAgent devTeam = TeamAgent.of(null)
        .name("dev_team")
        .protocol(TeamProtocols.SEQUENTIAL)
        .feedbackMode(false)
        .agentAdd(coder, tester)
        .outputKey("team.dev.result")
        .build();

Agent reviewer = SimpleAgent.of(chatModel)
        .name("reviewer")
        .role("质量审核员")
        .instruction("审核开发小组交付物:\n#{team.dev.result}")
        .outputKey("review.result")
        .build();

TeamAgent project = TeamAgent.of(null)
        .name("project_graph")
        .protocol(TeamProtocols.NONE)
        .agentAdd(devTeam, reviewer)
        .graphAdjuster(spec -> {
            spec.addStart(Agent.ID_START).linkAdd(devTeam.name());
            spec.addActivity(devTeam).linkAdd(reviewer.name());
            spec.addActivity(reviewer).linkAdd(Agent.ID_END);
            spec.addEnd(Agent.ID_END);
        })
        .build();

父 TeamTrace 记录 dev_teamreviewer;子 TeamTrace 记录 codertester

TeamTrace projectTrace = project.getTrace(session);
TeamTrace devTrace = devTeam.getTrace(session);

团队名称必须稳定且避免冲突。默认 TraceKey 是 __ 加团队名称,TeamAgent 内部 Graph ID 也使用该 TraceKey,例如:

team.name = dev_team
trace.key = __dev_team
graph.id  = __dev_team

1.2 独立业务 Graph 必须显式调用 TeamAgent

普通独立 Graph 没有父 TeamTrace,不能直接写 spec.addActivity(devTeam) 后期待业务 Context 自动变成团队 Prompt。应使用适配 Activity 明确输入、Session 和输出:

spec.addActivity("dev_team_step")
        .task((ctx, node) -> {
            AgentSession session = ctx.getAs(Agent.KEY_SESSION);
            String requirement = ctx.getAs("input.requirement");

            String result = devTeam.prompt(requirement)
                    .session(session)
                    .call()
                    .getContent();

            ctx.put("team.dev.result", result);
        })
        .linkAdd("next");

此时 TeamAgent 仍负责自己的内部 Graph、TeamTrace 和结果收敛;外层业务 Graph 负责把业务数据映射成团队 Prompt。只有在父 TeamAgent 的内部 Graph 中,才适合把子 TeamAgent 直接作为 Agent 节点使用。

2、Graph.asTask() 确定性子图

Graph prepareGraph = Graph.create("prepare_graph", spec -> {
    spec.addStart("prepare_start").linkAdd("normalize");
    spec.addActivity("normalize")
            .task((ctx, node) -> {
                String input = ctx.getAs("input.raw");
                ctx.put("input.normalized", input.trim());
            })
            .linkAdd("prepare_end");
    spec.addEnd("prepare_end");
});

放入主图:

Graph mainGraph = Graph.create("main_graph", spec -> {
    spec.addStart("start").linkAdd(prepareGraph.getId());
    spec.addActivity(prepareGraph.asTask()).linkAdd("agent_step");

    spec.addActivity("agent_step")
            .task((ctx, node) -> {
                AgentSession session = ctx.getAs(Agent.KEY_SESSION);
                String prompt = ctx.getAs("input.normalized");
                String result = analyst.prompt(prompt)
                        .session(session)
                        .call()
                        .getContent();
                ctx.put("analysis.result", result);
            })
            .linkAdd("end");
    spec.addEnd("end");
});

完整执行准备:

FlowContext context = FlowContext.of("main-001");
context.put("input.raw", "  分析迁移方案  ");
InMemoryAgentSession.of(context);

FlowEngine.newInstance().eval(mainGraph, context);

asTask() 直接持有 Graph,不要求预先注册。子图沿用父图的 Context、FlowEngine、Driver、FlowTrace 和执行步数。

3、通过 #graphId 引用子图

YAML/JSON 或按 ID 组合时可以使用:

.task("#prepare_graph")

被引用 Graph 必须先加载到同一个 FlowEngine:

engine.load(prepareGraph);
engine.load(mainGraph);
engine.eval("main_graph", context);

TeamAgent 内部使用 FlowEngine.newInstance(true) 创建简化引擎。该模式使用不可写的空 Graph/命名 Driver 注册表,不用于动态 load/unload,也不能依赖 #graphId 在内部解析注册式子图。在 graphAdjuster 中组合普通子图时,应使用直接持有 Graph 的 prepareGraph.asTask()

4、两类嵌套的本质区别

维度TeamAgent 嵌套Graph 子图
目标多 Agent 协作单元确定性流程复用
TeamTrace子团队有独立 TeamTrace不自动创建
Prompt子团队有协议和 working memory不自动处理
输出可用 TeamAgent outputKey子图自行写 Context
Context与父团队共享 Session Context与父图共享 Context
停止/恢复同时涉及 TeamTrace 和 FlowTrace主要由 FlowTrace 定位

5、ID 唯一性范围

Graph ID 应在以下范围保持唯一:

  • 同一个 FlowEngine 的注册空间;
  • 共享同一 FlowContext 的嵌套执行树。

它不是整个 JVM 的强制全局唯一,但复用 ID 会污染 FlowTrace 和网关临时状态。

Node ID 只需在所属 Graph 内唯一;Graph.asTask() 默认使用子图 ID 作为父图节点 ID,因此还要避免与父图节点重名。

6、停止和中断传播

asTask()#graphId 都在同一执行交换器中运行:

  • 子图正常到 End 后,父图继续;
  • 子图 stop() 会停止整个共享执行;
  • 子图 interrupt() 会截断当前分支;
  • 恢复时按各自 Graph ID 的 FlowTrace 位置定位。

因此子图节点也必须满足幂等要求。

7、分层建议

主 Graph:业务阶段、权限和治理边界
TeamAgent:某阶段内的多 Agent 协作
子 Graph:可复用的确定性处理过程
单 Agent:边界清晰的认知任务

这种分层比一个超大 TeamAgent 更容易测试、观察、恢复和版本演进。