【编程干货】本地用 Ollama + LLaMA 3 实现 Model Context Protocol(MCP)对话服务

模型上下文协议(MCP)本身提供的是一套标准化的通信规则和接口,简化了客户端应用的开发。

MCP 实际上是一套规范,官方把整套协议分成「传输层 + 协议层 + 功能层」三大块,并对初始化握手、能力协商、数据/工具暴露、安全约束等均给出了约束性 JSON-Schema。

基于 JSON-RPC 2.0 的开放协议,目标是像 USB-C 一样为 LLM 应用提供统一“插槽”。它定义三方角色——Host(应用)/Client(适配层)/Server(能力提供者),通过标准消息把 资源、工具、提示模板 等上下文按需送进 LLM,同时保留“人-机共管”审批流程,实现安全、可组合的本地或远程代理体系。

但是,需要注意,不要连接不信任的 server,MCP 没有在数据安全上提供任何保障。

下面我们可以实现一个最简的 MCP 服务。

0 先决条件

软件

版本建议

作用

操作系统

Windows 10/11、macOS 13+ 或任何较新的 Linux

仅需能运行 Ollama

Ollama

≥ 0.1.34

本地大语言模型后端

Python

3.10 – 3.12

运行 FastAPI 服务

Git

最新

</
实现一个 **Java + Spring Boot + Vue 3 + Ollama + DeepSeek R1** 的**流式聊天对话系统**,我们需要将前后端结合,调用本地大模型(Ollama)和远程大模型(DeepSeek R1)来实现 AI 流式回复。 --- ## ✅ 架构概览 ``` Vue 3 前端 ↓ Spring Boot 后端 ↓ Ollama API / DeepSeek API ``` --- ## ✅ 技术栈说明 | 技术 | 说明 | |------|------| | Vue 3 | 前端框架,使用 `<script setup>` 语法 | | Spring Boot | 后端服务,提供 RESTful 接口 | | Ollama | 本地运行的大语言模型(如 Llama3、Mistral) | | DeepSeek R1 | 远程大模型,通过 API 接入 | | SSE (Server-Sent Events) | 实现流式聊天,后端推送消息到前端 | | Reactor (Flux) | Spring WebFlux 支持响应式流 | --- ## ✅ 一、Ollama 本地部署 ### 1. 安装 Ollama ```bash # macOS brew install ollama # Linux curl -fsSL https://ollama.com/install.sh | sh ``` ### 2. 运行模型 ```bash ollama run llama3 ``` 默认 Ollama 启动在 `http://localhost:11434` --- ## ✅ 二、Spring Boot 后端实现 ### 1. 添加依赖(`pom.xml`) ```xml <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.apache.httpcomponents.client5</groupId> <artifactId>httpclient5</artifactId> <version>5.2.1</version> </dependency> </dependencies> ``` --- ### 2. 创建 `ChatController.java` ```java package com.zy.test.controller; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.io.entity.StringEntity; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Sinks; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/chat") public class ChatController { private final ObjectMapper objectMapper = new ObjectMapper(); @GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamChat(@RequestParam String model, @RequestParam String message) { Sinks.Many<String> sink = Sinks.many().multicast().onBackpressureBuffer(); new Thread(() -> { try { if ("ollama".equals(model)) { streamFromOllama(message, sink); } else if ("deepseek".equals(model)) { streamFromDeepSeek(message, sink); } else { sink.tryEmitNext("不支持的模型"); } } catch (Exception e) { sink.tryEmitNext("错误:" + e.getMessage()); } sink.tryEmitComplete(); }).start(); return sink.asFlux(); } private void streamFromOllama(String message, Sinks.Many<String> sink) throws Exception { URL url = new URL("http://localhost:11434/api/chat"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/json"); conn.setDoOutput(true); Map<String, Object> data = new HashMap<>(); data.put("model", "llama3"); data.put("messages", new Object[]{Map.of("role", "user", "content", message)}); data.put("stream", true); String jsonInputString = objectMapper.writeValueAsString(data); try (var os = conn.getOutputStream()) { byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8); os.write(input, 0, input.length); } try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { String responseLine; while ((responseLine = br.readLine()) != null) { sink.tryEmitNext("data: " + responseLine + "\n\n"); } } } private void streamFromDeepSeek(String message, Sinks.Many<String> sink) throws Exception { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost request = new HttpPost("https://api.deepseek.com/chat/completions"); request.setHeader("Authorization", "Bearer YOUR_DEEPSEEK_API_KEY"); request.setHeader("Content-Type", "application/json"); Map<String, Object> data = new HashMap<>(); data.put("model", "deepseek-chat"); data.put("messages", new Object[]{Map.of("role", "user", "content", message)}); data.put("stream", true); String jsonInputString = objectMapper.writeValueAsString(data); request.setEntity(new StringEntity(jsonInputString, ContentType.APPLICATION_JSON)); httpClient.execute(request, response -> { var entity = response.getEntity(); if (entity != null) { try (var reader = new BufferedReader(new InputStreamReader(entity.getContent()))) { String line; while ((line = reader.readLine()) != null) { sink.tryEmitNext("data: " + line + "\n\n"); } } } return null; }); } } } ``` --- ## ✅ 三、Vue 3 前端实现 ### 1. 创建 `Chat.vue` ```vue <template> <div class="chat-container"> <el-input v-model="inputMsg" @keyup.enter="sendMessage" placeholder="输入消息..." /> <div class="message-box"> <div v-for="(msg, index) in messages" :key="index" :class="msg.role" > <strong>{{ msg.role }}:</strong> {{ msg.content }} </div> </div> </div> </template> <script setup> import { ref } from &#39;vue&#39;; const inputMsg = ref(&#39;&#39;); const messages = ref([]); let eventSource = null; const sendMessage = () => { if (eventSource) eventSource.close(); const userMsg = inputMsg.value.trim(); if (!userMsg) { alert(&#39;请输入内容&#39;); return; } messages.value.push({ role: &#39;user&#39;, content: userMsg }); const aiMsgIndex = messages.value.push({ role: &#39;assistant&#39;, content: &#39;&#39; }) - 1; eventSource = new EventSource( `http://localhost:8080/api/chat/stream?model=ollama&message=${encodeURIComponent(userMsg)}` ); eventSource.onmessage = (e) => { const data = e.data.replace(/^data:\s*/, &#39;&#39;).trim(); messages.value[aiMsgIndex].content += data + &#39; &#39;; }; eventSource.onerror = () => { eventSource.close(); if (!messages.value[aiMsgIndex].content.trim()) { messages.value[aiMsgIndex].content = &#39;回答生成中断&#39;; } }; }; </script> <style scoped> .chat-container { padding: 20px; } .message-box { margin-top: 20px; } .user { color: #409EFF; } .assistant { color: #67C23A; } </style> ``` --- ## ✅ 四、效果演示 1. 用户在前端输入问题 2. 请求 `/api/chat/stream?model=ollama&message=xxx` 3. 后端调用 Ollama 或 DeepSeek 的流式接口 4. 使用 `SSE` 将 AI 的回复逐字返回前端 5. 前端实时显示 AI 的流式回复 --- ## ✅ 五、部署建议 | 环境 | 建议 | |------|------| | Ollama | 本地开发调试使用 | | DeepSeek | 生产环境使用,API 接口调用 | | Spring Boot | 使用 `ThreadPoolTaskExecutor` 提升并发能力 | | Vue 3 | 使用 `EventSource` 实现流式对话 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值