Solon v3.8.3

team - TeamAgent 自由模式 (NONE 协议)

</> markdown
2026年1月15日 下午6:14:46

在默认情况下,TeamAgent 遵循预定义的团队协议(如 Leader 或 A2A 模式)。但在某些业务场景下,我们需要打破固定协议,自定义 Agent 之间的流转逻辑,例如 并行计算、条件分支或复杂的网关聚合。

自由模式(NONE 协议) 的核心特征是:不使用内置协作协议,通过 graphAdjuster 完全自主定义执行图(Graph),实现“手写”业务流。

协议常量 TeamProtocols.NONE,v3.8.4 后内置,旧版可直接使用:

public class NoneProtocol implements TeamProtocol {
    @Override
    public String name() { return "NONE"; }
    public NoneProtocol(TeamAgentConfig config) { }
    @Override
    public void buildGraph(GraphSpec spec) { }
}

1、如何使用自由模式

通过指定 .protocol(TeamProtocols.NONE) 显式关闭内置编排引擎。

TeamAgent team = TeamAgent.of(chatModel)
        .name("my_custom_team")
        .protocol(TeamProtocols.NONE) // 切换为自由模式(或者用: NoneProtocol::new)
        .graphAdjuster(spec -> {
            // 在此处手绘执行图
        })
        .build();

2、自由模式的轨迹提取 (TeamTrace)

在自由模式下,由于框架不再代劳最终回答,我们需要从 TeamTrace 中手动提取或设置结果:

  • 踪迹获取:通过 team.getTrace(session) 获取当前会话的协作轨迹。
  • 节点回溯:通过 trace.getSteps() 遍历每个 Agent 的执行快照。
  • 手动定论:如果在执行图的末尾生成了最终结论,可以使用 trace.setFinalAnswer(...) 显式标记,以便后续流程或 UI 展示。

3、示例:A/B 测试共识决策系统

下面的示例展示了如何利用 NONE 协议 编排一个并行分析团队:将测试指标同步分发给三位专家,并在汇聚节点(Parallel Gateway)完成多数票表决。

public class ABTestingDecisionGraphTest {

    @Test
    @DisplayName("测试 A/B 测试 Graph:验证并行节点执行与结果汇聚决策")
    public void testABTestingDecisionProcess() throws Throwable {
        ChatModel chatModel = LlmUtil.getChatModel();

        // --- 1. 初始化专家角色 (利用 SimpleAgent 的 outputKey 自动回填能力) ---
        Agent dataAnalyst = createExpert(chatModel, "data_analyst", "数据分析专家", "data_opinion");
        Agent productManager = createExpert(chatModel, "product_manager", "产品经理", "product_opinion");
        Agent engineeringLead = createExpert(chatModel, "engineering_lead", "工程负责人", "engineering_opinion");

        TeamAgent team = TeamAgent.of(chatModel)
                .name("ab_testing_team")
                .protocol(TeamProtocols.NONE)
                .graphAdjuster(spec -> {

                    // A. 入口指派:从 Supervisor 指向数据准备节点
                    spec.addStart(Agent.ID_START)
                            .linkAdd("test_result_input");

                    // B. 数据准备 (Activity):模拟从数据库/API 加载测试指标
                    spec.addActivity("test_result_input")
                            .title("准备测试数据")
                            .task((ctx, node) -> {
                                ctx.put("variant_a_conv", 15.2);
                                ctx.put("variant_b_conv", 18.7);
                                System.out.println(">>> [Node] 业务指标载入 Context");
                            })
                            .linkAdd("parallel_analysis");

                    // C. 并行分发 (Parallel):同时触发三个专家的异步分析
                    spec.addParallel("parallel_analysis").title("启动多维度并行分析")
                            .linkAdd(dataAnalyst.name())
                            .linkAdd(productManager.name())
                            .linkAdd(engineeringLead.name());

                    // D. 结果汇聚:所有专家处理完后,自动跳转至决策网关
                    spec.addActivity(dataAnalyst).linkAdd("decision_gateway");
                    spec.addActivity(productManager).linkAdd("decision_gateway");
                    spec.addActivity(engineeringLead).linkAdd("decision_gateway");

                    // E. 共识决策 (Parallel 汇聚):基于 Context 中的专家意见进行多数票表决
                    spec.addParallel("decision_gateway")
                            .title("多数票共识决策")
                            .task((ctx, node) -> {
                                // 提取由 SimpleAgent.outputKey 自动同步过来的结果
                                String d = ctx.getAs("data_opinion");
                                String p = ctx.getAs("product_opinion");
                                String e = ctx.getAs("engineering_opinion");

                                int approveCount = 0;
                                if (isApprove(d)) approveCount++;
                                if (isApprove(p)) approveCount++;
                                if (isApprove(e)) approveCount++;

                                String finalVerdict = (approveCount >= 2) ? "PROMOTED_B" : "RETAINED_A";
                                ctx.put("ab_test_decision", finalVerdict);
                                System.out.println(">>> [Decision] 赞成票: " + approveCount + ", 最终裁决: " + finalVerdict);
                            })
                            .linkAdd(Agent.ID_END);

                    spec.addEnd(Agent.ID_END);
                })
                .build();

        // --- 2. 运行流程 ---
        AgentSession session = InMemoryAgentSession.of("session_ab_test_01");
        String query = "当前 A 转化率 15.2%, B 转化率 18.7%。请给出你的评估意见(approve/reject)。";
        team.call(Prompt.of(query), session);

        // --- 3. 结果验证 ---

        // A. 验证业务 Activity 逻辑:数据是否成功写入上下文
        Assertions.assertEquals(15.2, session.getSnapshot().getAs("variant_a_conv"), "数据加载节点未执行");

        // B. 验证 Agent 参与轨迹:Trace 记录 AI 专家的交互足迹
        TeamTrace trace = team.getTrace(session);
        List<String> agentFootprints = trace.getSteps().stream()
                .map(TeamTrace.TeamStep::getSource)
                .collect(Collectors.toList());

        System.out.println("AI 执行足迹: " + agentFootprints);
        Assertions.assertTrue(agentFootprints.contains("data_analyst"), "Trace 记录缺失专家节点");

        // C. 验证最终业务决策结果
        String decision = session.getSnapshot().getAs("ab_test_decision");
        Assertions.assertNotNull(decision, "决策结果未生成");
        System.out.println("测试成功。最终结论: " + decision);
    }

    /**
     * 构建专家 Agent,配置 outputKey 实现 Agent 输出与 Session Context 的自动映射
     */
    private Agent createExpert(ChatModel chatModel, String name, String role, String outputKey) {
        return SimpleAgent.of(chatModel)
                .name(name)
                .systemPrompt(SimpleSystemPrompt.builder()
                        .role(role)
                        .instruction("你负责评估 A/B 测试。如果 B 优于 A,回复 'approve',否则回复 'reject'。只输出单词。")
                        .build())
                .outputKey(outputKey) // 重要:Agent 执行完后会自动将 Content 写入 Context[outputKey]
                .chatOptions(o -> o.temperature(0.1F))
                .build();
    }

    private boolean isApprove(String opinion) {
        return opinion != null && opinion.toLowerCase().contains("approve");
    }
}