SpringAI(GA): 观测篇快速上手+源码解读

21 阅读22分钟

原文链接:SpringAI(GA): 观测篇快速上手+源码解读

教程说明

说明:本教程将采用2025年5月20日正式的GA版,给出如下内容

  1. 核心功能模块的快速上手教程
  2. 核心功能模块的源码级解读
  3. Spring ai alibaba增强的快速上手教程 + 源码级解读

版本:JDK21 + SpringBoot3.4.5 + SpringAI 1.0.0 + SpringAI Alibaba 1.0.0.2

将陆续完成如下章节教程。本章是第九章(观测篇)下的快速上手 + 源码解读

代码开源如下:github.com/GTyingzi/sp…

微信推文往届解读可参考:

第一章内容

SpringAI(GA)的chat:快速上手+自动注入源码解读

SpringAI(GA):ChatClient调用链路解读

第二章内容

SpringAI的Advisor:快速上手+源码解读

SpringAI(GA):Sqlite、Mysql、Redis消息存储快速上手

第三章内容

SpringAI(GA):Tool工具整合—快速上手

SpringAI(GA):Tool源码+工具触发链路解读

第四章内容

SpringAI(GA):结构化输出的快速上手+源码解读

第五章内容

SpringAI(GA):内存、Redis、ES的向量数据库存储—快速上手

SpringAI(GA):向量数据库理论源码解读+Redis、Es接入源码

第六章内容

SpringAI(GA):RAG快速上手+模块化解读

SpringAI(GA):RAG下的ETL快速上手

SpringAI(GA):RAG下的ETL源码解读

第七章内容

SpringAI(GA):Nacos2下的分布式MCP

SpringAI(GA):Nacos3下的分布式MCP

SpringAI(GA):MCP源码解读

SpringAI(GA): SpringAI下的MCP源码解读

第八章内容

SpringAI(GA): 多模型评估篇

整理不易,获取更好的观赏体验,可付费获取飞书云文档Spring AI最新教程权限,目前59.9,随着内容不断完善,会逐步涨价。

注:M6版快速上手教程+源码解读飞书云文档已免费提供

为鼓励大家积极参与为Spring Ai Alibaba开源社区github.com/alibaba/spr…

观测篇快速上手

[!TIP] 为其核心组件提供指标和跟踪功能,包括:ChtClient、Advisor、ChatModel、EmbeddingModel、Tool、VectorStore 等

以下实现了自定义的 ChtClient、ChatModel、Tool、EmbeddingModel 的观测处理器

实战代码可见:github.com/GTyingzi/sp… 下的 Observability 模块

配合成熟框架进行指标监控:《指标集成》

观测篇源码可见:《观测源码篇》

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-autoconfigure-model-openai</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-autoconfigure-model-tool</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-vector-store</artifactId>
    </dependency>

</dependencies>

application.yml

server:
  port: 8080

spring:
  application:
    name: observability

  ai:
    openai:
      api-key: ${DASHSCOPEAPIKEY}
      base-url: https://dashscope.aliyuncs.com/compatible-mode
      chat:
        options:
          model: qwen-max
      embedding:
        options:
          model: text-embedding-v1

config

自定义提供 ObservationRegistry 的 Bean,加载自定义的 ChatClient、ChatModel、Tool、EmbeddingModel 的观测处理器

package com.spring.ai.tutorial.observability.config;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.observation.ObservationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.ChatClientRequest;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.observation.ChatClientObservationContext;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.observation.ChatModelObservationContext;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext;
import org.springframework.ai.observation.AiOperationMetadata;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.observation.ToolCallingObservationContext;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

@Configuration
public class ObservationConfiguration {

    private static final Logger logger = LoggerFactory.getLogger(ObservationConfiguration.class);

    @Bean
    @ConditionalOnMissingBean(name = "observationRegistry")
    public ObservationRegistry observationRegistry(
            ObjectProvider<ObservationHandler<?>> observationHandlerObjectProvider) {
        ObservationRegistry observationRegistry = ObservationRegistry.create();
        ObservationRegistry.ObservationConfig observationConfig = observationRegistry.observationConfig();
        observationHandlerObjectProvider.orderedStream().forEach(handler -> {
            Type[] genericInterfaces = handler.getClass().getGenericInterfaces();
            for (Type type : genericInterfaces) {
                if (type instanceof ParameterizedType parameterizedType
                        && parameterizedType.getRawType() instanceof Class<?> clazz
                        && ObservationHandler.class.isAssignableFrom(clazz)) {

                    Type actualTypeArgument = parameterizedType.getActualTypeArguments()[0];
                    logger.info("load observation handler, supports context type: {}", actualTypeArgument);
                }
            }

            // 将handler添加到observationRegistry中
            observationConfig.observationHandler(handler);
        });
        return observationRegistry;
    }

    /**
     * 监听chat client调用
     */
    @Bean
    ObservationHandler<ChatClientObservationContext> chatClientObservationContextObservationHandler() {
        logger.info("ChatClientObservation start");
        return new ObservationHandler<>() {

            @Override
            public boolean supportsContext(Observation.Context context) {
                return context instanceof ChatClientObservationContext;
            }

            @Override
            public void onStart(ChatClientObservationContext context) {
                ChatClientRequest request = context.getRequest();
                List<? extends Advisor> advisors = context.getAdvisors();
                boolean stream = context.isStream();
                logger.info("💬ChatClientObservation start: ChatClientRequest : {}, Advisors : {}, stream : {}",
                        request, advisors, stream);
            }

            @Override
            public void onStop(ChatClientObservationContext context) {
                ObservationHandler.super.onStop(context);
            }
        };
    }

    /**
     * 监听chat model调用
     */
    @Bean
    ObservationHandler<ChatModelObservationContext> chatModelObservationContextObservationHandler() {
        logger.info("ChatModelObservation start");
        return new ObservationHandler<>() {

            @Override
            public boolean supportsContext(Observation.Context context) {
                return context instanceof ChatModelObservationContext;
            }

            @Override
            public void onStart(ChatModelObservationContext context) {
                AiOperationMetadata operationMetadata = context.getOperationMetadata();
                Prompt request = context.getRequest();
                logger.info("🤖ChatModelObservation start: AiOperationMetadata : {}",
                        operationMetadata);
                logger.info("🤖ChatModelObservation start: Prompt : {}",
                        request);
            }

            @Override
            public void onStop(ChatModelObservationContext context) {
                ChatResponse response = context.getResponse();
                logger.info("🤖ChatModelObservation start: ChatResponse : {}",
                        response);
            }
        };
    }

    /**
     * 监听工具调用
     */
    @Bean
    public ObservationHandler<ToolCallingObservationContext> toolCallingObservationContextObservationHandler() {
        logger.info("ToolCallingObservation start");
        return new ObservationHandler<>() {
            @Override
            public boolean supportsContext(Observation.Context context) {
                return context instanceof ToolCallingObservationContext;
            }

            @Override
            public void onStart(ToolCallingObservationContext context) {
                ToolDefinition toolDefinition = context.getToolDefinition();
                logger.info("🔨ToolCalling start: {} - {}", toolDefinition.name(), context.getToolCallArguments());
            }

            @Override
            public void onStop(ToolCallingObservationContext context) {
                ToolDefinition toolDefinition = context.getToolDefinition();
                logger.info("✅ToolCalling done: {} - {}", toolDefinition.name(), context.getToolCallResult());
            }
        };
    }

    /**
     * 监听embedding model调用
     */
    @Bean
    public ObservationHandler<EmbeddingModelObservationContext> embeddingModelObservationContextObservationHandler() {
        logger.info("EmbeddingModelObservation start");
        return new ObservationHandler<>() {
            @Override
            public boolean supportsContext(Observation.Context context) {
                return context instanceof EmbeddingModelObservationContext;
            }

            @Override
            public void onStart(EmbeddingModelObservationContext context) {
                logger.info("📚EmbeddingModelObservation start: {} - {}", context.getOperationMetadata().operationType(),
                        context.getOperationMetadata().provider());
            }
        };
    }


}

controller

ChatController
package com.spring.ai.tutorial.observability.controller;

import com.spring.ai.tutorial.observability.tools.TimeTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/chat")
public class ChatController {

    private final ChatClient chatClient;

    public ChatController(ChatClient.Builder builder) {
        this.chatClient = builder
                .build();
    }

