Selam, Gemini Live API'de genarete_reply de sürek...
# ask-ai
a
Selam, Gemini Live API'de genarete_reply de sürekli olarak aşağıdaki hatayı alıyorum. {"message": "received server content but no active generation.", "level": "WARNING", "name": "livekit.plugins.google", "pid": 116, "job_id": "AJ_cHJ3Qo2QP4HW", "room_id": "RM_cpNxatAEx3jo", "timestamp": "2026-02-17T091550.234766+00:00"} {"message": "received server content but no active generation.", "level": "WARNING", "name": "livekit.plugins.google", "pid": 116, "job_id": "AJ_cHJ3Qo2QP4HW", "room_id": "RM_cpNxatAEx3jo", "timestamp": "2026-02-17T091550.235641+00:00"} {"message": "received server content but no active generation.", "level": "WARNING", "name": "livekit.plugins.google", "pid": 116, "job_id": "AJ_cHJ3Qo2QP4HW", "room_id": "RM_cpNxatAEx3jo", "timestamp": "2026-02-17T091550.238502+00:00"} {"message": "no active generation to report metrics for", "level": "WARNING", "name": "livekit.plugins.google", "pid": 116, "job_id": "AJ_cHJ3Qo2QP4HW", "room_id": "RM_cpNxatAEx3jo", "timestamp": "2026-02-17T091550.238675+00:00"} VertexAI'da gemini-live-2.5-flash-preview-native-audio-09-2025 bu modeli kullanıyorum.
t
Thanks for reaching out, İbrahim. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Selam — paylaştığın loglardaki uyarıları ve kullandığın modeli (gemini-live-2.5-flash-preview-native-audio-09-2025) gördüm. Bu hataya yönelik neyi yanıtladığımı kısaça belirteyim: "received server content but no active generation" uyarısı, sunucudan içerik gelmesine rağmen o içeriği işleyecek aktif bir gerçek zamanlı (real‑time) üretim/generation oturumunun bulunmadığını gösterir. Aşağıda adım adım kontrol etmeni ve paylaşmanı rica ettiğim noktalar var — bu bilgilerle sorunu daha hızlı çözebiliriz. Öncelikle kontrol etmen gerekenler • Oturum başlatma:
generate_reply
çağrısından önce gerçek zamanlı (real‑time) oturumu/ses turn detection mekanizmasını doğru başlattın mı? Oturumun "active" olduğundan emin ol. • Model parametreleri: VertexAI kullanımını etkinleştirmek için
vertexai=True
parametresini sağladın mı? • Proje/konum:
project
ve
location
parametreleri Vertex AI projen/konumunla eşleşiyor mu? • Kimlik doğrulama:
GOOGLE_APPLICATION_CREDENTIALS
ortam değişkeni geçerli servis hesabı anahtarını işaret ediyor mu? • Modelin realtime desteği: Bu modelin (preview/flash live sürümü) real‑time/streaming özellikleri etkin olarak dağıtıldı mı? Muhtemel nedenler (neden bu uyarı çıkıyor) • Real‑time oturum doğru şekilde başlatılmadı ama sunucu yine de içerik gönderiyor — gelen veriyi eşleyecek "active generation" yok. • Model veya dağıtım (deployment) real‑time istekleri bekleyecek şekilde yapılandırılmamış. • Konfigürasyon parametrelerinde (vertexai, project, location, auth) uyumsuzluk var. Benden ne bekleyebilirsin / nasıl ilerleyelim Lütfen aşağıdaki bilgileri paylaş ki daha hedefli yardımcı olabileyim: 1.
generate_reply
çağrısının kısa bir kod/parça örneği (kimlik doğrulama ve ilgili parametrelerle birlikte). 2. Oturumu başlatma ve turn-detection akışını nasıl yapıyorsun? (örn. hangi çağrılar sırasıyla yapılıyor) 3. Vertex AI üzerinde model dağıtımının real‑time/streaming destekli olduğuna dair kısa bilgi (daha önce deploy ettiyseniz deploy ayarları veya ekran görüntüsü faydalı olur). 4. Hata/logların tamamı (varsa önceki INFO/ERROR satırları) — özellikle oturum başlatma zamanındaki loglar. Hızlı kontrol adımları denemek istersen (öncelik sırasına göre) 1. Oturumu başlat — sonra
generate_reply
çağrısı yap — loglarda "generation started" benzeri bir kayıt görünüyor mu? 2.
vertexai=True
,
project
,
location
ve
GOOGLE_APPLICATION_CREDENTIALS
doğru ayarlı mı kontrol et. 3. Aynı çağrıyı basit bir test ortamında (ör. local küçük test oda) tekrar dene; eğer orada çalışıyorsa production dağıtımında oturum yönetimi eksik olabilir. 4. Model versiyonunu ve preview notlarını kontrol et — bazı preview modellerin ek gereksinimleri olabilir. İstersen hemen kodunu veya ilgili konfigürasyon satırlarını paylaş, ben adım adım kontrol edip hangi noktada "active generation" eksikliğinin oluştuğunu birlikte bulayım. Sources: Gemini Live API plugin | LiveKit Documentation
a
llm_model = google.realtime.RealtimeModel( instructions=self._instructions, model="gemini-live-2.5-flash-native-audio", voice="Autonoe", language="tr", vertexai=True, thinking_config=types.ThinkingConfig( thinking_budget=0, ), ) Bu şekilde kullanıyorum.
t
Teşekkürler — paylaştığın
RealtimeModel
konfigürasyonunu gördüm. Bu iyi bir başlangıç; şimdi odaklanmamız gereken nokta gerçek‑zamanlı oturumun nasıl başlatıldığı ve
generate_reply
çağrısının nerede yapıldığı. Kısa özet: şu anki uyarı “sunucudan içerik geliyor ama aktif bir generation yok” diyor — bu genellikle oturum/turn yönetimi ya da modelin realtime dağıtımıyla ilgili bir eşleşme eksikliğinden kaynaklanır. Hemen kontrol etmeni istediğim noktalar • Oturum başlatma akışı:
RealtimeModel
örneğini oluşturduktan sonra gerçek‑zamanlı oturumu nasıl başlatıyorsun? (örn.
start_session
,
connect
veya benzeri bir çağrı var mı).
generate_reply
çağrısını hangi adımda yapıyorsun — oturum gerçekten "active" hale geldikten sonra mı? • Turn detection / audio stream: Mikrofon akışını (veya gönderdiğin audio chunk'ları) modelin beklediği şekilde streaming olarak mı gönderiyorsun? Sunucu stream içerik gönderiyor mu yoksa sadece event/notification mı geliyor? • `thinking_config`:
thinking_budget=0
kullanıyorsun — bunu bilerek 0 mı ayarladın? (Bazen sıfır süre model davranışını etkileyebilir.) • Model tam adı ve sürüm: Kodunda
model="gemini-live-2.5-flash-native-audio"
yazıyor. Daha önce loglarda
gemini-live-2.5-flash-preview-native-audio-09-2025
demiştin. Vertex AI üzerinde deploy ettiğin modelin adı/sürümü bu ile tam aynı mı? • Vertex AI ayarları:
vertexai=True
set edilmiş — ayrıca
project
,
location
parametrelerini çağrının yapıldığı yerde sağlıyor musun?
GOOGLE_APPLICATION_CREDENTIALS
ortam değişkeninin doğru servis hesabını işaret ettiğini doğruladın mı? Hemen deneyebileceğin hızlı testler 1. Basit bir “start session → wait for active event → generate_reply” akışı oluşturup logla: Oturumun gerçekten active olduğuna dair bir log/cevap görüyor musun? 2.
thinking_budget
değerini küçük pozitif bir değere (ör. 1000 ms) değiştirip tekrar dene; 0'ın etkisini gözlemle. 3. Model adını tam sürümle eşleştir: deploy ettiğin tam model ismini (Vertex AI'deki dağıtım adını) kodundaki
model
ile aynı yap. 4. Lokal veya test odasında aynı çağrıyı tekrarla; eğer orada çalışıyorsa production tarafında session init veya network/timeout farkı olabilir. Lütfen paylaşman yararlı olacak bilgiler 1.
generate_reply
çağrısını yaptığın kod bloğunu (gizli anahtar/kimlik bilgileri olmadan). 2. Oturumu başlatma ve event/response loop'unun kısa bir örneği (hangi fonksiyonlar, hangi sıralama). 3. Vertex AI üzerinde deploy ettiğin modelin tam adı/sürümü ve deploy ayarlarından real‑time/streaming özelliğinin açık olup olmadığı bilgisi. Bu bilgileri paylaşırsan, adım adım loglardan hangi noktada “active generation” eksikliğinin oluştuğunu birlikte tespit edelim ve gereken konfigürasyon değişikliklerini öneriyim.
a
""" Greeting Handler ================ Çağrı başladığında kullanıcı selamlamasını bekler ve greeting mesajını yönetir. Her çağrı için ayrı bir handler instance'ı oluşturulur (session-scoped). """ import asyncio from livekit.agents import AgentSession from ..config import Config from .logger import get_logger logger = get_logger("greeting") async def wait_for_user_greeting( session: AgentSession, room_name: str, ) -> bool: """ Kullanıcının ilk açılış konuşmasını bekle. Davranış: - Kullanıcı konuşursa, konuşması bitene kadar bekler ("speaking" -> "listening") - Kullanıcı hiç konuşmazsa timeout sonunda devam eder Args: session: LiveKit AgentSession instance room_name: Room name for logging Returns: True: kullanıcı timeout içinde konuşmaya başladı (bitirmese bile). False: kullanıcı timeout içinde hiç konuşmadı. Note: Bu fonksiyon her çağrı için ayrı çalışır (session-scoped). Event listener'lar session'a bağlı oldukları için izole çalışırlar. """ user_finished_speaking = asyncio.Event() user_started_speaking = False timeout = Config.GREETING_USER_TIMEOUT def on_user_state(ev): nonlocal user_started_speaking if ev.new_state == "speaking": user_started_speaking = True elif ev.new_state == "listening" and user_started_speaking: user_finished_speaking.set() # Event listener'ı kaydet session.on("user_state_changed", on_user_state) try: # Kullanıcı başlayıp konuşmayı tamamladıysa doğrudan devam et. await asyncio.wait_for(user_finished_speaking.wait(), timeout=timeout) return True except asyncio.TimeoutError: if not user_started_speaking: return False # Kullanıcı başladı ama "listening" state'ine dönemedi. # Çok uzun bloklamamak için bir timeout daha bekleyip devam et. try: await asyncio.wait_for(user_finished_speaking.wait(), timeout=timeout) except asyncio.TimeoutError: logger.warning( f"[{room_name}] User started speaking but didn't finish within extra {timeout}s — proceeding" ) return True finally: # Event listener'ı temizle (memory leak önleme) try: session.off("user_state_changed", on_user_state) except (AttributeError, TypeError): pass async def run_greeting_flow( session: AgentSession, room_name: str, greeting: str, ) -> bool: """ Çağrı açılış greeting akışını yönetir. 1) Kullanıcının kısa açılış konuşmasını bekler 2) Agent greeting mesajını generate_reply ile üretip söyler """ user_spoke = await wait_for_user_greeting(session=session, room_name=room_name) await session.generate_reply(instructions=greeting) return user_spoke Kod bu
t
Teşekkürler — paylaştığın handler ve
generate_reply
kullanım akışını gördüm. Seninle özetleyip, sorunu hedefe yönelik daraltmak için hangi kontrolleri yapacağımızı ve birkaç hızlı denenecek adımı paylaşıyorum. Kısa özet (hangi kısmı ele alıyorum) • run_greeting_flow içinde önce kullanıcı konuşmasını bekliyorsun, sonra doğrudan
await session.generate_reply(instructions=greeting)
çağırıyorsun. • Log: "received server content but no active generation" — bu, sunucudan içerik (stream/event) gelmesine rağmen modele/oturuma bağlı aktif bir generation iş parçacığı olmadığını gösteriyor. Genelde oturum/turn yönetimi veya modelin realtime dağıtımıyla eşleşmeme nedeniyle olur. Hemen kontrol etmeni istediğim noktalar • RealtimeModel ve AgentSession ilişkilendirmesi: RealtimeModel nesnesini oluşturduktan sonra bunu AgentSession'a nasıl bağlıyorsun? session içinde modelin aktif olarak kaydedildiğini/başlatıldığını gösteren kodu paylaşabilir misin? • Oturumun "active" olduğundan emin misin? generate_reply çağrısından önce session'ın gerçek‑zamanlı olarak bağlandığına dair (ör.
connect
,
start_session
veya session event'leriyle) bir onay/log görüyor musun? • generate_reply çağrısını tetiklediğin zaman kullanıcıdan gelen veya sunucudan gelen stream/event sıralaması nasıl? (Örn. sunucudan önce event/stream mi geliyor yoksa generate_reply çağrısı önce mi yapılıyor) • Model isim uyuşmazlığı: RealtimeModel oluştururken kullandığın
model="gemini-live-2.5-flash-native-audio"
ile önceki logda gördüğün
gemini-live-2.5-flash-preview-native-audio-09-2025
tam olarak eşleşiyor mu? Vertex AI'de deploy ettiğin tam model adını paylaşabilir misin? • `thinking_config`:
thinking_budget=0
kullanıyorsun — bunu bilinçli olarak 0 yaptın mı? Sıfır bazen davranışı etkileyebilir; test için küçük pozitif bir değere (ör. 1000 ms) ayarlayıp deneyebilir misin? Hızlı test adımları (öncelik sırasına göre) 1. Realtime oturumun aktif olduğunu doğrulayacak bir log ekle: generate_reply çağırmadan önce session’dan "active" veya "ready" event'ini bekleyip logla. Eğer oturum aktif değilse generate_reply çağırma. 2. Basit senaryo: run_greeting_flow içinde user_spoke sonucu ne olursa olsun generate_reply öncesi 1 saniye await asyncio.sleep(1) ekleyip tekrar dene — eğer hata azalırsa timing/sequence sorunu olabilir. 3. thinking_budget test:
thinking_config=types.ThinkingConfig(thinking_budget=1000)
yapıp tekrar dene. 4. Model adı eşleştirme: Vertex AI'de deploy edilen modelin tam sürüm ismini koddaki
model
ile birebir eşleştir. 5. Lokal test/izole ortam: Eğer mümkünse LiveKit AgentSession + RealtimeModel kullanarak minimal bir test akışı oluştur (kullanıcı konuşması simüle edilsin) ve logları karşılaştır. İstediğim/işe yarayacak ek bilgiler (bunları paylaşırsan daha hızlı çözeriz) 1. RealtimeModel'i AgentSession'a nasıl bağladığını gösteren kod bloğu (gizli anahtarlar olmadan). 2. generate_reply çağrısının tam şekli — run_greeting_flow dışında başka parametre veriyor musun? 3. Vertex AI'de deploy ettiğin modelin tam adı/sürümü ve deploy ayarlarından real‑time/streaming seçeneğinin açık olduğuna dair kısa bilgi. 4. Oturum başlangıcına ait log satırları (session connect / start time etrafındaki INFO/DEBUG/ERROR logları). Bir sonraki adım olarak koddan RealtimeModel–AgentSession bağlantısını gösterirsen, oturumun ne zaman active hale geldiğini ve
generate_reply
çağrısının tam sırasını birlikte inceleyeyim. Ardından gereken yerlerde bekleme/log veya konfigürasyon düzeltmesi önerip, nasıl değiştireceğini adım adım yazayım.
a
Realtime oturumun aktif olduğunu doğrulayacak bir log ekle: generate_reply çağırmadan önce session’dan "active" veya "ready" event'ini bekleyip logla. Eğer oturum aktif değilse generate_reply çağırma. Bunu nasıl yaparım? Aktif mi değil mi diye görmek iin.
t
Selam — iyi bir soru. Paylaştığın bağlamla, oturumun "active" veya "ready" olup olmadığını kontrol etmek için en sağlam yol `AgentSession`'ın yayımladığı olayları (event'leri) dinlemek ve
agent_state_changed
içinde
listening
(veya uygun hazır durum) geldiğini görmek. Aşağıda doğrudan kopyalayıp kullanabileceğin, kısa ve net bir örnek ve açıklamalar var. 1) Basit event dinleyici ile loglama (senin mevcut akışa ekle) • Amaç:
generate_reply
çağırmadan önce oturumun hazır olduğunu doğrulamak ve loglamak.
Copy code
import asyncio
from livekit.agents import AgentSession, AgentStateChangedEvent
from .logger import get_logger

