diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/CompatibleCustomFeatureTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/CompatibleCustomFeatureTest.java new file mode 100644 index 000000000..2960d707a --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/CompatibleCustomFeatureTest.java @@ -0,0 +1,60 @@ +package com.yomahub.liteflow.test.agent.features.compatiblecustom; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.property.LiteflowConfig; +import com.yomahub.liteflow.property.agent.ShellMode; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import com.yomahub.liteflow.test.agent.features.support.ReActAgentFeatureTestSupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; + +import javax.annotation.Resource; + +/** + * 覆盖 guide 中“自定义 OpenAI 兼容厂商”的基础接入方式。 + * + *

测试链路固定为 {@code THEN(prepare, agent, record)},确保 Agent 组件是作为 + * LiteFlow 节点参与整体 EL 编排,而不是脱离 LiteFlow 单独调用。 + */ +@TestPropertySource(value = "classpath:/agent/features/compatiblecustom/application.properties") +@SpringBootTest(classes = CompatibleCustomFeatureTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.agent.features.compatiblecustom.cmp" }) +public class CompatibleCustomFeatureTest { + + @Resource + private FlowExecutor flowExecutor; + + @Resource + private LiteflowConfig liteflowConfig; + + @BeforeEach + public void reset() throws Exception { + ReActAgentFeatureTestSupport.ensureAgentConfig( + liteflowConfig, + "target/wk_react_agent_compatiblecustom", + false, + null, + ShellMode.DISABLED); + ReActAgentFeatureTestSupport.resetAgentSessionManager(); + CompatibleCustomEchoAgentComponent.resetCompatibleProbe(); + } + + @Test + public void testCompatibleCustomAgentRunsInThenChain() { + LiteflowResponse response = flowExecutor.execute2Resp( + "compatibleCustomFeatureChain", "hello-compatible-custom"); + + Assertions.assertTrue(response.isSuccess(), response.getMessage()); + Assertions.assertEquals(1, CompatibleCustomEchoAgentComponent.COMPATIBLE_SPEC_RESOLVE_COUNT.get(), + "首次构建 Agent 时应解析一次 compatible-custom ModelSpec"); + Assertions.assertTrue(response.getSlot().getOutput("compatibleCustomRecord").toString() + .contains("hello-compatible-custom")); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomAgentCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomAgentCmp.java new file mode 100644 index 000000000..d646f6fc8 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomAgentCmp.java @@ -0,0 +1,11 @@ +package com.yomahub.liteflow.test.agent.features.compatiblecustom.cmp; + +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import org.springframework.stereotype.Component; + +/** + * 通过 compatible-custom 配置解析构建的测试 Agent,执行时使用本地 Echo 模型。 + */ +@Component("compatibleCustomAgent") +public class CompatibleCustomAgentCmp extends CompatibleCustomEchoAgentComponent { +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomPrepareCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomPrepareCmp.java new file mode 100644 index 000000000..bcb6ea556 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomPrepareCmp.java @@ -0,0 +1,17 @@ +package com.yomahub.liteflow.test.agent.features.compatiblecustom.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * compatible-custom 功能包的准备节点。 + * + *

把 LiteFlow 入参写入 chainReqData,供 Agent 组件的 userPrompt() 读取。 + */ +@Component("compatibleCustomPrepare") +public class CompatibleCustomPrepareCmp extends NodeComponent { + @Override + public void process() { + getSlot().setChainReqData(getSlot().getChainId(), getRequestData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomRecordCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomRecordCmp.java new file mode 100644 index 000000000..74f73f209 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/compatiblecustom/cmp/CompatibleCustomRecordCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.compatiblecustom.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * compatible-custom 功能包的记录节点。 + */ +@Component("compatibleCustomRecord") +public class CompatibleCustomRecordCmp extends NodeComponent { + @Override + public void process() { + getSlot().setOutput(getNodeId(), getSlot().getResponseData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/ConversationFeatureProbe.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/ConversationFeatureProbe.java new file mode 100644 index 000000000..7f655c85b --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/ConversationFeatureProbe.java @@ -0,0 +1,27 @@ +package com.yomahub.liteflow.test.agent.features.conversation; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * conversation 功能包的测试观测点。 + */ +public final class ConversationFeatureProbe { + public static final AtomicReference AGENT_A_CONVERSATION_ID = new AtomicReference<>(); + public static final AtomicReference AGENT_B_CONVERSATION_ID = new AtomicReference<>(); + public static final AtomicReference AGENT_A_KEY = new AtomicReference<>(); + public static final AtomicReference AGENT_B_KEY = new AtomicReference<>(); + public static final AtomicReference AGENT_A_WORKSPACE = new AtomicReference<>(); + public static final AtomicReference AGENT_B_WORKSPACE = new AtomicReference<>(); + + private ConversationFeatureProbe() { + } + + public static void reset() { + AGENT_A_CONVERSATION_ID.set(null); + AGENT_B_CONVERSATION_ID.set(null); + AGENT_A_KEY.set(null); + AGENT_B_KEY.set(null); + AGENT_A_WORKSPACE.set(null); + AGENT_B_WORKSPACE.set(null); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/ConversationFeatureTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/ConversationFeatureTest.java new file mode 100644 index 000000000..328274e20 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/ConversationFeatureTest.java @@ -0,0 +1,74 @@ +package com.yomahub.liteflow.test.agent.features.conversation; + +import com.yomahub.liteflow.agent.component.ReActAgentComponent; +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.property.LiteflowConfig; +import com.yomahub.liteflow.property.agent.ShellMode; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import com.yomahub.liteflow.test.agent.features.support.ReActAgentFeatureTestSupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; + +import javax.annotation.Resource; +import java.util.Map; + +/** + * 覆盖 guide 中 conversationId、agentKey 与 workspace 的协作边界。 + * + *

链路使用 {@code THEN(agentA, agentB)},两个 Agent 默认使用不同 nodeId 作为 + * agentKey,但会继承同一个 conversationId,因此应共享同一个 workspace 子目录。 + */ +@TestPropertySource(value = "classpath:/agent/features/conversation/application.properties") +@SpringBootTest(classes = ConversationFeatureTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.agent.features.conversation.cmp" }) +public class ConversationFeatureTest { + + private static final String RAW_CONVERSATION_ID = "chat/user 1"; + + @Resource + private FlowExecutor flowExecutor; + + @Resource + private LiteflowConfig liteflowConfig; + + @BeforeEach + public void reset() throws Exception { + ReActAgentFeatureTestSupport.ensureAgentConfig( + liteflowConfig, + "target/wk_react_agent_conversation", + false, + null, + ShellMode.DISABLED); + ReActAgentFeatureTestSupport.resetAgentSessionManager(); + CompatibleCustomEchoAgentComponent.resetCompatibleProbe(); + ConversationFeatureProbe.reset(); + } + + @Test + public void testConversationIdIsSanitizedAndSharedAcrossThenAgents() { + LiteflowResponse response = flowExecutor.execute2Resp("conversationFeatureChain", Map.of( + ReActAgentComponent.CONVERSATION_ID_REQUEST_KEY, RAW_CONVERSATION_ID, + "prompt", "share workspace")); + + Assertions.assertTrue(response.isSuccess(), response.getMessage()); + Assertions.assertEquals(2, CompatibleCustomEchoAgentComponent.COMPATIBLE_SPEC_RESOLVE_COUNT.get(), + "两个不同 agentKey 的 Agent 应分别构建各自的 compatible-custom 模型"); + Assertions.assertNotEquals(RAW_CONVERSATION_ID, ConversationFeatureProbe.AGENT_A_CONVERSATION_ID.get(), + "ctx 中的 conversationId 应使用安全化后的目录名"); + Assertions.assertEquals(ConversationFeatureProbe.AGENT_A_CONVERSATION_ID.get(), + ConversationFeatureProbe.AGENT_B_CONVERSATION_ID.get(), + "同一条 THEN 链路内后续 Agent 应复用首个 Agent 的 conversation"); + Assertions.assertEquals(ConversationFeatureProbe.AGENT_A_WORKSPACE.get(), + ConversationFeatureProbe.AGENT_B_WORKSPACE.get(), + "同一 conversation 下的多个 agentKey 应共享 workspace"); + Assertions.assertEquals("conversationAgentA", ConversationFeatureProbe.AGENT_A_KEY.get()); + Assertions.assertEquals("conversationAgentB", ConversationFeatureProbe.AGENT_B_KEY.get()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationAgentACmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationAgentACmp.java new file mode 100644 index 000000000..9eb4270c0 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationAgentACmp.java @@ -0,0 +1,19 @@ +package com.yomahub.liteflow.test.agent.features.conversation.cmp; + +import com.yomahub.liteflow.test.agent.features.conversation.ConversationFeatureProbe; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import org.springframework.stereotype.Component; + +/** + * 第一个 conversation Agent,负责触发 conversation 解析并记录安全化后的上下文。 + */ +@Component("conversationAgentA") +public class ConversationAgentACmp extends CompatibleCustomEchoAgentComponent { + @Override + protected String userPrompt() { + ConversationFeatureProbe.AGENT_A_CONVERSATION_ID.set(ctx().getConversationId()); + ConversationFeatureProbe.AGENT_A_KEY.set(ctx().getAgentKey()); + ConversationFeatureProbe.AGENT_A_WORKSPACE.set(ctx().getWorkspaceDir().toString()); + return super.userPrompt(); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationAgentBCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationAgentBCmp.java new file mode 100644 index 000000000..40ff983b0 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationAgentBCmp.java @@ -0,0 +1,19 @@ +package com.yomahub.liteflow.test.agent.features.conversation.cmp; + +import com.yomahub.liteflow.test.agent.features.conversation.ConversationFeatureProbe; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import org.springframework.stereotype.Component; + +/** + * 第二个 conversation Agent,不覆写 resolveConversationId(),用于验证 slot 中的 conversation 复用。 + */ +@Component("conversationAgentB") +public class ConversationAgentBCmp extends CompatibleCustomEchoAgentComponent { + @Override + protected String userPrompt() { + ConversationFeatureProbe.AGENT_B_CONVERSATION_ID.set(ctx().getConversationId()); + ConversationFeatureProbe.AGENT_B_KEY.set(ctx().getAgentKey()); + ConversationFeatureProbe.AGENT_B_WORKSPACE.set(ctx().getWorkspaceDir().toString()); + return super.userPrompt(); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationPrepareCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationPrepareCmp.java new file mode 100644 index 000000000..4b7570e7e --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationPrepareCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.conversation.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * 保留原始请求 Map,使默认 resolveConversationId() 能读取约定的 conversationId 字段。 + */ +@Component("conversationPrepare") +public class ConversationPrepareCmp extends NodeComponent { + @Override + public void process() { + getSlot().setChainReqData(getSlot().getChainId(), getRequestData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationRecordCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationRecordCmp.java new file mode 100644 index 000000000..025effeea --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/conversation/cmp/ConversationRecordCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.conversation.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * conversation 功能包的记录节点。 + */ +@Component("conversationRecord") +public class ConversationRecordCmp extends NodeComponent { + @Override + public void process() { + getSlot().setOutput(getNodeId(), getSlot().getResponseData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/CompatibleCustomPlatformConnectivityTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/CompatibleCustomPlatformConnectivityTest.java new file mode 100644 index 000000000..4270a5101 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/CompatibleCustomPlatformConnectivityTest.java @@ -0,0 +1,78 @@ +package com.yomahub.liteflow.test.agent.features.platform; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.property.LiteflowConfig; +import com.yomahub.liteflow.property.agent.AgentConfig; +import com.yomahub.liteflow.property.agent.PlatformCredential; +import com.yomahub.liteflow.property.agent.ShellMode; +import com.yomahub.liteflow.test.agent.features.support.ReActAgentFeatureTestSupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; + +import javax.annotation.Resource; + +/** + * 覆盖 guide 中“测试者提供 baseUrl 和 key 后访问 OpenAI 兼容平台”的真实冒烟场景。 + * + *

该测试默认跳过。运行者需要提供: + * {@code TEST_LITEFLOW_COMPATIBLE_CUSTOM_API_KEY}、 + * {@code TEST_LITEFLOW_COMPATIBLE_CUSTOM_BASE_URL},以及可选的 + * {@code TEST_LITEFLOW_COMPATIBLE_CUSTOM_MODEL}。 + */ +@TestPropertySource(value = "classpath:/agent/features/platform/application.properties") +@SpringBootTest(classes = CompatibleCustomPlatformConnectivityTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.agent.features.platform.cmp" }) +public class CompatibleCustomPlatformConnectivityTest { + + @Resource + private FlowExecutor flowExecutor; + + @Resource + private LiteflowConfig liteflowConfig; + + @BeforeEach + public void reset() throws Exception { + ensureMinimalAgentConfigWithoutFakeCredential(); + ReActAgentFeatureTestSupport.resetAgentSessionManager(); + } + + @Test + public void testCompatibleCustomPlatformConnectivityWhenCredentialProvided() { + PlatformCredential credential = liteflowConfig.getAgent().getOpenaiCompatible() + .get(ReActAgentFeatureTestSupport.COMPATIBLE_CONFIG_KEY); + Assumptions.assumeTrue(credential != null + && credential.getApiKey() != null + && !credential.getApiKey().isBlank() + && credential.getBaseUrl() != null + && !credential.getBaseUrl().isBlank(), + "compatible-custom api-key/base-url 未配置,跳过真实平台冒烟测试"); + + LiteflowResponse response = flowExecutor.execute2Resp( + "compatibleCustomPlatformChain", + "用一句中文短句回复:LiteFlow ReAct Agent 连通性正常。"); + + Assertions.assertTrue(response.isSuccess(), response.getMessage()); + Object reply = response.getSlot().getOutput("platformRecord"); + Assertions.assertNotNull(reply, "真实平台应返回非空回复"); + Assertions.assertFalse(reply.toString().isBlank(), "真实平台回复不应为空白"); + } + + private void ensureMinimalAgentConfigWithoutFakeCredential() { + if (liteflowConfig.getAgent() == null) { + liteflowConfig.setAgent(new AgentConfig()); + } + AgentConfig agentConfig = liteflowConfig.getAgent(); + agentConfig.getWorkspace().setRoot("target/wk_react_agent_platform"); + agentConfig.getShell().setMode(ShellMode.DISABLED); + agentConfig.getDefaults().setMaxIterations(3); + agentConfig.getLogging().setReactEnabled(false); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/CompatibleCustomLiveAgentCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/CompatibleCustomLiveAgentCmp.java new file mode 100644 index 000000000..b1e454b21 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/CompatibleCustomLiveAgentCmp.java @@ -0,0 +1,53 @@ +package com.yomahub.liteflow.test.agent.features.platform.cmp; + +import com.yomahub.liteflow.agent.component.ReActAgentComponent; +import com.yomahub.liteflow.agent.model.ModelSpec; +import com.yomahub.liteflow.agent.openai.OpenAICompatible; +import com.yomahub.liteflow.test.agent.features.support.ReActAgentFeatureTestSupport; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 真实 compatible-custom Agent。该组件不覆写 buildModel(),会真正使用 OpenAI 兼容端点。 + */ +@Component("compatibleCustomLiveAgent") +public class CompatibleCustomLiveAgentCmp extends ReActAgentComponent { + + @Value("${test.compatible-custom.model:gpt-4o-mini}") + private String modelName; + + @Override + protected ModelSpec model() { + return OpenAICompatible.custom( + ReActAgentFeatureTestSupport.COMPATIBLE_CONFIG_KEY, + modelName) + .temperature(0.1) + .maxTokens(64); + } + + @Override + protected String systemPrompt() { + return "你是 LiteFlow ReAct Agent 的连通性测试助手,只能用一句中文回复。"; + } + + @Override + protected String userPrompt() { + Object reqData = getSlot().getChainReqData(getSlot().getChainId()); + return reqData == null ? "" : reqData.toString(); + } + + @Override + protected int maxIterations() { + return 3; + } + + @Override + protected boolean enableShellTool() { + return false; + } + + @Override + protected boolean enableWorkspaceFileTools() { + return false; + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/PlatformPrepareCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/PlatformPrepareCmp.java new file mode 100644 index 000000000..fbc715ccf --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/PlatformPrepareCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.platform.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * platform 功能包的准备节点。 + */ +@Component("platformPrepare") +public class PlatformPrepareCmp extends NodeComponent { + @Override + public void process() { + getSlot().setChainReqData(getSlot().getChainId(), getRequestData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/PlatformRecordCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/PlatformRecordCmp.java new file mode 100644 index 000000000..546041e50 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/platform/cmp/PlatformRecordCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.platform.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * platform 功能包的记录节点。 + */ +@Component("platformRecord") +public class PlatformRecordCmp extends NodeComponent { + @Override + public void process() { + getSlot().setOutput(getNodeId(), getSlot().getResponseData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/ShellFeatureProbe.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/ShellFeatureProbe.java new file mode 100644 index 000000000..b397c14b3 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/ShellFeatureProbe.java @@ -0,0 +1,21 @@ +package com.yomahub.liteflow.test.agent.features.shell; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * shell 功能包的测试观测点。 + */ +public final class ShellFeatureProbe { + public static final AtomicReference WORKSPACE = new AtomicReference<>(); + public static final AtomicReference PWD_OUTPUT = new AtomicReference<>(); + public static final AtomicReference DENIED_OUTPUT = new AtomicReference<>(); + + private ShellFeatureProbe() { + } + + public static void reset() { + WORKSPACE.set(null); + PWD_OUTPUT.set(null); + DENIED_OUTPUT.set(null); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/ShellFeatureTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/ShellFeatureTest.java new file mode 100644 index 000000000..bee7290a8 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/ShellFeatureTest.java @@ -0,0 +1,62 @@ +package com.yomahub.liteflow.test.agent.features.shell; + +import com.yomahub.liteflow.agent.tool.ManagedShellCommandTool; +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.property.LiteflowConfig; +import com.yomahub.liteflow.property.agent.ShellMode; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import com.yomahub.liteflow.test.agent.features.support.ReActAgentFeatureTestSupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 覆盖 guide 中受管 Shell 工具的安全配置。 + * + *

配置为 WHITELIST,仅允许 {@code pwd}。测试验证命令在当前 conversation workspace + * 中执行,并确认未列入白名单的命令会被拒绝。 + */ +@TestPropertySource(value = "classpath:/agent/features/shell/application.properties") +@SpringBootTest(classes = ShellFeatureTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.agent.features.shell.cmp" }) +public class ShellFeatureTest { + + @Resource + private FlowExecutor flowExecutor; + + @Resource + private LiteflowConfig liteflowConfig; + + @BeforeEach + public void reset() throws Exception { + ReActAgentFeatureTestSupport.ensureAgentConfig( + liteflowConfig, + "target/wk_react_agent_shell", + false, + null, + ShellMode.WHITELIST); + liteflowConfig.getAgent().getShell().setWhitelist(List.of("pwd")); + ReActAgentFeatureTestSupport.resetAgentSessionManager(); + CompatibleCustomEchoAgentComponent.resetCompatibleProbe(); + ShellFeatureProbe.reset(); + } + + @Test + public void testManagedShellRunsInWorkspaceAndRejectsNonWhitelistedCommand() { + LiteflowResponse response = flowExecutor.execute2Resp("shellFeatureChain", "shell-tools"); + + Assertions.assertTrue(response.isSuccess(), response.getMessage()); + Assertions.assertEquals(ShellFeatureProbe.WORKSPACE.get(), ShellFeatureProbe.PWD_OUTPUT.get().trim(), + "pwd 应在当前 conversation workspace 目录下执行"); + Assertions.assertTrue(ShellFeatureProbe.DENIED_OUTPUT.get().contains("not allowed by whitelist")); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellAgentCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellAgentCmp.java new file mode 100644 index 000000000..6a53fc265 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellAgentCmp.java @@ -0,0 +1,21 @@ +package com.yomahub.liteflow.test.agent.features.shell.cmp; + +import com.yomahub.liteflow.agent.tool.ManagedShellCommandTool; +import com.yomahub.liteflow.test.agent.features.shell.ShellFeatureProbe; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import org.springframework.stereotype.Component; + +/** + * 直接调用受管 Shell 工具,避免依赖模型是否主动选择工具。 + */ +@Component("shellAgent") +public class ShellAgentCmp extends CompatibleCustomEchoAgentComponent { + @Override + protected String userPrompt() { + ManagedShellCommandTool tool = new ManagedShellCommandTool(ctx().getWorkspaceDir(), agentConfig()); + ShellFeatureProbe.WORKSPACE.set(ctx().getWorkspaceDir().toAbsolutePath().normalize().toString()); + ShellFeatureProbe.PWD_OUTPUT.set(tool.executeCommand("pwd")); + ShellFeatureProbe.DENIED_OUTPUT.set(tool.executeCommand("echo denied")); + return super.userPrompt(); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellPrepareCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellPrepareCmp.java new file mode 100644 index 000000000..da1ac1d51 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellPrepareCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.shell.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * shell 功能包的准备节点。 + */ +@Component("shellPrepare") +public class ShellPrepareCmp extends NodeComponent { + @Override + public void process() { + getSlot().setChainReqData(getSlot().getChainId(), getRequestData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellRecordCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellRecordCmp.java new file mode 100644 index 000000000..3bfa6b003 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/shell/cmp/ShellRecordCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.shell.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * shell 功能包的记录节点。 + */ +@Component("shellRecord") +public class ShellRecordCmp extends NodeComponent { + @Override + public void process() { + getSlot().setOutput(getNodeId(), getSlot().getResponseData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/SkillsFeatureProbe.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/SkillsFeatureProbe.java new file mode 100644 index 000000000..d7ff9e361 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/SkillsFeatureProbe.java @@ -0,0 +1,21 @@ +package com.yomahub.liteflow.test.agent.features.skills; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; + +/** + * skills 功能包的测试观测点。 + */ +public final class SkillsFeatureProbe { + public static final AtomicReference> USED_SKILLS = new AtomicReference<>(List.of()); + public static final AtomicReference> TOOL_NAMES = new AtomicReference<>(List.of()); + + private SkillsFeatureProbe() { + } + + public static void reset() { + USED_SKILLS.set(List.of()); + TOOL_NAMES.set(new CopyOnWriteArrayList<>()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/SkillsFeatureTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/SkillsFeatureTest.java new file mode 100644 index 000000000..f2918e982 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/SkillsFeatureTest.java @@ -0,0 +1,75 @@ +package com.yomahub.liteflow.test.agent.features.skills; + +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.property.LiteflowConfig; +import com.yomahub.liteflow.property.agent.ShellMode; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import com.yomahub.liteflow.test.agent.features.support.ReActAgentFeatureTestSupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; + +import javax.annotation.Resource; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * 覆盖 guide 中 Skills 开启、组件级技能过滤,以及 usedSkills() 的记录语义。 + * + *

本测试通过本地模型桩返回一次 {@code load_skill_through_path} 工具调用, + * 让 AgentScope 真实加载 filesystem skill,再由 LiteFlow 的 {@code usedSkills()} 读取结果。 + */ +@TestPropertySource(value = "classpath:/agent/features/skills/application.properties") +@SpringBootTest(classes = SkillsFeatureTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.agent.features.skills.cmp" }) +public class SkillsFeatureTest { + + @Resource + private FlowExecutor flowExecutor; + + @Resource + private LiteflowConfig liteflowConfig; + + @BeforeEach + public void reset() throws Exception { + ReActAgentFeatureTestSupport.ensureAgentConfig( + liteflowConfig, + "target/wk_react_agent_skills", + true, + resolveSkillsPath(), + ShellMode.DISABLED); + ReActAgentFeatureTestSupport.resetAgentSessionManager(); + CompatibleCustomEchoAgentComponent.resetCompatibleProbe(); + SkillsFeatureProbe.reset(); + } + + private static String resolveSkillsPath() { + Path moduleRelative = Path.of("src/test/resources/agent/features/skills"); + if (Files.isDirectory(moduleRelative)) { + return moduleRelative.toAbsolutePath().normalize().toString(); + } + return Path.of("liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills") + .toAbsolutePath() + .normalize() + .toString(); + } + + @Test + public void testUsedSkillsTracksFilesystemSkillLoadedInThenChain() { + LiteflowResponse response = flowExecutor.execute2Resp("skillsFeatureChain", "load-feature-skill"); + + Assertions.assertTrue(response.isSuccess(), response.getMessage()); + Assertions.assertEquals(List.of("feature-demo"), SkillsFeatureProbe.USED_SKILLS.get(), + "load_skill_through_path 成功后 usedSkills() 应返回技能名"); + Assertions.assertTrue(SkillsFeatureProbe.TOOL_NAMES.get().contains("load_skill_through_path"), + "开启 skills 后 Toolkit 应包含 AgentScope 的技能加载工具"); + Assertions.assertEquals(1, CompatibleCustomEchoAgentComponent.COMPATIBLE_SPEC_RESOLVE_COUNT.get()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsAgentCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsAgentCmp.java new file mode 100644 index 000000000..aa7477ddf --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsAgentCmp.java @@ -0,0 +1,43 @@ +package com.yomahub.liteflow.test.agent.features.skills.cmp; + +import com.yomahub.liteflow.agent.model.ModelSpec; +import com.yomahub.liteflow.agent.openai.OpenAICompatible; +import com.yomahub.liteflow.test.agent.features.skills.SkillsFeatureProbe; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import com.yomahub.liteflow.test.agent.features.support.ReActAgentFeatureTestSupport; +import io.agentscope.core.message.Msg; +import io.agentscope.core.model.Model; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 使用 compatible-custom 配置解析,同时只允许加载 feature-demo 一个技能。 + */ +@Component("skillsAgent") +public class SkillsAgentCmp extends CompatibleCustomEchoAgentComponent { + @Override + protected List skills() { + return List.of("feature-demo"); + } + + @Override + protected ModelSpec model() { + return OpenAICompatible.custom( + ReActAgentFeatureTestSupport.COMPATIBLE_CONFIG_KEY, + "compatible-custom-skills-test-model"); + } + + @Override + protected Model buildModel() { + model().resolve(agentConfig()); + COMPATIBLE_SPEC_RESOLVE_COUNT.incrementAndGet(); + return new SkillsLoadingModel(); + } + + @Override + protected void handleReply(Msg reply) { + SkillsFeatureProbe.USED_SKILLS.set(usedSkills()); + super.handleReply(reply); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsLoadingModel.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsLoadingModel.java new file mode 100644 index 000000000..ef45ae287 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsLoadingModel.java @@ -0,0 +1,53 @@ +package com.yomahub.liteflow.test.agent.features.skills.cmp; + +import com.yomahub.liteflow.test.agent.features.skills.SkillsFeatureProbe; +import io.agentscope.core.message.Msg; +import io.agentscope.core.message.TextBlock; +import io.agentscope.core.message.ToolUseBlock; +import io.agentscope.core.model.ChatResponse; +import io.agentscope.core.model.GenerateOptions; +import io.agentscope.core.model.Model; +import io.agentscope.core.model.ToolSchema; +import reactor.core.publisher.Flux; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * skills 功能包的本地模型桩:首轮请求触发 load_skill_through_path,第二轮返回最终文本。 + */ +public class SkillsLoadingModel implements Model { + private final AtomicInteger callCount = new AtomicInteger(); + + @Override + public Flux stream(List messages, List toolSchemas, GenerateOptions options) { + List inputTexts = messages == null ? List.of() : messages.stream() + .map(Msg::getTextContent) + .toList(); + List toolNames = toolSchemas == null ? List.of() : toolSchemas.stream() + .map(ToolSchema::getName) + .toList(); + SkillsFeatureProbe.TOOL_NAMES.set(toolNames); + if (callCount.incrementAndGet() == 1 && inputTexts.contains("load-feature-skill")) { + return Flux.just(ChatResponse.builder() + .content(List.of(new ToolUseBlock( + "load-feature-demo-call", + "load_skill_through_path", + Map.of("skillId", "feature-demo_filesystem-features_skills", "path", "SKILL.md"), + "{\"skillId\":\"feature-demo_filesystem-features_skills\",\"path\":\"SKILL.md\"}", + null))) + .finishReason("tool_calls") + .build()); + } + return Flux.just(ChatResponse.builder() + .content(List.of(TextBlock.builder().text("skill-loaded").build())) + .finishReason("stop") + .build()); + } + + @Override + public String getModelName() { + return "skill-loading-model"; + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsPrepareCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsPrepareCmp.java new file mode 100644 index 000000000..b0b30bda9 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsPrepareCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.skills.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * skills 功能包的准备节点。 + */ +@Component("skillsPrepare") +public class SkillsPrepareCmp extends NodeComponent { + @Override + public void process() { + getSlot().setChainReqData(getSlot().getChainId(), getRequestData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsRecordCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsRecordCmp.java new file mode 100644 index 000000000..e2c6150cf --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/skills/cmp/SkillsRecordCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.skills.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * skills 功能包的记录节点。 + */ +@Component("skillsRecord") +public class SkillsRecordCmp extends NodeComponent { + @Override + public void process() { + getSlot().setOutput(getNodeId(), getSlot().getResponseData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/CompatibleCustomEchoAgentComponent.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/CompatibleCustomEchoAgentComponent.java new file mode 100644 index 000000000..292fd2b21 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/CompatibleCustomEchoAgentComponent.java @@ -0,0 +1,67 @@ +package com.yomahub.liteflow.test.agent.features.support; + +import com.yomahub.liteflow.agent.component.ReActAgentComponent; +import com.yomahub.liteflow.agent.model.ModelSpec; +import com.yomahub.liteflow.agent.openai.OpenAICompatible; +import io.agentscope.core.model.Model; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 功能测试使用的 OpenAI 兼容模型测试桩。 + * + *

组件仍然通过 {@link OpenAICompatible#custom(String, String)} 解析 + * {@code liteflow.agent.openai-compatible.compatible-custom.*} 配置,覆盖真实入口的 + * credential 读取路径;真正执行时返回本地 Echo 模型,避免普通功能测试依赖外网。 + */ +public abstract class CompatibleCustomEchoAgentComponent extends ReActAgentComponent { + + public static final AtomicInteger COMPATIBLE_SPEC_RESOLVE_COUNT = new AtomicInteger(); + + public static void resetCompatibleProbe() { + COMPATIBLE_SPEC_RESOLVE_COUNT.set(0); + } + + @Override + protected ModelSpec model() { + return OpenAICompatible.custom( + ReActAgentFeatureTestSupport.COMPATIBLE_CONFIG_KEY, + "compatible-custom-test-model") + .temperature(0.1) + .maxTokens(64) + .stream(false); + } + + @Override + protected Model buildModel() { + model().resolve(agentConfig()); + COMPATIBLE_SPEC_RESOLVE_COUNT.incrementAndGet(); + return new CompatibleCustomEchoModel(getNodeId()); + } + + @Override + protected String systemPrompt() { + return "compatible custom test agent: " + getNodeId(); + } + + @Override + protected String userPrompt() { + Object reqData = getSlot().getChainReqData(getSlot().getChainId()); + return reqData == null ? "" : reqData.toString(); + } + + @Override + protected boolean enableShellTool() { + return false; + } + + @Override + protected boolean enableWorkspaceFileTools() { + return false; + } + + @Override + protected boolean enableReActLogging() { + return false; + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/CompatibleCustomEchoModel.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/CompatibleCustomEchoModel.java new file mode 100644 index 000000000..e7d53be81 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/CompatibleCustomEchoModel.java @@ -0,0 +1,41 @@ +package com.yomahub.liteflow.test.agent.features.support; + +import io.agentscope.core.message.Msg; +import io.agentscope.core.message.TextBlock; +import io.agentscope.core.model.ChatResponse; +import io.agentscope.core.model.GenerateOptions; +import io.agentscope.core.model.Model; +import io.agentscope.core.model.ToolSchema; +import reactor.core.publisher.Flux; + +import java.util.List; + +/** + * 功能测试使用的本地 Echo 模型,避免普通用例依赖真实平台网络。 + */ +public class CompatibleCustomEchoModel implements Model { + + private final String nodeId; + + public CompatibleCustomEchoModel(String nodeId) { + this.nodeId = nodeId; + } + + @Override + public Flux stream(List messages, List toolSchemas, GenerateOptions options) { + List inputTexts = messages == null ? List.of() : messages.stream() + .map(Msg::getTextContent) + .toList(); + return Flux.just(ChatResponse.builder() + .content(List.of(TextBlock.builder() + .text("reply:" + nodeId + ":" + inputTexts) + .build())) + .finishReason("stop") + .build()); + } + + @Override + public String getModelName() { + return "compatible-custom-echo"; + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/ReActAgentFeatureTestSupport.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/ReActAgentFeatureTestSupport.java new file mode 100644 index 000000000..824b1bf32 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/support/ReActAgentFeatureTestSupport.java @@ -0,0 +1,61 @@ +package com.yomahub.liteflow.test.agent.features.support; + +import com.yomahub.liteflow.property.LiteflowConfig; +import com.yomahub.liteflow.property.agent.AgentConfig; +import com.yomahub.liteflow.property.agent.PlatformCredential; +import com.yomahub.liteflow.property.agent.ShellMode; + +/** + * 新增 ReAct Agent 功能测试的共享辅助方法。 + * + *

这些测试每个功能包都使用独立的 Spring 配置文件,但有两件事需要保持一致: + * 1)补齐 SpringBoot 属性绑定在单模块测试中偶发缺失的 agent 段; + * 2)每个测试方法开始前清掉 ReActAgentComponent 内部缓存的 SessionManager。 + */ +public final class ReActAgentFeatureTestSupport { + + public static final String COMPATIBLE_CONFIG_KEY = "compatible-custom"; + public static final String DEFAULT_COMPATIBLE_API_KEY = "test-compatible-key"; + public static final String DEFAULT_COMPATIBLE_BASE_URL = "http://127.0.0.1:65535/v1"; + + private ReActAgentFeatureTestSupport() { + } + + public static void ensureAgentConfig( + LiteflowConfig liteflowConfig, + String workspaceRoot, + boolean skillsEnabled, + String skillsPath, + ShellMode shellMode) { + if (liteflowConfig.getAgent() == null) { + liteflowConfig.setAgent(new AgentConfig()); + } + AgentConfig agentConfig = liteflowConfig.getAgent(); + agentConfig.getWorkspace().setRoot(workspaceRoot); + agentConfig.getShell().setMode(shellMode); + agentConfig.getDefaults().setMaxIterations(6); + agentConfig.getLogging().setReactEnabled(false); + agentConfig.getSkills().setEnabled(skillsEnabled); + if (skillsPath != null) { + agentConfig.getSkills().setPath(skillsPath); + } + agentConfig.getSkills().setStrict(true); + + PlatformCredential credential = agentConfig.getOpenaiCompatible() + .computeIfAbsent(COMPATIBLE_CONFIG_KEY, key -> new PlatformCredential()); + if (credential.getApiKey() == null || credential.getApiKey().isBlank()) { + credential.setApiKey(DEFAULT_COMPATIBLE_API_KEY); + } + if (credential.getBaseUrl() == null || credential.getBaseUrl().isBlank()) { + credential.setBaseUrl(DEFAULT_COMPATIBLE_BASE_URL); + } + } + + public static void resetAgentSessionManager() throws Exception { + Class holder = Class.forName( + "com.yomahub.liteflow.agent.component.ReActAgentComponent$AgentSessionManagerHolder"); + var reset = holder.getDeclaredMethod("resetForTesting"); + reset.setAccessible(true); + reset.invoke(null); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/WorkspaceFeatureProbe.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/WorkspaceFeatureProbe.java new file mode 100644 index 000000000..872fb9834 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/WorkspaceFeatureProbe.java @@ -0,0 +1,24 @@ +package com.yomahub.liteflow.test.agent.features.workspace; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * workspace 功能包的测试观测点。 + */ +public final class WorkspaceFeatureProbe { + public static final AtomicReference TRUNCATED_READ = new AtomicReference<>(); + public static final AtomicReference> LIST_RESULT = new AtomicReference<>(List.of()); + public static final AtomicReference RELATIVE_ESCAPE_DENIED = new AtomicReference<>(); + public static final AtomicReference ABSOLUTE_ESCAPE_DENIED = new AtomicReference<>(); + + private WorkspaceFeatureProbe() { + } + + public static void reset() { + TRUNCATED_READ.set(null); + LIST_RESULT.set(List.of()); + RELATIVE_ESCAPE_DENIED.set(null); + ABSOLUTE_ESCAPE_DENIED.set(null); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/WorkspaceFeatureTest.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/WorkspaceFeatureTest.java new file mode 100644 index 000000000..18c313e02 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/WorkspaceFeatureTest.java @@ -0,0 +1,65 @@ +package com.yomahub.liteflow.test.agent.features.workspace; + +import com.yomahub.liteflow.agent.tool.WorkspaceFileTools; +import com.yomahub.liteflow.core.FlowExecutor; +import com.yomahub.liteflow.flow.LiteflowResponse; +import com.yomahub.liteflow.property.LiteflowConfig; +import com.yomahub.liteflow.property.agent.ShellMode; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import com.yomahub.liteflow.test.agent.features.support.ReActAgentFeatureTestSupport; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.TestPropertySource; + +import javax.annotation.Resource; + +/** + * 覆盖 guide 中 workspace 文件工具的关键安全边界。 + * + *

测试不让模型自行决定是否调用工具,而是在 Agent 生命周期内直接构造 + * {@link WorkspaceFileTools},这样可以稳定验证工具行为,同时仍通过 THEN 链路触发。 + */ +@TestPropertySource(value = "classpath:/agent/features/workspace/application.properties") +@SpringBootTest(classes = WorkspaceFeatureTest.class) +@EnableAutoConfiguration +@ComponentScan({ "com.yomahub.liteflow.test.agent.features.workspace.cmp" }) +public class WorkspaceFeatureTest { + + @Resource + private FlowExecutor flowExecutor; + + @Resource + private LiteflowConfig liteflowConfig; + + @BeforeEach + public void reset() throws Exception { + ReActAgentFeatureTestSupport.ensureAgentConfig( + liteflowConfig, + "target/wk_react_agent_workspace", + false, + null, + ShellMode.DISABLED); + liteflowConfig.getAgent().getWorkspace().setMaxFileBytes(4); + liteflowConfig.getAgent().getWorkspace().setMaxListSize(1); + ReActAgentFeatureTestSupport.resetAgentSessionManager(); + CompatibleCustomEchoAgentComponent.resetCompatibleProbe(); + WorkspaceFeatureProbe.reset(); + } + + @Test + public void testWorkspaceToolsReadWriteListDeleteAndDenyEscapes() { + LiteflowResponse response = flowExecutor.execute2Resp("workspaceFeatureChain", "workspace-tools"); + + Assertions.assertTrue(response.isSuccess(), response.getMessage()); + Assertions.assertEquals("abcd", WorkspaceFeatureProbe.TRUNCATED_READ.get(), + "read_file 应按 max-file-bytes 截断读取"); + Assertions.assertEquals(1, WorkspaceFeatureProbe.LIST_RESULT.get().size(), + "list_files 应按 max-list-size 限制返回数量"); + Assertions.assertTrue(WorkspaceFeatureProbe.RELATIVE_ESCAPE_DENIED.get().contains("path escapes workspace")); + Assertions.assertTrue(WorkspaceFeatureProbe.ABSOLUTE_ESCAPE_DENIED.get().contains("absolute path denied")); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspaceAgentCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspaceAgentCmp.java new file mode 100644 index 000000000..f94574afb --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspaceAgentCmp.java @@ -0,0 +1,33 @@ +package com.yomahub.liteflow.test.agent.features.workspace.cmp; + +import com.yomahub.liteflow.agent.tool.WorkspaceFileTools; +import com.yomahub.liteflow.test.agent.features.support.CompatibleCustomEchoAgentComponent; +import com.yomahub.liteflow.test.agent.features.workspace.WorkspaceFeatureProbe; +import org.springframework.stereotype.Component; + +/** + * 验证 WorkspaceFileTools 的具体行为,并将检查结果暴露给测试断言。 + */ +@Component("workspaceAgent") +public class WorkspaceAgentCmp extends CompatibleCustomEchoAgentComponent { + @Override + protected String userPrompt() { + WorkspaceFileTools tools = new WorkspaceFileTools(ctx().getWorkspaceDir(), agentConfig()); + tools.writeFile("notes/a.txt", "abcdef"); + tools.writeFile("notes/b.txt", "ghijkl"); + WorkspaceFeatureProbe.TRUNCATED_READ.set(tools.readFile("notes/a.txt")); + WorkspaceFeatureProbe.LIST_RESULT.set(tools.listFiles("notes")); + tools.deleteFile("notes/b.txt"); + try { + tools.readFile("../escape.txt"); + } catch (SecurityException e) { + WorkspaceFeatureProbe.RELATIVE_ESCAPE_DENIED.set(e.getMessage()); + } + try { + tools.readFile("/tmp/escape.txt"); + } catch (SecurityException e) { + WorkspaceFeatureProbe.ABSOLUTE_ESCAPE_DENIED.set(e.getMessage()); + } + return super.userPrompt(); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspacePrepareCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspacePrepareCmp.java new file mode 100644 index 000000000..bffbc3631 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspacePrepareCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.workspace.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * workspace 功能包的准备节点。 + */ +@Component("workspacePrepare") +public class WorkspacePrepareCmp extends NodeComponent { + @Override + public void process() { + getSlot().setChainReqData(getSlot().getChainId(), getRequestData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspaceRecordCmp.java b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspaceRecordCmp.java new file mode 100644 index 000000000..8797094ba --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/java/com/yomahub/liteflow/test/agent/features/workspace/cmp/WorkspaceRecordCmp.java @@ -0,0 +1,15 @@ +package com.yomahub.liteflow.test.agent.features.workspace.cmp; + +import com.yomahub.liteflow.core.NodeComponent; +import org.springframework.stereotype.Component; + +/** + * workspace 功能包的记录节点。 + */ +@Component("workspaceRecord") +public class WorkspaceRecordCmp extends NodeComponent { + @Override + public void process() { + getSlot().setOutput(getNodeId(), getSlot().getResponseData()); + } +} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/compatiblecustom/application.properties b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/compatiblecustom/application.properties new file mode 100644 index 000000000..8eb51e62f --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/compatiblecustom/application.properties @@ -0,0 +1,11 @@ +liteflow.rule-source=agent/features/compatiblecustom/flow.el.xml +liteflow.print-banner=false + +# 本功能包验证 OpenAI compatible-custom 配置路径。这里使用假 key 和本地 baseUrl, +# 组件会解析 ModelSpec 但不会发起真实网络请求。 +liteflow.agent.workspace.root=target/wk_react_agent_compatiblecustom +liteflow.agent.shell.mode=disabled +liteflow.agent.defaults.max-iterations=6 +liteflow.agent.logging.react-enabled=false +liteflow.agent.openai-compatible.compatible-custom.api-key=test-compatible-key +liteflow.agent.openai-compatible.compatible-custom.base-url=http://127.0.0.1:65535/v1 diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/compatiblecustom/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/compatiblecustom/flow.el.xml new file mode 100644 index 000000000..92d51dfce --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/compatiblecustom/flow.el.xml @@ -0,0 +1,10 @@ + + + + + + + THEN(compatibleCustomPrepare, compatibleCustomAgent, compatibleCustomRecord); + + + diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/conversation/application.properties b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/conversation/application.properties new file mode 100644 index 000000000..0fa2e2684 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/conversation/application.properties @@ -0,0 +1,11 @@ +liteflow.rule-source=agent/features/conversation/flow.el.xml +liteflow.print-banner=false + +# conversation 功能包验证 conversationId 与 agentKey 的拆分语义。 +# 仍使用 compatible-custom 的假凭据,仅覆盖配置解析路径,不进行真实模型请求。 +liteflow.agent.workspace.root=target/wk_react_agent_conversation +liteflow.agent.shell.mode=disabled +liteflow.agent.defaults.max-iterations=6 +liteflow.agent.logging.react-enabled=false +liteflow.agent.openai-compatible.compatible-custom.api-key=test-compatible-key +liteflow.agent.openai-compatible.compatible-custom.base-url=http://127.0.0.1:65535/v1 diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/conversation/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/conversation/flow.el.xml new file mode 100644 index 000000000..03650712a --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/conversation/flow.el.xml @@ -0,0 +1,10 @@ + + + + + + + THEN(conversationPrepare, conversationAgentA, conversationAgentB, conversationRecord); + + + diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/platform/application.properties b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/platform/application.properties new file mode 100644 index 000000000..b9c752424 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/platform/application.properties @@ -0,0 +1,12 @@ +liteflow.rule-source=agent/features/platform/flow.el.xml +liteflow.print-banner=false + +# platform 功能包是真实 OpenAI compatible-custom 端点的冒烟测试。 +# 默认不配置 key/baseUrl,测试会跳过;测试者需要显式提供下面两个环境变量。 +liteflow.agent.workspace.root=target/wk_react_agent_platform +liteflow.agent.shell.mode=disabled +liteflow.agent.defaults.max-iterations=3 +liteflow.agent.logging.react-enabled=false +liteflow.agent.openai-compatible.compatible-custom.api-key=${TEST_LITEFLOW_COMPATIBLE_CUSTOM_API_KEY:} +liteflow.agent.openai-compatible.compatible-custom.base-url=${TEST_LITEFLOW_COMPATIBLE_CUSTOM_BASE_URL:} +test.compatible-custom.model=${TEST_LITEFLOW_COMPATIBLE_CUSTOM_MODEL:gpt-4o-mini} diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/platform/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/platform/flow.el.xml new file mode 100644 index 000000000..5c31021f0 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/platform/flow.el.xml @@ -0,0 +1,10 @@ + + + + + + + THEN(platformPrepare, compatibleCustomLiveAgent, platformRecord); + + + diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/shell/application.properties b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/shell/application.properties new file mode 100644 index 000000000..8f9564691 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/shell/application.properties @@ -0,0 +1,12 @@ +liteflow.rule-source=agent/features/shell/flow.el.xml +liteflow.print-banner=false + +# shell 功能包验证受管 Shell 工具的工作目录和白名单策略。 +liteflow.agent.workspace.root=target/wk_react_agent_shell +liteflow.agent.shell.mode=whitelist +liteflow.agent.shell.whitelist=pwd +liteflow.agent.shell.max-output-bytes=2048 +liteflow.agent.defaults.max-iterations=6 +liteflow.agent.logging.react-enabled=false +liteflow.agent.openai-compatible.compatible-custom.api-key=test-compatible-key +liteflow.agent.openai-compatible.compatible-custom.base-url=http://127.0.0.1:65535/v1 diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/shell/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/shell/flow.el.xml new file mode 100644 index 000000000..505d68b0b --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/shell/flow.el.xml @@ -0,0 +1,10 @@ + + + + + + + THEN(shellPrepare, shellAgent, shellRecord); + + + diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/application.properties b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/application.properties new file mode 100644 index 000000000..851f2c900 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/application.properties @@ -0,0 +1,14 @@ +liteflow.rule-source=agent/features/skills/flow.el.xml +liteflow.print-banner=false + +# skills 功能包验证 SkillBox 加载和 usedSkills() 记录。compatible-custom 使用假凭据, +# 模型调用由本地测试桩完成。 +liteflow.agent.workspace.root=target/wk_react_agent_skills +liteflow.agent.shell.mode=disabled +liteflow.agent.defaults.max-iterations=6 +liteflow.agent.logging.react-enabled=false +liteflow.agent.skills.enabled=true +liteflow.agent.skills.path=src/test/resources/agent/features/skills +liteflow.agent.skills.strict=true +liteflow.agent.openai-compatible.compatible-custom.api-key=test-compatible-key +liteflow.agent.openai-compatible.compatible-custom.base-url=http://127.0.0.1:65535/v1 diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/feature-demo/SKILL.md b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/feature-demo/SKILL.md new file mode 100644 index 000000000..3b823e926 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/feature-demo/SKILL.md @@ -0,0 +1,8 @@ +--- +name: feature-demo +description: Feature demo skill for LiteFlow ReAct Agent tests +--- + +# Feature Demo Skill + +Use this skill when a test request asks the agent to load the feature demo skill. diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/flow.el.xml new file mode 100644 index 000000000..3bba50e76 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/skills/flow.el.xml @@ -0,0 +1,10 @@ + + + + + + + THEN(skillsPrepare, skillsAgent, skillsRecord); + + + diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/workspace/application.properties b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/workspace/application.properties new file mode 100644 index 000000000..53b2f744d --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/workspace/application.properties @@ -0,0 +1,12 @@ +liteflow.rule-source=agent/features/workspace/flow.el.xml +liteflow.print-banner=false + +# workspace 功能包验证内置文件工具的真实读写、截断、列表上限和路径越界保护。 +liteflow.agent.workspace.root=target/wk_react_agent_workspace +liteflow.agent.workspace.max-file-bytes=4 +liteflow.agent.workspace.max-list-size=1 +liteflow.agent.shell.mode=disabled +liteflow.agent.defaults.max-iterations=6 +liteflow.agent.logging.react-enabled=false +liteflow.agent.openai-compatible.compatible-custom.api-key=test-compatible-key +liteflow.agent.openai-compatible.compatible-custom.base-url=http://127.0.0.1:65535/v1 diff --git a/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/workspace/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/workspace/flow.el.xml new file mode 100644 index 000000000..b60c690e5 --- /dev/null +++ b/liteflow-testcase-el/liteflow-testcase-el-react-agent/src/test/resources/agent/features/workspace/flow.el.xml @@ -0,0 +1,10 @@ + + + + + + + THEN(workspacePrepare, workspaceAgent, workspaceRecord); + + +