前言
理解 Spring AI 以后,第二步就该看 ChatClient。
在 Java 后端里,ChatClient 是最容易入门的入口。它把模型对话封装成比较符合 Spring 风格的调用方式,让我们不用一开始就直接处理各个模型厂商的 HTTP 细节。
但我觉得学习 ChatClient 时不能只停在“怎么发一句话”。真正要落地,应该关心怎么封装、怎么管理 prompt、怎么处理结构化输出、怎么做超时和观测。
最小调用长什么样
最简单的调用大概是这样:
String answer = chatClient.prompt()
.user("解释一下 Java 虚拟线程适合什么场景")
.call()
.content();
这段代码很直观。用户输入进入模型,模型返回文本。
如果只是内部工具或学习 demo,这样已经够了。但业务系统不能让 Controller 到处直接写这段代码。因为一旦分散,后面就很难统一处理模型、日志、异常、成本和权限。
先封装场景服务
我会按业务场景封装,而不是按模型封装。
比如摘要场景:
public interface DocumentSummaryService {
SummaryResult summarize(String title, String content);
}
分类场景:
public interface TicketClassifyService {
TicketClassifyResult classify(String description);
}
这样业务层只知道自己要摘要或分类,不知道底层用哪个模型,也不关心 ChatClient 的细节。
模型切换、提示词调整、输出校验都放在实现里。
系统提示和用户输入要分开
很多初学写法会把所有内容拼成一个大字符串。
String prompt = "你是客服助手,帮我分类:" + text;
这样能跑,但不利于管理。
更好的方式是把 system 和 user 分开。system 描述角色、规则和输出要求,user 放用户输入。
String result = chatClient.prompt()
.system("你是工单分类助手,只能返回指定 JSON 结构。")
.user(description)
.call()
.content();
这样后续调整系统规则时,不会和用户输入混在一起。安全上也更清楚,因为用户输入不能轻易覆盖系统规则。
模板变量要显式
复杂 prompt 通常会有变量。
比如摘要模板需要标题、正文、摘要长度、输出语言。如果直接字符串拼接,很容易漏转义,也不利于测试。
我更喜欢把输入定义成明确对象:
public record SummaryCommand(
String title,
String content,
int maxWords) {
}
然后在服务层组装 prompt。这样参数来源清楚,也方便做校验。
比如 content 最大长度不能无限大,maxWords 要有范围。不要等输入进入模型以后才发现 token 爆了。
结构化输出要校验
现在做 AI 工程,结构化输出已经是基本功。
很多业务不需要一段自由文本,而是需要 JSON、枚举、字段值。
比如:
public record TicketClassifyResult(
String category,
String priority,
String reason) {
}
即使 Spring AI 能帮助把输出映射成对象,后端仍然要校验。
category 必须在系统支持的分类里,priority 只能是高、中、低,reason 不能太长。解析失败时要有降级逻辑,不能直接抛给用户一段模型原始输出。
流式输出适合交互,不适合所有场景
ChatClient 支持流式返回时,用户体验会好很多。聊天、长文生成、解释类功能都适合边生成边展示。
但不是所有场景都适合流式。
如果模型输出要进入业务判断,比如分类、审核、字段抽取,就更适合一次性拿到完整结果再解析校验。流式输出中间状态不完整,业务处理会更复杂。
所以我会按场景选:
1.用户阅读体验优先,用流式。 2.结构化业务处理,非流式。 3.后台任务,非流式更简单。 4.长文本生成,可以流式但要有取消机制。
超时和取消要设计
模型调用比普通接口更慢,也更不稳定。
用户实时请求要有明确超时。后台任务可以放宽,但也不能无限等待。
如果前端用户关闭页面,后端最好能取消不必要的生成。否则用户已经走了,模型还在继续消耗 token。
工程上至少要考虑:
1.连接超时。 2.读取超时。 3.总耗时限制。 4.用户取消。 5.失败重试。 6.降级提示。
AI 调用不是普通本地方法,它是外部依赖。外部依赖就必须有边界。
日志记录要克制
ChatClient 调用最容易想记录完整 prompt 和 response。
这对开发调试很方便,但生产环境要谨慎。用户输入可能包含隐私、合同、账号、内部资料。模型输出也可能包含敏感推断。
我建议默认记录元信息:
1.场景名。 2.模型名。 3.请求 ID。 4.输入 token。 5.输出 token。 6.耗时。 7.是否成功。 8.错误类型。
完整内容可以采样、脱敏,或者进入受控审计存储。不要直接全量打应用日志。
成本从第一版开始算
AI 功能上线后,成本问题来得很快。
如果没有按场景统计 token,用一段时间后很难知道钱花在哪里。
ChatClient 封装层应该记录每次调用的用量。按场景、模型、用户或租户统计都可以。这样才能回答:哪个功能最贵,哪个输入过长,哪里可以换小模型,哪里可以加缓存。
很多 AI 功能不是技术上不能做,而是成本上不能无限做。
小结
ChatClient 是 Spring AI 里最容易上手的入口,但真正落地要从“怎么调”走到“怎么封装”。
业务层不要直接散落模型调用。按场景封装服务,分开 system 和 user,显式管理模板变量,校验结构化输出,处理超时、取消、日志和成本。
这样 ChatClient 才不是一个漂亮的 demo API,而是能放进 Java 后端项目里的稳定基础能力。
喜欢的话,留下你的评论吧~