package cn.iocoder.yudao.module.ai.framework.ai.config;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactory;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.AiModelFactoryImpl;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.baichuan.BaiChuanChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.doubao.DouBaoChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.gemini.GeminiChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.grok.GrokChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.hunyuan.HunYuanChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.midjourney.api.MidjourneyApi;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowApiConstants;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.siliconflow.SiliconFlowChatModel;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.suno.api.SunoApi;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.model.xinghuo.XingHuoChatModel;
|
import com.openai.client.okhttp.OpenAIOkHttpClient;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.AiWebSearchClient;
|
import cn.iocoder.yudao.module.ai.framework.ai.core.webserch.bocha.AiBoChaWebSearchClient;
|
import cn.iocoder.yudao.module.ai.tool.method.PersonService;
|
import io.micrometer.observation.ObservationRegistry;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.ai.chat.model.ChatModel;
|
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
import org.springframework.ai.deepseek.DeepSeekChatOptions;
|
import org.springframework.ai.deepseek.api.DeepSeekApi;
|
import org.springframework.ai.embedding.BatchingStrategy;
|
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
|
import org.springframework.ai.model.tool.ToolCallingManager;
|
import org.springframework.ai.openai.OpenAiChatModel;
|
import org.springframework.ai.openai.OpenAiChatOptions;
|
import org.springframework.ai.support.ToolCallbacks;
|
import org.springframework.ai.tokenizer.JTokkitTokenCountEstimator;
|
import org.springframework.ai.tokenizer.TokenCountEstimator;
|
import org.springframework.ai.tool.ToolCallback;
|
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusServiceClientProperties;
|
import org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreProperties;
|
import org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreProperties;
|
import org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreProperties;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Configuration;
|
|
import java.util.List;
|
import java.util.Optional;
|
|
/**
|
* 芋道 AI 自动配置
|
*
|
* @author fansili
|
*/
|
@Configuration
|
@EnableConfigurationProperties({ YudaoAiProperties.class,
|
QdrantVectorStoreProperties.class, // 解析 Qdrant 配置
|
RedisVectorStoreProperties.class, // 解析 Redis 配置
|
MilvusVectorStoreProperties.class, MilvusServiceClientProperties.class // 解析 Milvus 配置
|
})
|
@Slf4j
|
public class AiAutoConfiguration {
|
|
@Bean
|
public AiModelFactory aiModelFactory() {
|
return new AiModelFactoryImpl();
|
}
|
|
@Bean
|
@ConditionalOnMissingBean
|
public ObservationRegistry observationRegistry() {
|
// 特殊:兜底有 ObservationRegistry Bean,避免相关的 ChatModel 创建报错。相关 issue:https://t.zsxq.com/CuPu4
|
return ObservationRegistry.NOOP;
|
}
|
|
// ========== 各种 AI Client 创建 ==========
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.gemini.enable", havingValue = "true")
|
public GeminiChatModel geminiChatModel(YudaoAiProperties yudaoAiProperties) {
|
YudaoAiProperties.Gemini properties = yudaoAiProperties.getGemini();
|
return buildGeminiChatClient(properties);
|
}
|
|
public GeminiChatModel buildGeminiChatClient(YudaoAiProperties.Gemini properties) {
|
if (StrUtil.isEmpty(properties.getModel())) {
|
properties.setModel(GeminiChatModel.MODEL_DEFAULT);
|
}
|
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
.openAiClient(OpenAIOkHttpClient.builder()
|
.baseUrl(GeminiChatModel.BASE_URL)
|
.apiKey(properties.getApiKey())
|
.build())
|
.options(OpenAiChatOptions.builder()
|
.model(properties.getModel())
|
.temperature(properties.getTemperature())
|
.maxTokens(properties.getMaxTokens())
|
.topP(properties.getTopP())
|
.build())
|
.toolCallingManager(getToolCallingManager())
|
.build();
|
return new GeminiChatModel(openAiChatModel);
|
}
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.doubao.enable", havingValue = "true")
|
public DouBaoChatModel douBaoChatClient(YudaoAiProperties yudaoAiProperties) {
|
YudaoAiProperties.DouBao properties = yudaoAiProperties.getDoubao();
|
return buildDouBaoChatClient(properties);
|
}
|
|
public DouBaoChatModel buildDouBaoChatClient(YudaoAiProperties.DouBao properties) {
|
if (StrUtil.isEmpty(properties.getModel())) {
|
properties.setModel(DouBaoChatModel.MODEL_DEFAULT);
|
}
|
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
.openAiClient(OpenAIOkHttpClient.builder()
|
.baseUrl(DouBaoChatModel.BASE_URL)
|
.apiKey(properties.getApiKey())
|
.build())
|
.options(OpenAiChatOptions.builder()
|
.model(properties.getModel())
|
.temperature(properties.getTemperature())
|
.maxTokens(properties.getMaxTokens())
|
.topP(properties.getTopP())
|
.build())
|
.toolCallingManager(getToolCallingManager())
|
.build();
|
return new DouBaoChatModel(openAiChatModel);
|
}
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.siliconflow.enable", havingValue = "true")
|
public SiliconFlowChatModel siliconFlowChatClient(YudaoAiProperties yudaoAiProperties) {
|
YudaoAiProperties.SiliconFlow properties = yudaoAiProperties.getSiliconflow();
|
return buildSiliconFlowChatClient(properties);
|
}
|
|
public SiliconFlowChatModel buildSiliconFlowChatClient(YudaoAiProperties.SiliconFlow properties) {
|
if (StrUtil.isEmpty(properties.getModel())) {
|
properties.setModel(SiliconFlowApiConstants.MODEL_DEFAULT);
|
}
|
DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
.deepSeekApi(DeepSeekApi.builder()
|
.baseUrl(SiliconFlowApiConstants.DEFAULT_BASE_URL)
|
.apiKey(properties.getApiKey())
|
.build())
|
.defaultOptions(DeepSeekChatOptions.builder()
|
.model(properties.getModel())
|
.temperature(properties.getTemperature())
|
.maxTokens(properties.getMaxTokens())
|
.topP(properties.getTopP())
|
.build())
|
.toolCallingManager(getToolCallingManager())
|
.build();
|
return new SiliconFlowChatModel(openAiChatModel);
|
}
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.hunyuan.enable", havingValue = "true")
|
public HunYuanChatModel hunYuanChatClient(YudaoAiProperties yudaoAiProperties) {
|
YudaoAiProperties.HunYuan properties = yudaoAiProperties.getHunyuan();
|
return buildHunYuanChatClient(properties);
|
}
|
|
public HunYuanChatModel buildHunYuanChatClient(YudaoAiProperties.HunYuan properties) {
|
if (StrUtil.isEmpty(properties.getModel())) {
|
properties.setModel(HunYuanChatModel.MODEL_DEFAULT);
|
}
|
// 特殊:由于混元大模型不提供 deepseek,而是通过知识引擎,所以需要区分下 URL
|
if (StrUtil.isEmpty(properties.getBaseUrl())) {
|
properties.setBaseUrl(
|
StrUtil.startWithIgnoreCase(properties.getModel(), "deepseek") ? HunYuanChatModel.DEEP_SEEK_BASE_URL
|
: HunYuanChatModel.BASE_URL);
|
}
|
// 创建 DeepSeekChatModel、HunYuanChatModel 对象
|
DeepSeekChatModel openAiChatModel = DeepSeekChatModel.builder()
|
.deepSeekApi(DeepSeekApi.builder()
|
.baseUrl(properties.getBaseUrl())
|
.completionsPath(HunYuanChatModel.COMPLETE_PATH)
|
.apiKey(properties.getApiKey())
|
.build())
|
.defaultOptions(DeepSeekChatOptions.builder()
|
.model(properties.getModel())
|
.temperature(properties.getTemperature())
|
.maxTokens(properties.getMaxTokens())
|
.topP(properties.getTopP())
|
.build())
|
.toolCallingManager(getToolCallingManager())
|
.build();
|
return new HunYuanChatModel(openAiChatModel);
|
}
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
|
public XingHuoChatModel xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
|
YudaoAiProperties.XingHuo properties = yudaoAiProperties.getXinghuo();
|
return buildXingHuoChatClient(properties);
|
}
|
|
public XingHuoChatModel buildXingHuoChatClient(YudaoAiProperties.XingHuo properties) {
|
if (StrUtil.isEmpty(properties.getModel())) {
|
properties.setModel(XingHuoChatModel.MODEL_DEFAULT);
|
}
|
OpenAIOkHttpClient.Builder builder = OpenAIOkHttpClient.builder()
|
.baseUrl(XingHuoChatModel.BASE_URL_V1)
|
.apiKey(properties.getAppKey() + ":" + properties.getSecretKey());
|
if ("x1".equals(properties.getModel())) {
|
builder.baseUrl(XingHuoChatModel.BASE_URL_V2);
|
}
|
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
.openAiClient(builder.build())
|
.options(OpenAiChatOptions.builder()
|
.model(properties.getModel())
|
.temperature(properties.getTemperature())
|
.maxTokens(properties.getMaxTokens())
|
.topP(properties.getTopP())
|
.build())
|
// TODO @芋艿:星火的 function call 有 bug,会报 ToolResponseMessage must have an id 错误!!!
|
.toolCallingManager(getToolCallingManager())
|
.build();
|
return new XingHuoChatModel(openAiChatModel);
|
}
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.baichuan.enable", havingValue = "true")
|
public BaiChuanChatModel baiChuanChatClient(YudaoAiProperties yudaoAiProperties) {
|
YudaoAiProperties.BaiChuan properties = yudaoAiProperties.getBaichuan();
|
return buildBaiChuanChatClient(properties);
|
}
|
|
public BaiChuanChatModel buildBaiChuanChatClient(YudaoAiProperties.BaiChuan properties) {
|
if (StrUtil.isEmpty(properties.getModel())) {
|
properties.setModel(BaiChuanChatModel.MODEL_DEFAULT);
|
}
|
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
.openAiClient(OpenAIOkHttpClient.builder()
|
.baseUrl(BaiChuanChatModel.BASE_URL)
|
.apiKey(properties.getApiKey())
|
.build())
|
.options(OpenAiChatOptions.builder()
|
.model(properties.getModel())
|
.temperature(properties.getTemperature())
|
.maxTokens(properties.getMaxTokens())
|
.topP(properties.getTopP())
|
.build())
|
.toolCallingManager(getToolCallingManager())
|
.build();
|
return new BaiChuanChatModel(openAiChatModel);
|
}
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.midjourney.enable", havingValue = "true")
|
public MidjourneyApi midjourneyApi(YudaoAiProperties yudaoAiProperties) {
|
YudaoAiProperties.Midjourney config = yudaoAiProperties.getMidjourney();
|
return new MidjourneyApi(config.getBaseUrl(), config.getApiKey(), config.getNotifyUrl());
|
}
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.suno.enable", havingValue = "true")
|
public SunoApi sunoApi(YudaoAiProperties yudaoAiProperties) {
|
return new SunoApi(yudaoAiProperties.getSuno().getBaseUrl());
|
}
|
|
public ChatModel buildGrokChatClient(YudaoAiProperties.Grok properties) {
|
if (StrUtil.isEmpty(properties.getModel())) {
|
properties.setModel(GrokChatModel.MODEL_DEFAULT);
|
}
|
OpenAiChatModel openAiChatModel = OpenAiChatModel.builder()
|
.openAiClient(OpenAIOkHttpClient.builder()
|
.baseUrl(Optional.ofNullable(properties.getBaseUrl())
|
.orElse(GrokChatModel.BASE_URL))
|
.apiKey(properties.getApiKey())
|
.build())
|
.options(OpenAiChatOptions.builder()
|
.model(properties.getModel())
|
.temperature(properties.getTemperature())
|
.maxTokens(properties.getMaxTokens())
|
.topP(properties.getTopP())
|
.build())
|
.toolCallingManager(getToolCallingManager())
|
.build();
|
return new DouBaoChatModel(openAiChatModel);
|
}
|
|
// ========== RAG 相关 ==========
|
|
@Bean
|
public TokenCountEstimator tokenCountEstimator() {
|
return new JTokkitTokenCountEstimator();
|
}
|
|
@Bean
|
public BatchingStrategy batchingStrategy() {
|
return new TokenCountBatchingStrategy();
|
}
|
|
private static ToolCallingManager getToolCallingManager() {
|
return SpringUtil.getBean(ToolCallingManager.class);
|
}
|
|
// ========== Web Search 相关 ==========
|
|
@Bean
|
@ConditionalOnProperty(value = "yudao.ai.web-search.enable", havingValue = "true")
|
public AiWebSearchClient webSearchClient(YudaoAiProperties yudaoAiProperties) {
|
return new AiBoChaWebSearchClient(yudaoAiProperties.getWebSearch().getApiKey());
|
}
|
|
// ========== MCP 相关 ==========
|
|
/**
|
* 参考自 <a href="https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html">MCP Server Boot Starter</>
|
*/
|
@Bean
|
public List<ToolCallback> toolCallbacks(PersonService personService) {
|
return List.of(ToolCallbacks.from(personService));
|
}
|
|
}
|