基于GLM-4-9B-Chat-1M的SpringBoot微服务开发指南
想在一个微服务项目里集成一个能“记住”超长对话的AI助手吗?比如,让一个客服系统能完整回顾用户过去几万字的聊天记录,或者让一个文档分析工具一口气读完上百页的PDF再回答问题。听起来很酷,但一想到要把一个支持百万上下文的AI大模型塞进SpringBoot服务里,是不是觉得有点无从下手?
别担心,这篇文章就是来帮你解决这个问题的。我会带你一步步把GLM-4-9B-Chat-1M这个“大家伙”集成到SpringBoot微服务架构里,从设计API接口,到配置服务注册发现,再到处理负载均衡,最后给你一个能直接跑起来的完整项目示例。整个过程就像搭积木,我们一块一块来。
1. 项目准备与环境搭建
在开始敲代码之前,我们得先把“地基”打好。这里说的地基,就是运行GLM-4-9B-Chat-1M模型所需要的环境。这个模型有90亿参数,支持百万级上下文,对硬件有一定要求,但别怕,我们一步步来。
1.1 硬件与软件要求
首先,你得有一台带GPU的机器。模型官方推荐使用A100这样的高性能显卡,因为它的显存够大。如果你的显存没那么充裕,比如只有24G的RTX 4090,也不用灰心,我们可以通过量化(一种降低模型精度来节省显存的技术)的方式来运行,只是效果可能会打一点点折扣。
软件方面,你需要准备好这几样东西:
- Java 17或更高版本:这是运行现代SpringBoot应用的基础。
- Python 3.8+ 和 Pip:因为模型的推理部分我们通常用Python来写,SpringBoot通过进程调用来和它交互。
- Docker(可选但推荐):用Docker可以把模型运行环境封装起来,避免污染你的主机环境,部署起来也方便。
- 一个SpringBoot项目骨架:你可以用Spring Initializr快速生成一个。
1.2 创建SpringBoot项目
打开你喜欢的IDE,或者直接用命令行,创建一个新的SpringBoot项目。关键依赖要选上这几个:
- Spring Web:用来构建我们的RESTful API。
- Spring Cloud Netflix Eureka Client(或Consul、Nacos等):用于服务注册与发现。这篇文章里我们用Eureka来举例,因为它比较经典易懂。
- Lombok:让我们的Java代码更简洁,少写很多Getter/Setter。
你的pom.xml文件里,依赖部分大概长这样:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 其他测试、配置等依赖 --> </dependencies>1.3 准备GLM-4-9B-Chat-1M模型环境
这是核心的一步。我们不直接把模型打包在Java项目里,而是单独准备一个Python服务来负责模型的加载和推理。这样做的原因是,AI模型的生态和库(像PyTorch, Transformers)基本都是Python的,用Java直接调用会非常麻烦。
创建Python虚拟环境:在你的项目根目录下,新建一个叫
ai-service的文件夹,然后进去创建一个独立的Python环境。mkdir ai-service && cd ai-service python -m venv venv # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate安装依赖:在激活的虚拟环境中,安装运行模型必需的库。最关键的是
transformers库,版本要4.44.0以上,这是模型要求的。pip install torch transformers # 如果你的CUDA版本合适,可以安装带CUDA的torch以获得GPU加速 # pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118下载模型(可选):你可以直接从Hugging Face下载模型权重,但文件很大(几十GB)。更常见的做法是在代码中指定模型名称,让
transformers库在第一次运行时自动下载。我们采用后一种方式。
好了,环境准备就绪,接下来我们开始设计整个系统的“骨架”——API。
2. 设计AI服务API接口
API是我们微服务对外的“窗口”,设计得好不好,直接关系到其他服务调用起来方不方便。我们的目标是为GLM-4-9B-Chat-1M设计一套简单、清晰、又能发挥其长上下文优势的接口。
2.1 定义核心请求与响应体
我们先想想,调用一个AI对话模型,最基本需要什么信息?无非是“用户说了什么话”,以及“历史对话记录”。对于GLM-4-9B-Chat-1M,我们还要考虑它支持工具调用等高级功能。但为了入门简单,我们先从最基础的聊天开始。
在SpringBoot项目中,我们创建几个Java类来定义这些数据结构。首先是一个表示单条消息的类:
package com.example.aimicroservice.dto; import lombok.Data; @Data public class ChatMessage { /** * 消息角色:user(用户), assistant(助手), system(系统) */ private String role; /** * 消息内容 */ private String content; }然后,是整个聊天请求的类。这里我们特意加入一个sessionId字段,用来标识不同的对话会话。虽然模型自己能处理长上下文,但我们在服务层用sessionId来管理和缓存不同会话的历史记录,会更灵活。
package com.example.aimicroservice.dto; import lombok.Data; import java.util.List; @Data public class ChatRequest { /** * 本次用户输入的提问 */ private String prompt; /** * 可选的会话ID,用于关联多轮对话历史 */ private String sessionId; /** * 可选的、自定义的对话历史。如果为空,服务端可能根据sessionId查询缓存。 */ private List<ChatMessage> history; /** * 生成参数:最大生成长度 */ private Integer maxNewTokens = 1024; /** * 生成参数:温度,控制随机性 */ private Float temperature = 0.7f; }最后是响应体,除了返回模型生成的回复,我们还可以返回一些元信息,比如本次消耗的token数(这对了解上下文使用情况和计费很有帮助)。
package com.example.aimicroservice.dto; import lombok.Data; @Data public class ChatResponse { /** * 模型生成的回复内容 */ private String response; /** * 本次交互消耗的总token数(输入+输出) */ private Integer totalTokens; /** * 请求状态码 */ private Integer code; /** * 状态信息 */ private String message; }2.2 创建SpringBoot控制器(Controller)
有了数据定义,我们就可以创建接收HTTP请求的入口了。在SpringBoot里,这就是Controller。
package com.example.aimicroservice.controller; import com.example.aimicroservice.dto.ChatRequest; import com.example.aimicroservice.dto.ChatResponse; import com.example.aimicroservice.service.AIModelService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @Slf4j @RestController @RequestMapping("/api/v1/chat") public class AIChatController { @Autowired private AIModelService aiModelService; @PostMapping("/completions") public ChatResponse chat(@RequestBody ChatRequest request) { log.info("收到聊天请求,sessionId: {}, prompt长度: {}", request.getSessionId(), request.getPrompt().length()); try { // 调用核心服务处理请求 return aiModelService.generateResponse(request); } catch (Exception e) { log.error("处理聊天请求时发生错误", e); ChatResponse errorResponse = new ChatResponse(); errorResponse.setCode(500); errorResponse.setMessage("服务内部错误: " + e.getMessage()); return errorResponse; } } // 可以再添加一个健康检查或模型信息查询的接口 @GetMapping("/health") public String health() { return "GLM-4-9B-Chat-1M Service is UP"; } }这个控制器很简单,它接收一个/api/v1/chat/completions的POST请求,把请求体转给AIModelService去处理,然后返回结果。还暴露了一个健康检查接口。接下来,这个AIModelService就是连接SpringBoot世界和Python模型世界的桥梁了。
3. 实现模型服务层与Python桥接
这是整个集成中最关键、也最有技巧的一层。SpringBoot(Java)如何调用Python的模型?我们有两种主流选择:一种是使用gRPC这类高性能RPC框架;另一种更简单直接,就是用Java的ProcessBuilder来启动Python脚本进程进行通信。为了直观易懂,我们采用第二种方式。
3.1 编写Python模型推理脚本
在之前创建的ai-service目录下,我们写一个Python脚本model_server.py。这个脚本会做几件事:加载模型、提供一个循环,从标准输入读取JSON格式的请求,推理后将结果写成JSON输出到标准输出。
# ai-service/model_server.py import sys import json import torch from transformers import AutoModelForCausalLM, AutoTokenizer def load_model(): """加载GLM-4-9B-Chat-1M模型""" print("正在加载模型...", file=sys.stderr) model_name = "THUDM/glm-4-9b-chat-1m" # 加载tokenizer和模型 tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.bfloat16, # 使用bfloat16节省显存 low_cpu_mem_usage=True, trust_remote_code=True ).cuda().eval() # 放到GPU上,并设置为评估模式 print("模型加载完毕!", file=sys.stderr) return model, tokenizer def process_request(model, tokenizer, request_data): """处理单个聊天请求""" prompt = request_data.get("prompt", "") history = request_data.get("history", []) max_new_tokens = request_data.get("max_new_tokens", 1024) # 构建模型所需的对话格式 # GLM-4-9B-Chat-1M 使用特定的chat_template messages = history + [{"role": "user", "content": prompt}] # 应用聊天模板,将对话历史格式化为模型输入 inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_tensors="pt", return_dict=True ) inputs = inputs.to("cuda") # 生成参数 gen_kwargs = { "max_new_tokens": max_new_tokens, "do_sample": True, "temperature": request_data.get("temperature", 0.7), "top_p": 0.9, } # 模型推理 with torch.no_grad(): outputs = model.generate(**inputs, **gen_kwargs) # 只取新生成的部分 generated_ids = outputs[:, inputs['input_ids'].shape[1]:] response = tokenizer.decode(generated_ids[0], skip_special_tokens=True) # 计算使用的token数(粗略估计) input_tokens = inputs['input_ids'].shape[1] output_tokens = generated_ids.shape[1] total_tokens = input_tokens + output_tokens return { "response": response, "total_tokens": total_tokens } def main(): model, tokenizer = load_model() print("模型服务就绪,等待标准输入...", file=sys.stderr) # 从标准输入持续读取请求 for line in sys.stdin: if not line.strip(): continue try: request = json.loads(line.strip()) result = process_request(model, tokenizer, request) # 将结果以JSON格式输出到标准输出,并立即刷新 print(json.dumps(result), flush=True) except json.JSONDecodeError as e: error_result = {"error": f"无效的JSON输入: {e}", "response": ""} print(json.dumps(error_result), flush=True) except Exception as e: error_result = {"error": f"处理请求时出错: {e}", "response": ""} print(json.dumps(error_result), flush=True) if __name__ == "__main__": main()这个脚本是一个简单的“服务器”,它从标准输入读一行JSON,处理,再往标准输出写一行JSON。这种设计让Java程序可以很容易地与之交互。
3.2 实现Java端的服务桥接
现在,我们在SpringBoot项目中创建AIModelService,它的核心职责是启动上面的Python进程,并通过进程的输入输出流与之通信。
package com.example.aimicroservice.service; import com.example.aimicroservice.dto.ChatRequest; import com.example.aimicroservice.dto.ChatResponse; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.*; import java.util.concurrent.*; @Slf4j @Service public class AIModelService { private Process pythonProcess; private BufferedWriter processInput; private BufferedReader processOutput; private final ObjectMapper objectMapper = new ObjectMapper(); private final ExecutorService executorService = Executors.newSingleThreadExecutor(); // 用于缓存会话历史,实际生产环境可以用Redis private final ConcurrentHashMap<String, String> sessionHistoryCache = new ConcurrentHashMap<>(); @PostConstruct public void init() throws IOException { log.info("正在启动Python模型服务进程..."); // 构建启动命令,假设python脚本在项目外部的ai-service目录 ProcessBuilder pb = new ProcessBuilder( "python", "model_server.py" ); // 设置工作目录到你的ai-service文件夹 pb.directory(new File("../ai-service")); pb.redirectErrorStream(true); // 将错误输出合并到标准输出,便于调试 pythonProcess = pb.start(); // 获取进程的输入输出流 processInput = new BufferedWriter(new OutputStreamWriter(pythonProcess.getOutputStream())); processOutput = new BufferedReader(new InputStreamReader(pythonProcess.getInputStream())); // 启动一个线程来读取Python进程的输出(错误信息等) executorService.submit(() -> { String line; try { while ((line = processOutput.readLine()) != null) { // 打印Python端的日志(以[Python]前缀区分) log.info("[Python] {}", line); } } catch (IOException e) { log.error("读取Python进程输出时出错", e); } }); log.info("Python模型服务进程启动成功,PID: {}", pythonProcess.pid()); } public ChatResponse generateResponse(ChatRequest request) throws IOException, InterruptedException { // 1. 准备请求数据(简化版,实际应合并历史) String sessionId = request.getSessionId(); String prompt = request.getPrompt(); // 构建发送给Python脚本的JSON对象 java.util.Map<String, Object> payload = new java.util.HashMap<>(); payload.put("prompt", prompt); payload.put("max_new_tokens", request.getMaxNewTokens()); payload.put("temperature", request.getTemperature()); // 这里可以加入从缓存获取的history payload.put("history", request.getHistory() != null ? request.getHistory() : java.util.Collections.emptyList()); String requestJson = objectMapper.writeValueAsString(payload); // 2. 发送请求到Python进程 synchronized (processInput) { processInput.write(requestJson); processInput.newLine(); processInput.flush(); } // 3. 读取Python进程的响应(这里需要一种机制来匹配请求-响应,简单场景下可以顺序读取) // 注意:这是一个简化的同步方式,生产环境需要更复杂的异步或请求ID匹配机制。 String responseLine; synchronized (processOutput) { responseLine = processOutput.readLine(); } if (responseLine == null) { throw new IOException("Python进程无响应,可能已终止"); } // 4. 解析响应 java.util.Map<String, Object> resultMap = objectMapper.readValue(responseLine, java.util.Map.class); ChatResponse response = new ChatResponse(); if (resultMap.containsKey("error")) { response.setCode(500); response.setMessage("模型推理错误: " + resultMap.get("error")); } else { response.setCode(200); response.setResponse((String) resultMap.get("response")); response.setTotalTokens((Integer) resultMap.get("total_tokens")); response.setMessage("成功"); } return response; } @PreDestroy public void shutdown() { log.info("正在关闭Python模型服务进程..."); executorService.shutdown(); if (pythonProcess != null && pythonProcess.isAlive()) { pythonProcess.destroy(); try { pythonProcess.waitFor(5, TimeUnit.SECONDS); } catch (InterruptedException e) { pythonProcess.destroyForcibly(); } } log.info("Python模型服务进程已关闭"); } }这个服务类在SpringBoot启动时(@PostConstruct)会拉起Python进程,在服务关闭时(@PreDestroy)会优雅地关闭它。generateResponse方法负责将Java对象转为JSON,发送给Python进程,然后读取并解析返回的JSON。
重要提示:上面的通信方式(同步逐行读写)在低并发下是可行的,但如果你的微服务需要同时处理很多请求,这种方式就会成为瓶颈。生产环境中,你需要考虑更健壮的方案,比如:
- 让Python脚本本身变成一个HTTP服务器(用FastAPI),然后Java服务通过HTTP客户端调用它。
- 使用消息队列(如RabbitMQ、Kafka)来解耦请求和响应。
- 使用gRPC,它能为跨语言服务调用提供高效的二进制通信。
为了教程的简洁和直观,我们采用了进程通信的方式。理解了核心思想后,你可以根据实际需求升级通信机制。
4. 微服务注册、发现与负载均衡
现在,我们的AI服务已经能独立工作了。但在一个真正的微服务架构里,这个服务通常不是孤立的。可能有多个实例(为了高可用和扩容),并且其他服务(比如一个前端Web服务或者一个业务处理服务)需要能方便地找到并调用它。这就是服务注册与发现要做的事。
4.1 搭建Eureka注册中心
首先,我们需要一个“电话簿”,也就是服务注册中心。我们单独创建一个SpringBoot应用作为Eureka Server。
创建一个新的SpringBoot模块或应用,引入依赖:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>在主类上添加
@EnableEurekaServer注解。@SpringBootApplication @EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }在
application.yml中配置:server: port: 8761 # Eureka默认端口 eureka: client: register-with-eureka: false # 自身不注册 fetch-registry: false # 不获取注册表 server: enable-self-preservation: false # 开发环境可关闭自我保护模式
启动这个应用,访问http://localhost:8761,你就能看到Eureka的控制面板了。
4.2 将AI服务注册到Eureka
回到我们的AI微服务项目,我们需要让它成为Eureka的客户端。
- 确保已经引入了
spring-cloud-starter-netflix-eureka-client依赖。 - 在主类上添加
@EnableEurekaClient注解(新版本SpringCloud通常可省略,只要依赖在就会自动注册)。 - 修改
application.yml,添加Eureka配置:spring: application: name: ai-chat-service # 服务名称,非常重要! server: port: 8080 # 本服务端口 eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ # 注册中心地址 instance: prefer-ip-address: true # 使用IP地址注册(代替主机名) instance-id: ${spring.cloud.client.ip-address}:${server.port} # 实例ID格式
现在启动你的AI服务,刷新Eureka控制台,你应该能看到名为AI-CHAT-SERVICE的服务已经注册上来了。
4.3 服务消费者与负载均衡
假设现在有另一个服务(比如一个用户门户网站user-portal)需要调用我们的AI服务。它不需要知道AI服务具体在哪台机器、哪个端口,它只需要通过服务名来调用。SpringCloud提供了RestTemplate或WebClient,配合@LoadBalanced注解,可以轻松实现基于服务名的负载均衡调用。
在user-portal服务中:
同样配置为Eureka客户端。
创建一个配置类,注入一个负载均衡的
RestTemplate:@Configuration public class RestTemplateConfig { @Bean @LoadBalanced // 这个注解是关键,它让RestTemplate具备服务发现和负载均衡能力 public RestTemplate restTemplate() { return new RestTemplate(); } }在需要调用的地方,使用服务名(
ai-chat-service)代替具体的IP和端口:@Service public class SomeBusinessService { @Autowired private RestTemplate restTemplate; public String askAI(String question) { // 注意URL,用的是服务名,不是具体地址 String url = "http://AI-CHAT-SERVICE/api/v1/chat/completions"; ChatRequest request = new ChatRequest(); request.setPrompt(question); request.setSessionId("user_123"); ChatResponse response = restTemplate.postForObject(url, request, ChatResponse.class); return response != null ? response.getResponse() : "调用AI服务失败"; } }
这样,即使你启动了多个ai-chat-service实例(在不同端口或不同机器上),Eureka都会将它们注册上来。RestTemplate通过内置的负载均衡器(默认是轮询),会自动将请求分发到不同的实例上,实现了高可用和水平扩展。
5. 总结与项目完整示例
走完上面这些步骤,一个集成GLM-4-9B-Chat-1M的SpringBoot微服务骨架就搭建起来了。我们来回顾一下核心要点:
整个架构的核心思路是“桥接”。我们用Python负责重度的模型加载和推理计算,这是它的强项;用Java和SpringBoot构建健壮、可扩展的微服务,处理HTTP请求、业务逻辑和服务治理。两者通过进程间通信(或更高级的RPC/HTTP)连接在一起。
部署和运行的时候,你需要按顺序启动几个部分:
- Eureka Server:服务注册中心,先启动它。
- AI Chat Service:我们的主角,启动后会自动向Eureka注册。
- 其他业务服务:比如
user-portal,它们会从Eureka发现AI服务并进行调用。
在实际生产环境中,你还需要考虑更多问题,比如:
- 配置管理:模型路径、生成参数等最好放到配置中心(如SpringCloud Config)。
- 容错与熔断:当AI服务不稳定时,调用方如何降级处理?可以用Resilience4j或Hystrix。
- 监控与日志:需要监控GPU使用情况、服务响应时间、Token消耗等。
- API网关:在多个微服务前放置一个网关(如SpringCloud Gateway)来统一处理路由、认证、限流。
最后,你可以把整个项目,包括AI服务的Python部分,打包成Docker镜像。这样部署起来就更加一致和方便了。虽然集成的过程有不少细节要处理,但看到自己搭建的服务能够处理超长上下文对话,那种成就感还是很棒的。希望这篇指南能帮你迈出第一步,祝你开发顺利!
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。