最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于SpringAI+Qdrant+Ollama本地模型和向量數(shù)據(jù)庫開發(fā)問答和RAG檢索(完整代碼)

 更新時間:2026年04月10日 10:13:07   作者:coder阿龍  
文章介紹了使用Ollama和Qdrant構(gòu)建AI對話系統(tǒng)的步驟,包括安裝模型、啟動向量數(shù)據(jù)庫、環(huán)境配置等,并提供了完整的代碼實現(xiàn),支持基本對話、文件檢索等功能,本文通過實例代碼給大家講解的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧

0.前置準(zhǔn)備

1.安裝ollama

然后安裝如下三個模型

C:\Users\GT-JYW-3>ollama list
NAME                    ID              SIZE      MODIFIED
qwen3-embedding:0.6b    ac6da0dfba84    639 MB    8 hours ago
qwen3.5:2b              324d162be6ca    2.7 GB    25 hours ago
deepseek-r1:1.5b        e0979632db5a    1.1 GB    25 hours ag

第一個是檢索用的,后面兩個對話用,根據(jù)自己需要安裝別的也行
對話需要帶有(tool),檢索需要帶有(embedding)

2.下載Qdrant

Qdrant是一個本地輕型的向量數(shù)據(jù)庫
下載地址: https://github.com/qdrant/qdrant/releases
windows直接雙擊啟動就行

3.環(huán)境準(zhǔn)備

jdk17,然后下面就是完整的代碼,直接復(fù)制粘貼就可以用

對話直接訪問 ip:port/chat?msg=你的問題。 流式返回就是chatS?msg=

再加一個&sessionId可以基于redis進(jìn)行會話存儲(使用的是DB:6)

文件檢索這個我寫死了個目錄,可以自行修改和擴(kuò)展在KnowledgeBaseConfig.java>defaultPath

然后訪問ip:port/file/scan 先進(jìn)行索引創(chuàng)建 然后再/file/chat?msg=你的問題即可

代碼99%都是AI寫的,本人親測是可以讀取文件夾里面PPT,MD,DOCX內(nèi)容的

只是模型太笨,可以換個更大的模型,這里用的幾個都是很小的來測試的

1.整體結(jié)構(gòu)

2.pom文件

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.0</version>
        <relativePath/>
    </parent>
    <groupId>com.cxl</groupId>
    <artifactId>springai-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springai-demo</name>
    <description>springai-demo</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M6</spring-ai.version>
    </properties>
    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-qdrant-store-spring-boot-starter</artifactId>
            <version>${spring-ai.version}</version> <!-- 替換為實際版本 -->
        </dependency>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- Spring AI Core -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-core</artifactId>
        </dependency>
        <!-- Spring AI Ollama Starter -->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
        </dependency>
        <!-- Spring Boot DevTools -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- Apache POI for PPTX extraction -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.5</version>
        </dependency>
        <!-- Spring Data Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- Spring AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-configuration-processor</artifactId>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

3.application.yaml文件

# ===================================================================
# Spring AI Demo 配置文件
# ===================================================================
# 服務(wù)器配置
server:
  port: 12000  # 應(yīng)用端口
# Spring 配置
spring:
  application:
    name: springai-demo  # 應(yīng)用名稱
  # Redis 配置 - 用于存儲對話歷史
  data:
    redis:
      host: localhost      # Redis 服務(wù)器地址
      port: 6379          # Redis 端口
      database: 6          # Redis 數(shù)據(jù)庫編號 (0-15)
  # AI 大模型配置
  ai:
    # Ollama 本地大模型服務(wù)配置
    ollama:
      base-url: http://localhost:11434  # Ollama API 地址
      embedding:
        model: qwen3-embedding:0.6b     # 嵌入模型 (用于 RAG 向量化)
        enabled: true
    # 嵌入向量配置
    embedding:
      options:
        model: qwen3-embedding:0.6b     # 嵌入模型名稱
    # 向量數(shù)據(jù)庫配置 (Qdrant)
    vectorstore:
      qdrant:
        host: localhost     # Qdrant 服務(wù)地址
        port: 6334         # Qdrant gRPC 端口 (REST API 端口為 6333)
        collection-name: my_docs  # 向量集合名稱
# ===================================================================
# 知識庫配置 (Knowledge Base)
# 用途: FileSearchService 用于 RAG 知識檢索
# ===================================================================
knowledge-base:
  default-path: "C:\\Users\\GT-JYW-3\\Documents\\doct"  # 知識庫文件默認(rèn)目錄
  collection-name: my_docs    # Qdrant 向量集合名稱
  host: localhost             # Qdrant 服務(wù)地址
  port: 6334                 # Qdrant 端口
  top-k: 5                   # 知識檢索返回的相關(guān)文檔數(shù)量
# ===================================================================
# 對話歷史配置 (Chat History)
# 用途: ChatHistoryService + ChatHistoryAspect 用于存儲會話上下文
# ===================================================================
chat-history:
  enabled: true               # 是否啟用對話歷史功能
  key-prefix: "chat:history:" # Redis Key 前綴
  max-size: 20               # 每個會話最大保存的消息數(shù)量
  expire-days: 7              # 歷史記錄過期天數(shù)
# ===================================================================
# System 預(yù)制詞配置 (AI 角色設(shè)定)
# 用途: ChatService 調(diào)用不同模型時使用的系統(tǒng)提示詞
# ===================================================================
system:
  settings:
    # 默認(rèn)提示詞 (當(dāng)模型沒有特定提示詞時使用)
    default-prompt: "你是一個有幫助的AI助手,請用簡潔專業(yè)的語言回答用戶問題。"
    # 針對不同模型的特定提示詞
    prompts:
      # Qwen 模型提示詞
      qwen3.5:2b: "你是Qwen3.5模型,一個由阿里云開發(fā)的大語言模型。你擅長中文理解和生成,請用專業(yè)、準(zhǔn)確的語言回答用戶問題。"
      # DeepSeek 模型提示詞
      deepseek-r1:1.5b: "你是DeepSeek-R1模型,一個專注于推理和思考的AI助手。請在回答問題時展示清晰的思考過程,提供深入的分析。"
