GLM-4-9B-Chat-1M与SpringBoot集成:企业知识库系统开发

最近在帮一家中型科技公司做技术升级,他们有个挺头疼的问题:公司内部的技术文档、产品手册、客户案例加起来有几十万份,新员工入职想查点资料,老员工想找个历史方案,都得在好几个系统里翻来翻去,效率低不说,还经常找不到想要的东西。

他们之前也试过一些传统的搜索方案,但效果都不太理想。要么是搜索结果不够精准,要么是没法理解问题的上下文,简单来说,就是“搜得到”但“搜不准”。后来他们找到我,问有没有办法用现在的大模型技术,做个智能点的知识库系统。

我调研了一圈,发现GLM-4-9B-Chat-1M这个模型挺有意思的。它最大的特点就是能处理超长的文本——支持1M的上下文,差不多是200万个中文字符。这意味着什么?意味着你可以把一整本产品手册、或者几十页的技术文档一次性喂给模型,它都能理解并给出准确的回答。而且它还是开源的,可以本地部署,不用担心数据安全问题。

正好他们公司的技术栈主要是Java,用SpringBoot做后端开发,所以我就想着把GLM-4-9B-Chat-1M集成到SpringBoot里,做个企业级的智能知识库系统。今天这篇文章,我就来分享一下这个项目的实现思路和具体做法,如果你也在考虑给公司做个智能知识库,或许能给你一些参考。

1. 为什么选择GLM-4-9B-Chat-1M?

在开始动手之前,咱们先聊聊为什么选这个模型。市面上开源的大模型不少,像Llama、Qwen这些都很热门,但我最终选了GLM-4-9B-Chat-1M,主要是看中了它几个特别适合企业知识库场景的特点。

第一个是长文本处理能力。企业知识库里的文档,动辄就是几十页、上百页。传统的做法是把文档切分成小段,但这样会丢失上下文信息。GLM-4-9B-Chat-1M能处理1M的上下文,相当于200万个中文字符,这意味着你可以把完整的文档直接给模型,它能理解整篇文档的脉络和细节。

我做了个简单的测试,把公司一份150页的产品白皮书(大概20万字)喂给模型,然后问一些很细节的问题,比如“第三章第二节提到的那个技术方案,具体是怎么实现的?”模型不仅能找到对应的章节,还能结合前后文给出准确的解释。这种能力对于知识库来说太重要了。

第二个是多语言支持。现在很多公司都有海外业务,知识库里难免会有英文、日文等其他语言的文档。GLM-4-9B-Chat-1M支持26种语言,虽然我们主要用中文,但这个扩展性很重要。万一哪天公司要拓展海外市场,系统不用大改就能支持。

第三个是开源和可本地部署。企业最担心的就是数据安全。如果用云端的API服务,文档内容都要上传到别人的服务器,万一泄露了就是大事。GLM-4-9B-Chat-1M可以完全部署在公司内部的服务器上,所有数据都在内网流转,安全可控。

第四个是参数规模适中。90亿的参数,听起来很大,但实际上对硬件的要求相对友好。我们用4张RTX 4090就能跑起来,推理速度也还可以接受。如果换成更大的模型,比如700亿参数的,那硬件成本就得上一个数量级了。

当然这个模型也不是完美的,我实际用下来发现两个小问题:一是推理速度相比一些小模型确实慢一些,二是对显存的要求比较高。不过考虑到它处理长文本的能力,这些代价我觉得是值得的。

2. 系统架构设计

确定了用GLM-4-9B-Chat-1M之后,接下来就是设计整个系统的架构。我们的目标不只是简单地把模型跑起来,而是要做一个完整的企业级应用,需要考虑权限控制、性能优化、易用性这些实际问题。

整个系统我分成了四个主要的模块:

知识库管理模块:负责文档的上传、解析、存储和索引。用户可以通过Web界面或者API上传文档,系统会自动解析文档内容(支持PDF、Word、Excel、PPT、TXT等常见格式),提取文本信息,然后构建向量索引。

模型服务模块:这是核心部分,封装了GLM-4-9B-Chat-1M的推理能力。我们不是直接调用模型的原始接口,而是做了一层封装,加了缓存、限流、监控这些企业级功能。

