新闻详情

新闻详情

首页 / 资讯中心 / 详情

SpringBoot接入DeepSeek-R1:Ollama本地部署与调用实践

发布时间:2026/9/26 17:09:49来源:尧图网络
SpringBoot接入DeepSeek-R1:Ollama本地部署与调用实践
简介一套面向Java开发者的deepseek-r1本地免费调用示例工程基于Spring Boot与Spring AI构建目标是在不依赖云端服务的前提下帮助中小型团队在本地环境中快速接入大模型能力同时规避API调用费用与数据传输风险。压缩包共7个文件包含2个Java源文件、1个Maven工程配置文件、1个应用配置properties、1个README说明文档、1个gitignore及1个LICENSE协议pom.xml用于一键还原依赖整体仅10KB结构精简便于导入IDE直接阅读或二次开发。源码清晰展示了请求构造、响应解析、参数调优等关键环节配合说明文档中对Spring Boot与Spring AI集成步骤的梳理可帮助开发者理解Ollama本地模型的调用逻辑。目前该资源已有1462人学习下载。通过这份示例读者既能掌握本地部署在数据安全与运行稳定性上的优势又能获得一套可复用的项目脚手架适合作为入门参考或工程起点。 4.3 完整的请求-DTO结构第5章避坑5.1 现象→原因→解决 格式3-5条首次请求超时/卡住——模型加载到内存——预热流式响应中文乱码——编码问题——设置UTF-8num_ctx超过默认值导致显存溢出——Ollama默认2K上下文——调小并发请求排队——Ollama单模型串行——用OpenAI兼容接口或手动加副本本地返回token里全是思考内容——R1是推理模型——业务上过滤端口被占用或服务起不来第6章验证与进阶技巧——会话保持 400-700字。会话保持的实现用session id存历史消息数组每次请求带上历史消息。这是最脏但最有效的办法。 结尾用第一人称教训从那以后我每次都先确认模型类型希望帮到你。好现在我开始完整写作确保字数8000-12000。让我特别注意第一章只用##没子标题150-250字中间章每章至少2个###至少1800字避坑章是##级最后一章400-700字代码块要有注释后有参数说明段落4-6行每段80字不用嵌套子标题的###就是##和###两级不用mermaid、emoji现在按格式要求写完整正文。先粗写然后细化、扩写到足够字数。关于章节里内容的具体性Ollama在写这篇文章时是0.5.x版本但我不能编造版本号。我可以写ollama --version来查。实际上Ollama的API是/api/chat和/api/generateOpenAI兼容端点/v1/chat/completions。这些是稳定的可以写。DeepSeek-R1蒸馏模型的具体大小是deepseek-r1:1.5b约1.1GB7b约4.7GB8b约4.9GB14b约9.0GB32b约20GB70b约43GB。这些是Ollama库的常见数据可以写但为了避免编造我可以说“以ollama pull时显示的大小为准”也可以用常见推理来写。推理过程标签是 这是DeepSeek-R1的特性可以写。对于硬件要求我写的是大概值我要么不写精确的xx分钟要么用“一般需要”。OK。让我组织正文结构1. 把 DeepSeek-R1 拉进 SpringBoot 工程本地部署才是真正意义上的免费调用开头引入200字左右2. 先把 DeepSeek-R1 跑起来Ollama 部署与蒸馏版选型2.1 为什么用 Ollama 而不是 vLLM2.2 deepseek-r1 蒸馏版本怎么选参数、显存与场景匹配2.3 部署命令与接口自检3. SpringBoot 调用 DeepSeek-R1RestTemplate 封装与参数透传3.1 工程依赖与配置项3.2 最简调用一个 ChatService 走通 /api/chat3.3 参数透传temperature、top_p、num_ctx 的映射方式3.4 响应解析 推理标签怎么剥离4. 把体验做起来流式输出、超时控制与连接复用4.1 用 WebClient 接 SSE 流式响应4.2 请求超时与线程池参数4.3 连接复用与 HTTP 客户端选择5. DeepSeek-R1 本地调用避坑指南七个现象、原因与解决5-6条6. 会话保持的一个有效技巧把上下文交给调用方管理400-700字代码sessionId - messages)这样共6章。目标字数第1章200第2-4章型中间章各2000避坑章1800第6章600。总共约8500-9000字。够。现在正式写作。注意语言口语化、避免AI味道。让我逐章写并计算。第1章## 1. 把 DeepSeek-R1 拉进 SpringBoot 工程本地部署才是真正意义上的免费调用 大多数团队接入大语言模型的第一反应是注册厂商 API一边调接口一边盯着账单。DeepSeek-R1 开源之后这个问题变成了另一条路模型权重公开官方还提供 1.5B 到 70B 的蒸馏版本一张消费级显卡就能跑起来。这意味着我们可以把模型部署在本机或者内网业务系统通过 SpringBoot 走 HTTP 调用彻底绕开按 token 计费的 API 通道。这篇文章就是把「本地部署模型 SpringBoot 封装调用」这条链路完整拆开来讲从 Ollama 运行时选型、蒸馏版本怎么挑到 RestTemplate 请求封装、流式输出、超时控制和典型踩坑全部按可复现的方式写。适合两类人一类是手上有 RTX 3060 级别显卡、想把 AI 能力接进现有后端服务的 Java 工程师另一类是公司对数据出域敏感、必须私有化部署 NLP 能力的项目负责人。约230字。第2章展开2.1 为什么用 Ollama 而不是 vLLM说明选型。对比表可以做。2.2 deepseek-r1 蒸馏版本怎么选表格模型名、体积、推荐显存、适用场景。1.5b/7b/8b/14b/32b/70b——但我不该写精确体积因为不同量化版本大小不同。我写“Ollama 仓库中标记的体积大约为 X”这个在Ollama官网是公开的。可以写为参考值。我用“约”字。deepseek-r1:1.5b 约1.1GB deepseek-r1:7b 约4.7GB deepseek-r1:8b 约4.9GB deepseek-r1:14b 约9.0GB deepseek-r1:32b 约20GB deepseek-r1:70b 约43GB这些都是Ollama库的公开数据Q4_K_M量化可以写。2.3 部署命令与自检bash代码。第3章SpringBoot调用 依赖、application.yml、Service代码、参数映射、think标签处理。第4章优化 WebClient SSE代码、超时池化等。第5章避坑第6章会话保持。开写正文。注意全文用中文标点代码块用英文标点没问题。所有代码块都要语言标注。代码内注释要中文。代码结构注意Java代码尽量标准需要import吗不写完整import也可以但给出类级别的关键import更专业。我加上必要的import。让我现在写全文注意不输出任何前置后置说明直接从## 1开始。 ## 1. 把 DeepSeek-R1 拉进 SpringBoot 工程本地部署才是真正意义上的免费调用大多数团队接入大语言模型的第一反应是注册厂商 API一边调接口一边盯着账单。DeepSeek-R1 开源之后这个问题变成了另一条路模型权重公开官方还提供 1.5B 到 70B 的蒸馏版本一张消费级显卡就能跑起来。这意味着我们可以把模型部署在本机或者内网业务系统通过 SpringBoot 走 HTTP 调用彻底绕开按 token 计费的 API 通道。这篇文章就是把「本地部署模型 SpringBoot 封装调用」这条链路完整拆开来讲从 Ollama 运行时选型、蒸馏版本怎么挑到 RestTemplate 请求封装、流式输出、超时控制和典型踩坑全部按可复现的方式写。适合两类人一类是手上有 RTX 3060 级别显卡、想把 AI 能力接进现有后端服务的 Java 工程师另一类是公司对数据出域敏感、必须私有化部署 NLP 能力的项目负责人。2. 先把 DeepSeek-R1 跑起来Ollama 部署与蒸馏版选型2.1 为什么用 Ollama 而不是 vLLM本地跑大模型可选的运行时不少vLLM 吞吐高但配置复杂llama.cpp 效率不错但要自己管模型文件对 SpringBoot 开发者都不够友好。我一般会推荐 Ollama 作为起点原因是它把「下载模型、启动服务、暴露接口」三步全都做成了开箱即用的事。一条命令装好ollama pull拉模型服务默认挂在 11434 端口自动带上了原生 API 和 OpenAI 兼容接口后端工程不需要引入任何深度学习组件。用 Ollama 还有一个容易被忽略的好处模型文件是统一管理的ollama list能看本地全部模型ollama rm能清掉不用的版本。你可以在同一台机器上同时放 7B 和 14B 两个蒸馏版跑业务接口时按场景切换 model 名称就行。相比 vLLM 要自己写 deployment 配置、维护磁盘里的模型目录这个管理成本低很多。下面是三个运行时选型的对比方便你按团队情况判断。运行时安装成本管理方式适合场景Ollama单命令安装模型自动拉取命令管理无额外组件中小团队、单机或内网部署、快速原型vLLM需要 Python 环境和显存规划手动配置模型路径与推理参数高并发场景、生产级吞吐、K8s 部署llama.cpp需要编译或下载二进制手动处理 gguf 与启动参数无 GPU 的低配服务器、嵌入式设备2.2 deepseek-r1 蒸馏版本怎么选参数、显存与场景匹配DeepSeek-R1 原版是 671B 的 MoE 模型本地普通机器跑不动日常使用基本都在官方蒸馏版本上做选择。Ollama 仓库里的 deepseek-r1 系列覆盖了从 1.5B 到 70B 六个规格体积和显存需求差别很大。我的习惯是根据「业务并发量 现有显卡 回答质量要求」三者取中间值来定。模型名量化后体积最低显存参考适用场景deepseek-r1:1.5b约 1.1 GB纯 CPU 可跑文本分类、关键词抽取、原型验证deepseek-r1:7b约 4.7 GB6 GB代码生成、通用对话、大多数后端场景deepseek-r1:8b约 4.9 GB6 GB同 7B回答风格略有差异deepseek-r1:14b约 9.0 GB12 GB逻辑推理、长文本分析deepseek-r1:32b约 20 GB24 GB复杂任务、高精度要求deepseek-r1:70b约 43 GB48 GB接近满血效果硬件门槛高在 SpringBoot 集成场景里7B 是最稳妥的起点显存要求低CPU 也能勉强推理回答质量对大部分业务足够了。如果你做的是复杂 SQL 生成或链路日志分析直接上 14B别在 7B 上反复调 prompt。有一个容易踩的误区只看模型体积不看上下文长度。Ollama 默认上下文是 2048 tokenR1 系列是推理模型思考过程会大量消耗 token后面讲到 num_ctx 参数时你会看到这个坑有多大。2.3 部署命令与接口自检安装和拉模型的过程不复杂但有几个命令建议按顺序执行避免后面排查困难。Linux 服务器用下面这条安装macOS 和 Windows 直接去官网下安装包# Linux 一键安装 Ollama curl -fsSL https://ollama.com/install.sh | sh # 查看安装结果 ollama --version # 拉取 7B 蒸馏版模型等待进度条完成 ollama pull deepseek-r1:7b # 查看本地已有模型 ollama listollama pull拉的是 Q4 量化版文件大小和上面的表格基本一致。拉取完成后不要急着写代码先用命令行验证模型能正常响应。这是我认为最重要的一步它能帮你把「模型问题」和「代码问题」隔离清楚# 命令行直接对话验证模型可用 ollama run deepseek-r1:7b 用一句话介绍 SpringBoot 的自动配置原理 # 验证 HTTP 接口是否正常返回 curl http://localhost:11434/api/chat \ -H Content-Type: application/json \ -d {model:deepseek-r1:7b,messages:[{role:user,content:你好}],stream:false}注意首次请求会有模型加载延迟。Ollama 是把模型读入显存后才开始推理7B 模型在机械硬盘上可能等 20 秒以上SSD 会快很多。curl 返回 JSON 后里面的message.content字段就是模型输出往往带着think开头的推理过程这个问题留到第三章专门处理。3. SpringBoot 调用 DeepSeek-R1RestTemplate 封装与参数透传3.1 工程依赖与配置项SpringBoot 侧不需要任何 AI 专用依赖标准spring-boot-starter-web就够用。这里有一个选型取舍Spring AI 官方对接了 Ollama但它的抽象层对很多后端团队反而增加了理解成本而且版本更新很快。我更习惯用 RestTemplate 直接调 HTTP 接口代码量少、控制力强、排查方便。新建工程时只保留 Web 依赖即可dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency接下来在application.yml里把 Ollama 地址和模型名配成外部化参数不要写死在代码里。这样换 14B 模型或者把服务迁到内网其他机器时只改配置不用发版deepseek: base-url: http://localhost:11434 model: deepseek-r1:7b timeout: 60s这里单独定义一个deepseek前缀是为了和业务配置区分开。timeout 设置成 60 秒是有意的R1 的推理过程比较长非流式接口在 7B 模型上首次调用就有可能要等十几秒默认的连接超时太短会直接报错。3.2 最简调用一个 ChatService 走通 /api/chat配置类绑定ConfigurationProperties然后写一个独立的 Service 封装聊天请求。我先给完整实现再逐行拆参数import com.fasterxml.jackson.databind.JsonNode; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.HashMap; import java.util.List; import java.util.Map; Service public class DeepSeekChatService { private final RestTemplate restTemplate; private final DeepSeekProperties properties; public DeepSeekChatService(RestTemplate restTemplate, DeepSeekProperties properties) { this.restTemplate restTemplate; this.properties properties; } public String chat(String userMessage) { // 构建 Ollama /api/chat 请求体 MapString, Object payload new HashMap(); payload.put(model, properties.getModel()); payload.put(stream, false); // 消息结构为 {role, content} 列表 MapString, String message new HashMap(); message.put(role, user); message.put(content, userMessage); payload.put(messages, List.of(message)); // 发起 POST 请求 HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object entity new HttpEntity(payload, headers); ResponseEntityJsonNode response restTemplate.exchange( properties.getBaseUrl() /api/chat, HttpMethod.POST, entity, JsonNode.class); // 提取回复内容 JsonNode body response.getBody(); if (body null || !body.has(message)) { throw new RuntimeException(DeepSeek 响应异常: body); } return body.path(message).path(content).asText(); } }这段代码里最核心的是请求体结构Ollama 的/api/chat接口接收model、messages、stream三个必填字段。messages是数组每个元素必须有role和content和 OpenAI 的对话格式一致。stream必须显式设成false不设置时默认也是 false但写出来能让读代码的人立刻知道这是个非流式调用。响应解析要走message.content路径不是直接取body.content这是容易看错的地方。RestTemplate.exchange方法接收 HttpEntity 和响应类型我直接用JsonNode接收避免为响应体专门写一个 DTO 类。如果你后面要接入更多模型能力再考虑把响应体抽象成统一结构。3.3 参数透传temperature、top_p、num_ctx 的映射方式直接把命令行的对话接进业务系统还不够R1 默认参数并不能适应所有场景。Ollama 的/api/chat支持在请求体重传options对象下面是完整可用的带参数版本MapString, Object options new HashMap(); options.put(temperature, 0.2); // 降低随机性代码生成更稳定 options.put(top_p, 0.9); // 核采样控制候选词范围 options.put(num_ctx, 8192); // 上下文窗口R1 推理消耗大 payload.put(options, options);这三个参数是后端接入时最常调的。temperature控制输出随机性代码生成和 SQL 生成建议 0.1 到 0.3通用对话可以放到 0.6 到 0.7。top_p与 temperature 是配合关系通常固定 0.9 不动不要两个同时激进地调。num_ctx是 Ollama 特有的上下文长度参数默认只有 2048这意味着模型只能看到最近 2048 个 token 的对话内容。DeepSeek-R1 的思考过程很容易就把这个窗口塞满导致回答到一半「失忆」。我通常起步直接设 8192如果你的显存够甚至可以上 16384。注意num_ctx设置太大会直接增加 KV Cache 显存占用7B 模型在 6GB 显卡上开到 8192 可能就接近极限了。这个参数要在显存和上下文长度之间做取舍没有银弹。3.4 响应解析think推理标签怎么剥离调用通了之后你会立刻发现一个问题R1 返回的内容开头带着一长串推理过程被think和/think包住后面才是真正回答用户的文本。这是深度求索故意保留的思维链输出。直接把这个原始文本返回给前端用户不仅显得怪异还会暴露模型内部推理逻辑。处理方式很简单在返回前做一次过滤找到/think标签取它后面的部分作为最终回复。我一般把这段逻辑放在 Service 里不算业务层的事public String extractAnswer(String rawContent) { String marker /think; int index rawContent.indexOf(marker); if (index 0) { return rawContent.substring(index marker.length()).trim(); } return rawContent; }调用方拿到的是剥离后的干净回答think部分只在排查问题时手动看。这里提醒一句不要试图用正则去匹配think开头的标签深度求索在部分场景会输出嵌套的尖括号内容简单的indexOf从尾部找反而最可靠。4. 把体验做起来流式输出、超时控制与连接复用4.1 用 WebClient 接 SSE 流式输出非流式接口在 7B 模型上生成一段回答可能要等 10 到 30 秒前端一直转圈体验很差。Ollama 的/api/chat支持 SSE 流式返回把stream设成 true每生成一部分 token 就推送一个 JSON。SpringBoot 里用 WebClient 接流式响应最方便它对 SSE 的解析是原生的。# 先用 curl 验证流式返回的数据格式 curl http://localhost:11434/api/chat \ -H Content-Type: application/json \ -d {model:deepseek-r1:7b,messages:[{role:user,content:写一个快速排序}],stream:true}curl 输出你会看到一行接一行的 JSON每行都有message.content字段这是增量内容。SpringBoot 侧用 WebClient 的retrieve().bodyToFlux()接收import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Flux; Service public class DeepSeekStreamService { private final WebClient webClient; public DeepSeekStreamService() { // 基础 URL 指向 Ollama连接超时设 5 秒读取超时留长 this.webClient WebClient.builder() .baseUrl(http://localhost:11434) .codecs(configurer - configurer.defaultCodecs() .maxInMemorySize(10 * 1024 * 1024)) .build(); } public FluxString streamChat(String userMessage) { MapString, Object payload new HashMap(); payload.put(model, deepseek-r1:7b); payload.put(stream, true); payload.put(messages, List.of(Map.of(role, user, content, userMessage))); return webClient.post() .uri(/api/chat) .bodyValue(payload) .accept(MediaType.TEXT_EVENT_STREAM) .retrieve() .bodyToFlux(JsonNode.class) .filter(node - node.has(message)) .map(node - node.path(message).path(content).asText()); } }这段代码的关键是bodyToFlux(JsonNode.class)每次 SSE 推送的 JSON 会被自动反序列化成 JsonNode然后filter过滤掉没有message字段的元数据行map取出增量内容。前端拿到这个 Flux 后可以通过 WebSocket 或者 Spring MVC 的text/event-stream再转发给浏览器。注意 WebClient 默认内存缓冲区只有 256KBR1 单条回复动辄几千 token不调大maxInMemorySize很容易报DataBufferLimitException。4.2 请求超时与线程池参数RestTemplate 默认用的是 JDK 连接超时往往只有 3 秒对大模型推理这种慢接口来说等于不可用。要在配置里显式设置连接超时和读取超时读取超时才是关键import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.web.client.RestTemplate; Configuration public class HttpClientConfig { Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory factory new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(5_000); // 建立连接最长等 5 秒 factory.setReadTimeout(120_000); // 等待响应最长等 120 秒 return new RestTemplate(factory); } }读超时给到 120 秒不是随手写的。R1 的非流式调用在 CPU 上推理时7B 模型生成 500 token 就可能要 40 秒以上60 秒超时在高峰时仍可能触发。给了 120 秒后绝大多数情况能兜住。线程池也要单独考虑SpringBoot 默认的 Tomcat 工作线程数是 200如果一个请求占住线程等模型输出 30 秒20 个并发就能拖垮整个应用。推荐给模型调用单独配一个线程池并限制最大连接数让 AI 请求排在队列里而不是占满核心线程。4.3 连接复用与长连接参数Ollama 本身是 HTTP 服务每次新建连接会有 TCP 握手开销。默认的SimpleClientHttpRequestFactory每次请求都新建连接高并发下会有性能损耗。这里一个常见做法是用 Apache HttpClient 5 或 OkHttp 做底层连接池。我用 Apache HttpClient 做演示import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; Configuration public class PooledHttpClientConfig { Bean public RestTemplate pooledRestTemplate() { PoolingHttpClientConnectionManager manager new PoolingHttpClientConnectionManager(); manager.setMaxTotal(20); // 总连接数上限 manager.setDefaultMaxPerRoute(10); // 单路由并发上限 CloseableHttpClient client HttpClients.custom() .setConnectionManager(manager) .evictExpiredConnections() .build(); HttpComponentsClientHttpRequestFactory factory new HttpComponentsClientHttpRequestFactory(client); factory.setConnectTimeout(5_000); return new RestTemplate(factory); } }连接池的setMaxTotal(20)决定了同时能向 Ollama 发起多少个请求。注意这里的 20 是 HTTP 连接层面的并发Ollama 的模型推理是串行的连接池再大模型也只能一个一个处理所以别把这个值设太大。这个池化配置的意义在于频繁的chat调用复用已有连接避免每次都走三次握手同时限流保护 Ollama不会因为几十个并发请求直接把服务压崩。5. DeepSeek-R1 本地调用避坑指南六个高频问题的现象、原因与解法5.1 首次请求卡了半分钟甚至直接超时现象SpringBoot 接口第一次调用时报Read timed out第二次调用却正常。原因ollama pull只是把模型文件下载到磁盘模型并没有加载进内存。第一次发起推理时Ollama 需要把几 GB 的权重文件读入显存机械硬盘上这个加载过程可能长达 30 秒以上。如果没有预热任何 HTTP 超时配置都兜不住。解决应用启动后主动发一次空请求预热模型让模型常驻显存。可以在 SpringBoot 的ApplicationRunner里做这个事Component public class ModelWarmupRunner implements ApplicationRunner { private final DeepSeekChatService chatService; public ModelWarmupRunner(DeepSeekChatService chatService) { this.chatService chatService; } Override public void run(ApplicationArguments args) { chatService.chat(你好); } }这一步把模型加载时间从请求链路里剥离出来。如果你的服务经常重启每次都要多等这段时间。5.2 模型一直输出think推理内容现象返回给用户的文本带大量thinking过程甚至只有推理没有结论。原因DeepSeek-R1 是推理模型为了保证逻辑正确模型会强制先生成思考过程再输出答案。这是模型本身的特性不是 bug。解决业务接口返回前剥离think.../think内容。第三章已经给了extractAnswer方法这里再强调一个细节有的回答是思考过程结束后直接结束没有/think后续内容也就是模型认为不需要回答了。提取时indexOf找不到标记就直接返回原始内容别让 NPE 把接口打挂。5.3 回答到一半「失忆」前面的要求全忘现象多轮对话时模型第三轮开始就把系统提示词里的要求忘掉答非所问。原因Ollama 默认num_ctx2048也就是上下文窗口只有 2048 个 token。R1 的思考过程动辄几百上千 token一轮对话就占掉大半个窗口轮次稍多旧消息就被挤出去了。解决在请求的options里显式设置num_ctx。先看显卡显存用ollama ps能看到当前模型实际占用的显存。7B 模型开 8192 上下文一般在 6GB 显卡上能跑如果要长对话要么升 32B 更大显存要么在业务层主动裁剪历史消息只传最近三轮对话。5.4 并发一高接口就集体超时现象压测时 10 个并发请求全部堆积前端全部转圈。原因Ollama 对同一个模型是串行推理的后到的请求排队等待不是并行处理。请求全压在 SpringBoot 的 Tomcat 工作线程上线程占满后所有接口都跟着卡。解决给 AI 调用单独设置线程池信号量限流拒绝超额请求。参考第四章的配置线程池核心线程数设置成 4 到 8 就够了不要和模型吞吐量脱节。如果是生产环境可以考虑部署两三个 Ollama 实例SpringBoot 侧做简单轮询。5.5 中文乱码或特殊字符被截断现象返回的中文偶尔出现?或乱码emoji 直接丢失。原因SSE 流式传输时编码指定不对或者 WebClient 的 buffer 不够大完整 JSON 被截断后反序列化失败。解决Ollama 服务和 SpringBoot 都强制 UTF-8。WebClient 侧把maxInMemorySize调到 10MB 以上。如果你通过 Servlet 的text/event-stream转发记得设置response.setCharacterEncoding(UTF-8)。5.6 Ollama 服务起不来端口被占用现象启动后 11434 端口无响应ollama serve报错。原因macOS 和某些 Linux 发行版上Ollama 安装后会自动注册开机启动。如果已经有一个实例占住了端口新起的进程就会失败。解决先看进程列表ps aux | grep ollama找到已有进程不要重复ollama serve。Windows 上检查系统托盘的 Ollama 图标右键退出后重新启动。日志在~/.ollama/logs目录下起不来时先看这里的输出。6. 会话保持的一个有效技巧把上下文交给调用方管理把 DeepSeek-R1 接进业务系统后第一个绕不开的问题是多轮对话怎么保持记忆。Ollama 的/api/chat和无状态 HTTP 接口一样每次请求都是独立的模型不记得之前的对话内容。很多人的第一反应是把 session 存在服务端内存里用 ConcurrentHashMap 维护 sessionId 到历史消息的映射这个做法小规模没问题但服务重启数据就丢了集群部署时还要引入 Redis。实际我用的方案更简单把历史消息数组直接放在请求体里由调用方自己维护上下文。public String chatWithHistory(ListMapString, String messages) { MapString, Object payload new HashMap(); payload.put(model, deepseek-r1:7b); payload.put(stream, false); payload.put(messages, messages); MapString, Object options new HashMap(); options.put(num_ctx, 8192); payload.put(options, options); HttpHeaders headers new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object entity new HttpEntity(payload, headers); ResponseEntityJsonNode response restTemplate.exchange( http://localhost:11434/api/chat, HttpMethod.POST, entity, JsonNode.class); String rawContent response.getBody().path(message).path(content).asText(); return extractAnswer(rawContent); }messages参数是一个从系统到历轮对话的完整数组结构是这样的ListMapString, String messages new ArrayList(); messages.add(Map.of(role, system, content, 你是业务助手回答简洁)); messages.add(Map.of(role, user, content, 什么是三级缓存)); messages.add(Map.of(role, assistant, content, Spring 三级缓存是解决循环依赖的机制…)); messages.add(Map.of(role, user, content, 那第一级缓存是什么));每次请求都把完整数组传过去模型通过注意力机制理解「那」指代的是上一轮的「三级缓存」。这个方案没有跨节点状态同步问题集群里的任何一台机器都能处理任意用户的请求。代价是消息数组会越来越大token 消耗指数上升。我一般限制最近三轮到五轮超出历史就裁剪掉只保留系统提示和最近对话。一旦消息数组长度超过 8000 token就触发一次文本摘要把旧对话压缩成一段背景描述再拼进数组。从那以后我每次接入新的模型服务都会先用 curl 把消息数组、流式开关、上下文长度这组参数调通再动 SpringBoot 代码。这个习惯帮我绕开了不少黑匣子问题也节省了大量联调时间。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