logger = get_logger("greeting")

# Bu event, oturumun hazır olduğunu işaret edecek
session_ready = asyncio.Event()

# AgentSession zaten oluşturulmuş ve llm olarak RealtimeModel atanmış olmalı
# session = AgentSession(...)

@session.on("agent_state_changed")
def _on_agent_state_changed(ev: AgentStateChangedEvent):
    <http://logger.info|logger.info>(f"Agent state changed: {ev.new_state}")
    # Genelde 'listening' agent'ın real-time olarak hazır olduğunu gösterir
    if ev.new_state == "listening":
        <http://logger.info|logger.info>("✅ Realtime session is active/ready.")
        session_ready.set()
    elif ev.new_state == "initializing":
        <http://logger.info|logger.info>("🔄 Session initializing...")
    # İstersen diğer durumlar için log ekle: 'thinking', 'speaking', vb.
2) generate_reply çağrısını beklemeye almak • Amaç: Eğer oturum hazır değilse generate_reply'i çağırmamak; belirli bir timeout sonrası hata ile devam etmek isteyebilirsin.
Copy code
async def safe_generate_reply(session: AgentSession, instructions: str, wait_timeout: float = 5.0):
    try:
        # Oturumun hazır olmasını bekle
        await asyncio.wait_for(session_ready.wait(), timeout=wait_timeout)
    except asyncio.TimeoutError:
        logger.warning("Session did not become ready within timeout — skipping generate_reply or handling fallback.")
        return None

    # Oturum hazırsa güvenle çağır
    return await session.generate_reply(instructions=instructions)
