quick-cartoon-22764
05/19/2026, 7:28 PMtall-airline-16756
05/19/2026, 7:29 PMtall-airline-16756
05/19/2026, 7:29 PMevent.item to have a role, but the item is an AgentHandoff which has no role. I'll help you fix that.
What I recommend:
• Update _on_conversation_item_added to check the item type before accessing role.
• Only access role for message items; handle or ignore other types (like agent_handoff) explicitly.
Example safe pattern:
def _on_conversation_item_added(event):
# ignore non-message items which don't have `role`
if getattr(event.item, "type", None) != "message":
return
if event.item.role == "user" and user_speech_start_time:
# existing logic
...
If you want, tell me:
• The current structure of event.item (keys you observe), and
• Any special handling you want for agent_handoff (e.g., logging, state update),
and I’ll give a tailored code snippet.
Sources: Chat context | LiveKit Documentation | How to Build a Background Observer for Voice AI Guardrails | LiveKit | How to Build a Background Observer for Voice AI Guardrails | LiveKit, with interactive elementsquick-cartoon-22764
05/20/2026, 8:15 PM@session.on("conversation_item_added")
def _on_conversation_item_added(event: ConversationItemAddedEvent):
nonlocal user_speech_start_time, agent_speech_start_time
item_end_time = event.created_at - call_start_time
if event.item.role == "user" and user_speech_start_time:
item_start_time = user_speech_start_time - call_start_time
user_speech_start_time = None
elif event.item.role == "assistant" and agent_speech_start_time:
item_start_time = agent_speech_start_time - call_start_time
agent_speech_start_time = None
else:
item_start_time = item_end_time
<http://log.info|log.info>(f"[{agent_name}][correlation_id: {agent.room_name}]: {event.item.role} - {event.item.content} message from {item_start_time:.2f}s to {item_end_time:.2f}s")
conversation_item = {
"id": event.item.id,
"type": event.item.type,
"role": event.item.role,
"content": event.item.content,
"interrupted": event.item.interrupted,
"transcript_confidence": event.item.transcript_confidence,
"start_time": round(item_start_time, 2),
"end_time": round(item_end_time, 2)
}
conversation_items.append(conversation_item)tall-airline-16756
05/20/2026, 8:15 PMagent_handoff) don’t have role. Update the handler to check event.item.type (or guard attribute access) before reading role and content.
Suggested patch — safe checks and explicit handling for non-message items:
@session.on("conversation_item_added")
def _on_conversation_item_added(event: ConversationItemAddedEvent):
nonlocal user_speech_start_time, agent_speech_start_time
item_type = getattr(event.item, "type", None)
item_id = getattr(event.item, "id", None)
item_end_time = event.created_at - call_start_time
# Only message items have `role` and `content`
if item_type == "message":
role = event.item.role
content = event.item.content
if role == "user" and user_speech_start_time:
item_start_time = user_speech_start_time - call_start_time
user_speech_start_time = None
elif role == "assistant" and agent_speech_start_time:
item_start_time = agent_speech_start_time - call_start_time
agent_speech_start_time = None
else:
item_start_time = item_end_time
log.info(f"[{agent_name}][correlation_id: {agent.room_name}]: {role} - {content} message from {item_start_time:.2f}s to {item_end_time:.2f}s")
conversation_item = {
"id": item_id,
"type": item_type,
"role": role,
"content": content,
"interrupted": getattr(event.item, "interrupted", None),
"transcript_confidence": getattr(event.item, "transcript_confidence", None),
"start_time": round(item_start_time, 2),
"end_time": round(item_end_time, 2)
}
else:
# Handle or ignore other item types (agent_handoff, function_call, etc.)
item_start_time = item_end_time
log.info(f"[{agent_name}][correlation_id: {agent.room_name}]: {item_type} item (id={item_id}) at {item_end_time:.2f}s")
conversation_item = {
"id": item_id,
"type": item_type,
"start_time": round(item_start_time, 2),
"end_time": round(item_end_time, 2),
}
# Optional: capture agent_handoff details if present
if item_type == "agent_handoff":
conversation_item.update({
"old_agent_id": getattr(event.item, "old_agent_id", None),
"new_agent_id": getattr(event.item, "new_agent_id", None),
})
conversation_items.append(conversation_item)
If you want, tell me whether you prefer to:
• Ignore non-message items entirely, or
• Log/store extra fields for specific types like agent_handoff (and which fields),
and I’ll adjust the snippet accordingly.
, with interactive elementsquick-cartoon-22764
05/20/2026, 8:21 PMsession = AgentSession(
userdata=userdata,
turn_handling=TurnHandlingOptions(
),
turn_detection=MultilingualModel(),
use_tts_aligned_transcript=False,
user_away_timeout= None, #agent_config['agent']['user_away_timeout'],
vad=ctx.proc.userdata["vad"],
min_endpointing_delay=agent_config['agent']['min_endpointing_delay'],
max_endpointing_delay=agent_config['agent']['max_endpointing_delay'],
preemptive_generation=agent_config['agent']['preemptive_generation'],
ivr_detection=agent_config['agent']['ivr_detection'],
stt=stt.FallbackAdapter(
stt=[
deepgram.STT(
model=agent_config['primary_stt']['model'],
interim_results=agent_config['primary_stt']['interim_results'],
smart_format=agent_config['primary_stt']['smart_format'],
punctuate=agent_config['primary_stt']['punctuate'],
filler_words=agent_config['primary_stt']['filler_words'],
profanity_filter=agent_config['primary_stt']['profanity_filter'],
language=agent_config['primary_stt']['language'],
numerals=agent_config['primary_stt']['numerals'],
api_key=config['DEEPGRAM_API_KEY']
),
google.STT(
model=agent_config['secondary_stt']['model'],
credentials_file=os.getenv("GOOGLE_APPLICATION_CREDENTIALS"),
location=agent_config['secondary_stt']['location']
)
]
),
llm=selected_llm,
tts=selected_tts,
)tall-airline-16756
05/20/2026, 8:22 PMagent_handoff) in conversation_item_added to avoid AttributeError, and (2) move deprecated session args into TurnHandlingOptions.
1) Patch for conversation_item_added (only access `role`/`content` for message items):
@session.on("conversation_item_added")
def _on_conversation_item_added(event: ConversationItemAddedEvent):
nonlocal user_speech_start_time, agent_speech_start_time
item_type = getattr(event.item, "type", None)
item_id = getattr(event.item, "id", None)
item_end_time = event.created_at - call_start_time
if item_type == "message":
role = event.item.role
content = event.item.content
if role == "user" and user_speech_start_time:
item_start_time = user_speech_start_time - call_start_time
user_speech_start_time = None
elif role == "assistant" and agent_speech_start_time:
item_start_time = agent_speech_start_time - call_start_time
agent_speech_start_time = None
else:
item_start_time = item_end_time
log.info(f"[{agent_name}][correlation_id: {agent.room_name}]: {role} - {content} message from {item_start_time:.2f}s to {item_end_time:.2f}s")
conversation_item = {
"id": item_id,
"type": item_type,
"role": role,
"content": content,
"interrupted": getattr(event.item, "interrupted", None),
"transcript_confidence": getattr(event.item, "transcript_confidence", None),
"start_time": round(item_start_time, 2),
"end_time": round(item_end_time, 2),
}
else:
# handle or ignore non-message items (agent_handoff, function_call, etc.)
item_start_time = item_end_time
log.info(f"[{agent_name}][correlation_id: {agent.room_name}]: {item_type} item (id={item_id}) at {item_end_time:.2f}s")
conversation_item = {
"id": item_id,
"type": item_type,
"start_time": round(item_start_time, 2),
"end_time": round(item_end_time, 2),
}
if item_type == "agent_handoff":
conversation_item.update({
"old_agent_id": getattr(event.item, "old_agent_id", None),
"new_agent_id": getattr(event.item, "new_agent_id", None),
})
conversation_items.append(conversation_item)
2) Replace deprecated AgentSession args by configuring turn_handling. Move turn_detection, endpointing delays, and preemptive_generation into `TurnHandlingOptions`:
session = AgentSession(
userdata=userdata,
turn_handling=TurnHandlingOptions(
turn_detection=MultilingualModel(),
preemptive_generation=agent_config['agent']['preemptive_generation'],
endpointing={
"min_delay": agent_config['agent']['min_endpointing_delay'], # seconds in Python
"max_delay": agent_config['agent']['max_endpointing_delay'],
},
),
use_tts_aligned_transcript=False,
user_away_timeout=None,
vad=ctx.proc.userdata["vad"],
ivr_detection=agent_config['agent']['ivr_detection'],
stt=stt.FallbackAdapter(
stt=[ ... ] # your existing STT setup
),
llm=selected_llm,
tts=selected_tts,
)
If you want, tell me whether you prefer to:
• ignore non-message items entirely, or
• record extra fields for types like agent_handoff (and which fields),
and I’ll return a tighter snippet for that preference.
Sources: https://docs.livekit.io/agents/logic/sessions.md| | Agent session | LiveKit Documentation | LiveKit turn detector plugin | LiveKit Documentation | How to Build a Background Observer for Voice AI Guardrails | LiveKit | How to Build a Background Observer for Voice AI Guardrails | LiveKit, with interactive elementsquick-cartoon-22764
05/20/2026, 8:43 PMtall-airline-16756
05/20/2026, 8:43 PMagent_handoff) don’t have role or content.
• Fix: only access `role`/`content` when `event.item.type == "message"`; handle other types separately.
Suggested snippet (replace your handler with this):
@session.on("conversation_item_added")
def _on_conversation_item_added(event: ConversationItemAddedEvent):
nonlocal user_speech_start_time, agent_speech_start_time
item_type = getattr(event.item, "type", None)
item_id = getattr(event.item, "id", None)
item_end_time = event.created_at - call_start_time
if item_type == "message":
role = event.item.role
content = event.item.content
if role == "user" and user_speech_start_time:
item_start_time = user_speech_start_time - call_start_time
user_speech_start_time = None
elif role == "assistant" and agent_speech_start_time:
item_start_time = agent_speech_start_time - call_start_time
agent_speech_start_time = None
else:
item_start_time = item_end_time
log.info(f"[{agent_name}][correlation_id: {agent.room_name}]: {role} - {content} message from {item_start_time:.2f}s to {item_end_time:.2f}s")
conversation_item = {
"id": item_id,
"type": item_type,
"role": role,
"content": content,
"interrupted": getattr(event.item, "interrupted", None),
"transcript_confidence": getattr(event.item, "transcript_confidence", None),
"start_time": round(item_start_time, 2),
"end_time": round(item_end_time, 2),
}
else:
# handle or ignore non-message items
item_start_time = item_end_time
log.info(f"[{agent_name}][correlation_id: {agent.room_name}]: {item_type} item (id={item_id}) at {item_end_time:.2f}s")
conversation_item = {
"id": item_id,
"type": item_type,
"start_time": round(item_start_time, 2),
"end_time": round(item_end_time, 2),
}
if item_type == "agent_handoff":
conversation_item.update({
"old_agent_id": getattr(event.item, "old_agent_id", None),
"new_agent_id": getattr(event.item, "new_agent_id", None),
})
conversation_items.append(conversation_item)
2) Deprecated turn-related args warning
• Problem: turn_detection, min_endpointing_delay, max_endpointing_delay, and preemptive_generation must be moved into TurnHandlingOptions.
• Fix: set those options inside turn_handling. In Python endpointing delays are in seconds.
Example change:
session = AgentSession(
userdata=userdata,
turn_handling=TurnHandlingOptions(
turn_detection=MultilingualModel(),
preemptive_generation=agent_config['agent']['preemptive_generation'],
endpointing={
"min_delay": agent_config['agent']['min_endpointing_delay'],
"max_delay": agent_config['agent']['max_endpointing_delay'],
},
),
use_tts_aligned_transcript=False,
user_away_timeout=None,
vad=ctx.proc.userdata["vad"],
ivr_detection=agent_config['agent']['ivr_detection'],
stt=stt.FallbackAdapter(stt=[ ... ]),
llm=selected_llm,
tts=selected_tts,
)
3) RoomInputOptions/RoomOutputOptions deprecation
• Problem: those classes are deprecated; use unified RoomOptions instead.
• Fix: replace any use of `RoomInputOptions`/`RoomOutputOptions` with RoomOptions and provide the same input/output configuration there. If you paste the current RoomInput/RoomOutput usage, I can give the exact RoomOptions mapping.
Next step
• Do you want me to:
◦ produce a full patched file with these changes applied, or
◦ convert specific RoomInput/RoomOutput code to RoomOptions if you paste it now? Sources: https://docs.livekit.io/agents/logic/sessions.md| | Agent session | LiveKit Documentation, with interactive elementsquick-cartoon-22764
05/20/2026, 8:45 PMawait session.start(
agent=agent,
room=ctx.room,
room_input_options=RoomInputOptions(
noise_cancellation=noise_cancellation.BVCTelephony(),
text_enabled=False,
),
)tall-airline-16756
05/20/2026, 8:45 PMroom_input_options with room_options using RoomOptions. In Python the field names changed (text_enabled → text_input). Example patch:
from livekit.agents import room_io
from livekit.plugins import noise_cancellation
await session.start(
agent=agent,
room=ctx.room,
room_options=room_io.RoomOptions(
noise_cancellation=noise_cancellation.BVCTelephony(),
text_input=False,
),
)
This removes the deprecation warning. If you also use any RoomOutputOptions, move those settings into the same RoomOptions. Paste your current input/output settings if you want a direct one-to-one mapping.
Sources: Agent session | LiveKit Documentation | Turns overview | LiveKit Documentation | How to Build a Background Observer for Voice AI Guardrails | LiveKit, with interactive elementsquick-cartoon-22764
05/20/2026, 8:47 PMtall-airline-16756
05/20/2026, 8:47 PMnoise_cancellation isn't a top-level field on RoomOptions. Put it under audio_input using AudioInputOptions. Example:
from livekit.agents import room_io
from livekit.plugins import noise_cancellation
await session.start(
agent=agent,
room=ctx.room,
room_options=room_io.RoomOptions(
audio_input=room_io.AudioInputOptions(
noise_cancellation=noise_cancellation.BVCTelephony()
),
text_input=False,
),
)
This moves the noise cancellation into the correct place and removes the error. If you want, paste any other RoomInput/RoomOutput fields you used and I’ll map them into RoomOptions.
Sources: https://docs.livekit.io/agents/logic/sessions.md| | Google AI and LiveKit | LiveKit Documentation | Agent session | LiveKit Documentation, with interactive elementsquick-cartoon-22764
05/20/2026, 9:06 PMtall-airline-16756
05/20/2026, 9:06 PM