This message was deleted.
# ask-ai
s
This message was deleted.
t
Thanks for reaching out, Anigilaje. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I looked at the logs you pasted. I’m responding to the "'AgentSession' object has no attribute 'chat_ctx'" error and the repeated "worker connection closed unexpectedly" warnings. Summary of causes and what you can do next • The AttributeError means code is trying to read
chat_ctx
directly from an AgentSession. The session does not hold the chat context; the Agent does. Fix any access like
session.chat_ctx
to
session.agent.chat_ctx
or
self.chat_ctx
inside the Agent. ◦ If you need to modify context programmatically, call
agent.update_chat_ctx(...)
(or use
session.agent.chat_ctx
) after ensuring the Agent is initialized. ◦ Add a defensive check during shutdown to avoid accessing context after the Agent has been torn down. • The "worker connection closed unexpectedly" warnings show the worker’s websocket to
<wss://dpi-kccd98ec.livekit.cloud>
is being closed and then the worker re-registers. Common causes: ◦ Network/firewall blocking or intermittent connectivity. ◦ Worker crash or overload causing abrupt close. ◦ Server-side timeout/auth issue or region-specific routing problems. • TTS logs: one TTS attempt returned
ttfb=-1.0
(failed/aborted), later one succeeded but with high TTFB (~13s). That points to occasional TTS service latency or interruptions. Actionable next steps (please try and tell me results) 1. Search code for any
session.chat_ctx
usage and change to
session.agent.chat_ctx
or access via the Agent instance. If you’d like, paste the small snippet that accesses chat context and I’ll show the exact fix. 2. Ensure agents are fully initialized before session code touches chat context (verify
session.start(agent=...)
or equivalent is completed). 3. Add a try/except around context access during shutdown to avoid the race condition. 4. For the websocket disconnects: ◦ Verify network allows outbound WebSocket to the LiveKit host and there are no intermittent drops. ◦ Check worker logs around the disconnect for any crash/exception prior to the close. ◦ Confirm worker tokens/credentials haven’t expired. ◦ If you suspect region/routing, try running a worker in another region near your users. 1. For TTS latency, check the TTS provider health in the same timeframe and retry with a smaller payload to reproduce. If you want, share: • The code snippet where chat context is accessed (lines around session use). • The worker logs just before a disconnect (to see if the worker crashed). With one of those I can give the exact code change or point to the precise log cause. Sources: Agent session | LiveKit docs | Agents (Python) | livekit/livekit_composite | DeepWiki | Agents (Node.js) | livekit/livekit_composite | DeepWiki
m
temp_agent = WAECExamAgent(user_id=user_id, student_name=student_name) chat_ctx = await temp_agent.load_chat_history() # Create agent with loaded prompt from file agent = WAECExamAgent( user_id=user_id, student_name=student_name, chat_ctx=chat_ctx, instructions_text=prompt_text ) logger.info("=" * 80) logger.info(f"🤖 AGENT INITIALIZED") logger.info(f"Prompt file used: {prompt_file_used}") logger.info(f"Instructions loaded: {'YES ✅' if prompt_text else 'NO - Using fallback ⚠️'}") if prompt_text: logger.info(f"Instructions length: {len(prompt_text)} characters") logger.info("=" * 80) # Create session with agent that has the correct prompt session = AgentSession( vad=ctx.proc.userdata["vad"], llm=openai.LLM(base_url="https://llama3b.publicaai.com/v1", api_key="publicaai-7d50feb52ff8ab0dca7880c3fc"), stt=openai.STT(model="whisper-1"), tts=openai.TTS( base_url="https://xtts.publicaai.com/v1", model="nigerian-english-xtts", voice="female" ), ) usage_collector = metrics.UsageCollector() @session.on("metrics_collected") def _on_metrics_collected(ev: MetricsCollectedEvent): metrics.log_metrics(ev.metrics) usage_collector.collect(ev.metrics) # Start session with agent (already connected to room above) await session.start( agent=agent, room=ctx.room, room_input_options=RoomInputOptions(text_enabled=True, audio_enabled=True ), room_output_options=RoomOutputOptions( transcription_enabled=True, audio_enabled=True ), ) # Update log context with participant info ctx.log_context_fields["user_id"] = user_id logger.info(f"Agent initialized with prompt for subject: {subject or 'default'}, topic: {topic or 'core'}") # Define shutdown callbacks async def log_usage(): summary = usage_collector.get_summary() logger.info(f"Usage summary: {summary}") async def save_conversation_on_shutdown(): try: await agent.save_chat_history(session.chat_ctx) logger.info(f"Conversation saved successfully for user: {user_id}") except Exception as e: logger.error(f"Error saving conversation: {str(e)}") async def delete_room(): try: async with api.LiveKitAPI( os.getenv("LIVEKIT_URL"), os.getenv("LIVEKIT_API_KEY"), os.getenv("LIVEKIT_API_SECRET") ) as api_client: await api_client.room.delete_room(api.DeleteRoomRequest( room=ctx.room.name, )) logger.info("Room deleted successfully") except Exception as e: logger.error(f"Error deleting room: {str(e)}") ctx.add_shutdown_callback(save_conversation_on_shutdown) ctx.add_shutdown_callback(log_usage) ctx.add_shutdown_callback(delete_room) tis is he code
t
Thanks — I reviewed your code. You’re hitting the AttributeError because you call
await agent.save_chat_history(session.chat_ctx)
during shutdown, but
AgentSession
does not expose
chat_ctx
. The chat context lives on the Agent (or inside the Agent instance), so accessing it from
session
causes the error when the session is torn down. Simple fixes (pick one) 1) Use the Agent’s context directly • Pass the Agent instance to save_chat_history:
Copy code
await agent.save_chat_history(agent.chat_ctx)
2) Access session.agent.chat_ctx (if session.agent is guaranteed) • Safer if you prefer to reference the session:
Copy code
if getattr(session, "agent", None):
    await agent.save_chat_history(session.agent.chat_ctx)
