77 ChatAgent ,
88 ChatMessage ,
99 Role ,
10- HostedFileSearchTool ,
11- HostedVectorStoreContent ,
1210 HostedCodeInterpreterTool ,
1311)
12+ from azure .ai .projects .models import ConnectionType
13+ from agent_framework_azure_ai import AzureAIAgentClient # Provided by agent_framework
14+
15+
1416from v4 .magentic_agents .common .lifecycle import AzureAgentBase
1517from v4 .magentic_agents .models .agent_models import MCPConfig , SearchConfig
1618from v4 .config .agent_registry import agent_registry
1719
1820
1921class FoundryAgentTemplate (AzureAgentBase ):
20- """Agent that uses Azure AI Search (RAG) and optional MCP tool via agent_framework."""
22+ """Agent that uses Azure AI Search (raw tool) OR MCP tool + optional Code Interpreter.
23+
24+ Priority:
25+ 1. Azure AI Search (if search_config contains required Azure Search fields)
26+ 2. MCP tool (legacy path)
27+ Code Interpreter is only attached on the MCP path (unless you want it also with Azure Search—currently skipped for incompatibility per request).
28+ """
2129
2230 def __init__ (
2331 self ,
@@ -37,41 +45,39 @@ def __init__(
3745 self .search = search_config
3846 self .logger = logging .getLogger (__name__ )
3947
48+ # Decide early whether Azure Search mode should be activated
49+ self ._use_azure_search = self ._is_azure_search_requested ()
50+
51+ # Placeholder for server-created Azure AI agent id (if Azure Search path)
52+ self ._azure_server_agent_id : Optional [str ] = None
53+
4054 # -------------------------
41- # Tool construction helpers
55+ # Mode detection
4256 # -------------------------
43- async def _make_file_search_tool (self ) -> Optional [ HostedFileSearchTool ] :
44- """Create File Search tool (RAG capability) using vector stores ."""
45- if not self .search or not self . search . vector_store_id :
46- self . logger . info ( "File search tool not enabled (missing vector_store_id)." )
47- return None
57+ def _is_azure_search_requested (self ) -> bool :
58+ """Determine if Azure AI Search raw tool path should be used ."""
59+ if not self .search :
60+ return False
61+ # Minimal heuristic: presence of required attributes
4862
49- try :
50- # HostedFileSearchTool uses vector stores, not direct Azure AI Search indexes
51- file_search_tool = HostedFileSearchTool (
52- inputs = [ HostedVectorStoreContent ( vector_store_id = self . search . vector_store_id )] ,
53- max_results = self . search . max_results if hasattr (self .search , 'max_results' ) else None ,
54- description = "Search through indexed documents"
63+ has_index = hasattr ( self . search , "index_name" ) and bool ( self . search . index_name )
64+ if has_index :
65+ self . logger . info (
66+ "Azure AI Search requested (connection_id=%s, index=%s)." ,
67+ getattr (self .search , "connection_name" , None ) ,
68+ getattr ( self . search , "index_name" , None ),
5569 )
56- self .logger .info ("Created HostedFileSearchTool with vector store: %s" , self .search .vector_store_id )
57- return file_search_tool
58- except Exception as ex :
59- self .logger .error ("File search tool creation failed: %s" , ex )
60- return None
70+ return True
71+ return False
72+
73+
6174
6275 async def _collect_tools (self ) -> List :
63- """Collect tool definitions for ChatAgent."""
76+ """Collect tool definitions for ChatAgent (MCP path only) ."""
6477 tools : List = []
6578
66- # File Search tool (RAG)
67- if self .search :
68- print ("Adding File Search tool." )
69- # search_tool = await self._make_file_search_tool()
70- # if search_tool:
71- # tools.append(search_tool)
72- # self.logger.info("Added File Search tool.")
7379
74- # Code Interpreter
80+ # Code Interpreter (only in MCP path per incompatibility note)
7581 if self .enable_code_interpreter :
7682 try :
7783 code_tool = HostedCodeInterpreterTool ()
@@ -85,28 +91,110 @@ async def _collect_tools(self) -> List:
8591 tools .append (self .mcp_tool )
8692 self .logger .info ("Added MCP tool: %s" , self .mcp_tool .name )
8793
88- self .logger .info ("Total tools collected: %d" , len (tools ))
94+ self .logger .info ("Total tools collected (MCP path) : %d" , len (tools ))
8995 return tools
9096
97+ # -------------------------
98+ # Azure Search helper
99+ # -------------------------
100+ async def _create_azure_search_enabled_client (self ):
101+ """
102+ Create a server-side Azure AI agent with raw Azure AI Search tool and return an AzureAIAgentClient.
103+ This mirrors your example while fitting existing lifecycle.
104+
105+ If these assumptions differ, adjust accordingly.
106+ """
107+
108+ connection_id = getattr (self .search , "connection_name" , "" )
109+ index_name = getattr (self .search , "index_name" , "" )
110+ query_type = getattr (self .search , "search_query_type" , "vector" )
111+
112+ # ai_search_conn_id = ""
113+ # async for connection in self.client.project_client.connections.list():
114+ # if connection.type == ConnectionType.AZURE_AI_SEARCH:
115+ # ai_search_conn_id = connection.id
116+ # break
117+ if not connection_id or not index_name :
118+ self .logger .error (
119+ "Missing azure_search_connection_id or azure_search_index_name in search_config; aborting Azure Search path."
120+ )
121+ return None
122+
123+ try :
124+ azure_agent = await self .client .project_client .agents .create_agent (
125+ model = self .model_deployment_name ,
126+ name = self .agent_name ,
127+ instructions = (
128+ f"{ self .agent_instructions } "
129+ "Always use the Azure AI Search tool and configured index for knowledge retrieval."
130+ ),
131+ tools = [{"type" : "azure_ai_search" }],
132+ tool_resources = {
133+ "azure_ai_search" : {
134+ "indexes" : [
135+ {
136+ "index_connection_id" : connection_id ,
137+ "index_name" : index_name ,
138+ "query_type" : query_type ,
139+ }
140+ ]
141+ }
142+ },
143+ )
144+ self ._azure_server_agent_id = azure_agent .id
145+ self .logger .info (
146+ "Created Azure server agent with Azure AI Search tool (agent_id=%s, index=%s)." ,
147+ azure_agent .id ,
148+ index_name ,
149+ )
150+
151+ chat_client = AzureAIAgentClient (
152+ project_client = self .client .project_client ,
153+ agent_id = azure_agent .id ,
154+ )
155+ return chat_client
156+ except Exception as ex :
157+ self .logger .error ("Failed to create Azure Search enabled agent: %s" , ex )
158+ return None
159+
91160 # -------------------------
92161 # Agent lifecycle override
93162 # -------------------------
94163 async def _after_open (self ) -> None :
95164 """Initialize ChatAgent after connections are established."""
96165 try :
97- tools = await self ._collect_tools ()
98-
99- self ._agent = ChatAgent (
100- chat_client = self .client ,
101- instructions = self .agent_instructions ,
102- name = self .agent_name ,
103- description = self .agent_description ,
104- tools = tools if tools else None ,
105- tool_choice = "auto" if tools else "none" ,
106- temperature = 0.7 ,
107- model_id = self .model_deployment_name ,
108- )
109-
166+ if self ._use_azure_search :
167+ # Azure Search mode (skip MCP + Code Interpreter due to incompatibility)
168+ self .logger .info ("Initializing agent in Azure AI Search mode (exclusive)." )
169+ chat_client = await self ._create_azure_search_enabled_client ()
170+ if not chat_client :
171+ raise RuntimeError ("Azure AI Search mode requested but setup failed." )
172+
173+ # In Azure Search raw tool path, tools/tool_choice are handled server-side.
174+ self ._agent = ChatAgent (
175+ chat_client = chat_client ,
176+ instructions = self .agent_instructions ,
177+ name = self .agent_name ,
178+ description = self .agent_description ,
179+ tool_choice = "required" , # Force usage
180+ temperature = 0.7 ,
181+ model_id = self .model_deployment_name ,
182+ )
183+ else :
184+ # use MCP path
185+ self .logger .info ("Initializing agent in MCP mode." )
186+ tools = await self ._collect_tools ()
187+ self ._agent = ChatAgent (
188+ chat_client = self .client ,
189+ instructions = self .agent_instructions ,
190+ name = self .agent_name ,
191+ description = self .agent_description ,
192+ tools = tools if tools else None ,
193+ tool_choice = "auto" if tools else "none" ,
194+ temperature = 0.7 ,
195+ model_id = self .model_deployment_name ,
196+ )
197+
110198 self .logger .info ("Initialized ChatAgent '%s'" , self .agent_name )
111199 except Exception as ex :
112200 self .logger .error ("Failed to initialize ChatAgent: %s" , ex )
@@ -128,10 +216,32 @@ async def invoke(self, prompt: str):
128216 raise RuntimeError ("Agent not initialized; call open() first." )
129217
130218 messages = [ChatMessage (role = Role .USER , text = prompt )]
131-
219+
132220 async for update in self ._agent .run_stream (messages = messages ):
133221 yield update
134222
223+ # -------------------------
224+ # Cleanup (optional override if you want to delete server-side agent)
225+ # -------------------------
226+ async def close (self ) -> None :
227+ """Extend base close to optionally delete server-side Azure agent."""
228+ try :
229+ if self ._use_azure_search and self ._azure_server_agent_id and hasattr (self , "project_client" ):
230+ try :
231+ await self .project_client .agents .delete_agent (self ._azure_server_agent_id )
232+ self .logger .info (
233+ "Deleted Azure server agent (id=%s) during close." , self ._azure_server_agent_id
234+ )
235+ except Exception as ex :
236+ self .logger .warning (
237+ "Failed to delete Azure server agent (id=%s): %s" ,
238+ self ._azure_server_agent_id ,
239+ ex ,
240+ )
241+ finally :
242+ await super ().close ()
243+
244+
135245# -------------------------
136246# Factory
137247# -------------------------
0 commit comments