    @GetMapping("/call")
    public String call(@RequestParam(value = "query", defaultValue = "你好,很高兴认识你,能简单介绍一下自己吗?")String query) {
        return chatClient.prompt(query).call().content();
    }

    /**
     * 调用工具版 - method
     */
    @GetMapping("/call/tool-method")
    public String callToolMethod(@RequestParam(value = "query", defaultValue = "请告诉我现在北京时间几点了") String query) {
        return chatClient.prompt(query).tools(new TimeTools()).call().content();
    }
}
VectorSimpleController
package com.spring.ai.tutorial.observability.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/vector/simple")
public class VectorSimpleController {
    private static final Logger logger = LoggerFactory.getLogger(VectorSimpleController.class);

    private final SimpleVectorStore simpleVectorStore;

    public VectorSimpleController(EmbeddingModel embeddingModel) {
        this.simpleVectorStore = SimpleVectorStore
                .builder(embeddingModel).build();
    }

    @GetMapping("/add")
    public void add() {
        logger.info("start add data");

        HashMap<String, Object> map = new HashMap<>();
        map.put("year", 2025);
        map.put("name", "yingzi");
        List<Document> documents = List.of(
                new Document("The World is Big and Salvation Lurks Around the Corner"),
                new Document("You walk forward facing the past and you turn back toward the future.", Map.of("year", 2024)),
                new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", map),
                new Document("1", "test content", map));
        simpleVectorStore.add(documents);
    }

    @GetMapping("/search")
    public List<Document> search() {
        logger.info("start search data");
        return simpleVectorStore.similaritySearch(SearchRequest
                .builder()
                .query("Spring")
                .topK(2)
                .build());
    }

}
效果

项目初始化阶段加载对应的观测处理器

简单调用/chat/call 接口,发现触发了 ChatClient、ChatModel 两个观测器

再调用下/chat/call/tool-method 接口触发工具,可见观测到了工具的入参、工具返回结果

再观测下嵌入模型

观测源码篇

[!TIP] 观测的实现机制:通过实现 ObservationHandler,提供对应的观测,再将ObservationHandler注入 ObservationRegistry 中,就能实现了监听

ObservationConvention

package io.micrometer.observation;

import io.micrometer.common.KeyValues;
import io.micrometer.common.lang.Nullable;

public interface ObservationConvention<T extends Observation.Context> extends KeyValuesConvention {
    ObservationConvention<Observation.Context> EMPTY = (context) -> false;

    default KeyValues getLowCardinalityKeyValues(T context) {
        return KeyValues.empty();
    }

    default KeyValues getHighCardinalityKeyValues(T context) {
        return KeyValues.empty();
    }

    boolean supportsContext(Observation.Context var1);

    @Nullable
    default String getName() {
        return null;
    }

    @Nullable
    default String getContextualName(T context) {
        return null;
    }
}

ChatModel 下的观测

pom.xml 文件
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-autoconfigure-model-chat-observation</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-model</artifactId>
</dependency>
ChatObservationProperties

聊天模型观测功能的配置属性类

  • boolean logCompletion:记录聊天模型的完成内容
  • boolean logPrompt:记录聊天模型的提示内容
  • boolean includeErrorLogging:记录聊天模型交互中的错误信息
package org.springframework.ai.model.chat.observation.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("spring.ai.chat.observations")
public class ChatObservationProperties {
    public static final String CONFIGPREFIX = "spring.ai.chat.observations";
    private boolean logCompletion = false;
    private boolean logPrompt = false;
    private boolean includeErrorLogging = false;

    public boolean isLogCompletion() {
        return this.logCompletion;
    }

    public void setLogCompletion(boolean logCompletion) {
        this.logCompletion = logCompletion;
    }

    public boolean isLogPrompt() {
        return this.logPrompt;
    }

    public void setLogPrompt(boolean logPrompt) {
        this.logPrompt = logPrompt;
    }

    public boolean isIncludeErrorLogging() {
        return this.includeErrorLogging;
    }

    public void setIncludeErrorLogging(boolean includeErrorLogging) {
        this.includeErrorLogging = includeErrorLogging;
    }
}
ChatObservationAutoConfiguration

自动装配和聊天模型的观测处理器,如日志、错误处理、指标等


方法名称
描述

chatModelMeterObservationHandler
对外提供ChatModelMeterObservationHandler的Bean,用于收集聊天模型的指标数据,用于监控和分析
TracerPresentObservationConfiguration(Tracer类存在)

chatModelPromptContentObservationHandler
条件:配置 log-prompt=true 或 log-completion=true
提供的Bean:TracingAwareLoggingObservationHandler
作用:记录聊天模型的提示内容或完成内容,并与追踪系统集成
errorLoggingObservationHandler
条件:配置 include-error-logging=true
提供的Bean:ErrorLoggingObservationHandler
作用:记录聊天模型交互中的错误信息
TracerNotPresentObservationConfiguration(Tracer类不存在)
chatModelPromptContentObservationHandler
条件:配置 log-prompt=true
提供的Bean:ChatModelPromptContentObservationHandler
作用:记录聊天模型的提示内容
chatModelCompletionObservationHandler
条件:配置 log-completion=true
提供的Bean:ChatModelCompletionObservationHandler
作用:记录聊天模型的完成内容
package org.springframework.ai.model.chat.observation.autoconfigure;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.tracing.Tracer;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.client.advisor.observation.AdvisorObservationContext;
import org.springframework.ai.chat.client.observation.ChatClientObservationContext;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.observation.ChatModelCompletionObservationHandler;
import org.springframework.ai.chat.observation.ChatModelMeterObservationHandler;
import org.springframework.ai.chat.observation.ChatModelObservationContext;
import org.springframework.ai.chat.observation.ChatModelPromptContentObservationHandler;
import org.springframework.ai.embedding.observation.EmbeddingModelObservationContext;
import org.springframework.ai.image.observation.ImageModelObservationContext;
import org.springframework.ai.model.observation.ErrorLoggingObservationHandler;
import org.springframework.ai.observation.TracingAwareLoggingObservationHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@AutoConfiguration(
    afterName = {"org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration"}
)
@ConditionalOnClass({ChatModel.class})
@EnableConfigurationProperties({ChatObservationProperties.class})
public class ChatObservationAutoConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(ChatObservationAutoConfiguration.class);

    private static void logPromptContentWarning() {
        logger.warn("You have enabled logging out the prompt content with the risk of exposing sensitive or private information. Please, be careful!");
    }

    private static void logCompletionWarning() {
        logger.warn("You have enabled logging out the completion content with the risk of exposing sensitive or private information. Please, be careful!");
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean({MeterRegistry.class})
    ChatModelMeterObservationHandler chatModelMeterObservationHandler(ObjectProvider<MeterRegistry> meterRegistry) {
        return new ChatModelMeterObservationHandler((MeterRegistry)meterRegistry.getObject());
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({Tracer.class})
    @ConditionalOnBean({Tracer.class})
    static class TracerPresentObservationConfiguration {
        @Bean
        @ConditionalOnMissingBean(
            value = {ChatModelPromptContentObservationHandler.class},
            name = {"chatModelPromptContentObservationHandler"}
        )
        @ConditionalOnProperty(
            prefix = "spring.ai.chat.observations",
            name = {"log-prompt"},
            havingValue = "true"
        )
        TracingAwareLoggingObservationHandler<ChatModelObservationContext> chatModelPromptContentObservationHandler(Tracer tracer) {
            ChatObservationAutoConfiguration.logPromptContentWarning();
            return new TracingAwareLoggingObservationHandler(new ChatModelPromptContentObservationHandler(), tracer);
        }

        @Bean
        @ConditionalOnMissingBean(
            value = {ChatModelCompletionObservationHandler.class},
            name = {"chatModelCompletionObservationHandler"}
        )
        @ConditionalOnProperty(
            prefix = "spring.ai.chat.observations",
            name = {"log-completion"},
            havingValue = "true"
        )
        TracingAwareLoggingObservationHandler<ChatModelObservationContext> chatModelCompletionObservationHandler(Tracer tracer) {
            ChatObservationAutoConfiguration.logCompletionWarning();
            return new TracingAwareLoggingObservationHandler(new ChatModelCompletionObservationHandler(), tracer);
        }

        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.ai.chat.observations",
            name = {"include-error-logging"},
            havingValue = "true"
        )
        ErrorLoggingObservationHandler errorLoggingObservationHandler(Tracer tracer) {
            return new ErrorLoggingObservationHandler(tracer, List.of(EmbeddingModelObservationContext.class, ImageModelObservationContext.class, ChatModelObservationContext.class, ChatClientObservationContext.class, AdvisorObservationContext.class));
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingClass({"io.micrometer.tracing.Tracer"})
    static class TracerNotPresentObservationConfiguration {
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.ai.chat.observations",
            name = {"log-prompt"},
            havingValue = "true"
        )
        ChatModelPromptContentObservationHandler chatModelPromptContentObservationHandler() {
            ChatObservationAutoConfiguration.logPromptContentWarning();
            return new ChatModelPromptContentObservationHandler();
        }

        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.ai.chat.observations",
            name = {"log-completion"},
            havingValue = "true"
        )
        ChatModelCompletionObservationHandler chatModelCompletionObservationHandler() {
            ChatObservationAutoConfiguration.logCompletionWarning();
            return new ChatModelCompletionObservationHandler();
        }
    }
}
ChatModelPromptContentObservationHandler

