使用Java、Ollama、DeepSeek与Milvus构建私有知识库问答系统的实践指南

在 AI 技术蓬勃发展的当下,利用大语言模型和向量数据库构建智能问答系统已成为一种趋势。本文将介绍如何使用 Java 结合 Ollama、DeepSeek 和 Milvus,实现一个基于私有知识库的问答系统,让 DeepSeek 根据构建的私有知识库回答用户问题。

一、项目概述

我们将实现一个系统,该系统能够:

  1. 使用 Ollama 提供的文本嵌入功能,将文本转换为向量。

  2. 将这些向量存储在 Milvus 向量数据库中。

  3. 使用 DeepSeek 模型根据用户问题生成回答。

  4. 结合 Milvus 中存储的私有知识库,提供更准确的问答服务。

二、环境准备

  1. 安装并运行 Milvus 服务

  2. 安装并运行 Ollama 服务器

  3. 下载DeepSeek及nomic-embed-text

stranger@StrangerdeMacBook-Pro ~ % ollama list
NAME                       ID              SIZE      MODIFIED     
deepseek-r1:1.5b           a42b25d8c10a    1.1 GB    27 hours ago    
bge-m3:latest              790764642607    1.2 GB    2 days ago      
nomic-embed-text:latest    0a109f422b47    274 MB    2 days ago      
deepseek-r1:7b             0a8c26691023    4.7 GB    11 days ago     

三、代码实现

1. Milvus 配置

首先,我们需要配置 Milvus 客户端,连接到 Milvus 服务器:

java复制

@Configuration
public class MilvusConfig {
    @Value("${milvus.host}")
    private String milvusHost;

    @Value("${milvus.port}")
    private int milvusPort;

    @Bean
    public MilvusClientV2 milvusClientV2() {
        ConnectConfig connectConfig = ConnectConfig.builder()
                .uri(String.format("http://%s:%d", milvusHost, milvusPort))
                .build();
        return new MilvusClientV2(connectConfig);
    }
}

application.yml 中配置 Milvus 的主机和端口:

yaml复制

milvus:
  host: localhost
  port: 19530

2. Milvus 服务实现

创建一个服务类 MilvusEmbeddingService,用于与 Milvus 交互:

java复制

@Service
public class MilvusEmbeddingService {
    private static final String COLLECTION_NAME = "user_data";
    private static final int VECTOR_DIM = 768;

    private final MilvusClientV2 milvusClientV2;

    @Autowired
    public MilvusEmbeddingService(MilvusClientV2 milvusClientV2) {
        this.milvusClientV2 = milvusClientV2;
    }

    @PostConstruct
    public void init() {
        createCollection();
    }

    public void addDocument(String text) {
        float[] vector = getEmbedding(text);

        JsonObject document = new JsonObject();
        document.addProperty("id", UUID.randomUUID().toString());
        document.addProperty("text", text);
        document.add("vector", getVectorJsonArray(vector));

        List<JsonObject> data = new ArrayList<>();
        data.add(document);

        InsertReq insertReq = InsertReq.builder()
                .collectionName(COLLECTION_NAME)
                .data(data)
                .build();

        InsertResp insertResp = milvusClientV2.insert(insertReq);
        System.out.println("Insert response: " + insertResp);
    }

    public List<String> searchDocuments(String queryText, int topK) {
        float[] queryVector = getEmbedding(queryText);

        SearchReq searchReq = SearchReq.builder()
                .collectionName(COLLECTION_NAME)
                .data(Collections.singletonList(new FloatVec(queryVector)))
                .topK(topK)
                .outputFields(Collections.singletonList("text"))
                .build();

        SearchResp searchResp = milvusClientV2.search(searchReq);
        List<String> relatedDocuments = new ArrayList<>();
        if (searchResp != null && searchResp.getSearchResults() != null && !searchResp.getSearchResults().isEmpty()) {
            for (SearchResp.SearchResult result : searchResp.getSearchResults().get(0)) {
                relatedDocuments.add(result.getEntity().get("text").toString());
                if (relatedDocuments.size() >= topK) {
                    break;
                }
            }
        }
        return relatedDocuments;
    }

