33import logging
44from typing import List , Optional
55
6- from azure .ai .agents .models import (
7- Agent ,
8- AzureAISearchTool ,
9- CodeInterpreterToolDefinition ,
10- )
11-
126from agent_framework import (
7+ ChatAgent ,
138 ChatMessage ,
149 Role ,
15- ChatOptions ,
16- HostedMCPTool ,
17- AggregateContextProvider ,
18- ChatAgent ,
19- ChatClientProtocol ,
20- ChatMessageStoreProtocol ,
21- ContextProvider ,
22- Middleware ,
23- ToolMode ,
24- ToolProtocol ,
10+ HostedFileSearchTool ,
11+ HostedVectorStoreContent ,
12+ HostedCodeInterpreterTool ,
2513)
2614from af .magentic_agents .common .lifecycle import AzureAgentBase
2715from af .magentic_agents .models .agent_models import MCPConfig , SearchConfig
2816from af .config .agent_registry import agent_registry
2917
30- # Broad exception flag
31- # pylint: disable=w0718
32-
3318
3419class FoundryAgentTemplate (AzureAgentBase ):
3520 """Agent that uses Azure AI Search (RAG) and optional MCP tool via agent_framework."""
@@ -44,253 +29,106 @@ def __init__(
4429 mcp_config : MCPConfig | None = None ,
4530 search_config : SearchConfig | None = None ,
4631 ) -> None :
47- super ().__init__ (mcp = mcp_config )
32+ super ().__init__ (mcp = mcp_config , model_deployment_name = model_deployment_name )
4833 self .agent_name = agent_name
4934 self .agent_description = agent_description
5035 self .agent_instructions = agent_instructions
51- self .model_deployment_name = model_deployment_name
5236 self .enable_code_interpreter = enable_code_interpreter
53- self .mcp = mcp_config
5437 self .search = search_config
55-
56- self ._search_connection = None
5738 self .logger = logging .getLogger (__name__ )
5839
59- if self .model_deployment_name in {"o3" , "o4-mini" }:
60- raise ValueError (
61- "Foundry agents do not support reasoning models in this implementation."
62- )
63-
6440 # -------------------------
6541 # Tool construction helpers
6642 # -------------------------
67- async def _make_azure_search_tool (self ) -> Optional [AzureAISearchTool ]:
68- """Create Azure AI Search tool (RAG capability)."""
69- if not (
70- self .client
71- and self .search
72- and self .search .connection_name
73- and self .search .index_name
74- ):
75- self .logger .info (
76- "Azure AI Search tool not enabled (missing config or client)."
77- )
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)." )
7847 return None
7948
8049 try :
81- self ._search_connection = await self .client .connections .get (
82- name = self .search .connection_name
83- )
84- self .logger .info (
85- "Found Azure AI Search connection: %s" , self ._search_connection .id
86- )
87-
88- return AzureAISearchTool (
89- index_connection_id = self ._search_connection .id ,
90- index_name = self .search .index_name ,
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"
9155 )
56+ self .logger .info ("Created HostedFileSearchTool with vector store: %s" , self .search .vector_store_id )
57+ return file_search_tool
9258 except Exception as ex :
93- self .logger .error (
94- "Azure AI Search tool creation failed: %s | connection=%s | index=%s" ,
95- ex ,
96- getattr (self .search , "connection_name" , None ),
97- getattr (self .search , "index_name" , None ),
98- )
59+ self .logger .error ("File search tool creation failed: %s" , ex )
9960 return None
10061
101- async def _collect_tools_and_resources (self ) -> tuple [ List , dict ] :
102- """Collect tool definitions + tool_resources for agent definition creation ."""
62+ async def _collect_tools (self ) -> List :
63+ """Collect tool definitions for ChatAgent ."""
10364 tools : List = []
104- tool_resources : dict = {}
10565
106- # Search tool
107- if self .search and self . search . connection_name and self . search . index_name :
108- search_tool = await self ._make_azure_search_tool ()
66+ # File Search tool (RAG)
67+ if self .search :
68+ search_tool = await self ._make_file_search_tool ()
10969 if search_tool :
110- tools .extend (search_tool .definitions )
111- tool_resources = search_tool .resources
112- self .logger .info (
113- "Added %d Azure AI Search tool definitions." ,
114- len (search_tool .definitions ),
115- )
116- else :
117- self .logger .warning ("Azure AI Search tool not configured properly." )
70+ tools .append (search_tool )
71+ self .logger .info ("Added File Search tool." )
11872
11973 # Code Interpreter
12074 if self .enable_code_interpreter :
12175 try :
122- tools .append (CodeInterpreterToolDefinition ())
123- self .logger .info ("Added Code Interpreter tool definition." )
124- except ImportError as ie :
125- self .logger .error ("Code Interpreter dependency missing: %s" , ie )
76+ code_tool = HostedCodeInterpreterTool ()
77+ tools .append (code_tool )
78+ self .logger .info ("Added Code Interpreter tool." )
79+ except Exception as ie :
80+ self .logger .error ("Code Interpreter tool creation failed: %s" , ie )
81+
82+ # MCP Tool (from base class)
83+ if self .mcp_tool :
84+ tools .append (self .mcp_tool )
85+ self .logger .info ("Added MCP tool: %s" , self .mcp_tool .name )
12686
127- self .logger .info ("Total tool definitions collected: %d" , len (tools ))
128- return tools , tool_resources
87+ self .logger .info ("Total tools collected: %d" , len (tools ))
88+ return tools
12989
13090 # -------------------------
13191 # Agent lifecycle override
13292 # -------------------------
13393 async def _after_open (self ) -> None :
134- # Instantiate persistent AzureAIAgentClient bound to existing agent_id
94+ """Initialize ChatAgent after connections are established."""
13595 try :
136- # AzureAIAgentClient(
137- # project_client=self.client,
138- # agent_id=str(definition.id),
139- # agent_name=self.agent_name,
140- # )
141- tools , tool_resources = await self ._collect_tools_and_resources ()
96+ tools = await self ._collect_tools ()
97+
14298 self ._agent = ChatAgent (
14399 chat_client = self .client ,
144- instructions = self .agent_description + " " + self . agent_instructions ,
100+ instructions = self .agent_instructions ,
145101 name = self .agent_name ,
146102 description = self .agent_description ,
147103 tools = tools if tools else None ,
148104 tool_choice = "auto" if tools else "none" ,
149- allow_multiple_tool_calls = True ,
150105 temperature = 0.7 ,
106+ model_id = self .model_deployment_name ,
151107 )
152-
108+
109+ self .logger .info ("Initialized ChatAgent '%s'" , self .agent_name )
153110 except Exception as ex :
154- self .logger .error ("Failed to initialize AzureAIAgentClient : %s" , ex )
111+ self .logger .error ("Failed to initialize ChatAgent : %s" , ex )
155112 raise
156113
157114 # Register agent globally
158115 try :
159116 agent_registry .register_agent (self )
160- self .logger .info (
161- "Registered agent '%s' in global registry." , self .agent_name
162- )
117+ self .logger .info ("Registered agent '%s' in global registry." , self .agent_name )
163118 except Exception as reg_ex :
164- self .logger .warning (
165- "Could not register agent '%s': %s" , self .agent_name , reg_ex
166- )
167-
168- # -------------------------
169- # Definition compatibility
170- # -------------------------
171- async def _check_connection_compatibility (self , existing_definition : Agent ) -> bool :
172- """Verify existing Azure AI Search connection matches current config."""
173- try :
174- if not (self .search and self .search .connection_name ):
175- self .logger .info ("No search config provided; assuming compatibility." )
176- return True
177-
178- tool_resources = getattr (existing_definition , "tool_resources" , None )
179- if not tool_resources :
180- self .logger .info (
181- "Existing agent has no tool resources; incompatible with search requirement."
182- )
183- return False
184-
185- azure_search = tool_resources .get ("azure_ai_search" , {})
186- indexes = azure_search .get ("indexes" , [])
187- if not indexes :
188- self .logger .info (
189- "Existing agent has no Azure AI Search indexes; incompatible."
190- )
191- return False
192-
193- existing_conn_id = indexes [0 ].get ("index_connection_id" )
194- if not existing_conn_id :
195- self .logger .info (
196- "Existing agent missing index_connection_id; incompatible."
197- )
198- return False
199-
200- current_connection = await self .client .connections .get (
201- name = self .search .connection_name
202- )
203- same = existing_conn_id == current_connection .id
204- if same :
205- self .logger .info ("Search connection compatible: %s" , existing_conn_id )
206- else :
207- self .logger .info (
208- "Search connection mismatch: existing=%s current=%s" ,
209- existing_conn_id ,
210- current_connection .id ,
211- )
212- return same
213- except Exception as ex :
214- self .logger .error ("Error during connection compatibility check: %s" , ex )
215- return False
216-
217- async def _get_azure_ai_agent_definition (self , agent_name : str ) -> Agent | None :
218- """Return existing agent definition by name or None."""
219- try :
220- async for agent in self .client .agents .list_agents ():
221- if agent .name == agent_name :
222- self .logger .info (
223- "Found existing agent '%s' (id=%s)." , agent_name , agent .id
224- )
225- return await self .client .agents .get_agent (agent .id )
226- return None
227- except Exception as e :
228- if "ResourceNotFound" in str (e ) or "404" in str (e ):
229- self .logger .info ("Agent '%s' not found; will create new." , agent_name )
230- else :
231- self .logger .warning (
232- "Unexpected error listing agent '%s': %s; will attempt creation." ,
233- agent_name ,
234- e ,
235- )
236- return None
237-
238- # -------------------------
239- # Diagnostics helper
240- # -------------------------
241- async def fetch_run_details (self , thread_id : str , run_id : str ) -> None :
242- """Log run diagnostics for a failed run."""
243- try :
244- run = await self .client .agents .runs .get (thread = thread_id , run = run_id )
245- self .logger .error (
246- "Run failure | status=%s | id=%s | last_error=%s | usage=%s" ,
247- getattr (run , "status" , None ),
248- run_id ,
249- getattr (run , "last_error" , None ),
250- getattr (run , "usage" , None ),
251- )
252- except Exception as ex :
253- self .logger .error (
254- "Failed fetching run details (thread=%s run=%s): %s" ,
255- thread_id ,
256- run_id ,
257- ex ,
258- )
119+ self .logger .warning ("Could not register agent '%s': %s" , self .agent_name , reg_ex )
259120
260121 # -------------------------
261122 # Invocation (streaming)
262123 # -------------------------
263124 async def invoke (self , prompt : str ):
264- """
265- Stream model output for a prompt.
266-
267- Yields ChatResponseUpdate objects:
268- - update.text for incremental text
269- - update.contents for tool calls / usage events
270- """
125+ """Stream model output for a prompt."""
271126 if not self ._agent :
272- raise RuntimeError ("Agent client not initialized; call open() first." )
127+ raise RuntimeError ("Agent not initialized; call open() first." )
273128
274129 messages = [ChatMessage (role = Role .USER , text = prompt )]
275-
276- tools = []
277- # Use mcp_tool prepared in AzureAgentBase
278- if self .mcp_tool and isinstance (self .mcp_tool , HostedMCPTool ):
279- tools .append (self .mcp_tool )
280-
281- chat_options = ChatOptions (
282- model_id = self .model_deployment_name ,
283- tools = tools if tools else None ,
284- tool_choice = "auto" if tools else "none" ,
285- allow_multiple_tool_calls = True ,
286- temperature = 0.7 ,
287- )
288-
289- async for update in self ._agent .run_stream (
290- messages = messages ,
291- # chat_options=chat_options,
292- # instructions=self.agent_instructions,
293- ):
130+
131+ async for update in self ._agent .run_stream (messages = messages ):
294132 yield update
295133
296134
@@ -305,7 +143,7 @@ async def create_foundry_agent(
305143 mcp_config : MCPConfig | None ,
306144 search_config : SearchConfig | None ,
307145) -> FoundryAgentTemplate :
308- """Factory to create and open a FoundryAgentTemplate (agent_framework version) ."""
146+ """Factory to create and open a FoundryAgentTemplate."""
309147 agent = FoundryAgentTemplate (
310148 agent_name = agent_name ,
311149 agent_description = agent_description ,
@@ -316,4 +154,4 @@ async def create_foundry_agent(
316154 search_config = search_config ,
317155 )
318156 await agent .open ()
319- return agent
157+ return agent
0 commit comments