diff --git a/pom.xml b/pom.xml
index 5facaa2..fe786b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -19,6 +19,7 @@
xianyu-core
xianyu-server
xianyu-goofish
+ xianyu-ai
0.0.1-SNAPSHOT
@@ -117,9 +118,30 @@
${jjwt.version}
+
+
+
+
+ spring-milestones
+ Spring Milestones
+ https://repo.spring.io/milestone
+
+ false
+
+
+
+ spring-snapshots
+ Spring Snapshots
+ https://repo.spring.io/snapshot
+
+ false
+
+
+
+
diff --git a/xianyu-ai/pom.xml b/xianyu-ai/pom.xml
new file mode 100644
index 0000000..83b62da
--- /dev/null
+++ b/xianyu-ai/pom.xml
@@ -0,0 +1,39 @@
+
+
+ 4.0.0
+
+ top.biwin
+ xianyu-freedom
+ ${revision}
+
+ xianyu-ai
+
+
+
+ top.biwin
+ xianyu-common
+ ${revision}
+
+
+ org.springframework.ai
+ spring-ai-openai-spring-boot-starter
+ 0.8.1
+
+
+
+ org.projectlombok
+ lombok
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ cn.hutool
+ hutool-all
+
+
+
+
diff --git a/xianyu-ai/src/main/java/top/biwin/xianyu/ai/config/AiProperties.java b/xianyu-ai/src/main/java/top/biwin/xianyu/ai/config/AiProperties.java
new file mode 100644
index 0000000..6355906
--- /dev/null
+++ b/xianyu-ai/src/main/java/top/biwin/xianyu/ai/config/AiProperties.java
@@ -0,0 +1,57 @@
+package top.biwin.xianyu.ai.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * AI 模块配置属性
+ *
+ * @author Little Code Sauce
+ * @since 2025
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "spring.ai.xianyu")
+public class AiProperties {
+
+ /**
+ * 默认使用的提供商名称
+ */
+ private String defaultProvider = "openai";
+
+ /**
+ * 提供商配置映射
+ * key: 提供商名称 (e.g., openai, deepseek, qwen)
+ */
+ private Map providers = new HashMap<>();
+
+ /**
+ * 单个提供商的配置
+ */
+ @Data
+ public static class ProviderConfig {
+ /**
+ * API Base URL
+ */
+ private String baseUrl;
+
+ /**
+ * API Key
+ */
+ private String apiKey;
+
+ /**
+ * 模型名称
+ */
+ private String model;
+
+ /**
+ * 温度 (0.0 - 1.0)
+ */
+ private Double temperature = 0.7;
+ }
+}
diff --git a/xianyu-ai/src/main/java/top/biwin/xianyu/ai/controller/AiController.java b/xianyu-ai/src/main/java/top/biwin/xianyu/ai/controller/AiController.java
new file mode 100644
index 0000000..77ed338
--- /dev/null
+++ b/xianyu-ai/src/main/java/top/biwin/xianyu/ai/controller/AiController.java
@@ -0,0 +1,53 @@
+package top.biwin.xianyu.ai.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import top.biwin.xianyu.ai.service.AiService;
+import top.biwin.xinayu.common.dto.response.BaseResponse;
+
+/**
+ * AI 交互控制器
+ *
+ * @author Little Code Sauce
+ * @since 2025
+ */
+@RestController
+@RequestMapping("/ai")
+@RequiredArgsConstructor
+public class AiController {
+
+ private final AiService aiService;
+
+ /**
+ * 与 AI 对话
+ *
+ * @param prompt 提示词
+ * @param provider 提供商(可选,如 openai, deepseek, qwen)
+ * @return AI 回复
+ */
+ @PostMapping("/chat")
+ public ResponseEntity chat(@RequestParam("prompt") String prompt,
+ @RequestParam(value = "provider", required = false) String provider) {
+ if (!StringUtils.hasText(prompt)) {
+ return ResponseEntity.ok(BaseResponse.builder()
+ .success(false)
+ .message("提示词不能为空")
+ .build());
+ }
+
+ String responseContent = aiService.chat(prompt, provider);
+
+ BaseResponse response = BaseResponse.builder()
+ .success(true)
+ .message("操作成功")
+ .data(responseContent)
+ .build();
+
+ return ResponseEntity.ok(response);
+ }
+}
diff --git a/xianyu-ai/src/main/java/top/biwin/xianyu/ai/service/AiService.java b/xianyu-ai/src/main/java/top/biwin/xianyu/ai/service/AiService.java
new file mode 100644
index 0000000..42981a7
--- /dev/null
+++ b/xianyu-ai/src/main/java/top/biwin/xianyu/ai/service/AiService.java
@@ -0,0 +1,22 @@
+package top.biwin.xianyu.ai.service;
+
+/**
+ * AI 服务接口
+ *
+ * 提供与 AI 模型交互的核心能力
+ *
+ *
+ * @author Little Code Sauce
+ * @since 2025
+ */
+public interface AiService {
+
+ /**
+ * 发送提示词并获取 AI 回复
+ *
+ * @param prompt 用户输入的提示词
+ * @param provider 指定的 AI 提供商(如果为 null,则使用默认配置)
+ * @return AI 的文本回复
+ */
+ String chat(String prompt, String provider);
+}
diff --git a/xianyu-ai/src/main/java/top/biwin/xianyu/ai/service/impl/AiServiceImpl.java b/xianyu-ai/src/main/java/top/biwin/xianyu/ai/service/impl/AiServiceImpl.java
new file mode 100644
index 0000000..0d1fee9
--- /dev/null
+++ b/xianyu-ai/src/main/java/top/biwin/xianyu/ai/service/impl/AiServiceImpl.java
@@ -0,0 +1,99 @@
+package top.biwin.xianyu.ai.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.ai.chat.ChatClient;
+import org.springframework.ai.chat.prompt.Prompt;
+import org.springframework.ai.openai.OpenAiChatClient;
+import org.springframework.ai.openai.OpenAiChatOptions;
+import org.springframework.ai.openai.api.OpenAiApi;
+import org.springframework.stereotype.Service;
+import top.biwin.xianyu.ai.config.AiProperties;
+import top.biwin.xianyu.ai.service.AiService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * AI 服务实现类
+ *
+ * 支持多厂商动态切换
+ *
+ *
+ * @author Little Code Sauce
+ * @since 2025
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AiServiceImpl implements AiService {
+
+ private final AiProperties aiProperties;
+
+ // 本地缓存 ChatClient,避免重复创建 overhead
+ private final Map clientCache = new ConcurrentHashMap<>();
+
+ @Override
+ public String chat(String promptText, String provider) {
+ // 1. 确定使用的 provider
+ String targetProvider = StrUtil.isBlank(provider) ? aiProperties.getDefaultProvider() : provider;
+ log.info("Processing AI request with provider: {}", targetProvider);
+
+ // 2. 获取或创建 Client
+ ChatClient chatClient = getClient(targetProvider);
+ if (chatClient == null) {
+ return "Configuration for provider '" + targetProvider + "' not found. Please check your application.yml 🐶";
+ }
+
+ // 3. 执行调用
+ try {
+ // 注意:0.8.1 版本中 OpenAiChatClient.call(String) 返回 String
+ return chatClient.call(promptText);
+ } catch (Exception e) {
+ log.error("AI call failed for provider: {}", targetProvider, e);
+ return "AI request failed: " + e.getMessage();
+ }
+ }
+
+ /**
+ * 获取或懒加载创建 ChatClient
+ */
+ private ChatClient getClient(String providerName) {
+ if (clientCache.containsKey(providerName)) {
+ return clientCache.get(providerName);
+ }
+
+ AiProperties.ProviderConfig config = aiProperties.getProviders().get(providerName);
+ if (config == null) {
+ log.warn("No configuration found for provider: {}", providerName);
+ return null;
+ }
+
+ log.info("Creating new ChatClient for provider: {} (URL: {}, Model: {})",
+ providerName, config.getBaseUrl(), config.getModel());
+
+ try {
+ // 构造 OpenAiApi
+ // 0.8.1 API: new OpenAiApi(baseUrl, apiKey)
+ OpenAiApi openAiApi = new OpenAiApi(config.getBaseUrl(), config.getApiKey());
+
+ // 构造 Options
+ OpenAiChatOptions options = OpenAiChatOptions.builder()
+ .withModel(config.getModel())
+ .withTemperature(config.getTemperature().floatValue())
+ .build();
+
+ // 构造 Client
+ // 0.8.1 API: new OpenAiChatClient(api, options)
+ OpenAiChatClient client = new OpenAiChatClient(openAiApi, options);
+
+ clientCache.put(providerName, client);
+ return client;
+ } catch (Exception e) {
+ log.error("Failed to create ChatClient for provider: {}", providerName, e);
+ return null;
+ }
+ }
+}
diff --git a/xianyu-server/pom.xml b/xianyu-server/pom.xml
index 95c3a69..01bc567 100644
--- a/xianyu-server/pom.xml
+++ b/xianyu-server/pom.xml
@@ -27,6 +27,11 @@
xianyu-goofish
${revision}
+
+ top.biwin
+ xianyu-ai
+ ${revision}
+
org.springframework.boot
spring-boot-starter-web
diff --git a/xianyu-server/src/main/java/top/biwin/xianyu/server/XianyuFreedomApplication.java b/xianyu-server/src/main/java/top/biwin/xianyu/server/XianyuFreedomApplication.java
index a39ff46..e22b35c 100644
--- a/xianyu-server/src/main/java/top/biwin/xianyu/server/XianyuFreedomApplication.java
+++ b/xianyu-server/src/main/java/top/biwin/xianyu/server/XianyuFreedomApplication.java
@@ -15,7 +15,7 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
* @author wangli
* @since 2026-01-20 23:51
*/
-@SpringBootApplication(scanBasePackages = "top.biwin")
+@SpringBootApplication(scanBasePackages = "top.biwin", exclude = {org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration.class})
@EntityScan(basePackages = "top.biwin.xianyu.core.entity")
@EnableJpaRepositories(basePackages = "top.biwin.xianyu.core.repository")
//@EnableConfigurationProperties
diff --git a/xianyu-server/src/main/resources/application.yml b/xianyu-server/src/main/resources/application.yml
index d463285..4221de1 100644
--- a/xianyu-server/src/main/resources/application.yml
+++ b/xianyu-server/src/main/resources/application.yml
@@ -12,6 +12,58 @@ spring:
application:
name: xianyu-freedom
+ # Spring AI Multi-Provider Configuration 🐶
+ ai:
+ xianyu:
+ default-provider: qwen # 默认使用 openai
+ providers:
+ # 1. OpenAI (官方)
+ openai:
+ base-url: ${OPENAI_BASE_URL:https://api.openai.com/v1}
+ api-key: ${OPENAI_API_KEY:sk-placeholder}
+ model: gpt-3.5-turbo
+ temperature: 0.7
+
+ # 2. DeepSeek (深度求索) - OpenAI Compatible
+ deepseek:
+ base-url: ${DEEPSEEK_BASE_URL:https://api.deepseek.com/v1}
+ api-key: ${DEEPSEEK_API_KEY:sk-placeholder}
+ model: deepseek-chat
+ temperature: 0.7
+
+ # 3. QianWen (通义千问) - OpenAI Compatible (or via DashScope)
+ qwen:
+ base-url: ${QWEN_BASE_URL:https://dashscope.aliyuncs.com/compatible-mode}
+ api-key: ${QWEN_API_KEY:sk-9e676358ac5e4c4cab54c953f59c28dc}
+ model: qwen-plus
+ temperature: 0.8
+
+ # 4. Moonshot (Kimi)
+ moonshot:
+ base-url: ${MOONSHOT_BASE_URL:https://api.moonshot.cn/v1}
+ api-key: ${MOONSHOT_API_KEY:sk-placeholder}
+ model: moonshot-v1-8k
+
+ # 5. Grok (xAI)
+ grok:
+ base-url: ${GROK_BASE_URL:https://api.x.ai/v1}
+ api-key: ${GROK_API_KEY:sk-placeholder}
+ model: grok-beta
+
+ # 6. Doubao (Bytedance) - Via Ark
+ doubao:
+ base-url: ${DOUBAO_BASE_URL:https://ark.cn-beijing.volces.com/api/v3}
+ api-key: ${DOUBAO_API_KEY:sk-placeholder}
+ model: ${DOUBAO_MODEL:ep-20240604015538-2q2h9} # Doubao models need specific Endpoint IDs
+
+ # 7. Gemini (Google) - Via OpenAI Compatible Endpoint (Proxy) recommended for 0.8.1
+ # Note: Direct Gemini requires VertexAI or Gemini specific starter, keeping it consistent here via proxy if available
+ # Or you can define it here and we might use a different logic in future, but for now assuming OpenAI compat layer
+ gemini:
+ base-url: ${GEMINI_BASE_URL:https://generativelanguage.googleapis.com/v1beta/openai/}
+ api-key: ${GEMINI_API_KEY:placeholder}
+ model: gemini-1.5-flash
+
datasource:
driver-class-name: org.sqlite.JDBC
url: jdbc:sqlite:db/xianyu_data.db
@@ -26,7 +78,7 @@ spring:
database-platform: org.hibernate.community.dialect.SQLiteDialect
hibernate:
ddl-auto: ${app.ddl-auto:update}
- show-sql: true # Set to false to disable SQL logging
+ show-sql: false # Set to false to disable SQL logging
open-in-view: false # 生产环境最佳实践,避免懒加载问题
properties:
hibernate: