Skip to content

Commit e3dbb90

Browse files
committed
Merge remote-tracking branch 'microsoft/macae-v3-dev-v2-vip' into macae-v3-fr-dev-92
2 parents e442372 + 4016f9b commit e3dbb90

File tree

7 files changed

+87
-115
lines changed

7 files changed

+87
-115
lines changed

data/agent_teams/new 29.txt

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/backend/v3/api/router.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
from kernel_agents.agent_factory import AgentFactory
2020
from semantic_kernel.agents.runtime import InProcessRuntime
2121
from v3.common.services.team_service import TeamService
22-
from v3.config.settings import connection_config, current_user_id, team_config
22+
from v3.config.settings import (connection_config, current_user_id,
23+
orchestration_config, team_config)
2324
from v3.orchestration.orchestration_manager import OrchestrationManager
2425

2526
router = APIRouter()
@@ -295,8 +296,35 @@ async def run_with_context():
295296
)
296297
raise HTTPException(status_code=400, detail=f"Error starting request: {e}") from e
297298

298-
@app_v3.post("/api/human_feedback")
299-
async def human_feedback_endpoint(human_feedback: messages.HumanFeedback, request: Request):
299+
@app_v3.post("/plan_approval")
300+
async def plan_approval(human_feedback: messages.PlanApprovalResponse, request: Request):
301+
""" Endpoint to receive plan approval or rejection from the user. """
302+
authenticated_user = get_authenticated_user_details(request_headers=request.headers)
303+
user_id = authenticated_user["user_principal_id"]
304+
if not user_id:
305+
raise HTTPException(
306+
status_code=401, detail="Missing or invalid user information"
307+
)
308+
# Set the approval in the orchestration config
309+
if user_id and human_feedback.plan_id:
310+
if orchestration_config and human_feedback.plan_id in orchestration_config.approvals:
311+
orchestration_config.approvals[human_feedback.plan_id] = human_feedback.approved
312+
track_event_if_configured(
313+
"PlanApprovalReceived",
314+
{
315+
"plan_id": human_feedback.plan_id,
316+
"approved": human_feedback.approved,
317+
"user_id": user_id,
318+
"feedback": human_feedback.feedback
319+
},
320+
)
321+
return {"status": "approval recorded"}
322+
else:
323+
logging.warning(f"No orchestration or plan found for plan_id: {human_feedback.plan_id}")
324+
raise HTTPException(status_code=404, detail="No active plan found for approval")
325+
326+
@app_v3.post("/user_clarification")
327+
async def user_clarification(human_feedback: messages.UserClarificationResponse, request: Request):
300328
pass
301329

302330

src/backend/v3/magentic_agents/proxy_agent.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from collections.abc import AsyncIterable
77
from typing import AsyncIterator, Optional
88

9-
import v3.models.messages as agent_messages
109
from pydantic import Field
1110
from semantic_kernel.agents import ( # pylint: disable=no-name-in-module
1211
AgentResponseItem, AgentThread)
@@ -22,6 +21,8 @@
2221
from v3.callbacks.response_handlers import (agent_response_callback,
2322
streaming_agent_response_callback)
2423
from v3.config.settings import current_user_id
24+
from v3.models.messages import (UserClarificationRequest,
25+
UserClarificationResponse)
2526

2627

2728
class DummyAgentThread(AgentThread):
@@ -145,6 +146,7 @@ async def invoke(self, message: str,*, thread: AgentThread | None = None,**kwarg
145146
)
146147
# Send clarification request via response handlers
147148
clarification_request = f"I need clarification about: {message}"
149+
148150
clarification_message = self._create_message_content(clarification_request, thread.id)
149151
await self._trigger_response_callbacks(clarification_message)
150152

src/backend/v3/models/messages.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,26 @@ class PlanApprovalRequest:
4343
"""Request for plan approval from the frontend."""
4444
plan: MPlan
4545
status: PlanStatus
46-
4746
context: dict | None = None
4847

