CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-commons

Common classes used across Spring AI providing document processing, text transformation, embedding utilities, observability support, and tokenization capabilities for AI application development

Overview
Eval results
Files

observability.mddocs/reference/

Observability

Observability provides comprehensive monitoring and tracing capabilities for AI operations using OpenTelemetry conventions.

Capabilities

Overview

The observability layer consists of:

  • AiOperationMetadata - Metadata record for AI operations
  • ObservabilityHelper - Utility methods for observability
  • TracingAwareLoggingObservationHandler - Handler for tracing-aware logging
  • Observation Conventions - Enums defining standardized attributes, metrics, and values

These components enable telemetry, monitoring, and debugging of AI applications using industry-standard OpenTelemetry semantics.

AiOperationMetadata

Metadata record for AI operations (inference, fine-tuning, evaluation).

package org.springframework.ai.observation;

record AiOperationMetadata(String operationType, String provider) {
    /**
     * Create AiOperationMetadata.
     * @param operationType type of operation (e.g., "chat", "embedding")
     * @param provider AI provider name (e.g., "openai", "anthropic")
     */
    AiOperationMetadata(String operationType, String provider);

    /**
     * Create builder.
     * @return builder instance
     */
    static Builder builder();
}

AiOperationMetadata.Builder

class AiOperationMetadata.Builder {
    /**
     * Set operation type.
     * @param operationType operation type
     * @return this builder
     */
    Builder operationType(String operationType);

    /**
     * Set provider name.
     * @param provider provider name
     * @return this builder
     */
    Builder provider(String provider);

    /**
     * Build metadata record.
     * @return AiOperationMetadata
     */
    AiOperationMetadata build();
}

Usage Examples

import org.springframework.ai.observation.AiOperationMetadata;

// Create metadata directly
AiOperationMetadata metadata1 = new AiOperationMetadata("chat", "openai");

// Create with builder
AiOperationMetadata metadata2 = AiOperationMetadata.builder()
    .operationType("embedding")
    .provider("anthropic")
    .build();

// Access metadata
String opType = metadata2.operationType();
String provider = metadata2.provider();

System.out.println("Operation: " + opType + ", Provider: " + provider);

ObservabilityHelper

Utility methods for observability operations.

package org.springframework.ai.observation;

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

abstract class ObservabilityHelper {
    /**
     * Concatenate map entries to JSON-like string.
     * Converts key-value pairs to "key1:value1,key2:value2" format.
     * @param keyValues map to concatenate
     * @return concatenated string
     */
    static String concatenateEntries(Map<String, Object> keyValues);

    /**
     * Concatenate strings to JSON array-like string.
     * Converts list to "[item1, item2, item3]" format.
     * @param strings list of strings
     * @return concatenated string
     */
    static String concatenateStrings(List<String> strings);
}

Usage Examples

import org.springframework.ai.observation.ObservabilityHelper;
import java.util.List;
import java.util.Map;

// Concatenate map entries
Map<String, Object> attributes = Map.of(
    "model", "gpt-4",
    "temperature", 0.7,
    "max_tokens", 1000
);

String attributesStr = ObservabilityHelper.concatenateEntries(attributes);
System.out.println(attributesStr);
// Output: "model:gpt-4,temperature:0.7,max_tokens:1000"

// Concatenate strings
List<String> finishReasons = List.of("stop", "length", "content_filter");
String reasonsStr = ObservabilityHelper.concatenateStrings(finishReasons);
System.out.println(reasonsStr);
// Output: "[stop, length, content_filter]"

// Use in logging
Map<String, Object> metadata = Map.of(
    "provider", "openai",
    "operation", "chat",
    "tokens_used", 500
);

String logMessage = "AI Operation: " + ObservabilityHelper.concatenateEntries(metadata);
System.out.println(logMessage);

TracingAwareLoggingObservationHandler

Wraps ObservationHandler to make tracing data available for logging.

package org.springframework.ai.observation;

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.tracing.Tracer;