3) run_greeting_flow içine entegrasyon (senin kod örneğine göre) • Kullanıcıyı bekledikten sonra generate_reply çağrısını
safe_generate_reply
ile yapabilirsin.
Copy code
async def run_greeting_flow(session: AgentSession, room_name: str, greeting: str) -> bool:
    user_spoke = await wait_for_user_greeting(session=session, room_name=room_name)

    # Güvenli çağrı — önce session'ın listening olduğundan emin olur
    await safe_generate_reply(session=session, instructions=greeting, wait_timeout=5.0)

    return user_spoke
Ek notlar ve tavsiyeler • Hangi agent durumunun "hazır" sayılacağını kontrol et: çoğu entegrasyonda
listening
hazır durumudur; eğer farklı bir durum kullanılıyorsa (ör.
ready
), onu kontrol et. • Timeout değeri: Prod ortamda 3–10 saniye mantıklı olabilir; çok kısa olursa race condition olabilir, çok uzun olursa kullanıcı deneyimi bozulur. • session_ready Event'ini her handler instance için izole et: senin handler'lar session-scoped çalıştığına göre her instance kendi Event'ini kullansın; aksi halde başka session'ların state'leri karışabilir. • Ek debug için: generate_reply hemen çağrıldığında ve hala hata alıyorsan, hem client hem server loglarında
generation_started
,
generation_finished
veya ilgili realtime generation event'lerini arayın. İstersen şimdi: • Mevcut AgentSession oluşturma kodunu paylaş (nasıl instantiate ediyorsun ve nerede start ediyorsun), ben onu örneğe tam entegre edip tam çalışır bir snippet haline getireyim. • Veya hemen `safe_generate_reply`'i koduna ekleyip test et, log çıktılarını paylaşırsan birlikte inceleyelim. Sources: Turns overview | LiveKit Documentation | Agent session | LiveKit Documentation
a
selam, bana realtime da çalışacak end_call function tool yazabilir misin?
t
Selam — isteğine göre realtime (AgentSession + RealtimeModel) ortamında güvenli şekilde çağrıyı sonlandıran bir
end_call
function tool örneği hazırladım. Kod, önce ajanın kısa bir veda cevabı üretmesini, playout tamamlanana kadar beklemesini ve sonra odayı kapatarak tüm katılımcıların bağlantısını sonlandırmasını sağlar. Her adımda hata kontrolü ve loglama bulunur. Not: Aşağıdaki örnekte
AgentSession
,
get_job_context
,
function_tool
ve
RunContext
nesnelerinin senin entegrasyonunda mevcut olduğunu varsayıyorum (paylaştığın bağlama uygun). Gizli/özel bilgileri ortama eklememeye dikkat et. Kod örneği
Copy code
import asyncio
from livekit.agents import function_tool, get_job_context, RunContext
from .logger import get_logger