# ===================================================================
# 應(yīng)用全局配置
# ===================================================================
app:
  context-aware-enabled: true      # 是否啟用上下文感知功能
  default-top-k: 3                # 默認(rèn)知識檢索數(shù)量
  session-timeout-minutes: 60     # 會話超時時間(分鐘)

4.啟用對話記憶注解

package com.cxl.annotation;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableChatHistory {
    boolean enabled() default true;
}

5.對話記憶注解切面

package com.cxl.aspect;
import com.cxl.annotation.EnableChatHistory;
import com.cxl.service.ChatHistoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import java.lang.reflect.Method;
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class ChatHistoryAspect {
    private final ChatHistoryService chatHistoryService;
    @Around("@annotation(com.cxl.annotation.EnableChatHistory)")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        EnableChatHistory annotation = method.getAnnotation(EnableChatHistory.class);
        if (!annotation.enabled()) {
            return joinPoint.proceed();
        }
        Object[] args = joinPoint.getArgs();
        String[] paramNames = signature.getParameterNames();
        String sessionId = null;
        String userMessage = null;
        for (int i = 0; i < args.length; i++) {
            String paramName = paramNames[i];
            if ("sessionId".equals(paramName) && args[i] != null) {
                sessionId = (String) args[i];
            }
            if ("msg".equals(paramName) && args[i] != null) {
                userMessage = (String) args[i];
            }
        }
        if (sessionId == null || sessionId.isEmpty()) {
            return joinPoint.proceed();
        }
        final String finalSessionId = sessionId;
        final String finalUserMessage = userMessage;
        log.info("========== ChatHistoryAspect ==========");
        log.info("SessionId: {}", finalSessionId);
        log.info("UserMessage: {}", finalUserMessage);
        Object result = joinPoint.proceed();
        if (result instanceof Flux) {
            Flux<String> fluxResult = (Flux<String>) result;
            return fluxResult
                    .collectList()
                    .doOnNext(chunks -> {
                        String fullResponse = String.join("", chunks);
                        chatHistoryService.addMessage(finalSessionId, "user", finalUserMessage);
                        chatHistoryService.addMessage(finalSessionId, "assistant", fullResponse);
                        log.info("Saved stream chat history to Redis");
                    })
                    .flatMapMany(chunks -> Flux.fromIterable(chunks));
        } else if (result instanceof String) {
            chatHistoryService.addMessage(finalSessionId, "user", finalUserMessage);
            chatHistoryService.addMessage(finalSessionId, "assistant", (String) result);
            log.info("Saved chat history to Redis");
        }
        return result;
    }
}

6.配置類

1.AIConfig

package com.cxl.config;
import io.micrometer.observation.ObservationRegistry;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.ollama.api.OllamaApi;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.ai.ollama.management.ModelManagementOptions;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.util.List;
@Configuration
public class AIConfig {
    @Bean
    public OllamaApi ollamaApi() {
        return new OllamaApi("http://localhost:11434");
    }
    @Bean
    public ObservationRegistry observationRegistry() {
        return ObservationRegistry.NOOP;
    }
    @Bean
    @Primary
    public ChatModel qwenChatModel(OllamaApi ollamaApi, ObservationRegistry observationRegistry) {
        return new OllamaChatModel(
                ollamaApi,
                OllamaOptions.builder().model("qwen3.5:2b").build(),
                null,
                List.of(),
                observationRegistry,
                ModelManagementOptions.builder().build()
        );
    }
    @Bean("deepseekChatModel")
    public ChatModel deepseekChatModel(OllamaApi ollamaApi, ObservationRegistry observationRegistry) {
        return new OllamaChatModel(
                ollamaApi,
                OllamaOptions.builder().model("deepseek-r1:1.5b").build(),
                null,
                List.of(),
                observationRegistry,
                ModelManagementOptions.builder().build()
        );
    }
    @Bean
    public EmbeddingModel embeddingModel(OllamaApi ollamaApi, ObservationRegistry observationRegistry) {
        return new OllamaEmbeddingModel(
                ollamaApi,
                OllamaOptions.builder().model("qwen3-embedding:0.6b").build(),
                observationRegistry,
                ModelManagementOptions.builder().build()
        );
    }
}

2.AppConfig

package com.cxl.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * 全局配置類
 * 管理應(yīng)用的全局開關(guān)和配置
 */
@Data
@Component
@ConfigurationProperties(prefix = "app")
public class AppConfig {
    /**
     * 上下文感知全局開關(guān)
     * true: 啟用上下文感知
     * false: 禁用上下文感知
     */
    private boolean contextAwareEnabled = true;
    /**
     * 文件檢索默認(rèn)返回數(shù)量
     */
    private int defaultTopK = 3;
    /**
     * 會話超時時間(分鐘)
     */
    private int sessionTimeoutMinutes = 60;
    /**
     * 文件掃描的最大文件大?。ㄗ止?jié))
     */
    private long maxFileSize = 10 * 1024 * 1024; // 10MB
    /**
     * 支持的文件類型
     */
    private String[] supportedFileTypes = {
            "txt", "md", "json", "xml", "html", "css", "js", "java",
            "pdf", "pptx", "ppt",
            "jpg", "jpeg", "png", "gif", "webp",
            "mp4", "avi", "mov", "wmv"
    };
}

3.對話客戶端配置