【AUTOSAR】 CP PDU Router(PduR)--从入门到放弃 2026/9/26 18:01:49

【AUTOSAR】 CP PDU Router(PduR)--从入门到放弃

【AUTOSAR】 CP PDU Router(PduR)–从入门到放弃 基于 AUTOSAR Classic Platform R23-11 的 PduR 规范(AUTOSAR_CP_SWS_PDURouter.pdf),配合 EXP_LayeredSoftwareArchitecture、SWS_CommunicationStackTypes、SWS_CAN…

阅读更多 →
软件重用实战:用 TaoToken 统一 Key 打通 Cline 与 CC Switch 的组件复用链路 2026/9/26 18:01:49

软件重用实战:用 TaoToken 统一 Key 打通 Cline 与 CC Switch 的组件复用链路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
无铬鞣皮板变硬的原因排查与对策全解析 2026/9/26 18:01:49

无铬鞣皮板变硬的原因排查与对策全解析

1. 先搞清楚一件事:无铬鞣不等于“不用鞣”这几年皮革厂老板普遍都有同一个困惑:客户点名要无铬鞣,环保审核也过了,结果成品革一出来,皮板硬得能当纸板用,强度倒是有了,可一折就出死痕&#xff…

阅读更多 →
给AI编码代理装上“辅助轮”:Trellis框架的规范约束与工程实践 2026/9/26 18:01:43

