Mert Özen Her Satırda Daha İleri
Yapay Zeka .NET ile Yapay Zeka — #13

ASP.NET Core'da Agent Host Etmek: AddAIAgent, OpenAI Uyumlu Endpoint, AG-UI ve A2A

Mert Özen 8 Kas 2026 11 dk 4 görüntülenme
ASP.NET Core'da Agent Host Etmek: AddAIAgent, OpenAI Uyumlu Endpoint, AG-UI ve A2A

Bir agent'ı AddAIAgent ile DI'a kaydedip MapOpenAIChatCompletions, MapAGUIServer ve MapA2AJsonRpc ile üç protocol'den açarsın; agent bir kez yazılır, adapter'lar yalnız biçimi çevirir. Gerçek çağrılarla çoklu choice dönen OpenAI cevabını, reasoning'i tarayıcıya taşıyan AG-UI akışını, A2A agent card'ını ve claim tabanlı session izolasyonuyla rate limiting'i görüyoruz.

Sürüm notu: Bu yazıdaki kodlar .NET 10 ile derlenip denenmiştir; Microsoft.Agents.AI 1.28.0, Microsoft.Agents.AI.Hosting 1.28.0-preview.261106.1, Microsoft.Agents.AI.Hosting.OpenAI 1.28.0-alpha.261106.1, Microsoft.Agents.AI.Hosting.AGUI.AspNetCore 1.28.0-preview.261106.1, Microsoft.Agents.AI.Hosting.A2A.AspNetCore 1.28.0-preview.261106.1, OllamaSharp 5.5.2 ve Ollama 0.34.4 (qwen3:4b) kullanılmıştır.

Önce Tahmin Et

Bookstore asistanı artık bir agent. Üç ekip onu kullanmak istiyor. Web ekibi tarayıcıdaki sohbet penceresine bağlamak istiyor. Raporlama ekibi elindeki OpenAI SDK'sıyla (Software Development Kit) çağırmak istiyor. Lojistik ekibinin kendi agent'ı da sipariş durumunu bu agent'a sormak istiyor. Üç ayrı endpoint mi yazacaksın, üç ayrı agent mı?

Bir de şunu düşün: Agent-User Interaction Protocol (AG-UI) endpoint'inde istek gövdesinde bir threadId var. Ada, Alan'ın thread id'sini tahmin edip gönderirse Alan'ın sohbetine devam edebilir mi?

Cevap: tek agent, üç adapter. İkinci sorunun cevabı ise senin ayarlarına bağlı. Varsayılan ayarlarla güvenli değil.

Hosting Katmanı: AIAgent'ı DI'a Koymak

Bu serinin "Microsoft Agent Framework'e Giriş" yazısında agent'ı AsAIAgent ile elle oluşturmuştuk. Bir ASP.NET Core Web API (Application Programming Interface) projesinde agent, diğer service'ler gibi DI (Dependency Injection) container'da yaşamalı. Microsoft.Agents.AI.Hosting paketi bunun için AddAIAgent'ı getiriyor. Protocol adapter'ları da ayrı paketlerde:

<ItemGroup>
  <PackageReference Include="Microsoft.Agents.AI" Version="1.28.0" />
  <PackageReference Include="Microsoft.Agents.AI.Hosting" Version="1.28.0-preview.261106.1" />
  <PackageReference Include="Microsoft.Agents.AI.Hosting.OpenAI" Version="1.28.0-alpha.261106.1" />
  <PackageReference Include="Microsoft.Agents.AI.Hosting.AGUI.AspNetCore" Version="1.28.0-preview.261106.1" />
  <PackageReference Include="Microsoft.Agents.AI.Hosting.A2A.AspNetCore" Version="1.28.0-preview.261106.1" />
  <PackageReference Include="OllamaSharp" Version="5.5.2" />
</ItemGroup>

Sürüm etiketlerine dikkat et. Çekirdek Microsoft.Agents.AI stabil 1.28.0, ama hosting paketlerinin hepsi preview, OpenAI adapter'ı ise alpha. Yani agent'ın kendisi GA (General Availability), onu dışarı açan katman henüz değil. Bu yazıdaki API adları da bu yüzden son birkaç sürümde değişti: dokümanlardaki bazı örnekler hâlâ MapAGUI derken 1.28'deki method MapAGUIServer. Kodu yazmadan önce kullandığın sürümün XML (Extensible Markup Language) dokümantasyonuna bakmak en güvenli yol.