class TracingAwareLoggingObservationHandler<T extends Observation.Context>
        implements ObservationHandler<T> {

    /**
     * Create handler with delegate and tracer.
     * @param delegate delegate observation handler
     * @param tracer tracer for tracing context
     */
    TracingAwareLoggingObservationHandler(ObservationHandler<T> delegate, Tracer tracer);

    /**
     * Handle observation start.
     * @param context observation context
     */
    void onStart(T context);

    /**
     * Handle observation error.
     * @param context observation context
     */
    void onError(T context);

    /**
     * Handle observation event.
     * @param event observation event
     * @param context observation context
     */
    void onEvent(Observation.Event event, T context);

    /**
     * Handle scope opened.
     * @param context observation context
     */
    void onScopeOpened(T context);

    /**
     * Handle scope closed.
     * @param context observation context
     */
    void onScopeClosed(T context);

    /**
     * Handle scope reset.
     * @param context observation context
     */
    void onScopeReset(T context);

    /**
     * Handle observation stop with tracing context.
     * @param context observation context
     */
    void onStop(T context);

    /**
     * Check if handler supports context.
     * @param context observation context
     * @return true if supported
     */
    boolean supportsContext(Observation.Context context);
}

Usage

import org.springframework.ai.observation.TracingAwareLoggingObservationHandler;
import io.micrometer.observation.ObservationHandler;
import io.micrometer.tracing.Tracer;

// Assuming you have a delegate handler and tracer
ObservationHandler<MyContext> delegateHandler = // ... your handler
Tracer tracer = // ... your tracer

// Wrap handler to add tracing awareness
TracingAwareLoggingObservationHandler<MyContext> tracingHandler =
    new TracingAwareLoggingObservationHandler<>(delegateHandler, tracer);

// Register with ObservationRegistry
// observationRegistry.observationConfig().observationHandler(tracingHandler);

Observation Conventions

AiObservationAttributes

OpenTelemetry attribute keys for AI operations following Gen AI semantic conventions.

package org.springframework.ai.observation.conventions;

enum AiObservationAttributes {
    // Operation attributes
    AI_OPERATION_TYPE("gen_ai.operation.name"),
    AI_PROVIDER("gen_ai.system"),

    // Request attributes
    REQUEST_MODEL("gen_ai.request.model"),
    REQUEST_FREQUENCY_PENALTY("gen_ai.request.frequency_penalty"),
    REQUEST_MAX_TOKENS("gen_ai.request.max_tokens"),
    REQUEST_PRESENCE_PENALTY("gen_ai.request.presence_penalty"),
    REQUEST_STOP_SEQUENCES("gen_ai.request.stop_sequences"),
    REQUEST_TEMPERATURE("gen_ai.request.temperature"),
    REQUEST_TOOL_NAMES("spring.ai.model.request.tool.names"),
    REQUEST_TOP_K("gen_ai.request.top_k"),
    REQUEST_TOP_P("gen_ai.request.top_p"),
    REQUEST_EMBEDDING_DIMENSIONS("gen_ai.request.embedding.dimensions"),
    REQUEST_IMAGE_RESPONSE_FORMAT("gen_ai.request.image.response_format"),
    REQUEST_IMAGE_SIZE("gen_ai.request.image.size"),
    REQUEST_IMAGE_STYLE("gen_ai.request.image.style"),

    // Response attributes
    RESPONSE_FINISH_REASONS("gen_ai.response.finish_reasons"),
    RESPONSE_ID("gen_ai.response.id"),
    RESPONSE_MODEL("gen_ai.response.model"),

    // Usage attributes
    USAGE_INPUT_TOKENS("gen_ai.usage.input_tokens"),
    USAGE_OUTPUT_TOKENS("gen_ai.usage.output_tokens"),
    USAGE_TOTAL_TOKENS("gen_ai.usage.total_tokens");

    /**
     * Get attribute key.
     * @return attribute key string
     */
    String value();
}

AiObservationMetricAttributes

Metric-specific attributes.

package org.springframework.ai.observation.conventions;

enum AiObservationMetricAttributes {
    TOKEN_TYPE("gen_ai.token.type");

    String value();
}

AiObservationMetricNames

Standard metric names for AI operations.

package org.springframework.ai.observation.conventions;

enum AiObservationMetricNames {
    OPERATION_DURATION("gen_ai.client.operation.duration"),
    TOKEN_USAGE("gen_ai.client.token.usage");

    String value();
}

AiOperationType

Types of AI operations.

package org.springframework.ai.observation.conventions;

enum AiOperationType {
    CHAT("chat"),
    EMBEDDING("embedding"),
    FRAMEWORK("framework"),
    IMAGE("image"),
    TEXT_COMPLETION("text_completion");

    String value();
}

AiProvider

AI system providers.

package org.springframework.ai.observation.conventions;