ChatModel 的模型请求 Prompt 的观测处理器

package org.springframework.ai.chat.observation;

import java.util.List;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.ai.content.Content;
import org.springframework.ai.observation.ObservabilityHelper;
import org.springframework.util.CollectionUtils;

public class ChatModelPromptContentObservationHandler implements ObservationHandler<ChatModelObservationContext> {

    private static final Logger logger = LoggerFactory.getLogger(ChatModelPromptContentObservationHandler.class);

    @Override
    public void onStop(ChatModelObservationContext context) {
       logger.info("Chat Model Prompt Content:\n{}", ObservabilityHelper.concatenateStrings(prompt(context)));
    }

    private List<String> prompt(ChatModelObservationContext context) {
       if (CollectionUtils.isEmpty(context.getRequest().getInstructions())) {
          return List.of();
       }

       return context.getRequest().getInstructions().stream().map(Content::getText).toList();
    }

    @Override
    public boolean supportsContext(Observation.Context context) {
       return context instanceof ChatModelObservationContext;
    }

}
ChatModelCompletionObservationHandler

ChatModel 的模型响应完成的观测处理器

package org.springframework.ai.chat.observation;

import java.util.List;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.springframework.ai.observation.ObservabilityHelper;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

public class ChatModelCompletionObservationHandler implements ObservationHandler<ChatModelObservationContext> {

    private static final Logger logger = LoggerFactory.getLogger(ChatModelCompletionObservationHandler.class);

    @Override
    public void onStop(ChatModelObservationContext context) {
       logger.info("Chat Model Completion:\n{}", ObservabilityHelper.concatenateStrings(completion(context)));
    }

    private List<String> completion(ChatModelObservationContext context) {
       if (context.getResponse() == null || context.getResponse().getResults() == null
             || CollectionUtils.isEmpty(context.getResponse().getResults())) {
          return List.of();
       }

       if (!StringUtils.hasText(context.getResponse().getResult().getOutput().getText())) {
          return List.of();
       }

       return context.getResponse()
          .getResults()
          .stream()
          .filter(generation -> generation.getOutput() != null
                && StringUtils.hasText(generation.getOutput().getText()))
          .map(generation -> generation.getOutput().getText())
          .toList();
    }

    @Override
    public boolean supportsContext(Observation.Context context) {
       return context instanceof ChatModelObservationContext;
    }

}
ModelObservationContext

封装 AI 模型交互过程的类

  • REQ request:泛型,AI 模型的请求对象
  • RES response:泛型,AI 模型的返回对象
  • AiOperationMetadata operationMetadata:操作的元数据,包括操作类型和提供者信息
package org.springframework.ai.model.observation;

import io.micrometer.observation.Observation;

import org.springframework.ai.observation.AiOperationMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;


public class ModelObservationContext<REQ, RES> extends Observation.Context {

    private final REQ request;

    private final AiOperationMetadata operationMetadata;

    @Nullable
    private RES response;

    public ModelObservationContext(REQ request, AiOperationMetadata operationMetadata) {
       Assert.notNull(request, "request cannot be null");
       Assert.notNull(operationMetadata, "operationMetadata cannot be null");
       this.request = request;
       this.operationMetadata = operationMetadata;
    }

    public REQ getRequest() {
       return this.request;
    }

    public AiOperationMetadata getOperationMetadata() {
       return this.operationMetadata;
    }

    @Nullable
    public RES getResponse() {
       return this.response;
    }

    public void setResponse(RES response) {
       Assert.notNull(response, "response cannot be null");
       this.response = response;
    }

}
ChatModelObservationContext

会话模型观测功能的上下文类

package org.springframework.ai.chat.observation;

import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.observation.ModelObservationContext;
import org.springframework.ai.observation.AiOperationMetadata;
import org.springframework.ai.observation.conventions.AiOperationType;

public class ChatModelObservationContext extends ModelObservationContext<Prompt, ChatResponse> {

    ChatModelObservationContext(Prompt prompt, String provider) {
       super(prompt,
             AiOperationMetadata.builder().operationType(AiOperationType.CHAT.value()).provider(provider).build());
    }

    public static Builder builder() {
       return new Builder();
    }

    public static final class Builder {

       private Prompt prompt;

       private String provider;

       private Builder() {
       }

       public Builder prompt(Prompt prompt) {
          this.prompt = prompt;
          return this;
       }

       public Builder provider(String provider) {
          this.provider = provider;
          return this;
       }

       public ChatModelObservationContext build() {
          return new ChatModelObservationContext(this.prompt, this.provider);
       }

    }

}
ModelUsageMetricsGenerator
package org.springframework.ai.model.observation;

import java.util.ArrayList;
import java.util.List;

import io.micrometer.common.KeyValue;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.observation.Observation;

import org.springframework.ai.chat.metadata.Usage;
import org.springframework.ai.observation.conventions.AiObservationMetricAttributes;
import org.springframework.ai.observation.conventions.AiObservationMetricNames;
import org.springframework.ai.observation.conventions.AiTokenType;

public final class ModelUsageMetricsGenerator {

    private static final String DESCRIPTION = "Measures number of input and output tokens used";

    private ModelUsageMetricsGenerator() {
    }

    public static void generate(Usage usage, Observation.Context context, MeterRegistry meterRegistry) {

       if (usage.getPromptTokens() != null) {
          Counter.builder(AiObservationMetricNames.TOKENUSAGE.value())
             .tag(AiObservationMetricAttributes.TOKENTYPE.value(), AiTokenType.INPUT.value())
             .description(DESCRIPTION)
             .tags(createTags(context))
             .register(meterRegistry)
             .increment(usage.getPromptTokens());
       }

       if (usage.getCompletionTokens() != null) {
          Counter.builder(AiObservationMetricNames.TOKENUSAGE.value())
             .tag(AiObservationMetricAttributes.TOKENTYPE.value(), AiTokenType.OUTPUT.value())
             .description(DESCRIPTION)
             .tags(createTags(context))
             .register(meterRegistry)
             .increment(usage.getCompletionTokens());
       }

       if (usage.getTotalTokens() != null) {
          Counter.builder(AiObservationMetricNames.TOKENUSAGE.value())
             .tag(AiObservationMetricAttributes.TOKENTYPE.value(), AiTokenType.TOTAL.value())
             .description(DESCRIPTION)
             .tags(createTags(context))
             .register(meterRegistry)
             .increment(usage.getTotalTokens());
       }

    }

    private static List<Tag> createTags(Observation.Context context) {
       List<Tag> tags = new ArrayList<>();
       for (KeyValue keyValue : context.getLowCardinalityKeyValues()) {
          tags.add(Tag.of(keyValue.getKey(), keyValue.getValue()));
       }
       return tags;
    }

}
ChatModelObservationConvention

ChatModel 角度下的观测接口类

package org.springframework.ai.chat.observation;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;


public interface ChatModelObservationConvention extends ObservationConvention<ChatModelObservationContext> {

    @Override
    default boolean supportsContext(Observation.Context context) {
       return context instanceof ChatModelObservationContext;
    }

}
DefaultChatModelObservationConvention

默认定义 ChatModel 观测约定的实现类,主要用于生成 Micrometer 观察功能所需的上下文信息和关键值

  • String DEFAULTNAME:默认观测名称为"genai.client.operation"
  • KeyValue REQUESTMODELNONE:请求为空的默认值
  • KeyValue RESPONSEMODELNONE:响应模型为空的默认值