13-agent-protocol-adapters-tr.png

Tek Agent, Üç Adapter: Program.cs

using System.Security.Claims;
using System.Threading.RateLimiting;
using A2A;
using A2A.AspNetCore;
using Bookstore.Assistant;
using Microsoft.Agents.AI.Hosting;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.AI;
using OllamaSharp;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<IChatClient>(new OllamaApiClient(
    new HttpClient { BaseAddress = new Uri("http://localhost:11434"), Timeout = TimeSpan.FromMinutes(10) },
    "qwen3:4b"));
builder.Services.AddSingleton<OrderTools>();

// Tek agent: DI'a kaydedilir, tool ve session store builder ile eklenir
IHostedAgentBuilder assistant = builder
    .AddAIAgent("bookstore-assistant",
        instructions: "You are the Bookstore assistant. Use tools for order questions. Answer in one short sentence.")
    .WithAITool(sp => AIFunctionFactory.Create(sp.GetRequiredService<OrderTools>().GetOrderStatus))
    .WithInMemorySessionStore();

// Üç protocol adapter'ı
builder.Services.AddOpenAIChatCompletions();
builder.Services.AddAGUIServer();
assistant.AddA2AServer();

// Kimlik, kullanıcı başına session izolasyonu ve rate limiting
builder.Services.AddAuthentication("DemoApiKey")
    .AddScheme<AuthenticationSchemeOptions, DemoApiKeyHandler>("DemoApiKey", null);
builder.Services.AddAuthorization();
builder.Services.AddHttpContextAccessor();
builder.Services.UseClaimsBasedAgentIsolation();
builder.Services.AddRateLimiter(o =>
{
    o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    o.AddPolicy("agent", http => RateLimitPartition.GetFixedWindowLimiter(
        http.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous",
        _ => new FixedWindowRateLimiterOptions { PermitLimit = 3, Window = TimeSpan.FromMinutes(1) }));
});

WebApplication app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();

app.MapOpenAIChatCompletions(assistant).RequireAuthorization().RequireRateLimiting("agent");
app.MapAGUIServer(assistant, "/agui").RequireAuthorization().RequireRateLimiting("agent");
app.MapA2AJsonRpc(assistant, "/a2a").RequireAuthorization().RequireRateLimiting("agent");

// Agent card keşif içindir ve herkese açıktır; endpoint'in kendisi değil
app.MapWellKnownAgentCard(new AgentCard
{
    Name = "bookstore-assistant",
    Description = "Answers Bookstore order status questions.",
    Version = "1.0.0",
    SupportedInterfaces = [new AgentInterface { Url = "http://localhost:19662/a2a", ProtocolBinding = "JSONRPC" }],
    DefaultInputModes = ["text"],
    DefaultOutputModes = ["text"],
    Skills = [new AgentSkill { Id = "order-status", Name = "Order status", Description = "Looks up the shipping status of an order." }],
});

app.Run();

AddAIAgent bir IHostedAgentBuilder döndürüyor. Agent'ın adı ("bookstore-assistant") aynı zamanda keyed service anahtarı. IChatClient'ı DI'dan buluyor, tool'u WithAITool ile ekliyoruz. Tool factory'si IServiceProvider alıyor, yani tool'un arkasındaki service DI'dan geliyor. WithInMemorySessionStore konuşmaları bellek içinde saklıyor. Production'da bunun yerine kalıcı bir AgentSessionStore gerekir; bu serinin bir önceki yazısında session'ı JSON'a (JavaScript Object Notation) çevirip saklamayı görmüştük.

Sonra aynı builder'ı üç Map... çağrısına veriyoruz. Agent bir kez tanımlanıyor; adapter'lar yalnız gelen isteği AIAgent'ın anlayacağı mesajlara, cevabı da kendi protocol'lerinin biçimine çeviriyor.

OpenAI Uyumlu Endpoint

Raporlama ekibinin OpenAI SDK'sı ya da herhangi bir Chat Completions istemcisi bu endpoint'e bağlanabilir. Route agent adını içeriyor:

