Skip to content

Commit 4175964

Browse files
committed
Update way we handle ending conversation and add VIP support
1 parent 380d625 commit 4175964

File tree

5 files changed

+174
-9
lines changed

5 files changed

+174
-9
lines changed

coffee_ws/src/coffee_voice_agent/scripts/agents/coffee_barista_agent.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
from state.state_manager import StateManager, AgentState
2424
from tools.coffee_tools import (
2525
get_current_time_impl, get_current_date_impl, get_coffee_menu_impl,
26-
get_ordering_instructions_impl, recommend_drink_impl, set_agent_instance
26+
get_ordering_instructions_impl, recommend_drink_impl, set_agent_instance,
27+
manage_conversation_time_impl, check_user_status_impl
2728
)
2829

2930
logger = logging.getLogger(__name__)
@@ -62,6 +63,16 @@ def __init__(self):
6263
name="recommend_drink",
6364
description="Recommend a drink based on user preference."
6465
),
66+
function_tool(
67+
manage_conversation_time_impl,
68+
name="manage_conversation_time",
69+
description="Intelligent conversation time management. Use this when you receive admin messages about time limits or need to make decisions about continuing or ending the conversation."
70+
),
71+
function_tool(
72+
check_user_status_impl,
73+
name="check_user_status",
74+
description="Check if a user has special status (VIP, staff, important guest) based on what they tell you about themselves."
75+
),
6576
]
6677
)
6778

@@ -192,6 +203,41 @@ async def process_text_stream():
192203

193204
logger.info("🔍 DEBUG: tts_node processing complete")
194205

206+
async def on_user_turn_completed(self, turn_ctx, new_message):
207+
"""Handle user turn completion - inject admin messages for time management"""
208+
if not self.state_manager.conversation_start_time:
209+
return
210+
211+
import time
212+
elapsed = time.time() - self.state_manager.conversation_start_time
213+
214+
# 5 minute warning
215+
if elapsed > 300 and not self.state_manager.five_minute_warning_sent:
216+
logger.info("Injecting 5-minute conversation warning via callback")
217+
turn_ctx.add_message(
218+
role="system",
219+
content="ADMIN: You've been chatting for 5 minutes. The user seems engaged. Consider mentioning you have time for 1-2 more questions, but be natural about it based on the conversation flow."
220+
)
221+
self.state_manager.five_minute_warning_sent = True
222+
223+
# 6 minute warning
224+
elif elapsed > 360 and not self.state_manager.six_minute_warning_sent:
225+
logger.info("Injecting 6-minute conversation warning via callback")
226+
turn_ctx.add_message(
227+
role="system",
228+
content="ADMIN: You've been chatting for 6 minutes. Suggest wrapping up soon to help other visitors, but use your judgment. Call the manage_conversation_time tool to make a decision."
229+
)
230+
self.state_manager.six_minute_warning_sent = True
231+
232+
# 7 minute limit
233+
elif elapsed > 420 and not self.state_manager.seven_minute_warning_sent:
234+
logger.info("Injecting 7-minute conversation limit via callback")
235+
turn_ctx.add_message(
236+
role="system",
237+
content="ADMIN: You've been chatting for 7 minutes - time limit reached. Call the manage_conversation_time tool with action='end' to wrap up gracefully, unless there are special circumstances."
238+
)
239+
self.state_manager.seven_minute_warning_sent = True
240+
195241
async def start_wake_word_detection(self, room):
196242
"""Start wake word detection in a separate thread"""
197243
if not self.porcupine_access_key:

coffee_ws/src/coffee_voice_agent/scripts/config/instructions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616
1717
Available emotions: excited, friendly, helpful, curious, enthusiastic, warm, professional, cheerful
1818
19+
ADMIN MESSAGE HANDLING:
20+
You may receive ADMIN messages about conversation timing and management. These are system notifications to help you manage your time effectively:
21+
22+
- When you see "ADMIN: You've been chatting for 5 minutes..." - Acknowledge naturally and consider mentioning you have time for more questions
23+
- When you see "ADMIN: You've been chatting for 6 minutes..." - Use the manage_conversation_time tool to make a decision about wrapping up
24+
- When you see "ADMIN: You've been chatting for 7 minutes..." - Use the manage_conversation_time tool with action='end' to wrap up gracefully
25+
26+
IMPORTANT: If a user mentions they are VIP, staff, or important guests (like "I'm Alice from Sui Foundation"), use the check_user_status tool to verify and potentially extend the conversation.
27+
28+
Always respond naturally to admin messages - incorporate them into your conversation flow rather than mentioning them directly to the user.
29+
1930
Your personality:
2031
- Enthusiastic about coffee and the blockchain conference
2132
- Knowledgeable about coffee drinks and brewing

