Azure Haberler Microsoft

Copilot Studio Ajanlarına Locust ve Azure Load Testing ile Yük Testi: Direct Line, WebSocket ve Gerçek Rakamlar

Copilot Studio agent Locust Azure Load Testing Direct Line akışı

Bir konuşma ajanı, bir API gibi cevap vermiyor. Tek bir mesaj gönderiyorsunuz, sunucu 200 OK dönüyor ve sonra hiçbir şey olmuyor. Gerçek cevap hâlâ yolda; WebSocket üzerinden tek parça ya da birkaç mesaj hâlinde akıyor. İki saniye de sürebilir, ajan düşünürken, bir tool çağırırken ya da başka bir ajan oluştururken üç dakika da. Peki istek “bitti” derken cevabı hazır olmayan bir işi nasıl yük testine sokarsınız?

Microsoft’tan Krishna Roy, Apps on Azure blogunda bu soruya çalışan bir cevap veriyor: Locust ile yazılmış bir Direct Line istemcisi. 30 dakikalık tek bir koşuda 253 konuşma ve 3.212 istek sıfır hata ile tamamlanmış; bunun içinde ilk mesajı 8,68 saniyede dönen ama tamamlanması 163,71 saniye süren bir “agent creation” turu da var.

Neden Copilot Studio ajanına yük testi yapılır?

Copilot Studio ile kurulan ajanlar, talep ve yük arttıkça otomatik ölçeklenen bir platformda çalışıyor. Ancak bu ölçekleme sonsuz değil: ortamın kapasitesi, kotaları, throttling ve servis limitleri içinde kalıyor. Üstelik bir tur, kendi işletim limitleri olan custom logic, connector ve backend servislere de uzanabiliyor. Eşzamanlı yük, isteğin geçtiği yolun her iki parçasında da gecikme veya hata ortaya çıkarabiliyor.

Yazının analiz çerçevesi burada net bir ayrım yapıyor. Bir tur (turn), tek bir kullanıcı mesajı ve ajanın buna cevap vermek için yaptığı her şey; turn.complete sinyaline kadar geçen süre. Bu süre iki bölgeyi kat ediyor:

  • Yeşil bölge (Copilot Studio yönetimindeki yol): Mesajı kabul eden Direct Line kanalı, ajanın parçaları (modeli, talimatları, bilgi kaynakları, tool’ları, skill’leri ve bağlı ajanları) üzerinde akıl yürüten enhanced orchestration runtime ve mesaj mesaj geri akan cevap. Platform kendi kapasite ve kota limitleri içinde otomatik ölçekleniyor.
  • Amber bölge (dış bağımlılıklar): Custom logic ve Power Automate flow’ları; Salesforce, ServiceNow, SharePoint, Dataverse gibi connector’lar; backend API’ler ve veritabanları; hosted MCP sunucuları. Her birinin kendi konfigürasyonu ve limitleri var.

Kritik nokta: yük altında her iki bölgedeki herhangi bir parça gecikme ekleyebilir veya hata verebilir. Ve sadece full-agent süreleri kaynağı göstermez.

Önce her bileşeni izole test edin

Yazının önerdiği sıralama şu: full-agent koşusundan önce cloud flow’a, connector’a, backend API’ye ve MCP sunucusuna tek tek temsili yük uygulayın. Böylece her bağımlılık için gecikmenin yükseldiği, throttling’in başladığı veya isteklerin hata vermeye başladığı istek oranını ölçmüş olursunuz.

İzole koşu, belirsiz bir “ajan yavaş hissettirdi” cümlesini ölçülmüş bileşen davranışına çeviriyor. Sonrasında full-agent koşusu bu sonuçlarla karşılaştırılabiliyor; hangi parçanın gecikmeye yol açtığı baştan varsayılmıyor.

Ama neden tam ajanı da test etmeli?