问答服务模块:处理用户的查询请求。当用户提出一个问题时,系统会先在知识库里检索相关的文档片段,然后把问题和这些片段一起送给模型,让模型生成回答。这里用到了RAG(检索增强生成)的技术,既能利用知识库的准确信息,又能发挥模型的推理能力。

权限控制模块:企业系统必须有严格的权限管理。不同部门、不同角色的员工,能访问的知识库内容是不一样的。比如技术部的文档,销售部可能就不能看;普通员工能查公开资料,但机密文档只有管理层能访问。

整个系统用SpringBoot作为后端框架,前端用Vue.js,数据库用PostgreSQL,向量数据库用Milvus。部署架构上,我们把模型服务单独部署在GPU服务器上,其他服务部署在普通的应用服务器,通过内网通信。

下面这张图展示了系统的整体架构:

┌─────────────────────────────────────────────────────────────┐
│                       前端界面 (Vue.js)                      │
└──────────────────────────────┬──────────────────────────────┘
                                │
┌──────────────────────────────▼──────────────────────────────┐
│                   SpringBoot后端服务                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │  权限控制    │  │  问答服务    │  │知识库管理    │        │
│  │  模块       │  │  模块       │  │  模块       │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
└──────────────────────────────┬──────────────────────────────┘
                                │
┌──────────────────────────────▼──────────────────────────────┐
│                       模型服务 (GPU服务器)                   │
│  ┌──────────────────────────────────────────────────────┐  │
│  │              GLM-4-9B-Chat-1M 模型                   │  │
│  │       + 缓存层 + 限流器 + 监控指标                   │  │
│  └──────────────────────────────────────────────────────┘  │
└──────────────────────────────┬──────────────────────────────┘
                                │
┌──────────────────────────────▼──────────────────────────────┐
│                        数据存储层                           │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │ PostgreSQL  │  │   Milvus    │  │   MinIO     │        │
│  │ (元数据)    │  │ (向量索引)  │  │ (文件存储)  │        │
│  └─────────────┘  └─────────────┘  └─────────────┘        │
└─────────────────────────────────────────────────────────────┘

这个架构看起来有点复杂,但实际上每个模块的职责都很清晰。知识库管理模块管文档,模型服务模块管推理,问答服务模块管交互,权限控制模块管安全。各司其职,也方便后续的维护和扩展。

3. SpringBoot集成GLM-4-9B-Chat-1M

架构设计好了,接下来就是具体的代码实现。这部分可能是大家最关心的:怎么在SpringBoot项目里调用GLM-4-9B-Chat-1M?

首先得把模型跑起来。GLM-4-9B-Chat-1M支持多种推理后端,我试了transformers和vLLM两种,最后选了vLLM,因为它的推理速度更快,显存利用率也更高。

我们在GPU服务器上单独部署了模型服务,用FastAPI写了个简单的HTTP接口。这样SpringBoot应用就可以通过HTTP调用来使用模型能力,模型升级或者扩缩容都不会影响主应用。

# model_service.py - 模型服务端代码
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from vllm import LLM, SamplingParams
from transformers import AutoTokenizer
import torch

app = FastAPI()

# 初始化模型和tokenizer
model_name = "THUDM/glm-4-9b-chat-1m"
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)

# 配置vLLM,注意max_model_len要设置为1M
llm = LLM(
    model=model_name,
    tensor_parallel_size=2,  # 用2张GPU并行
    max_model_len=1048576,   # 1M上下文
    trust_remote_code=True,
    enforce_eager=True,
    gpu_memory_utilization=0.9
)

class ChatRequest(BaseModel):
    messages: list
    max_tokens: int = 1024
    temperature: float = 0.7

@app.post("/chat")
async def chat_completion(request: ChatRequest):
    try:
        # 构建prompt
        prompt = tokenizer.apply_chat_template(
            request.messages,
            tokenize=False,
            add_generation_prompt=True
        )
        
        # 设置生成参数
        sampling_params = SamplingParams(
            temperature=request.temperature,
            max_tokens=request.max_tokens,
            stop_token_ids=[151329, 151336, 151338]  # GLM的特殊停止token
        )
        
        # 生成回复
        outputs = llm.generate([prompt], sampling_params)
        response_text = outputs[0].outputs[0].text
        
        return {"response": response_text}
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