curl -s localhost:19662/bookstore-assistant/v1/chat/completions -H 'X-Api-Key: demo-key-ada' \
  -H 'Content-Type: application/json' \
  -d '{"model":"bookstore-assistant","messages":[{"role":"user","content":"Where is my order 1042?"}]}'
{
  "id": "chatcmpl-Z1L8U2CeLMlz8ZaSRFb13d9s82cHx",
  "object": "chat.completion",
  "created": 1794122805,
  "model": "bookstore-assistant",
  "choices": [
    {
      "index": 0,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "tool_calls": [
          {
            "id": "8f3f7a4e",
            "type": "function",
            "function": {
              "name": "GetOrderStatus",
              "arguments": "{\"orderId\":1042}"
            }
          }
        ]
      }
    },
    {
      "index": 1,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "Your order 1042 is shipped on 2026-11-06 with 2 items and tracking number BKS-1042."
      }
    }
  ],
  "usage": {
    "completion_tokens": 360,
    "prompt_tokens": 531,
    "total_tokens": 891,
    ...
  }
}

Cevap doğru, ama biçim dikkat istiyor. Agent'ın ürettiği iki mesaj iki ayrı choice olarak dönmüş: choices[0] sunucuda zaten çalıştırılmış tool çağrısı, choices[1] asıl cevap. Standart bir OpenAI istemcisi genelde yalnız choices[0]'a bakar ve tool_calls görünce tool'u kendisinin çalıştırması gerektiğini düşünür. Bu paket alpha; istemci tarafını bu davranışa göre test etmeden raporlama ekibine "OpenAI uyumlu" diye verme. model alanında da modelin değil agent'ın adı var. Arkadaki qwen3:4b'yi istemci hiç görmüyor.

AG-UI: Web Arayüzü İçin Event Akışı

AG-UI, agent ile kullanıcı arayüzü arasında SSE (Server-Sent Events) üzerinden akan bir event protocol'ü. Microsoft Learn'deki AG-UI sayfası protocol'ün hâlâ geliştirme aşamasında olduğunu ve değişebileceğini yazıyor. İstek bir threadId, bir runId ve mesajlardan oluşuyor:

curl -s -N localhost:19662/agui -H 'X-Api-Key: demo-key-ada' -H 'Content-Type: application/json' \
  -d '{"threadId":"thread-ada-1","runId":"run-1","messages":[{"id":"m1","role":"user","content":"Where is my order 1042?"}]}'

Cevap 382 event'lik bir akış. Aşağıda event tiplerinin sırasını gösteren satırlar var; rawEvent alanlarını kısalttık:

data: {"type":"RUN_STARTED","threadId":"thread-ada-1","runId":"run-1"}
data: {"type":"REASONING_START","messageId":"msg_mi9vIdNSC6SG1CqEB08lOeZb"}
data: {"type":"REASONING_MESSAGE_CONTENT","messageId":"msg_mi9vIdNSC6SG1CqEB08lOeZb","delta":"Okay"}
data: {"type":"REASONING_MESSAGE_CONTENT","messageId":"msg_mi9vIdNSC6SG1CqEB08lOeZb","delta":","}
data: {"type":"REASONING_MESSAGE_CONTENT","messageId":"msg_mi9vIdNSC6SG1CqEB08lOeZb","delta":" the"}
...
data: {"type":"TOOL_CALL_START","toolCallId":"ab5670ff","toolCallName":"GetOrderStatus","parentMessageId":"d756ec30f89245839f91b8fd56fc74ce","rawEvent":{...}}
data: {"type":"TOOL_CALL_ARGS","toolCallId":"ab5670ff","delta":"{\"orderId\":1042}","rawEvent":{...}}
data: {"type":"TOOL_CALL_END","toolCallId":"ab5670ff","rawEvent":{...}}
data: {"type":"TOOL_CALL_RESULT","messageId":"ab5670ff","toolCallId":"ab5670ff","content":"\"Shipped on 2026-11-06, 2 items, tracking BKS-1042.\"","rawEvent":{...}}
...
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"729f7287262e4486b7ac6b0de5824eb5","delta":"Your","rawEvent":{...}}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"729f7287262e4486b7ac6b0de5824eb5","delta":" order","rawEvent":{...}}
...
data: {"type":"TEXT_MESSAGE_END","messageId":"729f7287262e4486b7ac6b0de5824eb5"}
data: {"type":"RUN_FINISHED","threadId":"thread-ada-1","runId":"run-1","outcome":{"type":"success"},"usage":[{"model":"qwen3:4b","inputTokens":523,"outputTokens":389,"totalTokens":912}]}

