Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e16780c
Enhance error handling and messaging in orchestration and websocket s…
Kingshuk-Microsoft Nov 21, 2025
a9b4d93
Merge branch 'dev-v3' into handleexceps
Kingshuk-Microsoft Nov 21, 2025
621ac6f
Refactor orchestration task handling and improve error logging
Kingshuk-Microsoft Nov 21, 2025
e4d1da7
Merge branch 'handleexceps' of https://github.com/microsoft/Multi-Age…
Kingshuk-Microsoft Nov 21, 2025
83200f8
Refactor code formatting and enhance error handling in orchestration …
Kingshuk-Microsoft Nov 21, 2025
59931a0
Add plan approval endpoint with detailed request and response specifi…
Kingshuk-Microsoft Nov 24, 2025
3fb7416
Remove redundant orchestration initialization in process_request
Kingshuk-Microsoft Nov 27, 2025
1e20aaf
Enhance error handling and logging in plan approval and orchestration…
Kingshuk-Microsoft Nov 28, 2025
3646dc2
Refactor error handling in plan approval to remove redundant exceptio…
Kingshuk-Microsoft Nov 28, 2025
ed1fd7e
Refactor plan approval error handling and clean up commented code in …
Kingshuk-Microsoft Nov 28, 2025
5e1daae
Enhance error handling in OrchestrationManager and PlanPage for impro…
Kingshuk-Microsoft Nov 28, 2025
34b68c5
Refactor error handling in OrchestrationManager to improve clarity an…
Kingshuk-Microsoft Nov 28, 2025
4dc66ec
Enhance error messages in OrchestrationManager and format error messa…
Kingshuk-Microsoft Dec 1, 2025
c0e4f2e
Refactor PlanChat and PlanPage components to remove network error han…
Kingshuk-Microsoft Dec 4, 2025
905a73a
indent fixed for description
Roopan-Microsoft Dec 9, 2025
6848e81
Merge branch 'handleexceps' of https://github.com/microsoft/Multi-Age…
Roopan-Microsoft Dec 9, 2025
53c4abd
Refactor .flake8 configuration for improved readability and organizat…
Kingshuk-Microsoft Dec 9, 2025
29ba6c5
commented import removed
Roopan-Microsoft Dec 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
[flake8]
max-line-length = 88
extend-ignore = E501
exclude = .venv, frontend, src/backend/tests
exclude =
.venv,
frontend,
src/frontend,
src/backend/tests,
*.tsx,
*.ts,
*.jsx,
*.js
ignore = E203, W503, G004, G200, E402
60 changes: 48 additions & 12 deletions src/backend/v3/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import Optional

