基于Ollama部署Deepseek

基于Ollama部署Deepseek
YYT环境与模型准备
首先需要在本地拉取所需的模型文件:
推理模型:拉取 DeepSeek R1 (1.5b版本)
1
ollama pull deepseek-r1:1.5b
向量模型:拉取文本嵌入模型
1
ollama pull nomic-embed-text
两种响应方式
一、非流式响应
适用于不需要实时打字机效果的场景,直接返回完整结果。
1 | public Response<ChatResponse> generate( String model, |
二、流式响应(见后文RAG核心流程)
RAG(检索增强生成)
核心概念:为什么需要RAG?
大模型的回答本质上是没有“记忆”的,它只能根据你当前输入的Prompt进行预测。大模型之所以看起来“有记忆”,是因为我们每次对话时都把之前的聊天记录重新喂给了它。
RAG(Retrieval-Augmented Generation) 即知识检索增强,是为了解决大模型无法回答私有或最新数据的问题。
举个例子:
如果你直接问大模型:“我昨天晚上吃的啥?” 它肯定不知道,因为它没有你生活的训练数据。
但是,如果你提前将你的生活记录(知识库)存入向量数据库,当你提问时,系统会先去库里检索,找到“昨天晚上吃的红苕稀饭”这条记录,然后带着这个答案去问大模型。这时,它就能准确回复你。
下面是启用与不启用知识库的对比效果:

RAG 实现流程一:解析文件到向量库
这个过程主要包含四个步骤:解析、分块、编码、入库。
1. 数据库连通性检查与文件遍历
在处理文件前,先确保向量数据库(PGVector)连接正常,随后遍历上传的文件列表。
1 | try { |
2. 文档解析与切分
这是最关键的一步。我们首先利用 TikaDocumentReader 将各种格式(PDF/Word等)的文件转为纯文本,然后使用 TokenTextSplitter 将长文本切分为适合向量化的小块。
1 | // 读取文件内容 |
关于切分策略的配置:
Spring AI 提供了默认参数,我们也可以按需覆盖,防止空参异常:
defaultChunkSize = 800:默认切分大小 800 Tokens。minChunkSizeChars = 350:最小字符数,防止切出太碎的片段导致语义丢失。minChunkLengthToEmbed = 5:小于这个长度不进行向量化。maxNumChunks = 10000:防止大文件导致内存溢出。keepSeparator = true:保留分隔符,维持句子完整性。
3. 元数据打标与向量存储
为了区分不同知识库(比如“财务制度”和“技术文档”),我们需要在 Document 的 Metadata 中打上标签(ragTag)。
1 | // 给文档打上知识库标签 |
4. 更新缓存
最后,将知识库标签存入 Redis,方便前端快速查询当前有哪些知识库可用。
1 | RList<String> elements = redissonClient.getList("ragTag"); |
RAG 实现流程二:核心检索与生成
当用户发起提问时,系统需要判断是否“强制使用知识库”。流程如下:
- 用户指定知识库 -> 检索相关信息 -> 注入提示词 -> 模型回答。
- 用户未指定 -> 直接由模型自由回答。
方法入口:
1 | public Flux<ChatResponse> generateStream(String model, String message, String knowledgeBase) |
步骤 1:初始化与模式判断
指定使用的本地模型(deepseek-r1:1.5b),并判断是否开启 RAG 模式。
1 | OllamaOptions options = OllamaOptions.create().withModel(model); |
步骤 2:向量检索(Retrieval)
如果启用了知识库,我们需要把用户的自然语言问题转化为向量,在 PGVector 中搜索最相似的片段。
注意这里的过滤逻辑:
必须使用 withFilterExpression,确保只在当前选定的 knowledgeBase 范围内搜索,避免跨库串词。
1 | SearchRequest request = SearchRequest.query(message) |
步骤 3:构建上下文
将检索到的 5 段分散的文档内容,拼接成一个完整的字符串,作为“背景知识”喂给大模型。
1 | String documentsCollectors = documents.stream() |
步骤 4:构造系统提示词
这是防止大模型“胡说八道”的关键。我们需要精细设计提示词模板。
1 | String SYSTEM_PROMPT = """ |
提示词设计要点解析:
| 关键指令 | 作用描述 |
|---|---|
| Use … DOCUMENTS | 强制模型仅依据提供的材料回答,减少幻觉。 |
| act as if you knew… | 让模型“假装”这些知识是它自带的,使回答语气更自然,而不是机械地复述。 |
| If unsure… | 不知道就说不知道,确保回答的严谨性。 |
| reply in Chinese | 强制中文输出,防止模型突然飙英文。 |
| {documents} | 这是一个动态占位符,用于注入上一步检索到的文本。 |
步骤 5:流式调用与异常兜底
最后,将组装好的消息(用户问题 + 系统提示词)发送给大模型。
1 | // 组装消息 |
兜底策略:
为了保证系统的健壮性,如果向量检索过程(如数据库挂了)发生异常,我们捕获该异常并降级为普通对话模式,确保用户至少能得到一个回复,而不是报错页面。
1 | } catch (Exception ignored) { |


