去年公司要做智能客服知识库问答,这个活儿落到了我这个纯 Java 后端头上。从 “没碰过大模型” 到上线一套可用的 RAG 问答系统,过程记录在这里 —— 架构怎么设计、代码怎么写、坑怎么踩,Java 工程师可以直接参考。
一、需求与架构:先想清楚再动手
需求: 客服人员输入问题,系统基于公司知识库(产品手册、FAQ、流程文档)给出带来源的回答。
技术选型: RAG(检索增强生成)—— 不微调模型,给大模型外挂一个可检索的知识库。理由:随时更新、可溯源、成本低。
整体架构:
文档入库:Word/PDF → 解析 → 切分 → 向量化 → 存入向量库
在线问答:问题 → 向量检索TopK → 拼Prompt → 调大模型 → 回答(带来源)
服务层:Spring Boot 统一封装(API/超时/重试/成本监控)
二、文档入库:切分是第一个坑
文档切分看着简单,做错全盘皆输:
// DocChunker.java - 按段落切分+重叠窗口
public class DocChunker {
public List split(String doc, int chunkSize, int overlap) {
List chunks = new ArrayList<>();
String[] paragraphs = doc.split(“\n\n”); // 先按段落边界
StringBuilder buffer = new StringBuilder();
for (String p : paragraphs) { if (buffer.length() + p.length() > chunkSize) { chunks.add(buffer.toString()); // 保留尾部重叠,避免一句话被拦腰切断 buffer = new StringBuilder( buffer.substring(Math.max(0, buffer.length() - overlap))); } buffer.append(p).append("\n\n"); } if (buffer.length() > 0) chunks.add(buffer.toString()); return chunks; }}
踩坑记录 1: 一开始按固定 500 字符切,产品参数表被切成两半,检索出来 “报价在表格下半截”。改成按段落边界切 + 重叠窗口后,检索质量明显提升。
三、向量检索:TopK 别拍脑袋
// Retriever.java - 向量化+相似度检索
@Service
public class Retriever {
private final EmbeddingClient embeddingClient;
private final VectorStore vectorStore;
public List<RetrievedDoc> retrieve(String question, int topK) { float[] qVec = embeddingClient.embed(question); List<RetrievedDoc> docs = vectorStore.search(qVec, topK); // 加Rerank重排:交叉编码器对结果二次排序,精度更高 return reranker.rerank(question, docs); }}
踩坑记录 2: TopK=3 漏答案、TopK=10 全是噪声。最后建了 20 条测试问答做评测集,K=5 + 重排组合效果最好。没有评测集的调参,都是碰运气。
四、问答组装:系统提示词是关键
回答质量一半在提示词。核心是 “约束 + 溯源”:
// RagService.java - 完整问答链路
@Service
public class RagService {
public String answer(String question) { List<RetrievedDoc> contexts = retriever.retrieve(question, 5); String prompt = """ 你是企业知识库助手,只依据以下资料回答: 【资料】 %s 【问题】 %s 要求: 1. 资料中没有的信息,必须回答"未找到相关资料",不要编造 2. 回答时标注引用的资料来源(文档名+段落) 3. 回答控制在150字以内,客服口径,口语化 """.formatted( contexts.stream() .map(d -> "[" + d.source() + "] " + d.content()) .collect(Collectors.joining("\n---\n")), question); return llmClient.chat(prompt); }}
踩坑记录 3: 没写 “没有就说没有” 之前,模型经常一本正经编答案;加上这条约束 + 强制标来源后,幻觉明显减少,客服反馈 “可信多了”。
五、工程化收尾:Java 后端的看家本领
大模型接入只是第一步,真正体现 Java 工程能力的在后面:
// 1. 统一封装:超时+错误码+退避重试
@Configuration
public class LlmConfig {
@Bean
public RestClient llmClient() {
return RestClient.builder()
.baseUrl(System.getenv(“LLM_API_URL”))
.defaultHeader(“Authorization”, "Bearer " + System.getenv(“LLM_API_KEY”))
.requestFactory(client -> client
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(60)))
.build();
}
}
// 2. 成本监控:token用量记录
@Component
public class TokenMeter {
private final MeterRegistry registry;
public void record(String model, int promptTokens, int completionTokens) { registry.counter("llm.tokens.prompt", "model", model).increment(promptTokens); registry.counter("llm.tokens.completion", "model", model).increment(completionTokens); // 按日聚合,超预算告警 }}
// 3. 结果缓存:相同问题不重复调模型
@Cacheable(cacheNames = “qa”, key = “#question”)
public String answerCached(String question) {
return answer(question);
}
要点: 密钥走环境变量、请求全量超时、重试限次退避、token 记账、结果缓存 —— 这套工程规范,是 Java 后端做 AI 应用的核心优势。
六、复盘:Java 工程师学 AI 应用,到底学什么
| 能力 | 重要度 | 说明 |
|---|---|---|
| 大模型 API 调用 | ★★★ | HTTP 封装,半天上手 |
| RAG 检索增强 | ★★★★★ | 企业落地标配,本次核心 |
| Prompt 设计 | ★★★★ | 约束 + 溯源,决定回答质量 |
| 上下文管理 | ★★★★ | 滑动窗口 + 摘要,多轮不爆 |
| 工程化 | ★★★★★ | Java 后端天生优势 |
这套能力我是在 Java AI 实训里体系化补的 ——8 个月 32 周 7 阶段,14 个真实项目从 LangChain、RAG、Agent 到大模型应用,再到 Java 全栈微服务,把 “会调 API” 练成了 “能交付系统”。学 AI 应用对后端工程师来说,不是转行,是升级:把大模型工程化地接进业务系统,正是我们的活。
下一个版本,我准备给系统加 Agent 能力(让模型会调用内部工具查订单、算指标)。这条路,越走越宽。
Q:Java 做 AI 应用需要重新学语言吗?
A:不需要。大模型 API 就是 HTTP 接口,Java 工程能力(Spring Boot、缓存、监控)直接复用。企业系统接大模型,Java 反而是主力。
Q:RAG 系统和 “直接问大模型” 有什么区别?
A:大模型不知道公司私有资料。RAG 给模型外挂知识库:检索相关资料再回答,可更新、可溯源、成本低。企业落地 90% 场景是 RAG。
Q:检索质量怎么调?
A:三步:文档按语义边界切分、TopK 用评测集调、加 Rerank 重排。缺任何一步,检索都是碰运气。
Q:怎么减少模型幻觉?
A:系统提示词写 “没有就说没有”+ 强制标注来源 + 回答限长。三条一起上,幻觉显著减少。这是客服场景的必备配置。
本文为 Java 后端 AI 应用实践记录,观点仅代表个人。