Çünkü kullanıcı bileşenleri değil, tam konuşmayı deneyimliyor. Yazının dört gerekçesi:

  • Daha çok kullanıcı, daha yavaş cevap olabilir. Tek testçi için hızlı çalışan ajan, çok sayıda eşzamanlı konuşmada yavaşlayabilir.
  • Farklı konuşmalar farklı yollar izler. Bazı işler ancak kullanıcı belirli bir seçim yaptıktan sonra başlıyor; tek mesajlık test o dala hiç ulaşmayabilir.
  • Tam cevap, gönderme isteğinden uzun sürer. Mesaj göndermek sadece başlangıç; yanıt süresi ajanın o tur için son cevabını döndüğünde bitiyor.
  • Gerçek sonuçlar daha iyi hedef koydurur. Ölçülmüş yanıt süreleri ve hata oranları production planlaması için net bir baseline veriyor.

Direct Line üzerinden bir konuşma nasıl işler?

Direct Line, cevapları WebSocket akışı ya da HTTP GET polling ile iletebiliyor. Microsoft’un performans testi rehberi, istemci uygulaması WebSocket kullanıyorsa testin de WebSocket kullanmasını söylüyor. Test edilen istemci WebSocket kullandığı için harness de öyle yapıyor.

1. Token al. Direct Line secret’ı token endpoint’ine gönderiliyor; tek bir konuşma için token ve saniye cinsinden expires_in dönüyor.

POST {host}/v3/directline/tokens/generate
Authorization: Bearer «Direct Line secret»

→ 200 { "conversationId": "…", "token": "eyJhbGci…", "expires_in": «saniye» }

2. Konuşmayı başlat ve socket’i aç. Dönen conversationId konuşmayı tanımlıyor, streamUrl ise cevapların alınacağı WebSocket adresi. İstemci socket’i açıp konuşma bitene kadar açık tutuyor.

POST {host}/v3/directline/conversations
Authorization: Bearer «token»

→ 201 { "conversationId": "…", "streamUrl": "wss://…/stream", "token": "eyJhbGci…" }

WS CONNECT wss://…/stream → socket açık (HTTP 101)

3. Mesaj gönder. Direct Line’da mesaj bir activity olarak temsil ediliyor. İstemci activity’yi HTTP POST ile gönderiyor, ajanın cevapları açık WebSocket’ten dönüyor.

POST {host}/v3/directline/conversations/{id}/activities
Authorization: Bearer «token»
{ "type": "message", "from": { "id": "user-…" }, "text": "…", "locale": "en-US" }

→ 200 { "id": "…" }

Buradaki 200 yanıtı yalnızca Direct Line’ın mesajı kabul ettiğini gösteriyor; ajanın cevabını içermiyor ve işlemenin bittiği anlamına gelmiyor. Yük testlerinde en sık yapılan hata da tam olarak bu 200’ü “cevap geldi” sanmak.

4. turn.complete’e kadar cevapları oku. Bir tur bir ya da birkaç ajan mesajı içerebiliyor. Ajan turu bitirdiğinde stream turn.complete adlı bir event gönderiyor. İstemci o tur için okumayı burada sonlandırıyor, WebSocket bir sonraki mesaj için açık kalıyor.

İki farklı ölçüm: TTFB ve ResponseTime

Yazının en önemli metodolojik katkısı bu ayrım:

  • TTFB: Gönderme isteğinin başlangıcından ilk ajan mesajına kadar geçen süre. (Not: burada TTFB, ilk network byte’ı değil, ilk tam ajan mesajı anlamında kullanılıyor.)
  • ResponseTime: Gönderme isteğinin başlangıcından son ajan mesajına kadar geçen süre.

Typing aktiviteleri ve turn.complete event’i zamanlama sınırı olarak kullanılmıyor; ilk ve son ajan mesajları ölçümü belirliyor. Tek mesajlık turlarda TTFB ve ResponseTime eşit oluyor. Ajan önce bir onay, sonra tamamlanmış sonucu gönderiyorsa ResponseTime TTFB’den uzun oluyor. Azure koşusunda agent-creation turu ortalama 8,68 saniyede ilk mesajı, 163,71 saniyede tamamlanmayı göstermiş.

