Skip to content

Commit 2f5d09d

Browse files
committed
added proxy agent support and fixed Hr scenario issues
1 parent eadcf5d commit 2f5d09d

File tree

10 files changed

+191
-336
lines changed

10 files changed

+191
-336
lines changed

data/agent_teams/hr.json

Lines changed: 37 additions & 53 deletions
Large diffs are not rendered by default.

data/agent_teams/marketing.json

Lines changed: 38 additions & 54 deletions
Large diffs are not rendered by default.

data/agent_teams/retail.json

Lines changed: 35 additions & 51 deletions
Large diffs are not rendered by default.

data/agent_teams/sample.json

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

src/backend/app_kernel.py

Lines changed: 55 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -663,60 +663,62 @@ async def get_plans(
663663
# Initialize agent team for this user session
664664
await OrchestrationManager.get_current_orchestration(user_id=user_id)
665665

666-
# Initialize memory context
667-
memory_store = await DatabaseFactory.get_database(user_id=user_id)
668-
if session_id:
669-
plan = await memory_store.get_plan_by_session(session_id=session_id)
670-
if not plan:
671-
track_event_if_configured(
672-
"GetPlanBySessionNotFound",
673-
{"status_code": 400, "detail": "Plan not found"},
674-
)
675-
raise HTTPException(status_code=404, detail="Plan not found")
676-
677-
# Use get_steps_by_plan to match the original implementation
678-
steps = await memory_store.get_steps_by_plan(plan_id=plan.id)
679-
plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps)
680-
plan_with_steps.update_step_counts()
681-
return [plan_with_steps]
682-
if plan_id:
683-
plan = await memory_store.get_plan_by_plan_id(plan_id=plan_id)
684-
if not plan:
685-
track_event_if_configured(
686-
"GetPlanBySessionNotFound",
687-
{"status_code": 400, "detail": "Plan not found"},
688-
)
689-
raise HTTPException(status_code=404, detail="Plan not found")
666+
# Replace the following with code to get plan run history from the database
667+
668+
# # Initialize memory context
669+
# memory_store = await DatabaseFactory.get_database(user_id=user_id)
670+
# if session_id:
671+
# plan = await memory_store.get_plan_by_session(session_id=session_id)
672+
# if not plan:
673+
# track_event_if_configured(
674+
# "GetPlanBySessionNotFound",
675+
# {"status_code": 400, "detail": "Plan not found"},
676+
# )
677+
# raise HTTPException(status_code=404, detail="Plan not found")
678+
679+
# # Use get_steps_by_plan to match the original implementation
680+
# steps = await memory_store.get_steps_by_plan(plan_id=plan.id)
681+
# plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps)
682+
# plan_with_steps.update_step_counts()
683+
# return [plan_with_steps]
684+
# if plan_id:
685+
# plan = await memory_store.get_plan_by_plan_id(plan_id=plan_id)
686+
# if not plan:
687+
# track_event_if_configured(
688+
# "GetPlanBySessionNotFound",
689+
# {"status_code": 400, "detail": "Plan not found"},
690+
# )
691+
# raise HTTPException(status_code=404, detail="Plan not found")
692+
693+
# # Use get_steps_by_plan to match the original implementation
694+
# steps = await memory_store.get_steps_by_plan(plan_id=plan.id)
695+
# messages = await memory_store.get_data_by_type_and_session_id(
696+
# "agent_message", session_id=plan.session_id
697+
# )
698+
699+
# plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps)
700+
# plan_with_steps.update_step_counts()
701+
702+
# # Format dates in messages according to locale
703+
# formatted_messages = format_dates_in_messages(
704+
# messages, config.get_user_local_browser_language()
705+
# )
706+
707+
# return [plan_with_steps, formatted_messages]
708+
709+
# all_plans = await memory_store.get_all_plans()
710+
# # Fetch steps for all plans concurrently
711+
# steps_for_all_plans = await asyncio.gather(
712+
# *[memory_store.get_steps_by_plan(plan_id=plan.id) for plan in all_plans]
713+
# )
714+
# # Create list of PlanWithSteps and update step counts
715+
# list_of_plans_with_steps = []
716+
# for plan, steps in zip(all_plans, steps_for_all_plans):
717+
# plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps)
718+
# plan_with_steps.update_step_counts()
719+
# list_of_plans_with_steps.append(plan_with_steps)
690720