logger = get_logger("end_call_tool")

@function_tool
async def end_call(ctx: RunContext, farewell_text: str = "Görüşmemiz için teşekkürler, iyi günler. Hoşça kal."):
    """
    Realtime ortamda çağrıyı güvenli şekilde sonlandıran tool.

    Akış:
    1) Ajandan kısa bir veda mesajı oluşturulur (generate_reply).
    2) Ajanın konuşmasının tamamlanmasını bekler (playout beklemesi).
    3) Job context üzerinden odayı siler / oturumu sonlandırır.
    4) Hata durumunda loglama ve uygun dönüş mesajı sağlar.
    """

    session = getattr(ctx, "session", None)
    if session is None:
        logger.error("RunContext içinde session bulunamadı.")
        return {"ok": False, "reason": "session_not_found"}

    # 1) Veda mesajı üret
    try:
        <http://logger.info|logger.info>("Producing farewell audio via generate_reply...")
        await session.generate_reply(instructions=farewell_text)
    except Exception as e:
        logger.exception("generate_reply sırasında hata oluştu.")
        # Hatalı olsa bile alttaki temizlik adımına devam edebiliriz
        # (özellikle oturumun kapatılması kritikse)
    
    # 2) Playout'un bitmesini bekle (kesinlikle bekleyin; aksi halde medya yarıda kesilebilir)
    try:
        # ctx.wait_for_playout() mevcutsa kullan; yoksa küçük bir bekleme koy
        if hasattr(ctx, "wait_for_playout"):
            <http://logger.info|logger.info>("Waiting for playout to finish...")
            await ctx.wait_for_playout()
        else:
            <http://logger.info|logger.info>("ctx.wait_for_playout bulunamadı — kısa bekleme yapılıyor (2s).")
            await asyncio.sleep(2)
    except Exception:
        logger.exception("Playout beklenirken hata oluştu; yine de devam ediyorum.")

    # 3) Odayı sil / oturumu sonlandır
    try:
        job_ctx = get_job_context()
        if not job_ctx or not getattr(job_ctx, "room", None):
            logger.error("Job context veya room bilgisi alınamadı.")
            return {"ok": False, "reason": "job_context_or_room_missing"}

        room_name = job_ctx.room.name
        <http://logger.info|logger.info>(f"Deleting room: {room_name} to end call for all participants...")
        # job_ctx.api.room.delete_room beklenen API çağrısıdır; entegrasyona göre değişebilir
        await job_ctx.api.room.delete_room({"room": room_name})
        <http://logger.info|logger.info>("Room deletion requested successfully.")
        return {"ok": True, "message": "Call ended and room deleted."}
    except Exception as e:
        logger.exception("Odayı silerken hata oluştu.")
        # Alternatif: Eğer delete_room başarısızsa session veya connection kapatılabilir
        try:
            if hasattr(session, "close"):
                await session.close()
                <http://logger.info|logger.info>("Session.close() çağrıldı.")
        except Exception:
            logger.exception("Session kapatılırken hata oluştu.")
        return {"ok": False, "reason": "room_delete_failed", "error": str(e)}