Yeni ajan deneyiminin üç tuzağı

Yazının en pratik bölümü burası. Naif yazılmış bir istemciyi sessizce bozacak üç davranış:

1. Cevaplarda role alanı olmayabiliyor. Klasik kanalda ajan mesajı from.role = "bot" taşıyor. Yeni deneyimde bazı cevaplar yalnızca from.id ile geliyor, role hiç yok. role == "bot" kontrolü yapan istemci bu mesajları düşürüyor ve “cevap yok” raporluyor. Çözüm: istemcinin kendi echo’su olmayan her mesajı cevap saymak.

def _is_bot_reply(self, activity):
    sender = activity.get("from") or {}
    role = sender.get("role")
    if role == "bot":
        return True
    if role == "user":
        return False
    return sender.get("id") != self._user_id  # role yok -> echo değil -> cevap

2. turn.complete açık bir tur-sonu event’i. Timeout beklemek yerine bu event’i okumak, hızlı bir turun boşuna beklemek yerine birkaç saniyede bitmesini sağlıyor.

3. Hatalar yapılandırılmış kod olarak dönüyor. Bir şey ters gittiğinde ajan, dilden bağımsız bir ErrorCode taşıyan trace activity’si gönderebiliyor. İstemci bunu resmi kod listesiyle eşleştirdiğinde koşu, “cevap gelmedi” yerine neden başarısız olduğunu raporluyor.

Dosya eklerini multipart upload ile göndermek

Dosya okuyan bir tur, salt metin turunun yapmadığı işleri çalıştırıyor. Harness, dosyaları Direct Line /upload endpoint’ine multipart/form-data olarak gönderiyor; aynı istekte opsiyonel bir mesaj activity’si de olabiliyor.

POST {host}/v3/directline/conversations/{id}/upload?userId={from.id}
Authorization: Bearer «token»
Content-Type: multipart/form-data; boundary=----loadtest-«random»

Birkaç önemli detay: userId query parametresi zorunlu ve gönderici kimliğini belirtiyor. Activity JSON’u attachments dizisi içermiyor; Direct Line ayrı file part’ları activity’ye attachment olarak kendisi ekliyor. Başarılı upload, normal metin gönderimiyle aynı { "id": "…" } şeklini döndüğü için mevcut WebSocket alma ve zamanlama yolu değişmeden çalışıyor.

Locust’un FastHttpUser sınıfında requests tarzı bir files= yardımcısı olmadığı için multipart gövde elle byte olarak kuruluyor: her istek için UUID tabanlı taze bir boundary, POST başlamadan önce tüm dosyaların okunması ve her ek için ayrı bir file part.

Önce lokal doğrulama, sonra Azure

Aynı Python workload dosyası hem lokalde hem Azure Load Testing’de kod değişikliği olmadan çalışıyor. Ortama özel ayarlar lokalde locust.conf, Azure’da locust.azure.conf dosyasından geliyor.

  • Lokal doğrulama: 4 sanal kullanıcı, 15 dakika, her 30 saniyede bir kullanıcı. Her iki konuşma yolu, dosya yüklemeleri, pacing, transaction isimleri ve diagnostics doğrulanmış. Sıfır hata.
  • Azure koşusu: Aynı iki kullanıcı sınıfı, 16 sanal kullanıcı, 30 dakika. 8 kullanıcı metin yolu, 8 kullanıcı dosya eki yolu.

Bağımlılıklar sade:

python -m pip install "locust==2.42.6" "python-dotenv>=1.0,<2.0" "websocket-client==1.9.0"

Önemli bir ayrıntı: 1.200 saniyelik stop-timeout sabit bir uzatma değil, üst sınır. 15 dakikalık pencere kapandığında Locust, devam eden konuşmanın turunu kesmek yerine tamamlanmasına izin veriyor. Bu, opsiyonel agent-creation turunun 300 saniyelik cevap deadline'ı olduğu için kritik.