Web arayüzü için bu, Chat Completions'tan çok daha zengin bir akış. Tool çağrısı başladığında "sipariş sorgulanıyor" göstergesi açılabilir, sonuç geldiğinde kapanabilir, metin token token yazılabilir. Akışın sayıları ise başka bir şey söylüyor. 382 event'in 325'i REASONING_MESSAGE_CONTENT: modelin 1199 karakterlik iç düşüncesi kelime kelime tarayıcıya gitti. Cevap metni ise yalnız 35 event. Ayrıca her TEXT_MESSAGE_CONTENT event'i, tek karakterlik delta için bile, içinde modelId ve createdAt olan bir rawEvent nesnesi taşıyor. Tek cümlelik bir cevap için akışın toplam boyutu yaklaşık 57 KB. Tool onayı gerektiren senaryolarda AG-UI'nin interrupt mekanizması devreye girer. Angular tarafındaki ayrıntılar ilerideki Angular ile AI (Artificial Intelligence) Arayüzleri serisinin konusu.

A2A: Agent'tan Agent'a

A2A (Agent2Agent) protocol'ü bir agent'ın başka bir agent'ı keşfedip ona iş vermesi için tasarlandı. Keşif, iyi bilinen bir adresteki agent card ile yapılıyor:

curl -s localhost:19662/.well-known/agent-card.json
{
    "name": "bookstore-assistant",
    "description": "Answers Bookstore order status questions.",
    "version": "1.0.0",
    "supportedInterfaces": [
        {
            "url": "http://localhost:19662/a2a",
            "protocolBinding": "JSONRPC",
            "protocolVersion": "1.0"
        }
    ],
    "capabilities": {},
    "skills": [
        {
            "id": "order-status",
            "name": "Order status",
            "description": "Looks up the shipping status of an order.",
            "tags": []
        }
    ],
    "defaultInputModes": [
        "text"
    ],
    "defaultOutputModes": [
        "text"
    ]
}

Lojistik ekibinin agent'ı bu kartı okuyup /a2a adresine JSON-RPC (JSON Remote Procedure Call) isteği atıyor. A2A 1.0'da method adı SendMessage, rol değerleri ROLE_USER ve ROLE_AGENT:

curl -s localhost:19662/a2a -H 'X-Api-Key: demo-key-ada' -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{"messageId":"m1","role":"ROLE_USER","parts":[{"text":"Where is my order 1042?"}]}}}'
{"jsonrpc":"2.0","id":1,"result":{"message":{"role":"ROLE_AGENT","parts":[{"text":""},{"text":"Your order 1042 is shipped with tracking number BKS-1042, delivered on 2026-11-06 with 2 items."}],"messageId":"c2f1e32cb05747bcadb9c427e0b8af71","contextId":"9978bdae5f8c4e0d98f491405ba00cb6"}}}

Cevapta iki şey göze çarpıyor. İlk part boş bir metin; tool çağrısı yapan ilk model turunun metin içeriği boştu ve adapter onu da taşımış. İkincisi daha önemli: model "shipped" bilgisini "delivered on 2026-11-06" diye çevirmiş. Tool "kargoya verildi" diyor, model "teslim edildi" diyor. Aynı agent, aynı soru, üç protocol'de üç farklı cümle üretti ve biri yanlış. Protocol adapter'ı modelin doğruluğunu artırmaz. Bunun için bu serinin "IChatClient Kullanan Kodu Test Etmek" yazısındaki evaluation'lar gerekir.

Bir de contextId var: A2A'daki konuşma kimliği. İstemci sonraki mesajda bunu gönderirse aynı session'a devam eder.

Authentication, Kullanıcı Başına Session ve Rate Limiting

Hosting paketlerinin XML dokümantasyonu aynı uyarıyı defalarca tekrarlıyor: AddOpenAIChatCompletions, AddAGUIServer ve UseClaimsBasedAgentIsolation authentication ya da authorization yapılandırmaz, endpoint'leri senin korumanı bekler. Yukarıda üç endpoint'e de RequireAuthorization() ekledik. Örnekteki kimlik doğrulaması yalnız demo amaçlı: X-Api-Key header'ındaki değeri configuration'daki bir müşteri id'sine çeviren küçük bir handler.

