astonishing-waitress-60915
03/05/2026, 3:02 PMclosing_message cümlesini sesli oynatır.
3) Kullanıcı gerçekten araya girerse kapatmayı iptal eder.
4) Aksi halde çağrıyı sonlandırır.
"""
ctx = get_job_context()
session = context.session
logger.info(f"Ending call started - room: {ctx.room.name}")
# Tool'a girmeden önce aktif agent konuşmasını kes ve playback state'ini stabilize et.
try:
session.interrupt()
logger.info(f"Pre-tool speech interrupted - room: {ctx.room.name}")
except Exception as e:
logger.warning(f"Pre-tool speech interrupt failed: {e}")
try:
await context.wait_for_playout()
logger.info(
f"Pre-tool wait_for_playout completed - room: {ctx.room.name}"
)
except Exception as wait_err:
logger.warning(
f"Pre-tool wait_for_playout failed: {wait_err}"
)
interrupted = await speak_message(
session,
closing_message.strip() or "Görüşme için teşekkür ederim, iyi günler.",
wait_for_playout=True,
allow_interruptions=True,
)
if interrupted:
logger.info(
f"Kullanıcı araya girdi, kapatmaktan vazgeçiliyor - room: {ctx.room.name}"
)
return None
logger.info(f"Closing message played, ending call now - room: {ctx.room.name}")
# Mark call as agent-ended for reporting.
current_agent = getattr(session, "current_agent", None)
if current_agent:
session_report = getattr(current_agent, "_session_report_service", None)
if session_report:
session_report.set_ended_by("agent")
await hangup(ctx, reason="agent_ended")
{"@timestamp": "2026-03-05T145329.454Z", "log.level": "INFO", "message": "Ending call started - room: 69a998c3a3d90d6fa858474f", "service.name": "voice-kit-agent", "event.dataset": "end_call", "logger.name": "end_call", "call_id": "69a998c3a3d90d6fa858474f", "room_name": "69a998c3a3d90d6fa858474f", "job_id": "AJ_Aw66XHu76W4E"}
{"@timestamp": "2026-03-05T145329.455Z", "log.level": "INFO", "message": "Pre-tool speech interrupted - room: 69a998c3a3d90d6fa858474f", "service.name": "voice-kit-agent", "event.dataset": "end_call", "logger.name": "end_call", "call_id": "69a998c3a3d90d6fa858474f", "room_name": "69a998c3a3d90d6fa858474f", "job_id": "AJ_Aw66XHu76W4E"}
{"@timestamp": "2026-03-05T145329.490Z", "log.level": "INFO", "message": "Pre-tool wait_for_playout completed - room: 69a998c3a3d90d6fa858474f", "service.name": "voice-kit-agent", "event.dataset": "end_call", "logger.name": "end_call", "call_id": "69a998c3a3d90d6fa858474f", "room_name": "69a998c3a3d90d6fa858474f", "job_id": "AJ_Aw66XHu76W4E"}
{"message": "tool_choice is not supported by the Google Realtime API.", "level": "WARNING", "name": "livekit.plugins.google", "pid": 118, "job_id": "AJ_Aw66XHu76W4E", "room_id": "RM_nEnAotDyx6k4", "timestamp": "2026-03-05T145329.500204+00:00"}
{"message": "speech not done in time after interruption, cancelling the speech arbitrarily.", "level": "ERROR", "name": "livekit.agents", "speech_id": "speech_094f921d8821", "timeout": 5.0, "pid": 118, "job_id": "AJ_Aw66XHu76W4E", "room_id": "RM_nEnAotDyx6k4", "timestamp": "2026-03-05T145334.455719+00:00"}
{"message": "RealtimeModel metrics", "level": "INFO", "name": "livekit.agents", "model_name": "gemini-2.5-flash-native-audio-preview-12-2025", "model_provider": "Gemini", "ttft": 0.3, "input_tokens": 7434, "cached_input_tokens": 0, "input_text_tokens": 7406, "input_cached_text_tokens": 0, "input_image_tokens": 0, "input_cached_image_tokens": 0, "input_audio_tokens": 28, "input_cached_audio_tokens": 0, "output_tokens": 234, "output_text_tokens": 0, "output_audio_tokens": 234, "output_image_tokens": 0, "total_tokens": 7668, "tokens_per_second": 40.16, "pid": 118, "job_id": "AJ_Aw66XHu76W4E", "room_id": "RM_nEnAotDyx6k4", "timestamp": "2026-03-05T145335.929654+00:00"}
{"message": "tool_choice is not supported by the Google Realtime API.", "level": "WARNING", "name": "livekit.plugins.google", "pid": 118, "job_id": "AJ_Aw66XHu76W4E", "room_id": "RM_nEnAotDyx6k4", "timestamp": "2026-03-05T145335.940921+00:00"}
{"@timestamp": "2026-03-05T145335.952Z", "log.level": "INFO", "message": "Closing message played, ending call now - room: 69a998c3a3d90d6fa858474f", "service.name": "voice-kit-agent", "event.dataset": "end_call", "logger.name": "end_call", "call_id": "69a998c3a3d90d6fa858474f", "room_name": "69a998c3a3d90d6fa858474f", "job_id": "AJ_Aw66XHu76W4E"}
{"@timestamp": "2026-03-05T145335.990Z", "log.level": "DEBUG", "message": "Bağlantı kesildi: 4 (PARTICIPANT_REMOVED)", "service.name": "voice-kit-agent", "event.dataset": "agent-factory", "logger.name": "agent-factory", "room_name": "69a998c3a3d90d6fa858474f", "job_id": "AJ_Aw66XHu76W4E"}
{"@timestamp": "2026-03-05T145335.990Z", "log.level": "DEBUG", "message": " Çağrı sonlandı: agent (sebep: PARTICIPANT_REMOVED, sonuç: completed)", "service.name": "voice-kit-agent", "event.dataset": "agent-factory", "logger.name": "agent-factory", "room_name": "69a998c3a3d90d6fa858474f", "job_id": "AJ_Aw66XHu76W4E"}
{"message": "speech not done in time after interruption, cancelling the speech arbitrarily.", "level": "ERROR", "name": "livekit.agents", "speech_id": "speech_094f921d8821", "timeout": 5.0, "pid": 118, "job_id": "AJ_Aw66XHu76W4E", "room_id": "RM_nEnAotDyx6k4", "timestamp": "2026-03-05T145334.455719+00:00"} bu hatayı alıyorum. Bunu nasıl çözebilirim?tall-airline-16756
03/05/2026, 3:02 PMtall-airline-16756
03/05/2026, 3:03 PMspeak_message(..., wait_for_playout=True, allow_interruptions=True) çağrısı sonrası bekleme yapılıyor; ancak kesinti durumunda bu bekleme takılabiliyor ve 5s zaman aşımı tetikleniyor.
Hızlı çözüm önerileri (en etkili olanlar önde) 1) wait_for_playout True kullanmayı bırak (engelleyici beklemeyi kaldır)
• Konuşmayı başlat ama bloklama yapma; sonrasında kısa bir süre sonra handle.interrupted kontrol et.
• Örnek akış:
◦ Başlat: handle = speak_message(..., wait_for_playout=False, allow_interruptions=True)
◦ Kısa bekleme: await asyncio.sleep(0.5)
◦ Eğer handle.interrupted ise iptal et; değilse hangup işlemini sürdür.
• Neden: Realtime modellerde güvenilir playout bitiş sinyali alınamayabilir; bloklamayı kaldırmak zaman aşımı hatasını önler.
2) Eğer mutlaka beklemen gerekiyorsa: kendi timeout’ını arttır ve asyncio.wait_for ile yakala
• Örnek:
a. handle = speak_message(..., wait_for_playout=False, allow_interruptions=True)
b. try: await asyncio.wait_for(handle, timeout=10.0)
except asyncio.TimeoutError: logger.warning("Playback timed out"); assume interrupted or proceed safely
• Neden: Framework’in 5s varsayılanı yetersiz kalıyorsa, kontrollü ve logged bir şekilde daha uzun bekleyebilirsin.
3) Kritik kapanış konuşmasında kesintileri geçici olarak devre dışı bırak
• Eğer kapanış cümlesinin tamamen okunması çok önemliyse, kısa süreliğine kesintileri kapat:
◦ context.disallow_interruptions() / context.allow_interruptions() (kodunda böyle bir API varsa)
◦ Ancak: Kullanıcının gerçekten aramayı bitirmesini isteyeceksen UX açısından dikkatli ol — zorlayıcı olabilir.
4) speak_message’ın döndürdüğü handle’ın `interrupted`/durum alanını kullan
• speak_message’ın bir SpeechHandle döndürdüğünden emin ol; sonra onun .interrupted veya .done benzeri alanından gerçek kullanıcı kesmesi mi yoksa framework timeout’u mu olduğunu ayırt et.
Kod örneği (özet, uyarlayabilirsin):
handle = speak_message(
session,
closing_message.strip() or "Görüşme için teşekkür ederim, iyi günler.",
wait_for_playout=False,
allow_interruptions=True,
)
# kısa bir grace period
await asyncio.sleep(0.5)
if getattr(handle, "interrupted", False):
<http://logger.info|logger.info>("Kullanıcı araya girdi, kapatma iptal ediliyor")
return None
# devam edip kapat
await hangup(ctx, reason="agent_ended")
Ek tavsiyeler
• speak_message içindeki implementasyonun gerçekten bir handle döndürdüğünden emin ol; eğer speak_message zaten await ediliyorsa wait_for_playout=False ile non-blocking versiyonunu kullan.
• Loglarda speech_id ile birlikte çıktıları izle; böylece gerçek kullanıcı müdahalesi ile framework timeout’unu ayırt edebilirsin.
• Realtime model kullanıyorsan, mümkünse kritik kapanışlarda non-realtime (buffered) TTS’ye geçiş veya daha kısa/tek cümleli farewell kullanmak stabiliteyi artırır.
Özet
• Hata, speak_message sonrası playout tamamlanmadığı için 5s timeout yüzünden çıkıyor.
• En iyi pratik: wait_for_playout=True yerine non-blocking başlat + kısa bekleme + handle.interrupted kontrolü veya kontrollü, daha uzun timeout kullanmak.
• Kapanışı kesin yapmadan önce kullanıcı kesintisini handle üzerinden kontrol etmen yeterli olacaktır.
İstersen kodunun ilgili kısmını ben senin için doğrudan düzeltebilirim (tam speak_message imzasını ve SpeechHandle yapısını paylaşırsan). Hangi yaklaşımı tercih edersin: hemen non-blocking ile devam etmek mi, yoksa timeout’ı yükseltip daha kontrollü beklemek mi?
Sources: Realtime models overview | LiveKit Documentation | How to detect when an agent has finished speaking | LiveKitastonishing-waitress-60915
03/05/2026, 3:04 PMmessage through the active pipeline.
- Standard pipeline: session.say(message, ...)
- Realtime pipeline: session.generate_reply(instructions=...)
Args:
session: AgentSession instance.
message: Text to speak.
allow_interruptions: Users can interrupt. (Standard pipeline only)
add_to_chat_ctx: Add to chat context. (Standard pipeline only)
wait_for_playout: Wait for audio to finish playing before returning.
Returns:
True if the user interrupted, False otherwise.
If wait_for_playout=False, False means interruption status is not yet known.
"""
if _is_realtime_session(session):
safe_announce_instructions = (
"Kullanıcıya yalnızca şu cümleyi aynen söyle. "
"Ek cümle kurma, açıklama yapma:\n"
f"{message}"
)
# generate_reply is sync and returns SpeechHandle.
handle = session.generate_reply(
instructions=safe_announce_instructions,
allow_interruptions=allow_interruptions,
input_modality="audio",
)
else:
handle = session.say(
message,
allow_interruptions=allow_interruptions,
add_to_chat_ctx=add_to_chat_ctx,
)
if wait_for_playout:
await handle.wait_for_playout()
return handle.interrupted
return False
Bunu da gönderiyorum sana.tall-airline-16756
03/05/2026, 3:05 PMspeak_message ve _is_realtime_session fonksiyonları, loglardaki hatanın kaynağını doğruluyor: realtime pipeline (generate_reply) kullanıldığında `wait_for_playout=True ile handle.wait_for_playout()` bekleniyor ve kesinti sonrası framework 5s içinde bitiş sinyali alamayınca "speech not done in time..." hatasını atıyor.
Ne yapmalısın (özet ve adımlar)
• Realtime oturumlarda wait_for_playout=True kullanmaktan kaçın. Realtime modelleri playout bitiş sinyallerini güvenilir şekilde sağlamıyor; bu yüzden bloklayıcı bekleme zaman aşımına uğruyor.
• Bunun yerine non-blocking başlat + kısa grace period ile handle.interrupted kontrol et veya kendi kontrollü timeout mekanizmanı kullan.
Önerilen kod değişikliği (uyarlaması kolay, küçük) 1) speak_message çağrısını non-blocking yap:
• wait_for_playout=False ile çağır.
• Döndürülen handle üzerinden kısa bir süre sonra interrupted kontrol et.
Örnek (end_call içindeki ilgili kısma uygula):
1. speak_message çağır:
handle = speak_message(
session,
closing_message.strip() or "Görüşme için teşekkür ederim, iyi günler.",
wait_for_playout=False,
allow_interruptions=True,
)
1. Kısa grace period ve kontrol:
await asyncio.sleep(0.5) # 0.5s gibi kısa bir bekleme
if getattr(handle, "interrupted", False):
<http://logger.info|logger.info>("Kullanıcı araya girdi, kapatma iptal ediliyor - room: {ctx.room.name}")
return None
1. Kapatmayı sürdür:
# closing message çalınıyor; kullanıcı kesmediyse kapat
await hangup(ctx, reason="agent_ended")
Alternatif: Kontrollü bekleme + genişletilmiş timeout
• Eğer tamamen beklemek istiyorsan, handle.wait_for_playout() kullanma yerine asyncio.wait_for(handle, timeout=...) veya await asyncio.wait_for(handle.wait_for_playout(), timeout=10.0) gibi daha uzun, yakalanabilir bir timeout kullan.
• Timeout yakalandığında bunu logla ve kapatmaya karar ver ya da iptal et. Böylece framework'ün 5s kısıtına takılmaktan kurtulursun.
Neden bu güvenli/iyi:
• Realtime pipeline (generate_reply) playout bitişini garantilemediği için bloklayıcı bekleme hata üretir.
• Non-blocking yaklaşım hem UX açısından daha kararlı (kapanış mesajı başlar ve kullanıcı müdahalesi varsa yakalanır) hem de framework zaman aşımı loglarını engeller.
• Kısa grace period ile gerçek kullanıcı müdahalesini yakalayabilir, spam/ses gecikmesi gibi framework gecikmelerini hataya dönüştürmezsin.
Ek doğrulamalar / dikkat etmen gerekenler
• speak_message çağrısının gerçekten bir handle döndürdüğünden emin ol (senin kodda generate_reply/say SpeechHandle dönüyor gibi görünüyor; handle üzerinde interrupted alanı mevcut mu kontrol et).
• Eğer handle üzerinde .interrupted yoksa, speak_message içinden handle yerine uygun durum bilgisini sağlayan bir wrapper döndürmeyi düşün.
• Kapatma sırasında kritik bir kullanım varsa (mesajın kesin okunması gerektiği durum), kısa süreliğine kesintileri devre dışı bırakma seçeneğini düşün; fakat kullanıcı deneyimini bozabileceğini unutma.
İstersen ben şu adımlarda yardımcı olabilirim:
• Senin için end_call fonksiyonunu paylaştığın speak_message ile uyumlu olacak şekilde direkt düzeltilmiş hâlini yazayım.
• Ya da hangi timeout/await stratejisini tercih edeceğini söyle, ona göre örnek kod gönderiyim.
Hangi yaklaşımı tercih edersin: "non-blocking + kısa kontrol" mı yoksa "uzatılmış kontrollü timeout" mu?