Azure Load Testing tarafı ve secret yönetimi

Testi oluşturmadan önce Direct Line secret'ını Azure Key Vault'ta saklamak, Azure Load Testing kaynağının system-assigned managed identity'sini etkinleştirmek ve bu kimliğe secret okuma izni vermek gerekiyor. Azure CLI'da --keyvault-reference-id parametresine literal null değeri (boş veya atlanmış argüman değil) verilmesi, load testing kaynağının kendi system-assigned identity'sini kullanmasını söylüyor.

Yazının açıkça uyardığı bir güvenlik noktası var: --env yalnızca hassas olmayan ayarlar için kullanılmalı. Secret, özel --secret parametresi ile Key Vault secret identifier'ı olarak geçirilmeli. Yazının kendi 2026-07-12 Demo Run'ı secret'ı --env ile geçirmiş ve bunun run metadata'sında ifşa olabileceği not düşülmüş; önerilen komut bunu düzeltiyor.

Bir diğer pratik uyarı: Azure'ın test-run debug modu kasıtlı olarak kapalı bırakılmış. Debug modundaki koşular, Locust'ta yapılandırılan süreden bağımsız olarak 10 dakika ile sınırlanıyor.

30 dakikalık Demo Run sonuçları

Koşu 12 Temmuz 2026'da tek bir Azure Load Testing engine'i ile yapılmış, PASSED verdict ile ve servis hata detayı olmadan tamamlanmış. 16 kullanıcıya 7 dakika 30 saniyede ulaşılmış, ardından yaklaşık 22 dakika 30 saniyelik istikrarlı bir pencere korunmuş.

  • Sanal kullanıcı: 16 (8 metin + 8 dosya)
  • Tamamlanan konuşma: 253 (98 metin, 155 dosya)
  • Kaydedilen istek örneği: 3.212
  • Başarısız örnek: 0
  • Tamamlanan agent-creation turu: 5

Metin yolunda tüm konuşmalar T05 confirm adımına, dosya yolunda tüm konuşmalar T03 confirm adımına ulaşmış.

Metin konuşması: Normal metin yolunda requirement turu p90'da 18,93 saniyeye çıkmış. Agent creation ise ortalama 8,68 saniyede ilk mesajı dönmüş ama tamamlanması ortalama 163,71 saniye, p90'da 202,37 saniye sürmüş.

Dosya konuşması: En yavaş dosya turu attachment processing olmuş: ortalama 22,19 saniye, p90'da 27,80 saniye. Token, conversation-start, send ve upload çağrılarının hepsi p90'da 220 milisaniye veya altında kalmış.

Rakamlar ne söylüyor?

1. Ölçülen gecikmeye ajan işi hakim. Direct Line HTTP operasyonlarının hepsi p90'da 220 ms altında kalırken tam sohbet turları saniyeler, agent-creation dalı ise dakikalar sürmüş. Yani ölçülen uçtan uca sürenin büyük kısmı Direct Line mesajı kabul ettikten sonra birikiyor. Sonuçlar orchestration, model, tool ve downstream servis sürelerini birbirinden ayırmıyor.

2. TTFB tam cevabı tarif etmiyor. Target turu ortalama 3,08 saniyede ilk cevabı, 6,62 saniyede tamamlanmayı göstermiş. Conditional action turu 3,29 saniye / 9,46 saniye. Agent creation bu makası iki buçuk dakikadan fazlaya açmış. Sadece ilk cevabı ölçmek, bu turların pahalı kısmını gizlerdi.

3. Eşit kullanıcı, eşit konuşma sayısı demek değil. Kullanıcı dağılımı 8'e 8 olmasına rağmen dosya yolu 155, metin yolu 98 konuşma tamamlamış. Bu fark, daha uzun çok turlu metin akışı ve ara sıra devreye giren agent-creation dalıyla tutarlı. Sanal kullanıcı dağılımı eşzamanlılığı tarif ediyor; tamamlanan iterasyon sayısı senaryo süresine de bağlı.