// Yalnız demo: gerçek uygulamada JWT/OpenID Connect kullan
public sealed class DemoApiKeyHandler(
    IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, IConfiguration config)
    : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
{
    protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue("X-Api-Key", out StringValues key))
        {
            return Task.FromResult(AuthenticateResult.NoResult());
        }

        string? customerId = config[$"DemoApiKeys:{key}"];
        if (customerId is null)
        {
            return Task.FromResult(AuthenticateResult.Fail("Unknown API key."));
        }

        ClaimsIdentity identity = new([new Claim(ClaimTypes.NameIdentifier, customerId)], Scheme.Name);
        return Task.FromResult(AuthenticateResult.Success(
            new AuthenticationTicket(new ClaimsPrincipal(identity), Scheme.Name)));
    }
}

Gerçek bir uygulamada bunun yerine JWT (JSON Web Token) ya da OpenID Connect kullanırsın; ayrıntılar Kimlik Doğrulama serisinde. Önemli olan, handler'ın ürettiği NameIdentifier claim'i. UseClaimsBasedAgentIsolation session'ları bu claim'e göre bölüyor. Paket dokümantasyonu claim'in kullanıcıyı benzersiz tanımlaması gerektiğini özellikle vurguluyor: görünen ad, e-posta takma adı gibi değişebilen ya da tekrar edebilen claim'ler güvenli bir izolasyon anahtarı değil.

Anahtarsız istek ve rate limiting:

curl -s -i localhost:19662/bookstore-assistant/v1/chat/completions \
  -H 'Content-Type: application/json' -d '{"model":"x","messages":[{"role":"user","content":"hi"}]}'
HTTP/1.1 401 Unauthorized

for i in 1 2 3 4; do curl -s -o /dev/null -w "request $i: %{http_code}\n" \
  localhost:19662/bookstore-assistant/v1/chat/completions \
  -H 'X-Api-Key: demo-key-ada' -H 'Content-Type: application/json' -d '{}'; done
request 1: 400
request 2: 400
request 3: 400
request 4: 429

Rate limit deneyini bilerek bozuk gövdeli isteklerle yaptık: rate limiter endpoint'ten önce çalıştığı için her istek bir izin harcıyor, ama hiçbiri modele gitmiyor. Dördüncü istek 429 aldı. Aynı anda Alan'ın anahtarıyla atılan istek ise 400 aldı, yani Alan'ın kotası ayrı. Partition anahtarı IP (Internet Protocol) adresi değil, kullanıcı claim'i. Bu serinin "LLM (Large Language Model) Maliyetini Kontrol Etmek" yazısında da gördüğümüz gibi, gerçek bir agent endpoint'inde istek sayısına ek olarak token bütçesi de sınırlanmalı. Burada her izin, tool turlarıyla birlikte birden fazla model çağrısı demek.

Thread Id Bir Yetki Değildir

Açılıştaki ikinci soruyu deneyelim. Alan, Ada'nın thread id'sini (thread-ada-1) biliyor ve aynı thread'e kendi anahtarıyla yazıyor. Ardından Ada aynı soruyu kendi anahtarıyla soruyor. Her iki çıktıda da SSE akışındaki metin delta'larını birleştirdik ve RUN_FINISHED event'indeki input token sayısını ekledik:

curl -s -N localhost:19662/agui -H 'X-Api-Key: demo-key-alan' -H 'Content-Type: application/json' \
  -d '{"threadId":"thread-ada-1","runId":"run-x","messages":[{"id":"m9","role":"user","content":"Which order number did I ask about earlier in this thread?"}]}'
I don't have access to previous messages in this thread, so I can't determine which order number you're referring to.
RUN_FINISHED usage: inputTokens 172

curl -s -N localhost:19662/agui -H 'X-Api-Key: demo-key-ada' -H 'Content-Type: application/json' \
  -d '{"threadId":"thread-ada-1","runId":"run-y","messages":[{"id":"m10","role":"user","content":"Which order number did I ask about earlier in this thread?"}]}'
You asked about order number 1042 earlier in this thread.
RUN_FINISHED usage: inputTokens 304

Alan aynı threadId'yi gönderdi ama Ada'nın konuşmasını göremedi. Modele giden input 172 token, yani yalnız system prompt ve Alan'ın sorusu. Ada'nın isteği ise 304 token ve model 1042'yi hatırlıyor. Bunu sağlayan UseClaimsBasedAgentIsolation: session store'daki anahtar artık yalnız thread id değil, kullanıcının NameIdentifier claim'iyle birlikte bir bileşik anahtar. Bu satırı kaldırırsan iki kullanıcı aynı thread id ile aynı konuşmayı paylaşır. AG-UI dokümantasyonunun deyişiyle thread id bir korelasyon kimliğidir, kimlik doğrulama sınırı değildir.

