Skip to content

Commit e4896d5

Browse files
agent framework v2 changes
1 parent 38194fb commit e4896d5

8 files changed

Lines changed: 276 additions & 1059 deletions

File tree

src/backend/pyproject.toml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,20 @@ requires-python = ">=3.11"
77
dependencies = [
88
"azure-ai-evaluation==1.11.0",
99
"azure-ai-inference==1.0.0b9",
10-
"azure-ai-projects==1.0.0",
11-
"azure-ai-agents==1.2.0b5",
10+
"azure-ai-projects==2.0.0b3",
1211
"azure-cosmos==4.9.0",
1312
"azure-identity==1.24.0",
1413
"azure-monitor-events-extension==0.1.0",
15-
"azure-monitor-opentelemetry==1.7.0",
14+
"azure-monitor-opentelemetry>=1.8.0",
1615
"azure-search-documents==11.5.3",
1716
"fastapi==0.116.1",
18-
"openai==1.105.0",
19-
"opentelemetry-api==1.36.0",
20-
"opentelemetry-exporter-otlp-proto-grpc==1.36.0",
21-
"opentelemetry-exporter-otlp-proto-http==1.36.0",
22-
"opentelemetry-instrumentation-fastapi==0.57b0",
23-
"opentelemetry-instrumentation-openai==0.46.2",
24-
"opentelemetry-sdk==1.36.0",
17+
"openai>=2.8.0",
18+
"opentelemetry-api>=1.39.0",
19+
"opentelemetry-exporter-otlp-proto-grpc>=1.39.0",
20+
"opentelemetry-exporter-otlp-proto-http>=1.39.0",
21+
"opentelemetry-instrumentation-fastapi>=0.57b0",
22+
"opentelemetry-instrumentation-openai>=0.46.2",
23+
"opentelemetry-sdk>=1.39.0",
2524
"pytest==8.4.1",
2625
"pytest-asyncio==0.24.0",
2726
"pytest-cov==5.0.0",
@@ -31,6 +30,7 @@ dependencies = [
3130
"uvicorn==0.35.0",
3231
"pylint-pydantic==0.3.5",
3332
"pexpect==4.9.0",
34-
"mcp==1.13.1",
35-
"agent-framework>=1.0.0b251105",
36-
]
33+
"mcp>=1.24.0,<2",
34+
"agent-framework-azure-ai==1.0.0b260130",
35+
"agent-framework-core==1.0.0b260130"
36+
]

src/backend/uv.lock

Lines changed: 100 additions & 926 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/backend/v4/callbacks/response_handlers.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
import re
99
from typing import Any
1010

11-
from agent_framework import ChatMessage
12-
# Removed: from agent_framework._content import FunctionCallContent (does not exist)
13-
14-
from agent_framework._workflows._magentic import AgentRunResponseUpdate # Streaming update type from workflows
11+
from agent_framework import ChatMessage, AgentRunUpdateEvent
1512