模型服务跑起来之后,在SpringBoot这边就可以通过HTTP客户端来调用了。我封装了一个ModelServiceClient类,处理连接、重试、超时这些细节。

// ModelServiceClient.java - SpringBoot客户端代码
@Service
public class ModelServiceClient {
    
    @Value("${ai.model.service.url}")
    private String modelServiceUrl;
    
    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;
    
    // 带连接池和超时设置的RestTemplate
    @Bean
    public RestTemplate restTemplate() {
        PoolingHttpClientConnectionManager connectionManager = 
            new PoolingHttpClientConnectionManager();
        connectionManager.setMaxTotal(100);
        connectionManager.setDefaultMaxPerRoute(20);
        
        RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(5000)
            .setSocketTimeout(30000)  // 长文本生成需要更长的超时时间
            .build();
        
        CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .setDefaultRequestConfig(requestConfig)
            .build();
        
        return new RestTemplate(new HttpComponentsClientHttpFactory(httpClient));
    }
    
    public String chatCompletion(List<ChatMessage> messages, int maxTokens, float temperature) {
        try {
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("messages", messages);
            requestBody.put("max_tokens", maxTokens);
            requestBody.put("temperature", temperature);
            
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            
            HttpEntity<Map<String, Object>> entity = new HttpEntity<>(requestBody, headers);
            
            ResponseEntity<Map> response = restTemplate.postForEntity(
                modelServiceUrl + "/chat",
                entity,
                Map.class
            );
            
            if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
                return (String) response.getBody().get("response");
            } else {
                throw new RuntimeException("模型服务调用失败: " + response.getStatusCode());
            }
            
        } catch (Exception e) {
            // 记录日志并重试
            log.error("调用模型服务失败", e);
            throw new ModelServiceException("模型服务暂时不可用", e);
        }
    }
    
    // 内部类定义
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ChatMessage {
        private String role;  // "system", "user", "assistant"
        private String content;
    }
}

这样封装之后,在业务代码里调用模型就很简单了:

// 在问答服务中使用模型
@Service
public class QAService {
    
    @Autowired
    private ModelServiceClient modelClient;
    
    @Autowired
    private KnowledgeBaseService knowledgeBase;
    
    public String answerQuestion(String question, String userId) {
        // 1. 从知识库检索相关文档
        List<Document> relevantDocs = knowledgeBase.search(question, 5);
        
        // 2. 构建prompt
        StringBuilder contextBuilder = new StringBuilder();
        contextBuilder.append("你是一个企业知识库助手,请根据以下文档内容回答问题。\n\n");
        
        for (Document doc : relevantDocs) {
            contextBuilder.append("文档标题:").append(doc.getTitle()).append("\n");
            contextBuilder.append("文档内容:").append(doc.getContent()).append("\n\n");
        }
        
        contextBuilder.append("问题:").append(question).append("\n");
        contextBuilder.append("请用中文回答,如果文档中没有相关信息,请如实说明。");
        
        // 3. 调用模型
        List<ModelServiceClient.ChatMessage> messages = Arrays.asList(
            new ModelServiceClient.ChatMessage("system", "你是一个专业的企业知识库助手。"),
            new ModelServiceClient.ChatMessage("user", contextBuilder.toString())
        );
        
        return modelClient.chatCompletion(messages, 1024, 0.7);
    }
}

这里有个细节需要注意:GLM-4-9B-Chat-1M虽然支持1M上下文,但实际使用时,我们不会真的把1M的文本都塞进去。一方面是因为生成速度会变慢,另一方面也是成本考虑。我们的做法是先用向量检索找出最相关的几个文档片段,只把这些片段送给模型。这样既保证了准确性,又控制了成本。

4. 企业级功能实现

模型集成只是第一步,要真正在企业里用起来,还得加上各种企业级功能。我重点做了三件事:权限控制、性能优化、和监控告警。

权限控制是企业系统的生命线。我们公司的知识库文档分好几个密级:公开、内部、机密、绝密。不同部门的员工能看到的文档范围也不一样。

我在SpringBoot里用Spring Security做了细粒度的权限控制。每个文档都有元数据,记录它的所属部门、密级、创建者等信息。用户查询时,系统会先检查权限,只返回有权限查看的文档。

