Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ requires-python = ">=3.11"
dependencies = [
"azure-ai-evaluation==1.11.0",
"azure-ai-inference==1.0.0b9",
"azure-ai-projects==2.0.0b3",
"azure-ai-projects==2.0.0",
"azure-cosmos==4.9.0",
"azure-identity==1.24.0",
"azure-monitor-events-extension==0.1.0",
Expand All @@ -32,8 +32,9 @@ dependencies = [
"mcp==1.26.0",
"werkzeug==3.1.5",
"azure-core==1.38.0",
"agent-framework-azure-ai==1.0.0b260130",
"agent-framework-core==1.0.0b260130",
"agent-framework-azure-ai==1.0.0rc4",
"agent-framework-core==1.0.0rc4",
"agent-framework-orchestrations==1.0.0b260311",
"urllib3==2.6.3",
"protobuf==5.29.6",
"cryptography==46.0.5",
Expand Down
44 changes: 30 additions & 14 deletions src/backend/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions src/backend/v4/callbacks/response_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import re
from typing import Any

from agent_framework import ChatMessage
from agent_framework import Message

from v4.config.settings import connection_config
from v4.models.messages import (
Expand Down Expand Up @@ -64,22 +64,22 @@ def _extract_tool_calls_from_contents(contents: list[Any]) -> list[AgentToolCall

def agent_response_callback(
agent_id: str,
message: ChatMessage,
message: Message,
user_id: str | None = None,
) -> None:
"""
Final (non-streaming) agent response callback using agent_framework ChatMessage.
Final (non-streaming) agent response callback using agent_framework Message.
"""
agent_name = getattr(message, "author_name", None) or agent_id or "Unknown Agent"
role = getattr(message, "role", "assistant")

# FIX: Properly extract text from ChatMessage
# ChatMessage has a .text property that concatenates all TextContent items
# FIX: Properly extract text from Message
# Message has a .text property that concatenates all TextContent items
text = ""
if isinstance(message, ChatMessage):
if isinstance(message, Message):
text = message.text # Use the property directly
else:
# Fallback for non-ChatMessage objects
# Fallback for non-Message objects
text = str(getattr(message, "text", ""))

text = clean_citations(text or "")
Expand Down Expand Up @@ -125,8 +125,8 @@ async def streaming_agent_response_callback(
# If text is None, don't fall back to str(update) as that would show object repr
# Just skip if there's no actual text content
if chunk_text is None:
# Check if update is a ChatMessage
if isinstance(update, ChatMessage):
# Check if update is a Message
if isinstance(update, Message):
chunk_text = update.text or ""
elif hasattr(update, "content"):
chunk_text = str(update.content) if update.content else ""
Expand Down
11 changes: 5 additions & 6 deletions src/backend/v4/magentic_agents/common/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from typing import Any, Optional

from agent_framework import (
ChatAgent,
HostedMCPTool,
Agent,
MCPStreamableHTTPTool,
)

Expand Down Expand Up @@ -46,8 +45,8 @@ def __init__(
) -> None:
self._stack: AsyncExitStack | None = None
self.mcp_cfg: MCPConfig | None = mcp
self.mcp_tool: HostedMCPTool | None = None
self._agent: ChatAgent | None = None
self.mcp_tool: MCPStreamableHTTPTool | None = None
self._agent: Agent | None = None
self.team_service: TeamService | None = team_service
self.team_config: TeamConfiguration | None = team_config
self.client: Optional[AgentsClient] = None
Expand Down Expand Up @@ -155,9 +154,9 @@ def get_chat_client(self) -> AzureAIClient:
"""
if (
self._agent
and self._agent.chat_client
and self._agent.client
):
return self._agent.chat_client # type: ignore
return self._agent.client # type: ignore
chat_client = AzureAIClient(
project_endpoint=self.project_endpoint,
agent_name=self.agent_name,
Expand Down
57 changes: 27 additions & 30 deletions src/backend/v4/magentic_agents/foundry_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import logging
from typing import List, Optional

from agent_framework import (ChatAgent, ChatMessage, HostedCodeInterpreterTool,
Role)
from agent_framework import (Agent, Message, ChatOptions)
from agent_framework_azure_ai import \
AzureAIClient # Provided by agent_framework
from azure.ai.projects.models import (
PromptAgentDefinition,
AzureAISearchAgentTool,
AzureAISearchTool,
AzureAISearchToolResource,
AISearchIndexResource,
)
Expand Down Expand Up @@ -92,17 +91,13 @@ def _is_azure_search_requested(self) -> bool:
return False

async def _collect_tools(self) -> List:
"""Collect tool definitions for ChatAgent (MCP path only)."""
"""Collect tool definitions for Agent (MCP path only)."""
tools: List = []

# Code Interpreter (only in MCP path per incompatibility note)
# Code Interpreter is now handled server-side via AzureAIClient agent definition.
# HostedCodeInterpreterTool was removed in rc4.
if self.enable_code_interpreter:
try:
code_tool = HostedCodeInterpreterTool()
tools.append(code_tool)
self.logger.info("Added Code Interpreter tool.")
except Exception as ie:
self.logger.error("Code Interpreter tool creation failed: %s", ie)
self.logger.info("Code Interpreter requested — handled server-side by AzureAIClient.")

# MCP Tool (from base class)
if self.mcp_tool:
Expand All @@ -121,7 +116,7 @@ async def _create_azure_search_enabled_client(self) -> Optional[AzureAIClient]:

This uses the AIProjectClient.agents.create_version() approach with:
- PromptAgentDefinition for agent configuration
- AzureAISearchAgentTool with AzureAISearchToolResource for search capability
- AzureAISearchTool with AzureAISearchToolResource for search capability
- AISearchIndexResource for index configuration with project_connection_id

Requirements:
Expand Down Expand Up @@ -167,7 +162,7 @@ async def _create_azure_search_enabled_client(self) -> Optional[AzureAIClient]:
top_k,
)

# Create agent using create_version with PromptAgentDefinition and AzureAISearchAgentTool
# Create agent using create_version with PromptAgentDefinition and AzureAISearchTool
# This approach matches the Knowledge Mining Solution Accelerator pattern
try:
enhanced_instructions = (
Expand All @@ -181,7 +176,7 @@ async def _create_azure_search_enabled_client(self) -> Optional[AzureAIClient]:
model=self.model_deployment_name,
instructions=enhanced_instructions,
tools=[
AzureAISearchAgentTool(
AzureAISearchTool(
azure_ai_search=AzureAISearchToolResource(
indexes=[
AISearchIndexResource(
Expand Down Expand Up @@ -253,37 +248,39 @@ async def _after_open(self) -> None:
)

# In Azure Search raw tool path, tools/tool_choice are handled server-side.
self._agent = ChatAgent(
self._agent = Agent(
id=self.get_agent_id(),
chat_client=chat_client,
client=chat_client,
instructions=self.agent_instructions,
name=self.agent_name,
description=self.agent_description,
tool_choice="required", # Force usage
temperature=temp,
model_id=self.model_deployment_name,
default_options={"store": False}, # Client-managed conversation to avoid stale tool call IDs across rounds
default_options=ChatOptions(
store=False,
tool_choice="required",
temperature=temp,
),
)
else:
# MCP path (also used by RAI agent which has no tools)
self.logger.info("Initializing agent in MCP mode.")
tools = await self._collect_tools()
self._agent = ChatAgent(
self._agent = Agent(
id=self.get_agent_id(),
chat_client=self.get_chat_client(),
client=self.get_chat_client(),
instructions=self.agent_instructions,
name=self.agent_name,
description=self.agent_description,
tools=tools if tools else None,
tool_choice="auto" if tools else "none",
temperature=temp,
model_id=self.model_deployment_name,
default_options={"store": False}, # Client-managed conversation to avoid stale tool call IDs across rounds
default_options=ChatOptions(
store=False,
tool_choice="auto" if tools else "none",
temperature=temp,
),
)
self.logger.info("Initialized ChatAgent '%s'", self.agent_name)
self.logger.info("Initialized Agent '%s'", self.agent_name)

except Exception as ex:
self.logger.error("Failed to initialize ChatAgent: %s", ex)
self.logger.error("Failed to initialize Agent: %s", ex)
raise

# Register agent globally
Expand All @@ -305,9 +302,9 @@ async def invoke(self, prompt: str):
if not self._agent:
raise RuntimeError("Agent not initialized; call open() first.")

messages = [ChatMessage(role=Role.USER, text=prompt)]
messages = [Message(role="user", text=prompt)]

async for update in self._agent.run_stream(messages):
async for update in self._agent.run(messages, stream=True):
yield update

# -------------------------
Expand Down
Loading
Loading