coffee_ws/src/coffee_voice_agent/scripts/config/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
# Configurable timeout settings
1010
USER_RESPONSE_TIMEOUT = int(os.getenv("USER_RESPONSE_TIMEOUT", "15")) # seconds
1111
FINAL_TIMEOUT = int(os.getenv("FINAL_TIMEOUT", "10")) # seconds after prompt
12-
MAX_CONVERSATION_TIME = int(os.getenv("MAX_CONVERSATION_TIME", "180")) # 3 minutes total
12+
MAX_CONVERSATION_TIME = int(os.getenv("MAX_CONVERSATION_TIME", "420")) # 7 minutes total
13+
CONVERSATION_WARNING_TIME = int(os.getenv("CONVERSATION_WARNING_TIME", "300")) # 5 minutes - first warning
14+
FINAL_WARNING_TIME = int(os.getenv("FINAL_WARNING_TIME", "360")) # 6 minutes - final warning
1315

1416
# WebSocket server settings
1517
WEBSOCKET_HOST = os.getenv("WEBSOCKET_HOST", "localhost")

coffee_ws/src/coffee_voice_agent/scripts/state/state_manager.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from config.settings import (
1818
USER_RESPONSE_TIMEOUT, FINAL_TIMEOUT, MAX_CONVERSATION_TIME,
19-
VALID_EMOTIONS
19+
CONVERSATION_WARNING_TIME, FINAL_WARNING_TIME, VALID_EMOTIONS
2020
)
2121

2222
logger = logging.getLogger(__name__)
@@ -58,6 +58,12 @@ def __init__(self, agent=None):
5858
# Goodbye coordination flag
5959
self.goodbye_pending = False # Flag to coordinate goodbye handling between user and assistant handlers
6060

61+
# Conversation timing tracking for admin message injection
62+
self.conversation_start_time = None
63+
self.five_minute_warning_sent = False
64+
self.six_minute_warning_sent = False
65+
self.seven_minute_warning_sent = False
66+
6167
# Phase 4: Mutual exclusion for virtual request processing
6268
self.virtual_request_processing_lock = asyncio.Lock()
6369
# Removed batching complexity - no longer needed
@@ -111,26 +117,37 @@ async def _exit_current_state(self):
111117
async def _enter_new_state(self):
112118
"""Initialize new state"""
113119
if self.current_state == AgentState.ACTIVE:
120+
# Start conversation timing tracking
121+
import time
122+
self.conversation_start_time = time.time()
123+
self.five_minute_warning_sent = False
124+
self.six_minute_warning_sent = False
125+
self.seven_minute_warning_sent = False
126+
114127
# Start max conversation timer (absolute limit)
115128
self.conversation_timer = asyncio.create_task(self._max_conversation_timeout())
116129
elif self.current_state == AgentState.DORMANT:
130+
# Reset conversation timing
131+
self.conversation_start_time = None
132+
117133
# Resume wake word detection when returning to dormant
118134
if self.agent:
119135
self.agent.wake_word_paused = False
120136
logger.info("Resumed wake word detection")
121137

122138
async def _max_conversation_timeout(self):
123-
"""Handle maximum conversation time limit"""
139+
"""Handle absolute maximum conversation time limit (fallback)"""
124140
try:
125-
await asyncio.sleep(MAX_CONVERSATION_TIME) # 5 minute absolute limit
141+
# Wait for absolute maximum time (7 minutes)
142+
await asyncio.sleep(MAX_CONVERSATION_TIME)
126143
if self.session and self.current_state == AgentState.ACTIVE:
127-
logger.info("Maximum conversation time reached - ending session")
144+
logger.info("Absolute maximum conversation time reached - ending conversation")
128145

129146
# Set ending flag to prevent timer conflicts
130147
self.ending_conversation = True
131148