package com.cxl.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class ChatClientConfig {
    @Bean
    @Primary
    public ChatClient qwenChatClient(ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
    @Bean("deepseekChatClient")
    public ChatClient deepseekChatClient(@Qualifier("deepseekChatModel") ChatModel chatModel) {
        return ChatClient.builder(chatModel).build();
    }
}

4.知識庫配置(向量數(shù)據(jù)庫)

package com.cxl.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "knowledge-base")
public class KnowledgeBaseConfig {
    private String defaultPath = "C:\\Users\\GT-JYW-3\\Documents\\doct";
    private String collectionName = "my_docs";
    private String host = "localhost";
    private int port = 6334;
    private int topK = 5;
    public String getDefaultPath() {
        return defaultPath;
    }
    public void setDefaultPath(String defaultPath) {
        this.defaultPath = defaultPath;
    }
    public String getCollectionName() {
        return collectionName;
    }
    public void setCollectionName(String collectionName) {
        this.collectionName = collectionName;
    }
    public String getHost() {
        return host;
    }
    public void setHost(String host) {
        this.host = host;
    }
    public int getPort() {
        return port;
    }
    public void setPort(int port) {
        this.port = port;
    }
    public int getTopK() {
        return topK;
    }
    public void setTopK(int topK) {
        this.topK = topK;
    }
}

5.系統(tǒng)配置

package com.cxl.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
 * System預(yù)制詞配置類
 * 用于管理不同模型的System預(yù)制詞
 */
@Data
@Component
@ConfigurationProperties(prefix = "system.settings")
public class SystemSettings {
    /**
     * 不同模型的System預(yù)制詞配置
     * key: 模型名稱
     * value: System預(yù)制詞內(nèi)容
     */
    private Map<String, String> prompts = new HashMap<>();
    /**
     * 默認(rèn)System預(yù)制詞
     */
    private String defaultPrompt = "你是一個有幫助的AI助手。";
    /**
     * 根據(jù)模型名稱獲取對應(yīng)的System預(yù)制詞
     * @param modelName 模型名稱
     * @return System預(yù)制詞
     */
    public String getSystemPrompt(String modelName) {
        return prompts.getOrDefault(modelName, defaultPrompt);
    }
}

7.接口

1.基本對話&流式返回

