Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
48 changes: 0 additions & 48 deletions data/agent_teams/new 29.txt

This file was deleted.

34 changes: 31 additions & 3 deletions src/backend/v3/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from kernel_agents.agent_factory import AgentFactory
from semantic_kernel.agents.runtime import InProcessRuntime
from v3.common.services.team_service import TeamService
from v3.config.settings import connection_config, current_user_id, team_config
from v3.config.settings import (connection_config, current_user_id,
orchestration_config, team_config)
from v3.orchestration.orchestration_manager import OrchestrationManager

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

@app_v3.post("/api/human_feedback")
async def human_feedback_endpoint(human_feedback: messages.HumanFeedback, request: Request):
@app_v3.post("/plan_approval")
async def plan_approval(human_feedback: messages.PlanApprovalResponse, request: Request):
""" Endpoint to receive plan approval or rejection from the user. """
authenticated_user = get_authenticated_user_details(request_headers=request.headers)
user_id = authenticated_user["user_principal_id"]
if not user_id:
raise HTTPException(
status_code=401, detail="Missing or invalid user information"
)
# Set the approval in the orchestration config
if user_id and human_feedback.plan_id:
if orchestration_config and human_feedback.plan_id in orchestration_config.approvals:
orchestration_config.approvals[human_feedback.plan_id] = human_feedback.approved
track_event_if_configured(
"PlanApprovalReceived",
{
"plan_id": human_feedback.plan_id,
"approved": human_feedback.approved,
"user_id": user_id,
"feedback": human_feedback.feedback
},
)
return {"status": "approval recorded"}
else:
logging.warning(f"No orchestration or plan found for plan_id: {human_feedback.plan_id}")
raise HTTPException(status_code=404, detail="No active plan found for approval")

@app_v3.post("/user_clarification")
async def user_clarification(human_feedback: messages.UserClarificationResponse, request: Request):
pass


Expand Down
4 changes: 3 additions & 1 deletion src/backend/v3/magentic_agents/proxy_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from collections.abc import AsyncIterable
from typing import AsyncIterator, Optional

import v3.models.messages as agent_messages
from pydantic import Field
from semantic_kernel.agents import ( # pylint: disable=no-name-in-module
AgentResponseItem, AgentThread)
Expand All @@ -22,6 +21,8 @@
from v3.callbacks.response_handlers import (agent_response_callback,
streaming_agent_response_callback)
from v3.config.settings import current_user_id
from v3.models.messages import (UserClarificationRequest,
UserClarificationResponse)


class DummyAgentThread(AgentThread):
Expand Down Expand Up @@ -145,6 +146,7 @@ async def invoke(self, message: str,*, thread: AgentThread | None = None,**kwarg
)
# Send clarification request via response handlers
clarification_request = f"I need clarification about: {message}"

clarification_message = self._create_message_content(clarification_request, thread.id)
await self._trigger_response_callbacks(clarification_message)

Expand Down
18 changes: 6 additions & 12 deletions src/backend/v3/models/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,26 @@ class PlanApprovalRequest:
"""Request for plan approval from the frontend."""
plan: MPlan
status: PlanStatus

context: dict | None = None

@dataclass(slots=True)
class PlanApprovalResponse:
"""Response for plan approval from the frontend."""
plan_id: str
approved: bool
feedback: str | None = None

@dataclass(slots=True)
class ReplanApprovalRequest:
"""Request for replan approval from the frontend."""
new_plan: MPlan
reason: str
context: dict | None = None

@dataclass(slots=True)
class ReplanApprovalResponse:
"""Response for replan approval from the frontend."""
plan_id: str
approved: bool
feedback: str | None = None

Expand All @@ -73,8 +75,8 @@ class UserClarificationRequest:
@dataclass(slots=True)
class UserClarificationResponse:
"""Response for user clarification from the frontend."""
def __init__(self, answer: str):
self.answer = answer
plan_id: str | None
answer: str = ""

@dataclass(slots=True)
class FinalResultMessage:
Expand Down Expand Up @@ -111,12 +113,4 @@ class ApprovalRequest(KernelBaseModel):
session_id: str
user_id: str
action: str
agent_name: str

@dataclass(slots=True)
class HumanClarification(KernelBaseModel):
"""Message containing human clarification on a plan."""

plan_id: str
session_id: str
human_clarification: str
agent_name: str
33 changes: 8 additions & 25 deletions src/backend/v3/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,15 @@ def agent(self):
def agent(self, value):
self._agent = value if value is not None else ""