Kısa açıklama ve dikkat edilmesi gerekenler • generate_reply: Veda mesajını ajandan üretir. generate_reply başarısız olsa bile temizlik adımına (odanın kapatılması) geçilmesi genellikle istenir, aksi halde oturum açık kalabilir. • ctx.wait_for_playout(): Bu fonksiyonun varlığı entegrasyonuna bağlıdır; varsa kesinlikle kullan. YOKSA kısa bir bekleme (ör. 1–3s) koymak iyi bir yedek olsa da ideal çözüm
wait_for_playout
kullanmaktır. • get_job_context() ve job_ctx.api.room.delete_room: Bu çağrıların exact signature'ı senin kod tabanına/SDK sürümüne göre farklı olabilir. Örnek kodda delete_room'a dict geçtim; eğer SDK'n delete_room için spesifik bir request sınıfı bekliyorsa onu kullan. • Alternatif temizlik: delete_room başarısızsa
session.close()
veya altta kalan bağlantıları kapatma adımları eklenmelidir. • Logging: Her adım loglanıyor; prod ortamda daha fazla telemetri eklemek faydalı olur. İstersen ben: • Senin kullandığın job_ctx.api.room.delete_room çağrı imzasını paylaşırsan kodu tam olarak sana uygun hale getireyim. • Veya
ctx.wait_for_playout()
vs yoksa nasıl playout bekleme ekleyeceğini içeren bir varyant atayım. Hazırsan, alttaki bilgileri paylaş: • job_ctx.api.room.delete_room fonksiyonunun tam imzası (veya hata alıyorsan ilgili exception trace), • ctx içinde hangi yardımcı fonksiyonların mevcut olduğu (öz.
wait_for_playout
,
session.close
). Sources: Agents telephony integration | LiveKit Documentation | Why is there extra audio at the end of my recordings of an agent session? |...
a
async def disconnect_sip_participants(ctx: JobContext) -> bool: """ Disconnect all SIP participants from the room. This properly terminates SIP/phone calls. Args: ctx: LiveKit job context Returns: True if any SIP participant was disconnected """ disconnected = False room_name = ctx.room.name try: for participant in list(ctx.room.remote_participants.values()): if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP: await ctx.api.room.remove_participant( api.RoomParticipantIdentity( room=room_name, identity=participant.identity, ) ) disconnected = True except Exception as e: logger.error(f"❌ Failed to disconnect SIP participants: {e}") return disconnected def extract_sip_info( participant: rtc.RemoteParticipant, ) -> tuple[str, str, str, Dict[str, str]]: """ Extract caller, called number, trunk ID and SIP headers from participant. Returns: (caller_number, called_number, sip_trunk_id, sip_headers) """ # Extract raw numbers raw_called = ( participant.attributes.get("sip.trunkPhoneNumber") or participant.attributes.get("sip.trunk_phone_number") or "" ) raw_caller = ( participant.attributes.get("sip.phoneNumber") or participant.attributes.get("sip.phone_number") or "" ) # Extract SIP Trunk ID (both camelCase and camelCase variants) sip_trunk_id = ( participant.attributes.get("sip.trunkID") or participant.attributes.get("sip.trunkId") or "" ) # Normalize called_number = raw_called caller_number = raw_caller # Extract headers sip_headers = {} for k, v in participant.attributes.items(): sip_headers[k] = v # Also clean sip.h. prefix for easier access if k.startswith("sip.h."): sip_headers[k.replace("sip.h.", "")] = v return caller_number, called_number, sip_trunk_id, sip_headers def log_participant_info( participant: rtc.RemoteParticipant, caller: str, called: str, sip_headers: Dict[str, str], ): """ Katılımcı bilgilerini log'a yazar. SIP ve web katılımcıları için farklı formatlarda log çıktısı üretir. SIP çağrıları için arayan/aranan numaraları ve SIP header'ları gösterir. Args: participant: LiveKit RemoteParticipant nesnesi caller: Arayan telefon numarası (normalize edilmiş) called: Aranan telefon numarası / DID (normalize edilmiş) sip_headers: SIP protokol header'ları (sip.* attribute'ları) Log formatı: - SIP: "📞 SIP Call: +905551234567 → +902127067294" - Web: "💻 Web participant: web-user-123" """ if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP: logger.info(f"📞 SIP Call: {caller} → {called}") logger.info(f"📨 SIP Attributes: {sip_headers}") else: logger.info(f"💻 Web participant: {participant.identity}") async def hangup( ctx: JobContext, reason: str = "Call ended", ) -> bool: """ End the phone call by disconnecting SIP participants, then shutting down the agent job. Steps: 1. Disconnect SIP participants (hangs up the phone) 2. Call ctx.shutdown() to properly close the session/job Args: ctx: LiveKit job context reason: Reason for ending the call (logged only) Returns: True if successful, False otherwise """ room_name = ctx.room.name try: # Hang up phone legs only (room is not force-deleted). sip_disconnected = await disconnect_sip_participants(ctx) if not sip_disconnected: logger.warning("No SIP participants to disconnect") # Properly shutdown the session to stop event handlers/tasks. ctx.shutdown(reason) return True except Exception as e: logger.error(f"❌ Failed to hangup {room_name}: {e}") # Last resort: try shutdown anyway try: ctx.shutdown(reason) return True except Exception: return False Telefonu kapatmak için bunu kullanıyorum. Python agents versiyonum 1.4.2 bunu daha basit ve güvenli şekilde nasıl yazabilirim?
t
Selam — paylaştığın kodu inceledim. Amaç: SIP katılımcılarını güvenli şekilde ayırıp (hang up) oturumu/işi düzgün kapatmak. Mevcut akış mantıklı; ben bunu daha basit, güvenli ve hata toleransı yüksek bir hale getirecek şekilde yeniden düzenledim. Aşağıda hem geliştirilmiş kod hem de kısa açıklamalar var. Python agents v1.4.2 ile uyumlu olacak şekilde minimal ve anlaşılır tuttum. Önerilen değişikliklerin ana fikirleri • Tek bir ana fonksiyon (end_call) ile SIP bağlantılarını ayır, logla, ardından güvenli shutdown yap. • Her adımı try/except ile izole et; bir adım başarısız olsa bile temizlik adımlarına devam et. • Daha kısa, okunabilir yardımcı fonksiyonlar: sip disconnect, participant logging. • Ağ/uzun işlem durumları için opsiyonel timeout veya retry eklenebilir (şimdilik basit tutuyoruz). Kod — basit, güvenli end_call tool
Copy code
import asyncio
from typing import Dict, Tuple
from livekit import rtc, api
from livekit.agents import get_job_context
from .logger import get_logger