package com.cxl.controller;
import com.cxl.annotation.EnableChatHistory;
import com.cxl.service.ChatService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping
@RequiredArgsConstructor
public class ChatController {
    private final ChatService chatService;
    @EnableChatHistory
    @GetMapping("/chat")
    public String chat(
            @RequestParam String msg,
            @RequestParam(required = false) String sessionId,
            @RequestParam(defaultValue = "qwen") String model) {
        return chatService.chat(msg, model, sessionId);
    }
    @EnableChatHistory
    @GetMapping(value = "/chatS", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(
            @RequestParam String msg,
            @RequestParam(required = false) String sessionId,
            @RequestParam(defaultValue = "qwen") String model) {
        return chatService.chatStreamRaw(msg, model, sessionId);
    }
}

2.基于文件目錄對話

package com.cxl.controller;
import com.cxl.annotation.EnableChatHistory;
import com.cxl.service.ChatHistoryService;
import com.cxl.service.ChatService;
import com.cxl.service.FileSearchService;
import com.cxl.service.FileSearchService.DocumentInfo;
import com.cxl.service.SessionManagerService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/file")
@RequiredArgsConstructor
public class FileSearchController {
    private final FileSearchService fileSearchService;
    private final ChatService chatService;
    private final SessionManagerService sessionManagerService;
    private final ChatHistoryService chatHistoryService;
    @GetMapping("/scan")
    public Map<String, Object> scanDirectory(
            @RequestParam(required = false) String directory,
            @RequestParam(required = false) String sessionId) {
        if (sessionId != null) {
            SessionManagerService.SessionInfo session = sessionManagerService.getOrCreateSession(sessionId);
            session.updateLastActivity();
        }
        int fileCount = fileSearchService.scanDirectory(directory);
        Map<String, Object> result = new HashMap<>();
        result.put("status", "success");
        result.put("message", "文件夾掃描完成");
        result.put("fileCount", fileCount);
        return result;
    }
    @EnableChatHistory
    @GetMapping("/chat")
    public String fileChat(
            @RequestParam String msg,
            @RequestParam String sessionId,
            @RequestParam(defaultValue = "qwen") String model,
            @RequestParam(required = false) Boolean contextAware,
            @RequestParam(defaultValue = "3") int topK,
            @RequestParam(required = false) String fileType) {
        SessionManagerService.SessionInfo session = sessionManagerService.getOrCreateSession(sessionId);
        session.updateLastActivity();
        boolean useContextAware = session.isContextAware();
        if (contextAware != null) {
            useContextAware = contextAware;
            session.setContextAware(useContextAware);
        }
        Map<String, Object> filters = new HashMap<>();
        if (fileType != null && !fileType.isEmpty()) {
            filters.put("filetype", fileType);
        }
        List<DocumentInfo> documents = fileSearchService.search(msg, topK, filters);
        StringBuilder context = new StringBuilder();
        if (useContextAware && !documents.isEmpty()) {
            context.append("根據(jù)以下文件內(nèi)容回答問題:\n\n");
            for (DocumentInfo doc : documents) {
                context.append("文件: ").append(doc.filename)
                       .append("\n路徑: ").append(doc.path)
                       .append("\n內(nèi)容: ").append(doc.content.substring(0, Math.min(500, doc.content.length())))
                       .append("...\n\n");
            }
        }
        String prompt = context.toString() + "用戶問題: " + msg;
        return chatService.chat(prompt, model, sessionId);
    }
    @EnableChatHistory
    @GetMapping(value = "/chatS", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> fileChatStream(
            @RequestParam String msg,
            @RequestParam String sessionId,
            @RequestParam(defaultValue = "qwen") String model,
            @RequestParam(required = false) Boolean contextAware,
            @RequestParam(defaultValue = "3") int topK,
            @RequestParam(required = false) String fileType) {
        SessionManagerService.SessionInfo session = sessionManagerService.getOrCreateSession(sessionId);
        session.updateLastActivity();
        boolean useContextAware = session.isContextAware();
        if (contextAware != null) {
            useContextAware = contextAware;
            session.setContextAware(useContextAware);
        }
        Map<String, Object> filters = new HashMap<>();
        if (fileType != null && !fileType.isEmpty()) {
            filters.put("filetype", fileType);
        }
        List<DocumentInfo> documents = fileSearchService.search(msg, topK, filters);
        StringBuilder context = new StringBuilder();
        if (useContextAware && !documents.isEmpty()) {
            context.append("根據(jù)以下文件內(nèi)容回答問題:\n\n");
            for (DocumentInfo doc : documents) {
                context.append("文件: ").append(doc.filename)
                       .append("\n路徑: ").append(doc.path)
                       .append("\n內(nèi)容: ").append(doc.content.substring(0, Math.min(500, doc.content.length())))
                       .append("...\n\n");
            }
        }
        String prompt = context.toString() + "用戶問題: " + msg;
        return chatService.chatStreamRaw(prompt, model, sessionId);
    }
    @GetMapping("/clear")
    public Map<String, Object> clearAll(@RequestParam(required = false) String sessionId) {
        fileSearchService.clearAll();
        if (sessionId != null) {
            chatHistoryService.clearHistory(sessionId);
        }
        Map<String, Object> result = new HashMap<>();
        result.put("status", "success");
        result.put("message", sessionId != null
            ? "已清除向量數(shù)據(jù)庫和會話歷史記錄"
            : "已清除向量數(shù)據(jù)庫中的所有文檔");
        return result;
    }
    @GetMapping("/context")
    public Map<String, Object> setContextAware(
            @RequestParam String sessionId,
            @RequestParam boolean contextAware) {
        SessionManagerService.SessionInfo session = sessionManagerService.getOrCreateSession(sessionId);
        session.setContextAware(contextAware);
        Map<String, Object> result = new HashMap<>();
        result.put("status", "success");
        result.put("contextAware", contextAware);
        result.put("sessionId", sessionId);
        return result;
    }
}

8.Service

1.ChatHistoryService

package com.cxl.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
@ConfigurationProperties(prefix = "chat-history")
public class ChatHistoryService {
    private String keyPrefix = "chat:history:";
    private int maxSize = 20;
    private int expireDays = 7;
    private boolean enabled = true;
    private final StringRedisTemplate redisTemplate;
    public ChatHistoryService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    public void addMessage(String sessionId, String role, String content) {
        if (!enabled) {
            log.debug("Chat history is disabled, skipping save");
            return;
        }
        if (sessionId == null || sessionId.isEmpty()) {
            log.warn("sessionId is null or empty, skipping save");
            return;
        }
        String key = keyPrefix + sessionId;
        String message = String.format("%s|%s|%s",
            LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
            role,
            content
        );
        try {
            redisTemplate.opsForList().rightPush(key, message);
            redisTemplate.opsForList().trim(key, -maxSize, -1);
            redisTemplate.expire(key, expireDays, TimeUnit.DAYS);
            log.info("Saved message to Redis - sessionId: {}, role: {}, key: {}", sessionId, role, key);
        } catch (Exception e) {
            log.error("Failed to save message to Redis: {}", e.getMessage(), e);
        }
    }
    public List<ChatMessage> getHistory(String sessionId) {
        if (!enabled) {
            log.debug("Chat history is disabled, returning empty history");
            return new ArrayList<>();
        }
        if (sessionId == null || sessionId.isEmpty()) {
            log.warn("sessionId is null or empty, returning empty history");
            return new ArrayList<>();
        }
        String key = keyPrefix + sessionId;
        log.info("Getting history from Redis - sessionId: {}, key: {}", sessionId, key);
        try {
            List<String> rawMessages = redisTemplate.opsForList().range(key, 0, -1);
            if (rawMessages == null || rawMessages.isEmpty()) {
                log.info("No history found in Redis for sessionId: {}", sessionId);
                return new ArrayList<>();
            }
            log.info("Found {} messages in Redis for sessionId: {}", rawMessages.size(), sessionId);
            List<ChatMessage> messages = new ArrayList<>();
            for (String raw : rawMessages) {
                String[] parts = raw.split("\\|", 3);
                if (parts.length >= 3) {
                    ChatMessage msg = new ChatMessage();
                    msg.setTimestamp(parts[0]);
                    msg.setRole(parts[1]);
                    msg.setContent(parts[2]);
                    messages.add(msg);
                }
            }
            return messages;
        } catch (Exception e) {
            log.error("Failed to get history from Redis: {}", e.getMessage(), e);
            return new ArrayList<>();
        }
    }
    public String getLastUserMessage(String sessionId) {
        List<ChatMessage> history = getHistory(sessionId);
        for (int i = history.size() - 1; i >= 0; i--) {
            if ("user".equals(history.get(i).getRole())) {
                return history.get(i).getContent();
            }
        }
        return null;
    }
    public void clearHistory(String sessionId) {
        String key = keyPrefix + sessionId;
        redisTemplate.delete(key);
    }
    public String getKeyPrefix() { return keyPrefix; }
    public void setKeyPrefix(String keyPrefix) { this.keyPrefix = keyPrefix; }
    public int getMaxSize() { return maxSize; }
    public void setMaxSize(int maxSize) { this.maxSize = maxSize; }
    public int getExpireDays() { return expireDays; }
    public void setExpireDays(int expireDays) { this.expireDays = expireDays; }
    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }
    public static class ChatMessage {
        private String timestamp;
        private String role;
        private String content;
        public String getTimestamp() { return timestamp; }
        public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
        public String getRole() { return role; }
        public void setRole(String role) { this.role = role; }
        public String getContent() { return content; }
        public void setContent(String content) { this.content = content; }
    }
}

2.ChatService

package com.cxl.service;
import com.cxl.config.SystemSettings;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class ChatService {
    private final ChatClient qwenChatClient;
    private final ChatClient deepseekChatClient;
    private final SystemSettings systemSettings;
    private final ChatHistoryService chatHistoryService;
    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public ChatService(
            ChatClient qwenChatClient,
            @Qualifier("deepseekChatClient") ChatClient deepseekChatClient,
            SystemSettings systemSettings,
            ChatHistoryService chatHistoryService) {
        this.qwenChatClient = qwenChatClient;
        this.deepseekChatClient = deepseekChatClient;
        this.systemSettings = systemSettings;
        this.chatHistoryService = chatHistoryService;
    }
    public String chat(String msg, String model) {
        return chatWithHistory(msg, model, null);
    }
    public String chat(String msg, String model, String sessionId) {
        return chatWithHistory(msg, model, sessionId);
    }
    private String chatWithHistory(String msg, String model, String sessionId) {
        String startTime = LocalDateTime.now().format(formatter);
        log.info("========== 對話開始 ==========");
        log.info("開始時間: {}", startTime);
        log.info("使用模型: {}", model);
        log.info("用戶消息: {}", msg);
        if (sessionId != null) {
            log.info("會話ID: {}", sessionId);
        }
        ChatClient chatClient = getChatClient(model);
        String modelName = getModelName(model);
        String systemPrompt = systemSettings.getSystemPrompt(modelName);
        log.info("System預(yù)制詞: {}", systemPrompt);
        long startMillis = System.currentTimeMillis();
        ChatClient.ChatClientRequestSpec promptSpec = chatClient.prompt()
                .system(systemPrompt);
        if (sessionId != null) {
            List<ChatHistoryService.ChatMessage> history = chatHistoryService.getHistory(sessionId);
            if (!history.isEmpty()) {
                StringBuilder historyContext = new StringBuilder();
                historyContext.append("\n以下是之前的對話歷史:\n");
                for (ChatHistoryService.ChatMessage chatMsg : history) {
                    historyContext.append(chatMsg.getRole()).append(": ").append(chatMsg.getContent()).append("\n");
                }
                promptSpec = promptSpec.system(systemPrompt + historyContext.toString());
            }
        }
        String content = promptSpec.user(msg).call().content();
        long endMillis = System.currentTimeMillis();
        log.info("響應(yīng)耗時: {} ms", endMillis - startMillis);
        log.info("AI回復(fù): {}", content);
        log.info("========== 對話結(jié)束 ==========\n");
        return content;
    }
    public Flux<String> chatStreamRaw(String msg, String model) {
        return chatStreamWithHistory(msg, model, null);
    }
    public Flux<String> chatStreamRaw(String msg, String model, String sessionId) {
        return chatStreamWithHistory(msg, model, sessionId);
    }
    private Flux<String> chatStreamWithHistory(String msg, String model, String sessionId) {
        String startTime = LocalDateTime.now().format(formatter);
        log.info("========== 流式對話開始 ==========");
        log.info("開始時間: {}", startTime);
        log.info("使用模型: {}", model);
        log.info("用戶消息: {}", msg);
        if (sessionId != null) {
            log.info("會話ID: {}", sessionId);
        }
        ChatClient chatClient = getChatClient(model);
        String modelName = getModelName(model);
        String systemPrompt = systemSettings.getSystemPrompt(modelName);
        log.info("System預(yù)制詞: {}", systemPrompt);
        long startMillis = System.currentTimeMillis();
        ChatClient.ChatClientRequestSpec promptSpec = chatClient.prompt()
                .system(systemPrompt);
        if (sessionId != null) {
            List<ChatHistoryService.ChatMessage> history = chatHistoryService.getHistory(sessionId);
            if (!history.isEmpty()) {
                StringBuilder historyContext = new StringBuilder();
                historyContext.append("\n以下是之前的對話歷史:\n");
                for (ChatHistoryService.ChatMessage chatMsg : history) {
                    historyContext.append(chatMsg.getRole()).append(": ").append(chatMsg.getContent()).append("\n");
                }
                promptSpec = promptSpec.system(systemPrompt + historyContext.toString());
            }
        }
        return promptSpec.user(msg)
                .stream()
                .content()
                .doOnComplete(() -> {
                    long endMillis = System.currentTimeMillis();
                    log.info("響應(yīng)耗時: {} ms", endMillis - startMillis);
                    log.info("========== 流式對話結(jié)束 ==========\n");
                })
                .doOnError(error -> {
                    log.error("流式對話發(fā)生錯誤: {}", error.getMessage(), error);
                });
    }
    private ChatClient getChatClient(String model) {
        if ("deepseek".equalsIgnoreCase(model)) {
            return deepseekChatClient;
        }
        return qwenChatClient;
    }
    private String getModelName(String model) {
        if ("deepseek".equalsIgnoreCase(model)) {
            return "deepseek-r1:1.5b";
        }
        return "qwen3.5:2b";
    }
}

3.FileSearchService

package com.cxl.service;
import com.cxl.config.KnowledgeBaseConfig;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFSlide;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.Filter.Expression;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
public class FileSearchService {
    private final VectorStore vectorStore;
    private final KnowledgeBaseConfig knowledgeBaseConfig;
    public FileSearchService(VectorStore vectorStore, KnowledgeBaseConfig knowledgeBaseConfig) {
        this.vectorStore = vectorStore;
        this.knowledgeBaseConfig = knowledgeBaseConfig;
    }
    public int scanDirectory(String directoryPath) {
        String actualPath = (directoryPath == null || directoryPath.isEmpty())
                ? knowledgeBaseConfig.getDefaultPath()
                : directoryPath;
        log.info("開始掃描文件夾: {}", actualPath);
        List<Document> documents = new ArrayList<>();
        File directory = new File(actualPath);
        if (!directory.exists() || !directory.isDirectory()) {
            log.error("文件夾不存在或不是目錄: {}", actualPath);
            return 0;
        }
        try {
            scanFile(directory, documents);
            if (!documents.isEmpty()) {
                vectorStore.add(documents);
                log.info("成功掃描 {} 個文件并添加到向量數(shù)據(jù)庫", documents.size());
            }
        } catch (Exception e) {
            log.error("掃描文件夾失敗: {}", e.getMessage(), e);
            return 0;
        }
        return documents.size();
    }
    private void scanFile(File file, List<Document> documents) throws IOException {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    scanFile(f, documents);
                }
            }
        } else {
            String content = extractContent(file);
            if (content != null && !content.trim().isEmpty()) {
                Map<String, Object> metadata = new HashMap<>();
                metadata.put("filename", file.getName());
                metadata.put("path", file.getAbsolutePath());
                metadata.put("filetype", getFileType(file.getName()));
                metadata.put("extension", getFileExtension(file.getName()));
                metadata.put("size", (int) file.length());
                Document doc = Document.builder()
                        .id(UUID.randomUUID().toString())
                        .text(content)
                        .metadata(metadata)
                        .build();
                documents.add(doc);
            }
        }
    }
    private String extractContent(File file) {
        try {
            String extension = getFileExtension(file.getName()).toLowerCase();
            Path path = Paths.get(file.getAbsolutePath());
            if (extension.equals("txt") || extension.equals("md") || extension.equals("json") ||
                extension.equals("xml") || extension.equals("html") || extension.equals("css") ||
                extension.equals("js") || extension.equals("java") || extension.equals("py") ||
                extension.equals("go") || extension.equals("rs") || extension.equals("c") ||
                extension.equals("cpp") || extension.equals("h") || extension.equals("sql")) {
                return Files.readString(path);
            } else if (extension.equals("pptx") || extension.equals("ppt")) {
                return extractPptContent(file);
            } else {
                return String.format("文件名: %s\n路徑: %s\n文件大小: %d bytes\n文件類型: %s",
                        file.getName(), file.getAbsolutePath(), file.length(), getFileType(file.getName()));
            }
        } catch (Exception e) {
            log.warn("提取文件內(nèi)容失敗: {} - {}", file.getAbsolutePath(), e.getMessage());
            return null;
        }
    }
    private String extractPptContent(File file) {
        String extension = getFileExtension(file.getName()).toLowerCase();
        StringBuilder content = new StringBuilder();
        content.append("文件名: ").append(file.getName()).append("\n");
        content.append("路徑: ").append(file.getAbsolutePath()).append("\n\n");
        if (!extension.equals("pptx")) {
            return String.format("文件名: %s\n路徑: %s\n文件大小: %d bytes\n文件類型: PPT(僅支持.pptx格式)",
                    file.getName(), file.getAbsolutePath(), file.length());
        }
        try (FileInputStream fis = new FileInputStream(file);
             XMLSlideShow ppt = new XMLSlideShow(fis)) {
            List<XSLFSlide> slides = ppt.getSlides();
            for (int i = 0; i < slides.size(); i++) {
                XSLFSlide slide = slides.get(i);
                content.append("=== 第 ").append(i + 1).append(" 頁 ===\n");
                for (Object shape : slide.getShapes()) {
                    if (shape instanceof org.apache.poi.xslf.usermodel.XSLFTextShape) {
                        org.apache.poi.xslf.usermodel.XSLFTextShape textShape =
                            (org.apache.poi.xslf.usermodel.XSLFTextShape) shape;
                        String text = textShape.getText();
                        if (text != null && !text.trim().isEmpty()) {
                            content.append(text).append("\n");
                        }
                    }
                }
                content.append("\n");
            }
        } catch (Exception e) {
            log.warn("提取PPT內(nèi)容失敗: {} - {}", file.getAbsolutePath(), e.getMessage());
            return String.format("文件名: %s\n路徑: %s\n文件大小: %d bytes\n文件類型: PPT",
                    file.getName(), file.getAbsolutePath(), file.length());
        }
        return content.toString();
    }
    private String getFileType(String filename) {
        String extension = getFileExtension(filename).toLowerCase();
        switch (extension) {
            case "txt": case "md": case "json": case "xml": case "html":
            case "css": case "js": case "java": case "py": case "go": case "rs":
            case "c": case "cpp": case "h": case "sql":
                return "text";
            case "pdf": case "doc": case "docx": return "document";
            case "pptx": case "ppt": return "presentation";
            case "jpg": case "jpeg": case "png": case "gif": case "webp": return "image";
            case "mp4": case "avi": case "mov": case "wmv": return "video";
            default: return "other";
        }
    }
    private String getFileExtension(String filename) {
        int lastDotIndex = filename.lastIndexOf('.');
        return lastDotIndex > 0 ? filename.substring(lastDotIndex + 1) : "";
    }
    public List<DocumentInfo> search(String query, int topK, Map<String, Object> filters) {
        log.info("開始檢索: {}", query);
        try {
            int actualTopK = (topK > 0) ? topK : knowledgeBaseConfig.getTopK();
            SearchRequest.Builder builder = SearchRequest.builder()
                    .query(query)
                    .topK(actualTopK);
            if (filters != null && filters.containsKey("filetype")) {
                String filterType = (String) filters.get("filetype");
                Expression filter = new FilterExpressionBuilder()
                        .eq("filetype", filterType)
                        .build();
                builder.filterExpression(filter);
            }
            List<org.springframework.ai.document.Document> results = vectorStore.similaritySearch(builder.build());
            return results.stream()
                    .map(this::toDocumentInfo)
                    .collect(Collectors.toList());
        } catch (Exception e) {
            log.error("檢索失敗: {}", e.getMessage(), e);
            return new ArrayList<>();
        }
    }
    private DocumentInfo toDocumentInfo(org.springframework.ai.document.Document doc) {
        DocumentInfo info = new DocumentInfo();
        info.id = doc.getId();
        info.content = doc.getText();
        info.filename = (String) doc.getMetadata().getOrDefault("filename", "");
        info.path = (String) doc.getMetadata().getOrDefault("path", "");
        info.filetype = (String) doc.getMetadata().getOrDefault("filetype", "");
        info.extension = (String) doc.getMetadata().getOrDefault("extension", "");
        Object sizeObj = doc.getMetadata().get("size");
        info.size = (sizeObj != null) ? ((Number) sizeObj).longValue() : 0L;
        return info;
    }
    public void clearAll() {
        try {
            SearchRequest searchRequest = SearchRequest.builder()
                    .query("*")
                    .topK(1000)
                    .build();
            List<org.springframework.ai.document.Document> allDocs = vectorStore.similaritySearch(searchRequest);
            List<String> ids = allDocs.stream()
                    .map(org.springframework.ai.document.Document::getId)
                    .collect(Collectors.toList());
            if (!ids.isEmpty()) {
                vectorStore.delete(ids);
            }
            log.info("已清除向量數(shù)據(jù)庫中的所有文檔,共 {} 條", ids.size());
        } catch (Exception e) {
            log.error("清除向量數(shù)據(jù)庫失敗: {}", e.getMessage(), e);
        }
    }
    public static class DocumentInfo {
        public String id;
        public String filename;
        public String path;
        public String content;
        public String filetype;
        public String extension;
        public long size;
    }
}

