99from semantic_kernel .kernel import Kernel
1010from semantic_kernel .contents import ChatHistory
1111from semantic_kernel .agents .azure_ai .azure_ai_agent import AzureAIAgent
12+
1213# Load environment variables from .env file
1314load_dotenv ()
1415
16+
1517class AppConfig :
1618 """Application configuration class that loads settings from environment variables."""
17-
19+
1820 def __init__ (self ):
1921 """Initialize the application configuration with environment variables."""
2022 # Azure authentication settings
2123 self .AZURE_TENANT_ID = self ._get_optional ("AZURE_TENANT_ID" )
2224 self .AZURE_CLIENT_ID = self ._get_optional ("AZURE_CLIENT_ID" )
2325 self .AZURE_CLIENT_SECRET = self ._get_optional ("AZURE_CLIENT_SECRET" )
24-
26+
2527 # CosmosDB settings
2628 self .COSMOSDB_ENDPOINT = self ._get_optional ("COSMOSDB_ENDPOINT" )
2729 self .COSMOSDB_DATABASE = self ._get_optional ("COSMOSDB_DATABASE" )
2830 self .COSMOSDB_CONTAINER = self ._get_optional ("COSMOSDB_CONTAINER" )
29-
31+
3032 # Azure OpenAI settings
31- self .AZURE_OPENAI_DEPLOYMENT_NAME = self ._get_required ("AZURE_OPENAI_DEPLOYMENT_NAME" , "gpt-4o" )
32- self .AZURE_OPENAI_API_VERSION = self ._get_required ("AZURE_OPENAI_API_VERSION" , "2024-11-20" )
33+ self .AZURE_OPENAI_DEPLOYMENT_NAME = self ._get_required (
34+ "AZURE_OPENAI_DEPLOYMENT_NAME" , "gpt-4o"
35+ )
36+ self .AZURE_OPENAI_API_VERSION = self ._get_required (
37+ "AZURE_OPENAI_API_VERSION" , "2024-11-20"
38+ )
3339 self .AZURE_OPENAI_ENDPOINT = self ._get_required ("AZURE_OPENAI_ENDPOINT" )
34- self .AZURE_OPENAI_SCOPES = [f"{ self ._get_optional ('AZURE_OPENAI_SCOPE' , 'https://cognitiveservices.azure.com/.default' )} " ]
35-
40+ self .AZURE_OPENAI_SCOPES = [
41+ f"{ self ._get_optional ('AZURE_OPENAI_SCOPE' , 'https://cognitiveservices.azure.com/.default' )} "
42+ ]
43+
3644 # Frontend settings
37- self .FRONTEND_SITE_NAME = self ._get_optional ("FRONTEND_SITE_NAME" , "http://127.0.0.1:3000" )
38-
45+ self .FRONTEND_SITE_NAME = self ._get_optional (
46+ "FRONTEND_SITE_NAME" , "http://127.0.0.1:3000"
47+ )
48+
3949 # Azure AI settings
4050 self .AZURE_AI_SUBSCRIPTION_ID = self ._get_required ("AZURE_AI_SUBSCRIPTION_ID" )
4151 self .AZURE_AI_RESOURCE_GROUP = self ._get_required ("AZURE_AI_RESOURCE_GROUP" )
4252 self .AZURE_AI_PROJECT_NAME = self ._get_required ("AZURE_AI_PROJECT_NAME" )
43- self .AZURE_AI_AGENT_PROJECT_CONNECTION_STRING = self ._get_required ("AZURE_AI_AGENT_PROJECT_CONNECTION_STRING" )
44-
53+ self .AZURE_AI_AGENT_PROJECT_CONNECTION_STRING = self ._get_required (
54+ "AZURE_AI_AGENT_PROJECT_CONNECTION_STRING"
55+ )
56+
4557 # Cached clients and resources
4658 self ._azure_credentials = None
4759 self ._cosmos_client = None
4860 self ._cosmos_database = None
4961 self ._ai_project_client = None
50-
62+
5163 def _get_required (self , name : str , default : Optional [str ] = None ) -> str :
5264 """Get a required configuration value from environment variables.
53-
65+
5466 Args:
5567 name: The name of the environment variable
5668 default: Optional default value if not found
57-
69+
5870 Returns:
5971 The value of the environment variable or default if provided
60-
72+
6173 Raises:
6274 ValueError: If the environment variable is not found and no default is provided
6375 """
6476 if name in os .environ :
6577 return os .environ [name ]
6678 if default is not None :
67- logging .warning ("Environment variable %s not found, using default value" , name )
79+ logging .warning (
80+ "Environment variable %s not found, using default value" , name
81+ )
6882 return default
69- raise ValueError (f"Environment variable { name } not found and no default provided" )
70-
83+ raise ValueError (
84+ f"Environment variable { name } not found and no default provided"
85+ )
86+
7187 def _get_optional (self , name : str , default : str = "" ) -> str :
7288 """Get an optional configuration value from environment variables.
73-
89+
7490 Args:
7591 name: The name of the environment variable
7692 default: Default value if not found (default: "")
77-
93+
7894 Returns:
7995 The value of the environment variable or the default value
8096 """
8197 if name in os .environ :
8298 return os .environ [name ]
8399 return default
84-
100+
85101 def _get_bool (self , name : str ) -> bool :
86102 """Get a boolean configuration value from environment variables.
87-
103+
88104 Args:
89105 name: The name of the environment variable
90-
106+
91107 Returns:
92108 True if the environment variable exists and is set to 'true' or '1', False otherwise
93109 """
94110 return name in os .environ and os .environ [name ].lower () in ["true" , "1" ]
95-
111+
96112 def get_azure_credentials (self ):
97113 """Get Azure credentials using DefaultAzureCredential.
98-
114+
99115 Returns:
100116 DefaultAzureCredential instance for Azure authentication
101117 """
102118 # Cache the credentials object
103119 if self ._azure_credentials is not None :
104120 return self ._azure_credentials
105-
121+
106122 try :
107123 self ._azure_credentials = DefaultAzureCredential ()
108124 return self ._azure_credentials
109125 except Exception as exc :
110126 logging .warning ("Failed to create DefaultAzureCredential: %s" , exc )
111127 return None
112-
128+
113129 def get_cosmos_database_client (self ):
114130 """Get a Cosmos DB client for the configured database.
115-
131+
116132 Returns:
117133 A Cosmos DB database client
118134 """
@@ -129,136 +145,161 @@ def get_cosmos_database_client(self):
129145
130146 return self ._cosmos_database
131147 except Exception as exc :
132- logging .error ("Failed to create CosmosDB client: %s. CosmosDB is required for this application." , exc )
148+ logging .error (
149+ "Failed to create CosmosDB client: %s. CosmosDB is required for this application." ,
150+ exc ,
151+ )
133152 raise
134-
153+
135154 def create_kernel (self ):
136155 """Creates a new Semantic Kernel instance.
137-
156+
138157 Returns:
139158 A new Semantic Kernel instance
140159 """
141160 # Create a new kernel instance without manually configuring OpenAI services
142161 # The agents will be created using Azure AI Agent Project pattern instead
143162 kernel = Kernel ()
144163 return kernel
145-
164+
146165 def get_ai_project_client (self ):
147166 """Create and return an AIProjectClient for Azure AI Foundry using from_connection_string.
148-
167+
149168 Returns:
150169 An AIProjectClient instance
151170 """
152171 if self ._ai_project_client is not None :
153172 return self ._ai_project_client
154-
173+
155174 try :
156175 credential = self .get_azure_credentials ()
157176 if credential is None :
158- raise RuntimeError ("Unable to acquire Azure credentials; ensure DefaultAzureCredential is configured" )
159-
177+ raise RuntimeError (
178+ "Unable to acquire Azure credentials; ensure DefaultAzureCredential is configured"
179+ )
180+
160181 connection_string = self .AZURE_AI_AGENT_PROJECT_CONNECTION_STRING
161182 self ._ai_project_client = AIProjectClient .from_connection_string (
162- credential = credential ,
163- conn_str = connection_string
183+ credential = credential , conn_str = connection_string
164184 )
165185 logging .info ("Successfully created AIProjectClient using connection string" )
166186 return self ._ai_project_client
167187 except Exception as exc :
168188 logging .error ("Failed to create AIProjectClient: %s" , exc )
169189 raise
170-
190+
171191 async def create_azure_ai_agent (
172192 self ,
173- kernel : Kernel ,
174- agent_name : str ,
175- instructions : str ,
176- agent_type : str = "assistant" ,
193+ kernel : Kernel ,
194+ agent_name : str ,
195+ instructions : str ,
196+ agent_type : str = "assistant" ,
177197 tools = None ,
178198 tool_resources = None ,
179199 response_format = None ,
180- temperature : float = 0.0
200+ temperature : float = 0.0 ,
181201 ):
182202 """
183203 Creates a new Azure AI Agent with the specified name and instructions using AIProjectClient.
184-
204+ If an agent with the given name (assistant_id) already exists, it tries to retrieve it first.
205+
185206 Args:
186207 kernel: The Semantic Kernel instance
187- agent_name: The name of the agent
208+ agent_name: The name of the agent (will be used as assistant_id)
188209 instructions: The system message / instructions for the agent
189210 agent_type: The type of agent (defaults to "assistant")
190211 tools: Optional tool definitions for the agent
191212 tool_resources: Optional tool resources required by the tools
192213 response_format: Optional response format to control structured output
193214 temperature: The temperature setting for the agent (defaults to 0.0)
194-
215+
195216 Returns:
196217 A new AzureAIAgent instance
197218 """
198219 try :
199220 # Get the AIProjectClient
200221 project_client = self .get_ai_project_client ()
201-
222+
223+ # First try to get an existing agent with this name as assistant_id
224+ try :
225+ logging .info (f"Trying to retrieve existing agent with ID: { agent_name } " )
226+ existing_definition = await project_client .agents .get_agent (agent_name )
227+ logging .info (f"Found existing agent with ID: { agent_name } " )
228+
229+ # Create the agent instance directly with project_client and existing definition
230+ agent = AzureAIAgent (
231+ client = project_client , definition = existing_definition , kernel = kernel
232+ )
233+
234+ logging .info (
235+ f"Successfully loaded existing Azure AI Agent for { agent_name } "
236+ )
237+ return agent
238+ except Exception as e :
239+ # The Azure AI Projects SDK throws an exception when the agent doesn't exist
240+ # (not returning None), so we catch it and proceed to create a new agent
241+ if "ResourceNotFound" in str (e ) or "404" in str (e ):
242+ logging .info (
243+ f"Agent with ID { agent_name } not found. Will create a new one."
244+ )
245+ else :
246+ # Log unexpected errors but still try to create a new agent
247+ logging .warning (
248+ f"Unexpected error while retrieving agent { agent_name } : { str (e )} . Attempting to create new agent."
249+ )
250+
202251 # Tool handling: We need to distinguish between our SK functions and
203252 # the tool definitions needed by project_client.agents.create_agent
204253 tool_definitions = None
205254 kernel_functions = []
206-
255+
207256 # If tools are provided and they are SK KernelFunctions, we need to handle them differently
208257 # than if they are already tool definitions expected by AIProjectClient
209258 if tools :
210259 # Check if tools are SK KernelFunctions
211- if all (hasattr (tool , 'name' ) and hasattr (tool , 'invoke' ) for tool in tools ):
260+ if all (
261+ hasattr (tool , "name" ) and hasattr (tool , "invoke" ) for tool in tools
262+ ):
212263 # Store the kernel functions to register with the agent later
213264 kernel_functions = tools
214265 # For now, we don't extract tool definitions from kernel functions
215266 # This would require additional code to convert SK functions to AI Project tool definitions
216- logging .warning ("Kernel functions provided as tools will be registered with the agent after creation" )
267+ logging .warning (
268+ "Kernel functions provided as tools will be registered with the agent after creation"
269+ )
217270 else :
218271 # Assume these are already proper tool definitions for create_agent
219272 tool_definitions = tools
220- logging .info ("\n \n \n \n \n " )
221- logging .info ("Tool definitions: %s" , tool_definitions )
222- logging .info ("\n \n \n \n \n " )
223- # Create the agent using the project client
224- if response_format is not None :
225- logging .info ("\n \n \n \n \n " )
226- logging .info ("Response format provided: %s" , response_format )
227- logging .info ("\n \n \n \n \n " )
228-
229-
273+
274+ logging .info (f"Creating new agent with ID: { agent_name } " )
275+
276+ # Create the agent using the project client with the agent_name as both name and assistantId
230277 agent_definition = await project_client .agents .create_agent (
231278 model = self .AZURE_OPENAI_DEPLOYMENT_NAME ,
232279 name = agent_name ,
233280 instructions = instructions ,
234- tools = tool_definitions , # Only pass tool_definitions, not kernel functions
281+ tools = tool_definitions ,
235282 tool_resources = tool_resources ,
236283 temperature = temperature ,
237- response_format = response_format
284+ response_format = response_format ,
238285 )
239-
286+
240287 # Create the agent instance directly with project_client and definition
241- agent_kwargs = {
242- "client" : project_client ,
243- "definition" : agent_definition ,
244- "kernel" : kernel
245- }
246-
247-
248- # For other agents, create using standard AzureAIAgent
249- agent = AzureAIAgent (** agent_kwargs )
250-
288+ agent = AzureAIAgent (
289+ client = project_client , definition = agent_definition , kernel = kernel
290+ )
291+
251292 # Register the kernel functions with the agent if any were provided
252293 if kernel_functions :
253294 for function in kernel_functions :
254- if hasattr (agent , ' add_function' ):
295+ if hasattr (agent , " add_function" ):
255296 agent .add_function (function )
256-
297+
257298 return agent
258299 except Exception as exc :
259300 logging .error ("Failed to create Azure AI Agent: %s" , exc )
260301 raise
261302
262303
263304# Create a global instance of AppConfig
264- config = AppConfig ()
305+ config = AppConfig ()
0 commit comments