logger = get_logger("end_call")

async def _disconnect_sip_participants(ctx) -> bool:
    """
    Disconnect all SIP participants. Returns True if any were disconnected.
    """
    disconnected = False
    room_name = ctx.room.name

    try:
        # copy to list to avoid runtime dict size changes
        participants = list(ctx.room.remote_participants.values())
        for participant in participants:
            if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
                try:
                    await ctx.api.room.remove_participant(
                        api.RoomParticipantIdentity(room=room_name, identity=participant.identity)
                    )
                    <http://logger.info|logger.info>(f"Disconnected SIP participant: {participant.identity}")
                    disconnected = True
                except Exception as e:
                    logger.exception(f"Failed to remove SIP participant {participant.identity}: {e}")
    except Exception as e:
        logger.exception(f"Failed while iterating participants to disconnect SIPs: {e}")

    return disconnected

def _extract_sip_info(participant: rtc.RemoteParticipant) -> Tuple[str, str, str, Dict[str, str]]:
    """
    Return (caller_number, called_number, sip_trunk_id, sip_headers)
    """
    attrs = participant.attributes or {}
    raw_called = attrs.get("sip.trunkPhoneNumber") or attrs.get("sip.trunk_phone_number") or ""
    raw_caller = attrs.get("sip.phoneNumber") or attrs.get("sip.phone_number") or ""
    sip_trunk_id = attrs.get("sip.trunkID") or attrs.get("sip.trunkId") or ""

    sip_headers = {}
    for k, v in attrs.items():
        sip_headers[k] = v
        if k.startswith("sip.h."):
            sip_headers[k.replace("sip.h.", "")] = v

    return raw_caller, raw_called, sip_trunk_id, sip_headers

