mirror of
https://gitee.com/270580156/weiyu.git
synced 2026-09-09 02:59:28 +00:00
update
This commit is contained in:
47
modules/ai/SpringAI_RAG_Refactor.md
Normal file
47
modules/ai/SpringAI_RAG_Refactor.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# SpringAI RAG Controller 重构说明
|
||||
|
||||
## 问题描述
|
||||
原有的 `SpringAIRagController` 使用了特定的 `OllamaChatModel`,当 Ollama 没有启用时会导致以下错误:
|
||||
```
|
||||
Parameter 2 of constructor in com.bytedesk.ai.springai.rag.SpringAIRagController required a bean of type 'org.springframework.ai.ollama.OllamaChatModel' that could not be found.
|
||||
```
|
||||
|
||||
## 解决方案
|
||||
将特定的 `OllamaChatModel` 替换为通用的 `ChatModel` 接口,使控制器能够适配任何 ChatModel 实现。
|
||||
|
||||
## 主要更改
|
||||
|
||||
### 1. SpringAIRagController.java
|
||||
- **字段更改**: 将 `private final OllamaChatModel bytedeskOllamaChatModel` 改为 `private final ChatModel chatModel`
|
||||
- **导入更改**: 移除 `OllamaChatModel` 导入,添加 `ChatModel` 导入
|
||||
- **注解更改**: 添加 `@ConditionalOnBean(ChatModel.class)` 确保只有在有 ChatModel 可用时才创建控制器
|
||||
- **方法更新**: 所有使用 `bytedeskOllamaChatModel` 的地方都替换为 `chatModel`
|
||||
|
||||
### 2. 新增配置类
|
||||
|
||||
#### ChatModelConfig.java
|
||||
- 提供备用的 ChatModel 配置
|
||||
- 只有在没有其他 ChatModel 实现时才创建 FallbackChatModel
|
||||
- 受 `bytedesk.features.java-ai=true` 配置控制
|
||||
|
||||
#### FallbackChatModel.java
|
||||
- 简单的 ChatModel 实现作为备用
|
||||
- 当没有其他 ChatModel 可用时返回友好的错误消息
|
||||
- 提醒用户配置正确的 ChatModel 实现
|
||||
|
||||
## 兼容性
|
||||
此更改向后兼容:
|
||||
- 当 Ollama 启用时,会优先使用 OllamaChatModel
|
||||
- 当其他 ChatModel 实现(如 OpenAI)可用时,会使用相应的实现
|
||||
- 当没有任何 ChatModel 可用时,会使用 FallbackChatModel 避免启动错误
|
||||
|
||||
## 配置要求
|
||||
确保在 `application.properties` 中设置:
|
||||
```properties
|
||||
bytedesk.features.java-ai=true
|
||||
```
|
||||
|
||||
## 测试建议
|
||||
1. 测试 Ollama 启用的情况
|
||||
2. 测试 Ollama 禁用但有其他 ChatModel 的情况
|
||||
3. 测试完全没有 ChatModel 的情况(应该使用 FallbackChatModel)
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* @Author: jackning 270580156@qq.com
|
||||
* @Date: 2025-05-31 10:00:00
|
||||
* @LastEditors: jackning 270580156@qq.com
|
||||
* @LastEditTime: 2025-05-31 13:49:47
|
||||
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
|
||||
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
|
||||
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
|
||||
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
|
||||
* contact: 270580156@qq.com
|
||||
*
|
||||
* Copyright (c) 2025 by bytedesk.com, All Rights Reserved.
|
||||
*/
|
||||
package com.bytedesk.ai.springai.config;
|
||||
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* ChatModel配置类
|
||||
* 提供ChatModel的默认实现,当没有其他ChatModel可用时作为备用
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class ChatModelConfig {
|
||||
|
||||
/**
|
||||
* 当没有其他ChatModel bean时,提供一个默认的ChatModel实现
|
||||
* 这个bean只有在启用Java AI功能时才会创建
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
@ConditionalOnMissingBean(ChatModel.class)
|
||||
@ConditionalOnProperty(name = "bytedesk.features.java-ai", havingValue = "true", matchIfMissing = false)
|
||||
public ChatModel defaultChatModel() {
|
||||
log.warn("No ChatModel implementation found, using fallback implementation");
|
||||
|
||||
// 返回一个简单的ChatModel实现作为备用
|
||||
return new FallbackChatModel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* @Author: jackning 270580156@qq.com
|
||||
* @Date: 2025-05-31 10:00:00
|
||||
* @LastEditors: jackning 270580156@qq.com
|
||||
* @LastEditTime: 2025-05-31 10:00:00
|
||||
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
|
||||
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
|
||||
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
|
||||
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
|
||||
* contact: 270580156@qq.com
|
||||
*
|
||||
* Copyright (c) 2025 by bytedesk.com, All Rights Reserved.
|
||||
*/
|
||||
package com.bytedesk.ai.springai.config;
|
||||
|
||||
import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.chat.prompt.Prompt;
|
||||
import org.springframework.ai.chat.model.Generation;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 备用ChatModel实现
|
||||
* 当没有其他ChatModel可用时使用此实现
|
||||
*/
|
||||
@Slf4j
|
||||
public class FallbackChatModel implements ChatModel {
|
||||
|
||||
@Override
|
||||
public ChatResponse call(Prompt prompt) {
|
||||
log.warn("FallbackChatModel is being used - please configure a proper ChatModel implementation");
|
||||
|
||||
// 创建一个简单的响应
|
||||
String responseText = "AI服务暂时不可用,请稍后再试。请确保已正确配置ChatModel实现(如Ollama、OpenAI等)。";
|
||||
|
||||
AssistantMessage assistantMessage = new AssistantMessage(responseText);
|
||||
Generation generation = new Generation(assistantMessage);
|
||||
return new ChatResponse(List.of(generation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String call(String message) {
|
||||
return call(new Prompt(new UserMessage(message))).getResult().getOutput().getText();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
package com.bytedesk.ai.springai.rag;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.ai.chat.client.advisor.api.Advisor;
|
||||
@@ -22,7 +24,7 @@ import org.springframework.ai.chat.messages.AssistantMessage;
|
||||
import org.springframework.ai.chat.messages.UserMessage;
|
||||
import org.springframework.ai.chat.model.ChatResponse;
|
||||
import org.springframework.ai.document.Document;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.chat.model.ChatModel;
|
||||
import org.springframework.ai.rag.Query;
|
||||
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
|
||||
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
|
||||
@@ -36,8 +38,10 @@ import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
|
||||
import org.springframework.ai.vectorstore.SearchRequest;
|
||||
import org.springframework.ai.vectorstore.VectorStore;
|
||||
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
|
||||
import org.springframework.ai.embedding.EmbeddingModel;
|
||||
import org.springframework.ai.vectorstore.elasticsearch.ElasticsearchVectorStore;
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -59,14 +63,14 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@RestController
|
||||
@RequestMapping("/spring/ai/rag")
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "spring.ai.ollama.chat.enabled", havingValue = "true", matchIfMissing = false)
|
||||
@ConditionalOnBean(ChatModel.class)
|
||||
public class SpringAIRagController {
|
||||
|
||||
private final VectorStore vectorStore;
|
||||
|
||||
private final OllamaChatModel bytedeskOllamaChatModel;
|
||||
private final EmbeddingModel embeddingModel;
|
||||
|
||||
// private final SpringAIVectorStoreService springAIVectorService;
|
||||
private final ChatModel chatModel;
|
||||
|
||||
private final ObservationRegistry observationRegistry;
|
||||
|
||||
@@ -87,7 +91,7 @@ public class SpringAIRagController {
|
||||
.build())
|
||||
.build();
|
||||
// 使用chatClient,添加ObservationRegistry
|
||||
ChatResponse response = ChatClient.builder(bytedeskOllamaChatModel, observationRegistry, null)
|
||||
ChatResponse response = ChatClient.builder(chatModel, observationRegistry, null)
|
||||
.build()
|
||||
.prompt()
|
||||
.advisors(qaAdvisor)
|
||||
@@ -107,7 +111,7 @@ public class SpringAIRagController {
|
||||
@RequestParam(value = "message", defaultValue = "什么时间考试?") String message,
|
||||
@RequestParam(value = "kbUid", defaultValue = "") String kbUid) {
|
||||
|
||||
ChatClient chatClient = ChatClient.builder(bytedeskOllamaChatModel)
|
||||
ChatClient chatClient = ChatClient.builder(chatModel)
|
||||
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
|
||||
.searchRequest(SearchRequest.builder().build())
|
||||
.build())
|
||||
@@ -136,13 +140,13 @@ public class SpringAIRagController {
|
||||
.similarityThreshold(0.50)
|
||||
.vectorStore(vectorStore)
|
||||
.build())
|
||||
// 允许为空
|
||||
// .queryAugmenter(ContextualQueryAugmenter.builder()
|
||||
// .allowEmptyContext(true)
|
||||
// .build())
|
||||
// 允许为空
|
||||
// .queryAugmenter(ContextualQueryAugmenter.builder()
|
||||
// .allowEmptyContext(true)
|
||||
// .build())
|
||||
.build();
|
||||
|
||||
String answer = ChatClient.builder(bytedeskOllamaChatModel)
|
||||
String answer = ChatClient.builder(chatModel)
|
||||
.defaultAdvisors(retrievalAugmentationAdvisor)
|
||||
.build()
|
||||
.prompt()
|
||||
@@ -163,7 +167,7 @@ public class SpringAIRagController {
|
||||
|
||||
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
|
||||
.queryTransformers(RewriteQueryTransformer.builder()
|
||||
.chatClientBuilder(ChatClient.builder(bytedeskOllamaChatModel).build().mutate())
|
||||
.chatClientBuilder(ChatClient.builder(chatModel).build().mutate())
|
||||
.build())
|
||||
.documentRetriever(VectorStoreDocumentRetriever.builder()
|
||||
.similarityThreshold(0.50)
|
||||
@@ -171,7 +175,7 @@ public class SpringAIRagController {
|
||||
.build())
|
||||
.build();
|
||||
|
||||
String answer = ChatClient.builder(bytedeskOllamaChatModel)
|
||||
String answer = ChatClient.builder(chatModel)
|
||||
.defaultAdvisors(retrievalAugmentationAdvisor)
|
||||
.build()
|
||||
.prompt()
|
||||
@@ -206,13 +210,13 @@ public class SpringAIRagController {
|
||||
// conversation history and a follow-up query into a standalone query that
|
||||
// captures the essence of the conversation.
|
||||
CompressionQueryTransformer queryTransformer = CompressionQueryTransformer.builder()
|
||||
.chatClientBuilder(ChatClient.builder(bytedeskOllamaChatModel).build().mutate())
|
||||
.chatClientBuilder(ChatClient.builder(chatModel).build().mutate())
|
||||
.build();
|
||||
|
||||
Query transformedQuery = queryTransformer.transform(query);
|
||||
|
||||
// 使用chatClient
|
||||
String answer = ChatClient.builder(bytedeskOllamaChatModel)
|
||||
String answer = ChatClient.builder(chatModel)
|
||||
// .defaultAdvisors(retrievalAugmentationAdvisor)
|
||||
.build()
|
||||
.prompt()
|
||||
@@ -241,13 +245,13 @@ public class SpringAIRagController {
|
||||
// to provide better results when querying a target system, such as a vector
|
||||
// store or a web search engine.
|
||||
QueryTransformer queryTransformer = RewriteQueryTransformer.builder()
|
||||
.chatClientBuilder(ChatClient.builder(bytedeskOllamaChatModel).build().mutate())
|
||||
.chatClientBuilder(ChatClient.builder(chatModel).build().mutate())
|
||||
.build();
|
||||
|
||||
Query transformedQuery = queryTransformer.transform(query);
|
||||
|
||||
// 使用chatClient
|
||||
String answer = ChatClient.builder(bytedeskOllamaChatModel)
|
||||
String answer = ChatClient.builder(chatModel)
|
||||
// .defaultAdvisors(retrievalAugmentationAdvisor)
|
||||
.build()
|
||||
.prompt()
|
||||
@@ -272,14 +276,14 @@ public class SpringAIRagController {
|
||||
Query query = new Query("Hvad er Danmarks hovedstad?");
|
||||
|
||||
QueryTransformer queryTransformer = TranslationQueryTransformer.builder()
|
||||
.chatClientBuilder(ChatClient.builder(bytedeskOllamaChatModel).build().mutate())
|
||||
.chatClientBuilder(ChatClient.builder(chatModel).build().mutate())
|
||||
.targetLanguage("english")
|
||||
.build();
|
||||
|
||||
Query transformedQuery = queryTransformer.transform(query);
|
||||
|
||||
// 使用chatClient
|
||||
String answer = ChatClient.builder(bytedeskOllamaChatModel)
|
||||
String answer = ChatClient.builder(chatModel)
|
||||
// .defaultAdvisors(retrievalAugmentationAdvisor)
|
||||
.build()
|
||||
.prompt()
|
||||
@@ -302,14 +306,14 @@ public class SpringAIRagController {
|
||||
@RequestParam(value = "kbUid", defaultValue = "") String kbUid) {
|
||||
|
||||
MultiQueryExpander queryExpander = MultiQueryExpander.builder()
|
||||
.chatClientBuilder(ChatClient.builder(bytedeskOllamaChatModel).build().mutate())
|
||||
.chatClientBuilder(ChatClient.builder(chatModel).build().mutate())
|
||||
.numberOfQueries(3)
|
||||
// .includeOriginal(false)
|
||||
.build();
|
||||
List<Query> queries = queryExpander.expand(new Query("How to run a Spring Boot app?"));
|
||||
|
||||
// 使用chatClient
|
||||
String answer = ChatClient.builder(bytedeskOllamaChatModel)
|
||||
String answer = ChatClient.builder(chatModel)
|
||||
// .defaultAdvisors(retrievalAugmentationAdvisor)
|
||||
.build()
|
||||
.prompt()
|
||||
@@ -393,7 +397,7 @@ public class SpringAIRagController {
|
||||
ResponseEntity<JsonResult<?>> observedChat(
|
||||
@RequestParam(value = "message", defaultValue = "什么时间考试?") String message) {
|
||||
|
||||
ChatClient chatClient = ChatClient.builder(bytedeskOllamaChatModel, observationRegistry, null)
|
||||
ChatClient chatClient = ChatClient.builder(chatModel, observationRegistry, null)
|
||||
.build();
|
||||
|
||||
ChatResponse response = chatClient.prompt()
|
||||
@@ -406,4 +410,86 @@ public class SpringAIRagController {
|
||||
return ResponseEntity.ok(JsonResult.success(response));
|
||||
}
|
||||
|
||||
// 查看当前ElasticsearchVectorStore详情
|
||||
// http://127.0.0.1:9003/spring/ai/rag/vector-store-info
|
||||
@GetMapping("/vector-store-info")
|
||||
ResponseEntity<JsonResult<?>> getVectorStoreInfo() {
|
||||
log.info("Getting VectorStore info");
|
||||
|
||||
Map<String, Object> vectorStoreInfo = new HashMap<>();
|
||||
|
||||
try {
|
||||
vectorStoreInfo.put("type", vectorStore.getClass().getSimpleName());
|
||||
vectorStoreInfo.put("className", vectorStore.getClass().getName());
|
||||
|
||||
// 如果是ElasticsearchVectorStore,获取更详细的信息
|
||||
if (vectorStore instanceof ElasticsearchVectorStore) {
|
||||
vectorStoreInfo.put("vectorStoreType", "ElasticsearchVectorStore");
|
||||
vectorStoreInfo.put("status", "Active");
|
||||
// 注意:由于ElasticsearchVectorStore的内部属性可能是私有的,
|
||||
// 这里只能获取到公开的信息
|
||||
vectorStoreInfo.put("description", "Elasticsearch-based vector store for semantic search");
|
||||
} else {
|
||||
vectorStoreInfo.put("vectorStoreType", "Unknown");
|
||||
}
|
||||
|
||||
vectorStoreInfo.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting vector store info: {}", e.getMessage());
|
||||
vectorStoreInfo.put("error", e.getMessage());
|
||||
vectorStoreInfo.put("status", "Error");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success(vectorStoreInfo));
|
||||
}
|
||||
|
||||
// 查看当前EmbeddingModel详情
|
||||
// http://127.0.0.1:9003/spring/ai/rag/embedding-model-info
|
||||
@GetMapping("/embedding-model-info")
|
||||
ResponseEntity<JsonResult<?>> getEmbeddingModelInfo() {
|
||||
log.info("Getting EmbeddingModel info");
|
||||
|
||||
Map<String, Object> embeddingModelInfo = new HashMap<>();
|
||||
|
||||
try {
|
||||
embeddingModelInfo.put("type", embeddingModel.getClass().getSimpleName());
|
||||
embeddingModelInfo.put("className", embeddingModel.getClass().getName());
|
||||
|
||||
// 获取embedding维度信息
|
||||
try {
|
||||
// 使用一个简单的测试文本来获取embedding维度
|
||||
var testEmbedding = embeddingModel.embed("test");
|
||||
embeddingModelInfo.put("dimensions", testEmbedding.length);
|
||||
embeddingModelInfo.put("status", "Active");
|
||||
embeddingModelInfo.put("testEmbeddingSize", testEmbedding.length);
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not get embedding dimensions: {}", e.getMessage());
|
||||
embeddingModelInfo.put("dimensionsError", e.getMessage());
|
||||
embeddingModelInfo.put("status", "Error");
|
||||
}
|
||||
|
||||
// 根据类名判断embedding模型类型
|
||||
String className = embeddingModel.getClass().getSimpleName();
|
||||
if (className.contains("Ollama")) {
|
||||
embeddingModelInfo.put("modelType", "Ollama Embedding Model");
|
||||
embeddingModelInfo.put("description", "Local Ollama embedding model (e.g., bgm-m3)");
|
||||
} else if (className.contains("Zhipuai") || className.contains("ZhiPu")) {
|
||||
embeddingModelInfo.put("modelType", "Zhipuai Embedding Model");
|
||||
embeddingModelInfo.put("description", "Zhipuai cloud embedding model");
|
||||
} else {
|
||||
embeddingModelInfo.put("modelType", "Unknown Embedding Model");
|
||||
}
|
||||
|
||||
embeddingModelInfo.put("timestamp", System.currentTimeMillis());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error getting embedding model info: {}", e.getMessage());
|
||||
embeddingModelInfo.put("error", e.getMessage());
|
||||
embeddingModelInfo.put("status", "Error");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success(embeddingModelInfo));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# RAG
|
||||
|
||||
- [spring ai alibaba](https://java2ai.com/blog/spring-ai-alibaba-module-rag)
|
||||
- [spring ai rag](https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html?spm=0.29160081.0.0.75c73b5blqQmqQ)
|
||||
Reference in New Issue
Block a user