Feat: 添加格式化输出参数解析,引入 AiServiceFactory 帮助创建 AiService

This commit is contained in:
LuanY77
2025-07-31 11:55:47 +08:00
parent 2d657d1363
commit 5cb64025bd
15 changed files with 453 additions and 95 deletions

View File

@@ -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}

View File

@@ -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();
}
}

View File

@@ -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));
});
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -27,6 +27,9 @@ public class ChatAnnotationProcessor extends AbstractAnnotationProcessor<AIChat,
// 处理用户提示词
parsePrompt(annotation.userPrompt(), context, wrapBean::setUserPrompt);
// 处理结构化输出参数绑定
parseOutput(context);
}
@Override

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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()
);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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()
);
}
}

View File

@@ -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 {
}