enum AiProvider {
    ANTHROPIC("anthropic"),
    AZURE_OPENAI("azure-openai"),
    BEDROCK_CONVERSE("bedrock_converse"),
    DEEPSEEK("deepseek"),
    GOOGLE_GENAI_AI("google_genai"),
    MINIMAX("minimax"),
    MISTRAL_AI("mistral_ai"),
    OCI_GENAI("oci_genai"),
    OLLAMA("ollama"),
    ONNX("onnx"),
    OPENAI("openai"),
    OPENAI_SDK("openai_sdk"),
    SPRING_AI("spring_ai"),
    VERTEX_AI("vertex_ai"),
    ZHIPUAI("zhipuai");

    String value();
}

AiTokenType

Token types for usage metrics.

package org.springframework.ai.observation.conventions;

enum AiTokenType {
    INPUT("input"),
    OUTPUT("output"),
    TOTAL("total");

    String value();
}

SpringAiKind

Types of Spring AI constructs.

package org.springframework.ai.observation.conventions;

enum SpringAiKind {
    ADVISOR("advisor"),
    CHAT_CLIENT("chat_client"),
    TOOL_CALL("tool_call"),
    VECTOR_STORE("vector_store");

    String value();
}

Vector Store Observability

VectorStoreObservationAttributes

OpenTelemetry attribute keys for vector store operations.

package org.springframework.ai.observation.conventions;

enum VectorStoreObservationAttributes {
    DB_COLLECTION_NAME("db.collection.name"),
    DB_NAMESPACE("db.namespace"),
    DB_OPERATION_NAME("db.operation.name"),
    DB_RECORD_ID("db.record.id"),
    DB_SYSTEM("db.system"),
    DB_SEARCH_SIMILARITY_METRIC("db.search.similarity_metric"),
    DB_VECTOR_DIMENSION_COUNT("db.vector.dimension_count"),
    DB_VECTOR_FIELD_NAME("db.vector.field_name"),
    DB_VECTOR_QUERY_CONTENT("db.vector.query.content"),
    DB_VECTOR_QUERY_FILTER("db.vector.query.filter"),
    DB_VECTOR_QUERY_RESPONSE_DOCUMENTS("db.vector.query.response.documents"),
    DB_VECTOR_QUERY_SIMILARITY_THRESHOLD("db.vector.query.similarity_threshold"),
    DB_VECTOR_QUERY_TOP_K("db.vector.query.top_k");

    String value();
}

VectorStoreProvider

Vector store systems.

package org.springframework.ai.observation.conventions;

enum VectorStoreProvider {
    AZURE("azure"),
    CASSANDRA("cassandra"),
    CHROMA("chroma"),
    COSMOSDB("cosmosdb"),
    COUCHBASE("couchbase"),
    ELASTICSEARCH("elasticsearch"),
    GEMFIRE("gemfire"),
    HANA("hana"),
    MARIADB("mariadb"),
    MILVUS("milvus"),
    MONGODB("mongodb"),
    NEO4J("neo4j"),
    OPENSEARCH("opensearch"),
    ORACLE("oracle"),
    PG_VECTOR("pgvector"),
    PINECONE("pinecone"),
    QDRANT("qdrant"),
    REDIS("redis"),
    SIMPLE("simple"),
    TYPESENSE("typesense"),
    WEAVIATE("weaviate");

    String value();
}

VectorStoreSimilarityMetric

Similarity metrics for vector search.

package org.springframework.ai.observation.conventions;

enum VectorStoreSimilarityMetric {
    COSINE("cosine"),
    DOT("dot"),
    EUCLIDEAN("euclidean"),
    MANHATTAN("manhattan");

    String value();
}

Practical Use Cases

Recording AI Operation Metrics

import org.springframework.ai.observation.conventions.*;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;

/**
 * Record metrics for AI operations.
 */
class AIOperationMetricsRecorder {
    private final ObservationRegistry observationRegistry;

