From f3465fcd9789d049550fb7bc0736818ea6f7ff87 Mon Sep 17 00:00:00 2001 From: Markus Date: Fri, 9 May 2025 07:53:10 -0700 Subject: [PATCH] Rework agent structure - solve foundry leaks - interop with foundry --- src/backend/app_config.py | 103 +------------ src/backend/kernel_agents/agent_base.py | 127 +++++++++++----- src/backend/kernel_agents/agent_factory.py | 79 +++------- src/backend/kernel_agents/generic_agent.py | 49 ++++++- .../kernel_agents/group_chat_manager.py | 88 +++++++---- src/backend/kernel_agents/hr_agent.py | 50 ++++++- src/backend/kernel_agents/human_agent.py | 62 ++++++-- src/backend/kernel_agents/marketing_agent.py | 50 ++++++- src/backend/kernel_agents/planner_agent.py | 137 +++++++++--------- .../kernel_agents/procurement_agent.py | 50 ++++++- src/backend/kernel_agents/product_agent.py | 50 ++++++- .../kernel_agents/tech_support_agent.py | 50 ++++++- 12 files changed, 591 insertions(+), 304 deletions(-) diff --git a/src/backend/app_config.py b/src/backend/app_config.py index b35732a99..ad8b9c558 100644 --- a/src/backend/app_config.py +++ b/src/backend/app_config.py @@ -1,15 +1,16 @@ # app_config.py -import os import logging -from typing import Optional, List, Dict, Any -from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential, ClientSecretCredential -from azure.cosmos.aio import CosmosClient +import os +from typing import Any, Dict, List, Optional + from azure.ai.projects.aio import AIProjectClient -from semantic_kernel.kernel import Kernel -from semantic_kernel.contents import ChatHistory +from azure.cosmos.aio import CosmosClient +from azure.identity import ClientSecretCredential, DefaultAzureCredential +from dotenv import load_dotenv from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent +from semantic_kernel.contents import ChatHistory from semantic_kernel.functions import KernelFunction +from semantic_kernel.kernel import Kernel # Load environment variables from .env file load_dotenv() @@ -189,94 +190,6 @@ def get_ai_project_client(self): logging.error("Failed to create AIProjectClient: %s", exc) raise - async def create_azure_ai_agent( - self, - agent_name: str, - instructions: str, - tools: Optional[List[KernelFunction]] = None, - client=None, - response_format=None, - temperature: float = 0.0, - ): - """ - Creates a new Azure AI Agent with the specified name and instructions using AIProjectClient. - If an agent with the given name (assistant_id) already exists, it tries to retrieve it first. - - Args: - kernel: The Semantic Kernel instance - agent_name: The name of the agent (will be used as assistant_id) - instructions: The system message / instructions for the agent - agent_type: The type of agent (defaults to "assistant") - tools: Optional tool definitions for the agent - tool_resources: Optional tool resources required by the tools - response_format: Optional response format to control structured output - temperature: The temperature setting for the agent (defaults to 0.0) - - Returns: - A new AzureAIAgent instance - """ - try: - # Get the AIProjectClient - if client is None: - client = self.get_ai_project_client() - - # # ToDo: This is the fixed code but commenting it out as agent clean up is no happening yet - # # and there are multiple versions of agents due to testing - # # First try to get an existing agent with this name as assistant_id - # try: - # agent_id = None - # agent_list = await client.agents.list_agents() - # for agent in agent_list.data: - # if agent.name == agent_name: - # agent_id = agent.id - # break - # # If the agent already exists, we can use it directly - # # Get the existing agent definition - # existing_definition = await client.agents.get_agent(agent_id) - # # Create the agent instance directly with project_client and existing definition - # agent = AzureAIAgent( - # client=client, - # definition=existing_definition, - # plugins=tools, - # ) - - # client.agents.list_agents() - - # return agent - # except Exception as e: - # # The Azure AI Projects SDK throws an exception when the agent doesn't exist - # # (not returning None), so we catch it and proceed to create a new agent - # if "ResourceNotFound" in str(e) or "404" in str(e): - # logging.info( - # f"Agent with ID {agent_name} not found. Will create a new one." - # ) - # else: - # # Log unexpected errors but still try to create a new agent - # logging.warning( - # f"Unexpected error while retrieving agent {agent_name}: {str(e)}. Attempting to create new agent." - # ) - - # Create the agent using the project client with the agent_name as both name and assistantId - agent_definition = await client.agents.create_agent( - model=self.AZURE_OPENAI_DEPLOYMENT_NAME, - name=agent_name, - instructions=instructions, - temperature=temperature, - response_format=response_format, - ) - - # Create the agent instance directly with project_client and definition - agent = AzureAIAgent( - client=client, - definition=agent_definition, - plugins=tools, - ) - - return agent - except Exception as exc: - logging.error("Failed to create Azure AI Agent: %s", exc) - raise - # Create a global instance of AppConfig config = AppConfig() diff --git a/src/backend/kernel_agents/agent_base.py b/src/backend/kernel_agents/agent_base.py index 69154c9ea..9bfebf46e 100644 --- a/src/backend/kernel_agents/agent_base.py +++ b/src/backend/kernel_agents/agent_base.py @@ -1,27 +1,21 @@ import json import logging import os -from typing import Any, Awaitable, Callable, Dict, List, Mapping, Optional, Union +from abc import ABC, abstractmethod +from typing import (Any, Awaitable, Callable, Dict, List, Mapping, Optional, + Union) import semantic_kernel as sk -from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent -from semantic_kernel.functions import KernelFunction -from semantic_kernel.functions.kernel_arguments import KernelArguments -from semantic_kernel.functions.kernel_function_decorator import kernel_function -from semantic_kernel.agents import AzureAIAgentThread - - # Import the new AppConfig instance from app_config import config from context.cosmos_memory_kernel import CosmosMemoryContext from event_utils import track_event_if_configured -from models.messages_kernel import ( - ActionRequest, - ActionResponse, - AgentMessage, - Step, - StepStatus, -) +from models.messages_kernel import (ActionRequest, ActionResponse, + AgentMessage, Step, StepStatus) +from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent +from semantic_kernel.functions import KernelFunction +from semantic_kernel.functions.kernel_arguments import KernelArguments +from semantic_kernel.functions.kernel_function_decorator import kernel_function # Default formatting instructions used across agents DEFAULT_FORMATTING_INSTRUCTIONS = "Instructions: returning the output of this function call verbatim to the user in markdown. Then write AGENT SUMMARY: and then include a summary of what you did." @@ -65,6 +59,7 @@ def __init__( endpoint=None, # Set as needed api_version=None, # Set as needed token=None, # Set as needed + model=config.AZURE_OPENAI_DEPLOYMENT_NAME, agent_name=agent_name, system_prompt=system_message, client=client, @@ -79,7 +74,7 @@ def __init__( self._tools = tools self._system_message = system_message self._chat_history = [{"role": "system", "content": self._system_message}] - self._agent = None # Will be initialized in async_init + # self._agent = None # Will be initialized in async_init # Required properties for AgentGroupChat compatibility self.name = agent_name # This is crucial for AgentGroupChat to identify agents @@ -97,24 +92,6 @@ def default_system_message(agent_name=None) -> str: name = agent_name return f"You are an AI assistant named {name}. Help the user by providing accurate and helpful information." - async def async_init(self): - """Asynchronously initialize the agent after construction. - - This method must be called after creating the agent to complete initialization. - """ - logging.info(f"Initializing agent: {self._agent_name}") - # Create Azure AI Agent or fallback - if not self._agent: - self._agent = await config.create_azure_ai_agent( - agent_name=self._agent_name, - instructions=self._system_message, - tools=self._tools, - ) - else: - logging.info(f"Agent {self._agent_name} already initialized.") - # Tools are registered with the kernel via get_tools_from_config - return self - async def handle_action_request(self, action_request: ActionRequest) -> str: """Handle an action request from another agent or the system. @@ -160,7 +137,7 @@ async def handle_action_request(self, action_request: ActionRequest) -> str: # thread = self.client.agents.get_thread( # thread=step.session_id # ) # AzureAIAgentThread(thread_id=step.session_id) - async_generator = self._agent.invoke( + async_generator = self.invoke( messages=f"{str(self._chat_history)}\n\nPlease perform this action", thread=thread, ) @@ -264,3 +241,83 @@ def save_state(self) -> Mapping[str, Any]: def load_state(self, state: Mapping[str, Any]) -> None: """Load the state of this agent.""" self._memory_store.load_state(state["memory"]) + + @classmethod + @abstractmethod + async def create(cls, **kwargs) -> "BaseAgent": + """Create an instance of the agent.""" + pass + + @staticmethod + async def _create_azure_ai_agent_definition( + agent_name: str, + instructions: str, + tools: Optional[List[KernelFunction]] = None, + client=None, + response_format=None, + temperature: float = 0.0, + ): + """ + Creates a new Azure AI Agent with the specified name and instructions using AIProjectClient. + If an agent with the given name (assistant_id) already exists, it tries to retrieve it first. + + Args: + kernel: The Semantic Kernel instance + agent_name: The name of the agent (will be used as assistant_id) + instructions: The system message / instructions for the agent + agent_type: The type of agent (defaults to "assistant") + tools: Optional tool definitions for the agent + tool_resources: Optional tool resources required by the tools + response_format: Optional response format to control structured output + temperature: The temperature setting for the agent (defaults to 0.0) + + Returns: + A new AzureAIAgent definition or an existing one if found + """ + try: + # Get the AIProjectClient + if client is None: + client = config.get_ai_project_client() + + # # First try to get an existing agent with this name as assistant_id + try: + agent_id = None + agent_list = await client.agents.list_agents() + for agent in agent_list.data: + if agent.name == agent_name: + agent_id = agent.id + break + # If the agent already exists, we can use it directly + # Get the existing agent definition + if agent_id is not None: + logging.info(f"Agent with ID {agent_id} exists.") + + existing_definition = await client.agents.get_agent(agent_id) + + return existing_definition + except Exception as e: + # The Azure AI Projects SDK throws an exception when the agent doesn't exist + # (not returning None), so we catch it and proceed to create a new agent + if "ResourceNotFound" in str(e) or "404" in str(e): + logging.info( + f"Agent with ID {agent_name} not found. Will create a new one." + ) + else: + # Log unexpected errors but still try to create a new agent + logging.warning( + f"Unexpected error while retrieving agent {agent_name}: {str(e)}. Attempting to create new agent." + ) + + # Create the agent using the project client with the agent_name as both name and assistantId + agent_definition = await client.agents.create_agent( + model=config.AZURE_OPENAI_DEPLOYMENT_NAME, + name=agent_name, + instructions=instructions, + temperature=temperature, + response_format=response_format, + ) + + return agent_definition + except Exception as exc: + logging.error("Failed to create Azure AI Agent: %s", exc) + raise diff --git a/src/backend/kernel_agents/agent_factory.py b/src/backend/kernel_agents/agent_factory.py index 6fa17ff63..38a3ab473 100644 --- a/src/backend/kernel_agents/agent_factory.py +++ b/src/backend/kernel_agents/agent_factory.py @@ -1,36 +1,33 @@ """Factory for creating agents in the Multi-Agent Custom Automation Engine.""" +import inspect import logging -from typing import Dict, List, Callable, Any, Optional, Type from types import SimpleNamespace -from semantic_kernel import Kernel -from semantic_kernel.functions import KernelFunction -from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent -import inspect - -from kernel_agents.agent_base import BaseAgent +from typing import Any, Callable, Dict, List, Optional, Type # Import the new AppConfig instance from app_config import config - +from azure.ai.projects.models import (ResponseFormatJsonSchema, + ResponseFormatJsonSchemaType) +from context.cosmos_memory_kernel import CosmosMemoryContext +from kernel_agents.agent_base import BaseAgent +from kernel_agents.generic_agent import GenericAgent +from kernel_agents.group_chat_manager import GroupChatManager # Import all specialized agent implementations from kernel_agents.hr_agent import HrAgent from kernel_agents.human_agent import HumanAgent from kernel_agents.marketing_agent import MarketingAgent -from kernel_agents.generic_agent import GenericAgent -from kernel_agents.tech_support_agent import TechSupportAgent +from kernel_agents.planner_agent import PlannerAgent # Add PlannerAgent import from kernel_agents.procurement_agent import ProcurementAgent from kernel_agents.product_agent import ProductAgent -from kernel_agents.planner_agent import PlannerAgent # Add PlannerAgent import -from kernel_agents.group_chat_manager import GroupChatManager -from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig -from context.cosmos_memory_kernel import CosmosMemoryContext -from models.messages_kernel import PlannerResponsePlan, AgentType - -from azure.ai.projects.models import ( - ResponseFormatJsonSchema, - ResponseFormatJsonSchemaType, -) +from kernel_agents.tech_support_agent import TechSupportAgent +from models.messages_kernel import AgentType, PlannerResponsePlan +from semantic_kernel import Kernel +from semantic_kernel.agents import AzureAIAgentThread # pylint:disable=E0611 +from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent +from semantic_kernel.functions import KernelFunction +from semantic_kernel.prompt_template.prompt_template_config import \ + PromptTemplateConfig logger = logging.getLogger(__name__) @@ -156,40 +153,6 @@ async def create_agent( ) tools = None - # Build the agent definition (functions schema) - definition = None - - try: - if client is None: - # Create the AIProjectClient instance using the config - # This is a placeholder; replace with actual client creation logic - client = config.get_ai_project_client() - except Exception as client_exc: - logger.error(f"Error creating AIProjectClient: {client_exc}") - raise - - try: - # Create the agent definition using the AIProjectClient (project-based pattern) - # For GroupChatManager, create a definition with minimal configuration - if client is not None: - - definition = await client.agents.create_agent( - model=config.AZURE_OPENAI_DEPLOYMENT_NAME, - name=agent_type_str, - instructions=system_message, - temperature=temperature, - response_format=response_format, # Add response_format if required - ) - logger.info( - f"Successfully created agent definition for {agent_type_str}" - ) - except Exception as agent_exc: - logger.error( - f"Error creating agent definition with AIProjectClient for {agent_type_str}: {agent_exc}" - ) - - raise - # Create the agent instance using the project-based pattern try: # Filter kwargs to only those accepted by the agent's __init__ @@ -205,18 +168,12 @@ async def create_agent( "tools": tools, "system_message": system_message, "client": client, - "definition": definition, **kwargs, }.items() if k in valid_keys } - agent = agent_class(**filtered_kwargs) + agent = await agent_class.create(**filtered_kwargs) - # Initialize the agent asynchronously if it has async_init - if hasattr(agent, "async_init") and inspect.iscoroutinefunction( - agent.async_init - ): - init_result = await agent.async_init() except Exception as e: logger.error( diff --git a/src/backend/kernel_agents/generic_agent.py b/src/backend/kernel_agents/generic_agent.py index 17c7c8eeb..4fd55fd08 100644 --- a/src/backend/kernel_agents/generic_agent.py +++ b/src/backend/kernel_agents/generic_agent.py @@ -1,5 +1,5 @@ import logging -from typing import List, Optional +from typing import Dict, List, Optional import semantic_kernel as sk from context.cosmos_memory_kernel import CosmosMemoryContext @@ -62,6 +62,53 @@ def __init__( definition=definition, ) + @classmethod + async def create( + cls, + **kwargs: Dict[str, str], + ) -> None: + """Asynchronously create the PlannerAgent. + + Creates the Azure AI Agent for planning operations. + + Returns: + None + """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + client = kwargs.get("client") + + try: + logging.info("Initializing GenericAgent from async init azure AI Agent") + + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=system_message, # Pass the formatted string, not an object + temperature=0.0, + response_format=None, + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + client=client, + definition=agent_definition, + ) + + except Exception as e: + logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") + raise + @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent. diff --git a/src/backend/kernel_agents/group_chat_manager.py b/src/backend/kernel_agents/group_chat_manager.py index c24807fda..6f86ccc10 100644 --- a/src/backend/kernel_agents/group_chat_manager.py +++ b/src/backend/kernel_agents/group_chat_manager.py @@ -1,34 +1,21 @@ -import logging import json -from datetime import datetime +import logging import re -from typing import Dict, List, Optional, Any, Tuple +from datetime import datetime +from typing import Any, Dict, List, Optional, Tuple import semantic_kernel as sk -from semantic_kernel.functions.kernel_function import KernelFunction -from semantic_kernel.agents import AgentGroupChat # pylint: disable=E0611 - -from semantic_kernel.agents.strategies import ( - SequentialSelectionStrategy, - TerminationStrategy, -) - -from kernel_agents.agent_base import BaseAgent from context.cosmos_memory_kernel import CosmosMemoryContext -from models.messages_kernel import ( - ActionRequest, - ActionResponse, - AgentMessage, - HumanFeedback, - Step, - StepStatus, - PlanStatus, - HumanFeedbackStatus, - InputTask, - Plan, -) -from models.messages_kernel import AgentType from event_utils import track_event_if_configured +from kernel_agents.agent_base import BaseAgent +from models.messages_kernel import (ActionRequest, ActionResponse, + AgentMessage, AgentType, HumanFeedback, + HumanFeedbackStatus, InputTask, Plan, + PlanStatus, Step, StepStatus) +from semantic_kernel.agents import AgentGroupChat # pylint: disable=E0611 +from semantic_kernel.agents.strategies import (SequentialSelectionStrategy, + TerminationStrategy) +from semantic_kernel.functions.kernel_function import KernelFunction class GroupChatManager(BaseAgent): @@ -99,6 +86,57 @@ def __init__( # This will be initialized in async_init self._azure_ai_agent = None + @classmethod + async def create( + cls, + **kwargs: Dict[str, str], + ) -> None: + """Asynchronously create the PlannerAgent. + + Creates the Azure AI Agent for planning operations. + + Returns: + None + """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + agent_tools_list = kwargs.get("agent_tools_list", None) + agent_instances = kwargs.get("agent_instances", None) + client = kwargs.get("client") + + try: + logging.info("Initializing GroupChatAgent from async init azure AI Agent") + + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=system_message, # Pass the formatted string, not an object + temperature=0.0, + response_format=None, + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + agent_tools_list=agent_tools_list, + agent_instances=agent_instances, + client=client, + definition=agent_definition, + ) + + except Exception as e: + logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") + raise + @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent. diff --git a/src/backend/kernel_agents/hr_agent.py b/src/backend/kernel_agents/hr_agent.py index 2957ca2c7..a5427dae4 100644 --- a/src/backend/kernel_agents/hr_agent.py +++ b/src/backend/kernel_agents/hr_agent.py @@ -1,4 +1,5 @@ -from typing import List, Optional +import logging +from typing import Dict, List, Optional import semantic_kernel as sk from context.cosmos_memory_kernel import CosmosMemoryContext @@ -63,6 +64,53 @@ def __init__( definition=definition, ) + @classmethod + async def create( + cls, + **kwargs: Dict[str, str], + ) -> None: + """Asynchronously create the PlannerAgent. + + Creates the Azure AI Agent for planning operations. + + Returns: + None + """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + client = kwargs.get("client") + + try: + logging.info("Initializing HRAgent from async init azure AI Agent") + + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=system_message, # Pass the formatted string, not an object + temperature=0.0, + response_format=None, + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + client=client, + definition=agent_definition, + ) + + except Exception as e: + logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") + raise + @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent. diff --git a/src/backend/kernel_agents/human_agent.py b/src/backend/kernel_agents/human_agent.py index 9aa22a946..7eb1ef0f5 100644 --- a/src/backend/kernel_agents/human_agent.py +++ b/src/backend/kernel_agents/human_agent.py @@ -1,20 +1,13 @@ import logging -from typing import List, Optional +from typing import Dict, List, Optional import semantic_kernel as sk from context.cosmos_memory_kernel import CosmosMemoryContext from event_utils import track_event_if_configured from kernel_agents.agent_base import BaseAgent -from models.messages_kernel import ( - ActionRequest, - AgentMessage, - AgentType, - ApprovalRequest, - HumanClarification, - HumanFeedback, - Step, - StepStatus, -) +from models.messages_kernel import (ActionRequest, AgentMessage, AgentType, + ApprovalRequest, HumanClarification, + HumanFeedback, Step, StepStatus) from semantic_kernel.functions import KernelFunction from semantic_kernel.functions.kernel_arguments import KernelArguments @@ -68,6 +61,53 @@ def __init__( definition=definition, ) + @classmethod + async def create( + cls, + **kwargs: Dict[str, str], + ) -> None: + """Asynchronously create the PlannerAgent. + + Creates the Azure AI Agent for planning operations. + + Returns: + None + """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + client = kwargs.get("client") + + try: + logging.info("Initializing HumanAgent from async init azure AI Agent") + + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=system_message, # Pass the formatted string, not an object + temperature=0.0, + response_format=None, + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + client=client, + definition=agent_definition, + ) + + except Exception as e: + logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") + raise + @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent. diff --git a/src/backend/kernel_agents/marketing_agent.py b/src/backend/kernel_agents/marketing_agent.py index 8c1ed80cd..951fa4fe5 100644 --- a/src/backend/kernel_agents/marketing_agent.py +++ b/src/backend/kernel_agents/marketing_agent.py @@ -1,4 +1,5 @@ -from typing import List, Optional +import logging +from typing import Dict, List, Optional import semantic_kernel as sk from context.cosmos_memory_kernel import CosmosMemoryContext @@ -62,6 +63,53 @@ def __init__( definition=definition, ) + @classmethod + async def create( + cls, + **kwargs: Dict[str, str], + ) -> None: + """Asynchronously create the PlannerAgent. + + Creates the Azure AI Agent for planning operations. + + Returns: + None + """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + client = kwargs.get("client") + + try: + logging.info("Initializing MarketingAgent from async init azure AI Agent") + + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=system_message, # Pass the formatted string, not an object + temperature=0.0, + response_format=None, + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + client=client, + definition=agent_definition, + ) + + except Exception as e: + logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") + raise + @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent. diff --git a/src/backend/kernel_agents/planner_agent.py b/src/backend/kernel_agents/planner_agent.py index 677821547..c9d9d651b 100644 --- a/src/backend/kernel_agents/planner_agent.py +++ b/src/backend/kernel_agents/planner_agent.py @@ -1,43 +1,30 @@ -import logging -import uuid +import datetime import json +import logging import re -import datetime -from typing import Dict, List, Optional, Any, Tuple -from pydantic import BaseModel, Field -from azure.ai.projects.models import ( - ResponseFormatJsonSchema, - ResponseFormatJsonSchemaType, -) +import uuid +from typing import Any, Dict, List, Optional, Tuple + import semantic_kernel as sk -from semantic_kernel.functions import KernelFunction -from semantic_kernel.functions.kernel_arguments import KernelArguments -from semantic_kernel.agents import ( - AzureAIAgent, - AzureAIAgentSettings, - AzureAIAgentThread, -) -from kernel_agents.agent_base import BaseAgent +from app_config import config +from azure.ai.projects.models import (ResponseFormatJsonSchema, + ResponseFormatJsonSchemaType) from context.cosmos_memory_kernel import CosmosMemoryContext -from models.messages_kernel import ( - AgentMessage, - AgentType, - InputTask, - Plan, - PlannerResponsePlan, - Step, - StepStatus, - PlanStatus, - HumanFeedbackStatus, -) from event_utils import track_event_if_configured -from app_config import config -from kernel_tools.hr_tools import HrTools +from kernel_agents.agent_base import BaseAgent from kernel_tools.generic_tools import GenericTools +from kernel_tools.hr_tools import HrTools from kernel_tools.marketing_tools import MarketingTools from kernel_tools.procurement_tools import ProcurementTools from kernel_tools.product_tools import ProductTools from kernel_tools.tech_support_tools import TechSupportTools +from models.messages_kernel import (AgentMessage, AgentType, + HumanFeedbackStatus, InputTask, Plan, + PlannerResponsePlan, PlanStatus, Step, + StepStatus) +from pydantic import BaseModel, Field +from semantic_kernel.functions import KernelFunction +from semantic_kernel.functions.kernel_arguments import KernelArguments class PlannerAgent(BaseAgent): @@ -123,35 +110,61 @@ def default_system_message(agent_name=None) -> str: """ return "You are a Planner agent responsible for creating and managing plans. You analyze tasks, break them down into steps, and assign them to the appropriate specialized agents." - async def async_init(self) -> None: - """Asynchronously initialize the PlannerAgent. + @classmethod + async def create( + cls, + **kwargs: Dict[str, Any], + ) -> None: + """Asynchronously create the PlannerAgent. Creates the Azure AI Agent for planning operations. Returns: None """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + available_agents = kwargs.get("available_agents", None) + agent_instances = kwargs.get("agent_instances", None) + client = kwargs.get("client") + + # Create the instruction template + try: logging.info("Initializing PlannerAgent from async init azure AI Agent") - # Get the agent template - defined in function to allow for easy updates - instructions = self._get_template() - if not self._agent: - # Create the Azure AI Agent using AppConfig with string instructions - self._agent = await config.create_azure_ai_agent( - agent_name=self._agent_name, - instructions=instructions, # Pass the formatted string, not an object - temperature=0.0, - response_format=ResponseFormatJsonSchemaType( - json_schema=ResponseFormatJsonSchema( - name=PlannerResponsePlan.__name__, - description=f"respond with {PlannerResponsePlan.__name__.lower()}", - schema=PlannerResponsePlan.model_json_schema(), - ) - ), - ) - logging.info("Successfully created Azure AI Agent for PlannerAgent") - return True + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=cls._get_template(), # Pass the formatted string, not an object + temperature=0.0, + response_format=ResponseFormatJsonSchemaType( + json_schema=ResponseFormatJsonSchema( + name=PlannerResponsePlan.__name__, + description=f"respond with {PlannerResponsePlan.__name__.lower()}", + schema=PlannerResponsePlan.model_json_schema(), + ) + ), + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + available_agents=available_agents, + agent_instances=agent_instances, + client=client, + definition=agent_definition, + ) + except Exception as e: logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") raise @@ -309,31 +322,12 @@ async def _create_structured_plan( # Get template variables as a dictionary args = self._generate_args(input_task.description) - # Use the Azure AI Agent instead of direct function invocation - if self._agent is None: - # Initialize the agent if it's not already done - await self.async_init() - - if self._agent is None: - raise RuntimeError("Failed to initialize Azure AI Agent for planning") - - # Log detailed information about the instruction being sent - # logging.info(f"Invoking PlannerAgent with instruction length: {len(instruction)}") - # Create kernel arguments - make sure we explicitly emphasize the task kernel_args = KernelArguments(**args) - # kernel_args["input"] = f"TASK: {input_task.description}\n\n{instruction}" - # Get the schema for our expected response format - - # Ensure we're using the right pattern for Azure AI agents with semantic kernel - # Properly handle async generation - # thread = AzureAIAgentThread( - # thread_id=input_task.session_id, client=self.client - # ) thread = None # thread = self.client.agents.create_thread(thread_id=input_task.session_id) - async_generator = self._agent.invoke( + async_generator = self.invoke( arguments=kernel_args, settings={ "temperature": 0.0, # Keep temperature low for consistent planning @@ -546,7 +540,8 @@ def _generate_args(self, objective: str) -> any: "tools_str": tools_str, } - def _get_template(self): + @staticmethod + def _get_template(): """Generate the instruction template for the LLM.""" # Build the instruction with proper format placeholders for .format() method diff --git a/src/backend/kernel_agents/procurement_agent.py b/src/backend/kernel_agents/procurement_agent.py index cc3261c35..f70e03c3b 100644 --- a/src/backend/kernel_agents/procurement_agent.py +++ b/src/backend/kernel_agents/procurement_agent.py @@ -1,4 +1,5 @@ -from typing import List, Optional +import logging +from typing import Dict, List, Optional import semantic_kernel as sk from context.cosmos_memory_kernel import CosmosMemoryContext @@ -62,6 +63,53 @@ def __init__( definition=definition, ) + @classmethod + async def create( + cls, + **kwargs: Dict[str, str], + ) -> None: + """Asynchronously create the PlannerAgent. + + Creates the Azure AI Agent for planning operations. + + Returns: + None + """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + client = kwargs.get("client") + + try: + logging.info("Initializing ProcurementAgent from async init azure AI Agent") + + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=system_message, # Pass the formatted string, not an object + temperature=0.0, + response_format=None, + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + client=client, + definition=agent_definition, + ) + + except Exception as e: + logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") + raise + @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent. diff --git a/src/backend/kernel_agents/product_agent.py b/src/backend/kernel_agents/product_agent.py index 6251de25e..6230bb097 100644 --- a/src/backend/kernel_agents/product_agent.py +++ b/src/backend/kernel_agents/product_agent.py @@ -1,4 +1,5 @@ -from typing import List, Optional +import logging +from typing import Dict, List, Optional import semantic_kernel as sk from context.cosmos_memory_kernel import CosmosMemoryContext @@ -66,6 +67,53 @@ def __init__( definition=definition, ) + @classmethod + async def create( + cls, + **kwargs: Dict[str, str], + ) -> None: + """Asynchronously create the PlannerAgent. + + Creates the Azure AI Agent for planning operations. + + Returns: + None + """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + client = kwargs.get("client") + + try: + logging.info("Initializing ProductAgent from async init azure AI Agent") + + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=system_message, # Pass the formatted string, not an object + temperature=0.0, + response_format=None, + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + client=client, + definition=agent_definition, + ) + + except Exception as e: + logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") + raise + @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent. diff --git a/src/backend/kernel_agents/tech_support_agent.py b/src/backend/kernel_agents/tech_support_agent.py index b2c90b872..3c67414fc 100644 --- a/src/backend/kernel_agents/tech_support_agent.py +++ b/src/backend/kernel_agents/tech_support_agent.py @@ -1,4 +1,5 @@ -from typing import List, Optional +import logging +from typing import Dict, List, Optional import semantic_kernel as sk from context.cosmos_memory_kernel import CosmosMemoryContext @@ -63,6 +64,53 @@ def __init__( definition=definition, ) + @classmethod + async def create( + cls, + **kwargs: Dict[str, str], + ) -> None: + """Asynchronously create the PlannerAgent. + + Creates the Azure AI Agent for planning operations. + + Returns: + None + """ + + session_id = kwargs.get("session_id") + user_id = kwargs.get("user_id") + memory_store = kwargs.get("memory_store") + tools = kwargs.get("tools", None) + system_message = kwargs.get("system_message", None) + agent_name = kwargs.get("agent_name") + client = kwargs.get("client") + + try: + logging.info("Initializing TechSupportAgent from async init azure AI Agent") + + # Create the Azure AI Agent using AppConfig with string instructions + agent_definition = await cls._create_azure_ai_agent_definition( + agent_name=agent_name, + instructions=system_message, # Pass the formatted string, not an object + temperature=0.0, + response_format=None, + ) + + return cls( + session_id=session_id, + user_id=user_id, + memory_store=memory_store, + tools=tools, + system_message=system_message, + agent_name=agent_name, + client=client, + definition=agent_definition, + ) + + except Exception as e: + logging.error(f"Failed to create Azure AI Agent for PlannerAgent: {e}") + raise + @staticmethod def default_system_message(agent_name=None) -> str: """Get the default system message for the agent.