乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • Spring AI 学习路径
  • 第1章 Spring AI 概述与核心理念

    • 章节导读
    • Spring AI 概述与可移植 API
    • 核心模块与依赖关系
  • 第2章 快速入门与第一个AI应用

    • 章节导读
    • 环境准备与配置
    • 第一个 AI 对话应用
  • 第3章 聊天模型与ChatClient

    • 章节导读
    • ChatClient 详解
    • ChatModel 底层抽象
    • 多轮对话与 ChatMemory
  • 第4章 提示词管理与模板

    • 章节导读
    • Prompt 与 Message 体系
    • 提示词模板与动态构建
  • 第5章 输出解析与结构化响应

    • 章节导读
    • 输出解析器与 BeanOutputConverter
  • 第6章 嵌入模型与向量存储

    • 章节导读
    • EmbeddingModel 与文本向量化
    • ETL 数据注入流水线
    • 向量存储抽象与配置
  • 第7章 检索增强生成(RAG)

    • 章节导读
    • RAG 核心机制与流程
    • QuestionAnswerAdvisor 详解
    • RAG 实战案例
  • 第8章 函数调用(Function Calling)

    • 章节导读
    • Tool 注解与函数声明
    • 函数调用实战
  • 第9章 多模态

    • 章节导读
    • 图像输入与视觉模型
    • 图像生成
  • 第10章 Advisor拦截器链

    • 章节导读
    • Advisor 链与内置拦截器
  • 第11章 MCP 协议与跨语言工具集成

    • 章节导读
    • MCP 协议深入
  • 第12章 可观测性测试与最佳实践

    • 章节导读
    • 可观测性与指标监控
    • 测试策略与 Mock 实践
    • 最佳实践与面试考点汇总

李眉站在白板前:"AI 调用跟数据库不一样——每次调用都花钱。Token 用量、延迟、成功率,一个都不能少。Spring AI 已经内置了 Micrometer 集成,我们只需要配置。"

可观测性与指标监控

定义与作用

可观测性是 AI 应用上线后的"生命体征监测系统"。Spring AI 基于 Micrometer Observation API 自动为每次 AI 调用生成分布式追踪 Span 和指标,覆盖 Token 用量、调用延迟、错误率三个核心维度。

核心原理

图释:ChatClient 内部通过 Observation API 自动创建 Span → Micrometer Registry 收集指标 → 导出到 Prometheus + Grafana 实现可视化,或导出到 Zipkin/Jaeger 实现调用链追踪。

关键配置

spring:
  ai:
    observation:
      include-usage: true     # 记录 Token 用量
      include-completion: false # 生产环境关闭,避免泄露敏感内容
      include-prompt: false   # 生产环境关闭

自动生成的指标

指标名称类型说明
spring.ai.chat.client.requestsCounterAI 调用总次数
spring.ai.chat.client.operation.durationTimer每次调用耗时(含网络)
gen_ai.client.operation.durationTimer生成式 AI 操作耗时
gen_ai.usage.input_tokensDistributionSummary输入 Token 数量
gen_ai.usage.output_tokensDistributionSummary输出 Token 数量
gen_ai.usage.total_tokensDistributionSummary总 Token 数量

完整示例一:集成 Prometheus + Grafana

场景说明

李眉需要为飞翔科技智能客服搭建 AI 调用监控面板。

依赖引入

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
management:
  endpoints:
    web:
      exposure:
        include: prometheus, metrics, health
  metrics:
    export:
      prometheus:
        enabled: true

spring:
  ai:
    observation:
      include-usage: true

关键代码

@RestController
public class ObservedChatController {

    private final ChatClient chatClient;
    private final MeterRegistry meterRegistry;

    public ObservedChatController(
            ChatClient chatClient,
            MeterRegistry meterRegistry) {
        this.chatClient = chatClient;
        this.meterRegistry = meterRegistry;
    }

    @GetMapping("/observed/chat")
    public String chat(@RequestParam String message) {
        // ChatClient 内部自动生成 Span,无需手动编码
        return chatClient.prompt()
                .user(message)
                .call()
                .content();
    }