方法名称
描述

getContextualName
操作元数据和请求选项生成上下文名称

getLowCardinalityKeyValues

生成低粒度的关键值,包括操作类型、提供者、请求模型和响应模型

getHighCardinalityKeyValues

生成高粒度的关键值,包括请求选项(如温度、工具名称)和响应信息(如令牌使用情况)
低粒度关键值
aiOperationType
操作类型
aiProvider
模型提供者
requestModel
生成请求模型名称
responseModel
生成响应模型名称
高粒度关键值
requestFrequencyPenalty
请求频率惩罚设置
requestMaxTokens
请求最大令牌数
requestPresencePenalty
请求存在惩罚设置
requestStopSequences
请求停止序列
requestTemperature
请求温度设置
requestTools
请求工具名称
requestTopK
请求 topk 采样设置
requestTopP
请求 topp 采样设置
responseFinishReasons
响应完成原因
responseId
响应唯一标识符
usageInputTokens
输入令牌使用量
usageOutputTokens
输出令牌使用量
usageTotalTokens
总令牌使用量
package org.springframework.ai.chat.observation;

import java.util.HashSet;
import java.util.Set;
import java.util.StringJoiner;

import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;

import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

public class DefaultChatModelObservationConvention implements ChatModelObservationConvention {

    public static final String DEFAULTNAME = "genai.client.operation";

    private static final KeyValue REQUESTMODELNONE = KeyValue
       .of(ChatModelObservationDocumentation.LowCardinalityKeyNames.REQUESTMODEL, KeyValue.NONEVALUE);

    private static final KeyValue RESPONSEMODELNONE = KeyValue
       .of(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSEMODEL, KeyValue.NONEVALUE);

    @Override
    public String getName() {
       return DEFAULTNAME;
    }

    @Override
    public String getContextualName(ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (StringUtils.hasText(options.getModel())) {
          return "%s %s".formatted(context.getOperationMetadata().operationType(), options.getModel());
       }
       return context.getOperationMetadata().operationType();
    }

    @Override
    public KeyValues getLowCardinalityKeyValues(ChatModelObservationContext context) {
       return KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context),
             responseModel(context));
    }

    protected KeyValue aiOperationType(ChatModelObservationContext context) {
       return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.AIOPERATIONTYPE,
             context.getOperationMetadata().operationType());
    }

    protected KeyValue aiProvider(ChatModelObservationContext context) {
       return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.AIPROVIDER,
             context.getOperationMetadata().provider());
    }

    protected KeyValue requestModel(ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (StringUtils.hasText(options.getModel())) {
          return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.REQUESTMODEL,
                options.getModel());
       }
       return REQUESTMODELNONE;
    }

    protected KeyValue responseModel(ChatModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && StringUtils.hasText(context.getResponse().getMetadata().getModel())) {
          return KeyValue.of(ChatModelObservationDocumentation.LowCardinalityKeyNames.RESPONSEMODEL,
                context.getResponse().getMetadata().getModel());
       }
       return RESPONSEMODELNONE;
    }

    @Override
    public KeyValues getHighCardinalityKeyValues(ChatModelObservationContext context) {
       var keyValues = KeyValues.empty();
       // Request
       keyValues = requestFrequencyPenalty(keyValues, context);
       keyValues = requestMaxTokens(keyValues, context);
       keyValues = requestPresencePenalty(keyValues, context);
       keyValues = requestStopSequences(keyValues, context);
       keyValues = requestTemperature(keyValues, context);
       keyValues = requestTools(keyValues, context);
       keyValues = requestTopK(keyValues, context);
       keyValues = requestTopP(keyValues, context);
       // Response
       keyValues = responseFinishReasons(keyValues, context);
       keyValues = responseId(keyValues, context);
       keyValues = usageInputTokens(keyValues, context);
       keyValues = usageOutputTokens(keyValues, context);
       keyValues = usageTotalTokens(keyValues, context);
       return keyValues;
    }

    // Request

    protected KeyValues requestFrequencyPenalty(KeyValues keyValues, ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (options.getFrequencyPenalty() != null) {
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUESTFREQUENCYPENALTY.asString(),
                String.valueOf(options.getFrequencyPenalty()));
       }
       return keyValues;
    }

    protected KeyValues requestMaxTokens(KeyValues keyValues, ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (options.getMaxTokens() != null) {
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUESTMAXTOKENS.asString(),
                String.valueOf(options.getMaxTokens()));
       }
       return keyValues;
    }

    protected KeyValues requestPresencePenalty(KeyValues keyValues, ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (options.getPresencePenalty() != null) {
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUESTPRESENCEPENALTY.asString(),
                String.valueOf(options.getPresencePenalty()));
       }
       return keyValues;
    }

    protected KeyValues requestStopSequences(KeyValues keyValues, ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (!CollectionUtils.isEmpty(options.getStopSequences())) {
          StringJoiner stopSequencesJoiner = new StringJoiner(", ", "[", "]");
          options.getStopSequences().forEach(value -> stopSequencesJoiner.add("\"" + value + "\""));
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUESTSTOPSEQUENCES.asString(),
                stopSequencesJoiner.toString());
       }
       return keyValues;
    }

    protected KeyValues requestTemperature(KeyValues keyValues, ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (options.getTemperature() != null) {
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUESTTEMPERATURE.asString(),
                String.valueOf(options.getTemperature()));
       }
       return keyValues;
    }

    protected KeyValues requestTools(KeyValues keyValues, ChatModelObservationContext context) {
       if (!(context.getRequest().getOptions() instanceof ToolCallingChatOptions options)) {
          return keyValues;
       }

       Set<String> toolNames = new HashSet<>(options.getToolNames());
       toolNames.addAll(options.getToolCallbacks().stream().map(tc -> tc.getToolDefinition().name()).toList());

       if (!CollectionUtils.isEmpty(toolNames)) {
          StringJoiner toolNamesJoiner = new StringJoiner(", ", "[", "]");
          toolNames.forEach(value -> toolNamesJoiner.add("\"" + value + "\""));
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUESTTOOLNAMES.asString(),
                toolNamesJoiner.toString());
       }
       return keyValues;
    }

    protected KeyValues requestTopK(KeyValues keyValues, ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (options.getTopK() != null) {
          return keyValues.and(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUESTTOPK.asString(),
                String.valueOf(options.getTopK()));
       }
       return keyValues;
    }

    protected KeyValues requestTopP(KeyValues keyValues, ChatModelObservationContext context) {
       ChatOptions options = context.getRequest().getOptions();
       if (options.getTopP() != null) {
          return keyValues.and(ChatModelObservationDocumentation.HighCardinalityKeyNames.REQUESTTOPP.asString(),
                String.valueOf(options.getTopP()));
       }
       return keyValues;
    }

    // Response

    protected KeyValues responseFinishReasons(KeyValues keyValues, ChatModelObservationContext context) {
       if (context.getResponse() != null && !CollectionUtils.isEmpty(context.getResponse().getResults())) {
          var finishReasons = context.getResponse()
             .getResults()
             .stream()
             .filter(generation -> StringUtils.hasText(generation.getMetadata().getFinishReason()))
             .map(generation -> generation.getMetadata().getFinishReason())
             .toList();
          if (CollectionUtils.isEmpty(finishReasons)) {
             return keyValues;
          }
          StringJoiner finishReasonsJoiner = new StringJoiner(", ", "[", "]");
          finishReasons.forEach(finishReason -> finishReasonsJoiner.add("\"" + finishReason + "\""));
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.RESPONSEFINISHREASONS.asString(),
                finishReasonsJoiner.toString());
       }
       return keyValues;
    }

    protected KeyValues responseId(KeyValues keyValues, ChatModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && StringUtils.hasText(context.getResponse().getMetadata().getId())) {
          return keyValues.and(ChatModelObservationDocumentation.HighCardinalityKeyNames.RESPONSEID.asString(),
                context.getResponse().getMetadata().getId());
       }
       return keyValues;
    }

    protected KeyValues usageInputTokens(KeyValues keyValues, ChatModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && context.getResponse().getMetadata().getUsage() != null
             && context.getResponse().getMetadata().getUsage().getPromptTokens() != null) {
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGEINPUTTOKENS.asString(),
                String.valueOf(context.getResponse().getMetadata().getUsage().getPromptTokens()));
       }
       return keyValues;
    }

    protected KeyValues usageOutputTokens(KeyValues keyValues, ChatModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && context.getResponse().getMetadata().getUsage() != null
             && context.getResponse().getMetadata().getUsage().getCompletionTokens() != null) {
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGEOUTPUTTOKENS.asString(),
                String.valueOf(context.getResponse().getMetadata().getUsage().getCompletionTokens()));
       }
       return keyValues;
    }

    protected KeyValues usageTotalTokens(KeyValues keyValues, ChatModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && context.getResponse().getMetadata().getUsage() != null
             && context.getResponse().getMetadata().getUsage().getTotalTokens() != null) {
          return keyValues.and(
                ChatModelObservationDocumentation.HighCardinalityKeyNames.USAGETOTALTOKENS.asString(),
                String.valueOf(context.getResponse().getMetadata().getUsage().getTotalTokens()));
       }
       return keyValues;
    }

}
代码定位