4.SessionManagerService

package com.cxl.service;
import lombok.Data;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Service
public class SessionManagerService {
    private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<>();
    private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    private int sessionTimeoutMinutes = 60;
    public SessionManagerService() {
        executorService.scheduleAtFixedRate(this::cleanupExpiredSessions, 
                sessionTimeoutMinutes, 
                sessionTimeoutMinutes, 
                TimeUnit.MINUTES);
    }
    public SessionInfo getOrCreateSession(String sessionId) {
        return sessions.computeIfAbsent(sessionId, id -> {
            SessionInfo session = new SessionInfo(id);
            return session;
        });
    }
    public void removeSession(String sessionId) {
        sessions.remove(sessionId);
    }
    private void cleanupExpiredSessions() {
        long now = System.currentTimeMillis();
        long timeoutMs = sessionTimeoutMinutes * 60 * 1000;
        sessions.entrySet().removeIf(entry -> {
            SessionInfo session = entry.getValue();
            boolean expired = now - session.getLastActivityTime() > timeoutMs;
            return expired;
        });
    }
    @Data
    public static class SessionInfo {
        private final String sessionId;
        private long lastActivityTime;
        private boolean contextAware = true;
        public SessionInfo(String sessionId) {
            this.sessionId = sessionId;
            this.lastActivityTime = System.currentTimeMillis();
        }
        public void updateLastActivity() {
            this.lastActivityTime = System.currentTimeMillis();
        }
    }
}