691-
# Use get_steps_by_plan to match the original implementation
692-
steps = await memory_store.get_steps_by_plan(plan_id=plan.id)
693-
messages = await memory_store.get_data_by_type_and_session_id(
694-
"agent_message", session_id=plan.session_id
695-
)
696-
697-
plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps)
698-
plan_with_steps.update_step_counts()
699-
700-
# Format dates in messages according to locale
701-
formatted_messages = format_dates_in_messages(
702-
messages, config.get_user_local_browser_language()
703-
)
704-
705-
return [plan_with_steps, formatted_messages]
706-
707-
all_plans = await memory_store.get_all_plans()
708-
# Fetch steps for all plans concurrently
709-
steps_for_all_plans = await asyncio.gather(
710-
*[memory_store.get_steps_by_plan(plan_id=plan.id) for plan in all_plans]
711-
)
712-
# Create list of PlanWithSteps and update step counts
713-
list_of_plans_with_steps = []
714-
for plan, steps in zip(all_plans, steps_for_all_plans):
715-
plan_with_steps = PlanWithSteps(**plan.model_dump(), steps=steps)
716-
plan_with_steps.update_step_counts()
717-
list_of_plans_with_steps.append(plan_with_steps)
718-
719-
return list_of_plans_with_steps
721+
return []
720722

721723

722724
@app.get("/api/steps/{plan_id}", response_model=List[Step])

src/backend/v3/config/settings.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ class OrchestrationConfig:
5858
"""Configuration for orchestration settings."""
5959

6060
def __init__(self):
61-
self._orchestrations = {}
61+
self.orchestrations = {}
6262
self.plans = {} # job_id -> current plan
6363
self.approvals = {} # job_id -> True/False/None
6464
self.sockets = {} # job_id -> WebSocket
6565

6666
def get_current_orchestration(self, user_id: str) -> MagenticOrchestration:
6767
"""get existing orchestration instance."""
68-
return self._orchestrations.get(user_id, None)
68+
return self.orchestrations.get(user_id, None)
6969

7070

7171
# Global config instances

src/backend/v3/magentic_agents/foundry_agent.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
""" Agent template for building foundry agents with Azure AI Search, Bing, and MCP plugins. """
22

3-
import asyncio
43
import logging
54
from typing import List, Optional
65

76
from azure.ai.agents.models import (AzureAISearchTool, BingGroundingTool,
87
CodeInterpreterToolDefinition)
98
from semantic_kernel.agents import AzureAIAgent # pylint: disable=E0611
10-
from semantic_kernel.agents import \
11-
AzureAIAgentSettings # pylint: disable=E0611
129
from v3.magentic_agents.common.lifecycle import AzureAgentBase
1310
from v3.magentic_agents.models.agent_models import (BingConfig, MCPConfig,
1411
SearchConfig)

src/backend/v3/magentic_agents/magentic_agent_factory.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from v3.magentic_agents.foundry_agent import FoundryAgentTemplate
1212
from v3.magentic_agents.models.agent_models import (BingConfig, MCPConfig,
1313
SearchConfig)
14+
from v3.magentic_agents.proxy_agent import ProxyAgent
1415
from v3.magentic_agents.reasoning_agent import ReasoningAgentTemplate
1516

1617

@@ -38,7 +39,7 @@ def parse_team_config(file_path: Union[str, Path]) -> SimpleNamespace:
3839
data = json.load(f)
3940
return json.loads(json.dumps(data), object_hook=lambda d: SimpleNamespace(**d))
4041

41-
async def create_agent_from_config(self, agent_obj: SimpleNamespace, team_model: str = None) -> Union[FoundryAgentTemplate, ReasoningAgentTemplate]:
42+
async def create_agent_from_config(self, agent_obj: SimpleNamespace) -> Union[FoundryAgentTemplate, ReasoningAgentTemplate, ProxyAgent]:
4243
"""
4344
Create an agent from configuration object.
4445
@@ -55,6 +56,10 @@ async def create_agent_from_config(self, agent_obj: SimpleNamespace, team_model:
5556
"""
5657
# Get model from agent config, team model, or environment
5758
deployment_name = getattr(agent_obj, 'deployment_name', None)
59+
60+
if not deployment_name and agent_obj.name.lower() == "proxyagent":
61+
self.logger.info("Creating ProxyAgent")
62+
return ProxyAgent()
5863

