ai-observability について
Spring AI 1.0+を使う開発者向けに、Micrometerのメトリクス、監査ログ、コスト帰属の実装方法をまとめたスキルです。GA版のAPI変更や、プロンプト漏えい防止、監査処理の非同期化に関する注意点も扱います。
主な機能
- 01Spring AI内蔵メトリクスの設定
- 02カスタムAIメトリクスの例
- 03プロンプトと応答の監査アドバイザー
- 04トークンコスト帰属の指針
- 05非同期監査保存の指針
活用シーン
- 01モデル呼び出しの遅延とトークン量の監視
- 02サニタイズ済みAI呼び出し監査データの保存
- 03モデル使用量に基づく運用コストの推定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rrezartprebreza/ spring-boot-skills --skill ai-observabilityインストール後、Claude Code で/ai-observabilityを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlexamples/bad-ai-audit-advisor.javagood-ai-audit-advisor.javatemplates/AiAuditAdvisor.javaAiMetrics.java詳細
AI オブザーバビリティ
依存関係
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
Spring AI 組み込みオブザーバビリティ
Spring AI 1.0+ には、Micrometer による計装機能が組み込まれています。
spring:
ai:
chat:
observations:
log-prompt: false # GA renamed include-prompt → log-prompt. true only in local dev (PII)
log-completion: false # GA renamed include-completion → log-completion
management:
metrics:
tags:
application: order-service
endpoints:
web:
exposure:
include: health,prometheus,metrics
自動生成されるメトリクス(OpenTelemetry GenAI セマンティック規約):
gen_ai.client.operation— モデル呼び出しのレイテンシー。プロバイダーとモデルがタグ付けされますgen_ai.client.token.usage— トークン数(入力/出力/合計)spring.ai.chat.client— ChatClient レベルの操作タイマー/スパン
カスタム AI メトリクス
@Component
@RequiredArgsConstructor
public class AiMetrics {
private final MeterRegistry meterRegistry;
private final Timer.Builder promptTimer = Timer.builder("ai.prompt.latency")
.description("LLM prompt latency");
private final Counter.Builder tokenCounter = Counter.builder("ai.tokens.used")
.description("Total tokens consumed");
public <T> T track(String operation, String model, Supplier<T> call) {
return Timer.builder("ai.prompt.latency")
.tag("operation", operation)
.tag("model", model)
.register(meterRegistry)
.record(call); // record(Supplier) — recordCallable throws checked Exception
}
public void recordTokens(String operation, String model, int inputTokens, int outputTokens) {
Counter.builder("ai.tokens.used")
.tag("operation", operation)
.tag("model", model)
.tag("type", "input")
.register(meterRegistry)
.increment(inputTokens);
Counter.builder("ai.tokens.used")
.tag("operation", operation)
.tag("model", model)
.tag("type", "output")
.register(meterRegistry)
.increment(outputTokens);
}
}
プロンプト/応答ログ記録アドバイザー
GA ではアドバイザー API 全体が置き換えられました。CallAroundAdvisor → CallAdvisor、AdvisedRequest →
ChatClientRequest、AdvisedResponse → ChatClientResponse、および Usage.getGenerationTokens() →
getCompletionTokens() です。エージェントは古い API を確実に生成しますが、1.0 ではコンパイルできません。
@Component
public class AiAuditAdvisor implements CallAdvisor {
private static final Logger log = LoggerFactory.getLogger(AiAuditAdvisor.class);
@Override
public ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
String requestId = UUID.randomUUID().toString();
long start = System.currentTimeMillis();
log.info("[AI-AUDIT] requestId={} promptLength={}",
requestId, request.prompt().getUserMessage().getText().length());
try {
ChatClientResponse response = chain.nextCall(request);
long latency = System.currentTimeMillis() - start;
ChatResponse chatResponse = response.chatResponse();
if (chatResponse != null && chatResponse.getMetadata() != null) {
Usage usage = chatResponse.getMetadata().getUsage();
log.info("[AI-AUDIT] requestId={} latencyMs={} inputTokens={} outputTokens={}",
requestId, latency,
usage.getPromptTokens(), usage.getCompletionTokens()); // GA: not getGenerationTokens()
}
return response;
} catch (Exception e) {
log.error("[AI-AUDIT] requestId={} FAILED after {}ms", requestId,
System.currentTimeMillis() - start, e);
throw e;
}
}
@Override
public String getName() { return "AiAuditAdvisor"; }
@Override
public int getOrder() { return Ordered.LOWEST_PRECEDENCE; }
}
コストの割り当て
- プロバイダーの料金は、適用開始日と通貨を含め、外部で管理する設定に保持します。
- 料金は、使用状況のメタデータで返されるプロバイダーモデル識別子と完全に一致するキーで管理します。
- 不明なモデルにはデフォルト料金を暗黙に適用せず、エラーにします。
- 料金変更後に過去のコストを再計算できるよう、トークン使用量の生データを保持します。
- 請求書にはプロバイダーの請求データを優先して使用します。アプリケーションによる推定値は、運用上の目安に限ります。
構造化 AI 監査ログ(DB)
audit advisor template はテレメトリをベストエフォートとして扱います。別の非同期 Bean の呼び出し時と、その内部での永続化時の両方で送信失敗を捕捉してください。executor が満杯または停止している場合、メソッドの実行前にタスクが拒否されることがあります。容量制限のある executor を使用し、監査タスクの拒否を監視してください。応答レイテンシーを監査ストレージから独立させる必要がある場合は、caller-runs を使用しないでください。生のプロバイダー例外メッセージではなく、サニタイズされたエラー種別を保存します。必須のコンプライアンス監査記録には、ベストエフォート設計ではなく、永続的な配信が必要です。
Spring のタスク実行と拒否ポリシーを参照してください。
@Entity
@Table(name = "ai_audit_log")
public class AiAuditLog {
@Id @GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private String operation;
private String model;
private int inputTokens;
private int outputTokens;
private double estimatedCostUsd;
private long latencyMs;
private boolean success;
private Instant createdAt;
}
// Async to avoid blocking main flow
@Async
public void saveAuditLog(AiAuditLog log) {
auditLogRepository.save(log);
}
application.yml — 完全なオブザーバビリティ
management:
endpoints:
web:
exposure:
include: health,prometheus,metrics,info
metrics:
distribution:
percentiles-histogram:
ai.prompt.latency: true # enables P50/P95/P99
tracing:
sampling:
probability: 1.0 # 100% trace sampling in dev, reduce in prod
logging:
level:
org.springframework.ai: DEBUG # enable in dev only
注意点
- エージェントが
CallAroundAdvisor/AdvisedRequestを実装する — GA で削除済みです。CallAdvisor/ChatClientRequestを使用してください - エージェントが
usage.getGenerationTokens()を呼び出す — GA でgetCompletionTokens()に名称が変更されました - エージェントが本番環境でプロンプト全体をログに記録する — PII を保護するため、
log-prompt: falseのままにしてください - エージェントが監査保存を非同期にしない — レイテンシーへの影響を避けるため、必ず
@Asyncを使用し、@Asyncメソッドは別の Bean に配置してください。this経由で呼び出すとプロキシを迂回し、同期実行されます - エージェントがトークン料金をハードコードする — 料金は変わるため、設定に切り出してください
- エージェントが失敗した呼び出しをメトリクスに含めない — エラータグを付けて、エラーを別途追跡してください