基于Spring AI Alibaba的智能知识助手系统:从零到一的RAG实战开发
📖 项目概述
在人工智能快速发展的今天,RAG(Retrieval-Augmented Generation)技术已成为构建智能问答系统的核心技术。本文将详细介绍一个基于Spring AI Alibaba DashScope深度集成的智能知识助手系统的完整开发过程,该系统采用现代化的技术栈,实现了企业级的RAG解决方案。
项目地址:https://github.com/Matthew-Miao/mxy-rag-server
🎯 项目核心价值
技术创新点
- 深度集成Spring AI Alibaba:原生支持阿里云通义千问模型,提供统一的AI接口
- 双模式AI支持:同时支持Spring AI Alibaba DashScope和OpenAI兼容模式
- 企业级RAG架构:完整的检索增强生成系统,支持多种文档格式
- 现代化技术栈:Spring Boot 3.x + PostgreSQL + pgvector + Redis
- 用户会话管理:基于ThreadLocal的用户上下文管理系统
业务价值
- 智能知识问答:基于用户上传的文档进行精准问答
- 多会话管理:支持多个对话会话,保持上下文连续性
- 实时流式对话:支持流式响应,提升用户体验
- 知识库管理:完整的文档上传、处理、检索功能
🏗️ 系统架构设计
整体架构
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端展示层 │ │ 业务逻辑层 │ │ 数据存储层 │
│ │ │ │ │ │
│ Vue 3 │◄──►│ Spring Boot │◄──►│ PostgreSQL │
│ TypeScript │ │ Spring AI │ │ + pgvector │
│ Element Plus │ │ MyBatis │ │ │
│ WebSocket │ │ WebSocket │ │ MySQL │
│ │ │ │ │ Redis │
└─────────────────┘ └─────────────────┘ └─────────────────┘│▼┌─────────────────┐│ AI服务层 ││ ││ 阿里云通义千问 ││ DashScope API ││ 文本嵌入模型 │└─────────────────┘
技术栈详解
后端技术栈
- Spring Boot 3.2.0:现代化的Java企业级框架
- Spring AI Alibaba:阿里云AI服务的Spring集成
- MyBatis:灵活的持久层框架
- PostgreSQL + pgvector:向量数据库,支持相似性搜索
- MySQL:业务数据存储
- Redis:缓存和会话管理
- WebSocket:实时通信支持
前端技术栈
- Vue 3:现代化的前端框架
- TypeScript:类型安全的JavaScript
- Element Plus:企业级UI组件库
- WebSocket Client:实时通信客户端
🔧 核心功能实现
1. Spring AI Alibaba集成
Maven依赖配置
<dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter-dashscope</artifactId><version>1.0.0-M2</version>
</dependency>
配置文件设置
spring:ai:dashscope:api-key: ${DASHSCOPE_API_KEY}chat:options:model: qwen-plustemperature: 0.7# OpenAI兼容模式配置(可选)# openai:# api-key: ${OPENAI_API_KEY}# base-url: https://dashscope.aliyuncs.com/compatible-mode/v1# chat:# options:# model: qwen-plus# temperature: 0.7
核心服务实现
@Service
public class KnowledgeBaseServiceImpl implements KnowledgeBaseService {private static final String SYSTEM_PROMPT = "你是一个智能助手。请始终使用中文回答用户的问题。当回答用户问题时,请遵循以下策略:\n" +"1. 对于基础知识问题(如数学计算、常识问题等),直接使用你的通用知识准确回答\n" +"2. 对于专业或特定领域的问题,优先从向量数据库中检索相关知识来回答\n" +"3. 如果向量数据库中没有找到相关信息,请从聊天记忆中寻找之前讨论过的相关内容\n" +"4. 如果以上都没有相关信息,请基于你的通用知识给出准确、有帮助的回答\n" +"5. 只有在确实无法回答时,才诚实地告知用户并建议他们提供更多信息";private final VectorStore vectorStore;private final ChatClient chatClient;public KnowledgeBaseServiceImpl(VectorStore vectorStore, @Qualifier("dashscopeChatModel") ChatModel chatModel,MessageWindowChatMemory messageWindowChatMemory) {this.vectorStore = vectorStore;this.chatClient = ChatClient.builder(chatModel).defaultAdvisors(SimpleLoggerAdvisor.builder().build(),MessageChatMemoryAdvisor.builder(messageWindowChatMemory).build()).defaultOptions(DashScopeChatOptions.builder().withTopP(0.7).build()).build();}@Overridepublic String chatWithKnowledge(String query, String conversationId, int topK) {try {String prompt = getRagStr(query, topK);// 调用LLM生成回答String answer = chatClient.prompt(prompt).system(SYSTEM_PROMPT).user(query).advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)).call().content();return answer;} catch (Exception e) {logger.error("知识库对话失败,查询: '{}'", query, e);throw new RuntimeException("对话处理失败: " + e.getMessage(), e);}}
}
2. 用户会话管理系统
用户上下文设计
@Component
public class UserSessionHolder {private static final ThreadLocal<UserSession> userSessionThreadLocal = new ThreadLocal<>();public static void setUserSession(UserSession userSession) {userSessionThreadLocal.set(userSession);}public static UserSession getUserSession() {return userSessionThreadLocal.get();}public static String getCurrentUserId() {UserSession session = getUserSession();return session != null ? session.getUserId() : null;}public static void clearUserSession() {userSessionThreadLocal.remove();}
}
用户认证拦截器
@Component
public class UserAuthInterceptor implements HandlerInterceptor {@Autowiredprivate UsersService usersService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取用户IDString userId = request.getHeader("X-User-Id");if (StringUtils.isEmpty(userId)) {userId = request.getHeader("userId");}if (StringUtils.isEmpty(userId)) {throw new BusinessException("用户ID不能为空");}// 验证用户存在性UsersDO user = usersService.getUserById(userId);if (user == null || user.getIsDeleted()) {throw new BusinessException("用户不存在或已被删除");}// 创建用户会话UserSession userSession = UserSession.builder().userId(userId).username(user.getUsername()).usersDO(user).sessionCreateTime(LocalDateTime.now()).ipAddress(getClientIpAddress(request)).userAgent(request.getHeader("User-Agent")).build();UserSessionHolder.setUserSession(userSession);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {UserSessionHolder.clearUserSession();}
}
3. 向量数据库集成
多数据源配置
@Configuration
@MapperScan(basePackages = "com.mxy.rag.mapper", sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class MultiDataSourceConfig {@Bean(name = "pgVectorDataSource")@Primary@ConfigurationProperties(prefix = "spring.datasource.pg-vector")public DataSource pgVectorDataSource() {return new HikariDataSource();}@Bean(name = "mysqlDataSource")@ConfigurationProperties(prefix = "spring.datasource.mysql")public DataSource mysqlDataSource() {return new HikariDataSource();}@Bean(name = "mysqlSqlSessionFactory")public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();bean.setDataSource(dataSource);bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));return bean.getObject();}@Bean(name = "mysqlTransactionManager")public DataSourceTransactionManager mysqlTransactionManager(@Qualifier("mysqlDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
}
pgvector配置
@Configuration
public class VectorStoreConfig {@Beanpublic VectorStore vectorStore(@Qualifier("pgVectorDataSource") DataSource dataSource, EmbeddingModel embeddingModel) {JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);return new PgVectorStore.Builder(jdbcTemplate, embeddingModel).withTableName("vector_store").withDimensions(1536).withDistanceType(PgVectorStore.PgDistanceType.COSINE_DISTANCE).withRemoveExistingVectorStoreTable(false).withIndexType(PgVectorStore.PgIndexType.HNSW).withSchemaValidation(false).build();}@Beanpublic MessageWindowChatMemory messageWindowChatMemory(CustomChatMemoryRepository customChatMemoryRepository) {return new MessageWindowChatMemory(customChatMemoryRepository, 10);}
}
自定义聊天记忆仓库:
@Component
public class CustomChatMemoryRepository implements ChatMemoryRepository {private final ChatSessionsMapper chatSessionsMapper;private final ChatMessagesMapper chatMessagesMapper;public CustomChatMemoryRepository(ChatSessionsMapper chatSessionsMapper, ChatMessagesMapper chatMessagesMapper) {this.chatSessionsMapper = chatSessionsMapper;this.chatMessagesMapper = chatMessagesMapper;}@Overridepublic List<Message> getMessages(String conversationId) {List<ChatMessagesDO> messageDOs = chatMessagesMapper.findBySessionId(conversationId);return messageDOs.stream().map(this::convertToMessage).collect(Collectors.toList());}@Overridepublic void saveMessages(String conversationId, List<Message> messages) {for (Message message : messages) {ChatMessagesDO messageDO = convertToChatMessagesDO(conversationId, message);chatMessagesMapper.insert(messageDO);}}@Overridepublic void deleteMessages(String conversationId) {chatMessagesMapper.deleteBySessionId(conversationId);}
}
REST API控制器
@RestController
@RequestMapping("/api/knowledge")
public class KnowledgeBaseController {private final KnowledgeBaseService knowledgeBaseService;public KnowledgeBaseController(KnowledgeBaseService knowledgeBaseService) {this.knowledgeBaseService = knowledgeBaseService;}@PostMapping("/upload")public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) {try {knowledgeBaseService.loadFile(file);return ResponseEntity.ok("文件上传并处理成功");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文件处理失败: " + e.getMessage());}}@PostMapping("/text")public ResponseEntity<String> addText(@RequestBody Map<String, String> request) {try {String text = request.get("text");if (text == null || text.trim().isEmpty()) {return ResponseEntity.badRequest().body("文本内容不能为空");}knowledgeBaseService.addText(text);return ResponseEntity.ok("文本添加成功");} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("文本处理失败: " + e.getMessage());}}@PostMapping("/chat")public ResponseEntity<String> chatWithKnowledge(@RequestBody Map<String, Object> request) {try {String query = (String) request.get("query");String conversationId = (String) request.get("conversationId");Integer topK = (Integer) request.getOrDefault("topK", 5);if (query == null || query.trim().isEmpty()) {return ResponseEntity.badRequest().body("查询内容不能为空");}String response = knowledgeBaseService.chatWithKnowledge(query, conversationId, topK);return ResponseEntity.ok(response);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("对话失败: " + e.getMessage());}}@PostMapping("/search")public ResponseEntity<List<String>> searchSimilar(@RequestBody Map<String, Object> request) {try {String query = (String) request.get("query");Integer topK = (Integer) request.getOrDefault("topK", 5);if (query == null || query.trim().isEmpty()) {return ResponseEntity.badRequest().body(null);}List<String> results = knowledgeBaseService.searchSimilar(query, topK);return ResponseEntity.ok(results);} catch (Exception e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);}}
}
文档处理服务
@Override
public void loadFile(MultipartFile file) {try {String originalFilename = file.getOriginalFilename();if (originalFilename == null) {throw new IllegalArgumentException("文件名不能为空");}logger.info("开始处理文件: {}", originalFilename);List<Document> documents;String fileExtension = getFileExtension(originalFilename).toLowerCase();if ("pdf".equals(fileExtension)) {// PDF文件处理PdfDocumentReader pdfReader = new PdfDocumentReader(file.getResource());documents = pdfReader.get();} else {// 其他文件类型使用Tika处理TikaDocumentReader tikaReader = new TikaDocumentReader(file.getResource());documents = tikaReader.get();}if (documents.isEmpty()) {throw new RuntimeException("未能从文件中提取到任何内容");}// 文本分块处理TextSplitter textSplitter = new TokenTextSplitter(500, 100, 5, 10000, true);List<Document> splitDocuments = textSplitter.apply(documents);// 添加元数据for (Document doc : splitDocuments) {doc.getMetadata().put("source", originalFilename);doc.getMetadata().put("upload_time", System.currentTimeMillis());}// 存储到向量数据库vectorStore.add(splitDocuments);logger.info("文件 {} 处理完成,共生成 {} 个文档块", originalFilename, splitDocuments.size());} catch (Exception e) {logger.error("文件处理失败: {}", file.getOriginalFilename(), e);throw new RuntimeException("文件处理失败: " + e.getMessage(), e);}
}private String getFileExtension(String filename) {int lastDotIndex = filename.lastIndexOf('.');return lastDotIndex > 0 ? filename.substring(lastDotIndex + 1) : "";
}
4. 用户认证与会话管理
用户认证拦截器
@Component
public class UserAuthInterceptor implements HandlerInterceptor {private final UserService userService;public UserAuthInterceptor(UserService userService) {this.userService = userService;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String userId = request.getHeader("User-Id");if (userId == null || userId.trim().isEmpty()) {response.setStatus(HttpStatus.UNAUTHORIZED.value());response.getWriter().write("缺少用户ID");return false;}// 验证用户是否存在UserDO user = userService.getUserById(userId);if (user == null) {response.setStatus(HttpStatus.UNAUTHORIZED.value());response.getWriter().write("用户不存在");return false;}// 将用户信息存储到会话中UserSession userSession = new UserSession();userSession.setUserId(userId);userSession.setUsername(user.getUsername());UserSessionHolder.setUserSession(userSession);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理会话信息UserSessionHolder.clearUserSession();}
}
Web配置
@Configuration
public class WebConfig implements WebMvcConfigurer {private final UserAuthInterceptor userAuthInterceptor;public WebConfig(UserAuthInterceptor userAuthInterceptor) {this.userAuthInterceptor = userAuthInterceptor;}@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(userAuthInterceptor).addPathPatterns("/api/**").excludePathPatterns("/api/users/register", "/api/users/login");}@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS").allowedHeaders("*").allowCredentials(true).maxAge(3600);}@Overridepublic void configureAsyncSupport(AsyncSupportConfigurer configurer) {configurer.setDefaultTimeout(30000);configurer.setTaskExecutor(taskExecutor());}@Beanpublic TaskExecutor taskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(10);executor.setMaxPoolSize(50);executor.setQueueCapacity(100);executor.setThreadNamePrefix("async-");executor.initialize();return executor;}
}
🗄️ 数据库设计
PostgreSQL(向量数据)
-- 启用pgvector扩展
CREATE EXTENSION IF NOT EXISTS vector;-- 向量存储表(Spring AI默认表结构)
CREATE TABLE vector_store (id UUID PRIMARY KEY DEFAULT gen_random_uuid(),content TEXT NOT NULL,metadata JSON,embedding vector(1536)
);-- 创建向量索引(余弦相似度)
CREATE INDEX vector_store_embedding_idx ON vector_store
USING hnsw (embedding vector_cosine_ops);-- 创建内容全文搜索索引
CREATE INDEX vector_store_content_idx ON vector_store
USING gin (to_tsvector('english', content));
MySQL(业务数据)
-- 用户表
CREATE TABLE users (id VARCHAR(50) PRIMARY KEY COMMENT '用户ID',username VARCHAR(100) NOT NULL UNIQUE COMMENT '用户名',password VARCHAR(255) NOT NULL COMMENT '密码',email VARCHAR(255) COMMENT '邮箱',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';-- 聊天会话表
CREATE TABLE chat_sessions (id VARCHAR(50) PRIMARY KEY COMMENT '会话ID',user_id VARCHAR(50) NOT NULL COMMENT '用户ID',title VARCHAR(255) NOT NULL COMMENT '会话标题',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除',INDEX idx_user_id (user_id),INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='聊天会话表';-- 聊天消息表
CREATE TABLE chat_messages (id VARCHAR(50) PRIMARY KEY COMMENT '消息ID',session_id VARCHAR(50) NOT NULL COMMENT '会话ID',user_id VARCHAR(50) NOT NULL COMMENT '用户ID',content TEXT NOT NULL COMMENT '消息内容',message_type VARCHAR(20) NOT NULL COMMENT '消息类型:USER/ASSISTANT',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',INDEX idx_session_id (session_id),INDEX idx_user_id (user_id),INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='聊天消息表';-- 系统配置表
CREATE TABLE system_config (id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '配置ID',config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键',config_value TEXT COMMENT '配置值',description VARCHAR(255) COMMENT '配置描述',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
🚀 部署与运维
生产环境配置
application-prod.yaml:
spring:datasource:pg-vector:driver-class-name: org.postgresql.Driverurl: jdbc:postgresql://${PG_HOST:localhost}:${PG_PORT:5432}/${PG_DATABASE:rag_vector}username: ${PG_USERNAME:postgres}password: ${PG_PASSWORD:password}hikari:maximum-pool-size: 20minimum-idle: 5idle-timeout: 300000max-lifetime: 1200000connection-timeout: 20000mysql:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:rag_business}?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghaiusername: ${MYSQL_USERNAME:root}password: ${MYSQL_PASSWORD:password}hikari:maximum-pool-size: 30minimum-idle: 10idle-timeout: 300000max-lifetime: 1200000connection-timeout: 20000ai:dashscope:api-key: ${DASHSCOPE_API_KEY}chat:options:model: ${DASHSCOPE_MODEL:qwen-plus}temperature: ${DASHSCOPE_TEMPERATURE:0.7}logging:level:com.mxy.rag: INFOorg.springframework.ai: INFOfile:name: logs/mxy-rag-server.logpattern:file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"server:port: ${SERVER_PORT:8080}servlet:context-path: /tomcat:max-threads: 200min-spare-threads: 10
Docker部署
Dockerfile:
FROM openjdk:17-jdk-slimWORKDIR /app# 复制jar文件
COPY target/mxy-rag-server-*.jar app.jar# 创建日志目录
RUN mkdir -p logs# 暴露端口
EXPOSE 8080# 启动应用
ENTRYPOINT ["java", "-Xms512m", "-Xmx2g", "-jar", "app.jar", "--spring.profiles.active=prod"]
Docker Compose配置
docker-compose.yml:
version: '3.8'
services:app:build: .ports:- "8080:8080"environment:- SPRING_PROFILES_ACTIVE=prod- DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY}- PG_HOST=postgres- PG_PORT=5432- PG_DATABASE=rag_vector- PG_USERNAME=postgres- PG_PASSWORD=postgres123- MYSQL_HOST=mysql- MYSQL_PORT=3306- MYSQL_DATABASE=rag_business- MYSQL_USERNAME=root- MYSQL_PASSWORD=mysql123depends_on:- postgres- mysqlvolumes:- ./logs:/app/logsrestart: unless-stoppedpostgres:image: pgvector/pgvector:pg16environment:- POSTGRES_DB=rag_vector- POSTGRES_USER=postgres- POSTGRES_PASSWORD=postgres123ports:- "5432:5432"volumes:- postgres_data:/var/lib/postgresql/data- ./init-scripts/init-postgres.sql:/docker-entrypoint-initdb.d/init.sqlrestart: unless-stoppedmysql:image: mysql:8.0environment:- MYSQL_ROOT_PASSWORD=mysql123- MYSQL_DATABASE=rag_businessports:- "3306:3306"volumes:- mysql_data:/var/lib/mysql- ./init-scripts/init-mysql.sql:/docker-entrypoint-initdb.d/init.sqlrestart: unless-stoppedvolumes:postgres_data:mysql_data:
📝 总结
本文详细介绍了基于Spring AI和阿里云DashScope构建RAG系统的完整实现方案。主要特点包括:
🎯 核心特性
- 多模型支持:集成阿里云DashScope,支持通义千问系列模型
- 向量检索:基于pgvector的高效向量存储和检索
- 多数据源:PostgreSQL存储向量数据,MySQL存储业务数据
- 聊天记忆:支持多轮对话的上下文记忆
- 文档处理:支持PDF、Word等多种文档格式
- 用户认证:完整的用户管理和会话管理
🛠️ 技术栈
- 后端框架:Spring Boot 3.x + Spring AI Alibaba
- AI模型:阿里云DashScope(通义千问)
- 向量数据库:PostgreSQL + pgvector
- 业务数据库:MySQL 8.0
- ORM框架:MyBatis Plus
- 文档处理:Apache Tika + Spring AI Document Readers
🚀 部署方式
- 容器化部署:Docker + Docker Compose
- 生产环境:支持环境变量配置
- 日志管理:结构化日志输出
- 监控运维:完整的错误处理和日志记录
通过本方案,可以快速构建一个功能完整、性能优异的企业级RAG系统,为知识管理和智能问答提供强有力的技术支撑。
## 💡 总结本项目展示了如何使用Spring AI Alibaba构建一个完整的RAG系统,具有以下特点:1. **技术先进性**:采用最新的Spring AI技术栈,深度集成阿里云AI服务
2. **架构合理性**:清晰的分层架构,良好的可扩展性
3. **功能完整性**:涵盖用户管理、会话管理、知识库管理等核心功能
4. **企业级特性**:完善的安全性、监控和日志系统通过本项目的实践,开发者可以快速掌握RAG系统的开发要点,为构建更复杂的AI应用奠定基础。**项目地址**:[https://github.com/Matthew-Miao/mxy-rag-server](https://github.com/Matthew-Miao/mxy-rag-server)欢迎Star和Fork,一起探讨AI应用开发的最佳实践!
*如果这篇文章对你有帮助,请点赞、收藏并关注,我会持续分享更多AI开发实战经验!*