5964
# Validate supported models
6065
supported_models = json.loads(os.getenv("SUPPORTED_MODELS"))
@@ -114,7 +119,7 @@ async def create_agent_from_config(self, agent_obj: SimpleNamespace, team_model:
114119
self.logger.info(f"Successfully created and initialized agent '{agent_obj.name}'")
115120
return agent
116121

117-
async def get_agents(self, file_path: str, team_model: str = None) -> List:
122+
async def get_agents(self, file_path: str) -> List:
118123
"""
119124
Create and return a team of agents from JSON configuration.
120125
@@ -137,7 +142,7 @@ async def get_agents(self, file_path: str, team_model: str = None) -> List:
137142
try:
138143
self.logger.info(f"Creating agent {i}/{len(team.agents)}: {agent_cfg.name}")
139144

140-
agent = await self.create_agent_from_config(agent_cfg, team_model)
145+
agent = await self.create_agent_from_config(agent_cfg)
141146
agents.append(agent)
142147
self._agent_list.append(agent) # Keep track for cleanup
143148

src/backend/v3/magentic_agents/proxy_agent.py

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (c) Microsoft. All rights reserved.
2+
""" Proxy agent that prompts for human clarification."""
23

3-
import asyncio
4+
import logging
45
import uuid
56
from collections.abc import AsyncIterable
67
from typing import AsyncIterator
@@ -25,6 +26,7 @@ def __init__(self, chat_history: ChatHistory | None = None, thread_id: str | Non
2526
self._chat_history = chat_history if chat_history is not None else ChatHistory()
2627
self._id: str = thread_id or f"thread_{uuid.uuid4().hex}"
2728
self._is_deleted = False
29+
self.logger = logging.getLogger(__name__)
2830

2931
@override
3032
async def _create(self) -> str:
@@ -76,16 +78,19 @@ class ProxyAgentResponseItem:
7678
def __init__(self, message: ChatMessageContent, thread: AgentThread):
7779
self.message = message
7880
self.thread = thread
81+
self.logger = logging.getLogger(__name__)
7982

8083
class ProxyAgent(Agent):
8184
"""Simple proxy agent that prompts for human clarification."""
8285

8386
def __init__(self):
8487
super().__init__(
8588
name="ProxyAgent",
86-
description="I help clarify requests by asking the human user for more information. Please ask me for more details about any unclear requirements, missing information, or if you need me to elaborate on any aspect of the task."
89+
description="""Call this agent when you need to clarify requests by asking the human user
90+
for more information. Ask it for more details about any unclear requirements, missing information,
91+
or if you need the user to elaborate on any aspect of the task."""
8792
)
88-
self.instructions = "I gather clarification from the human user when requested by other agents."
93+
self.instructions = ""
8994

9095
async def invoke(self, message: str,*, thread: AgentThread | None = None,**kwargs) -> AsyncIterator[ChatMessageContent]:
9196
"""Ask human user for clarification about the message."""
@@ -96,12 +101,13 @@ async def invoke(self, message: str,*, thread: AgentThread | None = None,**kwarg
96101
construct_thread=lambda: DummyAgentThread(),
97102
expected_type=DummyAgentThread,
98103
)
104+
# Replace with websocket call when available
99105
print(f"\n🤔 ProxyAgent: Another agent is asking for clarification about:")
100106
print(f" Request: {message}")
101107
print("-" * 60)
102108

103109
# Get human input
104-
human_response = input("💬 Please provide clarification: ").strip()
110+
human_response = input("Please provide clarification: ").strip()
105111

106112
if not human_response:
107113
human_response = "No additional clarification provided."
@@ -138,13 +144,13 @@ async def invoke_stream(self, messages, thread=None, **kwargs) -> AsyncIterator[
138144
else:
139145
message = str(messages)
140146

141-
142-
print(f"\n🤔 ProxyAgent: Another agent is asking for clarification about:")
147+
# Replace with websocket call when available
148+
print(f"\nProxyAgent: Another agent is asking for clarification about:")
143149
print(f" Request: {message}")
144150
print("-" * 60)
145151

146-
# Get human input
147-
human_response = input("💬 Please provide clarification: ").strip()
152+
# Get human input - replace with websocket call when available
153+
human_response = input("Please provide clarification: ").strip()
148154

149155
if not human_response:
150156
human_response = "No additional clarification provided."
@@ -180,22 +186,4 @@ async def get_response(self, chat_history, **kwargs):
180186

181187
async def create_proxy_agent():
182188
"""Factory function for human proxy agent."""
183-
return ProxyAgent()
184-
185-
# Test harness
186-
async def test_proxy_agent():
187-
"""Test the proxy agent."""
188-
print("🤖 Testing proxy agent...")
189-
190-
agent = ProxyAgent()
191-
test_messages = [
192-
"More information needed. What is the name of the employee?"
193-
]
194-
195-
for message in test_messages:
196-
print(f"\n🤖 Simulating agent request: {message}")
197-
async for response in agent.invoke(message):
198-
print(f"📝 Response: {response.content}")
199-
200-
if __name__ == "__main__":
201-
asyncio.run(test_proxy_agent())
189+
return ProxyAgent()

src/backend/v3/orchestration/orchestration_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async def get_current_orchestration(cls, user_id: str) -> MagenticOrchestration:
6262
factory = MagenticAgentFactory()
6363
# to do: change to parsing teams from cosmos db
6464
agents = await factory.get_agents(config.AGENT_TEAM_FILE)
65-
orchestration_config._orchestrations[user_id] = await cls.init_orchestration(agents)
65+
orchestration_config.orchestrations[user_id] = await cls.init_orchestration(agents)
6666
return orchestration_config.get_current_orchestration(user_id)
6767

6868
@classmethod

0 commit comments

Comments
 (0)