9.向量數(shù)據(jù)庫初始化

package com.cxl;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class QdrantCollectionCreator {
    private static final String COLLECTION_NAME = "my_docs";
    private static final int VECTOR_DIMENSION = 1024;
    private static final String HOST = "localhost";
    private static final int PORT = 6333;
    public static void main(String[] args) {
        System.out.println("=== Qdrant Collection Creator ===");
        HttpClient client = HttpClient.newHttpClient();
        System.out.println("\n1. Checking Qdrant service availability...");
        String healthUrl = "http://" + HOST + ":" + PORT + "/";
        try {
            HttpRequest healthRequest = HttpRequest.newBuilder()
                    .uri(URI.create(healthUrl))
                    .GET()
                    .build();
            HttpResponse<String> healthResponse = client.send(healthRequest, HttpResponse.BodyHandlers.ofString());
            System.out.println("Qdrant service status: " + healthResponse.statusCode());
            System.out.println("Response: " + healthResponse.body());
        } catch (Exception e) {
            System.err.println("ERROR: Cannot connect to Qdrant at http://" + HOST + ":" + PORT);
            return;
        }
        System.out.println("\n2. Listing available collections...");
        String listUrl = "http://" + HOST + ":" + PORT + "/collections";
        try {
            HttpRequest listRequest = HttpRequest.newBuilder()
                    .uri(URI.create(listUrl))
                    .GET()
                    .build();
            HttpResponse<String> listResponse = client.send(listRequest, HttpResponse.BodyHandlers.ofString());
            System.out.println("Status: " + listResponse.statusCode());
            System.out.println("Response: " + listResponse.body());
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
        }
        System.out.println("\n3. Checking if collection exists...");
        String checkUrl = "http://" + HOST + ":" + PORT + "/collections/" + COLLECTION_NAME;
        try {
            HttpRequest checkRequest = HttpRequest.newBuilder()
                    .uri(URI.create(checkUrl))
                    .GET()
                    .build();
            HttpResponse<String> checkResponse = client.send(checkRequest, HttpResponse.BodyHandlers.ofString());
            System.out.println("Status: " + checkResponse.statusCode());
            if (checkResponse.statusCode() == 200) {
                System.out.println("Collection '" + COLLECTION_NAME + "' already exists!");
                return;
            }
        } catch (Exception e) {
            System.out.println("Collection does not exist. Will create it...");
        }
        System.out.println("\n4. Creating collection with PUT...");
        String createUrl = "http://" + HOST + ":" + PORT + "/collections/" + COLLECTION_NAME;
        String requestBody = String.format("""
            {
                "vectors": {
                    "size": %d,
                    "distance": "Cosine"
                }
            }
            """, VECTOR_DIMENSION);
        try {
            HttpRequest createRequest = HttpRequest.newBuilder()
                    .uri(URI.create(createUrl))
                    .header("Content-Type", "application/json")
                    .PUT(HttpRequest.BodyPublishers.ofString(requestBody))
                    .build();
            HttpResponse<String> createResponse = client.send(createRequest, HttpResponse.BodyHandlers.ofString());
            System.out.println("Status: " + createResponse.statusCode());
            System.out.println("Response: " + createResponse.body());
            if (createResponse.statusCode() == 200 || createResponse.statusCode() == 201) {
                System.out.println("\n? Collection '" + COLLECTION_NAME + "' created successfully!");
                return;
            }
        } catch (Exception e) {
            System.err.println("Error with PUT: " + e.getMessage());
        }
        System.out.println("\n5. Trying POST to /collections endpoint...");
        String postUrl = "http://" + HOST + ":" + PORT + "/collections";
        try {
            HttpRequest postRequest = HttpRequest.newBuilder()
                    .uri(URI.create(postUrl))
                    .header("Content-Type", "application/json")
                    .POST(HttpRequest.BodyPublishers.ofString(requestBody))
                    .build();
            HttpResponse<String> postResponse = client.send(postRequest, HttpResponse.BodyHandlers.ofString());
            System.out.println("Status: " + postResponse.statusCode());
            System.out.println("Response: " + postResponse.body());
            if (postResponse.statusCode() == 200 || postResponse.statusCode() == 201) {
                System.out.println("\n? Collection '" + COLLECTION_NAME + "' created successfully!");
            } else {
                System.err.println("\n? Failed to create collection");
            }
        } catch (Exception e) {
            System.err.println("Error with POST: " + e.getMessage());
        }
    }
}

