ChatGLM-6B Java开发实战:SpringBoot微服务集成指南
ChatGLM-6B Java开发实战:SpringBoot微服务集成指南
1. 引言
如果你是一名Java开发者,最近肯定没少听同事聊起大模型。看着别人用Python三两行代码就能调起一个智能对话机器人,心里是不是有点痒痒?总感觉Java在这个领域有点使不上劲,好像天然就慢半拍。
别急,今天咱们就来打破这个刻板印象。
我最近花了不少时间研究,发现把ChatGLM-6B这种级别的对话模型集成到Java的SpringBoot微服务里,其实没想象中那么复杂。关键是要找到对的路子,避开那些坑。这篇文章就是我这段时间折腾出来的经验总结,手把手带你走一遍完整的集成流程。
咱们的目标很明确:让你能在自己熟悉的Java环境里,用上ChatGLM-6B的对话能力。不管是想给现有的系统加个智能客服模块,还是想开发个新的AI应用,这套方案都能给你打个扎实的基础。
2. 整体思路:为什么选择API桥接方案
在开始动手之前,咱们得先想清楚一件事:怎么把Python的模型和Java的服务连起来?
你可能想过几种方案。比如,直接在Java里加载PyTorch模型?这条路我试过,太折腾了,各种依赖冲突、内存管理问题,调试起来能让人崩溃。再比如,用JNI调用Python代码?理论上可行,但维护成本太高,部署也麻烦。
经过反复对比,我最终选择了API桥接这个方案。简单说,就是让ChatGLM-6B在Python环境里跑起来,提供一个HTTP接口,然后Java服务通过这个接口去调用。
这个方案有几个明显的好处:
第一,技术栈各司其职。Python负责它擅长的模型推理,Java负责它擅长的业务逻辑和微服务架构。两边都用自己最舒服的方式工作,互不干扰。
第二,部署灵活。Python服务可以单独部署,甚至放在GPU服务器上。Java服务该怎么部署还怎么部署,完全不影响现有的架构。
第三,维护简单。模型升级、参数调整都在Python那边完成,Java这边几乎不用动。出了问题也容易定位,是模型的问题还是业务逻辑的问题,一目了然。
第四,性能可控。通过HTTP调用,你可以很方便地加缓存、加负载均衡、加监控,这些都是Java生态里成熟的技术。
下面这张图展示了整个架构的组成:
┌─────────────────────────────────────────────────────────────┐
│ Java SpringBoot 微服务 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Controller│ │ Service │ │ Client │ │
│ │ │ │ │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
└───────────────────────────┼─────────────────────────────────┘
│ HTTP/REST
▼
┌─────────────────────────────────────────────────────────────┐
│ Python ChatGLM-6B API服务 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ FastAPI │ │ ChatGLM-6B │ │ Model │ │
│ │ 服务 │ │ 封装 │ │ 推理 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
整个流程就是:Java服务收到请求 → 调用Python的API → Python服务运行模型生成回复 → 返回给Java → Java再返回给客户端。
听起来是不是挺清晰的?接下来咱们就一步步实现它。
3. 环境准备:让ChatGLM-6B跑起来
3.1 Python环境搭建
首先得把ChatGLM-6B在Python环境里跑起来。这里我推荐用Conda来管理环境,能避免很多依赖冲突的问题。
# 创建专门的Python环境
conda create -n chatglm python=3.8
conda activate chatglm
# 安装基础依赖
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # CUDA 11.8
pip install transformers==4.27.1
pip install fastapi uvicorn
pip install sentencepiece protobuf
如果你的机器有GPU,记得装对应CUDA版本的PyTorch。没有GPU的话,就用CPU版本,不过推理速度会慢一些。
3.2 下载ChatGLM-6B模型
模型文件比较大,有十几个G,下载需要点时间。我建议直接从Hugging Face或者ModelScope下载,这两个源都比较稳定。
# 从Hugging Face下载(需要先安装git-lfs)
git lfs install
git clone https://huggingface.co/THUDM/chatglm-6b
# 如果下载慢,可以用ModelScope的镜像
git clone https://www.modelscope.cn/ZhipuAI/ChatGLM-6B.git chatglm-6b
cd chatglm-6b
git checkout v1.0.16
下载完成后,记得检查一下文件是否完整。主要看有没有这些文件:pytorch_model.bin、config.json、tokenizer.model。
3.3 编写Python API服务
现在来写一个简单的FastAPI服务,把ChatGLM-6B包装成HTTP接口。
# api_server.py
import os
from fastapi import FastAPI, Request
from transformers import AutoTokenizer, AutoModel
import uvicorn
import torch
import json
from datetime import datetime
app = FastAPI()
# 全局变量,避免重复加载模型
model = None
tokenizer = None
def load_model():
"""加载模型,只执行一次"""
global model, tokenizer
print("开始加载ChatGLM-6B模型...")
# 模型路径,改成你实际下载的路径
model_path = "./chatglm-6b"
# 加载tokenizer和模型
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModel.from_pretrained(model_path, trust_remote_code=True)
# 如果有GPU就用GPU,没有就用CPU
if torch.cuda.is_available():
model = model.half().cuda() # 半精度,节省显存
print(f"模型已加载到GPU: {torch.cuda.get_device_name(0)}")
else:
model = model.float() # CPU用全精度
print("模型已加载到CPU")
model = model.eval() # 设置为评估模式
print("模型加载完成!")
@app.on_event("startup")
async def startup_event():
"""服务启动时加载模型"""
load_model()
@app.post("/chat")
async def chat_completion(request: Request):
"""处理聊天请求"""
try:
# 解析请求数据
data = await request.json()
prompt = data.get("prompt", "")
history = data.get("history", [])
max_length = data.get("max_length", 2048)
top_p = data.get("top_p", 0.7)
temperature = data.get("temperature", 0.95)
if not prompt:
return {
"code": 400,
"message": "prompt不能为空",
"data": None
}
# 调用模型生成回复
with torch.no_grad(): # 不计算梯度,节省内存
response, new_history = model.chat(
tokenizer,
prompt,
history=history,
max_length=max_length,
top_p=top_p,
temperature=temperature
)
# 构造返回结果
result = {
"code": 200,
"message": "success",
"data": {
"response": response,
"history": new_history,
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
}
return result
except Exception as e:
return {
"code": 500,
"message": f"服务内部错误: {str(e)}",
"data": None
}
@app.get("/health")
async def health_check():
"""健康检查接口"""
return {
"code": 200,
"message": "服务运行正常",
"data": {
"model_loaded": model is not None,
"gpu_available": torch.cuda.is_available(),
"timestamp": datetime.now().isoformat()
}
}
if __name__ == "__main__":
# 启动服务,默认监听8000端口
uvicorn.run(
app,
host="0.0.0.0", # 允许外部访问
port=8000,
log_level="info"
)
这个服务提供了两个接口:
POST /chat:主要的聊天接口,接收用户输入,返回模型回复GET /health:健康检查接口,用来监控服务状态
保存为api_server.py,然后运行:
python api_server.py
如果一切正常,你会看到模型加载的日志,然后服务就启动在http://localhost:8000了。
3.4 测试Python API
服务启动后,先用curl测试一下:
# 测试健康检查
curl http://localhost:8000/health
# 测试聊天接口
curl -X POST "http://localhost:8000/chat" \
-H "Content-Type: application/json" \
-d '{
"prompt": "你好,请介绍一下你自己",
"history": []
}'
如果看到返回了模型的回复,说明Python这边的工作就完成了。现在ChatGLM-6B已经可以通过HTTP接口调用了。
4. SpringBoot微服务集成
4.1 创建SpringBoot项目
用你习惯的方式创建一个SpringBoot项目。我这边用Spring Initializr生成,选这些依赖:
- Spring Web
- Spring Boot DevTools
- Lombok
- Configuration Processor
Maven的pom.xml关键依赖如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.14</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>chatglm-springboot</artifactId>
<version>1.0.0</version>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- HTTP客户端 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 配置Python API客户端
接下来要创建一个HTTP客户端,用来调用刚才启动的Python API服务。
// ChatGptClient.java
package com.example.chatglm.client;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class ChatGptClient {
@Value("${chatglm.api.url:http://localhost:8000}")
private String apiUrl;
private final ObjectMapper objectMapper = new ObjectMapper();
private final CloseableHttpClient httpClient = HttpClients.createDefault();
/**
* 发送聊天请求
*/
public ChatResponse chat(String prompt, String history) {
try {
String url = apiUrl + "/chat";
// 构造请求体
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("prompt", prompt);
requestBody.put("history", history != null ? history : "");
requestBody.put("max_length", 2048);
requestBody.put("top_p", 0.7);
requestBody.put("temperature", 0.95);
String jsonBody = objectMapper.writeValueAsString(requestBody);
// 创建HTTP请求
HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setEntity(new StringEntity(jsonBody, "UTF-8"));
// 发送请求
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8");
if (response.getStatusLine().getStatusCode() == 200) {
return objectMapper.readValue(responseBody, ChatResponse.class);
} else {
log.error("API调用失败,状态码:{},响应:{}",
response.getStatusLine().getStatusCode(), responseBody);
return ChatResponse.error("API调用失败");
}
}
} catch (Exception e) {
log.error("调用ChatGLM API异常", e);
return ChatResponse.error("服务调用异常:" + e.getMessage());
}
}
/**
* 健康检查
*/
public boolean healthCheck() {
try {
String url = apiUrl + "/health";
HttpPost httpPost = new HttpPost(url);
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return response.getStatusLine().getStatusCode() == 200;
}
} catch (Exception e) {
log.error("健康检查失败", e);
return false;
}
}
/**
* 响应实体类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ChatResponse {
private Integer code;
private String message;
private ChatData data;
public static ChatResponse error(String message) {
return new ChatResponse(500, message, null);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ChatData {
private String response;
private String history;
private String time;
}
}
}
然后在application.yml里加上配置:
# application.yml
server:
port: 8080
chatglm:
api:
url: http://localhost:8000 # Python API服务的地址
spring:
jackson:
default-property-inclusion: non_null
serialization:
indent-output: true
4.3 实现业务逻辑层
有了客户端,接下来实现业务逻辑。这里我设计了一个简单的服务层,处理对话逻辑。
// ChatService.java
package com.example.chatglm.service;
import com.example.chatglm.client.ChatGptClient;
import com.example.chatglm.model.ChatRequest;
import com.example.chatglm.model.ChatResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service
public class ChatService {
@Autowired
private ChatGptClient chatGptClient;
// 简单的会话管理,实际生产环境可以用Redis
private final Map<String, String> sessionHistory = new ConcurrentHashMap<>();
/**
* 单轮对话(不带历史)
*/
public ChatResponse singleChat(ChatRequest request) {
log.info("收到单轮对话请求:{}", request.getPrompt());
ChatGptClient.ChatResponse apiResponse = chatGptClient.chat(
request.getPrompt(),
null
);
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
return ChatResponse.success(
apiResponse.getData().getResponse(),
apiResponse.getData().getTime()
);
} else {
return ChatResponse.error("模型服务异常:" + apiResponse.getMessage());
}
}
/**
* 多轮对话(带会话历史)
*/
public ChatResponse multiChat(ChatRequest request) {
String sessionId = request.getSessionId();
if (sessionId == null || sessionId.trim().isEmpty()) {
sessionId = generateSessionId();
}
log.info("会话[{}]收到消息:{}", sessionId, request.getPrompt());
// 获取历史记录
String history = sessionHistory.getOrDefault(sessionId, "");
// 调用API
ChatGptClient.ChatResponse apiResponse = chatGptClient.chat(
request.getPrompt(),
history
);
if (apiResponse.getCode() == 200 && apiResponse.getData() != null) {
// 更新历史记录
sessionHistory.put(sessionId, apiResponse.getData().getHistory());
return ChatResponse.success(
apiResponse.getData().getResponse(),
sessionId,
apiResponse.getData().getTime()
);
} else {
return ChatResponse.error("模型服务异常:" + apiResponse.getMessage());
}
}
/**
* 清空会话历史
*/
public void clearSession(String sessionId) {
sessionHistory.remove(sessionId);
log.info("已清空会话[{}]的历史记录", sessionId);
}
/**
* 健康检查
*/
public boolean healthCheck() {
return chatGptClient.healthCheck();
}
private String generateSessionId() {
return "session_" + System.currentTimeMillis() + "_" +
(int)(Math.random() * 1000);
}
}
对应的实体类:
// ChatRequest.java
package com.example.chatglm.model;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class ChatRequest {
@NotBlank(message = "消息内容不能为空")
private String prompt;
private String sessionId; // 会话ID,用于多轮对话
private Integer maxLength = 2048;
private Double topP = 0.7;
private Double temperature = 0.95;
}
// ChatResponse.java
package com.example.chatglm.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatResponse {
private Integer code;
private String message;
private Object data;
public static ChatResponse success(String response, String time) {
ChatData data = new ChatData();
data.setResponse(response);
data.setTime(time);
return new ChatResponse(200, "success", data);
}
public static ChatResponse success(String response, String sessionId, String time) {
ChatData data = new ChatData();
data.setResponse(response);
data.setSessionId(sessionId);
data.setTime(time);
return new ChatResponse(200, "success", data);
}
public static ChatResponse error(String message) {
return new ChatResponse(500, message, null);
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class ChatData {
private String response;
private String sessionId;
private String time;
}
}
4.4 实现RESTful接口
最后,暴露HTTP接口给前端或其他服务调用。
// ChatController.java
package com.example.chatglm.controller;
import com.example.chatglm.model.ChatRequest;
import com.example.chatglm.model.ChatResponse;
import com.example.chatglm.service.ChatService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Slf4j
@RestController
@RequestMapping("/api/chat")
@Validated
public class ChatController {
@Autowired
private ChatService chatService;
/**
* 单轮对话接口
*/
@PostMapping("/single")
public ChatResponse singleChat(@Valid @RequestBody ChatRequest request) {
log.info("单轮对话请求:{}", request.getPrompt());
return chatService.singleChat(request);
}
/**
* 多轮对话接口
*/
@PostMapping("/multi")
public ChatResponse multiChat(@Valid @RequestBody ChatRequest request) {
log.info("多轮对话请求,会话ID:{},消息:{}",
request.getSessionId(), request.getPrompt());
return chatService.multiChat(request);
}
/**
* 清空会话历史
*/
@DeleteMapping("/session/{sessionId}")
public ChatResponse clearSession(@PathVariable String sessionId) {
chatService.clearSession(sessionId);
return ChatResponse.success("会话历史已清空", null, null);
}
/**
* 健康检查
*/
@GetMapping("/health")
public ChatResponse healthCheck() {
boolean healthy = chatService.healthCheck();
if (healthy) {
return ChatResponse.success("服务正常", null, null);
} else {
return ChatResponse.error("模型服务不可用");
}
}
}
4.5 全局异常处理
为了更好的用户体验,加一个全局异常处理:
// GlobalExceptionHandler.java
package com.example.chatglm.handler;
import com.example.chatglm.model.ChatResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 参数校验异常
*/
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ChatResponse handleBindException(BindException e) {
String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
log.warn("参数校验失败:{}", message);
return ChatResponse.error("参数错误:" + message);
}
/**
* 参数校验异常(@Validated)
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ChatResponse handleConstraintViolationException(ConstraintViolationException e) {
String message = e.getConstraintViolations().iterator().next().getMessage();
log.warn("参数校验失败:{}", message);
return ChatResponse.error("参数错误:" + message);
}
/**
* 其他异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ChatResponse handleException(Exception e) {
log.error("系统异常", e);
return ChatResponse.error("系统繁忙,请稍后重试");
}
}
现在,一个完整的SpringBoot微服务就搭建好了。启动应用,访问http://localhost:8080就能看到服务运行了。
5. 测试与验证
5.1 测试SpringBoot接口
先用Postman或curl测试一下接口:
# 测试健康检查
curl http://localhost:8080/api/chat/health
# 测试单轮对话
curl -X POST "http://localhost:8080/api/chat/single" \
-H "Content-Type: application/json" \
-d '{
"prompt": "用Java写一个Hello World程序"
}'
# 测试多轮对话(第一次请求会创建会话)
curl -X POST "http://localhost:8080/api/chat/multi" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Java有哪些特点?"
}'
# 继续对话(使用返回的sessionId)
curl -X POST "http://localhost:8080/api/chat/multi" \
-H "Content-Type: application/json" \
-d '{
"prompt": "那Python呢?",
"sessionId": "session_123456789"
}'
5.2 编写单元测试
为了保证代码质量,写几个简单的单元测试:
// ChatServiceTest.java
package com.example.chatglm.service;
import com.example.chatglm.client.ChatGptClient;
import com.example.chatglm.model.ChatRequest;
import com.example.chatglm.model.ChatResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class ChatServiceTest {
@Mock
private ChatGptClient chatGptClient;
@InjectMocks
private ChatService chatService;
@Test
void testSingleChat_Success() {
// 模拟API返回
ChatGptClient.ChatResponse mockResponse = new ChatGptClient.ChatResponse(
200,
"success",
new ChatGptClient.ChatResponse.ChatData(
"这是一个测试回复",
"[]",
"2024-01-01 12:00:00"
)
);
when(chatGptClient.chat(anyString(), anyString()))
.thenReturn(mockResponse);
// 构造请求
ChatRequest request = new ChatRequest();
request.setPrompt("测试消息");
// 执行测试
ChatResponse response = chatService.singleChat(request);
// 验证结果
assertNotNull(response);
assertEquals(200, response.getCode());
assertEquals("success", response.getMessage());
}
@Test
void testHealthCheck_ServiceAvailable() {
when(chatGptClient.healthCheck()).thenReturn(true);
boolean healthy = chatService.healthCheck();
assertTrue(healthy);
}
}
5.3 性能测试建议
在实际使用前,建议做一下性能测试,了解服务的承载能力。可以用JMeter或wrk这样的工具。
这里给个简单的wrk测试命令:
# 测试单轮对话接口
wrk -t4 -c100 -d30s --latency \
-s post.lua \
http://localhost:8080/api/chat/single
# post.lua文件内容
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
wrk.body = '{"prompt": "测试性能"}'
主要关注这几个指标:
- QPS(每秒查询数):能处理多少请求
- 响应时间:平均响应时间、95分位响应时间
- 错误率:请求失败的比例
根据我的测试,在CPU模式下,ChatGLM-6B生成一个回复大概需要2-5秒。如果是GPU,能快很多,大概0.5-2秒。这个数据你可以作为参考。
6. 生产环境部署建议
6.1 Python服务部署优化
在实际生产环境,Python服务需要做一些优化:
使用Gunicorn+Uvicorn:提高并发处理能力
# gunicorn_config.py
workers = 4 # 根据CPU核心数调整
worker_class = "uvicorn.workers.UvicornWorker"
bind = "0.0.0.0:8000"
timeout = 300 # 超时时间,模型推理可能较慢
keepalive = 5
启动命令:
gunicorn -c gunicorn_config.py api_server:app
启用模型量化:减少内存占用
# 在api_server.py的load_model函数中修改
def load_model():
global model, tokenizer
model_path = "./chatglm-6b"
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# 使用8-bit量化
model = AutoModel.from_pretrained(
model_path,
trust_remote_code=True
).quantize(8).half().cuda() # 8-bit量化
# 或者使用4-bit量化(更省内存)
# model = AutoModel.from_pretrained(
# "THUDM/chatglm-6b-int4", # 需要下载量化版模型
# trust_remote_code=True
# ).half().cuda()
model = model.eval()
添加请求队列:防止并发过高
# 可以使用asyncio的Semaphore限制并发
import asyncio
class RequestLimiter:
def __init__(self, max_concurrent=2):
self.semaphore = asyncio.Semaphore(max_concurrent)
async def process(self, prompt, history):
async with self.semaphore:
# 处理请求
return await self._generate_response(prompt, history)
limiter = RequestLimiter(max_concurrent=2) # 最多同时处理2个请求
6.2 SpringBoot服务部署优化
连接池配置:优化HTTP客户端
# application-prod.yml
chatglm:
api:
url: http://python-api:8000 # 生产环境地址
connect-timeout: 5000 # 连接超时5秒
socket-timeout: 30000 # 读写超时30秒
max-connections: 100 # 最大连接数
max-per-route: 20 # 每个路由最大连接数
添加熔断降级:使用Resilience4j
<!-- pom.xml添加依赖 -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.1</version>
</dependency>
// 在ChatService中添加熔断
@CircuitBreaker(name = "chatglmApi", fallbackMethod = "fallbackResponse")
public ChatResponse multiChat(ChatRequest request) {
// 原有逻辑
}
// 降级方法
private ChatResponse fallbackResponse(ChatRequest request, Exception e) {
log.warn("ChatGLM服务降级,返回默认回复", e);
return ChatResponse.success(
"系统繁忙,请稍后重试",
request.getSessionId(),
LocalDateTime.now().toString()
);
}
监控和日志:添加监控指标
// 使用Micrometer添加监控
@Autowired
private MeterRegistry meterRegistry;
public ChatResponse multiChat(ChatRequest request) {
Timer.Sample sample = Timer.start(meterRegistry);
try {
// 业务逻辑
return result;
} finally {
sample.stop(Timer.builder("chatglm.request.duration")
.tag("type", "multi")
.register(meterRegistry));
}
}
6.3 容器化部署
建议使用Docker容器化部署,便于管理和扩展。
Python服务Dockerfile:
# Dockerfile.python
FROM python:3.8-slim
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
git \
git-lfs \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制代码
COPY api_server.py .
COPY chatglm-6b ./chatglm-6b/
# 下载模型(如果没提前下载)
# RUN git lfs install && \
# git clone https://huggingface.co/THUDM/chatglm-6b
EXPOSE 8000
CMD ["python", "api_server.py"]
SpringBoot服务Dockerfile:
# Dockerfile.java
FROM openjdk:11-jre-slim
WORKDIR /app
# 复制jar包
COPY target/chatglm-springboot-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
docker-compose编排:
# docker-compose.yml
version: '3.8'
services:
chatglm-python:
build:
context: .
dockerfile: Dockerfile.python
ports:
- "8000:8000"
environment:
- CUDA_VISIBLE_DEVICES=0 # 如果有GPU
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
chatglm-springboot:
build:
context: .
dockerfile: Dockerfile.java
ports:
- "8080:8080"
depends_on:
- chatglm-python
environment:
- CHATGLM_API_URL=http://chatglm-python:8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/chat/health"]
interval: 30s
timeout: 10s
retries: 3
6.4 安全考虑
API密钥验证:在生产环境一定要加认证
// 添加API密钥验证
@Component
public class ApiKeyInterceptor implements HandlerInterceptor {
@Value("${app.api-key}")
private String validApiKey;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String apiKey = request.getHeader("X-API-Key");
if (apiKey == null || !apiKey.equals(validApiKey)) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("Invalid API Key");
return false;
}
return true;
}
}
请求限流:防止滥用
// 使用Guava RateLimiter
@Component
public class RateLimitService {
private final RateLimiter rateLimiter = RateLimiter.create(10.0); // 每秒10个请求
public boolean tryAcquire() {
return rateLimiter.tryAcquire();
}
public boolean tryAcquire(String clientId) {
// 可以按客户端限流
return rateLimiter.tryAcquire();
}
}
7. 常见问题与调试技巧
7.1 Python服务启动失败
问题:模型加载时内存不足
解决:
- 使用量化版本模型(int4或int8)
- 增加swap空间
- 使用CPU模式(速度会慢)
# 使用CPU模式
model = AutoModel.from_pretrained(model_path, trust_remote_code=True).float()
问题:CUDA out of memory
解决:
- 减少batch size
- 使用
torch.cuda.empty_cache()清理缓存 - 使用梯度检查点(gradient checkpointing)
7.2 Java服务连接问题
问题:连接超时
解决:
# 增加超时时间
chatglm:
api:
connect-timeout: 10000 # 10秒
socket-timeout: 60000 # 60秒
问题:响应时间过长
解决:
- 添加超时控制
- 使用异步调用
- 添加缓存(对相同问题缓存回复)
// 添加缓存
@Component
public class ResponseCache {
private final Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
public String get(String key) {
return cache.getIfPresent(key);
}
public void put(String key, String value) {
cache.put(key, value);
}
}
7.3 性能优化建议
- 启用HTTP压缩:减少网络传输数据量
- 使用连接池:避免频繁创建连接
- 批量处理请求:如果有批量需求
- 异步处理:使用CompletableFuture或WebFlux
// 异步调用示例
@Service
public class AsyncChatService {
@Async
public CompletableFuture<ChatResponse> asyncChat(ChatRequest request) {
return CompletableFuture.supplyAsync(() -> {
// 同步调用
return chatService.multiChat(request);
});
}
}
7.4 监控和日志
建议添加详细的日志,方便排查问题:
@Slf4j
@Service
public class ChatService {
public ChatResponse multiChat(ChatRequest request) {
long startTime = System.currentTimeMillis();
try {
// 业务逻辑
ChatResponse response = // ...
long duration = System.currentTimeMillis() - startTime;
log.info("请求处理完成,耗时:{}ms,会话ID:{}",
duration, request.getSessionId());
return response;
} catch (Exception e) {
log.error("处理请求失败,会话ID:{},错误:{}",
request.getSessionId(), e.getMessage(), e);
throw e;
}
}
}
8. 总结
走完这一整套流程,你应该已经成功把ChatGLM-6B集成到SpringBoot微服务里了。回头看看,其实关键就是那几步:先把Python服务搭起来提供API,然后用Java去调用这个API,最后把整个东西包装成标准的微服务。
这种架构最大的好处就是灵活。Python那边专心做模型推理,Java这边专心做业务逻辑,各干各的,互不干扰。以后要是想换模型,或者模型升级了,只需要动Python那边,Java这边基本不用改。
实际用起来,你可能会发现响应时间有点长,特别是第一次请求的时候。这是正常的,模型加载和推理本来就需要时间。如果对性能要求高,可以考虑用GPU,或者对回复做缓存,相同的问题不用每次都让模型重新生成。
还有一点要注意,这套方案适合内部系统或者对实时性要求不高的场景。如果是高并发的线上服务,可能还需要再加一层消息队列,做请求的缓冲和削峰。
最后说句实在话,技术总是在变的,今天ChatGLM-6B还不错,明天可能就有更好的模型出来了。但这套集成思路是通用的,不管以后换什么模型,基本都能照这个套路来。关键是先把路跑通,后面优化起来就有方向了。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐
所有评论(0)