-
Notifications
You must be signed in to change notification settings - Fork 711
Expand file tree
/
Copy pathapp_config.py
More file actions
281 lines (237 loc) · 10.7 KB
/
Copy pathapp_config.py
File metadata and controls
281 lines (237 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# app_config.py
import os
import logging
from typing import Optional, List
from dotenv import load_dotenv
from azure.identity import DefaultAzureCredential
from azure.cosmos.aio import CosmosClient
from azure.ai.projects.aio import AIProjectClient
from semantic_kernel.kernel import Kernel
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
from semantic_kernel.functions import KernelFunction
# Load environment variables from .env file
load_dotenv()
class AppConfig:
"""Application configuration class that loads settings from environment variables."""
def __init__(self):
"""Initialize the application configuration with environment variables."""
# Azure authentication settings
self.AZURE_TENANT_ID = self._get_optional("AZURE_TENANT_ID")
self.AZURE_CLIENT_ID = self._get_optional("AZURE_CLIENT_ID")
self.AZURE_CLIENT_SECRET = self._get_optional("AZURE_CLIENT_SECRET")
# CosmosDB settings
self.COSMOSDB_ENDPOINT = self._get_optional("COSMOSDB_ENDPOINT")
self.COSMOSDB_DATABASE = self._get_optional("COSMOSDB_DATABASE")
self.COSMOSDB_CONTAINER = self._get_optional("COSMOSDB_CONTAINER")
# Azure OpenAI settings
self.AZURE_OPENAI_DEPLOYMENT_NAME = self._get_required(
"AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4o"
)
self.AZURE_OPENAI_API_VERSION = self._get_required(
"AZURE_OPENAI_API_VERSION", "2024-11-20"
)
self.AZURE_OPENAI_ENDPOINT = self._get_required("AZURE_OPENAI_ENDPOINT")
self.AZURE_OPENAI_SCOPES = [
f"{self._get_optional('AZURE_OPENAI_SCOPE', 'https://cognitiveservices.azure.com/.default')}"
]
# Frontend settings
self.FRONTEND_SITE_NAME = self._get_optional(
"FRONTEND_SITE_NAME", "http://127.0.0.1:3000"
)
# Azure AI settings
self.AZURE_AI_SUBSCRIPTION_ID = self._get_required("AZURE_AI_SUBSCRIPTION_ID")
self.AZURE_AI_RESOURCE_GROUP = self._get_required("AZURE_AI_RESOURCE_GROUP")
self.AZURE_AI_PROJECT_NAME = self._get_required("AZURE_AI_PROJECT_NAME")
self.AZURE_AI_AGENT_PROJECT_CONNECTION_STRING = self._get_required(
"AZURE_AI_AGENT_PROJECT_CONNECTION_STRING"
)
# Cached clients and resources
self._azure_credentials = None
self._cosmos_client = None
self._cosmos_database = None
self._ai_project_client = None
def _get_required(self, name: str, default: Optional[str] = None) -> str:
"""Get a required configuration value from environment variables.
Args:
name: The name of the environment variable
default: Optional default value if not found
Returns:
The value of the environment variable or default if provided
Raises:
ValueError: If the environment variable is not found and no default is provided
"""
if name in os.environ:
return os.environ[name]
if default is not None:
logging.warning(
"Environment variable %s not found, using default value", name
)
return default
raise ValueError(
f"Environment variable {name} not found and no default provided"
)
def _get_optional(self, name: str, default: str = "") -> str:
"""Get an optional configuration value from environment variables.
Args:
name: The name of the environment variable
default: Default value if not found (default: "")
Returns:
The value of the environment variable or the default value
"""
if name in os.environ:
return os.environ[name]
return default
def _get_bool(self, name: str) -> bool:
"""Get a boolean configuration value from environment variables.
Args:
name: The name of the environment variable
Returns:
True if the environment variable exists and is set to 'true' or '1', False otherwise
"""
return name in os.environ and os.environ[name].lower() in ["true", "1"]
def get_azure_credentials(self):
"""Get Azure credentials using DefaultAzureCredential.
Returns:
DefaultAzureCredential instance for Azure authentication
"""
# Cache the credentials object
if self._azure_credentials is not None:
return self._azure_credentials
try:
self._azure_credentials = DefaultAzureCredential()
return self._azure_credentials
except Exception as exc:
logging.warning("Failed to create DefaultAzureCredential: %s", exc)
return None
def get_cosmos_database_client(self):
"""Get a Cosmos DB client for the configured database.
Returns:
A Cosmos DB database client
"""
try:
if self._cosmos_client is None:
self._cosmos_client = CosmosClient(
self.COSMOSDB_ENDPOINT, credential=self.get_azure_credentials()
)
if self._cosmos_database is None:
self._cosmos_database = self._cosmos_client.get_database_client(
self.COSMOSDB_DATABASE
)
return self._cosmos_database
except Exception as exc:
logging.error(
"Failed to create CosmosDB client: %s. CosmosDB is required for this application.",
exc,
)
raise
def create_kernel(self):
"""Creates a new Semantic Kernel instance.
Returns:
A new Semantic Kernel instance
"""
# Create a new kernel instance without manually configuring OpenAI services
# The agents will be created using Azure AI Agent Project pattern instead
kernel = Kernel()
return kernel
def get_ai_project_client(self):
"""Create and return an AIProjectClient for Azure AI Foundry using from_connection_string.
Returns:
An AIProjectClient instance
"""
if self._ai_project_client is not None:
return self._ai_project_client
try:
credential = self.get_azure_credentials()
if credential is None:
raise RuntimeError(
"Unable to acquire Azure credentials; ensure DefaultAzureCredential is configured"
)
connection_string = self.AZURE_AI_AGENT_PROJECT_CONNECTION_STRING
self._ai_project_client = AIProjectClient.from_connection_string(
credential=credential, conn_str=connection_string
)
return self._ai_project_client
except Exception as exc:
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()