mirror of
https://gitee.com/dromara/liteFlow.git
synced 2026-06-10 11:17:00 +08:00
Feat: 添加格式化输出参数解析,引入 AiServiceFactory 帮助创建 AiService
This commit is contained in:
@@ -97,7 +97,7 @@ public @interface AIOutput {
|
||||
* <p>
|
||||
* 表示输出的 JSON Schema 定义。如果需要添加描述信息,请使用 LangChain4j 的相关注解
|
||||
*/
|
||||
Class<?> entityClass() default Object.class;
|
||||
Class<?> entityClass() default String.class;
|
||||
|
||||
/**
|
||||
* 如需启用,请设置 {@link AIOutput#responseType()} 为 {@link ResponseType#JSON}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package com.yomahub.liteflow.ai.context;
|
||||
|
||||
import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
import dev.langchain4j.rag.RetrievalAugmentor;
|
||||
import dev.langchain4j.rag.content.Content;
|
||||
import dev.langchain4j.service.TokenStream;
|
||||
import dev.langchain4j.service.tool.ToolExecution;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流式输出处理器
|
||||
@@ -11,9 +17,58 @@ import dev.langchain4j.model.chat.response.ChatResponse;
|
||||
|
||||
public interface StreamHandler {
|
||||
|
||||
/**
|
||||
* 当语言模型生成新的部分响应(通常是单个 token)时,将调用此方法。
|
||||
*
|
||||
* @param partialResponse 新生成的部分响应文本。
|
||||
*/
|
||||
void onPartialResponse(String partialResponse);
|
||||
|
||||
void onCompleteResponse(ChatResponse completeResponse);
|
||||
/**
|
||||
* 当使用 {@link RetrievalAugmentor} 检索到任何 {@link Content} 时,将调用此方法。
|
||||
* <p>
|
||||
* 此调用发生在与语言模型进行任何交互之前。
|
||||
*
|
||||
* @param retrievedContents 所有被检索到的内容列表。
|
||||
*/
|
||||
void onRetrieved(List<Content> retrievedContents);
|
||||
|
||||
/**
|
||||
* 当任何工具被执行后,将调用此方法。
|
||||
* <p>
|
||||
* 此调用发生在工具方法执行完成之后,下一个工具执行之前。
|
||||
*
|
||||
* @param toolExecution 包含已执行工具的名称、参数和结果的对象。
|
||||
*/
|
||||
void onToolExecuted(ToolExecution toolExecution);
|
||||
|
||||
/**
|
||||
* 当语言模型完成流式响应时,将调用此方法。
|
||||
*
|
||||
* @param chatResponse 完整的聊天响应结果。
|
||||
*/
|
||||
void onCompleteResponse(ChatResponse chatResponse);
|
||||
|
||||
/**
|
||||
* 当流式处理过程中发生错误时,将调用此方法。
|
||||
*
|
||||
* @param error 捕获到的异常或错误。
|
||||
*/
|
||||
void onError(Throwable error);
|
||||
|
||||
/**
|
||||
* 接受一个 {@link TokenStream} 对象,并注册流式处理的回调。
|
||||
* <p>
|
||||
* 自动开启 TokenStream 的处理流程,并在流式响应的各个阶段调用相应的方法。
|
||||
*
|
||||
* @param tokenStream 要处理的 {@link TokenStream} 对象。
|
||||
*/
|
||||
default void acceptTokenStream(TokenStream tokenStream) {
|
||||
tokenStream.onPartialResponse(this::onPartialResponse)
|
||||
.onRetrieved(this::onRetrieved)
|
||||
.onToolExecuted(this::onToolExecuted)
|
||||
.onCompleteResponse(this::onCompleteResponse)
|
||||
.onError(this::onError)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.yomahub.liteflow.ai.model;
|
||||
|
||||
import com.yomahub.liteflow.ai.domain.ModelConfig;
|
||||
import com.yomahub.liteflow.ai.exception.LiteFlowAIException;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.AIProxyWrapBean;
|
||||
import com.yomahub.liteflow.log.LFLog;
|
||||
import com.yomahub.liteflow.log.LFLoggerManager;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
@@ -27,9 +27,9 @@ public class ModelFactory {
|
||||
private static final Map<String, ModelProvider> MODEL_PROVIDER_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
// 模型类型缓存
|
||||
private static final Map<ModelConfig, ChatModel> CHAT_MODEL_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<ModelConfig, StreamingChatModel> STREAMING_CHAT_MODEL_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<ModelConfig, EmbeddingModel> EMBEDDING_MODEL_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<AIProxyWrapBean<?>, ChatModel> CHAT_MODEL_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<AIProxyWrapBean<?>, StreamingChatModel> STREAMING_CHAT_MODEL_CACHE = new ConcurrentHashMap<>();
|
||||
private static final Map<AIProxyWrapBean<?>, EmbeddingModel> EMBEDDING_MODEL_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
// 私有化构造函数
|
||||
private ModelFactory() {
|
||||
@@ -50,45 +50,48 @@ public class ModelFactory {
|
||||
/**
|
||||
* 获取指定提供者名称的ChatModel实例
|
||||
*
|
||||
* @param config 模型配置
|
||||
* @param wrapBean AI 节点包装 Bean,从中获取模型配置信息
|
||||
* @return ChatModel实例
|
||||
*/
|
||||
public static ChatModel getChatModel(ModelConfig config) {
|
||||
return CHAT_MODEL_CACHE.computeIfAbsent(config, key -> {
|
||||
ModelProvider provider = getProvider(key.getProvider());
|
||||
public static ChatModel getChatModel(AIProxyWrapBean<?> wrapBean) {
|
||||
return CHAT_MODEL_CACHE.computeIfAbsent(wrapBean, key -> {
|
||||
String providerName = key.getConfig().getProvider();
|
||||
ModelProvider provider = getProvider(providerName);
|
||||
// 创建 ChatModel 实例
|
||||
return provider.createChatModel(key)
|
||||
.orElseThrow(() -> new LiteFlowAIException("ChatModel is not supported for provider: " + key.getProvider()));
|
||||
.orElseThrow(() -> new LiteFlowAIException("ChatModel is not supported for provider: " + providerName));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定提供者名称的StreamingChatModel实例
|
||||
*
|
||||
* @param config 模型配置
|
||||
* @param wrapBean AI 节点包装 Bean,从中获取模型配置信息
|
||||
* @return StreamingChatModel实例
|
||||
*/
|
||||
public static StreamingChatModel getStreamingChatModel(ModelConfig config) {
|
||||
return STREAMING_CHAT_MODEL_CACHE.computeIfAbsent(config, key -> {
|
||||
ModelProvider provider = getProvider(config.getProvider());
|
||||
public static StreamingChatModel getStreamingChatModel(AIProxyWrapBean<?> wrapBean) {
|
||||
return STREAMING_CHAT_MODEL_CACHE.computeIfAbsent(wrapBean, key -> {
|
||||
String providerName = key.getConfig().getProvider();
|
||||
ModelProvider provider = getProvider(providerName);
|
||||
// 创建 StreamingChatModel 实例
|
||||
return provider.createStreamingChatModel(config)
|
||||
.orElseThrow(() -> new LiteFlowAIException("StreamingChatModel is not supported for provider: " + key.getProvider()));
|
||||
return provider.createStreamingChatModel(wrapBean)
|
||||
.orElseThrow(() -> new LiteFlowAIException("StreamingChatModel is not supported for provider: " + providerName));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定提供者名称的EmbeddingModel实例
|
||||
*
|
||||
* @param config 模型配置
|
||||
* @param wrapBean AI 节点包装 Bean,从中获取模型配置信息
|
||||
* @return EmbeddingModel实例
|
||||
*/
|
||||
public static EmbeddingModel getEmbeddingModel(ModelConfig config) {
|
||||
return EMBEDDING_MODEL_CACHE.computeIfAbsent(config, key -> {
|
||||
ModelProvider provider = getProvider(key.getProvider());
|
||||
public static EmbeddingModel getEmbeddingModel(AIProxyWrapBean<?> wrapBean) {
|
||||
return EMBEDDING_MODEL_CACHE.computeIfAbsent(wrapBean, key -> {
|
||||
String providerName = key.getConfig().getProvider();
|
||||
ModelProvider provider = getProvider(providerName);
|
||||
// 创建 EmbeddingModel 实例
|
||||
return provider.createEmbeddingModel(config)
|
||||
.orElseThrow(() -> new LiteFlowAIException("EmbeddingModel is not supported for provider: " + key.getProvider()));
|
||||
return provider.createEmbeddingModel(wrapBean)
|
||||
.orElseThrow(() -> new LiteFlowAIException("EmbeddingModel is not supported for provider: " + providerName));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.yomahub.liteflow.ai.model;
|
||||
|
||||
import com.yomahub.liteflow.ai.domain.ModelConfig;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.AIProxyWrapBean;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.model.embedding.EmbeddingModel;
|
||||
@@ -26,30 +26,30 @@ public interface ModelProvider {
|
||||
/**
|
||||
* 创建ChatModel实例
|
||||
*
|
||||
* @param config 模型配置
|
||||
* @param wrapBean AI 节点包装 Bean,从中获取模型配置信息
|
||||
* @return ChatModel实例
|
||||
*/
|
||||
default Optional<ChatModel> createChatModel(ModelConfig config) {
|
||||
default Optional<ChatModel> createChatModel(AIProxyWrapBean<?> wrapBean) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建StreamingChatModel实例
|
||||
*
|
||||
* @param config 模型配置
|
||||
* @param wrapBean AI 节点包装 Bean,从中获取模型配置信息
|
||||
* @return StreamingChatModel实例
|
||||
*/
|
||||
default Optional<StreamingChatModel> createStreamingChatModel(ModelConfig config) {
|
||||
default Optional<StreamingChatModel> createStreamingChatModel(AIProxyWrapBean<?> wrapBean) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建EmbeddingModel实例
|
||||
*
|
||||
* @param config 模型配置
|
||||
* @param wrapBean AI 节点包装 Bean,从中获取模型配置信息
|
||||
* @return EmbeddingModel实例
|
||||
*/
|
||||
default Optional<EmbeddingModel> createEmbeddingModel(ModelConfig config) {
|
||||
default Optional<EmbeddingModel> createEmbeddingModel(AIProxyWrapBean<?> wrapBean) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.yomahub.liteflow.ai.parse;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.prompt.PromptTemplateParser;
|
||||
import com.yomahub.liteflow.ai.parse.prompt.resource.PromptResource;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.AIProxyWrapBean;
|
||||
@@ -11,6 +13,7 @@ import com.yomahub.liteflow.log.LFLoggerManager;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -69,4 +72,26 @@ public abstract class AbstractAnnotationProcessor<A extends Annotation, T extend
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 {@link AIOutput} 的结构化输出参数
|
||||
*
|
||||
* @param context 处理器上下文
|
||||
*/
|
||||
protected void parseOutput(ProcessorContext<T> context) {
|
||||
AIOutput outputAnno = context.getAiOutputAnno();
|
||||
if (Objects.isNull(outputAnno)) return;
|
||||
|
||||
// 这里仅处理结构化输出相关的参数,其他参数交给 after 逻辑进行处理
|
||||
T wrapBean = context.getWrapBean();
|
||||
// 是否需要结构化输出
|
||||
if (Objects.equals(ResponseType.JSON, outputAnno.responseType())) {
|
||||
wrapBean.setResponseType(ResponseType.JSON);
|
||||
// 设置输出实体类
|
||||
wrapBean.setEntityClass(outputAnno.entityClass());
|
||||
} else {
|
||||
// 文本输出,设置 entityClass 为 String
|
||||
wrapBean.setEntityClass(String.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ public class ChatAnnotationProcessor extends AbstractAnnotationProcessor<AIChat,
|
||||
|
||||
// 处理用户提示词
|
||||
parsePrompt(annotation.userPrompt(), context, wrapBean::setUserPrompt);
|
||||
|
||||
// 处理结构化输出参数绑定
|
||||
parseOutput(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -32,11 +32,13 @@ public class ClassifyAnnotationProcessor extends AbstractAnnotationProcessor<AIC
|
||||
|
||||
// 处理用户提示词
|
||||
parsePrompt(annotation.userPrompt(), context, wrapBean::setUserPrompt);
|
||||
|
||||
// 处理结构化输出参数绑定
|
||||
parseOutput(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessAfterTrigger(AIClassify annotation, ProcessorContext<ClassifyProxyWrapBean> context, Object result) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
package com.yomahub.liteflow.ai.proxy.invocation;
|
||||
|
||||
import com.yomahub.liteflow.ai.context.ChatContext;
|
||||
import com.yomahub.liteflow.ai.exception.LiteFlowAIException;
|
||||
import com.yomahub.liteflow.ai.model.ModelFactory;
|
||||
import com.yomahub.liteflow.ai.parse.ProcessorContext;
|
||||
import com.yomahub.liteflow.ai.proxy.invocation.service.AiServiceFactory;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.ChatProxyWrapBean;
|
||||
import com.yomahub.liteflow.core.NodeComponent;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.service.TokenStream;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 聊天组件的调用处理器
|
||||
@@ -31,15 +38,35 @@ public class ChatAIInvocationHandler extends AbstractAIInvocationHandler<ChatPro
|
||||
}
|
||||
|
||||
private Void processStreaming(NodeComponent nodeComponent) {
|
||||
StreamingChatModel streamingChatModel = ModelFactory.getStreamingChatModel(wrapBean.getConfig());
|
||||
StreamingChatModel streamingChatModel = ModelFactory.getStreamingChatModel(wrapBean);
|
||||
// 创建AI服务实例
|
||||
Object aiService = AiServiceFactory.createAiService(wrapBean.getEntityClass(), streamingChatModel);
|
||||
|
||||
try {
|
||||
TokenStream tokenStream = AiServiceFactory.chatStream(aiService, wrapBean.getUserPrompt(), wrapBean.getSystemPrompt());
|
||||
// 处理流式响应
|
||||
ChatContext chatContext = nodeComponent.getContextBean(ChatContext.class);
|
||||
// 如果存在流处理器,则将TokenStream传递给它
|
||||
if (Objects.nonNull(chatContext)) {
|
||||
Optional.of(chatContext.getStreamHandler())
|
||||
.ifPresent(streamHandler -> streamHandler.acceptTokenStream(tokenStream));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
throw new LiteFlowAIException("Error during streaming chat processing", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Void processBlocking(NodeComponent nodeComponent) {
|
||||
ChatModel chatModel = ModelFactory.getChatModel(wrapBean.getConfig());
|
||||
private Object processBlocking(NodeComponent nodeComponent) {
|
||||
ChatModel chatModel = ModelFactory.getChatModel(wrapBean);
|
||||
|
||||
LOG.info("Processing chat request with model: {}", chatModel.getClass().getSimpleName());
|
||||
return null;
|
||||
// 创建AI服务实例
|
||||
Object aiService = AiServiceFactory.createAiService(wrapBean.getEntityClass(), chatModel);
|
||||
|
||||
try {
|
||||
return AiServiceFactory.chat(aiService, wrapBean.getUserPrompt(), wrapBean.getSystemPrompt());
|
||||
} catch (Throwable e) {
|
||||
throw new LiteFlowAIException("Error during blocking chat processing", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class ClassifyAIInvocationHandler extends AbstractAIInvocationHandler<Cla
|
||||
@Override
|
||||
protected Object doExecuteAIProcess(ProcessorContext<ClassifyProxyWrapBean> processorContext, Object[] args) {
|
||||
|
||||
ChatModel chatModel = ModelFactory.getChatModel(wrapBean.getConfig());
|
||||
ChatModel chatModel = ModelFactory.getChatModel(wrapBean);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
package com.yomahub.liteflow.ai.proxy.invocation.service;
|
||||
|
||||
import com.yomahub.liteflow.ai.util.SetUtil;
|
||||
import com.yomahub.liteflow.log.LFLog;
|
||||
import com.yomahub.liteflow.log.LFLoggerManager;
|
||||
import com.yomahub.liteflow.util.SerialsUtil;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.service.*;
|
||||
import net.bytebuddy.ByteBuddy;
|
||||
import net.bytebuddy.description.annotation.AnnotationDescription;
|
||||
import net.bytebuddy.description.modifier.Visibility;
|
||||
import net.bytebuddy.description.type.TypeDescription;
|
||||
import net.bytebuddy.dynamic.DynamicType;
|
||||
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* LangChain4j AI 服务的动态工厂类。
|
||||
* <p>
|
||||
* 这个工厂类帮助创建一个动态生成的接口,目的是将静态的泛型类型动态注入到接口方法中,从而复用LangChain4j的结构化输出的能力
|
||||
* <p>
|
||||
* <b>目标接口结构示例:</b>
|
||||
* <p>
|
||||
* 该工厂旨在创建如下结构的 {@code LiteFlowAIAssistant} 接口的实例:
|
||||
* <pre>{@code
|
||||
* public interface LiteFlowAIAssistant {
|
||||
*
|
||||
* // 以同步方式进行对话,并返回一个结构化的响应。
|
||||
* // @param userMessage 用户的输入消息。
|
||||
* // @param systemMessage 预设的系统级指令。
|
||||
* // @param <T> 响应结果的泛型类型。
|
||||
* // @return 包含模型响应结果的 Result 对象。
|
||||
* @SystemMessage("{{systemMessage}}")
|
||||
* @UserMessage("{{userMessage}}")
|
||||
* <T> Result<T> chat(@V("userMessage") String userMessage, @V("systemMessage") String systemMessage);
|
||||
*
|
||||
* // 以流式方式进行对话,逐步返回模型的响应。
|
||||
* // @param userMessage 用户的输入消息。
|
||||
* // @param systemMessage 预设的系统级指令。
|
||||
* // @return 一个 TokenStream 对象,用于处理流式响应。
|
||||
* @SystemMessage("{{systemMessage}}")
|
||||
* @UserMessage("{{userMessage}}")
|
||||
* TokenStream chatStream(@V("userMessage") String userMessage, @V("systemMessage") String systemMessage);
|
||||
*
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @author 苍镜月
|
||||
* @see dev.langchain4j.service.AiServices
|
||||
* @see dev.langchain4j.service.SystemMessage
|
||||
* @see dev.langchain4j.service.UserMessage
|
||||
* @since TODO
|
||||
*/
|
||||
|
||||
public class AiServiceFactory {
|
||||
|
||||
private static final LFLog LOG = LFLoggerManager.getLogger(AiServiceFactory.class);
|
||||
private static final String DYNAMIC_INTERFACE_PREFIX = "com.yomahub.liteflow.ai.proxy.invocation.service.DynamicLiteFlowAIAssistant_";
|
||||
|
||||
/**
|
||||
* 使用 {@link AiServices} 创建一个 AI 服务实例。
|
||||
*
|
||||
* @param dynamicResultType 动态指定的返回类型
|
||||
* @param chatModel chat 模型
|
||||
* @return 动态生成的 AI 服务实例
|
||||
*/
|
||||
public static Object createAiService(Class<?> dynamicResultType, ChatModel chatModel) {
|
||||
return doCreateAiService(dynamicResultType, chatModel, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 {@link AiServices} 创建一个 AI 服务实例。
|
||||
*
|
||||
* @param dynamicResultType 动态指定的返回类型
|
||||
* @param streamingChatModel 流式 chat 模型
|
||||
* @return 动态生成的 AI 服务实例
|
||||
*/
|
||||
public static Object createAiService(Class<?> dynamicResultType, StreamingChatModel streamingChatModel) {
|
||||
return doCreateAiService(dynamicResultType, null, streamingChatModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 {@link AiServices} 创建一个 AI 服务实例。
|
||||
*
|
||||
* @param dynamicResultType 动态指定的返回类型
|
||||
* @param chatModel chat 模型
|
||||
* @param streamingChatModel 流式 chat 模型
|
||||
* @return 动态生成的 AI 服务实例
|
||||
*/
|
||||
private static Object doCreateAiService(Class<?> dynamicResultType, ChatModel chatModel, StreamingChatModel streamingChatModel) {
|
||||
// 动态创建一个接口
|
||||
Class<?> dynamicInterface = createLiteFlowAIAssistant(dynamicResultType);
|
||||
LOG.info("successfully created dynamic interface for AiServices: {}", dynamicInterface.getName());
|
||||
|
||||
// 创建 AiService 实例
|
||||
AiServices<?> builder = AiServices.builder(dynamicInterface);
|
||||
SetUtil.setIfPresent(builder::chatModel, chatModel);
|
||||
SetUtil.setIfPresent(builder::streamingChatModel, streamingChatModel);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态创建一个接口,用于 LangChain4j 的 AI 服务
|
||||
* 使用动态代理是因为需要将静态的泛型类型动态注入
|
||||
*
|
||||
* @param dynamicResultType 动态指定的 chat 方法返回结果类型,例如 String.class
|
||||
* @return 动态生成的接口 Class 对象
|
||||
*/
|
||||
private static Class<?> createLiteFlowAIAssistant(Class<?> dynamicResultType) {
|
||||
// 获取动态指定的泛型类型
|
||||
TypeDescription.Generic genericResultType = TypeDescription.Generic.Builder
|
||||
.parameterizedType(Result.class, dynamicResultType)
|
||||
.build();
|
||||
|
||||
DynamicType.Builder<?> builder = new ByteBuddy()
|
||||
.makeInterface()
|
||||
.name(getDynamicInterfaceName(dynamicResultType));
|
||||
|
||||
// 定义 chat 方法
|
||||
builder = defineChatMethod(builder, genericResultType);
|
||||
// 定义 chatStream 方法
|
||||
builder = defineChatStreamMethod(builder);
|
||||
|
||||
DynamicType.Unloaded<?> dynamicType = builder.make();
|
||||
|
||||
return dynamicType
|
||||
.load(AiServiceFactory.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
|
||||
.getLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态生成的接口名称
|
||||
*
|
||||
* @param dynamicResultType 动态指定的返回类型
|
||||
* @return 动态生成的接口名称
|
||||
*/
|
||||
private static String getDynamicInterfaceName(Class<?> dynamicResultType) {
|
||||
return DYNAMIC_INTERFACE_PREFIX +
|
||||
dynamicResultType.getSimpleName() +
|
||||
"_" + SerialsUtil.generateShortUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义 chat 方法
|
||||
*/
|
||||
private static DynamicType.Builder<?> defineChatMethod(DynamicType.Builder<?> builder, TypeDescription.Generic genericResultType) {
|
||||
return builder.defineMethod("chat", genericResultType, Visibility.PUBLIC)
|
||||
.withParameter(String.class, "userMessage")
|
||||
.annotateParameter(AnnotationDescription.Builder.ofType(V.class).define("value", "userMessage").build())
|
||||
.withParameter(String.class, "systemMessage")
|
||||
.annotateParameter(AnnotationDescription.Builder.ofType(V.class).define("value", "systemMessage").build())
|
||||
.withoutCode()
|
||||
.annotateMethod(
|
||||
AnnotationDescription.Builder.ofType(SystemMessage.class).defineArray("value", "{{systemMessage}}").build(),
|
||||
AnnotationDescription.Builder.ofType(UserMessage.class).defineArray("value", "{{userMessage}}").build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定义 chatStream 方法
|
||||
*/
|
||||
private static DynamicType.Builder<?> defineChatStreamMethod(DynamicType.Builder<?> builder) {
|
||||
return builder.defineMethod("chatStream", TokenStream.class, Visibility.PUBLIC)
|
||||
.withParameter(String.class, "userMessage")
|
||||
.annotateParameter(AnnotationDescription.Builder.ofType(V.class).define("value", "userMessage").build())
|
||||
.withParameter(String.class, "systemMessage")
|
||||
.annotateParameter(AnnotationDescription.Builder.ofType(V.class).define("value", "systemMessage").build())
|
||||
.withoutCode()
|
||||
.annotateMethod(
|
||||
AnnotationDescription.Builder.ofType(SystemMessage.class).defineArray("value", "{{systemMessage}}").build(),
|
||||
AnnotationDescription.Builder.ofType(UserMessage.class).defineArray("value", "{{userMessage}}").build()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 chat 方法
|
||||
*
|
||||
* @param aiService AI 服务实例
|
||||
* @param userMessage 用户消息
|
||||
* @param systemMessage 系统消息
|
||||
* @return 调用结果
|
||||
*/
|
||||
public static Object chat(Object aiService, String userMessage, String systemMessage) throws Throwable {
|
||||
Method chatMethod = aiService.getClass().getMethod("chat", String.class, String.class);
|
||||
return chatMethod.invoke(aiService, userMessage, systemMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 chatStream 方法
|
||||
*
|
||||
* @param aiService AI 服务实例
|
||||
* @param userMessage 用户消息
|
||||
* @param systemMessage 系统消息
|
||||
* @return TokenStream 实例
|
||||
*/
|
||||
public static TokenStream chatStream(Object aiService, String userMessage, String systemMessage) throws Throwable {
|
||||
Method chatStreamMethod = aiService.getClass().getMethod("chatStream", String.class, String.class);
|
||||
return (TokenStream) chatStreamMethod.invoke(aiService, userMessage, systemMessage);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package com.yomahub.liteflow.ai.proxy.wrap;
|
||||
|
||||
import com.yomahub.liteflow.ai.annotation.AIComponent;
|
||||
import com.yomahub.liteflow.ai.domain.ModelConfig;
|
||||
import com.yomahub.liteflow.ai.domain.enums.ResponseType;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* AI节点包装 Bean
|
||||
@@ -26,6 +28,14 @@ public abstract class AIProxyWrapBean<T extends Annotation> {
|
||||
|
||||
protected String beanName;
|
||||
|
||||
protected String systemPrompt;
|
||||
|
||||
protected String userPrompt;
|
||||
|
||||
protected ResponseType responseType = ResponseType.TEXT;
|
||||
|
||||
protected Class<?> entityClass = String.class;
|
||||
|
||||
public AIProxyWrapBean() {
|
||||
}
|
||||
|
||||
@@ -61,6 +71,22 @@ public abstract class AIProxyWrapBean<T extends Annotation> {
|
||||
return beanName;
|
||||
}
|
||||
|
||||
public String getSystemPrompt() {
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
public String getUserPrompt() {
|
||||
return userPrompt;
|
||||
}
|
||||
|
||||
public ResponseType getResponseType() {
|
||||
return responseType;
|
||||
}
|
||||
|
||||
public Class<?> getEntityClass() {
|
||||
return entityClass;
|
||||
}
|
||||
|
||||
public void setNodeId(String nodeId) {
|
||||
this.nodeId = nodeId;
|
||||
}
|
||||
@@ -80,4 +106,52 @@ public abstract class AIProxyWrapBean<T extends Annotation> {
|
||||
public void setBeanName(String beanName) {
|
||||
this.beanName = beanName;
|
||||
}
|
||||
|
||||
public void setSystemPrompt(String systemPrompt) {
|
||||
this.systemPrompt = systemPrompt;
|
||||
}
|
||||
|
||||
public void setUserPrompt(String userPrompt) {
|
||||
this.userPrompt = userPrompt;
|
||||
}
|
||||
|
||||
public void setResponseType(ResponseType responseType) {
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
public void setEntityClass(Class<?> entityClass) {
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
AIProxyWrapBean<?> that = (AIProxyWrapBean<?>) o;
|
||||
return Objects.equals(getAnnotation(), that.getAnnotation()) &&
|
||||
Objects.equals(getConfig(), that.getConfig()) &&
|
||||
Objects.equals(getNodeId(), that.getNodeId()) &&
|
||||
Objects.equals(getNodeName(), that.getNodeName()) &&
|
||||
Objects.equals(getInterfaceClass(), that.getInterfaceClass()) &&
|
||||
Objects.equals(getBeanName(), that.getBeanName()) &&
|
||||
Objects.equals(getSystemPrompt(), that.getSystemPrompt()) &&
|
||||
Objects.equals(getUserPrompt(), that.getUserPrompt()) &&
|
||||
getResponseType() == that.getResponseType() &&
|
||||
Objects.equals(getEntityClass(), that.getEntityClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
getAnnotation(),
|
||||
getConfig(),
|
||||
getNodeId(),
|
||||
getNodeName(),
|
||||
getInterfaceClass(),
|
||||
getBeanName(),
|
||||
getSystemPrompt(),
|
||||
getUserPrompt(),
|
||||
getResponseType(),
|
||||
getEntityClass()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,6 @@ import com.yomahub.liteflow.ai.annotation.AIComponent;
|
||||
|
||||
public class ChatProxyWrapBean extends AIProxyWrapBean<AIChat> {
|
||||
|
||||
private String systemPrompt;
|
||||
|
||||
private String userPrompt;
|
||||
|
||||
private boolean streaming;
|
||||
|
||||
public ChatProxyWrapBean() {
|
||||
@@ -28,26 +24,10 @@ public class ChatProxyWrapBean extends AIProxyWrapBean<AIChat> {
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
public String getSystemPrompt() {
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
public String getUserPrompt() {
|
||||
return userPrompt;
|
||||
}
|
||||
|
||||
public boolean isStreaming() {
|
||||
return streaming;
|
||||
}
|
||||
|
||||
public void setSystemPrompt(String systemPrompt) {
|
||||
this.systemPrompt = systemPrompt;
|
||||
}
|
||||
|
||||
public void setUserPrompt(String userPrompt) {
|
||||
this.userPrompt = userPrompt;
|
||||
}
|
||||
|
||||
public void setStreaming(boolean streaming) {
|
||||
this.streaming = streaming;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,6 @@ import java.util.List;
|
||||
|
||||
public class ClassifyProxyWrapBean extends AIProxyWrapBean<AIClassify> {
|
||||
|
||||
|
||||
private String systemPrompt;
|
||||
|
||||
private String userPrompt;
|
||||
|
||||
private List<String> categories;
|
||||
|
||||
private boolean multiLabel;
|
||||
@@ -33,14 +28,6 @@ public class ClassifyProxyWrapBean extends AIProxyWrapBean<AIClassify> {
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
public String getSystemPrompt() {
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
public String getUserPrompt() {
|
||||
return userPrompt;
|
||||
}
|
||||
|
||||
public List<String> getCategories() {
|
||||
return categories;
|
||||
}
|
||||
@@ -49,14 +36,6 @@ public class ClassifyProxyWrapBean extends AIProxyWrapBean<AIClassify> {
|
||||
return multiLabel;
|
||||
}
|
||||
|
||||
public void setSystemPrompt(String systemPrompt) {
|
||||
this.systemPrompt = systemPrompt;
|
||||
}
|
||||
|
||||
public void setUserPrompt(String userPrompt) {
|
||||
this.userPrompt = userPrompt;
|
||||
}
|
||||
|
||||
public void setCategories(List<String> categories) {
|
||||
this.categories = categories;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.yomahub.liteflow.ai.ollama.model;
|
||||
import com.yomahub.liteflow.ai.domain.ModelConfig;
|
||||
import com.yomahub.liteflow.ai.domain.constant.ProviderName;
|
||||
import com.yomahub.liteflow.ai.model.ModelProviderRegistrar;
|
||||
import com.yomahub.liteflow.ai.proxy.wrap.AIProxyWrapBean;
|
||||
import dev.langchain4j.model.chat.ChatModel;
|
||||
import dev.langchain4j.model.chat.StreamingChatModel;
|
||||
import dev.langchain4j.model.ollama.OllamaChatModel;
|
||||
@@ -25,24 +26,26 @@ public class OllamaModelProvider extends ModelProviderRegistrar {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ChatModel> createChatModel(ModelConfig config) {
|
||||
// TODO
|
||||
return Optional.of(
|
||||
OllamaChatModel.builder()
|
||||
.baseUrl(config.getBaseUrl())
|
||||
.modelName(config.getModel())
|
||||
public Optional<ChatModel> createChatModel(AIProxyWrapBean<?> wrapBean) {
|
||||
// 设置模型配置和结构化输出
|
||||
ModelConfig modelConfig = wrapBean.getConfig();
|
||||
return Optional.of(OllamaChatModel
|
||||
.builder()
|
||||
.baseUrl(modelConfig.getBaseUrl())
|
||||
.modelName(modelConfig.getModel())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<StreamingChatModel> createStreamingChatModel(ModelConfig config) {
|
||||
// TODO
|
||||
return Optional.of(
|
||||
OllamaStreamingChatModel.builder()
|
||||
.baseUrl(config.getBaseUrl())
|
||||
.modelName(config.getModel())
|
||||
.build()
|
||||
public Optional<StreamingChatModel> createStreamingChatModel(AIProxyWrapBean<?> wrapBean) {
|
||||
// 设置模型配置和结构化输出
|
||||
ModelConfig modelConfig = wrapBean.getConfig();
|
||||
return Optional.of(OllamaStreamingChatModel
|
||||
.builder()
|
||||
.baseUrl(modelConfig.getBaseUrl())
|
||||
.modelName(modelConfig.getModel())
|
||||
.build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package com.yomahub.liteflow.test.cmp;
|
||||
|
||||
import com.yomahub.liteflow.ai.annotation.AIChat;
|
||||
import com.yomahub.liteflow.ai.annotation.AIComponent;
|
||||
import com.yomahub.liteflow.ai.annotation.AIInput;
|
||||
import com.yomahub.liteflow.ai.annotation.InputField;
|
||||
import com.yomahub.liteflow.ai.annotation.*;
|
||||
import com.yomahub.liteflow.ai.domain.enums.ResponseType;
|
||||
|
||||
/**
|
||||
* TODO
|
||||
@@ -29,5 +27,12 @@ import com.yomahub.liteflow.ai.annotation.InputField;
|
||||
@InputField(name = "answer", expression = "test", defaultValue = "The sky appears blue due to the scattering of sunlight by the atmosphere.")
|
||||
}
|
||||
)
|
||||
@AIOutput(
|
||||
responseType = ResponseType.JSON,
|
||||
entityClass = Integer.class,
|
||||
methodExpress = "setData",
|
||||
useKeyIndex = true,
|
||||
key = "result"
|
||||
)
|
||||
public interface AICmp {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user