在 ChatModel 实现类里内部 new 的对象,而不是自动注入

将数据导入 ChatModelObservationContext 中

ChatClient 下的观测

ChatClientObservationConvention

ChatClient 角度下的观测接口类

package org.springframework.ai.chat.client.observation;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;

public interface ChatClientObservationConvention extends ObservationConvention<ChatClientObservationContext> {
    default boolean supportsContext(Observation.Context context) {
        return context instanceof ChatClientObservationContext;
    }
}
DefaultChatClientObservationConvention

默认定义 ChatClient 观测约定的实现类,主要用于生成 Micrometer 观察功能所需的上下文信息和关键值

  • String name:观测名称,默认为"spring.ai.chat.client"

方法名称
描述

getContextualName
操作元数据生成上下文名称,格式为 provider + CHATCLIENT

getLowCardinalityKeyValues
生成低粒度的关键值,包括操作类型、提供者、Spring AI 类型和流模式

getHighCardinalityKeyValues
生成高粒度的关键值,包括顾问列表、会话 ID 和工具名称
低粒度
aiOperationType
操作类型
aiProvider
提供者
springAiKind
SpringAI类型
stream
流模式
高粒度
advisors
顾问列表
conversationId
会话ID
tools
工具名称
package org.springframework.ai.chat.client.observation;

import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import java.util.ArrayList;
import java.util.List;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.observation.ChatClientObservationDocumentation.HighCardinalityKeyNames;
import org.springframework.ai.chat.observation.ChatModelObservationDocumentation.LowCardinalityKeyNames;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.model.tool.ToolCallingChatOptions;
import org.springframework.ai.observation.ObservabilityHelper;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

public class DefaultChatClientObservationConvention implements ChatClientObservationConvention {
    public static final String DEFAULTNAME = "spring.ai.chat.client";
    private final String name;

    public DefaultChatClientObservationConvention() {
        this("spring.ai.chat.client");
    }

    public DefaultChatClientObservationConvention(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    @Nullable
    public String getContextualName(ChatClientObservationContext context) {
        return "%s %s".formatted(context.getOperationMetadata().provider(), SpringAiKind.CHATCLIENT.value());
    }

    public KeyValues getLowCardinalityKeyValues(ChatClientObservationContext context) {
        return KeyValues.of(new KeyValue[]{this.aiOperationType(context), this.aiProvider(context), this.springAiKind(), this.stream(context)});
    }

    protected KeyValue aiOperationType(ChatClientObservationContext context) {
        return KeyValue.of(LowCardinalityKeyNames.AIOPERATIONTYPE, context.getOperationMetadata().operationType());
    }

    protected KeyValue aiProvider(ChatClientObservationContext context) {
        return KeyValue.of(LowCardinalityKeyNames.AIPROVIDER, context.getOperationMetadata().provider());
    }

    protected KeyValue springAiKind() {
        return KeyValue.of(org.springframework.ai.chat.client.observation.ChatClientObservationDocumentation.LowCardinalityKeyNames.SPRINGAIKIND, SpringAiKind.CHATCLIENT.value());
    }

    protected KeyValue stream(ChatClientObservationContext context) {
        return KeyValue.of(org.springframework.ai.chat.client.observation.ChatClientObservationDocumentation.LowCardinalityKeyNames.STREAM, "" + context.isStream());
    }

    public KeyValues getHighCardinalityKeyValues(ChatClientObservationContext context) {
        KeyValues keyValues = KeyValues.empty();
        keyValues = this.advisors(keyValues, context);
        keyValues = this.conversationId(keyValues, context);
        keyValues = this.tools(keyValues, context);
        return keyValues;
    }

    protected KeyValues advisors(KeyValues keyValues, ChatClientObservationContext context) {
        if (CollectionUtils.isEmpty(context.getAdvisors())) {
            return keyValues;
        } else {
            List<String> advisorNames = context.getAdvisors().stream().map(Advisor::getName).toList();
            return keyValues.and(HighCardinalityKeyNames.CHATCLIENTADVISORS.asString(), ObservabilityHelper.concatenateStrings(advisorNames));
        }
    }

    protected KeyValues conversationId(KeyValues keyValues, ChatClientObservationContext context) {
        if (CollectionUtils.isEmpty(context.getRequest().context())) {
            return keyValues;
        } else {
            Object conversationIdValue = context.getRequest().context().get("chatmemoryconversationid");
            if (conversationIdValue instanceof String) {
                String conversationId = (String)conversationIdValue;
                if (StringUtils.hasText(conversationId)) {
                    return keyValues.and(HighCardinalityKeyNames.CHATCLIENTCONVERSATIONID.asString(), conversationId);
                }
            }

            return keyValues;
        }
    }

    protected KeyValues tools(KeyValues keyValues, ChatClientObservationContext context) {
        if (context.getRequest().prompt().getOptions() == null) {
            return keyValues;
        } else {
            ChatOptions var4 = context.getRequest().prompt().getOptions();
            if (var4 instanceof ToolCallingChatOptions) {
                ToolCallingChatOptions options = (ToolCallingChatOptions)var4;
                ArrayList var6 = new ArrayList(options.getToolNames());
                List toolCallbacks = options.getToolCallbacks();
                if (CollectionUtils.isEmpty(var6) && CollectionUtils.isEmpty(toolCallbacks)) {
                    return keyValues;
                } else {
                    toolCallbacks.forEach((toolCallback) -> var6.add(toolCallback.getToolDefinition().name()));
                    return keyValues.and(HighCardinalityKeyNames.CHATCLIENTTOOLNAMES.asString(), ObservabilityHelper.concatenateStrings(var6.stream().sorted().toList()));
                }
            } else {
                return keyValues;
            }
        }
    }
}
代码定位

在 DefaultChatClient 内部 new 的对象,而不是自动注入

工具下的观测

ToolCallingObservationContext

工具调用的观测类

  • ToolDefinition toolDefinition:工具的定义信息
  • ToolMetadata toolMetadata:工具的元数据信息
  • String toolCallArguments:工具调用时传递的参数
  • String toolCallResult:工具调用的结果
package org.springframework.ai.tool.observation;

import io.micrometer.observation.Observation;

import org.springframework.ai.observation.AiOperationMetadata;
import org.springframework.ai.observation.conventions.AiOperationType;
import org.springframework.ai.observation.conventions.AiProvider;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.metadata.ToolMetadata;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

public final class ToolCallingObservationContext extends Observation.Context {

    private final AiOperationMetadata operationMetadata = new AiOperationMetadata(AiOperationType.FRAMEWORK.value(),
          AiProvider.SPRINGAI.value());

    private final ToolDefinition toolDefinition;

    private final ToolMetadata toolMetadata;

    private final String toolCallArguments;

    @Nullable
    private String toolCallResult;

    private ToolCallingObservationContext(ToolDefinition toolDefinition, ToolMetadata toolMetadata,
          @Nullable String toolCallArguments, @Nullable String toolCallResult) {
       Assert.notNull(toolDefinition, "toolDefinition cannot be null");
       Assert.notNull(toolMetadata, "toolMetadata cannot be null");

       this.toolDefinition = toolDefinition;
       this.toolMetadata = toolMetadata;
       this.toolCallArguments = toolCallArguments != null ? toolCallArguments : "{}";
       this.toolCallResult = toolCallResult;
    }

    public AiOperationMetadata getOperationMetadata() {
       return this.operationMetadata;
    }

    public ToolDefinition getToolDefinition() {
       return this.toolDefinition;
    }

