init
This commit is contained in:
parent
ec792f0c44
commit
ad500f84af
22
pom.xml
22
pom.xml
@ -19,6 +19,7 @@
|
||||
<module>xianyu-core</module>
|
||||
<module>xianyu-server</module>
|
||||
<module>xianyu-goofish</module>
|
||||
<module>xianyu-ai</module>
|
||||
</modules>
|
||||
<properties>
|
||||
<revision>0.0.1-SNAPSHOT</revision>
|
||||
@ -117,9 +118,30 @@
|
||||
<version>${jjwt.version}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestones</id>
|
||||
<name>Spring Milestones</name>
|
||||
<url>https://repo.spring.io/milestone</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>spring-snapshots</id>
|
||||
<name>Spring Snapshots</name>
|
||||
<url>https://repo.spring.io/snapshot</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
|
||||
39
xianyu-ai/pom.xml
Normal file
39
xianyu-ai/pom.xml
Normal file
@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>top.biwin</groupId>
|
||||
<artifactId>xianyu-freedom</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<artifactId>xianyu-ai</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>top.biwin</groupId>
|
||||
<artifactId>xianyu-common</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ai</groupId>
|
||||
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
|
||||
<version>0.8.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@ -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<String, ProviderConfig> 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;
|
||||
}
|
||||
}
|
||||
@ -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<BaseResponse> 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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package top.biwin.xianyu.ai.service;
|
||||
|
||||
/**
|
||||
* AI 服务接口
|
||||
* <p>
|
||||
* 提供与 AI 模型交互的核心能力
|
||||
* </p>
|
||||
*
|
||||
* @author Little Code Sauce
|
||||
* @since 2025
|
||||
*/
|
||||
public interface AiService {
|
||||
|
||||
/**
|
||||
* 发送提示词并获取 AI 回复
|
||||
*
|
||||
* @param prompt 用户输入的提示词
|
||||
* @param provider 指定的 AI 提供商(如果为 null,则使用默认配置)
|
||||
* @return AI 的文本回复
|
||||
*/
|
||||
String chat(String prompt, String provider);
|
||||
}
|
||||
@ -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 服务实现类
|
||||
* <p>
|
||||
* 支持多厂商动态切换
|
||||
* </p>
|
||||
*
|
||||
* @author Little Code Sauce
|
||||
* @since 2025
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AiServiceImpl implements AiService {
|
||||
|
||||
private final AiProperties aiProperties;
|
||||
|
||||
// 本地缓存 ChatClient,避免重复创建 overhead
|
||||
private final Map<String, ChatClient> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,11 @@
|
||||
<artifactId>xianyu-goofish</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>top.biwin</groupId>
|
||||
<artifactId>xianyu-ai</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user