hey I wanna add a system message to a chat context...
# ask-ai
c
hey I wanna add a system message to a chat context for my llm to see it, how should I do that? is this working
_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 to
t
Thanks for reaching out, Clovis. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
You can add a system message to a chat context for your LLM by using the
add_message
method on the
ChatContext
object with the role set to `system`:
Copy code
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`:
Copy code
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:
Copy code
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 Docs
c
I am getting this
AttributeError: '_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?
t
Hey there! Let me break down how LiveKit handles chat contexts in a super friendly way. Think of it like a protective shield that keeps your chat context safe during tool execution. If you try to change the chat context mid-turn, LiveKit will give you a heads-up with a RuntimeError. It's basically saying, "Whoa, hold up! This context is read-only right now." But don't worry, they've got a simple workaround that's totally easy to use. Here's the smoothest way to modify your chat context: 1. Make a quick copy:
chat_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.py
c
is there something wrong with doing this?
if 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'
t
Hey there! I totally get why you're running into this tricky error. LiveKit's chat context is designed to be read-only, which means you can't just directly add messages like you might expect. No worries, though – I've got a super simple workaround that'll solve this in a snap! Here's a friendly step-by-step to add your system message: 1. First, make a copy of the context you can actually modify:
modifiable_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 Docs
c
I used what you said but I get this error message now, how do I need to update it then? ERROR livekit-agent - [session_1753206779272_l1rb30y] Error updating chat_ctx with summary: '_LiteralSpecialForm' object has no attribute 'SYSTEM'
t
Hey there! I totally get how frustrating it can be when you run into unexpected errors. The
'_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:
Copy code
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 Docs
c
okay now it's telling me this, what am I supposed to pass in instead? ERROR livekit-agent - [session_1753207149130_28je8co] Error updating chat_ctx with summary: 1 validation error for ChatMessage content Input should be a valid list [type=list_type, input_value='Transferred to Presenter... product demonstration.', input_type=str] For further information visit https://errors.pydantic.dev/2.11/v/list_type This is the kind of summary I am adding:
summary = "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))
t
Hey there! I totally get how frustrating error messages can be. 😊 The issue you're seeing is actually pretty simple - LiveKit Python needs the
content
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`:
Copy code
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:
Copy code
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...
c
okay so I fixed the thing and it's working, the only issue we have though is that the assistant messages and user messages are discarded, this is pretty concerning... Why is it doing that? we are almost there, is there a way to make sure the messages are kept? i mean we are copying the chat context which is supposed to contain the messages so why are they removed? there is no way to just add the tool call summary without removing the rest and then save it? like the add_message is supposed to just add a message not remove all the others.... What do you think?
t
Hey there! Let me break down what's happening with the add_message method. It's super important to understand that this method is like adding a new sticky note to your chat board - it doesn't erase the old ones. If you're noticing messages disappearing, don't worry! I've got some friendly tips to help you out. • Watch out for context copying: When you use
chat_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 Docs
c
can I modify the chat_ctx inside a function, before the return statement? or is there specific moment where I can't?
t
Hey there! Let me break down how you can tweak the chat context inside a function. It's pretty straightforward, but you'll want to work with a modifiable copy instead of the original read-only
chat_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 Docs
c
do you see any issue that could explain why I get this error for this code? Error:
ERROR 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 handoff
t
Hey there! I totally get how frustrating it can be when you run into an unexpected error like
AttributeError: 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:
Copy code
# 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.py
c
do you see any issue that could explain why I get this error for this code? Error:
ERROR 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 handoff
t
Hey there! I totally get why you might have run into this tricky error. Let me help you sort it out in a friendly way. The error
AttributeError: can't set attribute
is happening because of this line in your code:
Copy 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!