Hangisi Ne Zaman?

AdapterKim kullanırGüçlü yanıDikkat
AG-UI (MapAGUIServer)Kendi web/mobil arayüzünTool, reasoning ve metin için ayrı event'ler; interrupt ile onay akışıReasoning ve rawEvent tarayıcıya gidiyor; protocol hâlâ değişiyor
OpenAI uyumlu (MapOpenAIChatCompletions)Hazır OpenAI istemcisi olan ekipler, araçlarYeni istemci yazmadan bağlanmaAlpha; çok mesajlı cevap birden fazla choice olarak dönüyor
A2A (MapA2AJsonRpc)Başka agent'lar, başka ekiplerin servisleriAgent card ile keşif, contextId ile konuşmaCard herkese açık; içinde dahili bilgi olmasın

Üretimde Dikkat

  • WithInMemorySessionStore yalnız tek instance'lık geliştirme için. İkinci bir pod açıldığında ya da uygulama yeniden başladığında konuşmalar kaybolur. Kalıcı bir AgentSessionStore kullan.
  • Agent endpoint'leri uzun sürer: bu yazıdaki tek bir istek, paylaşılan yerel Ollama'da birkaç dakika sürdü. Reverse proxy timeout'larını ve SSE için buffering ayarlarını buna göre yap. Bu serinin streaming yazısındaki X-Accel-Buffering notu AG-UI için de geçerli.
  • AG-UI'de reasoning'in tarayıcıya gitmesini istemiyorsan agent tarafında filtrele ya da reasoning üretmeyen bir model/ayar seç. Reasoning, sistem talimatlarının ve tool sonuçlarının yorumunu içerebilir.
  • Agent card'ı bir API kataloğu gibi düşün: herkese açık. İç servis adlarını, yetki ayrıntılarını ya da test ortamı adreslerini oraya yazma.
  • Paketler preview ve alpha. Sürümü sabitle ve her güncellemede Map/Add API'lerinin adlarını ve davranışını yeniden doğrula.

Sık Düşülen Tuzaklar

En tehlikeli tuzak, dahili bir agent'ı authentication'sız public endpoint olarak açmak. MapAGUIServer ve MapOpenAIChatCompletions hiçbir yetki kontrolü eklemez; RequireAuthorization() yazmazsan agent'ın bütün tool'ları internete açılır. Tool'ların sipariş iptali gibi yan etkileri varsa bu doğrudan bir güvenlik açığıdır.

İkincisi, threadId'yi yetki sanmak. AG-UI dokümantasyonu bunu açıkça yazıyor: thread ve run id'leri protocol verisidir, yetki bilgisi değildir. İzolasyonu kullanıcı claim'iyle sen kurmalısın.

Üçüncüsü, rate limit'i IP adresine göre yapmak. Aynı kurumsal ağın arkasındaki bütün kullanıcılar tek kotayı paylaşır; mobil kullanıcıların IP'si sürekli değişir. Kimliği doğrulanmış kullanıcının claim'i daha doğru bir partition anahtarıdır.

Son olarak, API adlarını blog yazılarından kopyalamak. Bu yazı yazılırken bile dokümandaki örneklerle 1.28 paketindeki method adları farklıydı. Kullandığın sürümün XML dokümantasyonu en güncel kaynak.

Küçük Bir Deneme

★☆☆ MapAGUIServer satırından RequireAuthorization()'ı sil ve anahtarsız bir AG-UI isteği at. Hangi kullanıcı adına çalıştı ve session'ı hangi partition'a yazıldı?

★★☆ MapOpenAIResponses ile aynı agent'ı OpenAI Responses API'si biçiminde de aç. Aynı soruya dönen cevapta tool çağrısı ve asıl metin nasıl temsil ediliyor; Chat Completions'taki çoklu choice davranışı burada da var mı?

★★★ Rate limit policy'sini token bazlı yap: kullanıcı başına dakikada 2.000 token. İpucu: RUN_FINISHED event'indeki ve Chat Completions cevabındaki usage değerlerini bir middleware'de okuyup kullanıcıya göre biriktir.

Agent artık üç protocol üzerinden ve kullanıcı bazında güvenli biçimde dışarıda. Serinin son yazısında geriye bakacağız: Semantic Kernel'den Microsoft Agent Framework'e gelen yolu, destek durumunu ve eski bir Semantic Kernel (SK) projesini adım adım taşımayı göreceğiz.