给AI编码代理装上“辅助轮”:Trellis框架的规范约束与工程实践

最近在折腾AI编程代理的时候,我越来越觉得一个事儿不对劲:Cursor、Copilot这些工具,单点补全确实香,但一旦让代理去跑一个跨多文件的完整任务,经常会出现“自以为懂了,结果跑偏”的情况。上下文一多&#x…

阅读更多 →
Spring Boot报错:required a bean of type ‘java.lang.String‘,@AllArgsConstructor与构造器注入排障指南 2026/9/26 18:01:43

Spring Boot报错:required a bean of type ‘java.lang.String‘,@AllArgsConstructor与构造器注入排障指南

这个报错我前前后后见过不下十次,而且每一次排错过程都惊人地相似。同事把代码发过来,运行日志里躺着一句Parameter 0 of constructor ... required a bean of type java.lang.String that could not be found,项目启动直接失败。一看类上的注…

阅读更多 →
基于CT解剖分解的胸片骨抑制:可扩展监督与域适应实践 2026/9/26 18:01:43

基于CT解剖分解的胸片骨抑制:可扩展监督与域适应实践

胸片里的骨头影子,是每个做胸部影像分析的人绕不开的一道坎。不管是做病灶检测、肺炎筛查,还是做前后对比随访,肋骨和锁骨的投影总会像一层"栅栏"一样横在肺野前面,把真正想看的软组织细节遮掉一部分。骨抑制&#xff0…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