1613
from v4.config.settings import connection_config
1714
from v4.models.messages import (
@@ -111,26 +108,31 @@ def agent_response_callback(
111108

112109
async def streaming_agent_response_callback(
113110
agent_id: str,
114-
update: AgentRunResponseUpdate,
111+
update, # AgentRunUpdateEvent.data or similar streaming update object
115112
is_final: bool,
116113
user_id: str | None = None,
117114
) -> None:
118115
"""
119-
Streaming callback for incremental agent output (AgentRunResponseUpdate).
116+
Streaming callback for incremental agent output.
120117
"""
121118
if not user_id:
122119
return
123120

124121
try:
122+
# Handle both AgentRunUpdateEvent.data and raw text updates
125123
chunk_text = getattr(update, "text", None)
126-
if not chunk_text:
127-
contents = getattr(update, "contents", []) or []
128-
collected = []
129-
for item in contents:
130-
txt = getattr(item, "text", None)
131-
if txt:
132-
collected.append(str(txt))
133-
chunk_text = "".join(collected) if collected else ""
124+
125+
# If text is None, don't fall back to str(update) as that would show object repr
126+
# Just skip if there's no actual text content
127+
if chunk_text is None:
128+
# Check if update is a ChatMessage
129+
if isinstance(update, ChatMessage):
130+
chunk_text = update.text or ""
131+
elif hasattr(update, "content"):
132+
chunk_text = str(update.content) if update.content else ""
133+
else:
134+
# Skip if no text content available
135+
return
134136

135137
cleaned = clean_citations(chunk_text or "")
136138

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

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
MCPStreamableHTTPTool,
1111
)
1212

13-
# from agent_framework.azure import AzureAIAgentClient
14-
from agent_framework_azure_ai import AzureAIAgentClient
13+
# from agent_framework.azure import AzureAIClient
14+
from agent_framework_azure_ai import AzureAIClient
1515
from azure.ai.agents.aio import AgentsClient
1616
from azure.identity.aio import DefaultAzureCredential
1717
from common.database.database_base import DatabaseBase
@@ -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[AzureAIAgentClient] = None
54+
self.client: Optional[AzureAIClient] = None
5555
self.project_endpoint = project_endpoint
5656
self.creds: Optional[DefaultAzureCredential] = None
5757
self.memory_store: Optional[DatabaseBase] = memory_store
@@ -105,7 +105,7 @@ async def close(self) -> None:
105105
# Attempt to close the underlying agent/client if it exposes close()
106106
if self._agent and hasattr(self._agent, "close"):
107107
try:
108-
await self._agent.close() # AzureAIAgentClient has async close
108+
await self._agent.close() # AzureAIClient has async close
109109
except Exception as exc:
110110
# Best-effort close; log failure but continue teardown
111111
self.logger.warning(
@@ -148,24 +148,22 @@ async def _after_open(self) -> None:
148148
"""Subclasses must build self._agent here."""
149149
raise NotImplementedError
150150

151-
def get_chat_client(self, chat_client) -> AzureAIAgentClient:
152-
"""Return the underlying ChatClientProtocol (AzureAIAgentClient)."""
151+
def get_chat_client(self, chat_client) -> AzureAIClient:
152+
"""Return the underlying ChatClientProtocol (AzureAIClient)."""
153153
if chat_client:
154154
return chat_client
155155
if (
156156
self._agent
157157
and self._agent.chat_client
158-
and self._agent.chat_client.agent_id is not None
159158
):
160159
return self._agent.chat_client # type: ignore
161-
chat_client = AzureAIAgentClient(
160+
chat_client = AzureAIClient(
162161
project_endpoint=self.project_endpoint,
163162
model_deployment_name=self.model_deployment_name,
164-
async_credential=self.creds,
163+
credential=self.creds,
165164
)
166165
self.logger.info(
167-
"Created new AzureAIAgentClient for get chat client",
168-
extra={"agent_id": chat_client.agent_id},
166+
"Created new AzureAIClient for get chat client",
169167
)
170168
return chat_client
171169

@@ -219,20 +217,17 @@ async def resolve_agent_id(self, agent_id: str) -> Optional[str]:
219217
return None
220218

221219
def get_agent_id(self, chat_client) -> str:
222-
"""Return the underlying agent ID."""
223-
if chat_client and chat_client.agent_id is not None:
224-
return chat_client.agent_id
225-
if (
226-
self._agent
227-
and self._agent.chat_client
228-
and self._agent.chat_client.agent_id is not None
229-
):
230-
return self._agent.chat_client.agent_id # type: ignore
220+
"""Return the underlying agent ID or generate a new one.
221+
222+
Note: The new AzureAIClient doesn't expose agent_id directly.
223+
We generate a new ID if not available.
224+
"""
225+
# Generate a new agent ID since AzureAIClient doesn't expose agent_id
231226
id = generate_assistant_id()
232227
self.logger.info("Generated new agent ID: %s", id)
233228
return id
234229

235-
async def get_database_team_agent(self) -> Optional[AzureAIAgentClient]:
230+
async def get_database_team_agent(self) -> Optional[AzureAIClient]:
236231
"""Retrieve existing team agent from database, if any."""
237232
chat_client = None
238233
try:
@@ -258,24 +253,24 @@ async def get_database_team_agent(self) -> Optional[AzureAIAgentClient]:
258253

259254
# Create client with resolved ID, preferring project_client for RAI agents
260255
if self.agent_name == "RAIAgent" and self.project_client:
261-
chat_client = AzureAIAgentClient(
256+
chat_client = AzureAIClient(
262257
project_client=self.project_client,
263258
agent_id=resolved,
264-
async_credential=self.creds,
259+
credential=self.creds,
265260
)
266261
self.logger.info(
267-
"RAI.AgentReuseSuccess: Created AzureAIAgentClient via Projects SDK (id=%s)",
262+
"RAI.AgentReuseSuccess: Created AzureAIClient via Projects SDK (id=%s)",
268263
resolved,
269264
)
270265
else:
271-
chat_client = AzureAIAgentClient(
266+
chat_client = AzureAIClient(
272267
project_endpoint=self.project_endpoint,
273268
agent_id=resolved,
274269
model_deployment_name=self.model_deployment_name,
275-
async_credential=self.creds,
270+
credential=self.creds,
276271
)
277272
self.logger.info(
278-
"Created AzureAIAgentClient via endpoint (id=%s)", resolved
273+
"Created AzureAIClient via endpoint (id=%s)", resolved
279274
)
280275

281276
except Exception as ex:
@@ -339,10 +334,10 @@ async def _prepare_mcp_tool(self) -> None:
339334

340335
class AzureAgentBase(MCPEnabledBase):
341336
"""
342-
Extends MCPEnabledBase with Azure credential + AzureAIAgentClient contexts.
337+
Extends MCPEnabledBase with Azure credential + AzureAIClient contexts.
343338
Subclasses:
344339
- create or attach an Azure AI Agent definition
345-
- instantiate an AzureAIAgentClient and assign to self._agent
340+
- instantiate an AzureAIClient and assign to self._agent
346341
- optionally register themselves via agent_registry
347342
"""
348343

src/backend/v4/magentic_agents/foundry_agent.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from agent_framework import (ChatAgent, ChatMessage, HostedCodeInterpreterTool,
77
Role)
88
from agent_framework_azure_ai import \
9-
AzureAIAgentClient # Provided by agent_framework
9+
AzureAIClient # Provided by agent_framework
1010
from azure.ai.projects.models import ConnectionType
1111
from common.config.app_config import config
1212
from common.database.database_base import DatabaseBase
@@ -111,7 +111,7 @@ async def _collect_tools(self) -> List:
111111
# -------------------------
112112
# Azure Search helper
113113
# -------------------------
114-
async def _create_azure_search_enabled_client(self, chatClient=None) -> Optional[AzureAIAgentClient]:
114+
async def _create_azure_search_enabled_client(self, chatClient=None) -> Optional[AzureAIClient]:
115115
"""
116116
Create a server-side Azure AI agent with Azure AI Search raw tool.
117117
@@ -123,7 +123,7 @@ async def _create_azure_search_enabled_client(self, chatClient=None) -> Optional
123123
124124
125125
Returns:
126-
AzureAIAgentClient | None
126+
AzureAIClient | None
127127
"""
128128
if chatClient:
129129
return chatClient
@@ -205,10 +205,10 @@ async def _create_azure_search_enabled_client(self, chatClient=None) -> Optional
205205
query_type,
206206
)
207207

208-
chat_client = AzureAIAgentClient(
208+
chat_client = AzureAIClient(
209209
project_client=self.project_client,
210210
agent_id=azure_agent.id,
211-
async_credential=self.creds,
211+
credential=self.creds,
212212
)
213213
return chat_client
214214
except Exception as ex:
@@ -301,8 +301,8 @@ async def invoke(self, prompt: str):
301301

302302
agent_saved = False
303303
async for update in self._agent.run_stream(messages):
304-
# Save agent ID only once on first update (agent ID won't change during streaming)
305-
if not agent_saved and self._agent.chat_client.agent_id:
304+
# Save agent ID only once on first update
305+
if not agent_saved:
306306
await self.save_database_team_agent()
307307
agent_saved = True
308308
yield update

src/backend/v4/magentic_agents/proxy_agent.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@
1616
from typing import Any, AsyncIterable
1717

1818
from agent_framework import (
19-
AgentRunResponse,
20-
AgentRunResponseUpdate,
19+
AgentResponse,
20+
AgentResponseUpdate,
2121
BaseAgent,
2222
ChatMessage,
2323
Role,
24-
TextContent,
25-
UsageContent,
24+
Content,
2625
UsageDetails,
2726
AgentThread,
2827
)
@@ -88,7 +87,7 @@ async def run(
8887
*,
8988
thread: AgentThread | None = None,
9089
**kwargs: Any,
91-
) -> AgentRunResponse:
90+
) -> AgentResponse:
9291
"""
9392
Get complete clarification response (non-streaming).
9493
@@ -98,7 +97,7 @@ async def run(
9897
kwargs: Additional keyword arguments
9998
10099
Returns:
101-
AgentRunResponse with the clarification
100+
AgentResponse with the clarification
102101
"""
103102
# Collect all streaming updates
104103
response_messages: list[ChatMessage] = []
@@ -113,7 +112,7 @@ async def run(
113112
)
114113
)
115114

116-
return AgentRunResponse(
115+
return AgentResponse(
117116
messages=response_messages,
118117
response_id=response_id,
119118
)
@@ -124,7 +123,7 @@ def run_stream(
124123
*,
125124
thread: AgentThread | None = None,
126125
**kwargs: Any,
127-
) -> AsyncIterable[AgentRunResponseUpdate]:
126+
) -> AsyncIterable[AgentResponseUpdate]:
128127
"""
129128
Stream clarification process with human interaction.
130129
@@ -143,7 +142,7 @@ async def _invoke_stream_internal(
143142
messages: str | ChatMessage | list[str] | list[ChatMessage] | None,
144143
thread: AgentThread | None,
145144
**kwargs: Any,
146-
) -> AsyncIterable[AgentRunResponseUpdate]:
145+
) -> AsyncIterable[AgentResponseUpdate]:
147146
"""
148147
Internal streaming implementation.
149148
@@ -205,9 +204,10 @@ async def _invoke_stream_internal(
205204
message_id = str(uuid.uuid4())
206205

207206
# Yield final assistant text update with explicit text content
208-
text_update = AgentRunResponseUpdate(
207+
# New API: use Content.from_text() or pass text directly to AgentResponseUpdate
208+
text_update = AgentResponseUpdate(
209209
role=Role.ASSISTANT,
210-
contents=[TextContent(text=synthetic_reply)],
210+
text=synthetic_reply, # New API accepts text directly
211211
author_name=self.name,
212212
response_id=response_id,
213213
message_id=message_id,
@@ -218,10 +218,10 @@ async def _invoke_stream_internal(
218218

219219
# Yield synthetic usage update for consistency
220220
# Use same message_id to indicate this is part of the same message
221-
usage_update = AgentRunResponseUpdate(
221+
usage_update = AgentResponseUpdate(
222222
role=Role.ASSISTANT,
223223
contents=[
224-
UsageContent(
224+
Content.from_usage(
225225
UsageDetails(
226226
input_token_count=len(message_text.split()),
227227
output_token_count=len(synthetic_reply.split()),

src/backend/v4/orchestration/human_approval_manager.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,12 @@ class HumanApprovalMagenticManager(StandardMagenticManager):
3434
magentic_plan: Optional[MPlan] = None
3535
current_user_id: str # populated in __init__
3636

37-
def __init__(self, user_id: str, *args, **kwargs):
37+
def __init__(self, user_id: str, agent, *args, **kwargs):
3838
"""
3939
Initialize the HumanApprovalMagenticManager.
4040
Args:
4141
user_id: ID of the user to associate with this orchestration instance.
42+
agent: The manager ChatAgent for orchestration (required by new API).
4243
*args: Additional positional arguments for the parent StandardMagenticManager.
4344
**kwargs: Additional keyword arguments for the parent StandardMagenticManager.
4445
"""
@@ -76,7 +77,8 @@ def __init__(self, user_id: str, *args, **kwargs):
7677
kwargs["final_answer_prompt"] = ORCHESTRATOR_FINAL_ANSWER_PROMPT + final_append
7778

7879
self.current_user_id = user_id
79-
super().__init__(*args, **kwargs)
80+
# New API: StandardMagenticManager takes agent as first positional argument
81+
super().__init__(agent, *args, **kwargs)
8082

8183
async def plan(self, magentic_context: MagenticContext) -> Any:
8284
"""

0 commit comments

Comments
 (0)