    private float[] getEmbedding(String text) {
        try {
            JsonObject requestBody = new JsonObject();
            requestBody.addProperty("model", "nomic-embed-text");
            requestBody.addProperty("prompt", text);

            RequestBody body = RequestBody.create(
                    MediaType.parse("application/json; charset=utf-8"),
                    requestBody.toString()
            );

            Request request = new Request.Builder()
                    .url("http://localhost:11434/api/embeddings")
                    .post(body)
                    .build();

            Response response = new OkHttpClient().newCall(request).execute();
            if (!response.isSuccessful()) {
                System.out.println("Request failed: " + response.code());
                return new float[VECTOR_DIM];
            }

            String responseBody = response.body().string();
            JsonObject jsonResponse = new com.google.gson.JsonParser().parse(responseBody).getAsJsonObject();
            JsonArray embeddingsArray = jsonResponse.getAsJsonArray("embedding");

            if (embeddingsArray == null || embeddingsArray.size() == 0) {
                System.out.println("Empty embedding array");
                return new float[VECTOR_DIM];
            }

            float[] vector = new float[embeddingsArray.size()];
            for (int i = 0; i < embeddingsArray.size(); i++) {
                vector[i] = embeddingsArray.get(i).getAsFloat();
            }

            return vector;
        } catch (IOException e) {
            e.printStackTrace();
            return new float[VECTOR_DIM];
        }
    }

    private JsonArray getVectorJsonArray(float[] vector) {
        JsonArray jsonArray = new JsonArray();
        for (float value : vector) {
            jsonArray.add(value);
        }
        return jsonArray;
    }

    public void createCollection() {
        CreateCollectionReq.CollectionSchema schema = milvusClientV2.createSchema();
        schema.addField(AddFieldReq.builder()
                .fieldName("id")
                .dataType(DataType.VarChar)
                .isPrimaryKey(true)
                .autoID(false)
                .build());
        schema.addField(AddFieldReq.builder()
                .fieldName("text")
                .dataType(DataType.VarChar)
                .maxLength(1000)
                .build());
        schema.addField(AddFieldReq.builder()
                .fieldName("vector")
                .dataType(DataType.FloatVector)
                .dimension(VECTOR_DIM)
                .build());

        IndexParam indexParam = IndexParam.builder()
                .fieldName("vector")
                .metricType(IndexParam.MetricType.COSINE)
                .build();

        CreateCollectionReq createCollectionReq = CreateCollectionReq.builder()
                .collectionName(COLLECTION_NAME)
                .collectionSchema(schema)
                .indexParams(Collections.singletonList(indexParam))
                .build();

        milvusClientV2.createCollection(createCollectionReq);
        System.out.println("Collection created successfully");
    }
}

3. Ollama 和 DeepSeek 集成

在 Spring Boot 项目中,配置 Ollama 和 DeepSeek 的相关参数:

yaml复制

spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        model: deepseek-r1:1.5b
      embedding:
        model: nomic-embed-text:latest

4. 控制器实现

创建一个控制器 OllamaController,用于处理用户请求:

java复制

@RestController
@RequestMapping("/api/ollama")
public class OllamaController {
    @Autowired
    private MilvusEmbeddingService milvusService;
    @Autowired
    private OllamaChatModel ollamaChatModel;

    @PostMapping(value = "/chat", consumes = MediaType.APPLICATION_JSON_VALUE)
    public Flux<String> chatWithContext(@RequestBody Map<String, String> input) {
        String queryText = input.get("msg");

        // 将自定义文本内容存入向量数据库
        String data = getBaseData();
        String[] documents = data.split("\n");
        for (String doc : documents) {
            if (!doc.trim().isEmpty()) {
                milvusService.addDocument(doc);
            }
        }

        // 检索相关文档
        List<String> relatedDocuments = milvusService.searchDocuments(queryText, 3);

        // 生成带有上下文的提示
        String prompt = generatePromptWithContext(queryText, relatedDocuments);

        // 调用 Ollama API
        Flux<String> stream = ollamaChatModel.stream(prompt);

        return stream;
    }

    private String generatePromptWithContext(String userInput, List<String> context) {
        StringBuilder promptBuilder = new StringBuilder("以下是与您的问题相关的内容:\n");
        for (String doc : context) {
            promptBuilder.append(doc).append("\n");
        }
        promptBuilder.append("现在,请根据上述内容回答:").append(userInput);
        return promptBuilder.toString();
    }