    public AIOperationMetricsRecorder(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    public void recordChatOperation(String model, int inputTokens, int outputTokens,
                                     String finishReason) {
        Observation observation = Observation.createNotStarted(
            "ai.chat.operation",
            observationRegistry
        );

        observation
            .lowCardinalityKeyValue(
                AiObservationAttributes.AI_OPERATION_TYPE.value(),
                AiOperationType.CHAT.value()
            )
            .lowCardinalityKeyValue(
                AiObservationAttributes.AI_PROVIDER.value(),
                AiProvider.OPENAI.value()
            )
            .lowCardinalityKeyValue(
                AiObservationAttributes.REQUEST_MODEL.value(),
                model
            )
            .highCardinalityKeyValue(
                AiObservationAttributes.USAGE_INPUT_TOKENS.value(),
                String.valueOf(inputTokens)
            )
            .highCardinalityKeyValue(
                AiObservationAttributes.USAGE_OUTPUT_TOKENS.value(),
                String.valueOf(outputTokens)
            )
            .highCardinalityKeyValue(
                AiObservationAttributes.RESPONSE_FINISH_REASONS.value(),
                finishReason
            );

        observation.observe(() -> {
            // Actual operation happens here
            System.out.println("Executing chat operation...");
        });
    }

    public void recordEmbeddingOperation(String model, int tokenCount, int dimensions) {
        Observation.createNotStarted("ai.embedding.operation", observationRegistry)
            .lowCardinalityKeyValue(
                AiObservationAttributes.AI_OPERATION_TYPE.value(),
                AiOperationType.EMBEDDING.value()
            )
            .lowCardinalityKeyValue(
                AiObservationAttributes.REQUEST_MODEL.value(),
                model
            )
            .highCardinalityKeyValue(
                AiObservationAttributes.USAGE_INPUT_TOKENS.value(),
                String.valueOf(tokenCount)
            )
            .observe(() -> {
                System.out.println("Executing embedding operation...");
            });
    }
}

// Usage
ObservationRegistry registry = // ... your registry
AIOperationMetricsRecorder recorder = new AIOperationMetricsRecorder(registry);

recorder.recordChatOperation("gpt-4", 150, 450, "stop");
recorder.recordEmbeddingOperation("text-embedding-3-small", 500, 1536);

Vector Store Operation Tracking

import org.springframework.ai.observation.conventions.*;
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;

/**
 * Track vector store operations.
 */
class VectorStoreObserver {
    private final ObservationRegistry observationRegistry;

    public VectorStoreObserver(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    public void recordSearch(String collection, String query, int topK,
                              String similarityMetric, int dimensions) {
        Observation.createNotStarted("vector.store.search", observationRegistry)
            .lowCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_SYSTEM.value(),
                VectorStoreProvider.PG_VECTOR.value()
            )
            .lowCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_OPERATION_NAME.value(),
                "search"
            )
            .lowCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_COLLECTION_NAME.value(),
                collection
            )
            .lowCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_SEARCH_SIMILARITY_METRIC.value(),
                similarityMetric
            )
            .highCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_VECTOR_QUERY_TOP_K.value(),
                String.valueOf(topK)
            )
            .highCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_VECTOR_DIMENSION_COUNT.value(),
                String.valueOf(dimensions)
            )
            .observe(() -> {
                System.out.println("Executing vector search...");
            });
    }

    public void recordAdd(String collection, int documentCount, int dimensions) {
        Observation.createNotStarted("vector.store.add", observationRegistry)
            .lowCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_SYSTEM.value(),
                VectorStoreProvider.CHROMA.value()
            )
            .lowCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_OPERATION_NAME.value(),
                "add"
            )
            .lowCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_COLLECTION_NAME.value(),
                collection
            )
            .highCardinalityKeyValue(
                VectorStoreObservationAttributes.DB_VECTOR_DIMENSION_COUNT.value(),
                String.valueOf(dimensions)
            )
            .highCardinalityKeyValue(
                "document_count",
                String.valueOf(documentCount)
            )
            .observe(() -> {
                System.out.println("Adding documents to vector store...");
            });
    }
}

// Usage
ObservationRegistry registry = // ... your registry
VectorStoreObserver observer = new VectorStoreObserver(registry);

observer.recordSearch(
    "knowledge_base",
    "What is Spring AI?",
    5,
    VectorStoreSimilarityMetric.COSINE.value(),
    1536
);

observer.recordAdd("knowledge_base", 100, 1536);

Comprehensive Monitoring Dashboard

import org.springframework.ai.observation.conventions.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Collect and aggregate AI operation metrics.
 */
class AIMetricsDashboard {
    private final Map<String, OperationMetrics> operationMetrics = new ConcurrentHashMap<>();

    public void recordOperation(String operationType, String provider,
                                 long durationMs, int inputTokens, int outputTokens) {
        String key = operationType + ":" + provider;

        operationMetrics.computeIfAbsent(key, k -> new OperationMetrics())
            .record(durationMs, inputTokens, outputTokens);
    }

    public DashboardSnapshot getSnapshot() {
        Map<String, OperationStats> stats = new ConcurrentHashMap<>();

        operationMetrics.forEach((key, metrics) -> {
            stats.put(key, metrics.getStats());
        });

        return new DashboardSnapshot(stats);
    }

