Claude API'yi Production'a Taşımak: Hatalar, Rate Limit, Maliyet Takibi, OpenTelemetry ve IChatClient Köprüsü
Claude API'yi production'a taşırken SDK'nın tipli exception'larını en özelden başlayarak ayırır, retry'ı tek katmanda (SDK'da) bırakır, spend cap'i rate limit'ten ayırır ve her cevabın usage'ından maliyet metriği üretirsin. SDK 13.8 hata hiyerarşisi, 529 ve 429 retry davranışı, Polly ile çift retry, cache'in ITPM'e etkisi, OpenTelemetry span'leri ve temperature'ı yok sayan IChatClient köprüsü.
Sürüm notu: Bu yazıdaki kodlar .NET 10, Anthropic C# SDK 13.8.0, Microsoft.Extensions.AI 11.0.0, OpenTelemetry .NET 1.21.0 ve Claude API version 2026-10-21 ile derlenmiş, birim testleri çalıştırılmıştır. Hata, retry, span ve IChatClient testleri SDK'nın gerçek client'ını ağa çıkmayan sahte bir HttpMessageHandler ile kullanıyor; spend cap hatası belgedeki örnekten, diğer hata gövdeleri belgedeki hata yapısından türetilmiştir. Limitler, fiyatlar ve davranışlar 15 Kasım 2026'da platform.claude.com belgelerinden ve SDK'nın GitHub deposundaki CHANGELOG'dan alınmıştır. Bu yazıda resmî belgelerden yararlanılmıştır.
Önce Tahmin Et
Kasım indirimlerinin ilk sabahı. Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için yazdığımız alışveriş asistanının trafiği on katına çıkıyor. Destek ekibinden Gül, müşterilerin "beklenmeyen bir hata oluştu" mesajı gördüğünü yazıyor. Log'larda 429 ve 529 var. Emre'nin kodunda tek bir catch (Exception) var; HttpClient'a bir de Polly retry'ı eklenmiş. Ay sonunda Selin faturada beklenmedik bir artış görüyor ve hangi özelliğin ne harcadığını kimse söyleyemiyor.
Tahminin ne? Bir 529 isteğinde aslında kaç HTTP isteği gidiyor? Hangi hatayı yeniden denemek anlamlı, hangisini denemek boşa? Ve bir cevabın maliyetini, Console'daki faturayı beklemeden nasıl görürsün? Serinin son yazısında bu soruları cevaplıyoruz: SDK'nın exception hiyerarşisi ve retry davranışı, rate limit'ler, istek başına maliyet, OpenTelemetry ve Claude'u Microsoft.Extensions.AI'a bağlayan IChatClient köprüsü.
SDK'nın Hata Hiyerarşisi
C# SDK sayfası HTTP durum koduna göre fırlatılan exception'ları bir tabloyla veriyor: 400 için AnthropicBadRequestException, 401 için AnthropicUnauthorizedException, 403, 404, 422 ve 429 için kendi tipleri, bütün 5xx'ler için Anthropic5xxException. Bütün 4xx'ler Anthropic4xxException'dan türüyor, hepsinin ortak tabanı AnthropicApiException. Bunların dışında üç tip daha var: 200 cevabından sonra stream içinde gelen hatalar için dördüncü yazıdaki AnthropicSseException, ağ hataları için AnthropicIOException ve cevabın beklenen alanı içermediği durumlar için AnthropicInvalidDataException.
SDK'nın CHANGELOG'una göre 13.5.0 sürümü (4 Kasım 2026) bu hiyerarşiyi genişletti: belirli durum kodları ve API hata kodları için ayrı exception'lar ve AnthropicApiException üzerinde RequestID ile WorkspaceID. 13.8.0'da reflection ile baktığımızda 529 için AnthropicOverloadedException'ın Anthropic5xxException'dan, aylık harcama tavanı için AnthropicEnforcedSpendLimitReachedException'ın ve ramp limit için AnthropicRampLimitExceededException'ın AnthropicRateLimitException'dan türediğini gördük. Bu ayrım önemli, çünkü üçü de aynı 429 ve rate_limit_error tipiyle geliyor ama davranışları tamamen farklı.
Bookstore'da exception'ları bir karara çeviren küçük bir sınıflandırıcı yazdık:
{
// En özel tipler önce: spend cap ve ramp limit exception'ları AnthropicRateLimitException'dan türüyor.
public static ClaudeFailure Classify(Exception exception) => exception switch
{
AnthropicEnforcedSpendLimitReachedException e => From(e, ClaudeFailureKind.SpendCapReached, retryLater: false),
AnthropicRateLimitException e => From(e, ClaudeFailureKind.RateLimited, retryLater: true),
AnthropicOverloadedException e => From(e, ClaudeFailureKind.Overloaded, retryLater: true),
Anthropic5xxException e => From(e, ClaudeFailureKind.ServerError, retryLater: true),
AnthropicUnauthorizedException or AnthropicForbiddenException => From((AnthropicApiException)exception, ClaudeFailureKind.Unauthorized, retryLater: false),
Anthropic4xxException e => From(e, ClaudeFailureKind.InvalidRequest, retryLater: false),
AnthropicSseException => new(ClaudeFailureKind.StreamInterrupted, RetryLater: true, null, null, null),
AnthropicIOException => new(ClaudeFailureKind.Network, RetryLater: true, null, null, null),
_ => new(ClaudeFailureKind.Unexpected, RetryLater: false, null, null, null),
};
private static ClaudeFailure From(AnthropicApiException e, ClaudeFailureKind kind, bool retryLater) =>
new(kind, retryLater, (int)e.StatusCode, e.RequestID, e.ErrorCode);
}
Sıra önemli: en özel tipler önce. Spend cap'i genel rate limit dalından önce yakalamazsak "biraz bekle, tekrar dene" diyen bir cevap üretiriz; oysa belgeye göre bu hata ayın ilk gününe kadar sürüyor.
Retry: SDK Neyi Yeniden Dener?
SDK sayfasına göre client varsayılan olarak 2 kez, kısa bir üstel bekleme ile yeniden deniyor. Yeniden denenenler: bağlantı hataları, 408, 409, 429 ve 5xx. Hatalar sayfası SDK'nın retry-after header'ına uyduğunu ekliyor. Varsayılan timeout 10 dakika; ikisi de MaxRetries ve Timeout ile client'ta ya da tek bir çağrı için WithOptions ile değiştirilebiliyor. Birinci yazıdan beri bu değerleri ClaudeOptions'tan okuyoruz.
Bu davranışı sahte handler ile test ettik. 529 iki kez gelip üçüncüde 200 dönünce SDK üç istek gönderiyor ve her isteğe kaçıncı deneme olduğunu yazan bir header ekliyor:
[Fact]
public async Task Overloaded_IsRetriedBySdkUntilItSucceeds()
{
FakeClaudeHandler handler = new(
Error((HttpStatusCode)529, Overloaded, "req_test_529"),
Error((HttpStatusCode)529, Overloaded, "req_test_529"),
new FakeResponse(Ok));
AnthropicClient client = new() { ApiKey = "fake-api-key-for-tests", HttpClient = new HttpClient(handler), MaxRetries = 2 };
Message message = await Send(client);
Assert.Equal("msg_test_ok", message.ID);
Assert.Equal(3, handler.Requests.Count);
Assert.Equal(["0", "1", "2"], handler.Requests.Select(r => r.Headers.GetValues("x-stainless-retry-count").Single()));
}
400 ise hiç yeniden denenmiyor; testte tek istek gidiyor. Asıl sürpriz spend cap'te:
[Fact]
public async Task SpendCap_IsStillRetriedBySdkWithoutRetryAfter()
{
FakeClaudeHandler handler = new(
Error(HttpStatusCode.TooManyRequests, SpendCap, "req_018EeWyXxfu5pfWkrYcMdjWG"),
Error(HttpStatusCode.TooManyRequests, SpendCap, "req_018EeWyXxfu5pfWkrYcMdjWG"),
Error(HttpStatusCode.TooManyRequests, SpendCap, "req_018EeWyXxfu5pfWkrYcMdjWG"));
AnthropicClient client = new() { ApiKey = "fake-api-key-for-tests", HttpClient = new HttpClient(handler), MaxRetries = 2 };
await Assert.ThrowsAsync<Anthropic.Models.AnthropicEnforcedSpendLimitReachedException>(() => Send(client));
// Her 429 yeniden denenir; bu, bir sonraki aya kadar hata vermeye devam eder.
Assert.Equal(3, handler.Requests.Count);
}
Belgeye göre spend cap 429'u retry-after header'ı taşımıyor ve SDK'nın otomatik retry'ları dahil her deneme ay sonuna kadar başarısız oluyor. Test bunu doğruluyor: MaxRetries = 2 ile üç istek gidiyor, sonra AnthropicEnforcedSpendLimitReachedException geliyor. Bu ek istekler ücretsiz ama boşa; uygulamanın kendi retry katmanı bu exception'ı görünce durmalı.
Polly ile çift retry tuzağı tam burada. SDK'nın kullandığı HttpClient'a External Service Integration ve Resilience serisinin üçüncü yazısındaki standard resilience handler'ı da eklersen iki katman çarpılır: handler her HTTP isteğini kendi kurallarıyla yeniden dener, SDK da handler'dan dönen her hatayı. Bir 529 fırtınasında tek bir kullanıcı isteği bir düzine HTTP isteğine dönüşebilir ve bu, rate limit'i daha da zorlar. Kural basit: retry tek bir katmanda olsun. SDK'nın retry'ı Claude'un header'larını tanıdığı için genelde doğru yer; Polly'yi ancak SDK'nın retry'ını MaxRetries = 0 ile kapatıp kullan. Circuit breaker ise ayrı bir soru: SDK'da yok, ve Claude uzun süre 529 dönüyorsa çağrıyı hiç yapmamak için uygulama katmanında anlamlı.
Rate Limit'ler ve Spend Cap
Rate limits sayfasına göre limitler organizasyon düzeyinde ve kullanım tier'ına göre. 26 Haziran 2026'dan beri üç tier var: Start, Build ve Scale; her birinin aylık bir harcama tavanı var (500, 1.000 ve 200.000 USD). Messages API için limitler model sınıfı başına RPM (dakikadaki istek sayısı), ITPM (dakikadaki input token) ve OTPM (dakikadaki output token) olarak ölçülüyor ve token bucket (sürekli dolan bir kapasite havuzu) algoritmasıyla uygulanıyor; kapasite sabit aralıklarla sıfırlanmıyor, sürekli doluyor. Dakikada 60 istek, saniyede 1 istek olarak uygulanabiliyor; kısa patlamalar limiti aşabiliyor.
Yedinci yazıyla bağlantılı önemli bir ayrıntı: ITPM yalnız cache'lenmemiş input token'ları sayıyor. cache_read_input_tokens limite girmiyor; yani iyi çalışan bir prompt cache hem maliyeti hem rate limit baskısını düşürüyor. Ramp limit ayrı bir mekanizma: kullanım hızla artınca, ITPM ve OTPM'nin altında olsan bile 429 alabiliyorsun. Belge toplu işleri düşük hızla başlatıp yükü yaymayı öneriyor; sekizinci yazıdaki Message Batches API ramp limit'e tabi değil.
Her cevapta anthropic-ratelimit-requests-remaining, anthropic-ratelimit-tokens-remaining ve ilgili -reset header'ları geliyor. SDK bunları exception'a koymuyor; okumak için belgedeki gibi client.WithRawResponse gerekiyor. Workspace'ler için organizasyon limitinin altında kendi limitlerini de koyabiliyorsun; destek botu ile gece batch işini farklı workspace'lere ayırmak, birinin diğerini aç bırakmasını engelliyor. Kendi koyduğun harcama limitine ulaşınca ise API 429 değil, 400 dönüyor.
Hataları API Cevabına Çevirmek
Service'ler exception'ı yakalamıyorsa ASP.NET Core'un exception handler'ı devreye giriyor. Claude'a özgü bir IExceptionHandler yazdık:
// Bir service'ten kaçan SDK exception'larını ProblemDetails'e çevirir; SDK o ana kadar zaten yeniden denemiştir.
public sealed class ClaudeExceptionHandler(IProblemDetailsService problemDetails, ILogger<ClaudeExceptionHandler> logger)
: IExceptionHandler
{
public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception exception, CancellationToken cancellationToken)
{
if (exception is not AnthropicException)
{
return false;
}
ClaudeFailure failure = ClaudeFailureClassifier.Classify(exception);
// Anthropic desteğinin istediği şey request id; her hatada log'la.
logger.LogError(exception, "Claude call failed: kind={Kind} status={Status} requestId={RequestId} errorCode={ErrorCode}",
failure.Kind, failure.StatusCode, failure.RequestId, failure.ErrorCode);
context.Response.StatusCode = failure.RetryLater ? StatusCodes.Status503ServiceUnavailable : StatusCodes.Status502BadGateway;
if (failure.RetryLater)
{
context.Response.Headers.RetryAfter = "30";
}
return await problemDetails.TryWriteAsync(new ProblemDetailsContext
{
HttpContext = context,
Exception = exception,
ProblemDetails =
{
Title = failure.RetryLater ? "The assistant is busy. Try again shortly." : "The assistant is not available.",
Extensions = { ["kind"] = failure.Kind.ToString() },
},
});
}
}
Geçici hatalar (rate limit, overload, 5xx, ağ) 503 ve Retry-After ile, kalıcı olanlar 502 ile dönüyor. Spend cap kalıcı sayılıyor; müşteri tekrar denediğinde değişen bir şey yok, ekibin uyarılması gerekiyor. Log satırında RequestID var; belgeye göre her cevap bir request-id header'ı taşıyor ve Anthropic desteği bir isteği bu id ile buluyor. Spend cap testinde id ve hata kodu exception'dan doğru okunuyor:
[Fact]
public async Task SpendCap_IsItsOwnExceptionAndIsNotWorthRetrying()
{
FakeClaudeHandler handler = new(Error(HttpStatusCode.TooManyRequests, SpendCap, "req_018EeWyXxfu5pfWkrYcMdjWG"));
Exception error = await Assert.ThrowsAnyAsync<AnthropicRateLimitException>(() => Send(handler.CreateClient()));
ClaudeFailure failure = ClaudeFailureClassifier.Classify(error);
Assert.IsType<Anthropic.Models.AnthropicEnforcedSpendLimitReachedException>(error);
Assert.Equal(new ClaudeFailure(ClaudeFailureKind.SpendCapReached, false, 429, "req_018EeWyXxfu5pfWkrYcMdjWG",
"enforced_spend_limit_reached"), failure);
}
Maliyet Takibi
Faturayı ay sonunda görmek yetmiyor; hangi özelliğin ne harcadığını istek düzeyinde bilmek gerekiyor. Üçüncü yazıdaki ClaudePricing her cevabın usage'ından tahmini maliyeti hesaplıyordu. Bunu bir .NET Meter'a bağladık:
// SDK'nın span'lerinin yanında özellik başına token ve maliyet sayaçları; fatura Console'daki Cost sayfasıyla karşılaştırılır.
public sealed class ClaudeUsageMeter : IDisposable
{
public const string MeterName = "Bookstore.Claude";
private readonly Meter _meter;
private readonly Counter<long> _tokens;
private readonly Counter<double> _cost;
public ClaudeUsageMeter(IMeterFactory meterFactory)
{
_meter = meterFactory.Create(MeterName);
_tokens = _meter.CreateCounter<long>("bookstore.claude.tokens", unit: "{token}");
_cost = _meter.CreateCounter<double>("bookstore.claude.cost", unit: "USD");
}
public decimal Record(string feature, string model, TokenUsage usage)
{
KeyValuePair<string, object?> featureTag = new("feature", feature);
KeyValuePair<string, object?> modelTag = new("model", model);
_tokens.Add(usage.Input, featureTag, modelTag, new("type", "input"));
_tokens.Add(usage.CacheRead, featureTag, modelTag, new("type", "cache_read"));
_tokens.Add(usage.CacheWrite, featureTag, modelTag, new("type", "cache_write"));
_tokens.Add(usage.Output, featureTag, modelTag, new("type", "output"));
decimal usd = ClaudePricing.KnownModels.Contains(model) ? ClaudePricing.EstimateUsd(model, usage) : 0m;
_cost.Add((double)usd, featureTag, modelTag);
return usd;
}
public void Dispose() => _meter.Dispose();
}
Destek servisi her cevaptan sonra Record("support", ...) çağırıyor ve tahmini maliyeti log satırına da yazıyor. Sayaçlar özellik ve model etiketleriyle OpenTelemetry üzerinden dışarı çıkıyor; "bu hafta destek botu ne harcadı" sorusu bir dashboard sorgusuna dönüşüyor. Bu bir tahmin: fiyat tablosu bizim kodumuzda, sürüm değişince güncellenmesi gerekiyor.
Gerçek rakamlar Anthropic'te. Console'daki Usage ve Cost sayfaları bir yol. Programatik yol, 29 Ekim 2026'da beta olarak çıkan Usage and Cost Analytics API: analytics-usage-cost-2026-08-24 beta header'ıyla /v1/organizations/analytics/usage ve /cost endpoint'leri token ve USD maliyetini model, workspace ve API key bazında veriyor. Bu API normal bir API key ile değil, Admin API key ile çağrılıyor; belge bunu ayrı bir servise, bir secret manager'da ve süreli olarak vermeyi öneriyor. Verinin gecikmesi de var: belgeye göre veri tipik olarak 4 saat içinde güncelleniyor, 24 saate kadar sürebiliyor. Kendi sayaçların anlık, Anthropic'in rakamları kesin; ikisini düzenli karşılaştır.
OpenTelemetry
SDK sayfasına göre SDK'nın kaynaklarını AddSource("com.anthropic.sdk.csharp*") ile tracer provider'a eklediğinde her istek için bir span üretiliyor. Program.cs'deki kayıt:
builder.Services.AddClaude(builder.Configuration);
builder.Services.AddExceptionHandler<ClaudeExceptionHandler>();
builder.Services.AddSingleton<ClaudeUsageMeter>();
builder.Services.AddClaudeChatClient();
// Span'ler ve metrikler yalnız bir OTLP collector yapılandırılmışsa oraya gider (OTEL_EXPORTER_OTLP_ENDPOINT).
bool exportOtlp = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddSource("com.anthropic.sdk.csharp*").AddSource("Bookstore.ChatClient").AddAspNetCoreInstrumentation();
if (exportOtlp)
{
tracing.AddOtlpExporter();
}
})
.WithMetrics(metrics =>
{
metrics.AddMeter(ClaudeUsageMeter.MeterName);
if (exportOtlp)
{
metrics.AddOtlpExporter();
}
});
Sahte handler'la bir istek gönderip bir ActivityListener ile yakaladığımız span şöyle:
span source=com.anthropic.sdk.csharp name=anthropic.messages.create kind=Client status=Ok
tags: gen_ai.provider.name=anthropic, server.address=api.anthropic.com, server.port=443, http.request.method=POST, http.route=/v1/messages, gen_ai.operation.name=chat, gen_ai.request.model=claude-haiku-5-5, gen_ai.request.max_tokens=10, url.scheme=https, url.path=/v1/messages, url.full=https://api.anthropic.com/v1/messages, anthropic.request.id=req_test_ok, http.response.status_code=200, gen_ai.usage.input_tokens=5, gen_ai.response.id=msg_ok, gen_ai.response.model=claude-haiku-5-5, gen_ai.usage.output_tokens=1, anthropic.message.stop_reason=end_turn, http.request.resend_count=1
Span adı anthropic.messages.create; etiketler .NET ile Yapay Zeka serisinin sekizinci yazısındaki GenAI semantic conventions'a uyuyor (gen_ai.request.model, gen_ai.usage.input_tokens, gen_ai.response.id), yanında anthropic.request.id ve http.request.resend_count var. Retry'lar ayrı span değil, tek span'in resend_count değeri. Testimiz SDK'nın istekle birlikte traceparent header'ını da gönderdiğini doğruluyor; belgeye göre bu, API tarafındaki span'lerle (14 Ekim 2026'dan beri beta) aynı trace'i birleştirmeyi mümkün kılıyor.
Belgenin bir varsayılanı güvenlik açısından önemli: SDK span'leri varsayılan olarak yalnız metadata taşıyor. Prompt ve cevap metni ancak content mode açıkça content yapılırsa span'e giriyor. Müşteri mesajlarını observability platformuna taşımadan önce o platformun erişim ve saklama ayarlarını düşün.
IChatClient Köprüsü
SDK, Microsoft.Extensions.AI'ın IChatClient arayüzünü uyguluyor. Böylece Claude, .NET ile Yapay Zeka serisinde kurduğumuz pipeline'a girebiliyor: function invocation, caching, telemetry middleware'leri ve MCP (Model Context Protocol; modele tool ve veri kaynaklarını standart bir yolla bağlayan protokol) C# SDK'sının tool'ları. Kayıt:
public static class ChatClientRegistration
{
// Sağlayıcıdan bağımsız özellikler IChatClient kullanır; Claude'a özgü olanlar (cache breakpoint'leri, effort, skill'ler, batch'ler)
// doğrudan IAnthropicClient kullanmaya devam eder.
public static IServiceCollection AddClaudeChatClient(this IServiceCollection services)
{
services.AddChatClient(sp =>
{
ClaudeOptions options = sp.GetRequiredService<IOptions<ClaudeOptions>>().Value;
return sp.GetRequiredService<IAnthropicClient>().AsIChatClient(options.Model);
})
.UseFunctionInvocation()
.UseOpenTelemetry(sourceName: "Bookstore.ChatClient");
return services;
}
}
Ne zaman hangisi? Sağlayıcıdan bağımsız kalmak istediğin, sade bir sohbet ya da tool çağrısı olan özelliklerde IChatClient. Claude'a özgü bir şey gerektiğinde native SDK: yedinci yazıdaki cache breakpoint'leri, effort ve adaptive thinking, structured outputs'un output_config.format'ı, batch'ler, Files API, skill'ler, MCP connector ve Managed Agents. Bookstore ikisini yan yana kullanıyor; DI (dependency injection; bağımlılıkların dışarıdan verilmesi) container'ında iki ayrı kayıt var.
Köprünün bir davranışı sessiz:
[Fact]
public async Task ChatClient_IgnoresTemperatureForThisApiVersion()
{
FakeClaudeHandler handler = new(Ok);
IChatClient chat = handler.CreateClient().AsIChatClient("claude-haiku-5-5");
ChatResponse response = await chat.GetResponseAsync("Where is order 1042?", new ChatOptions { Temperature = 0.2f, MaxOutputTokens = 50 });
Assert.Equal("Dune ships tomorrow.", response.Text);
JsonElement body = JsonDocument.Parse(handler.RequestBodies[0]).RootElement;
Assert.False(body.TryGetProperty("temperature", out _));
Assert.Equal(50, body.GetProperty("max_tokens").GetInt32());
}
ChatOptions.Temperature hatasız kabul ediliyor ama istekte temperature yok. Birinci yazıda gördüğümüz gibi API version 2026-10-21 sampling parametrelerini kaldırdı; SDK 13 adapter'ı bu yüzden onları yok sayıyor. Başka bir sağlayıcıdan taşınan, temperature'a güvenen kod hata almadan farklı davranır. Bunu bilerek kullan.
Bütün testler:
Passed! - Failed: 0, Passed: 58, Skipped: 0, Total: 58, Duration: 2 s - Bookstore.Api.Tests.dll (net10.0)
Üretimde Dikkat
Seriyi bir kontrol listesiyle kapatalım. Client singleton, key secret store'da (01). Her cevapta stop_reason ve usage okunuyor (02). Model ve effort bir değerlendirme setiyle seçilmiş (03). Stream'de CancellationToken SDK'ya geçiyor (04). Tool'larda yetki kodda (05). Structured output'tan sonra iş kuralı kontrolü var (06). Prompt cache hit oranı izleniyor (07). Toplu işler batch'te, dosyalar bir kez yükleniyor (08). Skill ve MCP token'ları en az yetkiyle (09). Uzun işlerde agent versiyonu sabit ve bütçeli (10). Ve bu yazıdan: tek katmanlı retry, spend cap için ayrı alarm, istek başına maliyet metriği, request id'li log'lar, metadata-only span'ler.
Sık Düşülen Tuzaklar
Her exception'ı tek catch ile yakalamak. Rate limit, spend cap ve bozuk istek aynı mesajla kullanıcıya gider ve hiçbiri doğru ele alınmaz.
Retry'ları sınırsız ya da çift katmanlı bırakmak. SDK zaten yeniden deniyor; üstüne konan her katman istek sayısını çarpar.
Spend cap'i rate limit sanmak. İkisi de 429; birini beklemek çözer, diğerini çözmez.
input_tokens'a bakarak rate limit hesabı yapmak. Cache'ten okunan token'lar ITPM'e girmiyor; asıl sayı farklı.
Prompt'ları span'e koymak. Content mode'u açmadan önce observability platformunun kişisel veri politikasını kontrol et.
IChatClient ile temperature ayarladığını sanmak. Bu API version'da etkisi yok.
Küçük Bir Deneme
★☆☆ ClaudeFailureClassifier'a AnthropicRampLimitExceededException için ayrı bir dal ekle. Rate limits sayfasındaki ramp limit açıklamasına göre bu durumda ne önerilir, bunu kod yorumuna değil teste yaz.
★★☆ Destek servisinin isteğini client.WithRawResponse.Messages.Create ile gönder, anthropic-ratelimit-tokens-remaining header'ını oku ve bir gauge metriğine yaz. Sahte handler'a header ekleyerek test et.
★★★ IChatClient pipeline'ına bir middleware ekle: her cevabın UsageDetails'ini okuyup ClaudeUsageMeter.Record çağırsın. Böylece native SDK ve IChatClient üzerinden giden çağrılar aynı maliyet metriğinde toplansın. İkisinin aynı sayıyı verdiğini bir testle doğrula.
Seriyi burada tamamlıyoruz. On bir yazıda Bookstore.Api'ye Claude'u ilk istekten production'a kadar bağladık: Messages API, model ve effort, streaming, tool use, structured outputs, prompt caching, batch ve Files API, skill'ler ve MCP connector, Managed Agents ve bugünkü hata, maliyet ve gözlem katmanı. Kod her yazının sonunda ayrı bir etiketle repo'da. API hızlı değişiyor; bu serideki her bilgi 15 Kasım 2026 tarihli belgelere dayanıyor. Yeni bir sürüm çıktığında önce release notes'u ve SDK'nın CHANGELOG'unu oku, sonra testleri çalıştır.