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;
})
// 结构化输出配置