4948
@dataclass(slots=True)
5049
class PlanApprovalResponse:
5150
"""Response for plan approval from the frontend."""
51+
plan_id: str
5252
approved: bool
5353
feedback: str | None = None
5454

5555
@dataclass(slots=True)
5656
class ReplanApprovalRequest:
5757
"""Request for replan approval from the frontend."""
58+
new_plan: MPlan
5859
reason: str
5960
context: dict | None = None
6061

6162
@dataclass(slots=True)
6263
class ReplanApprovalResponse:
6364
"""Response for replan approval from the frontend."""
65+
plan_id: str
6466
approved: bool
6567
feedback: str | None = None
6668

@@ -73,8 +75,8 @@ class UserClarificationRequest:
7375
@dataclass(slots=True)
7476
class UserClarificationResponse:
7577
"""Response for user clarification from the frontend."""
76-
def __init__(self, answer: str):
77-
self.answer = answer
78+
plan_id: str | None
79+
answer: str = ""
7880

7981
@dataclass(slots=True)
8082
class FinalResultMessage:
@@ -111,12 +113,4 @@ class ApprovalRequest(KernelBaseModel):
111113
session_id: str
112114
user_id: str
113115
action: str
114-
agent_name: str
115-
116-
@dataclass(slots=True)
117-
class HumanClarification(KernelBaseModel):
118-
"""Message containing human clarification on a plan."""
119-
120-
plan_id: str
121-
session_id: str
122-
human_clarification: str
116+
agent_name: str

src/backend/v3/models/models.py

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,32 +27,15 @@ def agent(self):
2727
def agent(self, value):
2828
self._agent = value if value is not None else ""
2929

30+
3031
class MPlan(BaseModel):
32+
"""model of a plan"""
3133
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
32-
session_id: Optional[str] = None
33-
team_id: Optional[str] = None
34-
user_id: Optional[str] = None
34+
user_id: str = ""
35+
team_id: str = ""
36+
plan_id: str = ""
3537
overall_status: PlanStatus = PlanStatus.CREATED
36-
progress: int = 0 # 0-100 percentage
37-
current_step: Optional[str] = None
38-
result: Optional[str] = None
39-
error_message: Optional[str] = None
40-
created_at: datetime = Field(datetime.now(timezone.utc))
41-
updated_at: datetime = Field(datetime.now(timezone.utc))
42-
estimated_completion: Optional[datetime] = None
43-
user_request: Optional[str] = None
38+
user_request: str = ""
4439
team: List[str] = []
45-
facts: Optional[str] = None
46-
steps: List[MStep] = Field(default_factory=list)
47-
48-
# class MPlan(BaseModel):
49-
# """model of a plan"""
50-
# session_id: str = ""
51-
# user_id: str = ""
52-
# team_id: str = ""
53-
# plan_id: str = ""
54-
# user_request: str = ""
55-
# team: List[str] = []
56-
# facts: str = ""
57-
# steps: List[MStep] = []
58-
40+
facts: str = ""
41+
steps: List[MStep] = []