    public ToolMetadata getToolMetadata() {
       return this.toolMetadata;
    }

    public String getToolCallArguments() {
       return this.toolCallArguments;
    }

    @Nullable
    public String getToolCallResult() {
       return this.toolCallResult;
    }

    public void setToolCallResult(@Nullable String toolCallResult) {
       this.toolCallResult = toolCallResult;
    }

    public static Builder builder() {
       return new Builder();
    }

    public static final class Builder {

       private ToolDefinition toolDefinition;

       private ToolMetadata toolMetadata = ToolMetadata.builder().build();

       private String toolCallArguments;

       @Nullable
       private String toolCallResult;

       private Builder() {
       }

       public Builder toolDefinition(ToolDefinition toolDefinition) {
          this.toolDefinition = toolDefinition;
          return this;
       }

       public Builder toolMetadata(ToolMetadata toolMetadata) {
          this.toolMetadata = toolMetadata;
          return this;
       }

       public Builder toolCallArguments(String toolCallArguments) {
          this.toolCallArguments = toolCallArguments;
          return this;
       }

       public Builder toolCallResult(@Nullable String toolCallResult) {
          this.toolCallResult = toolCallResult;
          return this;
       }

       public ToolCallingObservationContext build() {
          return new ToolCallingObservationContext(this.toolDefinition, this.toolMetadata, this.toolCallArguments,
                this.toolCallResult);
       }

    }

}
代码定位

EmbeddingModel 下的观测

pom.xml 文件
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-autoconfigure-model-embedding-observation</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-model</artifactId>
</dependency>
EmbeddingObservationAutoConfiguration

EmbeddingModel 的自动注入观测类

自动注入 EmbeddingModelMeterObservationHandler 的 Bean

package org.springframework.ai.model.embedding.observation.autoconfigure;

import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.observation.EmbeddingModelMeterObservationHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

@AutoConfiguration(
    afterName = {"org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration"}
)
@ConditionalOnClass({EmbeddingModel.class})
public class EmbeddingObservationAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean({MeterRegistry.class})
    EmbeddingModelMeterObservationHandler embeddingModelMeterObservationHandler(ObjectProvider<MeterRegistry> meterRegistry) {
        return new EmbeddingModelMeterObservationHandler((MeterRegistry)meterRegistry.getObject());
    }
}
EmbeddingModelMeterObservationHandler

EmbeddingModel 的观测处理器

package org.springframework.ai.embedding.observation;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;

import org.springframework.ai.model.observation.ModelUsageMetricsGenerator;

public class EmbeddingModelMeterObservationHandler implements ObservationHandler<EmbeddingModelObservationContext> {

    private final MeterRegistry meterRegistry;

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

    @Override
    public void onStop(EmbeddingModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && context.getResponse().getMetadata().getUsage() != null) {
          ModelUsageMetricsGenerator.generate(context.getResponse().getMetadata().getUsage(), context,
                this.meterRegistry);
       }
    }

    @Override
    public boolean supportsContext(Observation.Context context) {
       return context instanceof EmbeddingModelObservationContext;
    }

}
EmbeddingModelObservationContext

嵌入模型观测功能的上下文类

package org.springframework.ai.embedding.observation;

import org.springframework.ai.embedding.EmbeddingRequest;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.ai.model.observation.ModelObservationContext;
import org.springframework.ai.observation.AiOperationMetadata;
import org.springframework.ai.observation.conventions.AiOperationType;

public class EmbeddingModelObservationContext extends ModelObservationContext<EmbeddingRequest, EmbeddingResponse> {

    EmbeddingModelObservationContext(EmbeddingRequest embeddingRequest, String provider) {
       super(embeddingRequest,
             AiOperationMetadata.builder()
                .operationType(AiOperationType.EMBEDDING.value())
                .provider(provider)
                .build());
    }

    public static Builder builder() {
       return new Builder();
    }

    public static final class Builder {

       private EmbeddingRequest embeddingRequest;

       private String provider;

       private Builder() {
       }

       public Builder embeddingRequest(EmbeddingRequest embeddingRequest) {
          this.embeddingRequest = embeddingRequest;
          return this;
       }

       public Builder provider(String provider) {
          this.provider = provider;
          return this;
       }

       public EmbeddingModelObservationContext build() {
          return new EmbeddingModelObservationContext(this.embeddingRequest, this.provider);
       }

    }

}
EmbeddingModelObservationConvention

EmbeddingModel 角度下的观测接口类

package org.springframework.ai.embedding.observation;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;

public interface EmbeddingModelObservationConvention extends ObservationConvention<EmbeddingModelObservationContext> {

    @Override
    default boolean supportsContext(Observation.Context context) {
       return context instanceof EmbeddingModelObservationContext;
    }

}
DefaultEmbeddingModelObservationConvention

默认定义 EmbeddingModel 观测约定的实现类,主要用于生成 Micrometer 观察功能所需的上下文信息和关键值

  • String DEFAULTNAME:默认观测名称为"genai.client.operation"
  • KeyValue REQUESTMODELNONE:请求为空的默认值
  • KeyValue RESPONSEMODELNONE:响应模型为空的默认值

方法名称
描述

getContextualName
操作元数据生成上下文名称,格式为 provider + CHATCLIENT

getLowCardinalityKeyValues
生成低粒度的关键值,包括操作类型、提供者、Spring AI 类型和流模式

getHighCardinalityKeyValues
生成高粒度的关键值,包括顾问列表、会话 ID 和工具名称
低粒度
aiOperationType
操作类型
aiProvider
提供者
requestModel
请求模型名称
responseModel
响应模型名称
高粒度
requestEmbeddingDimension
嵌入维度
usageInputTokens
输入令牌使用量
usageTotalTokens
总令牌使用量
package org.springframework.ai.embedding.observation;

import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;

import org.springframework.util.StringUtils;

public class DefaultEmbeddingModelObservationConvention implements EmbeddingModelObservationConvention {

    public static final String DEFAULTNAME = "genai.client.operation";

    private static final KeyValue REQUESTMODELNONE = KeyValue
       .of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.REQUESTMODEL, KeyValue.NONEVALUE);

    private static final KeyValue RESPONSEMODELNONE = KeyValue
       .of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSEMODEL, KeyValue.NONEVALUE);

    @Override
    public String getName() {
       return DEFAULTNAME;
    }

    @Override
    public String getContextualName(EmbeddingModelObservationContext context) {
       if (StringUtils.hasText(context.getRequest().getOptions().getModel())) {
          return "%s %s".formatted(context.getOperationMetadata().operationType(),
                context.getRequest().getOptions().getModel());
       }
       return context.getOperationMetadata().operationType();
    }

    @Override
    public KeyValues getLowCardinalityKeyValues(EmbeddingModelObservationContext context) {
       return KeyValues.of(aiOperationType(context), aiProvider(context), requestModel(context),
             responseModel(context));
    }

    protected KeyValue aiOperationType(EmbeddingModelObservationContext context) {
       return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.AIOPERATIONTYPE,
             context.getOperationMetadata().operationType());
    }

    protected KeyValue aiProvider(EmbeddingModelObservationContext context) {
       return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.AIPROVIDER,
             context.getOperationMetadata().provider());
    }

    protected KeyValue requestModel(EmbeddingModelObservationContext context) {
       if (StringUtils.hasText(context.getRequest().getOptions().getModel())) {
          return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.REQUESTMODEL,
                context.getRequest().getOptions().getModel());
       }
       return REQUESTMODELNONE;
    }

    protected KeyValue responseModel(EmbeddingModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && StringUtils.hasText(context.getResponse().getMetadata().getModel())) {
          return KeyValue.of(EmbeddingModelObservationDocumentation.LowCardinalityKeyNames.RESPONSEMODEL,
                context.getResponse().getMetadata().getModel());
       }
       return RESPONSEMODELNONE;
    }

    @Override
    public KeyValues getHighCardinalityKeyValues(EmbeddingModelObservationContext context) {
       var keyValues = KeyValues.empty();
       // Request
       keyValues = requestEmbeddingDimension(keyValues, context);
       // Response
       keyValues = usageInputTokens(keyValues, context);
       keyValues = usageTotalTokens(keyValues, context);
       return keyValues;
    }

    // Request

    protected KeyValues requestEmbeddingDimension(KeyValues keyValues, EmbeddingModelObservationContext context) {
       if (context.getRequest().getOptions().getDimensions() != null) {
          return keyValues
             .and(EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.REQUESTEMBEDDINGDIMENSIONS
                .asString(), String.valueOf(context.getRequest().getOptions().getDimensions()));
       }
       return keyValues;
    }

    // Response

    protected KeyValues usageInputTokens(KeyValues keyValues, EmbeddingModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && context.getResponse().getMetadata().getUsage() != null
             && context.getResponse().getMetadata().getUsage().getPromptTokens() != null) {
          return keyValues.and(
                EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGEINPUTTOKENS.asString(),
                String.valueOf(context.getResponse().getMetadata().getUsage().getPromptTokens()));
       }
       return keyValues;
    }

    protected KeyValues usageTotalTokens(KeyValues keyValues, EmbeddingModelObservationContext context) {
       if (context.getResponse() != null && context.getResponse().getMetadata() != null
             && context.getResponse().getMetadata().getUsage() != null
             && context.getResponse().getMetadata().getUsage().getTotalTokens() != null) {
          return keyValues.and(
                EmbeddingModelObservationDocumentation.HighCardinalityKeyNames.USAGETOTALTOKENS.asString(),
                String.valueOf(context.getResponse().getMetadata().getUsage().getTotalTokens()));
       }
       return keyValues;
    }

}
代码定位