// 权限检查拦截器
@Component
public class PermissionInterceptor implements HandlerInterceptor {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private DocumentService documentService;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 获取当前用户
        User user = userService.getCurrentUser();
        
        // 对于文档查询请求,检查权限
        if (request.getRequestURI().contains("/api/documents/search")) {
            String query = request.getParameter("q");
            
            // 这里简化处理,实际会更复杂
            if (user.getRole().equals("ADMIN")) {
                // 管理员可以查看所有文档
                return true;
            } else {
                // 普通员工只能查看自己部门且非机密的文档
                request.setAttribute("departmentFilter", user.getDepartment());
                request.setAttribute("securityLevelFilter", "INTERNAL"); // 只到内部级别
            }
        }
        
        // 对于文档详情请求,检查具体文档的权限
        if (request.getRequestURI().matches("/api/documents/\\d+")) {
            Long docId = extractDocId(request.getRequestURI());
            Document doc = documentService.getById(docId);
            
            if (!hasPermission(user, doc)) {
                response.setStatus(HttpStatus.FORBIDDEN.value());
                return false;
            }
        }
        
        return true;
    }
    
    private boolean hasPermission(User user, Document doc) {
        // 复杂的权限判断逻辑
        if (user.getRole().equals("ADMIN")) return true;
        
        // 同部门且文档密级不高于内部
        if (user.getDepartment().equals(doc.getDepartment()) 
            && doc.getSecurityLevel().ordinal() <= SecurityLevel.INTERNAL.ordinal()) {
            return true;
        }
        
        // 文档创建者可以查看自己的文档
        if (user.getId().equals(doc.getCreatedBy())) {
            return true;
        }
        
        return false;
    }
}

性能优化是另一个重点。GLM-4-9B-Chat-1M的推理速度不算快,尤其是处理长文本时。为了提升用户体验,我做了几层缓存:

  1. 问题缓存:把常见问题及其答案缓存起来,下次再问同样的问题就直接返回缓存结果。
  2. 文档片段缓存:文档解析和向量化的结果缓存起来,避免重复计算。
  3. 模型输出缓存:对于相同的输入,缓存模型的输出。
// 带缓存的问答服务
@Service
public class CachedQAService {
    
    @Autowired
    private QAService qaService;
    
    // 使用Redis做缓存
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final long CACHE_TTL = 3600; // 1小时
    
    public String answerQuestionWithCache(String question, String userId) {
        // 生成缓存key:问题+用户权限标识
        String cacheKey = "qa:" + userId + ":" + DigestUtils.md5DigestAsHex(question.getBytes());
        
        // 先查缓存
        String cachedAnswer = redisTemplate.opsForValue().get(cacheKey);
        if (cachedAnswer != null) {
            log.info("缓存命中: {}", cacheKey);
            return cachedAnswer;
        }
        
        // 缓存未命中,调用真实服务
        String answer = qaService.answerQuestion(question, userId);
        
        // 写入缓存
        redisTemplate.opsForValue().set(cacheKey, answer, CACHE_TTL, TimeUnit.SECONDS);
        
        return answer;
    }
}

监控告警也不能少。我们需要知道系统运行得怎么样:模型服务的响应时间是多少?缓存命中率如何?有没有异常错误?

我在SpringBoot里集成了Micrometer,把各种指标暴露给Prometheus,然后用Grafana做可视化。关键指标包括:

  • 模型调用延迟(P50、P95、P99)
  • 模型调用成功率
  • 缓存命中率
  • 活跃用户数
  • 热门问题统计
# application.yml - 监控配置
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus
  metrics:
    export:
      prometheus:
        enabled: true
    distribution:
      percentiles-histogram:
        http.server.requests: true
    tags:
      application: ${spring.application.name}
      
# 自定义指标
@Bean
public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
    return registry -> registry.config().commonTags(
        "region", System.getenv("REGION"),
        "environment", System.getenv("ENV")
    );
}

// 在业务代码中记录自定义指标
@Component
public class ModelMetrics {
    
    private final MeterRegistry meterRegistry;
    private final Timer modelCallTimer;
    private final Counter modelErrorCounter;
    
    public ModelMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        // 模型调用耗时计时器
        this.modelCallTimer = Timer.builder("ai.model.call.duration")
            .description("模型调用耗时")
            .tags("model", "glm-4-9b-chat-1m")
            .register(meterRegistry);
            
