diff --git a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSession.java b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSession.java index fce034562..f011233b8 100644 --- a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSession.java +++ b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSession.java @@ -5,23 +5,73 @@ import java.time.Instant; import java.util.Objects; import java.util.concurrent.locks.ReentrantLock; +/** + * 单个 agent 在某次会话中的运行时状态。 + * + *

会话标识被拆分为两个维度: + *

+ * + *

{@code workspaceDir} 仅按 {@code conversationId} 创建,因此同一段对话中的多个 agent + * 共享同一个工作区目录(实现 agent 之间的文件协作)。 + */ public class AgentSession { - private final String sessionId; + + private final String conversationId; + private final String agentKey; + private final String cacheKey; private final Path workspaceDir; private final ReentrantLock lock = new ReentrantLock(); private volatile Object agent; private volatile Instant lastActive = Instant.now(); - public AgentSession(String sessionId, Path workspaceDir) { - this.sessionId = Objects.requireNonNull(sessionId); - this.workspaceDir = Objects.requireNonNull(workspaceDir); + public AgentSession(String conversationId, String agentKey, String cacheKey, Path workspaceDir) { + this.conversationId = Objects.requireNonNull(conversationId, "conversationId"); + this.agentKey = Objects.requireNonNull(agentKey, "agentKey"); + this.cacheKey = Objects.requireNonNull(cacheKey, "cacheKey"); + this.workspaceDir = Objects.requireNonNull(workspaceDir, "workspaceDir"); } - public String getSessionId() { return sessionId; } - public Path getWorkspaceDir() { return workspaceDir; } - public ReentrantLock getLock() { return lock; } - public Object getAgent() { return agent; } - public void setAgent(Object agent) { this.agent = agent; } - public Instant getLastActive() { return lastActive; } - public void touch() { this.lastActive = Instant.now(); } + public String getConversationId() { + return conversationId; + } + + public String getAgentKey() { + return agentKey; + } + + /** + * JVM 内的缓存 key 与持久化 key,由 {@code conversationId} 与 {@code agentKey} 组合并安全编码后得到。 + */ + public String getCacheKey() { + return cacheKey; + } + + public Path getWorkspaceDir() { + return workspaceDir; + } + + public ReentrantLock getLock() { + return lock; + } + + public Object getAgent() { + return agent; + } + + public void setAgent(Object agent) { + this.agent = agent; + } + + public Instant getLastActive() { + return lastActive; + } + + public void touch() { + this.lastActive = Instant.now(); + } } diff --git a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSessionManager.java b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSessionManager.java index 30301e927..798e4b6a6 100644 --- a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSessionManager.java +++ b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/AgentSessionManager.java @@ -25,22 +25,27 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; /** - * 跟踪当前 JVM 中存活的 AgentSession,并桥接到可插拔的 + * 跟踪当前 JVM 中存活的 {@link AgentSession},并桥接到可插拔的 * {@link Session}(由 {@link AgentSessionFactoryRegistry} 提供)。 * - *

这里将两类职责保持独立: + *

会话标识被拆分为两个维度: *

* - *

淘汰(空闲或超过容量)只移除缓存的 agent 实例。 - * workspace 文件以及磁盘、Redis、MySQL 中的持久化 session 数据会被保留。 + *

缓存与持久化都按 {@code (conversationId, agentKey)} 组合 key 隔离; + * workspace 目录则只按 {@code conversationId} 建一份,让同一段对话中的多个 agent + * 通过共享文件协作。 */ public class AgentSessionManager implements AutoCloseable { private static final Pattern SAFE = Pattern.compile("[a-zA-Z0-9_\\-]+"); + /** 缓存 key 内部使用的分隔符;不在 SAFE 字符集中以外,且不会出现在 NanoId 输出里。 */ + static final String KEY_SEPARATOR = "__"; + private final AgentConfig config; private final Path root; private final Map sessions = new ConcurrentHashMap<>(); @@ -73,26 +78,37 @@ public class AgentSessionManager implements AutoCloseable { cleaner.scheduleWithFixedDelay(this::cleanup, every, every, TimeUnit.MILLISECONDS); } - public AgentSession acquire(String sessionId) { - String safe = safeId(sessionId); - AgentSession s = sessions.computeIfAbsent(safe, id -> { - Path ws = root.resolve(id); - try { Files.createDirectories(ws); } - catch (IOException e) { throw new AgentConfigException("cannot create workspace: " + ws, e); } - return new AgentSession(id, ws); + /** + * 获取(或创建)一个 agent 会话。 + * + *

同一 {@code conversationId} 下的多个 {@code agentKey} 共享同一个 workspace 目录, + * 但分别拥有独立的 {@link AgentSession}(独立的 ReActAgent 实例、独立的记忆持久化 key)。 + */ + public AgentSession acquire(String conversationId, String agentKey) { + String safeCid = safeId(conversationId); + String safeKey = safeId(agentKey); + String cacheKey = safeCid + KEY_SEPARATOR + safeKey; + AgentSession s = sessions.computeIfAbsent(cacheKey, k -> { + Path ws = root.resolve(safeCid); + try { + Files.createDirectories(ws); + } catch (IOException e) { + throw new AgentConfigException("cannot create workspace: " + ws, e); + } + return new AgentSession(safeCid, safeKey, k, ws); }); s.touch(); enforceMaxSessions(); return s; } - public boolean contains(String sessionId) { - return sessions.containsKey(safeId(sessionId)); + public boolean contains(String conversationId, String agentKey) { + return sessions.containsKey(safeId(conversationId) + KEY_SEPARATOR + safeId(agentKey)); } /** * 将之前持久化的状态懒加载恢复到 agent 中。 - * 同一个 session id 在当前 JVM 生命周期内应只调用一次,并且应在 agent + * 同一个 {@code (conversationId, agentKey)} 在当前 JVM 生命周期内应只调用一次,并且应在 agent * 构建完成后、首次 {@code agent.call(...)} 前调用。 */ public void loadIfExists(AgentSession session, ReActAgent agent) { @@ -100,7 +116,7 @@ public class AgentSessionManager implements AutoCloseable { MemoryStorageConfig mc = config.getSession().getMemory(); if (!mc.isLoadOnFirstUse()) return; if (mc.getMode() == MemoryStorageMode.NONE) return; - SessionManager.forSessionId(session.getSessionId()) + SessionManager.forSessionId(session.getCacheKey()) .withSession(storage) .addComponent(agent) .loadIfExists(); @@ -111,7 +127,7 @@ public class AgentSessionManager implements AutoCloseable { if (storage == null || agent == null) return; MemoryStorageConfig mc = config.getSession().getMemory(); if (mc.getMode() == MemoryStorageMode.NONE) return; - SessionManager.forSessionId(session.getSessionId()) + SessionManager.forSessionId(session.getCacheKey()) .withSession(storage) .addComponent(agent) .saveSession(); @@ -149,18 +165,27 @@ public class AgentSessionManager implements AutoCloseable { } /** - * @param cleanWorkspace 为 true 时,同时删除磁盘上的 workspace 目录 - * (保留历史行为)。存储在其他位置的持久化 session - * 状态(例如 workspaceRoot/.agent-session、Redis、 - * MySQL)不会在这里被删除。 + * @param cleanWorkspace 为 true 时同时尝试删除磁盘上的 workspace 目录。由于同一 workspace + * 可能被 {@code (conversationId, *)} 下的多个 agent 共享,仅在该 + * conversation 下没有其他存活会话时才会真正删除目录。 + * 存储在其他位置的持久化 session 状态(例如 workspaceRoot/.agent-session、 + * Redis、MySQL)不会在这里被删除。 */ private void evictFromCache(AgentSession s, boolean cleanWorkspace) { - sessions.remove(s.getSessionId(), s); - if (cleanWorkspace) { + sessions.remove(s.getCacheKey(), s); + if (cleanWorkspace && !hasSiblingInSameConversation(s)) { deleteRecursively(s.getWorkspaceDir()); } } + private boolean hasSiblingInSameConversation(AgentSession evicted) { + String prefix = evicted.getConversationId() + KEY_SEPARATOR; + for (String key : sessions.keySet()) { + if (key.startsWith(prefix)) return true; + } + return false; + } + private static void deleteRecursively(Path p) { if (!Files.exists(p)) return; try (var walk = Files.walk(p)) { diff --git a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/NanoIdSessionIdGenerator.java b/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/NanoIdSessionIdGenerator.java deleted file mode 100644 index 3fa6085bc..000000000 --- a/liteflow-react-agent/liteflow-react-agent-core/src/main/java/com/yomahub/liteflow/agent/session/NanoIdSessionIdGenerator.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.yomahub.liteflow.agent.session; - -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.lang.id.NanoId; -import cn.hutool.core.util.StrUtil; - -import java.util.Date; - -public class NanoIdSessionIdGenerator { - - private static final char[] CODE_ALPHABET = - "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ".toCharArray(); - - public static String generate() { - String date = DateUtil.format(new Date(), "yyyyMMdd"); - String code = NanoId.randomNanoId(null, CODE_ALPHABET, 12); - return StrUtil.format("{}_{}", date, code); - } -} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentConversationContinuityTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentConversationContinuityTest.java new file mode 100644 index 000000000..3e3cca76d --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentConversationContinuityTest.java @@ -0,0 +1,118 @@ +package com.yomahub.liteflow.test.agent; + +import com.yomahub.liteflow.core.ExecuteOption; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.test.agent.cmp.CollabAgentACmp; +import com.yomahub.liteflow.test.agent.cmp.CollabAgentBCmp; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * 验证 {@code flowExecutor.execute2Resp(chainId, param, ExecuteOption)} 入口 + * 在 conversationId 维度上的语义: + *

+ */ +public class ReActAgentConversationContinuityTest extends AbstractReActAgentSpringbootTest { + + @BeforeEach + public void resetCollabState() { + CollabAgentACmp.reset(); + CollabAgentBCmp.reset(); + } + + @Test + public void testAutoConversationIdGeneratesNanoId() { + LiteflowResponse response = flowExecutor.execute2Resp("collabAgentChain", "go", + ExecuteOption.of().autoConversationId()); + + Assertions.assertTrue(response.isSuccess()); + String generated = response.getConversationId(); + Assertions.assertNotNull(generated, "autoConversationId() should produce a non-null cid"); + Assertions.assertFalse(generated.isBlank()); + Assertions.assertEquals(generated, CollabAgentACmp.SEEN_CONVERSATION_ID.get()); + Assertions.assertEquals(generated, CollabAgentBCmp.SEEN_CONVERSATION_ID.get()); + } + + @Test + public void testExplicitConversationIdHonored() { + String cid = "task-explicit-001"; + LiteflowResponse response = flowExecutor.execute2Resp("collabAgentChain", "go", + ExecuteOption.of().conversationId(cid)); + + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals(cid, response.getConversationId()); + Assertions.assertEquals(cid, CollabAgentACmp.SEEN_CONVERSATION_ID.get()); + Assertions.assertEquals(cid, CollabAgentBCmp.SEEN_CONVERSATION_ID.get()); + } + + /** + * 同一 conversationId 跨次调用:第二次进入 agent A 时应能看到第一次留下的标记文件。 + */ + @Test + public void testSameConversationIdSharesWorkspaceAcrossCalls() { + // 每次运行用唯一 cid,避免上一次 mvn test 残留在 target/wk_root 下的目录干扰断言。 + String cid = "continuity-" + System.nanoTime(); + + LiteflowResponse first = flowExecutor.execute2Resp("collabAgentChain", "first", + ExecuteOption.of().conversationId(cid)); + Assertions.assertTrue(first.isSuccess()); + String workspaceFromFirst = CollabAgentACmp.SEEN_WORKSPACE.get(); + Assertions.assertNotNull(workspaceFromFirst); + Assertions.assertEquals(Boolean.FALSE, CollabAgentACmp.MARKER_EXISTED_BEFORE_WRITE.get(), + "first call should see no pre-existing marker file"); + + CollabAgentACmp.reset(); + CollabAgentBCmp.reset(); + + LiteflowResponse second = flowExecutor.execute2Resp("collabAgentChain", "second", + ExecuteOption.of().conversationId(cid)); + Assertions.assertTrue(second.isSuccess()); + Assertions.assertEquals(cid, second.getConversationId()); + Assertions.assertEquals(workspaceFromFirst, CollabAgentACmp.SEEN_WORKSPACE.get(), + "second call with same conversationId should resolve to the same workspace dir"); + Assertions.assertEquals(Boolean.TRUE, CollabAgentACmp.MARKER_EXISTED_BEFORE_WRITE.get(), + "second call should observe the marker written by the previous call - workspace state persists across calls"); + Assertions.assertEquals(CollabAgentACmp.MARKER_CONTENT, CollabAgentBCmp.READ_MARKER.get()); + } + + /** + * 不在 ExecuteOption 中声明 cid 时(既不调用 .conversationId 也不调用 .autoConversationId), + * 框架不应主动写入 slot.conversationId,沿用既有 chain 行为。 + * 但 ReActAgentComponent 自己的 resolver 仍会兜底为本次执行生成一个,这是组件层面的契约, + * 与 FlowExecutor 入口语义独立。 + */ + @Test + public void testNullExecuteOptionDoesNotForceCidAtExecutorLayer() { + LiteflowResponse response = flowExecutor.execute2Resp("collabAgentChain", "no-cid", + ExecuteOption.of()); // 既未 .conversationId 也未 .autoConversationId + + Assertions.assertTrue(response.isSuccess()); + // 在没有显式声明的情况下,由 ReActAgentComponent 默认 resolver 生成 cid 并写回 slot; + // 因此 response 仍能取到 cid,只是这个 cid 来自组件而非 FlowExecutor 入口。 + Assertions.assertNotNull(response.getConversationId()); + } + + /** + * rid + cid 同时设置:验证组合不爆炸,所有维度都生效。 + */ + @Test + public void testRequestIdAndConversationIdComposed() { + String rid = "req-id-xyz"; + String cid = "conv-id-xyz"; + LiteflowResponse response = flowExecutor.execute2Resp("collabAgentChain", "compose", + ExecuteOption.of() + .requestId(rid) + .conversationId(cid)); + + Assertions.assertTrue(response.isSuccess()); + Assertions.assertEquals(rid, response.getRequestId()); + Assertions.assertEquals(cid, response.getConversationId()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentELChainTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentELChainTest.java index c1cd751b4..8e10416da 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentELChainTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentELChainTest.java @@ -31,7 +31,7 @@ public class ReActAgentELChainTest extends AbstractReActAgentSpringbootTest { // recordReply 会把 agent 回复保存到本节点 output,测试由此确认后置节点拿到了回复。 Object reply = response.getSlot().getOutput(RecordReplyCmp.NODE_ID); Assertions.assertNotNull(reply); - Assertions.assertTrue(reply.toString().contains("reply:" + StubReActAgentCmp.FIXED_SESSION_ID)); + Assertions.assertTrue(reply.toString().contains("reply:" + StubReActAgentCmp.FIXED_CONVERSATION_ID)); // userPrompt 必须来自 prepare 写入的 chainReqData,不能绕开 LiteFlow slot 上下文。 Assertions.assertEquals(List.of("hello-liteflow-agent"), StubReActAgentCmp.USER_PROMPTS); diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentMultiAgentChainTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentMultiAgentChainTest.java new file mode 100644 index 000000000..7682ff7f5 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentMultiAgentChainTest.java @@ -0,0 +1,76 @@ +package com.yomahub.liteflow.test.agent; + +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.test.agent.cmp.CollabAgentACmp; +import com.yomahub.liteflow.test.agent.cmp.CollabAgentBCmp; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +/** + * 验证一条 chain 内编排多个 ReAct Agent 时的会话/工作区行为: + * + */ +public class ReActAgentMultiAgentChainTest extends AbstractReActAgentSpringbootTest { + + private static final String CONV_ID = "collab-conv-1024"; + + @BeforeEach + public void resetCollabState() { + CollabAgentACmp.reset(); + CollabAgentBCmp.reset(); + } + + @Test + public void testMultipleAgentsShareWorkspaceButHaveDistinctAgentKeys() { + Map req = Map.of("conversationId", CONV_ID, "userInput", "go"); + LiteflowResponse response = flowExecutor.execute2Resp("collabAgentChain", req); + + Assertions.assertTrue(response.isSuccess(), + "chain failed: " + (response.getCause() == null ? "" : response.getCause().getMessage())); + + // 1. chain 整体共享 conversationId(来自请求 Map 中的 "conversationId") + Assertions.assertEquals(CONV_ID, response.getConversationId(), + "conversationId in request map should propagate to LiteflowResponse"); + Assertions.assertEquals(CONV_ID, CollabAgentACmp.SEEN_CONVERSATION_ID.get(), + "first agent should resolve conversationId from request data"); + Assertions.assertEquals(CONV_ID, CollabAgentBCmp.SEEN_CONVERSATION_ID.get(), + "second agent should reuse the same conversationId via slot"); + + // 2. workspace 共享:A 和 B 看到的 workspace 路径一致 + Assertions.assertNotNull(CollabAgentACmp.SEEN_WORKSPACE.get()); + Assertions.assertEquals(CollabAgentACmp.SEEN_WORKSPACE.get(), CollabAgentBCmp.SEEN_WORKSPACE.get(), + "both agents in the same conversation should resolve to the same workspace dir"); + + // 3. agentKey 不同:默认对应各自的 nodeId + Assertions.assertEquals("collabAgentA", CollabAgentACmp.SEEN_AGENT_KEY.get()); + Assertions.assertEquals("collabAgentB", CollabAgentBCmp.SEEN_AGENT_KEY.get()); + + // 4. 文件协作:A 写入的 marker 在 B 中可读 + Assertions.assertEquals(CollabAgentACmp.MARKER_CONTENT, CollabAgentBCmp.READ_MARKER.get(), + "agent B should be able to read the marker file written by agent A in the shared workspace"); + } + + @Test + public void testDifferentConversationIdsGetSeparateWorkspaces() { + flowExecutor.execute2Resp("collabAgentChain", + Map.of("conversationId", "convX", "userInput", "x")); + String workspaceX = CollabAgentACmp.SEEN_WORKSPACE.get(); + + flowExecutor.execute2Resp("collabAgentChain", + Map.of("conversationId", "convY", "userInput", "y")); + String workspaceY = CollabAgentACmp.SEEN_WORKSPACE.get(); + + Assertions.assertNotNull(workspaceX); + Assertions.assertNotNull(workspaceY); + Assertions.assertNotEquals(workspaceX, workspaceY, + "different conversationIds must resolve to different workspace directories"); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentSessionTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentSessionTest.java index 319f57ec5..ad1b5950b 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentSessionTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentSessionTest.java @@ -8,25 +8,25 @@ import org.junit.jupiter.api.Test; /** * 测试 ReAct Agent 的 Session 管理行为。 * - *

测试桩组件固定返回同一个 sessionId,因此同一个测试方法内连续执行两次链路时, + *

测试桩组件固定返回同一个 conversationId,因此同一个测试方法内连续执行两次链路时, * 第二次应该复用第一次构建好的 ReActAgent 实例,而不是重新构建模型、工具和系统提示词。 */ public class ReActAgentSessionTest extends AbstractReActAgentSpringbootTest { /** - * 验证相同 sessionId 会复用同一个受管 ReActAgent 实例。 + * 验证相同 conversationId + 同一 agent 节点会复用受管 ReActAgent 实例。 */ @Test - public void testStubAgentReusesManagedSessionForSameSessionId() { + public void testStubAgentReusesManagedSessionForSameConversationId() { LiteflowResponse first = flowExecutor.execute2Resp("stubAgentChain", "first"); LiteflowResponse second = flowExecutor.execute2Resp("stubAgentChain", "second"); Assertions.assertTrue(first.isSuccess()); Assertions.assertTrue(second.isSuccess()); - // 同一个 sessionId 只会触发一次模型解析,说明第二次执行复用了已有 agent。 + // 同一个 conversationId 在同一 agent 上只会触发一次模型解析,说明第二次执行复用了已有 agent。 Assertions.assertEquals(1, StubReActAgentCmp.SPEC_RESOLVE_COUNT.get(), - "same session id should reuse built ReActAgent"); + "same conversation id + agent key should reuse built ReActAgent"); // 系统提示词只在 agent 构建阶段使用一次,用户提示词则每次执行都要重新生成。 Assertions.assertEquals(1, StubReActAgentCmp.SYSTEM_PROMPT_COUNT.get(), @@ -40,5 +40,8 @@ public class ReActAgentSessionTest extends AbstractReActAgentSpringbootTest { // 第二次执行仍然能读取新的 LiteFlow 请求数据,而不是复用第一次的用户输入。 Assertions.assertTrue(StubReActAgentCmp.MODEL_PROBES.get(1).inputTexts().contains("second")); + + // LiteflowResponse 应该能透出 chain 内解析到的 conversationId。 + Assertions.assertEquals(StubReActAgentCmp.FIXED_CONVERSATION_ID, first.getConversationId()); } } diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentWorkspaceTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentWorkspaceTest.java index aac86e664..2f17cb670 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentWorkspaceTest.java +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/ReActAgentWorkspaceTest.java @@ -10,16 +10,16 @@ import java.nio.file.Path; /** * 测试 ReAct Agent 的 Workspace 目录管理。 * - *

ReActAgentComponent 会通过 AgentSessionManager 为每个 session 创建独立工作目录。 - * 这个类只验证 workspace 的创建和 sessionId 到目录名的映射,不测试工具注册细节。 + *

ReActAgentComponent 会通过 AgentSessionManager 为每个 conversation 创建独立工作目录。 + * 这个类只验证 workspace 的创建和 conversationId 到目录名的映射,不测试工具注册细节。 */ public class ReActAgentWorkspaceTest extends AbstractReActAgentSpringbootTest { /** - * 验证执行 agent 节点时会为当前 session 创建 workspace。 + * 验证执行 agent 节点时会按 conversationId 创建 workspace 目录。 */ @Test - public void testWorkspaceIsCreatedForResolvedSessionId() { + public void testWorkspaceIsCreatedForResolvedConversationId() { LiteflowResponse response = flowExecutor.execute2Resp("stubAgentChain", "workspace"); Assertions.assertTrue(response.isSuccess()); @@ -27,12 +27,12 @@ public class ReActAgentWorkspaceTest extends AbstractReActAgentSpringbootTest { StubReActAgentCmp.ModelProbe probe = StubReActAgentCmp.MODEL_PROBES.get(0); - // 模型桩会记录 ReActAgentContext 中的 sessionId 和 workspace 路径。 - Assertions.assertEquals(StubReActAgentCmp.FIXED_SESSION_ID, probe.sessionId()); + // 模型桩会记录 ReActAgentContext 中的 conversationId 和 workspace 路径。 + Assertions.assertEquals(StubReActAgentCmp.FIXED_CONVERSATION_ID, probe.conversationId()); Assertions.assertTrue(probe.workspaceExists()); - // 固定 sessionId 应该出现在 workspace 目录末尾,便于排查和隔离不同会话文件。 - Assertions.assertEquals(StubReActAgentCmp.FIXED_SESSION_ID, + // 固定 conversationId 应该出现在 workspace 目录末尾,便于排查和隔离不同会话文件。 + Assertions.assertEquals(StubReActAgentCmp.FIXED_CONVERSATION_ID, Path.of(probe.workspaceDir()).getFileName().toString()); } } diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/flow.el.xml index 008537733..8ff3d8576 100644 --- a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/flow.el.xml +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/flow.el.xml @@ -22,4 +22,8 @@ THEN(prepare, stubAgent, recordReply); + + THEN(prepare, collabAgentA, collabAgentB, recordReply); + +