class MPlan(BaseModel):
"""model of a plan"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
session_id: Optional[str] = None
team_id: Optional[str] = None
user_id: Optional[str] = None
user_id: str = ""
team_id: str = ""
plan_id: str = ""
overall_status: PlanStatus = PlanStatus.CREATED
progress: int = 0 # 0-100 percentage
current_step: Optional[str] = None
result: Optional[str] = None
error_message: Optional[str] = None
created_at: datetime = Field(datetime.now(timezone.utc))
updated_at: datetime = Field(datetime.now(timezone.utc))
estimated_completion: Optional[datetime] = None
user_request: Optional[str] = None
user_request: str = ""
team: List[str] = []
facts: Optional[str] = None
steps: List[MStep] = Field(default_factory=list)

# class MPlan(BaseModel):
# """model of a plan"""
# session_id: str = ""
# user_id: str = ""
# team_id: str = ""
# plan_id: str = ""
# user_request: str = ""
# team: List[str] = []
# facts: str = ""
# steps: List[MStep] = []

facts: str = ""
steps: List[MStep] = []
29 changes: 18 additions & 11 deletions src/backend/v3/orchestration/human_approval_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Extends StandardMagenticManager to add approval gates before plan execution.
"""

import asyncio
import re
from typing import Any, List, Optional

Expand All @@ -13,7 +14,8 @@
from semantic_kernel.agents.orchestration.prompts._magentic_prompts import \
ORCHESTRATOR_TASK_LEDGER_FACTS_PROMPT
from semantic_kernel.contents import ChatMessageContent
from v3.config.settings import connection_config, current_user_id
from v3.config.settings import (connection_config, current_user_id,
orchestration_config)
from v3.models.models import MPlan, MStep


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


async def _wait_for_user_approval(self) -> Optional[messages.PlanApprovalResponse]:
async def _wait_for_user_approval(self, plan_id: Optional[str] = None) -> Optional[messages.PlanApprovalResponse]: # plan_id will not be optional in future
"""Wait for user approval response."""
user_id = current_user_id.get()
# Temporarily use console input for approval - will switch to WebSocket or API in future
response = input("\nApprove this execution plan? [y/n]: ").strip().lower()
if response in ['y', 'yes']:
return messages.PlanApprovalResponse(approved=True)
elif response in ['n', 'no']:
return messages.PlanApprovalResponse(approved=False)
else:
print("Invalid input. Please enter 'y' for yes or 'n' for no.")
return await self._wait_for_user_approval()
# response = input("\nApprove this execution plan? [y/n]: ").strip().lower()
# if response in ['y', 'yes']:
# return messages.PlanApprovalResponse(approved=True, plan_id=plan_id if plan_id else "input")
# elif response in ['n', 'no']:
# return messages.PlanApprovalResponse(approved=False, plan_id=plan_id if plan_id else "input")
# else:
# print("Invalid input. Please enter 'y' for yes or 'n' for no.")
# return await self._wait_for_user_approval()
# In future, implement actual waiting for WebSocket or API response here
if plan_id not in orchestration_config.approvals:
orchestration_config.approvals[plan_id] = None
while orchestration_config.approvals[plan_id] is None:
await asyncio.sleep(0.2)
return messages.PlanApprovalResponse(approved=orchestration_config.approvals[plan_id], plan_id=plan_id)


async def prepare_final_answer(self, magentic_context: MagenticContext) -> ChatMessageContent:
Expand Down
36 changes: 21 additions & 15 deletions src/mcp_server/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,38 @@

import os
from typing import Optional
from pydantic import BaseModel, Field

from pydantic import BaseModel, ConfigDict, Field
from pydantic_settings import BaseSettings


class MCPServerConfig(BaseSettings):
"""MCP Server configuration."""

model_config = ConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore" # This will ignore extra environment variables
)

# Server settings
host: str = Field(default="0.0.0.0", env="MCP_HOST")
port: int = Field(default=9000, env="MCP_PORT")
debug: bool = Field(default=False, env="MCP_DEBUG")
host: str = Field(default="0.0.0.0")
port: int = Field(default=9000)
debug: bool = Field(default=False)

# Authentication settings
tenant_id: Optional[str] = Field(default=None, env="AZURE_TENANT_ID")
client_id: Optional[str] = Field(default=None, env="AZURE_CLIENT_ID")
jwks_uri: Optional[str] = Field(default=None, env="AZURE_JWKS_URI")
issuer: Optional[str] = Field(default=None, env="AZURE_ISSUER")
audience: Optional[str] = Field(default=None, env="AZURE_AUDIENCE")
tenant_id: Optional[str] = Field(default=None)
client_id: Optional[str] = Field(default=None)
jwks_uri: Optional[str] = Field(default=None)
issuer: Optional[str] = Field(default=None)
audience: Optional[str] = Field(default=None)

# MCP specific settings
server_name: str = Field(default="MACAE MCP Server", env="MCP_SERVER_NAME")
enable_auth: bool = Field(default=True, env="MCP_ENABLE_AUTH")

class Config:
env_file = ".env"
env_file_encoding = "utf-8"
server_name: str = Field(default="MACAE MCP Server")
enable_auth: bool = Field(default=True)

# Dataset path - added to handle the environment variable
dataset_path: str = Field(default="./datasets")


# Global configuration instance
Expand Down
Loading