src/backend/v3/orchestration/human_approval_manager.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Extends StandardMagenticManager to add approval gates before plan execution.
44
"""
55

6+
import asyncio
67
import re
78
from typing import Any, List, Optional
89

@@ -13,7 +14,8 @@
1314
from semantic_kernel.agents.orchestration.prompts._magentic_prompts import \
1415
ORCHESTRATOR_TASK_LEDGER_FACTS_PROMPT
1516
from semantic_kernel.contents import ChatMessageContent
16-
from v3.config.settings import connection_config, current_user_id
17+
from v3.config.settings import (connection_config, current_user_id,
18+
orchestration_config)
1719
from v3.models.models import MPlan, MStep
1820

1921

@@ -104,18 +106,23 @@ async def plan(self, magentic_context: MagenticContext) -> Any:
104106
# )
105107

106108

107-
async def _wait_for_user_approval(self) -> Optional[messages.PlanApprovalResponse]:
109+
async def _wait_for_user_approval(self, plan_id: Optional[str] = None) -> Optional[messages.PlanApprovalResponse]: # plan_id will not be optional in future
108110
"""Wait for user approval response."""
109-
user_id = current_user_id.get()
110111
# Temporarily use console input for approval - will switch to WebSocket or API in future
111-
response = input("\nApprove this execution plan? [y/n]: ").strip().lower()
112-
if response in ['y', 'yes']:
113-
return messages.PlanApprovalResponse(approved=True)
114-
elif response in ['n', 'no']:
115-
return messages.PlanApprovalResponse(approved=False)
116-
else:
117-
print("Invalid input. Please enter 'y' for yes or 'n' for no.")
118-
return await self._wait_for_user_approval()
112+
# response = input("\nApprove this execution plan? [y/n]: ").strip().lower()
113+
# if response in ['y', 'yes']:
114+
# return messages.PlanApprovalResponse(approved=True, plan_id=plan_id if plan_id else "input")
115+
# elif response in ['n', 'no']:
116+
# return messages.PlanApprovalResponse(approved=False, plan_id=plan_id if plan_id else "input")
117+
# else:
118+
# print("Invalid input. Please enter 'y' for yes or 'n' for no.")
119+
# return await self._wait_for_user_approval()
120+
# In future, implement actual waiting for WebSocket or API response here
121+
if plan_id not in orchestration_config.approvals:
122+
orchestration_config.approvals[plan_id] = None
123+
while orchestration_config.approvals[plan_id] is None:
124+
await asyncio.sleep(0.2)
125+
return messages.PlanApprovalResponse(approved=orchestration_config.approvals[plan_id], plan_id=plan_id)
119126

120127

121128
async def prepare_final_answer(self, magentic_context: MagenticContext) -> ChatMessageContent:

src/mcp_server/config/settings.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,38 @@
44

55
import os
66
from typing import Optional
7-
from pydantic import BaseModel, Field
7+
8+
from pydantic import BaseModel, ConfigDict, Field
89
from pydantic_settings import BaseSettings
910

1011

1112
class MCPServerConfig(BaseSettings):
1213
"""MCP Server configuration."""
14+
15+
model_config = ConfigDict(
16+
env_file=".env",
17+
env_file_encoding="utf-8",
18+
extra="ignore" # This will ignore extra environment variables
19+
)
1320

1421
# Server settings
15-
host: str = Field(default="0.0.0.0", env="MCP_HOST")
16-
port: int = Field(default=9000, env="MCP_PORT")
17-
debug: bool = Field(default=False, env="MCP_DEBUG")
22+
host: str = Field(default="0.0.0.0")
23+
port: int = Field(default=9000)
24+
debug: bool = Field(default=False)
1825

1926
# Authentication settings
20-
tenant_id: Optional[str] = Field(default=None, env="AZURE_TENANT_ID")
21-
client_id: Optional[str] = Field(default=None, env="AZURE_CLIENT_ID")
22-
jwks_uri: Optional[str] = Field(default=None, env="AZURE_JWKS_URI")
23-
issuer: Optional[str] = Field(default=None, env="AZURE_ISSUER")
24-
audience: Optional[str] = Field(default=None, env="AZURE_AUDIENCE")
27+
tenant_id: Optional[str] = Field(default=None)
28+
client_id: Optional[str] = Field(default=None)
29+
jwks_uri: Optional[str] = Field(default=None)
30+
issuer: Optional[str] = Field(default=None)
31+
audience: Optional[str] = Field(default=None)
2532

2633
# MCP specific settings
27-
server_name: str = Field(default="MACAE MCP Server", env="MCP_SERVER_NAME")
28-
enable_auth: bool = Field(default=True, env="MCP_ENABLE_AUTH")
29-
30-
class Config:
31-
env_file = ".env"
32-
env_file_encoding = "utf-8"
34+
server_name: str = Field(default="MACAE MCP Server")
35+
enable_auth: bool = Field(default=True)
36+
37+
# Dataset path - added to handle the environment variable
38+
dataset_path: str = Field(default="./datasets")
3339

3440

3541
# Global configuration instance

0 commit comments

Comments
 (0)