上线后怎么排查 AI 调用?Spring AI Observability:token/延迟/成本埋点与全链路追踪

上线后怎么排查 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 成本是怎么盯的;源码见置顶评论。

相关推荐
文丑颜不良啊34 分钟前
LeetCode1921~1940题目解析
java·leetcode
测试界的小码农1 小时前
【让 AI 生成的测试用例变成 pytest API 自动化脚本】
人工智能·测试用例·pytest
Tt有了t1 小时前
设计模式速查
java·python·设计模式
Thomas.Sir1 小时前
第15课:SpringBoot连接池详解:HikariCP配置、调优、监控,4.x连接池新特性
java·spring boot
天天代码码天天1 小时前
我开源了一个家庭备份工具:把手机照片和视频,存回自己的电脑
人工智能
AI源码1 小时前
基于机器学习的古籍文字识别与断句校正系统
人工智能·机器学习
用户8339049606952 小时前
记账大师 AIUI购物记账与交互可靠性实践
人工智能
数智人生局2 小时前
配置驱动的通用 ETL 执行引擎:告别"每个需求写一个 Spark 作业"(改配置不发版)
人工智能
Neurocoda2 小时前
基于 NumPy 从零实现 Tiny-AlexNet 在 MNIST 数据集上的验证
人工智能·深度学习