3) Defensive/shutdown-safe approach • Avoid accessing context after teardown and wrap in try/except:
Copy code
try:
    chat_ctx = getattr(session, "agent", None) and session.agent.chat_ctx
    if chat_ctx:
        await agent.save_chat_history(chat_ctx)
except Exception as e:
    logger.error(f"Error saving conversation: {e}")
Notes and small improvements • Ensure the Agent is fully initialized before calling
session.start(...)
(you already construct Agent then start session — good). • Add the defensive check because shutdown race conditions can remove the agent/session before callbacks run. • If
agent.save_chat_history
expects a specific type, pass exactly that (e.g.,
agent.chat_ctx
) rather than None. Optional: If you paste the
WAECExamAgent.save_chat_history
signature or
Agent
class snippet, I’ll give the exact one-line replacement to use.
m
async def entrypoint(ctx: JobContext): # Set log context fields early ctx.log_context_fields = { "room": ctx.room.name, } # IMPORTANT: Get participant info first to know student details await ctx.connect() participant = await ctx.wait_for_participant() user_id = participant.identity student_name = participant.name logger.info("=" * 80) logger.info(f"🎓 VOICE AGENT SESSION STARTED") logger.info(f"Participant: {student_name} (ID: {user_id})") # Get subject and topic from participant metadata (sent by frontend in token) subject = None topic = None prompt_text = None prompt_file_used = "default_fallback" if participant.metadata: try: metadata = json.loads(participant.metadata) subject = metadata.get("subject") topic = metadata.get("topic") logger.info(f"📚 Subject: {subject or 'Not specified'}") logger.info(f"📖 Topic: {topic or 'Not specified (will use core)'}") # Load prompt from file BEFORE creating agent if subject: prompt_text = await load_prompt_from_file(subject, topic) if prompt_text: if topic: prompt_file_used = f"prompts/{subject}/{topic}.txt" logger.info(f"✅ USING TOPIC PROMPT: prompts/{subject}/{topic}.txt") else: prompt_file_used = f"prompts/{subject}/core_{subject}_tutor.txt" logger.info(f"✅ USING CORE PROMPT: prompts/{subject}/core_{subject}_tutor.txt") logger.info(f"📝 Prompt preview (first 200 chars): {prompt_text[:200]}...") else: logger.warning(f"⚠️ No prompt file found for {subject}/{topic}, using fallback") else: logger.warning(f"⚠️ No subject specified, using default prompt") except Exception as e: logger.error(f"❌ Error parsing participant metadata: {e}") else: logger.warning("⚠️ No participant metadata found, using default prompt") logger.info("=" * 80) # Load chat history temp_agent = WAECExamAgent(user_id=user_id, student_name=student_name) chat_ctx = await temp_agent.load_chat_history() # Create agent with loaded prompt from file agent = WAECExamAgent( user_id=user_id, student_name=student_name, chat_ctx=chat_ctx, instructions_text=prompt_text ) logger.info("=" * 80) logger.info(f"🤖 AGENT INITIALIZED") logger.info(f"Prompt file used: {prompt_file_used}") logger.info(f"Instructions loaded: {'YES ✅' if prompt_text else 'NO - Using fallback ⚠️'}") if prompt_text: logger.info(f"Instructions length: {len(prompt_text)} characters") logger.info("=" * 80) # Create session with agent that has the correct prompt session = AgentSession( vad=ctx.proc.userdata["vad"], llm=openai.LLM(base_url="https://llama3b.publicaai.com/v1", api_key="publicaai-7d50feb52ff8ab0dca7880c3fc"), stt=openai.STT(model="whisper-1"), tts=openai.TTS( base_url="https://xtts.publicaai.com/v1", model="nigerian-english-xtts", voice="female" ), ) usage_collector = metrics.UsageCollector() @session.on("metrics_collected") def _on_metrics_collected(ev: MetricsCollectedEvent): metrics.log_metrics(ev.metrics) usage_collector.collect(ev.metrics) # Start session with agent (already connected to room above) await session.start( agent=agent, room=ctx.room, room_input_options=RoomInputOptions(text_enabled=True, audio_enabled=True ), room_output_options=RoomOutputOptions( transcription_enabled=True, audio_enabled=True ), ) # Update log context with participant info ctx.log_context_fields["user_id"] = user_id
t
Thanks — I reviewed your full entrypoint and the shutdown callback. You’re seeing the AttributeError because your shutdown callback does: await agent.save_chat_history(session.chat_ctx) but
AgentSession
does not expose
chat_ctx
. The chat context lives on the Agent instance. That call can fail (especially during shutdown) because
session.chat_ctx
is invalid or the session/agent was torn down. Recommended minimal fix • Replace the save call to use the Agent’s context directly:
Copy code
await agent.save_chat_history(agent.chat_ctx)
Safer shutdown-safe version • Use a guarded pattern to avoid race conditions if the agent/session has been cleared:
Copy code
try:
    chat_ctx = getattr(session, "agent", None)
    if chat_ctx:
        await agent.save_chat_history(session.agent.chat_ctx)
except Exception as e:
    logger.error(f"Error saving conversation: {e}")
Notes and quick suggestions • You already create and pass
agent
into
session.start(...)
— so
agent.chat_ctx
should hold the history you loaded. • Keep the defensive guard in the shutdown callback because shutdown ordering can remove session/agent before callbacks run. • If
save_chat_history
expects a specific object type, pass exactly
agent.chat_ctx
(not None). If you’d like, paste the
WAECExamAgent.save_chat_history
signature and I’ll give the exact one-line replacement and the final callback body to drop into your code.