mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-06-13 11:14:38 +08:00
Feat: 解析AIOutput,将大模型响应结果映射到上下文
This commit is contained in:
@@ -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<A extends Annotation, T extend
|
||||
wrapBean.setEntityClass(String.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将处理结果映射到上下文中
|
||||
*
|
||||
* @param context 处理器上下文
|
||||
* @param result 处理结果
|
||||
*/
|
||||
protected void mapOutput2Context(ProcessorContext<T> 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<A extends Annotation, T extends AIProxyWrap
|
||||
/**
|
||||
* 执行注解解析后处理
|
||||
*
|
||||
* @param annotation 待解析注解
|
||||
* @param context 处理器上下文
|
||||
* @param result 响应结果
|
||||
*/
|
||||
void postProcessAfterTrigger(A annotation, ProcessorContext<T> context, Object result);
|
||||
void postProcessAfterTrigger(ProcessorContext<T> context, Object result);
|
||||
}
|
||||
|
||||
@@ -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<AIChat,
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessAfterTrigger(AIChat annotation, ProcessorContext<ChatProxyWrapBean> context, Object result) {
|
||||
// TODO: 处理输出参数绑定
|
||||
public void postProcessAfterTrigger(ProcessorContext<ChatProxyWrapBean> context, Object result) {
|
||||
ChatProxyWrapBean wrapBean = context.getWrapBean();
|
||||
// 非流式输出,需要进行结构化处理
|
||||
if (!wrapBean.isStreaming()) {
|
||||
mapOutput2Context(context, result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<AIC
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessAfterTrigger(AIClassify annotation, ProcessorContext<ClassifyProxyWrapBean> context, Object result) {
|
||||
public void postProcessAfterTrigger(ProcessorContext<ClassifyProxyWrapBean> context, Object result) {
|
||||
mapOutput2Context(context, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<AI
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessAfterTrigger(AIRetrieval annotation, ProcessorContext<RetrievalProxyWrapBean> context, Object result) {
|
||||
public void postProcessAfterTrigger(ProcessorContext<RetrievalProxyWrapBean> context, Object result) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
})
|
||||
// 结构化输出配置
|
||||
|
||||
Reference in New Issue
Block a user