Solon v3.8.3

team - 示例3 - 持久化与恢复

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

模拟场景:Agent 团队在执行中途状态被存入数据库,随后重启并从断点恢复执行。

示例代码

示例也可以改造成这样的编排:

id: persistence_team
layout:
  - {id: 'start', type: 'start', link: 'searcher'}
  - {id: 'searcher', type: 'activity', task: '@searcher', link: 'router'}
  - {id: 'router', type: 'exclusive', task: '@router',
     meta: {agentNames: ['planner']},
     link: [{nextId: 'planner', when: '"planner".equals(next_agent)'},
            {nextId: 'end'}
     ]
  }
  - {id: 'planner', type: 'activity', task: '@planner', link: 'end'}
  - {id: 'end',  type: 'end'}

示例代码:

import demo.ai.agent.LlmUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.noear.solon.ai.agent.Agent;
import org.noear.solon.ai.agent.AgentSession;
import org.noear.solon.ai.agent.react.ReActAgent;
import org.noear.solon.ai.agent.session.InMemoryAgentSession;
import org.noear.solon.ai.agent.team.TeamAgent;
import org.noear.solon.ai.agent.team.TeamTrace;
import org.noear.solon.ai.chat.ChatModel;
import org.noear.solon.ai.chat.ChatRole;
import org.noear.solon.ai.chat.prompt.Prompt;
import org.noear.solon.flow.FlowContext;

/**
 * 状态持久化与断点续跑测试
 * <p>验证:当 Agent 系统发生崩溃或主动挂起后,能够通过序列化快照重建上下文记忆并继续后续决策。</p>
 */
public class TeamAgentPersistenceAndResumeTest {

    @Test
    public void testPersistenceAndResume() throws Throwable {
        ChatModel chatModel = LlmUtil.getChatModel();
        String teamName = "persistent_trip_manager";

        // 1. 构建一个带有自定义流程的团队
        TeamAgent tripAgent = TeamAgent.of(chatModel)
                .name(teamName)
                .graphAdjuster(spec -> {
                    // 自定义流程:Start -> searcher -> Supervisor (决策后续)
                    spec.addStart(Agent.ID_START).linkAdd("searcher");
                    spec.addActivity(ReActAgent.of(chatModel)
                                    .name("searcher")
                                    .description("天气搜索员,负责提供实时气候数据")
                                    .build())
                            .linkAdd(Agent.ID_SUPERVISOR);
                }).build();

        // --- 阶段 A:模拟第一阶段执行并手动构建持久化快照 ---
        // 假设我们在另一台机器上运行,执行完 searcher 后,我们将状态序列化到 DB
        FlowContext contextStep1 = FlowContext.of("order_sn_998");

        // 手动模拟 Trace 状态:已经完成了天气搜索
        TeamTrace snapshot = new TeamTrace(Prompt.of("帮我规划上海行程并给穿衣建议"));
        snapshot.addStep(ChatRole.ASSISTANT,"searcher", "上海明日天气:大雨转雷阵雨,气温 12 度。", 800L);
        // 设置当前路由断点为 Supervisor,准备让它恢复后进行决策
        snapshot.setRoute(Agent.ID_SUPERVISOR);

        // 将轨迹存入上下文,key 遵循框架规范 "__" + teamName
        contextStep1.put("__" + teamName, snapshot);

        // 模拟落库序列化(JSON)
        String jsonState = contextStep1.toJson();
        System.out.println(">>> 阶段 A:初始状态已持久化至数据库。当前断点:" + snapshot.getRoute());

        // --- 阶段 B:从持久化数据恢复并续跑 ---
        System.out.println("\n>>> 阶段 B:正在从 JSON 快照恢复任务...");

        // 从 JSON 重建 FlowContext,并包装成新的 AgentSession
        FlowContext restoredContext = FlowContext.fromJson(jsonState);
        AgentSession session = InMemoryAgentSession.of(restoredContext);

        // 验证恢复:调用时不传 Prompt,触发“断点续跑”模式
        String finalResult = tripAgent.call(session).getContent();

        // --- 阶段 C:核心验证 ---
        TeamTrace finalTrace = tripAgent.getTrace(session);

        // 验证 1:状态恢复完整性
        Assertions.assertNotNull(finalTrace, "恢复后的轨迹不应为空");
        Assertions.assertTrue(finalTrace.getStepCount() >= 2, "轨迹应包含预设的 searcher 步及后续生成步");

        // 验证 2:历史记忆持久性(Agent 是否还记得 searcher 提供的数据)
        boolean remembersWeather = finalTrace.getFormattedHistory().contains("上海明日天气");
        Assertions.assertTrue(remembersWeather, "恢复后的 Agent 应该记得快照中的天气信息");

        // 验证 3:最终决策结果
        Assertions.assertNotNull(finalResult);
        System.out.println("恢复执行后的最终答复: " + finalResult);
    }
}