import v3.models.messages as messages
from v3.models.messages import WebsocketMessageType
from auth.auth_utils import get_authenticated_user_details
from common.database.database_factory import DatabaseFactory
from common.models.messages_kernel import (
Expand Down Expand Up @@ -335,7 +336,6 @@ async def plan_approval(
):
"""
Endpoint to receive plan approval or rejection from the user.

---
tags:
- Plans
Expand Down Expand Up @@ -382,7 +382,6 @@ async def plan_approval(
500:
description: Internal server error
"""

authenticated_user = get_authenticated_user_details(request_headers=request.headers)
user_id = authenticated_user["user_principal_id"]
if not user_id:
Expand All @@ -399,23 +398,44 @@ async def plan_approval(
orchestration_config.set_approval_result(
human_feedback.m_plan_id, human_feedback.approved
)
# orchestration_config.plans[human_feedback.m_plan_id][
# "plan_id"
# ] = human_feedback.plan_id
print("Plan approval received:", human_feedback)
# print(
# "Updated orchestration config:",
# orchestration_config.plans[human_feedback.m_plan_id],
# )

try:
result = await PlanService.handle_plan_approval(
human_feedback, user_id
)
print("Plan approval processed:", result)

except ValueError as ve:
print(f"ValueError processing plan approval: {ve}")
except Exception as e:
print(f"Error processing plan approval: {e}")
logger.error(f"ValueError processing plan approval: {ve}")
await connection_config.send_status_update_async(
{
"type": WebsocketMessageType.ERROR_MESSAGE,
"data": {
"content": "Approval failed due to invalid input.",
"status": "error",
"timestamp": asyncio.get_event_loop().time(),
},
},
user_id,
message_type=WebsocketMessageType.ERROR_MESSAGE,
)

except Exception:
logger.error("Error processing plan approval", exc_info=True)
await connection_config.send_status_update_async(
{
"type": WebsocketMessageType.ERROR_MESSAGE,
"data": {
"content": "An unexpected error occurred while processing the approval.",
"status": "error",
"timestamp": asyncio.get_event_loop().time(),
},
},
user_id,
message_type=WebsocketMessageType.ERROR_MESSAGE,
)

track_event_if_configured(
"PlanApprovalReceived",
{
Expand All @@ -437,6 +457,22 @@ async def plan_approval(
)
except Exception as e:
logging.error(f"Error processing plan approval: {e}")
try:
await connection_config.send_status_update_async(
{
"type": WebsocketMessageType.ERROR_MESSAGE,
"data": {
"content": "An error occurred while processing your approval request.",
"status": "error",
"timestamp": asyncio.get_event_loop().time(),
},
},
user_id,
message_type=WebsocketMessageType.ERROR_MESSAGE,
)
except Exception as ws_error:
# Don't let WebSocket send failure break the HTTP response
logging.warning(f"Failed to send WebSocket error: {ws_error}")
raise HTTPException(status_code=500, detail="Internal server error")


Expand Down
1 change: 1 addition & 0 deletions src/backend/v3/models/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,4 @@ class WebsocketMessageType(str, Enum):
USER_CLARIFICATION_RESPONSE = "user_clarification_response"
FINAL_RESULT_MESSAGE = "final_result_message"
TIMEOUT_NOTIFICATION = "timeout_notification"
ERROR_MESSAGE = "error_message"
119 changes: 112 additions & 7 deletions src/backend/v3/orchestration/orchestration_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from common.models.messages_kernel import TeamConfiguration
from semantic_kernel.agents.orchestration.magentic import MagenticOrchestration
from semantic_kernel.agents.runtime import InProcessRuntime
from azure.core.exceptions import ResourceNotFoundError

# Create custom execution settings to fix schema issues
from semantic_kernel.connectors.ai.open_ai import (
Expand Down Expand Up @@ -76,20 +77,28 @@ def _user_aware_agent_callback(user_id: str):
"""Factory method that creates a callback with captured user_id"""

def callback(message: ChatMessageContent):
return agent_response_callback(message, user_id)

try:
return agent_response_callback(message, user_id)
except Exception as e:
logger = logging.getLogger(f"{__name__}.OrchestrationManager")
logger.error(f"Error in agent response callback: {e}")
return callback

@staticmethod
def _user_aware_streaming_callback(user_id: str):
"""Factory method that creates a streaming callback with captured user_id"""

async def callback(
streaming_message: StreamingChatMessageContent, is_final: bool
streaming_message: StreamingChatMessageContent,
is_final: bool
):
return await streaming_agent_response_callback(
streaming_message, is_final, user_id
)
try:
return await streaming_agent_response_callback(
streaming_message, is_final, user_id
)
except Exception as e:
logger = logging.getLogger(f"{__name__}.OrchestrationManager")
logger.error(f"Error in streaming agent response callback: {e}")

return callback

Expand Down Expand Up @@ -172,14 +181,110 @@ async def run_orchestration(self, user_id, input_task) -> None:
message_type=WebsocketMessageType.FINAL_RESULT_MESSAGE,
)
self.logger.info(f"Final result sent via WebSocket to user {user_id}")

except ResourceNotFoundError as e:
self.logger.error(f"Agent not found: {e}")
self.logger.info(f"Error: {e}")
self.logger.info(f"Error type: {type(e).__name__}")
if hasattr(e, "__dict__"):
self.logger.info(f"Error attributes: {e.__dict__}")
self.logger.info("=" * 50)
error_content = "**Attention:** The agent is currently unavailable. Please check if it was deleted or recreated.\n\nIf yes, please create a new plan from the home page."
self.logger.info(f"🔴 Sending error message to user {user_id}: {error_content}")

await connection_config.send_status_update_async(
{
"type": WebsocketMessageType.ERROR_MESSAGE,
"data": {
"content": error_content,
"status": "error",
"timestamp": asyncio.get_event_loop().time(),
},
},
user_id,
message_type=WebsocketMessageType.ERROR_MESSAGE,
)
self.logger.info(f"✅ Error message sent via WebSocket to user {user_id}")

except Exception as e:
self.logger.error(f"Error processing final result: {e}")
# Send error message to user
await connection_config.send_status_update_async(
{
"type": WebsocketMessageType.ERROR_MESSAGE,
"data": {
"content": "**Attention:** An error occurred while processing the final response.\n\nPlease try creating a new plan from the home page.",
"status": "error",
"timestamp": asyncio.get_event_loop().time(),
},
},
user_id,
message_type=WebsocketMessageType.ERROR_MESSAGE,
)

except RuntimeError as e:
if "did not return any response" in str(e):
self.logger.error(f"Agent failed: {e}")
# Send user-friendly error via WebSocket
await connection_config.send_status_update_async(
{
"type": WebsocketMessageType.ERROR_MESSAGE,
"data": {
"content": "**Attention:** I'm having trouble connecting to the agent right now.\n\nPlease try creating a new plan from the home page or try again later.",
"status": "error",
"timestamp": asyncio.get_event_loop().time(),
},
},
user_id,
message_type=WebsocketMessageType.ERROR_MESSAGE,
)
else:
self.logger.exception("Unexpected RuntimeError")
self.logger.info(f"Error: {e}")
self.logger.info(f"Error type: {type(e).__name__}")
if hasattr(e, "__dict__"):
self.logger.info(f"Error attributes: {e.__dict__}")
self.logger.info("=" * 50)
# Fallback error message
await connection_config.send_status_update_async(
{
"type": WebsocketMessageType.ERROR_MESSAGE,
"data": {
"content": "**Attention:** Something went wrong.\n\nPlease try creating a new plan from the home page or try again later.",
"status": "error",
"timestamp": asyncio.get_event_loop().time(),
},
},
user_id,
message_type=WebsocketMessageType.ERROR_MESSAGE,
)

except Exception as e:
self.logger.error(f"Unexpected error: {e}")
self.logger.error(f"🚨 Unexpected error during orchestration: {e}")
self.logger.info(f"Error: {e}")
self.logger.info(f"Error type: {type(e).__name__}")
if hasattr(e, "__dict__"):
self.logger.info(f"Error attributes: {e.__dict__}")
self.logger.info("=" * 50)

error_content = "**Attention:** Something went wrong.\n\nPlease try creating a new plan from the home page or try again later."

self.logger.info(f"🔴 Sending error message to user {user_id}: {error_content}")

await connection_config.send_status_update_async(
{
"type": WebsocketMessageType.ERROR_MESSAGE,
"data": {
"content": error_content,
"status": "error",
"timestamp": asyncio.get_event_loop().time(),
},
},
user_id,
message_type=WebsocketMessageType.ERROR_MESSAGE,
)

self.logger.info(f"✅ Error message sent via WebSocket to user {user_id}")

finally:
await runtime.stop_when_idle()
4 changes: 3 additions & 1 deletion src/frontend/src/models/enums.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,11 +252,13 @@ export enum WebsocketMessageType {
REPLAN_APPROVAL_RESPONSE = "replan_approval_response",
USER_CLARIFICATION_REQUEST = "user_clarification_request",
USER_CLARIFICATION_RESPONSE = "user_clarification_response",
FINAL_RESULT_MESSAGE = "final_result_message"
FINAL_RESULT_MESSAGE = "final_result_message",
ERROR_MESSAGE = 'error_message'
}

export enum AgentMessageType {
HUMAN_AGENT = "Human_Agent",
AI_AGENT = "AI_Agent",
SYSTEM_AGENT = "SYSTEM_AGENT"
}

Loading
Loading