Skip to content

Commit 01a74aa

Browse files
Merge pull request #714 from microsoft/dev-v3
chore: merge dev into main
2 parents a9a0557 + ad55b11 commit 01a74aa

File tree

11 files changed

+624
-601
lines changed

11 files changed

+624
-601
lines changed

.flake8

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
[flake8]
22
max-line-length = 88
33
extend-ignore = E501
4-
exclude = .venv, frontend, src/backend/tests
4+
exclude =
5+
.venv,
6+
frontend,
7+
src/frontend,
8+
src/backend/tests,
9+
*.tsx,
10+
*.ts,
11+
*.jsx,
12+
*.js
513
ignore = E203, W503, G004, G200, E402

docs/ACRBuildAndPushGuide.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,29 @@ If you want to update image tag and image manually you can follow below steps:
7474
![alt text](./images/AppServiceContainer.png)
7575

7676

77+
**MCP Server :**
78+
79+
```bash
80+
az acr login --name <containerregname>
81+
docker build --no-cache -f src/mcp_server/Dockerfile -t <acrloginserver>/<repo>:<tagname> ./src/mcp_server
82+
docker push <acrloginserver>/<repo>:<tagname>
83+
```
84+
85+
If you want to update image tag and image manually you can follow below steps:
86+
- Go to your **Container App** in the [Azure Portal](https://portal.azure.com/#home).
87+
- In the left menu, select **Containers**.
88+
- Under your container, update:
89+
90+
- Image source → Azure Container Registry / Docker Hub.
91+
92+
- Image name → myapp/mcp.
93+
94+
- Tag → change to the new one you pushed (e.g., v2).
95+
96+
- Click **Save** → this will create a new revision automatically with the updated image.
97+
98+
![alt text](./images/mcpContainer.png)
99+
77100
## ✅ Verification
78101

79102
Run the following command to verify that images were pushed successfully:

docs/TroubleShootingSteps.md

Lines changed: 137 additions & 572 deletions
Large diffs are not rendered by default.

docs/images/mcpContainer.png

99.8 KB
Loading

src/backend/v3/api/router.py

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Optional
66

77
import v3.models.messages as messages
8+
from v3.models.messages import WebsocketMessageType
89
from auth.auth_utils import get_authenticated_user_details
910
from common.database.database_factory import DatabaseFactory
1011
from common.models.messages_kernel import (
@@ -335,7 +336,6 @@ async def plan_approval(
335336
):
336337
"""
337338
Endpoint to receive plan approval or rejection from the user.
338-
339339
---
340340
tags:
341341
- Plans
@@ -382,7 +382,6 @@ async def plan_approval(
382382
500:
383383
description: Internal server error
384384
"""
385-
386385
authenticated_user = get_authenticated_user_details(request_headers=request.headers)
387386
user_id = authenticated_user["user_principal_id"]
388387
if not user_id:
@@ -399,23 +398,44 @@ async def plan_approval(
399398
orchestration_config.set_approval_result(
400399
human_feedback.m_plan_id, human_feedback.approved
401400
)
402-
# orchestration_config.plans[human_feedback.m_plan_id][
403-
# "plan_id"
404-
# ] = human_feedback.plan_id
405401
print("Plan approval received:", human_feedback)
406-
# print(
407-
# "Updated orchestration config:",
408-
# orchestration_config.plans[human_feedback.m_plan_id],
409-
# )
402+
410403
try:
411404
result = await PlanService.handle_plan_approval(
412405
human_feedback, user_id
413406
)
414407
print("Plan approval processed:", result)
408+
415409
except ValueError as ve:
416-
print(f"ValueError processing plan approval: {ve}")
417-
except Exception as e:
418-
print(f"Error processing plan approval: {e}")
410+
logger.error(f"ValueError processing plan approval: {ve}")
411+
await connection_config.send_status_update_async(
412+
{
413+
"type": WebsocketMessageType.ERROR_MESSAGE,
414+
"data": {
415+
"content": "Approval failed due to invalid input.",
416+
"status": "error",
417+
"timestamp": asyncio.get_event_loop().time(),
418+
},
419+
},
420+
user_id,
421+
message_type=WebsocketMessageType.ERROR_MESSAGE,
422+
)
423+
424+
except Exception:
425+
logger.error("Error processing plan approval", exc_info=True)
426+
await connection_config.send_status_update_async(
427+
{
428+
"type": WebsocketMessageType.ERROR_MESSAGE,
429+
"data": {
430+
"content": "An unexpected error occurred while processing the approval.",
431+
"status": "error",
432+
"timestamp": asyncio.get_event_loop().time(),
433+
},
434+
},
435+
user_id,
436+
message_type=WebsocketMessageType.ERROR_MESSAGE,
437+
)
438+
419439
track_event_if_configured(
420440
"PlanApprovalReceived",
421441
{
@@ -437,6 +457,22 @@ async def plan_approval(
437457
)
438458
except Exception as e:
439459
logging.error(f"Error processing plan approval: {e}")
460+
try:
461+
await connection_config.send_status_update_async(
462+
{
463+
"type": WebsocketMessageType.ERROR_MESSAGE,
464+
"data": {
465+
"content": "An error occurred while processing your approval request.",
466+
"status": "error",
467+
"timestamp": asyncio.get_event_loop().time(),
468+
},
469+
},
470+
user_id,
471+
message_type=WebsocketMessageType.ERROR_MESSAGE,
472+
)
473+
except Exception as ws_error:
474+
# Don't let WebSocket send failure break the HTTP response
475+
logging.warning(f"Failed to send WebSocket error: {ws_error}")
440476
raise HTTPException(status_code=500, detail="Internal server error")
441477

442478

src/backend/v3/models/messages.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,4 @@ class WebsocketMessageType(str, Enum):
214214
USER_CLARIFICATION_RESPONSE = "user_clarification_response"
215215
FINAL_RESULT_MESSAGE = "final_result_message"
216216
TIMEOUT_NOTIFICATION = "timeout_notification"
217+
ERROR_MESSAGE = "error_message"

src/backend/v3/orchestration/orchestration_manager.py

Lines changed: 112 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from common.models.messages_kernel import TeamConfiguration
1010
from semantic_kernel.agents.orchestration.magentic import MagenticOrchestration
1111
from semantic_kernel.agents.runtime import InProcessRuntime
12+
from azure.core.exceptions import ResourceNotFoundError
1213

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

7879
def callback(message: ChatMessageContent):
79-
return agent_response_callback(message, user_id)
80-
80+
try:
81+
return agent_response_callback(message, user_id)
82+
except Exception as e:
83+
logger = logging.getLogger(f"{__name__}.OrchestrationManager")
84+
logger.error(f"Error in agent response callback: {e}")
8185
return callback
8286

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

8791
async def callback(
88-
streaming_message: StreamingChatMessageContent, is_final: bool
92+
streaming_message: StreamingChatMessageContent,
93+
is_final: bool
8994
):
90-
return await streaming_agent_response_callback(
91-
streaming_message, is_final, user_id
92-
)
95+
try:
96+
return await streaming_agent_response_callback(
97+
streaming_message, is_final, user_id
98+
)
99+
except Exception as e:
100+
logger = logging.getLogger(f"{__name__}.OrchestrationManager")
101+
logger.error(f"Error in streaming agent response callback: {e}")
93102

94103
return callback
95104

@@ -172,14 +181,110 @@ async def run_orchestration(self, user_id, input_task) -> None:
172181
message_type=WebsocketMessageType.FINAL_RESULT_MESSAGE,
173182
)
174183
self.logger.info(f"Final result sent via WebSocket to user {user_id}")
184+
185+
except ResourceNotFoundError as e:
186+
self.logger.error(f"Agent not found: {e}")
187+
self.logger.info(f"Error: {e}")
188+
self.logger.info(f"Error type: {type(e).__name__}")
189+
if hasattr(e, "__dict__"):
190+
self.logger.info(f"Error attributes: {e.__dict__}")
191+
self.logger.info("=" * 50)
192+
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."
193+
self.logger.info(f"🔴 Sending error message to user {user_id}: {error_content}")
194+
195+
await connection_config.send_status_update_async(
196+
{
197+
"type": WebsocketMessageType.ERROR_MESSAGE,
198+
"data": {
199+
"content": error_content,
200+
"status": "error",
201+
"timestamp": asyncio.get_event_loop().time(),
202+
},
203+
},
204+
user_id,
205+
message_type=WebsocketMessageType.ERROR_MESSAGE,
206+
)
207+
self.logger.info(f"✅ Error message sent via WebSocket to user {user_id}")
208+
175209
except Exception as e:
210+
self.logger.error(f"Error processing final result: {e}")
211+
# Send error message to user
212+
await connection_config.send_status_update_async(
213+
{
214+
"type": WebsocketMessageType.ERROR_MESSAGE,
215+
"data": {
216+
"content": "**Attention:** An error occurred while processing the final response.\n\nPlease try creating a new plan from the home page.",
217+
"status": "error",
218+
"timestamp": asyncio.get_event_loop().time(),
219+
},
220+
},
221+
user_id,
222+
message_type=WebsocketMessageType.ERROR_MESSAGE,
223+
)
224+
225+
except RuntimeError as e:
226+
if "did not return any response" in str(e):
227+
self.logger.error(f"Agent failed: {e}")
228+
# Send user-friendly error via WebSocket
229+
await connection_config.send_status_update_async(
230+
{
231+
"type": WebsocketMessageType.ERROR_MESSAGE,
232+
"data": {
233+
"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.",
234+
"status": "error",
235+
"timestamp": asyncio.get_event_loop().time(),
236+
},
237+
},
238+
user_id,
239+
message_type=WebsocketMessageType.ERROR_MESSAGE,
240+
)
241+
else:
242+
self.logger.exception("Unexpected RuntimeError")
176243
self.logger.info(f"Error: {e}")
177244
self.logger.info(f"Error type: {type(e).__name__}")
178245
if hasattr(e, "__dict__"):
179246
self.logger.info(f"Error attributes: {e.__dict__}")
180247
self.logger.info("=" * 50)
248+
# Fallback error message
249+
await connection_config.send_status_update_async(
250+
{
251+
"type": WebsocketMessageType.ERROR_MESSAGE,
252+
"data": {
253+
"content": "**Attention:** Something went wrong.\n\nPlease try creating a new plan from the home page or try again later.",
254+
"status": "error",
255+
"timestamp": asyncio.get_event_loop().time(),
256+
},
257+
},
258+
user_id,
259+
message_type=WebsocketMessageType.ERROR_MESSAGE,
260+
)
181261

182262
except Exception as e:
183-
self.logger.error(f"Unexpected error: {e}")
263+
self.logger.error(f"🚨 Unexpected error during orchestration: {e}")
264+
self.logger.info(f"Error: {e}")
265+
self.logger.info(f"Error type: {type(e).__name__}")
266+
if hasattr(e, "__dict__"):
267+
self.logger.info(f"Error attributes: {e.__dict__}")
268+
self.logger.info("=" * 50)
269+
270+
error_content = "**Attention:** Something went wrong.\n\nPlease try creating a new plan from the home page or try again later."
271+
272+
self.logger.info(f"🔴 Sending error message to user {user_id}: {error_content}")
273+
274+
await connection_config.send_status_update_async(
275+
{
276+
"type": WebsocketMessageType.ERROR_MESSAGE,
277+
"data": {
278+
"content": error_content,
279+
"status": "error",
280+
"timestamp": asyncio.get_event_loop().time(),
281+
},
282+
},
283+
user_id,
284+
message_type=WebsocketMessageType.ERROR_MESSAGE,
285+
)
286+
287+
self.logger.info(f"✅ Error message sent via WebSocket to user {user_id}")
288+
184289
finally:
185290
await runtime.stop_when_idle()

src/frontend/src/models/enums.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,13 @@ export enum WebsocketMessageType {
252252
REPLAN_APPROVAL_RESPONSE = "replan_approval_response",
253253
USER_CLARIFICATION_REQUEST = "user_clarification_request",
254254
USER_CLARIFICATION_RESPONSE = "user_clarification_response",
255-
FINAL_RESULT_MESSAGE = "final_result_message"
255+
FINAL_RESULT_MESSAGE = "final_result_message",
256+
ERROR_MESSAGE = 'error_message'
256257
}
257258

258259
export enum AgentMessageType {
259260
HUMAN_AGENT = "Human_Agent",
260261
AI_AGENT = "AI_Agent",
262+
SYSTEM_AGENT = "SYSTEM_AGENT"
261263
}
262264

0 commit comments

Comments
 (0)