10.啟動類

package com.cxl;
import com.cxl.config.AppConfig;
import com.cxl.config.SystemSettings;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@SpringBootApplication
@EnableConfigurationProperties({SystemSettings.class, AppConfig.class})
public class SpringaiDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringaiDemoApplication.class, args);
    }
}

到此這篇關(guān)于基于SpringAI+Qdrant+Ollama本地模型和向量數(shù)據(jù)庫開發(fā)問答和RAG檢索(完整代碼)的文章就介紹到這了,更多相關(guān)SpringAI 向量數(shù)據(jù)庫開發(fā)問答和RAG檢索內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot實現(xiàn)mysql與clickhouse多數(shù)據(jù)源的項目實踐

    SpringBoot實現(xiàn)mysql與clickhouse多數(shù)據(jù)源的項目實踐

    本文主要介紹了SpringBoot實現(xiàn)mysql與clickhouse多數(shù)據(jù)源的項目實踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • 詳解租約機(jī)制以及在hbase中的應(yīng)用

    詳解租約機(jī)制以及在hbase中的應(yīng)用

    這篇文章主要介紹了詳解租約機(jī)制以及在hbase中的應(yīng)用的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • PowerJob的QueryConvertUtils工作流程源碼解讀

    PowerJob的QueryConvertUtils工作流程源碼解讀

    這篇文章主要為大家介紹了PowerJob的QueryConvertUtils工作流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • 關(guān)于springboot響應(yīng)式編程整合webFlux的問題

    關(guān)于springboot響應(yīng)式編程整合webFlux的問題

    在springboot2.x版本中提供了webFlux依賴模塊,該模塊有兩種模型實現(xiàn):一種是基于功能性端點的方式,另一種是基于SpringMVC注解方式,今天通過本文給大家介紹springboot響應(yīng)式編程整合webFlux的問題,感興趣的朋友一起看看吧
    2022-01-01
  • IDEA加載項目沒有src目錄的問題及解決

    IDEA加載項目沒有src目錄的問題及解決

    這篇文章主要介紹了IDEA加載項目沒有src目錄的問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • MyBatis中操作類對象的實現(xiàn)

    MyBatis中操作類對象的實現(xiàn)

    在MyBatis框架中,操作類對象是用于執(zhí)行數(shù)據(jù)庫操作的核心對象,本文主要介紹了MyBatis中操作類對象的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2023-11-11
  • SpringBoot連接Redis2種模式解析

    SpringBoot連接Redis2種模式解析

    這篇文章主要介紹了SpringBoot連接Redis2種模式解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-05-05
  • 一文詳解Reactor模型與實現(xiàn)示例

    一文詳解Reactor模型與實現(xiàn)示例

    這篇文章主要為大家介紹了Reactor模型與實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • java+mysql模擬實現(xiàn)銀行系統(tǒng)

    java+mysql模擬實現(xiàn)銀行系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了java+mysql模擬實現(xiàn)銀行系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-05-05
  • Java實例化的幾種方法總結(jié)

    Java實例化的幾種方法總結(jié)

    這篇文章主要介紹了Java實例化的幾種方法總結(jié)的相關(guān)資料,需要的朋友可以參考下
    2017-04-04

最新評論

万全县| 苗栗市| 军事| 白玉县| 福海县| 汾阳市| 怀安县| 犍为县| 湖北省| 喀喇| 永川市| 遵义市| 星子县| 永善县| 敦化市| 双流县| 兴和县| 南阳市| 宾阳县| 探索| 湖州市| 越西县| 台中县| 吕梁市| 上杭县| 遵义县| 浪卡子县| 宣城市| 南和县| 林西县| 伊春市| 南京市| 卢湾区| 简阳市| 沐川县| 谷城县| 云浮市| 株洲市| 驻马店市| 湘潭市| 海林市|