chilly-nest-87883
07/22/2025, 5:18 AM_self_.chat_ctx.append_message(ChatMessage(_role_=ChatRole.SYSTEM, _content_=summary)) ? and is there specific moments when I can't add anything? also will they persist during the whole conversation? cause I want them totall-airline-16756
07/22/2025, 5:18 AMtall-airline-16756
07/22/2025, 5:19 AMadd_message method on the ChatContext object with the role set to `system`:
chat_ctx.add_message(role="system", content="Your system message here")
For instruction-type system messages that should always be present at the start of the context, use `update_instructions`:
from livekit.agents.voice.generation import update_instructions
update_instructions(chat_ctx, instructions="Your system prompt here", add_if_missing=True)
Persistence and Restrictions:
• Messages added with turn_ctx.add_message() inside pipeline hooks are not persisted beyond the current turn.
• To make a system message persist for the whole conversation, add it directly to the main chat context and call update_chat_ctx after adding:
turn_ctx.add_message(role="system", content="Your system message")
await self.update_chat_ctx(turn_ctx)
• There are no strict restrictions on when you can add system messages, but persistence depends on where and how you add them.
System messages added to the main chat context (not just within a single turn) will persist for the entire conversation.
Sources: livekit-plugins/livekit-plugins-aws/livekit/plugins/aws/experimental/realti... | Pipeline nodes and hooks | LiveKit Docschilly-nest-87883
07/22/2025, 5:28 AMAttributeError: '_ReadOnlyChatContext' object has no attribute 'append_message', is it because LiveKit provides a read-only view of the chat context during tool execution to prevent mid-turn mutations?tall-airline-16756
07/22/2025, 5:29 AMchat_ctx = agent.chat_ctx.copy()
2. Go ahead and tweak that copy (like chat_ctx.add_message(...))
3. Update your agent's context: await agent.update_chat_ctx(chat_ctx)
This method keeps everything clean, safe, and exactly how you want it. Pretty neat, right?
Sources: livekit-agents/livekit/agents/voice/agent_activity.pychilly-nest-87883
07/22/2025, 4:16 PMif summary:
shared_chat_ctx.append_message(ChatMessage(role=ChatRole.SYSTEM, content=summary))
Cause I am getting this error:
WARNING livekit-agent - [session_1753200775436_q6mmit3] Error creating system summary for start_demo: '_ReadOnlyChatContext' object has no attribute 'append_message'tall-airline-16756
07/22/2025, 4:17 PMmodifiable_ctx = shared_chat_ctx.copy()
2. Now, go ahead and add your message:
modifiable_ctx.add_message(ChatMessage(role=ChatRole.SYSTEM, content=summary))
3. Finally, update the agent's context:
await agent.update_chat_ctx(modifiable_ctx)
This approach keeps things smooth and ensures your changes stick around. Trust me, trying to modify the read-only context directly is just going to give you a headache – so stick with this method, and you'll be golden!
Sources: Agents 0.x migration guide | LiveKit Docs | livekit-agents/livekit/agents/voice/generation.py | Pipeline nodes and hooks | LiveKit Docs | Voice pipeline nodes | LiveKit Docschilly-nest-87883
07/22/2025, 5:54 PMtall-airline-16756
07/22/2025, 5:55 PM'_LiteralSpecialForm' object has no attribute 'SYSTEM' error is actually pretty straightforward to fix in LiveKit's Python implementation.
Here's the deal: ChatRole works a bit differently in Python compared to TypeScript or JavaScript. Instead of using an enum, you'll want to use a string directly.
Here's how to do it right:
modifiable_ctx.add_message(ChatMessage(role="system", content=summary))
Just use the string "system" directly for the role. And just so you know, you can also use "developer", "system", "user", or "assistant".
Give this a try, and I bet the error will disappear! Let me know if you need any more help.
Sources: livekit-agents/livekit/agents/voice/generation.py | plugins/test/src/llm.ts | Vision Agent Quickstart | LiveKit Docschilly-nest-87883
07/22/2025, 6:02 PMsummary = "Transferred to Q&A agent for detailed question answering."
And here is what I use, the example you gave me:
modifiable_ctx = shared_chat_ctx.copy() # Mutable copy
modifiable_ctx.add_message(ChatMessage(role="system", content=summary))tall-airline-16756
07/22/2025, 6:04 PMcontent field in ChatMessage to be a list, not just a plain string. This helps support different types of content.
No worries though, I've got two easy ways to fix this for you!
First option: Just wrap your summary in a list when creating the `ChatMessage`:
modifiable_ctx.add_message(ChatMessage(role="system", content=[summary]))
Or if you prefer, you can use the add_message method, which will automatically handle the list conversion for you:
modifiable_ctx.add_message(role="system", content=summary)
Try either of these, and you'll be all set - that pesky validation error will be gone in no time! Let me know if you need any more help. 👍
Sources: livekit-plugins/livekit-plugins-aws/livekit/plugins/aws/experimental/realti...chilly-nest-87883
07/22/2025, 6:44 PMtall-airline-16756
07/22/2025, 6:46 PMchat_ctx.copy(), be careful with those extra parameters like exclude_instructions. They might accidentally filter out some of your messages. Pro tip: Just use copy() without any exclusion arguments to keep everything safe and sound!
• Don't forget to update: Think of update_chat_ctx() like saving your work. Messages you add with add_message() won't stick around unless you call await agent.update_chat_ctx(chat_ctx). It's like hitting the save button!
• Passing the context baton: If you're switching between agents, make sure you're passing the entire chat context. No cutting corners or trimming - give them the full story!
Here's a quick and easy way to keep all your messages safe:
1. Copy your chat context using chat_ctx.copy() - and keep it clean, no exclusions!
2. Add your summary or system message with add_message.
3. Call await agent.update_chat_ctx(chat_ctx) to make it official.
And just like that, you'll have your new message in the mix while keeping all your previous chats intact. Easy peasy!
Sources: livekit-agents/livekit/agents/voice/generation.py | Voice pipeline nodes | LiveKit Docs | Pipeline nodes and hooks | LiveKit Docs | complex-agents/personal_shopper/personal_shopper.py | Workflows | LiveKit Docschilly-nest-87883
07/22/2025, 8:32 PMtall-airline-16756
07/22/2025, 8:34 PMchat_ctx. Here's a friendly step-by-step guide:
1. First, create a copy of the chat context: modifiable_ctx = chat_ctx.copy()
2. Now you can freely make your changes to modifiable_ctx (like adding new messages)
3. Before you wrap up, just call await agent.update_chat_ctx(modifiable_ctx) to save your updates
Don't worry about directly changing the agent's chat_ctx property – that's a no-go. But with this copy-and-update method, you'll have no trouble making sure your changes stick. Hope this helps make things crystal clear!
Sources: Voice pipeline nodes | LiveKit Docs | livekit-agents/livekit/agents/voice/generation.py | Agents v0.x migration guide | LiveKit Docschilly-nest-87883
07/22/2025, 8:35 PMERROR livekit.agents - exception occurred while executing tool {"function": "handoff", "speech_id": "speech_c9e4fd4d7a44"}
Traceback (most recent call last):
File "/Users/clovishublin/Desktop/mirror-repos/livekit-agent-service/venv/lib/python3.9/site-packages/livekit/agents/voice/generation.py", line 517, in _traceable_fnc_tool
val = await function_callable()
File "/Users/clovishublin/Desktop/mirror-repos/livekit-agent-service/main.py", line 1984, in handoff
self.chat_ctx = modifiable_ctx # Update local reference
AttributeError: can't set attribute
For this code:
def _create_handoff_tool(self):
"""Create the handoff function tool"""
@function_tool(description=QUALIFICATION_HANDOFF_DESC)
async def handoff(context: RunContext, reason: str, target_agent: str):
<http://logger.info|logger.info>(f"[{self.session_id}] :arrows_counterclockwise: QualificationAgent handing off to {target_agent} - reason: {reason}")
# Register this function call with correct agent context BEFORE any transitions
# Extract call_id from context or generate one - this is the ground truth moment
call_id = getattr(context, 'call_id', f"handoff_{time.time()}")
self.session_collector.register_function_call(call_id, "QualificationAgent")
# Mark handoff as completed to prevent further text generation
self._handoff_completed = True
<http://logger.info|logger.info>(f"[{self.session_id}] :arrows_counterclockwise: QualificationAgent handoff completed - text generation disabled")
# Add summary BEFORE agent switch to preserve full context
summary = f"The conversation is being handed off to the {target_agent} agent because: {reason}."
modifiable_ctx = self.chat_ctx.copy() # Full copy of current context
modifiable_ctx.add_message(role="system", content=summary)
await self.update_chat_ctx(modifiable_ctx)
self.chat_ctx = modifiable_ctx # Update local reference
<http://logger.info|logger.info>(f"[{self.session_id}] :memo: Added handoff summary to chat_ctx before switch: {summary}")
# Get shared providers to reuse WebSocket connections
shared_stt, shared_llm, shared_tts = self.session_collector.get_shared_providers()
# ALWAYS return to orchestrator - no direct handoffs to other agents
orchestrator_agent = OrchestratorAgent(
self.agent_config,
room_name=self.room_name,
google_plugin_available=self.google_plugin_available,
chat_ctx=self.chat_ctx, # Use current agent's chat context (with summary)
session_collector=self.session_collector, # CRITICAL: Pass shared collector
shared_stt=shared_stt, # CRITICAL: Reuse STT to prevent new connections
shared_llm=shared_llm, # CRITICAL: Reuse LLM to prevent new connections
shared_tts=shared_tts, # CRITICAL: Reuse TTS to prevent new connections
is_initial_agent=False # This is not the initial agent anymore
)
# Record agent transition in shared collector WITH new instance
self.session_collector.record_agent_transition(
"QualificationAgent",
"OrchestratorAgent",
reason,
new_agent_instance=orchestrator_agent
)
# Return agent without message for silent handoff
return orchestrator_agent
return handofftall-airline-16756
07/22/2025, 8:36 PMAttributeError: can't set attribute. This happens because self.chat_ctx in LiveKit Python agents is actually a read-only property, which means you can't just assign a new value to it directly.
No worries, though! Here's the right way to update your chat context:
# Instead of this (which causes the error):
# self.chat_ctx = modifiable_ctx
# Use this:
await self.update_chat_ctx(modifiable_ctx)
The update_chat_ctx() method is your go-to solution here. It's the recommended way to make changes to your chat context, and it'll help you avoid those pesky attribute errors while keeping everything running smoothly.
Sources: Agents v0.x migration guide | LiveKit Docs | livekit-agents/livekit/agents/llm/chat_context.pychilly-nest-87883
07/22/2025, 8:39 PMERROR livekit.agents - exception occurred while executing tool {"function": "handoff", "speech_id": "speech_c9e4fd4d7a44"}
Traceback (most recent call last):
File "/Users/clovishublin/Desktop/mirror-repos/livekit-agent-service/venv/lib/python3.9/site-packages/livekit/agents/voice/generation.py", line 517, in _traceable_fnc_tool
val = await function_callable()
File "/Users/clovishublin/Desktop/mirror-repos/livekit-agent-service/main.py", line 1984, in handoff
self.chat_ctx = modifiable_ctx # Update local reference
AttributeError: can't set attribute
For this code:
def _create_handoff_tool(self):
"""Create the handoff function tool"""
@function_tool(description=QUALIFICATION_HANDOFF_DESC)
async def handoff(context: RunContext, reason: str, target_agent: str):
<http://logger.info|logger.info>(f"[{self.session_id}] :arrows_counterclockwise: QualificationAgent handing off to {target_agent} - reason: {reason}")
# Register this function call with correct agent context BEFORE any transitions
# Extract call_id from context or generate one - this is the ground truth moment
call_id = getattr(context, 'call_id', f"handoff_{time.time()}")
self.session_collector.register_function_call(call_id, "QualificationAgent")
# Mark handoff as completed to prevent further text generation
self._handoff_completed = True
<http://logger.info|logger.info>(f"[{self.session_id}] :arrows_counterclockwise: QualificationAgent handoff completed - text generation disabled")
# Add summary BEFORE agent switch to preserve full context
summary = f"The conversation is being handed off to the {target_agent} agent because: {reason}."
modifiable_ctx = self.chat_ctx.copy() # Full copy of current context
modifiable_ctx.add_message(role="system", content=summary)
await self.update_chat_ctx(modifiable_ctx)
self.chat_ctx = modifiable_ctx # Update local reference
<http://logger.info|logger.info>(f"[{self.session_id}] :memo: Added handoff summary to chat_ctx before switch: {summary}")
# Get shared providers to reuse WebSocket connections
shared_stt, shared_llm, shared_tts = self.session_collector.get_shared_providers()
# ALWAYS return to orchestrator - no direct handoffs to other agents
orchestrator_agent = OrchestratorAgent(
self.agent_config,
room_name=self.room_name,
google_plugin_available=self.google_plugin_available,
chat_ctx=self.chat_ctx, # Use current agent's chat context (with summary)
session_collector=self.session_collector, # CRITICAL: Pass shared collector
shared_stt=shared_stt, # CRITICAL: Reuse STT to prevent new connections
shared_llm=shared_llm, # CRITICAL: Reuse LLM to prevent new connections
shared_tts=shared_tts, # CRITICAL: Reuse TTS to prevent new connections
is_initial_agent=False # This is not the initial agent anymore
)
# Record agent transition in shared collector WITH new instance
self.session_collector.record_agent_transition(
"QualificationAgent",
"OrchestratorAgent",
reason,
new_agent_instance=orchestrator_agent
)
# Return agent without message for silent handoff
return orchestrator_agent
return handofftall-airline-16756
07/22/2025, 8:39 PMAttributeError: can't set attribute is happening because of this line in your code:
self.chat_ctx = modifiable_ctx # Update local reference
Just so you know, the chat_ctx property on LiveKit agents is actually read-only. This isn't to make things complicated, but to keep your chat context safe and synced up perfectly.
Here's the right way to update your chat context:
• Use await self.update_chat_ctx(modifiable_ctx) to save your changes. This is the magic method that'll work smoothly!
• Once you call update_chat_ctx, the agent's chat_ctx will automatically update. No manual assignment needed – how convenient, right?
Just remove that self.chat_ctx = modifiable_ctx line, and you'll be all set. Looks like the rest of your context handling is spot on!