132-
# Timeout message with sleepy emotion using delimiter format
133-
timeout_response = "sleepy:We've been chatting for a while! I'm getting a bit sleepy. Thanks for the conversation. Say 'hey barista' if you need me again."
149+
# Fallback timeout message if callback system didn't handle it
150+
timeout_response = "friendly:I've really enjoyed our conversation! To help other visitors, I'll need to wrap up here. Say 'hey barista' if you need me again."
134151
emotion, text = self.process_emotional_response(timeout_response)
135152
await self.say_with_emotion(text, emotion)
136153

coffee_ws/src/coffee_voice_agent/scripts/tools/coffee_tools.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,93 @@ async def recommend_drink_impl(context: RunContext, preference: str = "energizin
120120
logger.info(f"Drink recommendation for '{preference}': {base_recommendation}")
121121

122122
await send_tool_event("recommend_drink", "completed", [preference], full_recommendation)
123-
return full_recommendation
123+
return full_recommendation
124+
125+
126+
async def manage_conversation_time_impl(
127+
context: RunContext,
128+
action: str,
129+
reason: str,
130+
user_importance: str = "normal",
131+
extension_minutes: int = 0
132+
) -> str:
133+
"""
134+
Intelligent conversation time management based on context.
135+
136+
Args:
137+
action: What to do - "continue", "warn", "extend", "end"
138+
reason: LLM's reasoning for the decision
139+
user_importance: Assessment of user importance/engagement - "vip", "engaged", "normal"
140+
extension_minutes: How much to extend if action is 'extend'
141+
"""
142+
await send_tool_event("manage_conversation_time", "started", [action, reason, user_importance])
143+
144+
logger.info(f"LLM conversation decision: {action} - {reason} (user_importance: {user_importance})")
145+
146+
result = ""
147+
148+
if action == "end":
149+
# Signal the state manager to end conversation gracefully
150+
if _agent_instance and hasattr(_agent_instance, 'state_manager'):
151+
_agent_instance.state_manager.ending_conversation = True
152+
# Schedule conversation end after a brief delay
153+
asyncio.create_task(_delayed_conversation_end())
154+
result = f"Conversation ending initiated: {reason}"
155+
156+
elif action == "extend":
157+
logger.info(f"Conversation extended by {extension_minutes} minutes: {reason}")
158+
result = f"Conversation extended by {extension_minutes} minutes: {reason}"
159+
160+
elif action == "warn":
161+
logger.info(f"Conversation warning acknowledged: {reason}")
162+
result = f"Time warning acknowledged: {reason}"
163+
164+
elif action == "continue":
165+
logger.info(f"Conversation continues normally: {reason}")
166+
result = f"Conversation continues: {reason}"
167+
168+
else:
169+
result = f"Unknown action '{action}': {reason}"
170+
logger.warning(result)
171+
172+
await send_tool_event("manage_conversation_time", "completed", [action, reason, user_importance], result)
173+
return result
174+
175+
176+
async def check_user_status_impl(
177+
context: RunContext,
178+
user_identifier: str = ""
179+
) -> str:
180+
"""
181+
Check if user has special status (VIP, staff, important guest).
182+
183+
Args:
184+
user_identifier: Any identifier mentioned by the user (name, title, etc.)
185+
"""
186+
await send_tool_event("check_user_status", "started", [user_identifier])
187+
188+
# VIP keywords to check for
189+
vip_keywords = [
190+
"alice", "bob", "sui foundation", "event organizer", "staff", "organizer",
191+
"speaker", "sponsor", "mysten labs", "team", "developer", "builder"
192+
]
193+
194+
user_lower = user_identifier.lower()
195+
is_vip = any(keyword in user_lower for keyword in vip_keywords)
196+
197+
if is_vip:
198+
result = f"VIP user detected: {user_identifier}. Recommended extension: 3 minutes. Enhanced service advised."
199+
logger.info(f"VIP user identified: {user_identifier}")
200+
else:
201+
result = f"Standard user: {user_identifier}. Normal time limits apply."
202+
logger.info(f"Standard user: {user_identifier}")
203+
204+
await send_tool_event("check_user_status", "completed", [user_identifier], result)
205+
return result
206+
207+
208+
async def _delayed_conversation_end():
209+
"""Helper function to end conversation after a brief delay"""
210+
await asyncio.sleep(2) # Allow current response to complete
211+
if _agent_instance and hasattr(_agent_instance, 'state_manager'):
212+
await _agent_instance.state_manager.end_conversation()

0 commit comments

Comments
 (0)