用 Java 做大模型后端,整体可以理解为两件事:
-
把"模型能力"包装成一个可靠的服务(API)
-
让业务方(Web/小程序/APP/内部系统)能安全、高效地调用它
我先给你一个整体开发流程 ,然后分模块说需要注意什么,中间穿插一点 Spring Boot 风格的示例,方便你落地。
一、整体开发流程(从 0 到 上线)
第 1 步:明确需求和使用场景
先把这些问题写清楚:
-
是做 聊天问答 、补全 、多轮对话 ,还是 工具调用(函数调用) 、agent 流程?
-
延迟要求:用户能接受多少秒的响应?是否需要 流式输出(像 ChatGPT 那样一字一字出)?
-
并发量预估:QPS、峰值、日调用量?
-
模型来源:
-
直接调用云端大模型 API(如 OpenAI、DeepSeek、Moonshot、本地/私有化等)
-
还是自己部署本地/私有化大模型(vLLM、Triton 等推理服务)?
-
-
数据要求:是否涉及敏感数据 / 内网数据,是否要做 脱敏和日志控制?
这些会直接影响你后面:
-
架构复杂度
-
需要哪些中间件(缓存、消息队列、向量库等)
-
安全/合规要怎么做
第 2 步:技术选型与基本架构
后端框架(推荐):
-
Spring Boot:生态最成熟,文档多,适合大部分企业项目
-
对性能要求特别高或需要响应式的,可以考虑 Spring WebFlux 或 Vert.x,但一般 Spring MVC 足够
典型架构:
-
gateway/api:对外暴露 HTTP/HTTPS 接口 -
llm-service:封装调用大模型的逻辑 -
vector-service(可选):封装向量和检索,用于 RAG -
auth-service:鉴权、限流、配额 -
logging & monitoring:统一日志、打点、链路追踪
可以先从 单体 Spring Boot 应用 开始,把这些作为不同 package / module 抽象,将来再拆微服务。
第 3 步:项目初始化
以 Spring Boot 为例:
-
用 Spring Initializr 建一个项目:
- 依赖:
Spring Web、Spring Validation、Spring Boot Actuator、Lombok、Spring Security(按需)等
- 依赖:
-
设计基本目录结构,例如:
com.example.llmbackend
├── api // controller 层
├── service // 业务逻辑 & 模型编排
├── client // 调用大模型/向量库/其他服务
├── config // 配置类
├── dto // 请求/响应对象
├── domain // 领域对象(如果有)
└── infra // 基础设施,例如缓存、持久化等
第 4 步:对接大模型(核心)
这里分两种情况:调用外部模型 API 和 调用本地/私有化模型服务。
4.1 调用外部模型 API
一般就是 HTTP + JSON,流程:
-
定义一个 client(可以用
RestTemplate或WebClient,或 OkHttp/Feign) -
封装通用请求参数(模型名、温度、max_tokens、system prompt 等)
-
做好:
-
超时控制
-
重试策略
-
错误码转换(外部错误 -> 内部统一错误码)
-
伪代码示例(同步调用):
@Service
public class LlmClient {
private final WebClient webClient;
public LlmClient(WebClient.Builder builder) {
this.webClient = builder
.baseUrl("https://api.your-llm.com/v1")
.build();
}
public String chat(String model, String prompt) {
LlmRequest request = new LlmRequest(model, prompt);
LlmResponse response = webClient.post()
.uri("/chat/completions")
.header("Authorization", "Bearer " + "YOUR_API_KEY")
.bodyValue(request)
.retrieve()
.bodyToMono(LlmResponse.class)
.block(); // 简单起见,用 block,同步模型调用
return response.getChoices().get(0).getMessage().getContent();
}
}
实际上还需要加上超时、重试、日志等配置,避免调用卡死线程。
4.2 调用本地 / 私有化推理服务
比如你自己用 Python/vLLM 部署了一套 HTTP 服务,这里 Java 还是当"HTTP 客户端",模式一样,只是:
-
要注意 内网通信安全(mTLS、内网地址隔离)
-
服务发现(k8s Service、Nacos、Consul 等)
-
可能需要 批量接口(一次请求多条 prompt,做批推理)
第 5 步:设计对外 API(REST / WebSocket)
一般会有几类接口:
-
对话接口(非流式)
@RestController
@RequestMapping("/api/chat")
public class ChatController {private final ChatService chatService; public ChatController(ChatService chatService) { this.chatService = chatService; } @PostMapping public ChatResponse chat(@Valid @RequestBody ChatRequest request) { return chatService.chat(request); }}
-
ChatRequest里通常有:-
sessionId(会话 ID) -
messages(历史消息) -
tools/functions(可选) -
temperature、maxTokens等
-
-
ChatResponse需要包含:-
回复内容
-
token 消耗信息
-
是否触发工具调用等状态
-
- 对话接口(流式,效果更友好)
-
HTTP chunked / SSE / WebSocket 三种常见方式
-
Spring Boot 可用:
SseEmitter或 WebFlux 的Flux<ServerSentEvent<...>>
注意点:
-
流式接口要配合前端使用,例如前端逐字填充聊天内容
-
要考虑 中途取消(客户端断开,服务端要停止模型推理)
第 6 步:会话管理 & 上下文
多轮对话必须解决"上下文"问题:
-
简单做法:前端每次带上全部历史消息(但消息长会很贵)
-
稍微专业一点:
-
只带最近 N 轮消息
-
做 摘要(summary),把远历史压缩成一段短描述
-
结合 RAG:从知识库取 Top-K 相关文档,放入 system prompt 或上下文中
-
服务端需要做的:
-
存储会话 & 消息(Redis/MySQL/Mongo 均可)
-
提供接口:创建会话 / 拉取历史 / 删除会话
-
设计一个"构造上下文"的策略类(ContextBuilder),在调用模型前组合 prompt
第 7 步:集成 RAG(如果要查知识库)
简单版本流程:
-
文档离线切分 + 向量化,存入向量库(如 Milvus、PgVector、Elasticsearch + dense vector 字段 等)
-
查询时:
-
根据用户问题做向量检索,拿到相似的文档片段
-
将这些片段拼接到 prompt 里面,告诉模型:"以下是相关资料,你只能基于这些回答"
-
Java 侧需要:
-
一个
VectorStoreClient来封装向量库操作(插入/查询) -
一个
RagService,负责:-
调用向量库取文档
-
组装 prompt
-
调用
LlmClient
-
第 8 步:性能优化 & 稳定性
这里是大模型后端最容易踩坑的部分。
8.1 并发与线程模型
-
模型调用一般是 IO 密集(网络/推理服务),要避免大量阻塞线程
-
Spring WebFlux +
WebClient(响应式) 可以更高效地利用线程池 -
对外 API 的 timeout 要 小于 模型调用和上游负载均衡的 timeout,避免"僵尸请求"
8.2 限流 & 熔断 & 重试
-
针对接口、IP、用户、API key 做限流(如基于 Redis + Lua,或用 Resilience4j / Sentinel)
-
模型服务不稳定时:
-
快速失败(熔断),避免拖垮整条链路
-
明确错误信息给调用方(例如:系统繁忙,请稍后重试)
-
8.3 连接池 & Keep-Alive
-
调用外部模型 API 的 HTTP 客户端要:
-
复用连接(keep-alive)
-
设置最大连接数 / 每主机连接数
-
设置合理读写超时
-
第 9 步:日志、监控、追踪
至少要做到:
-
请求日志(不记录敏感信息 / prompt 可脱敏)
-
调用大模型的耗时、token 消耗、成功率
-
针对错误场景的报警(例如:5xx 比例 > 某阈值)
可以引入:
-
Prometheus + Grafana 做指标
-
Zipkin / Jaeger + Spring Cloud Sleuth 做链路追踪
-
ELK/EFK 做日志检索
第 10 步:部署与环境
-
容器化:用 Docker 打包 Spring Boot 应用,部署到 k8s
-
配置管理:使用 config server / Nacos / k8s ConfigMap & Secret
-
区分环境:
-
dev:直接连沙盒模型/小模型,便于调试
-
staging:接近生产的模型/参数,用来做压力测试
-
prod:配好限流、监控、告警后,再对外开放
-
二、开发中需要特别注意的点(踩坑清单)
我按主题给你列一份"注意事项"清单:
1. 安全 & 权限
-
对外一定要有 鉴权(token、API key、OAuth2 等)
-
对内部调用模型时的 API key / 密钥,放在:
-
环境变量,或
-
k8s Secret,或
-
专门的密钥管理系统
-
-
对日志中的:
-
用户输入(prompt)
-
模型输出(尤其含隐私信息)
做脱敏或可控开关
-
2. 成本控制
-
统计每次调用的:
-
输入 token
-
输出 token
-
-
按用户/部门/应用聚合统计,用于:
-
限额
-
结算/报销
-
优化提示词和上下文长度
-
3. Prompt 设计 & 可配置化
-
system prompt / 模板尽量做成 可配置(数据库 / 配置中心),避免写死在代码里
-
给不同业务线不同模板(客服、代码助手、知识问答等)
4. 版本管理 & 回滚
-
模型版本切换要小心:
-
比如从
model-v1切到model-v2 -
最好支持灰度:某个比例/某些用户先试新模型
-
-
保留能力:快速切回旧模型(配置开关 + 配置中心)
5. 测试策略
-
单元测试:
-
对业务逻辑、上下文构建、RAG 逻辑做单测
-
对 LlmClient 可以 mock 掉真实模型调用
-
-
集成测试:
- 在测试环境调用真实/小号模型,看整体链路
-
回归测试:
- 抽取一批典型问题,做 A/B,对比新旧版本的"主观效果"(可以人工评审)
三、给你一个极简端到端示例结构(非完整代码)
请求 DTO:
@Data
public class ChatRequest {
@NotBlank
private String sessionId;
@NotBlank
private String userMessage;
private Double temperature;
private Integer maxTokens;
}
响应 DTO:
@Data
@AllArgsConstructor
public class ChatResponse {
private String reply;
private long promptTokens;
private long completionTokens;
}
Service:
@Service
public class ChatService {
private final LlmClient llmClient;
private final ConversationStore conversationStore; // 自己实现,可用 Redis/DB
public ChatService(LlmClient llmClient, ConversationStore conversationStore) {
this.llmClient = llmClient;
this.conversationStore = conversationStore;
}
public ChatResponse chat(ChatRequest request) {
// 1. 取历史记录,构造上下文
List<Message> history = conversationStore.getHistory(request.getSessionId());
// 2. 构造 prompt(这里只是简单拼接)
String prompt = buildPrompt(history, request.getUserMessage());
// 3. 调用大模型
LlmResult result = llmClient.chat("your-model-name", prompt, request.getTemperature(), request.getMaxTokens());
// 4. 存历史
conversationStore.append(request.getSessionId(), request.getUserMessage(), result.getReply());
// 5. 返回
return new ChatResponse(result.getReply(), result.getPromptTokens(), result.getCompletionTokens());
}
private String buildPrompt(List<Message> history, String userMessage) {
// 这里可以做更多控制:截断、摘要、RAG 等
StringBuilder sb = new StringBuilder();
for (Message msg : history) {
sb.append(msg.getRole()).append(": ").append(msg.getContent()).append("\n");
}
sb.append("user: ").append(userMessage);
return sb.toString();
}
}
四、如果你想要一个"实战版"的开发路径
我帮你排一个可以直接照着走的 checklist:
-
用 Spring Boot 初始化一个项目,跑通一个
/health接口 -
写一个
LlmClient,先随便用 mock 返回固定回答(不连真实模型) -
定义
ChatRequest/ChatResponse,写ChatController+ChatService,本地调通一问一答 -
把
LlmClient替换成真实模型 API 调用,加入:-
超时
-
错误处理
-
简单日志
-
-
加上会话管理(按 sessionId 存历史),实现多轮对话
-
实现一个简单的限流和鉴权(比如每个 API key 每分钟 N 次)
-
部署到测试环境(Docker + k8s / 云主机)
-
加监控(Actuator + Prometheus)和日志搜索
-
压测(JMeter/Locust),根据瓶颈优化线程池、超时、连接池
-
最后再逐步加:RAG、流式输出、工具调用、灰度模型等能力