上线后怎么排查 AI 调用?Spring AI Observability:token/延迟/成本埋点与全链路追踪
系列:Spring AI 深入浅出(第 19 篇)
版本:Spring AI 2.0.1 / Spring Boot 4.1.x / JDK 21
标签:#SpringAI #Observability #可观测性 #监控
前置阅读:第 6 篇《Advisor 责任链原理》
预计字数:6000 字|可复制源码 + 全链路追踪图 + 踩坑 FAQ
本文将解决:AI 调用出了问题是模型慢还是你代码慢?没埋点就是抓瞎。本文把 AI 调用也纳入可观测体系。
你将学到:
- 为什么 AI 调用要可观测(成本/延迟/质量)
- Micrometer 埋点
- 关键指标:token 数、延迟、成本
- 全链路追踪(traceId 串联)
- 审计留痕
一、版本标注
本文基于 Spring AI 2.0.1 / Boot 4.1 / JDK 21。Spring AI 内置 Micrometer 观测集成;token 用量从 ChatResponse 的 metadata(Usage)取。精确 API 以 2.0.1 官方文档为准。
二、为什么 AI 调用要可观测
普通微服务可观测(延迟、错误率、吞吐)大家都熟,但 AI 调用多了三类特殊指标:
| 维度 | 指标 | 用途 |
|---|---|---|
| 成本 | 每次调用 token 数、费用 | AI 调用烧钱,不看成本会失控 |
| 延迟 | 首 token 延迟、总延迟 | 排查"模型慢"还是"代码慢" |
| 质量 | 输出长度、异常率 | 结合评估(第 20 篇)做质量监控 |
一句话:AI 调用是"按量计费"的,不埋点等于闭眼花钱。
三、Micrometer 埋点
Spring AI 基于 Micrometer 的 Observation 集成,开启后自动记录 AI 调用的观测数据(span/metrics)。开启方式(以官方文档为准):
yaml
management:
endpoints:
web:
exposure:
include: health,metrics,prometheus
接入 Prometheus + Grafana 后,AI 调用的延迟、用量等指标就能进监控看板。
四、关键指标:token 数 / 延迟 / 成本
4.1 取 token 用量
每次调用的 token 数从响应的 metadata 里取:
java
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.metadata.Usage;
ChatResponse response = chatClient.prompt().user(q).call().chatResponse();
Usage usage = response.getMetadata().getUsage();
long promptTokens = usage.getPromptTokens(); // 输入 token
long completionTokens = usage.getCompletionTokens(); // 输出 token
long total = usage.getTotalTokens(); // 总 token
各厂商的
Usage字段实现略有差异,取数时做好兜底(取不到就记 0 并告警)。
4.2 成本换算
成本 = 输入 token × 输入单价 + 输出 token × 输出单价。单价因模型而异,建议做成配置(模型 → 单价表),随用量一起上报。
4.3 延迟拆解
- 首 token 延迟(Time To First Token):流式体验的关键
- 总延迟:完整生成耗时
延迟拆解能帮你判断"慢在排队/网络,还是慢在生成"。
五、全链路追踪
AI 调用要能和其他服务串起来,靠 traceId:
#mermaid-svg-AaM3q6Q8Z7R4KDYh{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .error-icon{fill:#552222;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .marker.cross{stroke:#333333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AaM3q6Q8Z7R4KDYh p{margin:0;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .cluster-label text{fill:#333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .cluster-label span{color:#333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .cluster-label span p{background-color:transparent;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .label text,#mermaid-svg-AaM3q6Q8Z7R4KDYh span{fill:#333;color:#333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .node rect,#mermaid-svg-AaM3q6Q8Z7R4KDYh .node circle,#mermaid-svg-AaM3q6Q8Z7R4KDYh .node ellipse,#mermaid-svg-AaM3q6Q8Z7R4KDYh .node polygon,#mermaid-svg-AaM3q6Q8Z7R4KDYh .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .rough-node .label text,#mermaid-svg-AaM3q6Q8Z7R4KDYh .node .label text,#mermaid-svg-AaM3q6Q8Z7R4KDYh .image-shape .label,#mermaid-svg-AaM3q6Q8Z7R4KDYh .icon-shape .label{text-anchor:middle;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .rough-node .label,#mermaid-svg-AaM3q6Q8Z7R4KDYh .node .label,#mermaid-svg-AaM3q6Q8Z7R4KDYh .image-shape .label,#mermaid-svg-AaM3q6Q8Z7R4KDYh .icon-shape .label{text-align:center;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .node.clickable{cursor:pointer;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .arrowheadPath{fill:#333333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AaM3q6Q8Z7R4KDYh .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AaM3q6Q8Z7R4KDYh .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AaM3q6Q8Z7R4KDYh .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .cluster text{fill:#333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .cluster span{color:#333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AaM3q6Q8Z7R4KDYh rect.text{fill:none;stroke-width:0;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .icon-shape,#mermaid-svg-AaM3q6Q8Z7R4KDYh .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .icon-shape p,#mermaid-svg-AaM3q6Q8Z7R4KDYh .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .icon-shape rect,#mermaid-svg-AaM3q6Q8Z7R4KDYh .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AaM3q6Q8Z7R4KDYh .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AaM3q6Q8Z7R4KDYh .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AaM3q6Q8Z7R4KDYh :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 网关
traceId=abc
Controller
traceId=abc
Advisor 链
traceId=abc
模型调用
traceId=abc
要点:
- Spring AI 的观测集成会让 AI 调用继承当前 trace 上下文,自动串联
- 排查时一个 traceId 拉出"用户请求 → 业务逻辑 → AI 调用"全链路
- 配合日志(含 traceId),AI 调用不再是"黑盒"
六、审计留痕
合规场景(金融、医疗、企业内审)需要留痕"谁在什么时候问了什么、AI 答了什么":
- 结合第 6 篇的审计日志 Advisor,把输入输出、用量、耗时记录到日志/数据库
- 日志里带 traceId、userId、conversationId,事后可完整回溯
- 敏感场景注意:留痕内容要做脱敏(呼应第 21 篇安全)
java
// 审计 Advisor 思路(详见第 6 篇):before 记输入,after 记输出 + 用量 + 耗时
七、原理图解:AI 调用全链路观测
#mermaid-svg-162ZFHJf7gVdXUKF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-162ZFHJf7gVdXUKF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-162ZFHJf7gVdXUKF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-162ZFHJf7gVdXUKF .error-icon{fill:#552222;}#mermaid-svg-162ZFHJf7gVdXUKF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-162ZFHJf7gVdXUKF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-162ZFHJf7gVdXUKF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-162ZFHJf7gVdXUKF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-162ZFHJf7gVdXUKF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-162ZFHJf7gVdXUKF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-162ZFHJf7gVdXUKF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-162ZFHJf7gVdXUKF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-162ZFHJf7gVdXUKF .marker.cross{stroke:#333333;}#mermaid-svg-162ZFHJf7gVdXUKF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-162ZFHJf7gVdXUKF p{margin:0;}#mermaid-svg-162ZFHJf7gVdXUKF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-162ZFHJf7gVdXUKF .cluster-label text{fill:#333;}#mermaid-svg-162ZFHJf7gVdXUKF .cluster-label span{color:#333;}#mermaid-svg-162ZFHJf7gVdXUKF .cluster-label span p{background-color:transparent;}#mermaid-svg-162ZFHJf7gVdXUKF .label text,#mermaid-svg-162ZFHJf7gVdXUKF span{fill:#333;color:#333;}#mermaid-svg-162ZFHJf7gVdXUKF .node rect,#mermaid-svg-162ZFHJf7gVdXUKF .node circle,#mermaid-svg-162ZFHJf7gVdXUKF .node ellipse,#mermaid-svg-162ZFHJf7gVdXUKF .node polygon,#mermaid-svg-162ZFHJf7gVdXUKF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-162ZFHJf7gVdXUKF .rough-node .label text,#mermaid-svg-162ZFHJf7gVdXUKF .node .label text,#mermaid-svg-162ZFHJf7gVdXUKF .image-shape .label,#mermaid-svg-162ZFHJf7gVdXUKF .icon-shape .label{text-anchor:middle;}#mermaid-svg-162ZFHJf7gVdXUKF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-162ZFHJf7gVdXUKF .rough-node .label,#mermaid-svg-162ZFHJf7gVdXUKF .node .label,#mermaid-svg-162ZFHJf7gVdXUKF .image-shape .label,#mermaid-svg-162ZFHJf7gVdXUKF .icon-shape .label{text-align:center;}#mermaid-svg-162ZFHJf7gVdXUKF .node.clickable{cursor:pointer;}#mermaid-svg-162ZFHJf7gVdXUKF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-162ZFHJf7gVdXUKF .arrowheadPath{fill:#333333;}#mermaid-svg-162ZFHJf7gVdXUKF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-162ZFHJf7gVdXUKF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-162ZFHJf7gVdXUKF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-162ZFHJf7gVdXUKF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-162ZFHJf7gVdXUKF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-162ZFHJf7gVdXUKF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-162ZFHJf7gVdXUKF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-162ZFHJf7gVdXUKF .cluster text{fill:#333;}#mermaid-svg-162ZFHJf7gVdXUKF .cluster span{color:#333;}#mermaid-svg-162ZFHJf7gVdXUKF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-162ZFHJf7gVdXUKF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-162ZFHJf7gVdXUKF rect.text{fill:none;stroke-width:0;}#mermaid-svg-162ZFHJf7gVdXUKF .icon-shape,#mermaid-svg-162ZFHJf7gVdXUKF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-162ZFHJf7gVdXUKF .icon-shape p,#mermaid-svg-162ZFHJf7gVdXUKF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-162ZFHJf7gVdXUKF .icon-shape rect,#mermaid-svg-162ZFHJf7gVdXUKF .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-162ZFHJf7gVdXUKF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-162ZFHJf7gVdXUKF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-162ZFHJf7gVdXUKF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户请求 traceId
Controller
Advisor 链
模型调用
观测数据
token/延迟/成本
Prometheus/Grafana
审计日志
输入输出/用量
八、踩坑 FAQ
坑 1:观测未开启,看不到指标
现象 :Prometheus 里没有 AI 相关指标。
原因 :观测集成没开启,或没暴露 metrics 端点。
解决 :按官方文档开启 observation 集成,暴露 metrics/prometheus 端点。
坑 2:traceId 断链
现象 :AI 调用那段没有 traceId,链路断了。
原因 :异步/线程切换时 trace 上下文没传递。
解决:确保 trace 上下文正确传播(用框架的观测工具,别手动丢上下文)。
坑 3:token 数取不到
现象 :usage.getTotalTokens() 返回空/报错。
原因 :不同厂商 Usage 实现字段不一,或版本 API 差异。
解决 :做兜底(取不到记 0 并告警),核对 2.0.1 的 Usage API。
坑 4:成本换算口径不一致
现象 :不同模型的成本算出来对不上。
原因 :单价表没维护,或 token 口径(是否含 system)不一致。
解决:统一成本换算公式,单价做成配置并定期更新。
坑 5:审计日志记录了敏感信息
现象 :审计日志里有用户隐私,触发合规问题。
原因 :留痕时没脱敏。
解决:审计 Advisor 里先脱敏再记录(结合第 6 篇 PII 脱敏 Advisor)。
九、小结与合集导航
本篇把 AI 调用纳入可观测:
- Micrometer 埋点 + Prometheus/Grafana
- token 用量、成本换算、延迟拆解
- traceId 全链路追踪
- 审计留痕(脱敏后记录)
下一篇:《AI 评估与防幻觉》------质量怎么量化,上线前给输出打分。
十、互动钩子
投票:你用什么监控栈?
- Prometheus + Grafana
- 云厂商监控
- 还没上监控
评论区聊聊你的 AI 成本是怎么盯的;源码见置顶评论。