Skip to content

Commit 124fcf7

Browse files
Enhance agent orchestration and configuration for Azure AI Search integration
1 parent 95b0049 commit 124fcf7

5 files changed

Lines changed: 144 additions & 77 deletions

File tree

src/backend/v4/api/router.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,17 @@ async def process_request(
303303
)
304304
await memory_store.add_plan(plan)
305305

306+
# Ensure orchestration is initialized before running
307+
# Force rebuild for each new task since Magentic workflows cannot be reused after completion
308+
team_service = TeamService(memory_store)
309+
await OrchestrationManager.get_current_or_new_orchestration(
310+
user_id=user_id,
311+
team_config=team,
312+
team_switched=False,
313+
team_service=team_service,
314+
force_rebuild=True, # Always rebuild workflow for new tasks
315+
)
316+
306317
track_event_if_configured(
307318
"PlanCreated",
308319
{

src/backend/v4/magentic_agents/common/lifecycle.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(
5151
self._agent: ChatAgent | None = None
5252
self.team_service: TeamService | None = team_service
5353
self.team_config: TeamConfiguration | None = team_config
54-
self.client: Optional[AzureAIClient] = None
54+
self.client: Optional[AgentsClient] = None
5555
self.project_endpoint = project_endpoint
5656
self.creds: Optional[DefaultAzureCredential] = None
5757
self.memory_store: Optional[DatabaseBase] = memory_store
@@ -228,7 +228,25 @@ def get_agent_id(self, chat_client) -> str:
228228
return id
229229

230230
async def get_database_team_agent(self) -> Optional[AzureAIClient]:
231-
"""Retrieve existing team agent from database, if any."""
231+
"""Retrieve existing team agent from database, if any.
232+
233+
NOTE: Agent reuse is currently DISABLED to ensure fresh agents are created
234+
with the correct Azure AI Search configuration.
235+
This prevents issues with stale agents that may not have the search tool configured.
236+
237+
To re-enable agent reuse, set ENABLE_AGENT_REUSE=true in environment.
238+
"""
239+
import os
240+
241+
# DISABLED: Always create fresh agents to ensure Azure AI Search tool is configured
242+
enable_reuse = os.environ.get("ENABLE_AGENT_REUSE", "false").lower() == "true"
243+
if not enable_reuse:
244+
self.logger.info(
245+
"Agent reuse DISABLED: Creating fresh agent with search tools (agent_name=%s)",
246+
self.agent_name,
247+
)
248+
return None
249+
232250
chat_client = None
233251
try:
234252
agent_id = await get_database_team_agent_id(
@@ -251,15 +269,15 @@ async def get_database_team_agent(self) -> Optional[AzureAIClient]:
251269
)
252270
return None
253271

254-
# Create client with resolved ID, preferring project_client for RAI agents
272+
# Create client with resolved ID
255273
if self.agent_name == "RAIAgent" and self.project_client:
256274
chat_client = AzureAIClient(
257-
project_client=self.project_client,
275+
project_endpoint=self.project_endpoint,
258276
agent_id=resolved,
259277
credential=self.creds,
260278
)
261279
self.logger.info(
262-
"RAI.AgentReuseSuccess: Created AzureAIClient via Projects SDK (id=%s)",
280+
"RAI.AgentReuseSuccess: Created AzureAIClient (id=%s)",
263281
resolved,
264282
)
265283
else:
@@ -284,33 +302,36 @@ async def get_database_team_agent(self) -> Optional[AzureAIClient]:
284302
async def save_database_team_agent(self) -> None:
285303
"""Save current team agent to database (only if truly new or changed)."""
286304
try:
287-
if self._agent.id is None:
288-
self.logger.error("Cannot save database team agent: agent_id is None")
305+
if self._agent is None or self._agent.id is None:
306+
self.logger.error("Cannot save database team agent: agent or agent_id is None")
289307
return
290308

309+
# Use the agent ID from ChatAgent (set during creation)
310+
agent_id = self._agent.id
311+
291312
# Check if stored ID matches current ID
292313
stored_id = await get_database_team_agent_id(
293314
self.memory_store, self.team_config, self.agent_name
294315
)
295-
if stored_id == self._agent.chat_client.agent_id:
316+
if stored_id == agent_id:
296317
self.logger.info(
297-
"RAI reuse: id unchanged (id=%s); skip save.", self._agent.id
318+
"RAI reuse: id unchanged (id=%s); skip save.", agent_id
298319
)
299320
return
300321

301322
currentAgent = CurrentTeamAgent(
302323
team_id=self.team_config.team_id,
303324
team_name=self.team_config.name,
304325
agent_name=self.agent_name,
305-
agent_foundry_id=self._agent.chat_client.agent_id,
326+
agent_foundry_id=agent_id,
306327
agent_description=self.agent_description,
307328
agent_instructions=self.agent_instructions,
308329
)
309330
await self.memory_store.add_team_agent(currentAgent)
310331
self.logger.info(
311332
"Saved team agent to database (agent_name=%s, id=%s)",
312333
self.agent_name,
313-
self._agent.id,
334+
agent_id,
314335
)
315336

316337
except Exception as ex:

src/backend/v4/magentic_agents/foundry_agent.py

Lines changed: 88 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
Role)
88
from agent_framework_azure_ai import \
99
AzureAIClient # Provided by agent_framework
10-
from azure.ai.projects.models import ConnectionType
10+
from azure.ai.projects.models import (
11+
PromptAgentDefinition,
12+
AzureAISearchAgentTool,
13+
AzureAISearchToolResource,
14+
AISearchIndexResource,
15+
)
1116
from common.config.app_config import config
1217
from common.database.database_base import DatabaseBase
1318
from common.models.messages_af import TeamConfiguration
@@ -65,19 +70,23 @@ def __init__(
6570
self._use_azure_search = self._is_azure_search_requested()
6671
self.use_reasoning = use_reasoning
6772

68-
# Placeholder for server-created Azure AI agent id (if Azure Search path)
73+
# Placeholder for server-created Azure AI agent id/version (if Azure Search path)
6974
self._azure_server_agent_id: Optional[str] = None
75+
self._azure_server_agent_version: Optional[str] = None
7076

7177
# -------------------------
7278
# Mode detection
7379
# -------------------------
7480
def _is_azure_search_requested(self) -> bool:
7581
"""Determine if Azure AI Search raw tool path should be used."""
82+
print(f"[DEBUG _is_azure_search_requested] Agent={self.agent_name}, search={self.search}")
7683
if not self.search:
84+
print(f"[DEBUG _is_azure_search_requested] Agent={self.agent_name}: No search config, returning False")
7785
return False
7886
# Minimal heuristic: presence of required attributes
7987

8088
has_index = hasattr(self.search, "index_name") and bool(self.search.index_name)
89+
print(f"[DEBUG _is_azure_search_requested] Agent={self.agent_name}: has_index={has_index}, index_name={getattr(self.search, 'index_name', None)}")
8190
if has_index:
8291
self.logger.info(
8392
"Azure AI Search requested (connection_id=%s, index=%s).",
@@ -113,14 +122,17 @@ async def _collect_tools(self) -> List:
113122
# -------------------------
114123
async def _create_azure_search_enabled_client(self, chatClient=None) -> Optional[AzureAIClient]:
115124
"""
116-
Create a server-side Azure AI agent with Azure AI Search raw tool.
125+
Create a server-side Azure AI agent with Azure AI Search tool using create_version.
126+
127+
This uses the AIProjectClient.agents.create_version() approach with:
128+
- PromptAgentDefinition for agent configuration
129+
- AzureAISearchAgentTool with AzureAISearchToolResource for search capability
130+
- AISearchIndexResource for index configuration with project_connection_id
117131
118132
Requirements:
119-
- An Azure AI Project Connection (type=AZURE_AI_SEARCH) that contains either:
120-
a) API key + endpoint, OR
121-
b) Managed Identity (RBAC enabled on the Search service with Search Service Contributor + Search Index Data Reader).
133+
- An Azure AI Project Connection for Azure AI Search
122134
- search_config.index_name must exist in the Search service.
123-
135+
- search_config.connection_name should match the AI Project connection name
124136
125137
Returns:
126138
AzureAIClient | None
@@ -134,92 +146,106 @@ async def _create_azure_search_enabled_client(self, chatClient=None) -> Optional
134146
self.logger.error("Search configuration missing.")
135147
return None
136148

137-
desired_connection_name = getattr(self.search, "connection_name", None)
149+
# Get connection name - this is used as project_connection_id in create_version
150+
connection_name = getattr(self.search, "connection_name", None)
151+
if not connection_name:
152+
# Fallback to environment variable
153+
connection_name = config.AZURE_AI_SEARCH_CONNECTION_NAME
154+
self.logger.info("Using connection_name from environment: %s", connection_name)
155+
138156
index_name = getattr(self.search, "index_name", "")
139157
query_type = getattr(self.search, "search_query_type", "simple")
158+
top_k = getattr(self.search, "top_k", 5)
140159

141160
if not index_name:
142161
self.logger.error(
143162
"index_name not provided in search_config; aborting Azure Search path."
144163
)
145164
return None
146165

147-
resolved_connection_id = None
148-
149-
try:
150-
async for connection in self.project_client.connections.list():
151-
if connection.type == ConnectionType.AZURE_AI_SEARCH:
152-
153-
if (
154-
desired_connection_name
155-
and connection.name == desired_connection_name
156-
):
157-
resolved_connection_id = connection.id
158-
break
159-
# Fallback: if no specific connection requested and none resolved yet, take the first
160-
if not desired_connection_name and not resolved_connection_id:
161-
resolved_connection_id = connection.id
162-
# Do not break yet; we log but allow chance to find a name match later. If not, this stays.
163-
164-
if not resolved_connection_id:
165-
self.logger.error(
166-
"No Azure AI Search connection resolved. " "connection_name=%s",
167-
desired_connection_name,
168-
)
169-
# return None
170-
171-
self.logger.info(
172-
"Using Azure AI Search connection (id=%s, requested_name=%s).",
173-
resolved_connection_id,
174-
desired_connection_name,
166+
if not connection_name:
167+
self.logger.error(
168+
"connection_name not provided; aborting Azure Search path."
175169
)
176-
except Exception as ex:
177-
self.logger.error("Failed to enumerate connections: %s", ex)
178170
return None
179171

180-
# Create agent with raw tool
172+
self.logger.info(
173+
"Creating Azure AI Search agent with create_version: connection_name=%s, index=%s, query_type=%s, top_k=%s",
174+
connection_name,
175+
index_name,
176+
query_type,
177+
top_k,
178+
)
179+
180+
# Create agent using create_version with PromptAgentDefinition and AzureAISearchAgentTool
181+
# This approach matches the Knowledge Mining Solution Accelerator pattern
181182
try:
182-
azure_agent = await self.client.create_agent(
183-
model=self.model_deployment_name,
184-
name=self.agent_name,
185-
instructions=(
186-
f"{self.agent_instructions} "
187-
"Always use the Azure AI Search tool and configured index for knowledge retrieval."
183+
enhanced_instructions = (
184+
f"{self.agent_instructions} "
185+
"Always use the Azure AI Search tool and configured index for knowledge retrieval."
186+
)
187+
188+
print(f"[DEBUG] Creating agent '{self.agent_name}' with instructions (first 200 chars): {enhanced_instructions[:200]}...")
189+
print(f"[DEBUG] Agent model: {self.model_deployment_name}")
190+
print(f"[DEBUG] Search config: connection={connection_name}, index={index_name}, query_type={query_type}, top_k={top_k}")
191+
192+
azure_agent = await self.project_client.agents.create_version(
193+
agent_name=self.agent_name,
194+
definition=PromptAgentDefinition(
195+
model=self.model_deployment_name,
196+
instructions=enhanced_instructions,
197+
tools=[
198+
AzureAISearchAgentTool(
199+
azure_ai_search=AzureAISearchToolResource(
200+
indexes=[
201+
AISearchIndexResource(
202+
project_connection_id=connection_name,
203+
index_name=index_name,
204+
query_type=query_type,
205+
top_k=top_k,
206+
)
207+
]
208+
)
209+
)
210+
],
188211
),
189-
tools=[{"type": "azure_ai_search"}],
190-
tool_resources={
191-
"azure_ai_search": {
192-
"indexes": [
193-
{
194-
"index_connection_id": resolved_connection_id,
195-
"index_name": index_name,
196-
"query_type": query_type,
197-
}
198-
]
199-
}
200-
},
201212
)
213+
202214
self._azure_server_agent_id = azure_agent.id
215+
self._azure_server_agent_version = azure_agent.version
203216
self.logger.info(
204-
"Created Azure server agent with Azure AI Search tool (agent_id=%s, index=%s, query_type=%s).",
217+
"Created Azure AI Search agent via create_version (name=%s, id=%s, version=%s, connection=%s, index=%s, query_type=%s, top_k=%s).",
218+
azure_agent.name,
205219
azure_agent.id,
220+
azure_agent.version,
221+
connection_name,
206222
index_name,
207223
query_type,
224+
top_k,
208225
)
226+
print(f"[DEBUG] Created agent via create_version: name={azure_agent.name}, id={azure_agent.id}, version={azure_agent.version}")
227+
print(f"[DEBUG] Agent definition: {azure_agent.definition}")
228+
print(f"[DEBUG] Agent instructions from definition: {getattr(azure_agent.definition, 'instructions', 'N/A')}")
209229

230+
# Wrap in AzureAIClient using agent_name and agent_version (NOT agent_id)
231+
# AzureAIClient constructor: agent_name, agent_version, project_endpoint, credential
210232
chat_client = AzureAIClient(
211-
project_client=self.project_client,
212-
agent_id=azure_agent.id,
233+
project_endpoint=self.project_endpoint,
234+
agent_name=azure_agent.name,
235+
agent_version=azure_agent.version, # Use the specific version we just created
213236
credential=self.creds,
214237
)
215238
return chat_client
239+
216240
except Exception as ex:
217241
self.logger.error(
218-
"Failed to create Azure Search enabled agent (connection_id=%s, index=%s): %s",
219-
resolved_connection_id,
242+
"Failed to create Azure Search enabled agent via create_version (connection=%s, index=%s): %s",
243+
connection_name,
220244
index_name,
221245
ex,
222246
)
247+
import traceback
248+
traceback.print_exc()
223249
return None
224250

225251
# -------------------------

src/backend/v4/magentic_agents/models/agent_models.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class SearchConfig:
4343
connection_name: str | None = None
4444
endpoint: str | None = None
4545
index_name: str | None = None
46+
search_query_type: str = "simple" # Options: "simple", "vector_simple", "vector", "semantic", "hybrid"
47+
top_k: int = 5 # Number of results to return
4648

4749
@classmethod
4850
def from_env(cls, index_name: str) -> "SearchConfig":
@@ -58,5 +60,7 @@ def from_env(cls, index_name: str) -> "SearchConfig":
5860
return cls(
5961
connection_name=connection_name,
6062
endpoint=endpoint,
61-
index_name=index_name
63+
index_name=index_name,
64+
search_query_type="simple", # Use simple query type (keyword search)
65+
top_k=5
6266
)

src/backend/v4/orchestration/orchestration_manager.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,17 +231,22 @@ async def get_current_or_new_orchestration(
231231
team_config: TeamConfiguration,
232232
team_switched: bool,
233233
team_service: TeamService = None,
234+
force_rebuild: bool = False,
234235
):
235236
"""
236237
Return an existing workflow for the user or create a new one if:
237238
- None exists
238239
- Team switched flag is True
240+
- force_rebuild is True (for new tasks after workflow completion)
239241
"""
240242
current = orchestration_config.get_current_orchestration(user_id)
241-
if current is None or team_switched:
242-
if current is not None and team_switched:
243+
needs_rebuild = current is None or team_switched or force_rebuild
244+
245+
if needs_rebuild:
246+
if current is not None and (team_switched or force_rebuild):
247+
reason = "team switched" if team_switched else "force rebuild for new task"
243248
cls.logger.info(
244-
"Team switched, closing previous agents for user '%s'", user_id
249+
"Rebuilding orchestration for user '%s' (reason: %s)", user_id, reason
245250
)
246251
# Close prior agents (same logic as old version)
247252
for agent in getattr(current, "_participants", {}).values():

0 commit comments

Comments
 (0)