diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/annotation/AIClassify.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/annotation/AIClassify.java
index afd07627c..84bf83d19 100644
--- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/annotation/AIClassify.java
+++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/annotation/AIClassify.java
@@ -1,7 +1,11 @@
package com.yomahub.liteflow.ai.annotation;
-import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatOptions;
-import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
+import com.yomahub.liteflow.ai.context.ChatContext;
+import com.yomahub.liteflow.ai.engine.tool.registry.DelegatingToolRegistry;
+import com.yomahub.liteflow.ai.engine.tool.registry.ScanningToolRegistry;
+import com.yomahub.liteflow.ai.engine.tool.registry.StaticToolRegistry;
+import com.yomahub.liteflow.ai.engine.tool.registry.ToolRegistry;
+import com.yomahub.liteflow.ai.tool.SpringBeanToolRegistry;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -19,41 +23,6 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE)
public @interface AIClassify {
- /**
- * 从上下文中获取的请求参数的上下文路径表达式。
- *
- * 如果你不希望在注解中进行静态的模型配置,或者你希望使用特定厂商实现的 ChatRequest(其中可能存在一些独有的参数),
- * 请使用这个属性,并在上下文中提供对应的 {@link ChatRequest} 值。
- * 如果不提供,框架将统一使用 {@link ChatRequest} 发起请求。
- *
- * {@link ChatRequest} 可以是对应提供商的具体实现类。
- *
- * 如果你使用了这个属性可以不进行 {@link AIChat#systemPrompt()} 和 {@link AIChat#userPrompt()} 和 {@link AIChat#streaming()} 的配置,
- * 因为这些配置会从 {@link ChatRequest} 中获取。如果进行了配置,优先使用 {@link ChatRequest} 中的配置。
- *
- * 以及,你会发现 {@link ChatRequest#getOptions()} 的 {@link ChatOptions} 和 {@link AIComponent} 中的配置有重合。
- * 同样的,即使你进行了 {@link AIComponent} 的配置,会优先使用 {@link ChatRequest} 的配置。
- *
- * 请注意:如果相关的配置为空或默认值,则会使用 {@link AIComponent} 和 {@link AIChat} 中的配置!!!
- *
- * 该方法用于从一个必须提供 {@code get} 方法的上下文中检索数据。
- *
- * 表达式支持以下两种形式:
- *
- * -
- * 直接属性检索:
- * 例如,从上下文中获取 OpenAI 的 ChatRequest,他的名字为 {@code openAIChatRequest},
- * 表达式应为:{@code "openAIChatRequest"}。
- *
- * -
- * 嵌套属性检索 (例如 Map):
- * 例如,从上下文的一个名为 {@code requestMap} 的 Map 对象中,获取键为 {@code openAI} 的 ChatRequest 对象,
- * 表达式应为:{@code "requestMap.openAI"}。
- *
- *
- */
- String getChatRequest() default "";
-
/**
* 系统提示词
*/
@@ -73,4 +42,22 @@ public @interface AIClassify {
* 是否多标签分类,默认 false
*/
boolean multiLabel() default false;
+
+ /**
+ * 需要启用的工具名列表(默认全部启用)
+ *
+ * 工具注册于 {@link ChatContext#getToolRegistry()},请于上下文中传入
+ *
+ * 如果上下文中没有传入工具注册器,则会使用注册为 Spring Bean 的 {@link ToolRegistry}
+ * ,框架默认实现了 {@link SpringBeanToolRegistry}。
+ * 可以将 Tool 注册为 Bean 实现自动发现与注册。
+ *
+ *
+ * @see ToolRegistry
+ * @see StaticToolRegistry
+ * @see ScanningToolRegistry
+ * @see DelegatingToolRegistry
+ * @see SpringBeanToolRegistry
+ */
+ String[] toolNames() default {};
}
diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/domain/dto/ParsedClassifyAnnotationConfig.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/domain/dto/ParsedClassifyAnnotationConfig.java
index 98494ad23..44f806369 100644
--- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/domain/dto/ParsedClassifyAnnotationConfig.java
+++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/domain/dto/ParsedClassifyAnnotationConfig.java
@@ -1,9 +1,10 @@
package com.yomahub.liteflow.ai.domain.dto;
+import java.util.ArrayList;
import java.util.List;
/**
- * TODO
+ * AIClassify注解解析后配置
*
* @author 苍镜月
* @since TODO
@@ -15,6 +16,8 @@ public class ParsedClassifyAnnotationConfig extends ParsedAnnotationConfig {
private boolean multiLabel;
+ private List toolNames = new ArrayList<>();
+
public List getCategories() {
return categories;
}
@@ -23,6 +26,10 @@ public class ParsedClassifyAnnotationConfig extends ParsedAnnotationConfig {
return multiLabel;
}
+ public List getToolNames() {
+ return toolNames;
+ }
+
public void setCategories(List categories) {
this.categories = categories;
}
@@ -30,4 +37,8 @@ public class ParsedClassifyAnnotationConfig extends ParsedAnnotationConfig {
public void setMultiLabel(boolean multiLabel) {
this.multiLabel = multiLabel;
}
+
+ public void setToolNames(List toolNames) {
+ this.toolNames = toolNames;
+ }
}
diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/assemble/ClassifyRequestAssembler.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/assemble/ClassifyRequestAssembler.java
index 0a11fd4bd..01e0d2a27 100644
--- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/assemble/ClassifyRequestAssembler.java
+++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/assemble/ClassifyRequestAssembler.java
@@ -1,12 +1,31 @@
package com.yomahub.liteflow.ai.parse.assemble;
import com.yomahub.liteflow.ai.context.ChatContext;
+import com.yomahub.liteflow.ai.context.StreamHandler;
import com.yomahub.liteflow.ai.domain.dto.ModelConfigAggregator;
import com.yomahub.liteflow.ai.domain.dto.ParsedClassifyAnnotationConfig;
+import com.yomahub.liteflow.ai.engine.interact.transport.TransportType;
+import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatOptions;
import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
+import com.yomahub.liteflow.ai.engine.model.chat.message.Message;
+import com.yomahub.liteflow.ai.engine.model.chat.message.SystemMessage;
+import com.yomahub.liteflow.ai.engine.model.chat.message.UserMessage;
+import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
+import com.yomahub.liteflow.ai.engine.model.output.structure.TypeReference;
+import com.yomahub.liteflow.ai.engine.tool.registry.StaticToolRegistry;
+import com.yomahub.liteflow.ai.engine.tool.registry.ToolRegistry;
+import com.yomahub.liteflow.ai.model.ModelFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static com.yomahub.liteflow.ai.util.SetUtil.setIfPresent;
/**
- * TODO
+ * ChatRequest 组装器(意图识别)
*
* @author 苍镜月
* @since TODO
@@ -14,12 +33,113 @@ import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatRequest;
public class ClassifyRequestAssembler extends AbstractRequestAssembler {
+ @SuppressWarnings("rawtypes")
@Override
protected ChatRequest doAssemble(ParsedClassifyAnnotationConfig annotationConfig, ModelConfigAggregator config, ChatContext context) {
- // TODO
+ ChatRequest.Builder> builder = ModelFactory.getChatRequestBuilder(config.getProvider());
+ // 1. 连接 StreamHandler 回调
+ StreamHandler streamHandler = context.getStreamHandler();
+ if (Objects.nonNull(streamHandler)) {
+ LOG.info("Connecting StreamHandler to ChatRequest");
+ builder.onStart(streamHandler::onStart)
+ .onClose(streamHandler::onClose)
+ .onError(streamHandler::onError)
+ .onText(streamHandler::onText)
+ .onThinking(streamHandler::onThinking)
+ .onToolsCalling(streamHandler::onToolsCalling)
+ .onUsage(streamHandler::onUsage)
+ .onGrounding(streamHandler::onGrounding)
+ .onCompletion(streamHandler::onCompletion)
+ .onFinal(streamHandler::onFinal);
+ }
+
+ // 2. ChatOptions
+ ChatOptions.Builder> optionsBuilder = ChatOptions.builder();
+ setIfPresent(optionsBuilder::temperature, config.getTemperature());
+ setIfPresent(optionsBuilder::topP, config.getTopP());
+ setIfPresent(optionsBuilder::topK, config.getTopK());
+ setIfPresent(optionsBuilder::maxTokens, config.getMaxTokens());
+ setIfPresent(optionsBuilder::seed, config.getSeed());
+ setIfPresent(optionsBuilder::enableThinking, config.getEnableThinking().toBool());
+ builder.options(optionsBuilder.build());
+
+
+ // 3. Message
+ List messages = new ArrayList<>();
+ // 意图分类的系统消息
+ messages.add(buildClassifyMessage(annotationConfig));
+ setIfPresent(t -> messages.add(new SystemMessage(t)), annotationConfig.getSystemPrompt());
+ setIfPresent(t -> messages.add(new UserMessage(t)), annotationConfig.getUserPrompt());
+
+ builder.messages(messages);
+
+ // 4. streaming 相关参数
// 定死使用阻塞式传输
+ builder.streaming(false);
+ builder.transportType(TransportType.HTTP);
- return null;
+ // 4. 结构化输出
+ // 如果开启了多意图分类,那么返回值就是 List,否则就是单一的 String
+ if (annotationConfig.isMultiLabel()) {
+ builder.targetType(new TypeReference>() {
+ });
+ builder.responseType(ResponseType.JSON);
+ builder.strict(true);
+ } else {
+ builder.targetType(new TypeReference() {
+ });
+ builder.responseType(ResponseType.TEXT);
+ builder.strict(false);
+ }
+
+ // 5. 工具调用
+ ToolRegistry toolRegistry = context.getToolRegistry();
+ if (Objects.nonNull(toolRegistry)) {
+ StaticToolRegistry staticToolRegistry = new StaticToolRegistry();
+ HashSet targetToolNames = new HashSet<>(annotationConfig.getToolNames());
+ toolRegistry.getAllTools()
+ .stream()
+ .filter(tool -> targetToolNames.isEmpty() || targetToolNames.contains(tool.getName()))
+ .forEach(staticToolRegistry::register);
+ builder.toolRegistry(staticToolRegistry);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * 构建意图分类的系统消息
+ *
+ * @param annotationConfig 注解配置
+ * @return 系统消息
+ */
+ private Message buildClassifyMessage(ParsedClassifyAnnotationConfig annotationConfig) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("You are an expert intent classifier.\n");
+ sb.append("Your task is to analyze the user's query and classify it based on the predefined categories.\n\n");
+ sb.append("Available categories are:\n");
+
+ String categoriesString = annotationConfig.getCategories().stream()
+ .map(c -> String.format("- %s", c))
+ .collect(Collectors.joining("\n"));
+ sb.append(categoriesString);
+ sb.append("\n\n");
+
+ sb.append("Follow these rules strictly:\n");
+
+ if (annotationConfig.isMultiLabel()) {
+ sb.append("1. You may select one or more categories that are relevant to the user's query.\n");
+ sb.append("2. Your response MUST be a valid JSON array of strings, containing only the names of the selected categories.\n");
+ sb.append("3. For example: [\"category1\", \"category2\"]\n");
+ } else {
+ sb.append("1. You must select only ONE category that best matches the user's query.\n");
+ sb.append("2. Your response MUST be only the name of that single category.\n");
+ sb.append("3. For example: category1\n");
+ }
+
+ sb.append("4. Do NOT provide any explanations, introductions, or any text other than the category name(s) in the specified format.");
+
+ return new SystemMessage(sb.toString());
}
}
diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ContextAccessor.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ContextAccessor.java
index fa3ab27f5..24fe6a894 100644
--- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ContextAccessor.java
+++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ContextAccessor.java
@@ -59,19 +59,18 @@ public class ContextAccessor {
if (StrUtil.isBlank(expression) || Objects.isNull(value)) return;
// 检查结果是否为 Response 类型
- if (!(value instanceof Response)) {
- throw new LiteFlowAIException("AI node output value must be of type Response.");
- }
- // 如果 request 是 ChatRequest 且 value 是 ChatResponse,则尝试进行结构化转换
- if (context.getModelRequest() instanceof ChatRequest && value instanceof ChatResponse) {
- ChatRequest chatRequest = context.getModelRequest().toChatRequest();
- if (ResponseType.JSON.equals(chatRequest.getResponseType())) {
- value = ((ChatResponse) value).as(chatRequest.getOutputParser());
+ if (value instanceof Response) {
+ // 如果 request 是 ChatRequest 且 value 是 ChatResponse,则尝试进行结构化转换
+ if (context.getModelRequest() instanceof ChatRequest && value instanceof ChatResponse) {
+ ChatRequest chatRequest = context.getModelRequest().toChatRequest();
+ if (ResponseType.JSON.equals(chatRequest.getResponseType())) {
+ value = ((ChatResponse) value).as(chatRequest.getOutputParser());
+ } else {
+ value = ((ChatResponse) value).getContent();
+ }
} else {
- value = ((ChatResponse) value).getContent();
+ value = ((Response>) value).getContent();
}
- } else {
- value = ((Response>) value).getContent();
}
NodeComponent nodeComponent = context.getNodeComponent();
diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/AbstractAIComponentHandler.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/AbstractAIComponentHandler.java
index 737524731..d9999d508 100644
--- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/AbstractAIComponentHandler.java
+++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/AbstractAIComponentHandler.java
@@ -129,7 +129,7 @@ public abstract class AbstractAIComponentHandler {
.subclass(nodeComponentClass)
.name(generateProxyClassName(wrapBean))
.implement(wrapBean.getInterfaceClass())
- .method(getInterceptMethodName())
+ .method(getInterceptMethodName(wrapBean))
.intercept(InvocationHandlerAdapter.of(getInvocationHandler(wrapBean)))
.make()
.load(this.getClass().getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
@@ -174,7 +174,7 @@ public abstract class AbstractAIComponentHandler {
*
* @return 拦截方法名称
*/
- protected abstract ElementMatcher super MethodDescription> getInterceptMethodName();
+ protected abstract ElementMatcher super MethodDescription> getInterceptMethodName(AIProxyWrapBean wrapBean);
/**
* 判断是否支持指定的注解类型
diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/ChatComponentHandler.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/ChatComponentHandler.java
index 249495101..5549e0ca0 100644
--- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/ChatComponentHandler.java
+++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/ChatComponentHandler.java
@@ -45,7 +45,7 @@ public class ChatComponentHandler extends AbstractAIComponentHandler {
}
@Override
- protected ElementMatcher super MethodDescription> getInterceptMethodName() {
+ protected ElementMatcher super MethodDescription> getInterceptMethodName(AIProxyWrapBean wrapBean) {
return ElementMatchers.named(INTERCEPT_METHOD_NAME);
}
}
\ No newline at end of file
diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/ClassifyComponentHandler.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/ClassifyComponentHandler.java
index 39fc1480c..7a72bdbad 100644
--- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/ClassifyComponentHandler.java
+++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/handler/ClassifyComponentHandler.java
@@ -21,7 +21,8 @@ import java.lang.reflect.InvocationHandler;
*/
public class ClassifyComponentHandler extends AbstractAIComponentHandler {
- private static final String INTERCEPT_METHOD_NAME = "processSwitch";
+ private static final String INTERCEPT_SWITCH_METHOD_NAME = "processSwitch";
+ private static final String INTERCEPT_MULTI_SWITCH_METHOD_NAME = "processMultiSwitch";
@Override
public AITypeEnum getAIType() {
@@ -45,7 +46,11 @@ public class ClassifyComponentHandler extends AbstractAIComponentHandler getInterceptMethodName() {
- return ElementMatchers.named(INTERCEPT_METHOD_NAME);
+ protected ElementMatcher super MethodDescription> getInterceptMethodName(AIProxyWrapBean wrapBean) {
+ if (wrapBean.getAnnotation().multiLabel()) {
+ return ElementMatchers.named(INTERCEPT_MULTI_SWITCH_METHOD_NAME);
+ } else {
+ return ElementMatchers.named(INTERCEPT_SWITCH_METHOD_NAME);
+ }
}
}
\ No newline at end of file
diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/invocation/ClassifyAIInvocationHandler.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/invocation/ClassifyAIInvocationHandler.java
index 93e9ce88c..b485e4d8e 100644
--- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/invocation/ClassifyAIInvocationHandler.java
+++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/proxy/invocation/ClassifyAIInvocationHandler.java
@@ -2,6 +2,7 @@ package com.yomahub.liteflow.ai.proxy.invocation;
import com.yomahub.liteflow.ai.domain.dto.ParsedClassifyAnnotationConfig;
import com.yomahub.liteflow.ai.engine.model.chat.ChatModel;
+import com.yomahub.liteflow.ai.engine.model.chat.entity.ChatResponse;
import com.yomahub.liteflow.ai.exception.LiteFlowAIException;
import com.yomahub.liteflow.ai.model.ModelFactory;
import com.yomahub.liteflow.ai.parse.context.ProcessorContext;
@@ -30,18 +31,18 @@ public class ClassifyAIInvocationHandler extends AbstractAIInvocationHandler 1) {
- throw new LiteFlowAIException("Multi-label classification is not allowed when multiLabel is false");
- }
- if (annotationConfig.isMultiLabel() && annotationConfig.getCategories().size() < 2) {
- throw new LiteFlowAIException("At least two categories are required for multi-label classification");
- }
}
@Override
protected Object doExecuteAIProcess(ProcessorContext> processorContext, Object[] args) {
ChatModel chatModel = ModelFactory.getChatModel(wrapBean);
- return chatModel.chat(processorContext.getModelRequest().toChatRequest());
+ ChatResponse response = chatModel.chat(processorContext.getModelRequest().toChatRequest());
+ ParsedClassifyAnnotationConfig annotationConfig = (ParsedClassifyAnnotationConfig) processorContext.getParsedAnnotationConfig();
+ // 如果是多标签则返回结构化转换的list, 单标签返回 String
+ if (annotationConfig.isMultiLabel()) {
+ return response.as(processorContext.getModelRequest().toChatRequest().getOutputParser());
+ } else {
+ return response.getContent().getContent();
+ }
}
}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/ChatTest.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/ChatTest.java
similarity index 62%
rename from liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/ChatTest.java
rename to liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/ChatTest.java
index 4ef553d39..6366b2970 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/ChatTest.java
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/ChatTest.java
@@ -1,4 +1,4 @@
-package com.yomahub.liteflow.test.ai.core.proxy;
+package com.yomahub.liteflow.test.ai.core.chat;
import com.yomahub.liteflow.ai.context.ChatContext;
import com.yomahub.liteflow.ai.context.StreamHandler;
@@ -21,24 +21,17 @@ import javax.annotation.Resource;
* @since TODO
*/
-@TestPropertySource(properties = {"spring.config.location=classpath:core/proxy/application.yaml"})
+@TestPropertySource(properties = {"spring.config.location=classpath:core/chat/application.yaml"})
@SpringBootTest(classes = {ChatTest.class, SpringUtil.class})
@EnableAutoConfiguration
-@ComponentScan({"com.yomahub.liteflow.test.ai.core.proxy.cmp"})
+@ComponentScan({"com.yomahub.liteflow.test.ai.core.chat.cmp"})
public class ChatTest {
@Resource
private FlowExecutor flowExecutor;
- @Test
- public void testBlockingChat() {
- LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain1", null, ChatContext.class);
- Assertions.assertTrue(liteflowResponse.isSuccess());
- }
-
- @Test
- public void testStreaming() {
- StreamHandler streamHandler = StreamHandler.builder()
+ private StreamHandler getStreamHandler() {
+ return StreamHandler.builder()
.onStart(context -> System.out.println("chat start"))
.onClose(context -> System.out.println("chat close"))
.onError((context, t) -> {
@@ -61,10 +54,45 @@ public class ChatTest {
return response;
})
.build();
+ }
- ChatContext chatContext = new ChatContext(streamHandler);
+ @Test
+ public void testDashScopeChat() {
+ LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain1", null, ChatContext.class);
+ Assertions.assertTrue(liteflowResponse.isSuccess());
+ }
+ @Test
+ public void testDashScopeStream() {
+ ChatContext chatContext = new ChatContext(getStreamHandler());
LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain2", null, chatContext);
Assertions.assertTrue(liteflowResponse.isSuccess());
}
+
+
+ @Test
+ public void testOllamaChat() {
+ LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain3", null, ChatContext.class);
+ Assertions.assertTrue(liteflowResponse.isSuccess());
+ }
+
+ @Test
+ public void testOllamaStream() {
+ ChatContext chatContext = new ChatContext(getStreamHandler());
+ LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain4", null, chatContext);
+ Assertions.assertTrue(liteflowResponse.isSuccess());
+ }
+
+ @Test
+ public void testOpenAIChat() {
+ LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain5", null, ChatContext.class);
+ Assertions.assertTrue(liteflowResponse.isSuccess());
+ }
+
+ @Test
+ public void testOpenAIStream() {
+ ChatContext chatContext = new ChatContext(getStreamHandler());
+ LiteflowResponse liteflowResponse = flowExecutor.execute2Resp("chain6", null, chatContext);
+ Assertions.assertTrue(liteflowResponse.isSuccess());
+ }
}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/ACmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ACmp.java
similarity index 86%
rename from liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/ACmp.java
rename to liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ACmp.java
index 4e7f4fdad..7f04724bd 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/ACmp.java
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ACmp.java
@@ -1,4 +1,4 @@
-package com.yomahub.liteflow.test.ai.core.proxy.cmp;
+package com.yomahub.liteflow.test.ai.core.chat.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/BCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/BCmp.java
similarity index 87%
rename from liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/BCmp.java
rename to liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/BCmp.java
index d0e51d08a..38f5348ab 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/BCmp.java
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/BCmp.java
@@ -1,4 +1,4 @@
-package com.yomahub.liteflow.test.ai.core.proxy.cmp;
+package com.yomahub.liteflow.test.ai.core.chat.cmp;
import com.yomahub.liteflow.core.NodeComponent;
import org.springframework.stereotype.Component;
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/Output.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/Output.java
similarity index 87%
rename from liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/Output.java
rename to liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/Output.java
index 05ea5bafb..38f9fed71 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/Output.java
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/Output.java
@@ -1,4 +1,4 @@
-package com.yomahub.liteflow.test.ai.core.proxy.cmp;
+package com.yomahub.liteflow.test.ai.core.chat.cmp;
/**
* 测试结构化输出使用
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/AIBlockingChatCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/dashscope/DashScopeChatCmp.java
similarity index 66%
rename from liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/AIBlockingChatCmp.java
rename to liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/dashscope/DashScopeChatCmp.java
index 9d10ecc78..be0606fdf 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/AIBlockingChatCmp.java
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/dashscope/DashScopeChatCmp.java
@@ -1,4 +1,4 @@
-package com.yomahub.liteflow.test.ai.core.proxy.cmp;
+package com.yomahub.liteflow.test.ai.core.chat.cmp.dashscope;
import com.yomahub.liteflow.ai.annotation.*;
import com.yomahub.liteflow.ai.engine.interact.transport.TransportType;
@@ -13,20 +13,17 @@ import com.yomahub.liteflow.ai.util.TriState;
*/
@AIComponent(
- nodeId = "aiBlockingChatCmpId",
- nodeName = "aiBlockingChatCmpName",
-// provider = "ollama",
-// apiUrl = "http://localhost:11434",
-// model = "qwen3:32b",
+ nodeId = "DashScopeChat",
+ nodeName = "DashScopeChat",
provider = "dashscope",
apiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1",
- model = "deepseek-r1",
+ model = "qwen-flash",
enableThinking = TriState.FALSE,
readTimeout = "10m",
connectTimeout = "10m"
)
@AIChat(
- systemPrompt = "classpath:core/proxy/system_prompt.txt",
+ systemPrompt = "classpath:core/chat/system_prompt.txt",
userPrompt = "{{question}}",
streaming = false,
transportType = TransportType.HTTP
@@ -38,10 +35,10 @@ import com.yomahub.liteflow.ai.util.TriState;
)
@AIOutput(
responseType = ResponseType.JSON,
- typeName = "com.yomahub.liteflow.test.ai.core.proxy.cmp.Output",
+ typeName = "com.yomahub.liteflow.test.ai.core.chat.cmp.Output",
methodExpress = "setData",
useKeyIndex = true,
key = "result"
)
-public interface AIBlockingChatCmp {
+public interface DashScopeChatCmp {
}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/AIStreamingChatCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/dashscope/DashScopeStreamCmp.java
similarity index 64%
rename from liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/AIStreamingChatCmp.java
rename to liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/dashscope/DashScopeStreamCmp.java
index 88ab5485b..df56c9207 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/cmp/AIStreamingChatCmp.java
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/dashscope/DashScopeStreamCmp.java
@@ -1,4 +1,4 @@
-package com.yomahub.liteflow.test.ai.core.proxy.cmp;
+package com.yomahub.liteflow.test.ai.core.chat.cmp.dashscope;
import com.yomahub.liteflow.ai.annotation.*;
import com.yomahub.liteflow.ai.engine.interact.transport.TransportType;
@@ -13,13 +13,8 @@ import com.yomahub.liteflow.ai.util.TriState;
*/
@AIComponent(
- nodeId = "aiStreamingChatCmpId",
- nodeName = "aiStreamingChatCmpName",
-// provider = "ollama",
-// apiUrl = "http://localhost:11434",
-// model = "qwen3:32b",
-// provider = "openai",
-// apiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1",
+ nodeId = "DashScopeStream",
+ nodeName = "DashScopeStream",
provider = "dashscope",
apiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1",
model = "deepseek-r1",
@@ -28,7 +23,7 @@ import com.yomahub.liteflow.ai.util.TriState;
connectTimeout = "10m"
)
@AIChat(
- systemPrompt = "classpath:core/proxy/system_prompt.txt",
+ systemPrompt = "classpath:core/chat/system_prompt.txt",
userPrompt = "{{question}}",
streaming = true,
transportType = TransportType.SSE
@@ -40,10 +35,10 @@ import com.yomahub.liteflow.ai.util.TriState;
)
@AIOutput(
responseType = ResponseType.TEXT,
- typeName = "com.yomahub.liteflow.test.ai.core.proxy.cmp.Output",
+ typeName = "com.yomahub.liteflow.test.ai.core.chat.cmp.Output",
methodExpress = "setData",
useKeyIndex = true,
key = "result"
)
-public interface AIStreamingChatCmp {
+public interface DashScopeStreamCmp {
}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ollama/OllamaChatCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ollama/OllamaChatCmp.java
new file mode 100644
index 000000000..5cd56a355
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ollama/OllamaChatCmp.java
@@ -0,0 +1,44 @@
+package com.yomahub.liteflow.test.ai.core.chat.cmp.ollama;
+
+import com.yomahub.liteflow.ai.annotation.*;
+import com.yomahub.liteflow.ai.engine.interact.transport.TransportType;
+import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
+import com.yomahub.liteflow.ai.util.TriState;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@AIComponent(
+ nodeId = "OllamaChat",
+ nodeName = "OllamaChat",
+ provider = "ollama",
+ apiUrl = "http://localhost:11434",
+ model = "qwen3:32b",
+ enableThinking = TriState.FALSE,
+ readTimeout = "10m",
+ connectTimeout = "10m"
+)
+@AIChat(
+ systemPrompt = "classpath:core/chat/system_prompt.txt",
+ userPrompt = "{{question}}",
+ streaming = false,
+ transportType = TransportType.HTTP
+)
+@AIInput(
+ mapping = {
+ @InputField(name = "question", expression = "test", defaultValue = "What is LiteFlow?"),
+ }
+)
+@AIOutput(
+ responseType = ResponseType.JSON,
+ typeName = "com.yomahub.liteflow.test.ai.core.chat.cmp.Output",
+ methodExpress = "setData",
+ useKeyIndex = true,
+ key = "result"
+)
+public interface OllamaChatCmp {
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ollama/OllamaStreamCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ollama/OllamaStreamCmp.java
new file mode 100644
index 000000000..1d2df4ac6
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/ollama/OllamaStreamCmp.java
@@ -0,0 +1,44 @@
+package com.yomahub.liteflow.test.ai.core.chat.cmp.ollama;
+
+import com.yomahub.liteflow.ai.annotation.*;
+import com.yomahub.liteflow.ai.engine.interact.transport.TransportType;
+import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
+import com.yomahub.liteflow.ai.util.TriState;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@AIComponent(
+ nodeId = "OllamaStream",
+ nodeName = "OllamaStream",
+ provider = "ollama",
+ apiUrl = "http://localhost:11434",
+ model = "qwen3:32b",
+ enableThinking = TriState.TRUE,
+ readTimeout = "10m",
+ connectTimeout = "10m"
+)
+@AIChat(
+ systemPrompt = "classpath:core/chat/system_prompt.txt",
+ userPrompt = "{{question}}",
+ streaming = true,
+ transportType = TransportType.DnJson
+)
+@AIInput(
+ mapping = {
+ @InputField(name = "question", expression = "test", defaultValue = "简短讲解什么是 LiteFlow?"),
+ }
+)
+@AIOutput(
+ responseType = ResponseType.TEXT,
+ typeName = "com.yomahub.liteflow.test.ai.core.chat.cmp.Output",
+ methodExpress = "setData",
+ useKeyIndex = true,
+ key = "result"
+)
+public interface OllamaStreamCmp {
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/openai/OpenAIChatCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/openai/OpenAIChatCmp.java
new file mode 100644
index 000000000..46a24e702
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/openai/OpenAIChatCmp.java
@@ -0,0 +1,44 @@
+package com.yomahub.liteflow.test.ai.core.chat.cmp.openai;
+
+import com.yomahub.liteflow.ai.annotation.*;
+import com.yomahub.liteflow.ai.engine.interact.transport.TransportType;
+import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
+import com.yomahub.liteflow.ai.util.TriState;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@AIComponent(
+ nodeId = "OpenAIChat",
+ nodeName = "OpenAIChat",
+ provider = "openai",
+ apiUrl = "https://ark.cn-beijing.volces.com/api/v3",
+ model = "doubao-seed-1-6-250615",
+ enableThinking = TriState.FALSE,
+ readTimeout = "10m",
+ connectTimeout = "10m"
+)
+@AIChat(
+ systemPrompt = "classpath:core/chat/system_prompt.txt",
+ userPrompt = "{{question}}",
+ streaming = false,
+ transportType = TransportType.HTTP
+)
+@AIInput(
+ mapping = {
+ @InputField(name = "question", expression = "test", defaultValue = "What is LiteFlow?"),
+ }
+)
+@AIOutput(
+ responseType = ResponseType.JSON,
+ typeName = "com.yomahub.liteflow.test.ai.core.chat.cmp.Output",
+ methodExpress = "setData",
+ useKeyIndex = true,
+ key = "result"
+)
+public interface OpenAIChatCmp {
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/openai/OpenAIStreamCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/openai/OpenAIStreamCmp.java
new file mode 100644
index 000000000..04cc6f8db
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/chat/cmp/openai/OpenAIStreamCmp.java
@@ -0,0 +1,44 @@
+package com.yomahub.liteflow.test.ai.core.chat.cmp.openai;
+
+import com.yomahub.liteflow.ai.annotation.*;
+import com.yomahub.liteflow.ai.engine.interact.transport.TransportType;
+import com.yomahub.liteflow.ai.engine.model.output.ResponseType;
+import com.yomahub.liteflow.ai.util.TriState;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@AIComponent(
+ nodeId = "OpenAIStream",
+ nodeName = "OpenAIStream",
+ provider = "openai",
+ apiUrl = "https://ark.cn-beijing.volces.com/api/v3",
+ model = "doubao-seed-1-6-250615",
+ enableThinking = TriState.TRUE,
+ readTimeout = "10m",
+ connectTimeout = "10m"
+)
+@AIChat(
+ systemPrompt = "classpath:core/chat/system_prompt.txt",
+ userPrompt = "{{question}}",
+ streaming = true,
+ transportType = TransportType.SSE
+)
+@AIInput(
+ mapping = {
+ @InputField(name = "question", expression = "test", defaultValue = "简短讲解什么是 LiteFlow?"),
+ }
+)
+@AIOutput(
+ responseType = ResponseType.TEXT,
+ typeName = "com.yomahub.liteflow.test.ai.core.chat.cmp.Output",
+ methodExpress = "setData",
+ useKeyIndex = true,
+ key = "result"
+)
+public interface OpenAIStreamCmp {
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/ClassifyTest.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/ClassifyTest.java
new file mode 100644
index 000000000..af42fdb54
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/ClassifyTest.java
@@ -0,0 +1,37 @@
+package com.yomahub.liteflow.test.ai.core.classify;
+
+import com.yomahub.liteflow.ai.context.ChatContext;
+import com.yomahub.liteflow.ai.util.SpringUtil;
+import com.yomahub.liteflow.core.FlowExecutor;
+import com.yomahub.liteflow.flow.LiteflowResponse;
+import org.junit.jupiter.api.Assertions;
+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;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@TestPropertySource(properties = {"spring.config.location=classpath:core/classify/application.yaml"})
+@SpringBootTest(classes = {ClassifyTest.class, SpringUtil.class})
+@EnableAutoConfiguration
+@ComponentScan({"com.yomahub.liteflow.test.ai.core.classify.cmp"})
+public class ClassifyTest {
+
+ @Resource
+ private FlowExecutor flowExecutor;
+
+ @Test
+ public void testClassify() {
+ LiteflowResponse response = flowExecutor.execute2Resp("chain1", null, ChatContext.class);
+ Assertions.assertTrue(response.isSuccess());
+ }
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/ACmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/ACmp.java
new file mode 100644
index 000000000..1bdb1506f
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/ACmp.java
@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.ai.core.classify.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@Component("a")
+public class ACmp extends NodeComponent {
+
+ @Override
+ public void process() throws Exception {
+ System.out.println("ACmp executed!");
+ }
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/AIClassifyCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/AIClassifyCmp.java
new file mode 100644
index 000000000..bc17d996c
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/AIClassifyCmp.java
@@ -0,0 +1,41 @@
+package com.yomahub.liteflow.test.ai.core.classify.cmp;
+
+import com.yomahub.liteflow.ai.annotation.*;
+import com.yomahub.liteflow.ai.util.TriState;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@AIComponent(
+ nodeId = "ai",
+ nodeName = "ai",
+// provider = "ollama",
+// apiUrl = "http://localhost:11434",
+// model = "qwen3:32b",
+ provider = "openai",
+ apiUrl = "https://ark.cn-beijing.volces.com/api/v3",
+ model = "doubao-seed-1-6-250615",
+ enableThinking = TriState.FALSE,
+ readTimeout = "10m",
+ connectTimeout = "10m"
+)
+@AIClassify(
+ systemPrompt = "你是一个意图识别高手,你需要识别用户的意图",
+ userPrompt = "{{question}}",
+ categories = {"java", "python"})
+@AIInput(
+ mapping = {
+ @InputField(name = "question", expression = "test", defaultValue = "请帮我写一段Java代码"),
+ }
+)
+@AIOutput(
+ methodExpress = "setData",
+ useKeyIndex = true,
+ key = "result"
+)
+public interface AIClassifyCmp {
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/JavaCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/JavaCmp.java
new file mode 100644
index 000000000..b5aa0521f
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/JavaCmp.java
@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.ai.core.classify.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@Component("java")
+public class JavaCmp extends NodeComponent {
+
+ @Override
+ public void process() throws Exception {
+ System.out.println("Java component executed.");
+ }
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/PythonCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/PythonCmp.java
new file mode 100644
index 000000000..6972057dc
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/classify/cmp/PythonCmp.java
@@ -0,0 +1,20 @@
+package com.yomahub.liteflow.test.ai.core.classify.cmp;
+
+import com.yomahub.liteflow.core.NodeComponent;
+import org.springframework.stereotype.Component;
+
+/**
+ * TODO
+ *
+ * @author 苍镜月
+ * @since TODO
+ */
+
+@Component("python")
+public class PythonCmp extends NodeComponent {
+
+ @Override
+ public void process() throws Exception {
+ System.out.println("Python component executed.");
+ }
+}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/TmpTest.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/TmpTest.java
deleted file mode 100644
index 904c2ea8c..000000000
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/proxy/TmpTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-package com.yomahub.liteflow.test.ai.core.proxy;
-
-/**
- * TODO
- *
- * @author 苍镜月
- * @since TODO
- */
-
-public class TmpTest {
-//
-// @Test
-// public void test1() {
-// Provider provider = new Provider() {
-// @Override
-// public Optional getString() {
-// return Optional.empty();
-// }
-//
-// @Override
-// public Optional getDouble() {
-// return Optional.empty();
-// }
-// };
-//
-// System.out.println(provider.getDouble().orElseThrow(() -> new RuntimeException("No value present")));
-//
-// }
-//
-//
-// static interface Provider {
-// Optional getString();
-//
-// Optional getDouble();
-// }
-//
-//
-//
-// @Test
-// public void testProxyFactoryBean() throws Exception {
-//// AIComponentProxyFactoryBean factoryBean = new AIComponentProxyFactoryBean<>(cmp.class);
-////
-//// System.out.println(cmp.class.getAnnotations());
-//// Class interfaceClass = factoryBean.getInterfaceClass();
-//// Class> objectType = factoryBean.getObjectType();
-////
-//// System.out.println(interfaceClass);
-//// System.out.println(Arrays.toString(interfaceClass.getAnnotations()));
-////
-//// System.out.println(objectType);
-//// System.out.println(Arrays.toString(objectType.getAnnotations()));
-// }
-//
-// @AIComponent
-// @AIChat
-// @AIInput
-// public interface cmp {}
-}
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/tool/cmp/AIBlockingChatCmp.java b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/tool/cmp/AIBlockingChatCmp.java
index fa910ef54..c49148952 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/tool/cmp/AIBlockingChatCmp.java
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/java/com/yomahub/liteflow/test/ai/core/tool/cmp/AIBlockingChatCmp.java
@@ -20,9 +20,12 @@ import com.yomahub.liteflow.ai.util.TriState;
// provider = "ollama",
// apiUrl = "http://localhost:11434",
// model = "qwen3:32b",
- provider = "dashscope",
- apiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1",
- model = "deepseek-r1",
+// provider = "dashscope",
+// apiUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1",
+// model = "deepseek-r1",
+ provider = "openai",
+ apiUrl = "https://ark.cn-beijing.volces.com/api/v3",
+ model = "doubao-seed-1-6-250615",
enableThinking = TriState.FALSE,
readTimeout = "10m",
connectTimeout = "10m"
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/chat/application.yaml b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/chat/application.yaml
new file mode 100644
index 000000000..7648bd5a5
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/chat/application.yaml
@@ -0,0 +1,10 @@
+liteflow:
+ rule-source: core/chat/flow.el.xml
+ ai:
+ base-packages:
+ - com.yomahub.liteflow.test.ai.core.chat.cmp
+ enable: true
+ openai:
+ api-key:
+ dashscope:
+ api-key:
\ No newline at end of file
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/chat/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/chat/flow.el.xml
new file mode 100644
index 000000000..47dc71af1
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/chat/flow.el.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ THEN(a, DashScopeChat, b);
+
+
+
+ THEN(a, DashScopeStream);
+
+
+
+ THEN(a, OllamaChat, b);
+
+
+
+ THEN(a, OllamaStream);
+
+
+
+ THEN(a, OpenAIChat, b);
+
+
+
+ THEN(a, OpenAIStream);
+
+
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/proxy/system_prompt.txt b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/chat/system_prompt.txt
similarity index 100%
rename from liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/proxy/system_prompt.txt
rename to liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/chat/system_prompt.txt
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/classify/application.yaml b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/classify/application.yaml
new file mode 100644
index 000000000..22e33e9b8
--- /dev/null
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/classify/application.yaml
@@ -0,0 +1,10 @@
+liteflow:
+ rule-source: core/classify/flow.el.xml
+ ai:
+ base-packages:
+ - com.yomahub.liteflow.test.ai.core.classify.cmp
+ enable: true
+ openai:
+ api-key:
+ dashscope:
+ api-key:
\ No newline at end of file
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/proxy/flow.el.xml b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/classify/flow.el.xml
similarity index 54%
rename from liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/proxy/flow.el.xml
rename to liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/classify/flow.el.xml
index 3aa3d6e6a..b5e3ef41a 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/proxy/flow.el.xml
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/classify/flow.el.xml
@@ -2,10 +2,6 @@
- THEN(a, aiBlockingChatCmpId, b);
-
-
-
- THEN(a, aiStreamingChatCmpId);
+ THEN(a, SWITCH(ai).TO(java, python));
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/proxy/application.yaml b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/proxy/application.yaml
deleted file mode 100644
index acab13888..000000000
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/proxy/application.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-liteflow:
- rule-source: core/proxy/flow.el.xml
- ai:
- base-packages:
- - com.yomahub.liteflow.test.ai.core.proxy.cmp
- enable: true
\ No newline at end of file
diff --git a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/tool/application.yaml b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/tool/application.yaml
index ee85c9f1a..387de1fb7 100644
--- a/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/tool/application.yaml
+++ b/liteflow-testcase-el/liteflow-testcase-el-ai/src/test/resources/core/tool/application.yaml
@@ -3,4 +3,8 @@ liteflow:
ai:
base-packages:
- com.yomahub.liteflow.test.ai.core.tool.cmp
- enable: true
\ No newline at end of file
+ enable: true
+ openai:
+ api-key:
+ dashscope:
+ api-key: