Commit
·
0d56066
1
Parent(s):
0b5851a
safety agent upgrades to enable creative freedom, loops fixed v2
Browse files- app.py +39 -14
- src/orchestrator_engine.py +29 -0
app.py
CHANGED
|
@@ -514,28 +514,37 @@ async def process_message_async(message: str, history: Optional[List], session_i
|
|
| 514 |
|
| 515 |
new_history = list(history) if isinstance(history, list) else []
|
| 516 |
|
| 517 |
-
# Check if this is a safety choice response
|
| 518 |
message_upper = message.strip().upper()
|
| 519 |
is_safety_choice = message_upper in ['YES', 'NO', 'APPLY', 'KEEP', 'Y', 'N']
|
| 520 |
|
| 521 |
# Check if we have a pending safety choice for this session
|
| 522 |
-
if is_safety_choice and orchestrator is not None
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
|
|
|
|
|
|
|
|
|
| 526 |
|
| 527 |
# Determine user decision
|
| 528 |
user_decision = message_upper in ['YES', 'APPLY', 'Y']
|
| 529 |
|
| 530 |
-
# Process the safety choice
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
|
| 540 |
# Add user message
|
| 541 |
new_history.append({"role": "user", "content": message.strip()})
|
|
@@ -559,6 +568,10 @@ async def process_message_async(message: str, history: Optional[List], session_i
|
|
| 559 |
"session_id": session_id
|
| 560 |
}
|
| 561 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 562 |
return new_history, "", reasoning_data, performance_data, context_data, session_id, ""
|
| 563 |
|
| 564 |
# Add user message (normal flow)
|
|
@@ -578,11 +591,18 @@ async def process_message_async(message: str, history: Optional[List], session_i
|
|
| 578 |
try:
|
| 579 |
logger.info("Attempting full orchestration...")
|
| 580 |
# First, try normal processing to check for user choice
|
|
|
|
| 581 |
result = await orchestrator.process_request(
|
| 582 |
session_id=session_id,
|
| 583 |
user_input=message.strip()
|
| 584 |
)
|
| 585 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
# Check if user choice is required
|
| 587 |
if result.get('requires_user_choice', False):
|
| 588 |
logger.info("User choice required for safety concerns")
|
|
@@ -610,6 +630,11 @@ async def process_message_async(message: str, history: Optional[List], session_i
|
|
| 610 |
'safety_analysis': result.get('safety_analysis', {})
|
| 611 |
}
|
| 612 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
# Add assistant message with choice prompt
|
| 614 |
new_history.append({
|
| 615 |
"role": "assistant",
|
|
|
|
| 514 |
|
| 515 |
new_history = list(history) if isinstance(history, list) else []
|
| 516 |
|
| 517 |
+
# Check if this is a safety choice response (BEFORE normal processing)
|
| 518 |
message_upper = message.strip().upper()
|
| 519 |
is_safety_choice = message_upper in ['YES', 'NO', 'APPLY', 'KEEP', 'Y', 'N']
|
| 520 |
|
| 521 |
# Check if we have a pending safety choice for this session
|
| 522 |
+
if is_safety_choice and orchestrator is not None:
|
| 523 |
+
# Check both _pending_choices (from app.py) and awaiting_safety_response (from orchestrator)
|
| 524 |
+
pending_choice = getattr(orchestrator, '_pending_choices', {}).get(session_id)
|
| 525 |
+
awaiting_response = getattr(orchestrator, 'awaiting_safety_response', {}).get(session_id, False)
|
| 526 |
+
|
| 527 |
+
if pending_choice or awaiting_response:
|
| 528 |
+
logger.info(f"Processing safety choice response: {message_upper} (session: {session_id})")
|
| 529 |
|
| 530 |
# Determine user decision
|
| 531 |
user_decision = message_upper in ['YES', 'APPLY', 'Y']
|
| 532 |
|
| 533 |
+
# Process the safety choice directly (bypasses normal safety checks)
|
| 534 |
+
if pending_choice:
|
| 535 |
+
choice_result = await orchestrator.handle_user_safety_decision(
|
| 536 |
+
pending_choice['choice_id'],
|
| 537 |
+
user_decision,
|
| 538 |
+
session_id
|
| 539 |
+
)
|
| 540 |
+
|
| 541 |
+
# Clean up pending choice
|
| 542 |
+
if hasattr(orchestrator, '_pending_choices'):
|
| 543 |
+
orchestrator._pending_choices.pop(session_id, None)
|
| 544 |
+
else:
|
| 545 |
+
# Fallback: if no pending choice but flag is set, skip safety check
|
| 546 |
+
logger.warning(f"Safety response flag set but no pending choice found - bypassing safety check")
|
| 547 |
+
return new_history, "", {}, {}, {}, session_id, ""
|
| 548 |
|
| 549 |
# Add user message
|
| 550 |
new_history.append({"role": "user", "content": message.strip()})
|
|
|
|
| 568 |
"session_id": session_id
|
| 569 |
}
|
| 570 |
|
| 571 |
+
# Ensure flags are cleared
|
| 572 |
+
if hasattr(orchestrator, 'awaiting_safety_response'):
|
| 573 |
+
orchestrator.awaiting_safety_response.pop(session_id, None)
|
| 574 |
+
|
| 575 |
return new_history, "", reasoning_data, performance_data, context_data, session_id, ""
|
| 576 |
|
| 577 |
# Add user message (normal flow)
|
|
|
|
| 591 |
try:
|
| 592 |
logger.info("Attempting full orchestration...")
|
| 593 |
# First, try normal processing to check for user choice
|
| 594 |
+
# NOTE: Binary safety responses are already handled above, so this won't process them
|
| 595 |
result = await orchestrator.process_request(
|
| 596 |
session_id=session_id,
|
| 597 |
user_input=message.strip()
|
| 598 |
)
|
| 599 |
|
| 600 |
+
# Check if result indicates this was a safety response (should have been handled above)
|
| 601 |
+
if result.get('is_safety_response', False):
|
| 602 |
+
logger.warning("Safety response detected in normal processing - should have been handled earlier")
|
| 603 |
+
# Skip further processing
|
| 604 |
+
return new_history, "", {}, {}, {}, session_id, ""
|
| 605 |
+
|
| 606 |
# Check if user choice is required
|
| 607 |
if result.get('requires_user_choice', False):
|
| 608 |
logger.info("User choice required for safety concerns")
|
|
|
|
| 630 |
'safety_analysis': result.get('safety_analysis', {})
|
| 631 |
}
|
| 632 |
|
| 633 |
+
# Ensure awaiting_safety_response flag is also set in orchestrator
|
| 634 |
+
if not hasattr(orchestrator, 'awaiting_safety_response'):
|
| 635 |
+
orchestrator.awaiting_safety_response = {}
|
| 636 |
+
orchestrator.awaiting_safety_response[session_id] = True
|
| 637 |
+
|
| 638 |
# Add assistant message with choice prompt
|
| 639 |
new_history.append({
|
| 640 |
"role": "assistant",
|
src/orchestrator_engine.py
CHANGED
|
@@ -41,6 +41,11 @@ class MVPOrchestrator:
|
|
| 41 |
}
|
| 42 |
self.max_revision_attempts = 2
|
| 43 |
self.revision_timeout = 30 # seconds
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
logger.info("MVPOrchestrator initialized with safety revision thresholds")
|
| 45 |
|
| 46 |
async def process_request(self, session_id: str, user_input: str) -> dict:
|
|
@@ -50,6 +55,23 @@ class MVPOrchestrator:
|
|
| 50 |
logger.info(f"Processing request for session {session_id}")
|
| 51 |
logger.info(f"User input: {user_input[:100]}")
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
# Clear previous trace for new request
|
| 54 |
self.execution_trace = []
|
| 55 |
start_time = time.time()
|
|
@@ -231,6 +253,9 @@ class MVPOrchestrator:
|
|
| 231 |
logger.info(f"Safety concerns detected for intent '{intent_class}' - requiring user choice")
|
| 232 |
processing_time = time.time() - start_time
|
| 233 |
|
|
|
|
|
|
|
|
|
|
| 234 |
return {
|
| 235 |
'requires_user_choice': True,
|
| 236 |
'choice_prompt': choice_prompt,
|
|
@@ -401,6 +426,10 @@ class MVPOrchestrator:
|
|
| 401 |
dict: Final response based on user choice
|
| 402 |
"""
|
| 403 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
if not SAFETY_CHOICE_AVAILABLE:
|
| 405 |
logger.warning("Safety choice modules not available")
|
| 406 |
return {'error': 'Safety choice system not available'}
|
|
|
|
| 41 |
}
|
| 42 |
self.max_revision_attempts = 2
|
| 43 |
self.revision_timeout = 30 # seconds
|
| 44 |
+
|
| 45 |
+
# Safety response tracking to prevent infinite loops
|
| 46 |
+
self.awaiting_safety_response = {} # session_id -> True/False
|
| 47 |
+
self._pending_choices = {} # session_id -> choice_data
|
| 48 |
+
|
| 49 |
logger.info("MVPOrchestrator initialized with safety revision thresholds")
|
| 50 |
|
| 51 |
async def process_request(self, session_id: str, user_input: str) -> dict:
|
|
|
|
| 55 |
logger.info(f"Processing request for session {session_id}")
|
| 56 |
logger.info(f"User input: {user_input[:100]}")
|
| 57 |
|
| 58 |
+
# Safety context bypass: Skip safety checks for binary responses to safety prompts
|
| 59 |
+
user_input_upper = user_input.strip().upper()
|
| 60 |
+
is_binary_response = user_input_upper in ['YES', 'NO', 'APPLY', 'KEEP', 'Y', 'N']
|
| 61 |
+
|
| 62 |
+
if is_binary_response and self.awaiting_safety_response.get(session_id, False):
|
| 63 |
+
logger.info(f"Binary safety response detected ({user_input_upper}) - bypassing safety check to prevent loop")
|
| 64 |
+
# Clear the flag immediately to prevent re-triggering
|
| 65 |
+
self.awaiting_safety_response[session_id] = False
|
| 66 |
+
|
| 67 |
+
# Return a signal that this should be handled by the choice handler, not normal processing
|
| 68 |
+
return {
|
| 69 |
+
'is_safety_response': True,
|
| 70 |
+
'response': user_input_upper,
|
| 71 |
+
'requires_user_choice': False,
|
| 72 |
+
'skip_safety_check': True
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
# Clear previous trace for new request
|
| 76 |
self.execution_trace = []
|
| 77 |
start_time = time.time()
|
|
|
|
| 253 |
logger.info(f"Safety concerns detected for intent '{intent_class}' - requiring user choice")
|
| 254 |
processing_time = time.time() - start_time
|
| 255 |
|
| 256 |
+
# Set flag to indicate we're awaiting safety response for this session
|
| 257 |
+
self.awaiting_safety_response[session_id] = True
|
| 258 |
+
|
| 259 |
return {
|
| 260 |
'requires_user_choice': True,
|
| 261 |
'choice_prompt': choice_prompt,
|
|
|
|
| 426 |
dict: Final response based on user choice
|
| 427 |
"""
|
| 428 |
try:
|
| 429 |
+
# Clear the awaiting safety response flag immediately to prevent loops
|
| 430 |
+
if session_id:
|
| 431 |
+
self.awaiting_safety_response[session_id] = False
|
| 432 |
+
|
| 433 |
if not SAFETY_CHOICE_AVAILABLE:
|
| 434 |
logger.warning("Safety choice modules not available")
|
| 435 |
return {'error': 'Safety choice system not available'}
|