Varsayımlar, sınırlar ve operasyonel uyarı

Yazının en olgun tarafı, sonuçlarının sınırlarını kendisinin çizmesi:

  • Kasıtlı olarak baseline ölçek. 4 kullanıcılık lokal ve 16 kullanıcılık Azure profili baseline üretiyor, stres testi değil. Gerçek kullanıcı davranışını aşan yükün mesaj tüketimi overage'ına ve ortam throttling'ine yol açabileceği uyarısı var.
  • Tek ajan, tek bölge. Rakamlar tek bir Direct Line regional endpoint üzerindeki tek bir ajanı tarif ediyor; farklı ajan, model veya bölge kendi imzasını üretir.
  • Preview davranışı. İstemci, turn.complete event'ine ve role'ü opsiyonel cevaplara dayanıyor. Yeni ajan deneyimi 13 Temmuz 2026 itibarıyla production-ready preview; ürün durumu ve yanıt şekilleri değişebilir.
  • Tek Azure engine, hafif yük. Doygunluk (saturation) karakterizasyonu daha yüksek eşzamanlılık ve birden fazla engine gerektiriyor.
  • Tek Demo Run. Sonuçlar bu 30 dakikalık pencereyi tarif ediyor; SLA hedefi koymadan önce tekrarlanan koşularla karşılaştırılmalı.
  • Sadece sohbet turları. Harness sign-in kartlarını, adaptive card submit'lerini veya token-token streaming render'ı test etmiyor.

Operasyonel not da net: kullanıcı veya engine sayısını artırmadan önce Copilot Studio ortamının kotalarını ve bağlı her bağımlılığın kapasitesini doğrulayın. Workload beklenen trafiği modellemeli, production sistemleri sınırsız bir stres hedefi olarak kullanmamalı.

Kod nerede?

Çalıştırılabilir tam örnek kroy92/copilot-studio-load-testing reposunda. İçinde new_chat_client.py, iki sınıflı locustfile_discovery_demo.py workload'ı, üç attachment fixture'ı, lokal ve Azure Locust config dosyaları, requirements.txt ve azure-loadtest.yaml bulunuyor.

Copilot Studio ajanlarını production'a almadan önce yük altında davranışını ölçmek isteyen ekipler için bu yazı, hazır bir başlangıç noktası sunuyor. Özellikle üç şey çok değerli: 200 OK'in cevap gelmesi anlamına gelmediğinin altını çizmesi, TTFB ile ResponseTime ayrımını kurması ve yeni ajan deneyiminin role-opsiyonel cevaplar gibi sessizce bozan davranışlarını önceden işaretlemesi. Yeşil bölge / amber bölge ayrımı da mimari bir düşünme çerçevesi olarak akılda kalıcı: ajanın yavaşladığı yerin Copilot Studio mu yoksa arkasındaki connector mı olduğunu bilmeden optimizasyona başlamak zaman kaybı. Pratik öneri, yazının kendi sıralamasını izlemek: önce bileşenleri izole ölçün, sonra tam ajanı yükleyin, her koşuyu Key Vault üzerinden secret'la çalıştırın ve rakamları tek bir koşuya değil tekrarlanan baseline'lara dayandırın.

Kaynak: https://techcommunity.microsoft.com/blog/appsonazureblog/load-testing-copilot-studio-agents-with-locust-and-azure-load-testing/4535445

Yazar Hakkında

Kerem Şuğle

Solution Architect, VMware vExpert ve Microsoft sertifikalı altyapı uzmanı. VMware vSphere/vSAN/VCF, Azure, AWS, Google Cloud, enterprise sanallaştırma ve yapay zeka konularında 15+ yıl deneyim. AI/cloud dönüşümü, sovereign cloud, enterprise güvenlik ve modern altyapı mimarisi alanlarında yazıyor.

Leave a Comment