Skip to content

Commit 13f53bf

Browse files
committed
fix for pulling config
1 parent 6800648 commit 13f53bf

File tree

2 files changed

+280
-0
lines changed

2 files changed

+280
-0
lines changed

src/backend/app_config.py

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# app_config.py
2+
import os
3+
import logging
4+
from typing import Optional, List, Dict, Any
5+
from dotenv import load_dotenv
6+
from azure.identity import DefaultAzureCredential, ClientSecretCredential
7+
from azure.cosmos.aio import CosmosClient
8+
from azure.ai.projects.aio import AIProjectClient
9+
from semantic_kernel.kernel import Kernel
10+
from semantic_kernel.contents import ChatHistory
11+
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
12+
from semantic_kernel.functions import KernelFunction
13+
14+
# Load environment variables from .env file
15+
load_dotenv()
16+
17+
18+
class AppConfig:
19+
"""Application configuration class that loads settings from environment variables."""
20+
21+
def __init__(self):
22+
"""Initialize the application configuration with environment variables."""
23+
# Azure authentication settings
24+
self.AZURE_TENANT_ID = self._get_optional("AZURE_TENANT_ID")
25+
self.AZURE_CLIENT_ID = self._get_optional("AZURE_CLIENT_ID")
26+
self.AZURE_CLIENT_SECRET = self._get_optional("AZURE_CLIENT_SECRET")
27+
28+
# CosmosDB settings
29+
self.COSMOSDB_ENDPOINT = self._get_optional("COSMOSDB_ENDPOINT")
30+
self.COSMOSDB_DATABASE = self._get_optional("COSMOSDB_DATABASE")
31+
self.COSMOSDB_CONTAINER = self._get_optional("COSMOSDB_CONTAINER")
32+
33+
# Azure OpenAI settings
34+
self.AZURE_OPENAI_DEPLOYMENT_NAME = self._get_required(
35+
"AZURE_OPENAI_DEPLOYMENT_NAME", "gpt-4o"
36+
)
37+
self.AZURE_OPENAI_API_VERSION = self._get_required(
38+
"AZURE_OPENAI_API_VERSION", "2024-11-20"
39+
)
40+
self.AZURE_OPENAI_ENDPOINT = self._get_required("AZURE_OPENAI_ENDPOINT")
41+
self.AZURE_OPENAI_SCOPES = [
42+
f"{self._get_optional('AZURE_OPENAI_SCOPE', 'https://cognitiveservices.azure.com/.default')}"
43+
]
44+
45+
# Frontend settings
46+
self.FRONTEND_SITE_NAME = self._get_optional(
47+
"FRONTEND_SITE_NAME", "http://127.0.0.1:3000"
48+
)
49+
50+
# Azure AI settings
51+
self.AZURE_AI_SUBSCRIPTION_ID = self._get_required("AZURE_AI_SUBSCRIPTION_ID")
52+
self.AZURE_AI_RESOURCE_GROUP = self._get_required("AZURE_AI_RESOURCE_GROUP")
53+
self.AZURE_AI_PROJECT_NAME = self._get_required("AZURE_AI_PROJECT_NAME")
54+
self.AZURE_AI_AGENT_PROJECT_CONNECTION_STRING = self._get_required(
55+
"AZURE_AI_AGENT_PROJECT_CONNECTION_STRING"
56+
)
57+
58+
# Cached clients and resources
59+
self._azure_credentials = None
60+
self._cosmos_client = None
61+
self._cosmos_database = None
62+
self._ai_project_client = None
63+
64+
def _get_required(self, name: str, default: Optional[str] = None) -> str:
65+
"""Get a required configuration value from environment variables.
66+
67+
Args:
68+
name: The name of the environment variable
69+
default: Optional default value if not found
70+
71+
Returns:
72+
The value of the environment variable or default if provided
73+
74+
Raises:
75+
ValueError: If the environment variable is not found and no default is provided
76+
"""
77+
if name in os.environ:
78+
return os.environ[name]
79+
if default is not None:
80+
logging.warning(
81+
"Environment variable %s not found, using default value", name
82+
)
83+
return default
84+
raise ValueError(
85+
f"Environment variable {name} not found and no default provided"
86+
)
87+
88+
def _get_optional(self, name: str, default: str = "") -> str:
89+
"""Get an optional configuration value from environment variables.
90+
91+
Args:
92+
name: The name of the environment variable
93+
default: Default value if not found (default: "")
94+
95+
Returns:
96+
The value of the environment variable or the default value
97+
"""
98+
if name in os.environ:
99+
return os.environ[name]
100+
return default
101+
102+
def _get_bool(self, name: str) -> bool:
103+
"""Get a boolean configuration value from environment variables.
104+
105+
Args:
106+
name: The name of the environment variable
107+
108+
Returns:
109+
True if the environment variable exists and is set to 'true' or '1', False otherwise
110+
"""
111+
return name in os.environ and os.environ[name].lower() in ["true", "1"]
112+
113+
def get_azure_credentials(self):
114+
"""Get Azure credentials using DefaultAzureCredential.
115+
116+
Returns:
117+
DefaultAzureCredential instance for Azure authentication
118+
"""
119+
# Cache the credentials object
120+
if self._azure_credentials is not None:
121+
return self._azure_credentials
122+
123+
try:
124+
self._azure_credentials = DefaultAzureCredential()
125+
return self._azure_credentials
126+
except Exception as exc:
127+
logging.warning("Failed to create DefaultAzureCredential: %s", exc)
128+
return None
129+
130+
def get_cosmos_database_client(self):
131+
"""Get a Cosmos DB client for the configured database.
132+
133+
Returns:
134+
A Cosmos DB database client
135+
"""
136+
try:
137+
if self._cosmos_client is None:
138+
self._cosmos_client = CosmosClient(
139+
self.COSMOSDB_ENDPOINT, credential=self.get_azure_credentials()
140+
)
141+
142+
if self._cosmos_database is None:
143+
self._cosmos_database = self._cosmos_client.get_database_client(
144+
self.COSMOSDB_DATABASE
145+
)
146+
147+
return self._cosmos_database
148+
except Exception as exc:
149+
logging.error(
150+
"Failed to create CosmosDB client: %s. CosmosDB is required for this application.",
151+
exc,
152+
)
153+
raise
154+
155+
def create_kernel(self):
156+
"""Creates a new Semantic Kernel instance.
157+
158+
Returns:
159+
A new Semantic Kernel instance
160+
"""
161+
# Create a new kernel instance without manually configuring OpenAI services
162+
# The agents will be created using Azure AI Agent Project pattern instead
163+
kernel = Kernel()
164+
return kernel
165+
166+
def get_ai_project_client(self):
167+
"""Create and return an AIProjectClient for Azure AI Foundry using from_connection_string.
168+
169+
Returns:
170+
An AIProjectClient instance
171+
"""
172+
if self._ai_project_client is not None:
173+
return self._ai_project_client
174+
175+
try:
176+
credential = self.get_azure_credentials()
177+
if credential is None:
178+
raise RuntimeError(
179+
"Unable to acquire Azure credentials; ensure DefaultAzureCredential is configured"
180+
)
181+
182+
connection_string = self.AZURE_AI_AGENT_PROJECT_CONNECTION_STRING
183+
self._ai_project_client = AIProjectClient.from_connection_string(
184+
credential=credential, conn_str=connection_string
185+
)
186+
logging.info("Successfully created AIProjectClient using connection string")
187+
return self._ai_project_client
188+
except Exception as exc:
189+
logging.error("Failed to create AIProjectClient: %s", exc)
190+
raise
191+
192+
async def create_azure_ai_agent(
193+
self,
194+
kernel: Kernel,
195+
agent_name: str,
196+
instructions: str,
197+
tools: Optional[List[KernelFunction]] = None,
198+
response_format=None,
199+
temperature: float = 0.0,
200+
):
201+
"""
202+
Creates a new Azure AI Agent with the specified name and instructions using AIProjectClient.
203+
If an agent with the given name (assistant_id) already exists, it tries to retrieve it first.
204+
205+
Args:
206+
kernel: The Semantic Kernel instance
207+
agent_name: The name of the agent (will be used as assistant_id)
208+
instructions: The system message / instructions for the agent
209+
agent_type: The type of agent (defaults to "assistant")
210+
tools: Optional tool definitions for the agent
211+
tool_resources: Optional tool resources required by the tools
212+
response_format: Optional response format to control structured output
213+
temperature: The temperature setting for the agent (defaults to 0.0)
214+
215+
Returns:
216+
A new AzureAIAgent instance
217+
"""
218+
try:
219+
# Get the AIProjectClient
220+
project_client = self.get_ai_project_client()
221+
222+
# First try to get an existing agent with this name as assistant_id
223+
try:
224+
logging.info(f"Trying to retrieve existing agent with ID: {agent_name}")
225+
existing_definition = await project_client.agents.get_agent(agent_name)
226+
logging.info(f"Found existing agent with ID: {agent_name}")
227+
228+
# Create the agent instance directly with project_client and existing definition
229+
agent = AzureAIAgent(
230+
client=project_client,
231+
definition=existing_definition,
232+
kernel=kernel,
233+
plugins=tools,
234+
)
235+
236+
logging.info(
237+
f"Successfully loaded existing Azure AI Agent for {agent_name}"
238+
)
239+
return agent
240+
except Exception as e:
241+
# The Azure AI Projects SDK throws an exception when the agent doesn't exist
242+
# (not returning None), so we catch it and proceed to create a new agent
243+
if "ResourceNotFound" in str(e) or "404" in str(e):
244+
logging.info(
245+
f"Agent with ID {agent_name} not found. Will create a new one."
246+
)
247+
else:
248+
# Log unexpected errors but still try to create a new agent
249+
logging.warning(
250+
f"Unexpected error while retrieving agent {agent_name}: {str(e)}. Attempting to create new agent."
251+
)
252+
253+
# Create the agent using the project client with the agent_name as both name and assistantId
254+
agent_definition = await project_client.agents.create_agent(
255+
model=self.AZURE_OPENAI_DEPLOYMENT_NAME,
256+
name=agent_name,
257+
instructions=instructions,
258+
temperature=temperature,
259+
response_format=response_format,
260+
)
261+
262+
# Create the agent instance directly with project_client and definition
263+
agent = AzureAIAgent(
264+
client=project_client,
265+
definition=agent_definition,
266+
kernel=kernel,
267+
plugins=tools,
268+
)
269+
270+
return agent
271+
except Exception as exc:
272+
logging.error("Failed to create Azure AI Agent: %s", exc)
273+
raise
274+
275+
276+
# Create a global instance of AppConfig
277+
config = AppConfig()

src/backend/utils_kernel.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
# Semantic Kernel imports
1010
import semantic_kernel as sk
11+
12+
# Import AppConfig from app_config
13+
from app_config import config
1114
from azure.identity import DefaultAzureCredential
1215
from context.cosmos_memory_kernel import CosmosMemoryContext
1316

0 commit comments

Comments
 (0)