66
77"""
88Module for managing APIView Copilot agents.
9+
10+ Agents are retrieved by name if they exist, or created if not. Each chat session
11+ creates a new thread with the persistent agent, rather than creating/deleting agents per request.
912"""
1013
1114import asyncio
15+ import logging
1216from contextlib import contextmanager
1317from typing import Optional
1418
2529from src .agent .tools ._search_tools import SearchTools
2630from src .agent .tools ._utility_tools import UtilityTools
2731
32+ logger = logging .getLogger (__name__ )
33+
34+
35+ def _get_agents_endpoint () -> str :
36+ """Get the Azure AI Agents endpoint.
37+
38+ Constructs the full endpoint: {FOUNDRY_ENDPOINT}/api/projects/{FOUNDRY_PROJECT}
39+ """
40+ settings = SettingsManager ()
41+ endpoint = settings .get ("FOUNDRY_ENDPOINT" )
42+ project = settings .get ("FOUNDRY_PROJECT" )
43+
44+ if not endpoint :
45+ raise ValueError ("FOUNDRY_ENDPOINT not configured in AppConfiguration." )
46+ if not project :
47+ raise ValueError ("FOUNDRY_PROJECT not configured in AppConfiguration." )
48+
49+ # Construct the full agents endpoint
50+ endpoint = endpoint .rstrip ("/" )
51+ return f"{ endpoint } /api/projects/{ project } "
52+
53+
54+ def _get_client () -> AgentsClient :
55+ """Create and return an AgentsClient."""
56+ credential = get_credential ()
57+ endpoint = _get_agents_endpoint ()
58+ return AgentsClient (endpoint = endpoint , credential = credential )
59+
60+
61+ def _get_or_create_agent (
62+ client : AgentsClient ,
63+ name : str ,
64+ description : str ,
65+ instructions : str ,
66+ toolset : ToolSet ,
67+ ) -> str :
68+ """Get existing agent by name or create a new one if none exists.
69+
70+ Args:
71+ client: The AgentsClient to use
72+ name: Agent name to search for / create
73+ description: Agent description
74+ instructions: Agent instructions
75+ toolset: Tools available to the agent
76+
77+ Returns:
78+ The agent ID
79+ """
80+ settings = SettingsManager ()
81+ model_deployment_name = settings .get ("FOUNDRY_KERNEL_MODEL" )
82+ if not model_deployment_name :
83+ raise ValueError ("FOUNDRY_KERNEL_MODEL not configured in AppConfiguration." )
84+
85+ # Search for existing agent by name
86+ logger .info ("Searching for existing agent by name '%s'..." , name )
87+ try :
88+ existing_agents = list (client .list_agents ())
89+ for agent in existing_agents :
90+ if agent .name == name :
91+ logger .info ("Found existing agent: %s" , agent .id )
92+ return agent .id
93+ except Exception as e :
94+ logger .warning ("Error listing agents: %s, will create new one" , e )
95+
96+ # Create new agent only if none exists with that name
97+ logger .info ("No existing agent found, creating new agent '%s'..." , name )
98+ try :
99+ agent = client .create_agent (
100+ name = name ,
101+ description = description ,
102+ model = model_deployment_name ,
103+ instructions = instructions ,
104+ toolset = toolset ,
105+ )
106+ logger .info ("Created agent: %s" , agent .id )
107+ return agent .id
108+ except Exception as e :
109+ error_msg = str (e )
110+ if "timed out" in error_msg .lower ():
111+ raise RuntimeError (
112+ "Failed to create agent: Azure Agents service timed out. "
113+ "This may be a temporary service issue. Please try again in a moment."
114+ ) from e
115+ else :
116+ raise RuntimeError (f"Failed to create agent: { error_msg } " ) from e
117+
28118
29119async def invoke_agent (
30120 * ,
@@ -52,9 +142,6 @@ async def invoke_agent(
52142 await asyncio .to_thread (client .messages .create , thread_id = thread_id , role = "user" , content = user_input )
53143
54144 # 3) Process a run (polls until terminal state; executes tools if auto-enabled)
55- import logging
56-
57- logger = logging .getLogger (__name__ )
58145 logger .info ("Processing agent run... (this may take a moment)" )
59146 await asyncio .to_thread (client .runs .create_and_process , thread_id = thread_id , agent_id = agent_id )
60147 logger .info ("Agent run completed" )
@@ -87,10 +174,12 @@ def extract_text(obj):
87174
88175@contextmanager
89176def get_readwrite_agent ():
90- """Create and yield the read-write APIView Copilot agent."""
91- settings = SettingsManager ()
92- endpoint = settings .get ("FOUNDRY_ENDPOINT" )
93- model_deployment_name = settings .get ("FOUNDRY_KERNEL_MODEL" )
177+ """Get or create the read-write APIView Copilot agent.
178+
179+ The agent is retrieved by name if it exists, or created if not.
180+ Each chat session creates a new thread with this persistent agent.
181+ """
182+ client = _get_client ()
94183
95184 ai_instructions = """
96185Your job is to receive a request from the user, determine their intent, and pass the request to the
@@ -99,52 +188,34 @@ def get_readwrite_agent():
99188process the request. You will also handle any errors that occur during the processing of the request and return
100189an appropriate error message to the user.
101190"""
102- credential = get_credential ()
103- client = AgentsClient (endpoint = endpoint , credential = credential )
104-
105- # TODO: These were treated as subagents before and may not be applicable in the new format.
106- # delete_agent = await stack.enter_async_context(get_delete_agent())
107- # create_agent = await stack.enter_async_context(get_create_agent())
108- # retrieve_agent = await stack.enter_async_context(get_retrieve_agent())
109- # link_agent = await stack.enter_async_context(get_link_agent())
110191
111192 toolset = ToolSet ()
112193 tools = SearchTools ().all_tools () + ApiReviewTools ().all_tools () + UtilityTools ().all_tools ()
113194 toolset .add (FunctionTool (tools ))
114195
115- try :
116- agent = client .create_agent (
117- name = "APIView Copilot Readwrite Agent" ,
118- description = "A read-write agent that can perform searches and trigger allowed actions." ,
119- model = model_deployment_name ,
120- instructions = ai_instructions ,
121- toolset = toolset ,
122- )
123- except Exception as e :
124- error_msg = str (e )
125- if "timed out" in error_msg .lower ():
126- raise RuntimeError (
127- "Failed to create agent: Azure Agents service timed out. "
128- "This may be a temporary service issue. Please try again in a moment."
129- ) from e
130- else :
131- raise RuntimeError (f"Failed to create agent: { error_msg } " ) from e
196+ agent_id = _get_or_create_agent (
197+ client = client ,
198+ name = "APIView Copilot Readwrite Agent" ,
199+ description = "A read-write agent that can perform searches and trigger allowed actions." ,
200+ instructions = ai_instructions ,
201+ toolset = toolset ,
202+ )
132203
133204 # enable all tools by default
134205 client .enable_auto_function_calls (tools = toolset )
135206
136- try :
137- yield client , agent .id
138- finally :
139- client .delete_agent (agent .id )
207+ yield client , agent_id
208+ # Note: We don't delete the agent - it persists for reuse
140209
141210
142211@contextmanager
143212def get_readonly_agent ():
144- """Create and yield a read-only APIView Copilot agent (no mutating tools)."""
145- settings = SettingsManager ()
146- endpoint = settings .get ("FOUNDRY_ENDPOINT" )
147- model_deployment_name = settings .get ("FOUNDRY_KERNEL_MODEL" )
213+ """Get or create a read-only APIView Copilot agent (no mutating tools).
214+
215+ The agent is retrieved by name if it exists, or created if not.
216+ Each chat session creates a new thread with this persistent agent.
217+ """
218+ client = _get_client ()
148219
149220 ai_instructions = """
150221You are a READ-ONLY assistant.
@@ -153,9 +224,6 @@ def get_readonly_agent():
153224- If asked to update/create/delete/link data or run indexers, refuse and explain alternatives.
154225"""
155226
156- credential = get_credential ()
157- client = AgentsClient (endpoint = endpoint , credential = credential )
158-
159227 toolset = ToolSet ()
160228
161229 # Exclude tools that can mutate state or trigger background operations.
@@ -165,28 +233,16 @@ def get_readonly_agent():
165233 tools = safe_search_tools + ApiReviewTools ().all_tools () + UtilityTools ().all_tools ()
166234 toolset .add (FunctionTool (tools ))
167235
168- try :
169- agent = client .create_agent (
170- name = "APIView Copilot Readonly Agent" ,
171- description = "A read-only agent for search/retrieve/summarize without side effects." ,
172- model = model_deployment_name ,
173- instructions = ai_instructions ,
174- toolset = toolset ,
175- )
176- except Exception as e :
177- error_msg = str (e )
178- if "timed out" in error_msg .lower ():
179- raise RuntimeError (
180- "Failed to create agent: Azure Agents service timed out. "
181- "This may be a temporary service issue. Please try again in a moment."
182- ) from e
183- else :
184- raise RuntimeError (f"Failed to create agent: { error_msg } " ) from e
236+ agent_id = _get_or_create_agent (
237+ client = client ,
238+ name = "APIView Copilot Readonly Agent" ,
239+ description = "A read-only agent for search/retrieve/summarize without side effects." ,
240+ instructions = ai_instructions ,
241+ toolset = toolset ,
242+ )
185243
186244 # enable all tools by default
187245 client .enable_auto_function_calls (tools = toolset )
188246
189- try :
190- yield client , agent .id
191- finally :
192- client .delete_agent (agent .id )
247+ yield client , agent_id
248+ # Note: We don't delete the agent - it persists for reuse
0 commit comments