From a23e6042b2239cd447a379632b1eb1d11545573e Mon Sep 17 00:00:00 2001 From: LuanY77 <2307984361@qq.com> Date: Thu, 31 Jul 2025 16:33:21 +0800 Subject: [PATCH] =?UTF-8?q?Feat:=20=E8=A7=A3=E6=9E=90AIOutput=EF=BC=8C?= =?UTF-8?q?=E5=B0=86=E5=A4=A7=E6=A8=A1=E5=9E=8B=E5=93=8D=E5=BA=94=E7=BB=93?= =?UTF-8?q?=E6=9E=9C=E6=98=A0=E5=B0=84=E5=88=B0=E4=B8=8A=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ai/parse/AbstractAnnotationProcessor.java | 30 +++++ .../liteflow/ai/parse/AnnotationParser.java | 5 +- .../ai/parse/AnnotationProcessor.java | 4 +- .../parse/anno/ChatAnnotationProcessor.java | 10 +- .../anno/ClassifyAnnotationProcessor.java | 5 +- .../anno/RetrievalAnnotationProcessor.java | 4 +- .../ai/parse/context/ContextAccessor.java | 119 ++++++++++++++++++ .../parse/{ => context}/ProcessorContext.java | 2 +- .../ai/parse/prompt/PromptTemplateParser.java | 29 +---- .../ai/ollama/model/OllamaModelProvider.java | 2 + 10 files changed, 173 insertions(+), 37 deletions(-) create mode 100644 liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ContextAccessor.java rename liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/{ => context}/ProcessorContext.java (97%) diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AbstractAnnotationProcessor.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AbstractAnnotationProcessor.java index a8d4af324..2adb3cd3a 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AbstractAnnotationProcessor.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AbstractAnnotationProcessor.java @@ -5,6 +5,8 @@ import com.yomahub.liteflow.ai.annotation.AIInput; import com.yomahub.liteflow.ai.annotation.AIOutput; import com.yomahub.liteflow.ai.domain.enums.AITypeEnum; import com.yomahub.liteflow.ai.domain.enums.ResponseType; +import com.yomahub.liteflow.ai.parse.context.ContextAccessor; +import com.yomahub.liteflow.ai.parse.context.ProcessorContext; import com.yomahub.liteflow.ai.parse.prompt.PromptTemplateParser; import com.yomahub.liteflow.ai.parse.prompt.resource.PromptResource; import com.yomahub.liteflow.ai.proxy.wrap.AIProxyWrapBean; @@ -94,4 +96,32 @@ public abstract class AbstractAnnotationProcessor context, Object result) { + if (Objects.isNull(result)) { + LOG.warn("AI processing result is null, skipping context mapping."); + return; + } + + AIOutput outputAnno = context.getAiOutputAnno(); + if (Objects.isNull(outputAnno)) { + LOG.warn("No AIOutput annotation found, skipping context mapping."); + return; + } + + // 将结果映射到上下文中 + String expression = outputAnno.methodExpress(); + if (StrUtil.isNotBlank(expression)) { + ContextAccessor.setContextValueByExpression(expression, context, result); + LOG.info("Mapped AI output to context with expression: {}", expression); + } else { + LOG.warn("AIOutput expression is blank, skipping context mapping."); + } + } } diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AnnotationParser.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AnnotationParser.java index 63167f9af..80aee130e 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AnnotationParser.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AnnotationParser.java @@ -1,6 +1,7 @@ package com.yomahub.liteflow.ai.parse; import com.yomahub.liteflow.ai.domain.enums.AITypeEnum; +import com.yomahub.liteflow.ai.parse.context.ProcessorContext; import com.yomahub.liteflow.log.LFLog; import com.yomahub.liteflow.log.LFLoggerManager; @@ -52,7 +53,7 @@ public class AnnotationParser { /** * 执行注解解析后处理 * - * @param annotation 待解析注解 + * @param annotation 待解析注解(这里的注解参数是为了帮助找到对应的处理器) * @param context 处理器上下文 * @param result 响应结果 */ @@ -60,7 +61,7 @@ public class AnnotationParser { public static void postProcessAfterTrigger(Annotation annotation, ProcessorContext context, Object result) { AITypeEnum type = AITypeEnum.fromAnnotationType(annotation); AnnotationProcessor processor = getProcessor(type.getCode()); - processor.postProcessAfterTrigger(annotation, context, result); + processor.postProcessAfterTrigger(context, result); } private static AnnotationProcessor getProcessor(Integer code) { diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AnnotationProcessor.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AnnotationProcessor.java index 14127f581..9ee2a317c 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AnnotationProcessor.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/AnnotationProcessor.java @@ -1,5 +1,6 @@ package com.yomahub.liteflow.ai.parse; +import com.yomahub.liteflow.ai.parse.context.ProcessorContext; import com.yomahub.liteflow.ai.proxy.wrap.AIProxyWrapBean; import java.lang.annotation.Annotation; @@ -24,9 +25,8 @@ public interface AnnotationProcessor context, Object result); + void postProcessAfterTrigger(ProcessorContext context, Object result); } diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/ChatAnnotationProcessor.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/ChatAnnotationProcessor.java index 2a7bf647a..b459dd838 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/ChatAnnotationProcessor.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/ChatAnnotationProcessor.java @@ -3,7 +3,7 @@ package com.yomahub.liteflow.ai.parse.anno; import com.yomahub.liteflow.ai.annotation.AIChat; import com.yomahub.liteflow.ai.domain.enums.AITypeEnum; import com.yomahub.liteflow.ai.parse.AbstractAnnotationProcessor; -import com.yomahub.liteflow.ai.parse.ProcessorContext; +import com.yomahub.liteflow.ai.parse.context.ProcessorContext; import com.yomahub.liteflow.ai.proxy.wrap.ChatProxyWrapBean; import com.yomahub.liteflow.ai.util.SetUtil; @@ -33,8 +33,12 @@ public class ChatAnnotationProcessor extends AbstractAnnotationProcessor context, Object result) { - // TODO: 处理输出参数绑定 + public void postProcessAfterTrigger(ProcessorContext context, Object result) { + ChatProxyWrapBean wrapBean = context.getWrapBean(); + // 非流式输出,需要进行结构化处理 + if (!wrapBean.isStreaming()) { + mapOutput2Context(context, result); + } } @Override diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/ClassifyAnnotationProcessor.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/ClassifyAnnotationProcessor.java index 34e2ea589..136c8ee9a 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/ClassifyAnnotationProcessor.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/ClassifyAnnotationProcessor.java @@ -3,7 +3,7 @@ package com.yomahub.liteflow.ai.parse.anno; import com.yomahub.liteflow.ai.annotation.AIClassify; import com.yomahub.liteflow.ai.domain.enums.AITypeEnum; import com.yomahub.liteflow.ai.parse.AbstractAnnotationProcessor; -import com.yomahub.liteflow.ai.parse.ProcessorContext; +import com.yomahub.liteflow.ai.parse.context.ProcessorContext; import com.yomahub.liteflow.ai.proxy.wrap.ClassifyProxyWrapBean; import com.yomahub.liteflow.ai.util.SetUtil; @@ -38,7 +38,8 @@ public class ClassifyAnnotationProcessor extends AbstractAnnotationProcessor context, Object result) { + public void postProcessAfterTrigger(ProcessorContext context, Object result) { + mapOutput2Context(context, result); } @Override diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/RetrievalAnnotationProcessor.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/RetrievalAnnotationProcessor.java index 058d8df5f..ba2272031 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/RetrievalAnnotationProcessor.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/anno/RetrievalAnnotationProcessor.java @@ -3,7 +3,7 @@ package com.yomahub.liteflow.ai.parse.anno; import com.yomahub.liteflow.ai.annotation.AIRetrieval; import com.yomahub.liteflow.ai.domain.enums.AITypeEnum; import com.yomahub.liteflow.ai.parse.AbstractAnnotationProcessor; -import com.yomahub.liteflow.ai.parse.ProcessorContext; +import com.yomahub.liteflow.ai.parse.context.ProcessorContext; import com.yomahub.liteflow.ai.proxy.wrap.RetrievalProxyWrapBean; /** @@ -21,7 +21,7 @@ public class RetrievalAnnotationProcessor extends AbstractAnnotationProcessor context, Object result) { + public void postProcessAfterTrigger(ProcessorContext context, Object result) { } 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 new file mode 100644 index 000000000..8e842fcae --- /dev/null +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ContextAccessor.java @@ -0,0 +1,119 @@ +package com.yomahub.liteflow.ai.parse.context; + +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; +import com.yomahub.liteflow.ai.annotation.AIOutput; +import com.yomahub.liteflow.ai.annotation.OutputField; +import com.yomahub.liteflow.ai.exception.LiteFlowAIException; +import com.yomahub.liteflow.ai.util.SetUtil; +import com.yomahub.liteflow.core.NodeComponent; +import com.yomahub.liteflow.log.LFLog; +import com.yomahub.liteflow.log.LFLoggerManager; +import dev.langchain4j.service.Result; + +import java.util.Objects; + +/** + * 上下文访问器 + * + * @author 苍镜月 + * @since TODO + */ + +public class ContextAccessor { + + private static final LFLog LOG = LFLoggerManager.getLogger(ContextAccessor.class); + + /** + * 根据表达式在上下文中查找值(AIInput注解使用) + * + * @param expression 表达式 + * @param context 处理器上下文 + * @return 查找到的值 + */ + public static String searchContextByExpression(String expression, ProcessorContext context) { + if (StrUtil.isBlank(expression)) { + return null; + } + + try { + NodeComponent nodeComponent = context.getNodeComponent(); + return nodeComponent.getContextValue(expression); + } catch (Exception e) { + LOG.info("Failed to search context by expression: {}", expression); + return null; + } + } + + /** + * 根据表达式在上下文中设置值(AIOutput注解使用) + * + * @param expression 表达式 + * @param context 处理器上下文 + * @param value 值 + */ + public static void setContextValueByExpression(String expression, ProcessorContext context, Object value) { + if (StrUtil.isBlank(expression) || Objects.isNull(value)) return; + + // 检查结果是否为 Result 类型 + if (!(value instanceof Result)) { + throw new LiteFlowAIException("AIOutput annotation value must be of type Result."); + } + // 获取 Result 的内容 + value = ((Result) value).content(); + + NodeComponent nodeComponent = context.getNodeComponent(); + AIOutput outputAnno = context.getAiOutputAnno(); + + // 检查是否使用了嵌套索引 + if (outputAnno.useKeyIndex()) { + // 如果同时使用了 key 和 index,则抛出异常 + if (SetUtil.isPresent(outputAnno.key()) && SetUtil.isPresent(outputAnno.index())) { + throw new LiteFlowAIException("AIOutput annotation cannot use both key and index together."); + } + // 如果 key 和 index 都未设置,则抛出异常 + if (SetUtil.isNotPresent(outputAnno.key()) && SetUtil.isNotPresent(outputAnno.index())) { + throw new LiteFlowAIException("AIOutput annotation must specify either key or index when useKeyIndex is true."); + } + Object key = SetUtil.isPresent(outputAnno.key()) ? outputAnno.key() : outputAnno.index(); + nodeComponent.setContextValue(expression, key, value); + } else { + // 如果未使用嵌套索引,则直接设置值 + nodeComponent.setContextValue(expression, value); + } + + // 处理输出字段映射 + if (SetUtil.isPresent(outputAnno.mapping())) { + for (OutputField outputField : outputAnno.mapping()) { + // 获取源字段值 + Object fieldValue = ReflectUtil.getFieldValue(value, outputField.sourceField()); + + if (Objects.isNull(fieldValue)) { + continue; + } + + // 确定目标表达式,如果未指定,直接集成 AIOutput 的主表达式 + String targetExpression = StrUtil.isNotBlank(outputField.methodExpress()) + ? outputField.methodExpress() + : outputAnno.methodExpress(); + + // 使用了嵌套索引 + if (outputAnno.useKeyIndex()) { + // 如果同时使用了 key 和 index,则抛出异常 + if (SetUtil.isPresent(outputField.key()) && SetUtil.isPresent(outputField.index())) { + throw new LiteFlowAIException("OutputField annotation cannot use both key and index together."); + } + // 如果 key 和 index 都未设置,则抛出异常 + if (SetUtil.isNotPresent(outputField.key()) && SetUtil.isNotPresent(outputField.index())) { + throw new LiteFlowAIException("OutputField annotation must specify either key or index when useKeyIndex is true."); + } + Object key = SetUtil.isPresent(outputField.key()) ? outputField.key() : outputField.index(); + nodeComponent.setContextValue(targetExpression, key, fieldValue); + } else { + // 未使用嵌套索引,直接设置值 + nodeComponent.setContextValue(targetExpression, fieldValue); + } + } + } + } +} diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/ProcessorContext.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ProcessorContext.java similarity index 97% rename from liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/ProcessorContext.java rename to liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ProcessorContext.java index 1411d0fca..4f8340820 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/ProcessorContext.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/context/ProcessorContext.java @@ -1,4 +1,4 @@ -package com.yomahub.liteflow.ai.parse; +package com.yomahub.liteflow.ai.parse.context; import com.yomahub.liteflow.ai.annotation.AIInput; import com.yomahub.liteflow.ai.annotation.AIOutput; diff --git a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/prompt/PromptTemplateParser.java b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/prompt/PromptTemplateParser.java index 468658b5b..b0cb01b53 100644 --- a/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/prompt/PromptTemplateParser.java +++ b/liteflow-ai/liteflow-ai-core/src/main/java/com/yomahub/liteflow/ai/parse/prompt/PromptTemplateParser.java @@ -3,8 +3,8 @@ package com.yomahub.liteflow.ai.parse.prompt; import cn.hutool.core.util.StrUtil; import com.yomahub.liteflow.ai.annotation.InputField; import com.yomahub.liteflow.ai.exception.LiteFlowAIException; -import com.yomahub.liteflow.ai.parse.ProcessorContext; -import com.yomahub.liteflow.core.NodeComponent; +import com.yomahub.liteflow.ai.parse.context.ContextAccessor; +import com.yomahub.liteflow.ai.parse.context.ProcessorContext; import java.util.*; import java.util.function.Function; @@ -73,7 +73,7 @@ public class PromptTemplateParser { // 第一优先级:使用 InputField 中的 expression 映射 if (Objects.nonNull(field)) { // 使用表达式在上下文查找 - String value = searchContextByExpression(field.expression(), context); + String value = ContextAccessor.searchContextByExpression(field.expression(), context); if (StrUtil.isNotBlank(value)) { return value; } @@ -88,7 +88,7 @@ public class PromptTemplateParser { } // 第二优先级,占位符作为表达式在上下文中查找 - String value = searchContextByExpression(placeholder, context); + String value = ContextAccessor.searchContextByExpression(placeholder, context); if (StrUtil.isNotBlank(value)) { return value; } @@ -96,25 +96,4 @@ public class PromptTemplateParser { // 如果都没有找到,返回原始占位符 return placeholder; } - - /** - * 根据表达式在上下文中查找值 - * 这里可以根据实际需要实现更复杂的查找逻辑 - * - * @param expression 表达式 - * @param context 处理器上下文 - * @return 查找到的值 - */ - private static String searchContextByExpression(String expression, ProcessorContext context) { - if (StrUtil.isBlank(expression)) { - return null; - } - - try { - NodeComponent nodeComponent = context.getNodeComponent(); - return nodeComponent.getContextValue(expression); - } catch (Exception e) { - return null; - } - } } \ No newline at end of file diff --git a/liteflow-ai/liteflow-ai-ollama/src/main/java/com/yomahub/liteflow/ai/ollama/model/OllamaModelProvider.java b/liteflow-ai/liteflow-ai-ollama/src/main/java/com/yomahub/liteflow/ai/ollama/model/OllamaModelProvider.java index eb1c5ec94..988931156 100644 --- a/liteflow-ai/liteflow-ai-ollama/src/main/java/com/yomahub/liteflow/ai/ollama/model/OllamaModelProvider.java +++ b/liteflow-ai/liteflow-ai-ollama/src/main/java/com/yomahub/liteflow/ai/ollama/model/OllamaModelProvider.java @@ -50,6 +50,7 @@ public class OllamaModelProvider extends ModelProviderRegistrar { // TODO timeout 类型转换 // setIfPresent(ollamaChatModelBuilder::timeout, modelConfig.getTimeout()); setIfPresent(ollamaChatModelBuilder::maxRetries, modelConfig.getMaxRetries()); + // TODO 自定义请求头 return ollamaChatModelBuilder; }) // 结构化输出配置 @@ -82,6 +83,7 @@ public class OllamaModelProvider extends ModelProviderRegistrar { setIfPresent(ollamaStreamingChatModelBuilder::stop, modelConfig.getStop()); // TODO timeout 类型转换 // setIfPresent(ollamaStreamingChatModelBuilder::timeout, modelConfig.getTimeout()); + // TODO 自定义请求头 return ollamaStreamingChatModelBuilder; }) // 结构化输出配置