VectorStore 下的观测

pom.xml 文件
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-autoconfigure-vector-store-observation</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-model</artifactId>
</dependency>
VectorStoreObservationProperties

向量数据库观测功能的配置属性类

package org.springframework.ai.vectorstore.observation.autoconfigure;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("spring.ai.vectorstore.observations")
public class VectorStoreObservationProperties {
    public static final String CONFIGPREFIX = "spring.ai.vectorstore.observations";
    private boolean logQueryResponse = false;

    public boolean isLogQueryResponse() {
        return this.logQueryResponse;
    }

    public void setLogQueryResponse(boolean logQueryResponse) {
        this.logQueryResponse = logQueryResponse;
    }
}
VectorStoreObservationAutoConfiguration

向量观测的自动配置类

  • TracerPresentObservationConfiguration(类路径存在 Tracer):对外提供 TracingAwareLoggingObservationHandler 的 Bean
  • TracerNotPresentObservationConfiguration(类路径不存在 Tracer):对外提供 VectorStoreQueryResponseObservationHandler 的 Bean
package org.springframework.ai.vectorstore.observation.autoconfigure;

import io.micrometer.tracing.Tracer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.observation.TracingAwareLoggingObservationHandler;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationContext;
import org.springframework.ai.vectorstore.observation.VectorStoreQueryResponseObservationHandler;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@AutoConfiguration(
    afterName = {"org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration"}
)
@ConditionalOnClass({VectorStore.class})
@EnableConfigurationProperties({VectorStoreObservationProperties.class})
public class VectorStoreObservationAutoConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(VectorStoreObservationAutoConfiguration.class);

    private static void logQueryResponseContentWarning() {
        logger.warn("You have enabled logging out of the query response content with the risk of exposing sensitive or private information. Please, be careful!");
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({Tracer.class})
    @ConditionalOnBean({Tracer.class})
    static class TracerPresentObservationConfiguration {
        @Bean
        @ConditionalOnMissingBean(
            value = {VectorStoreQueryResponseObservationHandler.class},
            name = {"vectorStoreQueryResponseObservationHandler"}
        )
        @ConditionalOnProperty(
            prefix = "spring.ai.vectorstore.observations",
            name = {"log-query-response"},
            havingValue = "true"
        )
        TracingAwareLoggingObservationHandler<VectorStoreObservationContext> vectorStoreQueryResponseObservationHandler(Tracer tracer) {
            VectorStoreObservationAutoConfiguration.logQueryResponseContentWarning();
            return new TracingAwareLoggingObservationHandler(new VectorStoreQueryResponseObservationHandler(), tracer);
        }
    }

    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnMissingClass({"io.micrometer.tracing.Tracer"})
    static class TracerNotPresentObservationConfiguration {
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(
            prefix = "spring.ai.vectorstore.observations",
            name = {"log-query-response"},
            havingValue = "true"
        )
        VectorStoreQueryResponseObservationHandler vectorStoreQueryResponseObservationHandler() {
            VectorStoreObservationAutoConfiguration.logQueryResponseContentWarning();
            return new VectorStoreQueryResponseObservationHandler();
        }
    }
}
VectorStoreQueryResponseObservationHandler

VectorStore 的观测处理器

package org.springframework.ai.vectorstore.observation;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.observation.ObservabilityHelper;
import org.springframework.util.CollectionUtils;

public class VectorStoreQueryResponseObservationHandler implements ObservationHandler<VectorStoreObservationContext> {
    private static final Logger logger = LoggerFactory.getLogger(VectorStoreQueryResponseObservationHandler.class);

    public void onStop(VectorStoreObservationContext context) {
        logger.info("Vector Store Query Response:\n{}", ObservabilityHelper.concatenateStrings(this.documents(context)));
    }

    private List<String> documents(VectorStoreObservationContext context) {
        return CollectionUtils.isEmpty(context.getQueryResponse()) ? List.of() : context.getQueryResponse().stream().map(Document::getText).toList();
    }

    public boolean supportsContext(Observation.Context context) {
        return context instanceof VectorStoreObservationContext;
    }
}
VectorStoreObservationContext

存储向量存储操作相关元数据的上下文类

  • String databaseSystem:数据库系统的名称
  • String operationName:操作的名称,例如添加、删除或查询
  • String collectionName:集合的名称,用于标识操作的目标集合
  • Integer dimensions:量的维度
  • String fieldName:字段名称
  • String namespace:命名空间,用于区分不同的存储区域
  • String similarityMetric:似度度量方法,用于查询操作
  • SearchRequest queryRequest:查询请求的详细信息
  • List queryResponse:查询操作的响应结果
package org.springframework.ai.vectorstore.observation;

import io.micrometer.observation.Observation;
import java.util.List;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

public class VectorStoreObservationContext extends Observation.Context {
    private final String databaseSystem;
    private final String operationName;
    @Nullable
    private String collectionName;
    @Nullable
    private Integer dimensions;
    @Nullable
    private String fieldName;
    @Nullable
    private String namespace;
    @Nullable
    private String similarityMetric;
    @Nullable
    private SearchRequest queryRequest;
    @Nullable
    private List<Document> queryResponse;

    public VectorStoreObservationContext(String databaseSystem, String operationName) {
        Assert.hasText(databaseSystem, "databaseSystem cannot be null or empty");
        Assert.hasText(operationName, "operationName cannot be null or empty");
        this.databaseSystem = databaseSystem;
        this.operationName = operationName;
    }

    public static Builder builder(String databaseSystem, String operationName) {
        return new Builder(databaseSystem, operationName);
    }

    public static Builder builder(String databaseSystem, Operation operation) {
        return builder(databaseSystem, operation.value);
    }

    public String getDatabaseSystem() {
        return this.databaseSystem;
    }

    public String getOperationName() {
        return this.operationName;
    }

    @Nullable
    public String getCollectionName() {
        return this.collectionName;
    }

    public void setCollectionName(@Nullable String collectionName) {
        this.collectionName = collectionName;
    }

    @Nullable
    public Integer getDimensions() {
        return this.dimensions;
    }

    public void setDimensions(@Nullable Integer dimensions) {
        this.dimensions = dimensions;
    }

    @Nullable
    public String getFieldName() {
        return this.fieldName;
    }

    public void setFieldName(@Nullable String fieldName) {
        this.fieldName = fieldName;
    }

    @Nullable
    public String getNamespace() {
        return this.namespace;
    }

    public void setNamespace(@Nullable String namespace) {
        this.namespace = namespace;
    }

    @Nullable
    public String getSimilarityMetric() {
        return this.similarityMetric;
    }

    public void setSimilarityMetric(@Nullable String similarityMetric) {
        this.similarityMetric = similarityMetric;
    }

    @Nullable
    public SearchRequest getQueryRequest() {
        return this.queryRequest;
    }

    public void setQueryRequest(@Nullable SearchRequest queryRequest) {
        this.queryRequest = queryRequest;
    }

    @Nullable
    public List<Document> getQueryResponse() {
        return this.queryResponse;
    }

    public void setQueryResponse(@Nullable List<Document> queryResponse) {
        this.queryResponse = queryResponse;
    }