    static class OperationMetrics {
        private final AtomicInteger requestCount = new AtomicInteger(0);
        private final AtomicLong totalDurationMs = new AtomicLong(0);
        private final AtomicLong totalInputTokens = new AtomicLong(0);
        private final AtomicLong totalOutputTokens = new AtomicLong(0);

        void record(long durationMs, int inputTokens, int outputTokens) {
            requestCount.incrementAndGet();
            totalDurationMs.addAndGet(durationMs);
            totalInputTokens.addAndGet(inputTokens);
            totalOutputTokens.addAndGet(outputTokens);
        }

        OperationStats getStats() {
            int count = requestCount.get();
            if (count == 0) return new OperationStats(0, 0, 0, 0, 0);

            return new OperationStats(
                count,
                totalDurationMs.get() / count,
                totalInputTokens.get(),
                totalOutputTokens.get(),
                (totalInputTokens.get() + totalOutputTokens.get()) / count
            );
        }
    }

    record OperationStats(
        int requestCount,
        long avgDurationMs,
        long totalInputTokens,
        long totalOutputTokens,
        long avgTokensPerRequest
    ) {}

    record DashboardSnapshot(Map<String, OperationStats> operationStats) {
        public String formatReport() {
            StringBuilder report = new StringBuilder("AI Operations Dashboard\n");
            report.append("=".repeat(60)).append("\n\n");

            operationStats.forEach((key, stats) -> {
                report.append(String.format("""
                    Operation: %s
                    - Requests: %,d
                    - Avg Duration: %,d ms
                    - Total Input Tokens: %,d
                    - Total Output Tokens: %,d
                    - Avg Tokens/Request: %,d

                    """,
                    key,
                    stats.requestCount(),
                    stats.avgDurationMs(),
                    stats.totalInputTokens(),
                    stats.totalOutputTokens(),
                    stats.avgTokensPerRequest()
                ));
            });

            return report.toString();
        }
    }
}

// Usage
AIMetricsDashboard dashboard = new AIMetricsDashboard();

// Record operations
dashboard.recordOperation(
    AiOperationType.CHAT.value(),
    AiProvider.OPENAI.value(),
    1200,  // 1.2 seconds
    150,   // input tokens
    450    // output tokens
);

dashboard.recordOperation(
    AiOperationType.EMBEDDING.value(),
    AiProvider.OPENAI.value(),
    300,   // 0.3 seconds
    500,   // input tokens
    0      // no output tokens for embedding
);

// Get snapshot
AIMetricsDashboard.DashboardSnapshot snapshot = dashboard.getSnapshot();
System.out.println(snapshot.formatReport());

Standardized Attribute Usage

import org.springframework.ai.observation.conventions.*;
import org.springframework.ai.observation.ObservabilityHelper;
import java.util.Map;
import java.util.List;

/**
 * Examples of using standardized observation attributes.
 */
class StandardizedObservability {

    public void logChatRequest(String model, double temperature, int maxTokens,
                                int topK, double topP) {
        Map<String, Object> attributes = Map.of(
            AiObservationAttributes.REQUEST_MODEL.value(), model,
            AiObservationAttributes.REQUEST_TEMPERATURE.value(), temperature,
            AiObservationAttributes.REQUEST_MAX_TOKENS.value(), maxTokens,
            AiObservationAttributes.REQUEST_TOP_K.value(), topK,
            AiObservationAttributes.REQUEST_TOP_P.value(), topP
        );

        String attributesStr = ObservabilityHelper.concatenateEntries(attributes);
        System.out.println("Chat Request Attributes: " + attributesStr);
    }

    public void logChatResponse(String model, List<String> finishReasons,
                                 int inputTokens, int outputTokens) {
        Map<String, Object> responseAttrs = Map.of(
            AiObservationAttributes.RESPONSE_MODEL.value(), model,
            AiObservationAttributes.USAGE_INPUT_TOKENS.value(), inputTokens,
            AiObservationAttributes.USAGE_OUTPUT_TOKENS.value(), outputTokens,
            AiObservationAttributes.USAGE_TOTAL_TOKENS.value(), inputTokens + outputTokens
        );

        String finishReasonsStr = ObservabilityHelper.concatenateStrings(finishReasons);

        System.out.println("Response Attributes: " +
            ObservabilityHelper.concatenateEntries(responseAttrs));
        System.out.println("Finish Reasons: " + finishReasonsStr);
    }