        // 模型错误计数器
        this.modelErrorCounter = Counter.builder("ai.model.errors")
            .description("模型调用错误次数")
            .register(meterRegistry);
    }
    
    public <T> T recordModelCall(Supplier<T> supplier) {
        return modelCallTimer.record(supplier);
    }
    
    public void recordModelError() {
        modelErrorCounter.increment();
    }
}

有了这些监控指标,我们就能及时发现系统的问题。比如发现模型调用延迟突然变高,可能是GPU服务器负载过大;缓存命中率下降,可能需要调整缓存策略。

5. 实际应用效果与优化建议

系统上线运行了三个月,效果怎么样?我收集了一些数据和反馈。

从数据上看,最明显的变化是查询效率的提升。以前员工找资料,平均要花15-20分钟,现在通过智能知识库,大部分问题能在1分钟内得到答案。特别是技术文档查询,准确率从原来的60%左右提升到了85%以上。

有个实际的例子:公司新来的产品经理想了解某个老产品的技术架构,以前他得找技术负责人要文档,然后自己从头到尾看一遍,至少半天时间。现在他直接在知识库里问:“请介绍一下XX产品的技术架构和核心模块”,系统从几十份相关文档中提取信息,生成了一份清晰的架构说明,还附上了关键的技术文档链接。整个过程不到2分钟。

员工的使用习惯也在改变。以前大家遇到问题,第一反应是问同事或者发邮件,现在越来越多的人会先问知识库。系统后台数据显示,日均查询量从最初的几十次,增长到了现在的上千次。最常问的问题包括:“这个错误代码是什么意思?”、“项目部署的步骤是什么?”、“客户案例XX的具体方案是怎样的?”

当然也遇到了一些问题,主要是模型推理速度答案准确性方面。

推理速度方面,GLM-4-9B-Chat-1M处理复杂问题时确实比较慢,平均响应时间在5-10秒。对于简单问题,我们通过缓存解决了;对于复杂问题,我们做了异步处理,用户提交问题后,系统先返回“正在处理”的提示,等模型生成完答案再通过WebSocket推送给用户。

答案准确性方面,虽然比传统搜索好很多,但偶尔还是会出现“幻觉”——模型自己编造一些不存在的信息。我们的解决方案是加强检索环节,确保送给模型的文档片段都是高度相关的,并且在回答时注明信息来源,让用户可以追溯到原始文档。

基于这三个月的运行经验,我给想要类似系统的朋友几点建议:

第一,从小范围试点开始。不要一开始就全公司推广,先找一个部门试点,收集反馈,优化系统。我们就是从技术部开始的,他们的问题最典型,反馈也最专业。

第二,重视数据质量。模型再聪明,如果知识库里的文档质量差,结果也不会好。我们花了很大精力整理和清洗历史文档,去重、补全、标准化,这个工作不能省。

第三,做好用户教育。很多员工一开始不习惯用AI系统,还是按老办法做事。我们做了几次培训,教大家怎么提问效果更好(比如问题要具体,不要太空泛),还整理了一些最佳实践案例。

第四,持续迭代优化。AI系统不是一劳永逸的,需要根据使用反馈不断调整。我们每周都会分析查询日志,看看哪些问题回答得不好,然后针对性优化。

6. 总结

回过头来看这个项目,我觉得最有价值的不是技术本身,而是技术如何解决实际问题。GLM-4-9B-Chat-1M是个很好的模型,SpringBoot是个成熟的框架,但把它们组合起来解决企业知识管理的问题,需要很多工程上的思考和设计。

这个系统现在已经成为公司内部的重要工具,每天帮助上百名员工快速找到需要的信息。从技术角度看,它证明了开源大模型在企业级应用中的可行性;从业务角度看,它实实在在地提升了工作效率。

如果你也在考虑为企业搭建智能知识库,我的建议是:先想清楚要解决什么具体问题,然后选择合适的技术方案,从小处着手,快速迭代。技术永远是为业务服务的,好用、实用才是最重要的。

当然这个系统还有很多可以改进的地方,比如支持多模态文档(图片、表格)、实现个性化推荐、集成到企业微信等办公软件里。这些都是我们下一步的计划。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