    public static enum Operation {
        ADD("add"),
        DELETE("delete"),
        QUERY("query");

        public final String value;

        private Operation(String value) {
            this.value = value;
        }

        public String value() {
            return this.value;
        }
    }

    public static class Builder {
        private final VectorStoreObservationContext context;

        public Builder(String databaseSystem, String operationName) {
            this.context = new VectorStoreObservationContext(databaseSystem, operationName);
        }

        public Builder collectionName(String collectionName) {
            this.context.setCollectionName(collectionName);
            return this;
        }

        public Builder dimensions(Integer dimensions) {
            this.context.setDimensions(dimensions);
            return this;
        }

        public Builder fieldName(@Nullable String fieldName) {
            this.context.setFieldName(fieldName);
            return this;
        }

        public Builder namespace(String namespace) {
            this.context.setNamespace(namespace);
            return this;
        }

        public Builder queryRequest(SearchRequest request) {
            this.context.setQueryRequest(request);
            return this;
        }

        public Builder queryResponse(List<Document> documents) {
            this.context.setQueryResponse(documents);
            return this;
        }

        public Builder similarityMetric(String similarityMetric) {
            this.context.setSimilarityMetric(similarityMetric);
            return this;
        }

        public VectorStoreObservationContext build() {
            return this.context;
        }
    }
}
VectorStoreObservationConvention

向量观测接口类

package org.springframework.ai.vectorstore.observation;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;

public interface VectorStoreObservationConvention extends ObservationConvention<VectorStoreObservationContext> {
    default boolean supportsContext(Observation.Context context) {
        return context instanceof VectorStoreObservationContext;
    }
}
DefaultVectorStoreObservationConvention

默认的向量存储操作观察约定的实现类

  • String name:观测名称,默认为"db.vector.client.operation"

方法名称
描述

getContextualName
根据数据库系统和操作名称生成上下文名称,格式为 databaseSystem + operationName

getLowCardinalityKeyValues
生成低粒度的关键值,包括 Spring AI 类型、数据库系统和操作名称

getHighCardinalityKeyValues
生成高粒度的关键值,包括集合名称、维度、字段名称、命名空间、查询内容、相似度度量等
低粒度
dbOperationName
操作名称
dbSystem
数据库系统
springAiKind
SpringAI类型
高粒度
collectionName
集合名词
dimensions
向量纬度
fieldName
字段名称
metadataFilter
查询过滤条件
namespace
命名空间
queryContent
查询内容
similarityMetric
相似性度量
similarityThreshold
相似度阈值
topK
生产查询结果的TopK关键值
package org.springframework.ai.vectorstore.observation;

import io.micrometer.common.KeyValue;
import io.micrometer.common.KeyValues;
import org.springframework.ai.observation.conventions.SpringAiKind;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.HighCardinalityKeyNames;
import org.springframework.ai.vectorstore.observation.VectorStoreObservationDocumentation.LowCardinalityKeyNames;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

public class DefaultVectorStoreObservationConvention implements VectorStoreObservationConvention {
    public static final String DEFAULTNAME = "db.vector.client.operation";
    private final String name;

    public DefaultVectorStoreObservationConvention() {
        this("db.vector.client.operation");
    }

    public DefaultVectorStoreObservationConvention(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    @Nullable
    public String getContextualName(VectorStoreObservationContext context) {
        return "%s %s".formatted(context.getDatabaseSystem(), context.getOperationName());
    }

    public KeyValues getLowCardinalityKeyValues(VectorStoreObservationContext context) {
        return KeyValues.of(new KeyValue[]{this.springAiKind(), this.dbSystem(context), this.dbOperationName(context)});
    }

    protected KeyValue springAiKind() {
        return KeyValue.of(LowCardinalityKeyNames.SPRINGAIKIND, SpringAiKind.VECTORSTORE.value());
    }

    protected KeyValue dbSystem(VectorStoreObservationContext context) {
        return KeyValue.of(LowCardinalityKeyNames.DBSYSTEM, context.getDatabaseSystem());
    }

    protected KeyValue dbOperationName(VectorStoreObservationContext context) {
        return KeyValue.of(LowCardinalityKeyNames.DBOPERATIONNAME, context.getOperationName());
    }

    public KeyValues getHighCardinalityKeyValues(VectorStoreObservationContext context) {
        KeyValues keyValues = KeyValues.empty();
        keyValues = this.collectionName(keyValues, context);
        keyValues = this.dimensions(keyValues, context);
        keyValues = this.fieldName(keyValues, context);
        keyValues = this.metadataFilter(keyValues, context);
        keyValues = this.namespace(keyValues, context);
        keyValues = this.queryContent(keyValues, context);
        keyValues = this.similarityMetric(keyValues, context);
        keyValues = this.similarityThreshold(keyValues, context);
        keyValues = this.topK(keyValues, context);
        return keyValues;
    }

    protected KeyValues collectionName(KeyValues keyValues, VectorStoreObservationContext context) {
        return StringUtils.hasText(context.getCollectionName()) ? keyValues.and(HighCardinalityKeyNames.DBCOLLECTIONNAME.asString(), context.getCollectionName()) : keyValues;
    }

    protected KeyValues dimensions(KeyValues keyValues, VectorStoreObservationContext context) {
        return context.getDimensions() != null && context.getDimensions() > 0 ? keyValues.and(HighCardinalityKeyNames.DBVECTORDIMENSIONCOUNT.asString(), "" + context.getDimensions()) : keyValues;
    }

    protected KeyValues fieldName(KeyValues keyValues, VectorStoreObservationContext context) {
        return StringUtils.hasText(context.getFieldName()) ? keyValues.and(HighCardinalityKeyNames.DBVECTORFIELDNAME.asString(), context.getFieldName()) : keyValues;
    }

    protected KeyValues metadataFilter(KeyValues keyValues, VectorStoreObservationContext context) {
        return context.getQueryRequest() != null && context.getQueryRequest().getFilterExpression() != null ? keyValues.and(HighCardinalityKeyNames.DBVECTORQUERYFILTER.asString(), context.getQueryRequest().getFilterExpression().toString()) : keyValues;
    }

    protected KeyValues namespace(KeyValues keyValues, VectorStoreObservationContext context) {
        return StringUtils.hasText(context.getNamespace()) ? keyValues.and(HighCardinalityKeyNames.DBNAMESPACE.asString(), context.getNamespace()) : keyValues;
    }

    protected KeyValues queryContent(KeyValues keyValues, VectorStoreObservationContext context) {
        return context.getQueryRequest() != null && StringUtils.hasText(context.getQueryRequest().getQuery()) ? keyValues.and(HighCardinalityKeyNames.DBVECTORQUERYCONTENT.asString(), context.getQueryRequest().getQuery()) : keyValues;
    }

    protected KeyValues similarityMetric(KeyValues keyValues, VectorStoreObservationContext context) {
        return StringUtils.hasText(context.getSimilarityMetric()) ? keyValues.and(HighCardinalityKeyNames.DBSEARCHSIMILARITYMETRIC.asString(), context.getSimilarityMetric()) : keyValues;
    }

    protected KeyValues similarityThreshold(KeyValues keyValues, VectorStoreObservationContext context) {
        return context.getQueryRequest() != null && context.getQueryRequest().getSimilarityThreshold() >= (double)0.0F ? keyValues.and(HighCardinalityKeyNames.DBVECTORQUERYSIMILARITYTHRESHOLD.asString(), String.valueOf(context.getQueryRequest().getSimilarityThreshold())) : keyValues;
    }

    protected KeyValues topK(KeyValues keyValues, VectorStoreObservationContext context) {
        return context.getQueryRequest() != null && context.getQueryRequest().getTopK() > 0 ? keyValues.and(HighCardinalityKeyNames.DBVECTORQUERYTOPK.asString(), "" + context.getQueryRequest().getTopK()) : keyValues;
    }
}
代码定位

学习交流圈

你好,我是影子,曾先后在🐻、新能源、老铁就职,现在是一名AI研发工程师,同时作为Spring AI Alibaba开源社区的Committer。目前新建了一个交流群,一个人走得快,一群人走得远。另外,本人长期维护一套飞书云文档笔记,涵盖后端、大数据系统化的面试资料,可私信免费获取

题外篇

去年暑期向我咨询的一个朋友最近拿了字节的实习offer跟我报喜,准备得很早且很有规划,未来只要平衡好学校的关系未来发展不可限量