    public void logVectorStoreQuery(String collection, int topK, String metric,
                                     int dimensions) {
        Map<String, Object> queryAttrs = Map.of(
            VectorStoreObservationAttributes.DB_COLLECTION_NAME.value(), collection,
            VectorStoreObservationAttributes.DB_VECTOR_QUERY_TOP_K.value(), topK,
            VectorStoreObservationAttributes.DB_SEARCH_SIMILARITY_METRIC.value(), metric,
            VectorStoreObservationAttributes.DB_VECTOR_DIMENSION_COUNT.value(), dimensions
        );

        System.out.println("Vector Query: " +
            ObservabilityHelper.concatenateEntries(queryAttrs));
    }
}

// Usage
StandardizedObservability obs = new StandardizedObservability();

obs.logChatRequest("gpt-4", 0.7, 1000, 10, 0.9);
obs.logChatResponse("gpt-4", List.of("stop"), 150, 450);
obs.logVectorStoreQuery("knowledge_base", 5, "cosine", 1536);

Best Practices

  1. Use Standard Attributes: Always use the predefined attribute enums for consistency
  2. Low vs High Cardinality: Use low cardinality for dimensions (model, provider), high cardinality for values (tokens, durations)
  3. Track Token Usage: Always record input/output tokens for cost monitoring
  4. Monitor Operation Duration: Track duration to identify performance bottlenecks
  5. Standardize Naming: Use OpenTelemetry Gen AI semantic conventions for interoperability

Thread Safety and Performance

Thread Safety:

  • AiOperationMetadata: Immutable record, thread-safe
  • ObservabilityHelper: Thread-safe (static utility methods)
  • TracingAwareLoggingObservationHandler: Thread-safe if delegate handler is thread-safe
  • Observation enums: Thread-safe (enum constants)

Performance:

  • Observation recording: Low overhead (~microseconds)
  • String concatenation: O(n) where n is number of entries
  • Marker-based logging: Minimal overhead
  • Observation handlers: Depends on implementation (typically async)

Error Handling

Common Exceptions:

  • NullPointerException: If required observation parameters are null
  • IllegalArgumentException: If observation context is invalid
  • RuntimeException: Observation handler errors (depends on implementation)

Edge Cases:

// Empty metadata map
Map<String, Object> empty = Map.of();
String result = ObservabilityHelper.concatenateEntries(empty);  // Returns ""

// Null values in map
Map<String, Object> withNull = Map.of("key", "value");
// Note: Map.of() doesn't allow null values, will throw NullPointerException

// Empty list
List<String> empty = List.of();
String result = ObservabilityHelper.concatenateStrings(empty);  // Returns "[]"

// Observation with null context
try {
    Observation observation = Observation.createNotStarted("name", registry);
    observation.observe(() -> {
        // operation
    });
} catch (Exception e) {
    // Handle observation errors
}

OpenTelemetry Integration

Semantic Conventions: Spring AI Commons follows OpenTelemetry Gen AI semantic conventions for interoperability:

  • Attribute names use gen_ai.* prefix for AI operations
  • Metric names use gen_ai.client.* prefix
  • Vector store operations use db.* prefix
  • All enums provide .value() method for string representation

Attribute Cardinality:

  • Low Cardinality: Use for dimensions (model, provider, operation type)
  • High Cardinality: Use for values (tokens, IDs, specific queries)

Example:

observation
    .lowCardinalityKeyValue("gen_ai.system", "openai")           // Low: few distinct values
    .highCardinalityKeyValue("gen_ai.response.id", responseId);  // High: many distinct values

Best Practices

  1. Use Standard Attributes: Always use predefined attribute enums for consistency
  2. Low vs High Cardinality: Use low cardinality for dimensions, high for values
  3. Track Token Usage: Always record input/output tokens for cost monitoring
  4. Monitor Operation Duration: Track duration to identify performance bottlenecks
  5. Standardize Naming: Use OpenTelemetry Gen AI semantic conventions
  6. Handle Errors: Wrap observation code in try-catch to prevent failures
  7. Async Handlers: Use async observation handlers to minimize overhead

Configuration Example

Micrometer Configuration:

import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.aop.ObservedAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
class ObservabilityConfig {
    @Bean
    ObservationRegistry observationRegistry() {
        return ObservationRegistry.create();
    }

    @Bean
    ObservedAspect observedAspect(ObservationRegistry registry) {
        return new ObservedAspect(registry);
    }
}

Related Documentation

  • Evaluation - Evaluating AI responses
  • Tokenization - Token counting for usage tracking

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-ai--spring-ai-commons

docs

index.md

README.md

tile.json