def _log_participant_info(participant: rtc.RemoteParticipant):
    caller, called, _, sip_headers = _extract_sip_info(participant)
    if participant.kind == rtc.ParticipantKind.PARTICIPANT_KIND_SIP:
        <http://logger.info|logger.info>(f"📞 SIP Call: {caller} → {called} (identity={participant.identity})")
        logger.debug(f"📨 SIP Attributes: {sip_headers}")
    else:
        <http://logger.info|logger.info>(f"💻 Web participant: {participant.identity}")

async def end_call(ctx, reason: str = "Call ended") -> bool:
    """
    Cleanly end the call:
    1) Log participants
    2) Disconnect SIP participants (hang up phone legs)
    3) Shutdown the job/context to clean tasks and handlers

    Returns True if shutdown was attempted (successful or best-effort), False on catastrophic failure.
    """
    room_name = getattr(ctx.room, "name", "<unknown>")
    <http://logger.info|logger.info>(f"Ending call for room {room_name} — reason: {reason}")

    # 1) Log current participants for debugging/telemetry
    try:
        for p in list(ctx.room.remote_participants.values()):
            _log_participant_info(p)
    except Exception:
        logger.exception("Failed to enumerate/log participants")

    # 2) Disconnect SIP participants (best-effort)
    try:
        sip_disconnected = await _disconnect_sip_participants(ctx)
        if not sip_disconnected:
            <http://logger.info|logger.info>("No SIP participants found to disconnect.")
    except Exception:
        logger.exception("Unexpected error while disconnecting SIP participants")

    # 3) Shutdown the job/context to stop agent tasks and handlers
    try:
        # ctx.shutdown is synchronous in your sample; wrap in try to ensure it runs.
        ctx.shutdown(reason)
        <http://logger.info|logger.info>("ctx.shutdown() called successfully.")
        return True
    except Exception as e:
        logger.exception(f"ctx.shutdown() failed: {e}")
        # As last resort, try any session-level close if available
        try:
            session = getattr(ctx, "session", None)
            if session is not None and hasattr(session, "close"):
                await session.close()
                <http://logger.info|logger.info>("session.close() called as fallback.")
        except Exception:
            logger.exception("Fallback session.close() also failed.")
        return False