    private String getBaseData() {
        return "姓名 年龄 手机号 地址\n" +
                "张三 25 13800138000 北京市朝阳区望京SOHO\n" +
                "李四 30 13900139000 上海市浦东新区陆家嘴\n" +
                "王五 28 13700137000 广州市天河区珠江新城\n" +
                "赵六 35 13600136000 深圳市南山区科技园\n" +
                "孙七 22 13400134000 成都市武侯区桐梓林\n" +
                "周八 26 13500135000 杭州市西湖区文三路\n" +
                "吴九 29 13300133000 南京市玄武区珠江路\n" +
                "郑十 32 13200132000 武汉市洪山区光谷\n" +
                "钱十一 27 13100131000 西安市雁塔区小寨\n" +
                "陈十二 31 13000130000 长沙市岳麓区梅溪湖\n" +
                "杨十三 24 15900159000 重庆市渝中区解放碑\n" +
                "黄十四 23 15800158000 苏州市姑苏区观前街\n" +
                "徐十五 33 15700157000 天津市南开区鞍山西道\n" +
                "李四 21 15600156000 沈阳市和平区三好街\n" +
                "何十七 34 15500155000 大连市中山区人民路\n" +
                "郭十八 20 15400154000 青岛市市南区香港中路\n" +
                "林十九 36 15300153000 厦门市思明区莲坂\n" +
                "马二十 27 15200152000 福州市鼓楼区五四路\n" +
                "罗二十一 30 15100151000 合肥市庐阳区三孝口\n" +
                "宋二十二 28 15000150000 南昌市东湖区八一大道\n" +
                "唐二十三 32 18800188000 昆明市盘龙区北京路\n" +
                "刘二十四 26 18700187000 长沙市芙蓉区五一广场\n" +
                "白二十五 29 18600186000 贵阳市南明区花果园\n" +
                "杜二十六 35 18500185000 南宁市青秀区民族大道\n" +
                "冯二十七 23 18400184000 哈尔滨市南岗区学府路\n" +
                "李四 24 18300183000 长春市朝阳区红旗街\n" +
                "董二十九 33 18200182000 石家庄市长安区中山路\n" +
                "彭三十 31 18100181000 郑州市金水区花园路";
    }
}

5. 相关依赖

        <!-- 识别图片 -->
        <dependency>
			<groupId>ai.djl.tensorflow</groupId>
			<artifactId>tensorflow-model-zoo</artifactId>
			<version>0.20.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>

		<!-- HTTP客户端 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId> 
		</dependency>

		<dependency>
			<groupId>io.milvus</groupId>
			<artifactId>milvus-sdk-java</artifactId>
			<version>2.5.4</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
		</dependency>

6. 配置文件

server:
  port: 8080
  servlet:
    context-path: /deepseek
spring:
  application:
    name: deepseek
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        model: deepseek-r1:1.5b
      embedding:
        model: nomic-embed-text:latest
milvus:
  host: localhost
  port: 19530
  collection:
    name: user_data
    dimension: 768  # Dimension of the embedding vector
    index-type: IVF_FLAT
    metric-type: L2

四、运行与测试

  1. 启动 Milvus 服务器和 Ollama 服务器。

  2. 启动 Spring Boot 应用程序。

  3. 使用thymeleaf简单做了个页面

  4. 效果如下:

从代码逻辑和设计来看,存在以下一些问题点, 本文只是简单demo,没做深究:

