Skip to content

Commit d887cf7

Browse files
authored
[AVC] Update agent endpoints (#13798)
* Update agent endpoint. * Code review feedback.
1 parent 9c20107 commit d887cf7

File tree

4 files changed

+401
-67
lines changed

4 files changed

+401
-67
lines changed

packages/python-packages/apiview-copilot/scripts/infra/_variables.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,8 @@ def __init__(self, *, is_staging: bool, path: str = "variables.yaml"):
7272
self.keyvault_endpoint = f"https://{self.keyvault_name}.vault.azure.net/"
7373
self.app_configuration_endpoint = f"https://{self.app_configuration_name}.azconfig.io"
7474
self.webapp_endpoint = f"https://{self.webapp_name}.azurewebsites.net/"
75-
self.foundry_endpoint = (
76-
f"https://{self.foundry_account_name}.services.ai.azure.com/api/projects/{self.foundry_project_name}"
77-
)
75+
self.foundry_endpoint = f"https://{self.foundry_account_name}.services.ai.azure.com"
76+
self.foundry_project = self.foundry_project_name
7877
self.assignee_object_id = os.getenv("ASSIGNEE_OBJECT_ID")
7978
self.is_staging = is_staging
8079

packages/python-packages/apiview-copilot/scripts/infra/create_resources.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,7 @@ def populate_settings(v: Variables):
11021102
"foundry_endpoint",
11031103
"foundry_kernel_model",
11041104
"foundry_api_version",
1105+
"foundry_project",
11051106
"openai_endpoint",
11061107
"rg_name",
11071108
"search_endpoint",

packages/python-packages/apiview-copilot/src/agent/_agent.py

Lines changed: 120 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66

77
"""
88
Module 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

1114
import asyncio
15+
import logging
1216
from contextlib import contextmanager
1317
from typing import Optional
1418

@@ -25,6 +29,92 @@
2529
from src.agent.tools._search_tools import SearchTools
2630
from 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

29119
async 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
89176
def 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 = """
96185
Your 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():
99188
process the request. You will also handle any errors that occur during the processing of the request and return
100189
an 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
143212
def 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 = """
150221
You 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

Comments
 (0)