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.binconfig.jsontokenizer.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服务启动失败

问题:模型加载时内存不足

解决

  1. 使用量化版本模型(int4或int8)
  2. 增加swap空间
  3. 使用CPU模式(速度会慢)
# 使用CPU模式
model = AutoModel.from_pretrained(model_path, trust_remote_code=True).float()

问题:CUDA out of memory

解决

  1. 减少batch size
  2. 使用torch.cuda.empty_cache()清理缓存
  3. 使用梯度检查点(gradient checkpointing)

7.2 Java服务连接问题

问题:连接超时

解决

# 增加超时时间
chatglm:
  api:
    connect-timeout: 10000  # 10秒
    socket-timeout: 60000   # 60秒

问题:响应时间过长

解决

  1. 添加超时控制
  2. 使用异步调用
  3. 添加缓存(对相同问题缓存回复)
// 添加缓存
@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 性能优化建议

  1. 启用HTTP压缩:减少网络传输数据量
  2. 使用连接池:避免频繁创建连接
  3. 批量处理请求:如果有批量需求
  4. 异步处理:使用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星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

更多推荐