1. 固定数据的插入问题

  • 方法getBaseData() 返回固定文本数据,每次调用 /api/ollama/chat 接口时,都会调用 milvusService.addDocument(doc) 将这些固定数据存入 Milvus。

  • 问题

  • 每次请求都会插入相同的数据,导致 Milvus 数据库数据冗余。

  • 如果用户多次调用接口,数据库中会包含大量重复数据。

  • 数据的 ID 是通过 UUID.randomUUID().toString() 生成的,这不会重复,但 Milvus 集合中会出现大量重复的文本内容。

  • 2. 上下文文档数量固定

  • 方法searchDocuments(queryText, 3) 调用时,总是返回最多 3 条相关文档。

  • 问题

  • 3 条文档的限制是硬编码,不能灵活调整。

  • 如果用户问题需要更多上下文,或者更少的上下文,这个数字可能不够。

  • 需要根据实际情况考虑是否动态调整检索结果的数量。

  • 3. 提示字符串的格式问题

  • 方法generatePromptWithContext() 构造了一个固定的提示字符串格式,包含上下文内容和用户问题。

  • 问题

  • 提示的格式是否合理?是否符合 Ollama 模型的预期输入格式?

  • 如果上下文内容过长,提示字符串可能会超出模型的输入限制,导致生成失败。

  • 4. 流式响应的处理问题

  • 方法ollamaChatModel.stream(prompt) 返回一个流式响应。

  • 问题

  • 流式响应的格式是否在接口文档中定义清楚?

  • 客户端是否能够正确处理流式响应?

  • 如果 Ollama 模型返回异常数据,流式响应如何处理?

  • 5. 向量生成的效率问题

  • 方法getEmbedding() 每次都需要调用 Ollama API 获取文本的嵌入向量。

  • 问题

  • 如果每次处理请求都需要生成大量的向量,效率可能较低。

  • 缺少缓存机制,对相同的文本重复生成向量。

  • 6. 错误处理和异常日志

  • 问题

  • 代码中对异常的处理较为简单,大部分地方直接用 e.printStackTrace() 输出错误。

  • 缺少详细的日志记录,不利于问题的排查和调试。

  • 7. 资源释放问题

  • 问题

  • 使用了 OkHttp 客户端,但在 getEmbedding() 方法中没有明确关闭客户端。

  • OkHttp 的连接可能不会被及时释放,导致资源浪费。

  • 8. 配置的硬编码问题

  • 问题

  • Milvus 的集合名称 user_data 是硬编码的,如果需要动态调整,需要修改代码。

  • 数据字段的定义(如 idtextvector)也硬编码在代码中,灵活性较差。

  • 9. 数据一致性问题

  • 问题

  • 如果在并发环境下,多个请求同时插入或检索数据,是否会导致数据一致性问题?

  • Milvus 的插入和检索操作是否保证原子性?

  • 10. 扩展

    问答链(Chain)
    LangChain框架中定义的结构化处理流程,通过多个组件的链式调用完成知识检索、上下文整合、答案生成等任务‌

    典型链式操作类型(LangChain实现)

    1. MapReduceDocumentsChain
      将文档分块处理后分别分析,再汇总结果,适用于长文本处理‌。
    2. RefineDocumentsChain
      通过迭代优化逐步精炼答案,提升生成内容准确性‌。
    3. StuffDocumentsChain
      直接将所有相关文档片段拼接为上下文输入LLM,适合短文本场景‌。

    本文只是简单直接将所有相关文档片段拼接为上下文输入LLM,类似于StuffDocumentsChain

    方法 1:使用 Python 的 LangChain 并通过 REST API 调用

             2:在 Java 中手动实现问答链逻辑

            3:使用替代的 Java 库

                  虽然 LangChain 没有 Java SDK,但可以使用其他 Java 库和框架:

  •         Deeplearning4j:用于深度学习和自然语言处理。

  •         Wikipedia Library:用于知识检索和问答。

  • 五、总结

    通过结合 Java、Ollama、DeepSeek 和 Milvus,我们成功构建了一个基于私有知识库的问答系统。Milvus 提供了高效的向量存储和检索功能,Ollama 提供了强大的文本嵌入服务,而 DeepSeek 则负责生成自然语言回答。这种组合为构建智能问答系统提供了一个强大的技术栈。

    文章只是简单基于文本导入向量库,文件及图片等相关功能。。。。。。

    三天后。。。

    书接上回:

    像DeepSeek、Kimi这样的网页端如何实现识别上传的文件和图片,尤其是它们作为基于文本的语言模型,提到识别Word、PDF等文档,涉及到文档解析技术,需要多模态模型将非文本信息转化为文本,再交给语言模型处理,这里简单扩展一下文件处理

    添加依赖

    <!-- PDF解析 -->
    		<dependency>
    			<groupId>org.apache.pdfbox</groupId>
    			<artifactId>pdfbox</artifactId>
    			<version>2.0.27</version>
    		</dependency>
    
    		<!-- 支持XML解析 -->
    		<dependency>
    			<groupId>org.apache.xmlbeans</groupId>
    			<artifactId>xmlbeans</artifactId>
    			<version>5.1.1</version>
    		</dependency>
    
    		<dependency>
    			<groupId>commons-fileupload</groupId>
    			<artifactId>commons-fileupload</artifactId>
    			<version>1.4</version>
    		</dependency>
    
    		<!-- Apache Commons IO -->
    		<dependency>
    			<groupId>commons-io</groupId>
    			<artifactId>commons-io</artifactId>
    			<version>2.11.0</version>
    		</dependency>
    
    		<!-- Office文档解析 -->
    		<dependency>
    			<groupId>org.apache.poi</groupId>
    			<artifactId>poi</artifactId>
    			<version>5.2.3</version>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.poi</groupId>
    			<artifactId>poi-ooxml</artifactId>
    			<version>5.2.3</version>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.poi</groupId>
    			<artifactId>poi-scratchpad</artifactId>
    			<version>5.2.3</version>
    		</dependency>
    1. 文件解析器接口
    import java.io.InputStream;
    
    public interface FileParser {
        String parse(InputStream is) throws Exception;
    }
    2. 具体解析器实现
    
    import cn.com.skyvis.milvus.service.FileParser;
    import org.apache.poi.hwpf.HWPFDocument;
    
    import java.io.InputStream;
    
    public class DocParser implements FileParser {
        @Override
        public String parse(InputStream is) throws Exception {
            try (HWPFDocument doc = new HWPFDocument(is)) {
                return doc.getDocumentText().replaceAll("\u0007", "");
            }
        }
    }
    
    
    
    import cn.com.skyvis.milvus.service.FileParser;
    import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
    import org.apache.poi.xwpf.usermodel.XWPFDocument;
    import java.io.InputStream;
    
    public class DocxParser implements FileParser {
        @Override
        public String parse(InputStream is) throws Exception {
            try (XWPFDocument doc = new XWPFDocument(is)) {
                return new XWPFWordExtractor(doc).getText();
            }
        }
    }
    
    
    import cn.com.skyvis.milvus.service.FileParser;
    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.text.PDFTextStripper;
    import java.io.InputStream;
    
    public class PdfParser implements FileParser {
        @Override
        public String parse(InputStream is) throws Exception {
            try (PDDocument doc = PDDocument.load(is)) {
                return new PDFTextStripper().getText(doc);
            }
        }
    }
    
    
    
    import cn.com.skyvis.milvus.service.FileParser;
    import org.apache.poi.hslf.usermodel.HSLFSlideShow;
    import org.apache.poi.sl.extractor.SlideShowExtractor;
    import java.io.InputStream;
    
    public class PptParser implements FileParser {
        @Override
        public String parse(InputStream is) throws Exception {
            try (HSLFSlideShow slideshow = new HSLFSlideShow(is)) {
                SlideShowExtractor extractor = new SlideShowExtractor(slideshow);
                return extractor.getText();
            }
        }
    }
    
    
    import cn.com.skyvis.milvus.service.FileParser;
    import org.apache.poi.xslf.usermodel.XMLSlideShow;
    import org.apache.poi.xslf.usermodel.XSLFSlide;
    import org.apache.poi.xslf.usermodel.XSLFTextShape;
    import java.io.InputStream;
    import java.util.stream.Collectors;
    
    public class PptxParser implements FileParser {
        @Override
        public String parse(InputStream is) throws Exception {
            try (XMLSlideShow slideshow = new XMLSlideShow(is)) {
                return slideshow.getSlides().stream()
                        .map(this::extractTextFromSlide)
                        .collect(Collectors.joining("\n"));
            }
        }
    
        private String extractTextFromSlide(XSLFSlide slide) {
            return slide.getShapes().stream()
                    .filter(shape -> shape instanceof XSLFTextShape)
                    .map(shape -> ((XSLFTextShape) shape).getText().trim())
                    .filter(text -> !text.isEmpty())
                    .collect(Collectors.joining("\n"));
        }
    }
    
    
    import cn.com.skyvis.milvus.service.FileParser;
    import org.apache.commons.io.IOUtils;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.StandardCharsets;
    
    public class TxtParser implements FileParser {
        @Override
        public String parse(InputStream is) throws IOException {
            return IOUtils.toString(is, String.valueOf(StandardCharsets.UTF_8));
        }
    }
    
    
    import cn.com.skyvis.milvus.service.FileParser;
    import org.apache.poi.xssf.usermodel.XSSFWorkbook;
    import org.apache.poi.ss.usermodel.Sheet;
    import org.apache.poi.ss.usermodel.Row;
    import org.apache.poi.ss.usermodel.Cell;
    import java.io.InputStream;
    
    public class XlsxParser implements FileParser {
        @Override
        public String parse(InputStream is) throws Exception {
            StringBuilder sb = new StringBuilder();
            try (XSSFWorkbook workbook = new XSSFWorkbook(is)) {
                for (Sheet sheet : workbook) {
                    for (Row row : sheet) {
                        for (Cell cell : row) {
                            sb.append(cell.toString()).append("\t");
                        }
                        sb.append("\n");
                    }
                }
            }
            return sb.toString();
        }
    }
    3. 解析器分发器
    import cn.com.skyvis.milvus.service.impl.*;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.*;
    import java.util.HashMap;
    import java.util.Map;
    
    @Service
    public class FileParserDispatcher {
        private static final Map<String, FileParser> PARSERS = new HashMap<>();
    
        // 初始化解析器映射
        static {
            PARSERS.put("pdf", new PdfParser());
            PARSERS.put("doc", new DocParser());
            PARSERS.put("docx", new DocxParser());
            PARSERS.put("xlsx", new XlsxParser());
            PARSERS.put("ppt", new PptParser());
            PARSERS.put("pptx", new PptxParser());
            PARSERS.put("txt", new TxtParser());
        }
    
        public String parse(MultipartFile file) throws Exception {
            String ext = getFileExtension(file.getOriginalFilename());
            FileParser parser = PARSERS.getOrDefault(ext, new TxtParser());
            return parser.parse(file.getInputStream());
        }
    
        private static String getFileExtension(String filename) {
            return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
        }
    
        public static void main(String[] args) {
            File file = new File("/Users/stranger/Desktop/未命名.pptx");
            String ext = getFileExtension(file.getName());
            FileParser parser = PARSERS.getOrDefault(ext, new TxtParser());
            try {
                InputStream inputStream = new FileInputStream(file);
                String parse = null;
                try {
                    parse = parser.parse(inputStream);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                System.out.println("parse = " + parse);
            } catch (FileNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    4. SpringBoot控制器

    import cn.com.skyvis.milvus.service.FileParserDispatcher;
    import org.springframework.ai.ollama.OllamaChatModel;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.MediaType;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;
    import org.springframework.web.server.ResponseStatusException;
    import reactor.core.publisher.Flux;
    
    
    @RestController
    @RequestMapping("/api/files")
    public class FileController {
    
        @Autowired
        private FileParserDispatcher dispatcher;
        @Autowired
        private OllamaChatModel ollamaChatModel;
    
        @PostMapping(value = "/handleFileUpload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
        public Flux<String> handleFileUpload(
                @RequestPart("file") MultipartFile file,
                @RequestPart("msg") String queryText) {
    
            try {
                // 文件解析
                String textContent = dispatcher.parse(file);
    
                // 生成提示模板
                String prompt = generatePromptWithContext(queryText, textContent);
    
                // 流式响应
                return ollamaChatModel.stream(prompt)
                        .onErrorResume(e -> Flux.error(
                                new ResponseStatusException(
                                        HttpStatus.BAD_REQUEST,
                                        "模型服务异常: " + e.getMessage())
                        ));
    
            } catch (Exception e) {
                return Flux.error(new ResponseStatusException(
                        HttpStatus.INTERNAL_SERVER_ERROR,
                        "文件处理失败: " + e.getMessage()
                ));
            }
        }
    
        private String generatePromptWithContext(String userInput, String context) {
            return String.format("""
                以下是与您的问题相关的文件内容:
                %s
                现在,请根据上述内容回答:%s
                """, context, userInput);
        }
    }

    4. 运行结果

    以上是解析常用文件;至于图片就比较复杂了,文字类图片还好说,通过OCR即可提取文字,但这只是基于纯文本模型,如果想要识别图片的色彩和构图等元素,即像人类一样感知色彩的明暗对比、形状的组合方式、构图的视觉重心等,则可能需集成视觉模型(如深度学习中的卷积神经网络,CNN)来处理图片的视觉信息。。。。。先鸽

    作者:匿名。。

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用Java、Ollama、DeepSeek与Milvus构建私有知识库问答系统的实践指南

    发表回复