Nasıl kullanırsın • JobContext veya benzeri ctx'i alıp doğrudan
await end_call(ctx, "Kullanıcı isteği")
şeklinde çağır. • Eğer ajanın kendisi bir function_tool kullanıyorsa, bu fonksiyonu o tool içinde çağırabilirsin. Neden bu yapı daha güvenli/sade • Fonksiyonlar izole: disconnect, extract, log işlevleri ayrı; testi ve bakımı kolay. • Her adımda hatayı yakalayıp logluyoruz; uç noktada temizlik adımlarına devam ediyoruz. • ctx.shutdown çağrısı tek seferde çağrılıyor; başarısız olursa session.close fallback ile en azından bağlantı kapatılmaya çalışılıyor. Opsiyonel iyileştirmeler • remove_participant çağrıları için retry/backoff ekle (ağ hataları için). • Eğer
ctx.shutdown
asenkron bir versiyonunu destekliyorsa onu kullan (ör. await ctx.shutdown()). • SIP disconnect sırasında paralel işlem istersen asyncio.gather ile paralel çalıştırılabilir; ancak rate-limit veya API kısıtlamalarına dikkat et. • Telemetri/metric: kaç SIP hattı kapatıldı, ne kadar sürdü gibi metrik ekleyebilirsin. İstersen ben: • Bu kodu senin function_tool sarmalayıcına (ör. @function_tool) uygun hale getirip örnek agent içi kullanım snippet'i ekleyeyim. • Ya da delete_room gibi alternatif bir tam sonlandırma (tüm oda kapanışı) ekleyeyim — hangi davranışı tercih ediyorsun: yalnızca SIP hangup (şimdiki) mı yoksa oda silme ile tam sonlandırma mı? Hangi seçeneği tercih ediyorsun, ben ona göre snippet'i final hâline getirip sana uyarlayayım.