    @GetMapping("/observed/stats")
    public Map<String, Object> stats() {
        Map<String, Object> result = new HashMap<>();
        
        // 查询 Token 用量
        meterRegistry.find("gen_ai.usage.total_tokens")
            .summary().ifPresent(s -> {
                result.put("totalTokens", s.totalAmount());
                result.put("avgTokens", s.mean());
            });
        
        // 查询调用延迟
        meterRegistry.find("gen_ai.client.operation.duration")
            .timer().ifPresent(t -> {
                result.put("avgDurationMs", t.mean(TimeUnit.MILLISECONDS));
                result.put("totalCalls", t.count());
            });
        
        return result;
    }
}

Grafana 面板查询

# AI 调用 QPS
rate(spring_ai_chat_client_requests_total[1m])

# 平均 Token 消耗
gen_ai_usage_total_tokens_sum / gen_ai_usage_total_tokens_count

# P99 延迟(毫秒)
histogram_quantile(0.99, 
    rate(gen_ai_client_operation_duration_seconds_bucket[5m])) * 1000

完整示例二:自定义观测埋点

场景说明

大翔要求按"用户提问类别"分类统计 AI 调用量。

@Component
public class CategoryTaggingAdvisor implements RequestResponseAdvisor {

    private final MeterRegistry meterRegistry;

    public CategoryTaggingAdvisor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @Override
    public Prompt adviseRequest(Prompt prompt, 
            Map<String, Object> context) {
        String userContent = prompt.getInstructions().stream()
            .filter(m -> m.getMessageType() == MessageType.USER)
            .map(Message::getContent)
            .findFirst().orElse("");

        // 简单分类(实际可用 AI 做更精准的分类)
        String category = categorize(userContent);
        
        // 按类别计数
        Counter.builder("ai.chat.category")
            .tag("category", category)
            .register(meterRegistry)
            .increment();

        return prompt;
    }

    private String categorize(String text) {
        if (text.contains("课程") || text.contains("学分")) 
            return "course";
        if (text.contains("费用") || text.contains("学费")) 
            return "financial";
        return "general";
    }

    @Override
    public ChatResponse adviseResponse(ChatResponse response, 
            Map<String, Object> context) {
        return response;
    }

    @Override
    public String getName() { return "category-tagging"; }
}

运行结果

访问 /actuator/prometheus 输出:

ai_chat_category_total{category="course"} 1523.0
ai_chat_category_total{category="financial"} 421.0
ai_chat_category_total{category="general"} 3056.0

在 Grafana 中可配置饼图,直观展示各类别调用占比。

易错场景与面试考点

易错场景一:生产环境开启 include-prompt 导致敏感信息泄露

# ❌ 错误:生产环境记录完整 Prompt
spring:
  ai:
    observation:
      include-prompt: true   # Prompt 可能含用户隐私数据

问题分析

追踪系统中的 Span 标签对运维人员可见,开启 include-prompt 会将用户问题和检索上下文记录到 Zipkin/Jaeger,造成隐私泄露。

# ✅ 正确:生产环境只记录 Token 用量
spring:
  ai:
    observation:
      include-usage: true
      include-prompt: false
      include-completion: false

面试高频题

Q1:Spring AI 如何实现可观测性?

基于 Micrometer Observation API,自动为 ChatClient.call() 和 ChatModel.call() 生成 Span,命名规则为 spring.ai.<provider>.<action>(如 spring.ai.openai.chat)。Span 包含模型名称、Token 用量、调用状态等标签,可通过 Prometheus/Grafana 导出。

Q2:如何估算 AI API 调用成本?

通过 Micrometer 的 Token 用量指标:输入 Token × 输入单价 + 输出 Token × 输出单价。利用 DistributionSummary 统计每日总 Token 量,乘以对应模型的价格即可估算日成本。Grafana 面板可配置成本折线图。

本章小结

  • Spring AI 自动生成 AI 调用 Span 和指标,零侵入
  • include-usage 开启 Token 用量监控,include-prompt 生产环境必须关闭
  • 支持 Prometheus + Grafana 可视化,Zipkin/Jaeger 分布式追踪
  • 自定义 Advisor 可注入业务维度的观测埋点
上一页
章节导读
下一页
测试策略与 Mock 实践