Skip to content

Commit f9a9576

Browse files
committed
update agents behavior for group chat
1 parent 7be42a4 commit f9a9576

File tree

6 files changed

+725
-365
lines changed

6 files changed

+725
-365
lines changed

src/backend/app_config.py

Lines changed: 118 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -9,110 +9,126 @@
99
from semantic_kernel.kernel import Kernel
1010
from semantic_kernel.contents import ChatHistory
1111
from semantic_kernel.agents.azure_ai.azure_ai_agent import AzureAIAgent
12+
1213
# Load environment variables from .env file
1314
load_dotenv()
1415

16+
1517
class 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

Comments
 (0)