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
5 changes: 3 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
"name": "Multi Agent Custom Automation Engine Solution Accelerator",
"image": "mcr.microsoft.com/devcontainers/python:3.11-bullseye",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/docker-in-docker:2": {"version": "latest"},
"ghcr.io/azure/azure-dev/azd:latest": {},
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {}
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {"shellautocompletion": true,
"version": "latest"}
},
"customizations": {
"vscode": {
Expand Down
38 changes: 19 additions & 19 deletions .devcontainer/setupEnv.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
#!/bin/bash

cd ./src/backend
uv add -r requirements.txt

cd ../frontend
uv add -r requirements.txt

cd ..


#!/bin/sh

echo "Pull latest code for the current branch"
git fetch
git pull

set -e

echo "Setting up Backend..."
cd ./src/backend
uv sync --frozen
cd ../../

echo "Setting up Frontend..."
cd ./src/frontend
npm install
pip install -r requirements.txt
cd ../../

# pip install --upgrade pip


# (cd ./src/frontend; pip install -r requirements.txt)


# (cd ./src/backend; pip install -r requirements.txt)

echo "Setting up MCP..."
cd ./src/mcp_server
uv sync --frozen
cd ../../

echo "Setup complete! 🎉"
2 changes: 1 addition & 1 deletion data/agent_teams/marketing.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"use_reasoning": false,
"index_name": "",
"index_foundry_name": "",
"coding_tools": true
"coding_tools": false
},
{
"input_key": "",
Expand Down
2 changes: 1 addition & 1 deletion data/agent_teams/retail.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"use_reasoning": false,
"index_name": "macae-index",
"index_foundry_name": "",
"coding_tools": true
"coding_tools": false
},
{
"input_key": "",
Expand Down
110 changes: 76 additions & 34 deletions src/backend/v3/magentic_agents/foundry_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import asyncio
import logging
from typing import List, Optional
from typing import Awaitable, List, Optional

from azure.ai.agents.models import (AzureAISearchTool, BingGroundingTool,
CodeInterpreterToolDefinition)
from semantic_kernel.agents import AzureAIAgent # pylint: disable=E0611
from semantic_kernel.agents import Agent, AzureAIAgent # pylint: disable=E0611
from v3.magentic_agents.common.lifecycle import AzureAgentBase
from v3.magentic_agents.models.agent_models import MCPConfig, SearchConfig

Expand Down Expand Up @@ -43,6 +43,7 @@ def __init__(self, agent_name: str,
if self.model_deployment_name in ["o3", "o4-mini"]:
raise ValueError("The current version of Foundry agents do not support reasoning models.")

# Uncomment to enable bing grounding capabilities (requires Bing connection in Foundry and uncommenting other code)
# async def _make_bing_tool(self) -> Optional[BingGroundingTool]:
# """Create Bing search tool for web search."""
# if not all([self.client, self.bing.connection_name]):
Expand Down Expand Up @@ -119,19 +120,24 @@ async def _collect_tools_and_resources(self) -> tuple[List, dict]:
return tools, tool_resources

async def _after_open(self) -> None:

# Collect all tools
tools, tool_resources = await self._collect_tools_and_resources()

# Create agent definition with all tools
definition = await self.client.agents.create_agent(
model=self.model_deployment_name,
name=self.agent_name,
description=self.agent_description,
instructions=self.agent_instructions,
tools=tools,
tool_resources=tool_resources
)
"""Initialize the AzureAIAgent with the collected tools and MCP plugin."""

# Try to get existing agent definition from Foundry
definition = await self._get_azure_ai_agent_definition(self.agent_name)
# If not found in Foundry, create a new one
if definition is None:
# Collect all tools
tools, tool_resources = await self._collect_tools_and_resources()

# Create agent definition with all tools
definition = await self.client.agents.create_agent(
model=self.model_deployment_name,
name=self.agent_name,
description=self.agent_description,
instructions=self.agent_instructions,
tools=tools,
tool_resources=tool_resources
)

# Add MCP plugins if available
plugins = [self.mcp_plugin] if self.mcp_plugin else []
Expand All @@ -146,25 +152,25 @@ async def _after_open(self) -> None:
self.logger.error("Failed to create AzureAIAgent: %s", ex)
raise

# After self._agent creation in _after_open:
# Diagnostics
try:
tool_names = [t.get("function", {}).get("name") for t in (definition.tools or []) if isinstance(t, dict)]
self.logger.info(
"Foundry agent '%s' initialized. Azure tools: %s | MCP plugin: %s",
self.agent_name,
tool_names,
getattr(self.mcp_plugin, 'name', None)
)
if not tool_names and not plugins:
self.logger.warning(
"Foundry agent '%s' has no Azure tool definitions and no MCP plugin. "
"Subsequent tool calls may fail.", self.agent_name
)
except Exception as diag_ex:
self.logger.warning("Diagnostics collection failed: %s", diag_ex)

self.logger.info("%s initialized with %d tools and %d plugins", self.agent_name, len(tools), len(plugins))
# # After self._agent creation in _after_open:
# # Diagnostics
# try:
# tool_names = [t.get("function", {}).get("name") for t in (definition.tools or []) if isinstance(t, dict)]
# self.logger.info(
# "Foundry agent '%s' initialized. Azure tools: %s | MCP plugin: %s",
# self.agent_name,
# tool_names,
# getattr(self.mcp_plugin, 'name', None)
# )
# if not tool_names and not plugins:
# self.logger.warning(
# "Foundry agent '%s' has no Azure tool definitions and no MCP plugin. "
# "Subsequent tool calls may fail.", self.agent_name
# )
# except Exception as diag_ex:
# self.logger.warning("Diagnostics collection failed: %s", diag_ex)

# self.logger.info("%s initialized with %d tools and %d plugins", self.agent_name, len(tools), len(plugins))

async def fetch_run_details(self, thread_id: str, run_id: str):
"""Fetch and log run details after a failure."""
Expand All @@ -180,6 +186,42 @@ async def fetch_run_details(self, thread_id: str, run_id: str):
except Exception as ex:
self.logger.error("Could not fetch run details: %s", ex)

async def _get_azure_ai_agent_definition(self, agent_name: str)-> Awaitable[Agent | None]:
"""
Gets an Azure AI Agent with the specified name and instructions using AIProjectClient if it is already created.
"""
# # First try to get an existing agent with this name as assistant_id
try:
agent_id = None
agent_list = self.client.agents.list_agents()
async for agent in agent_list:
if agent.name == agent_name:
agent_id = agent.id
break
# If the agent already exists, we can use it directly
# Get the existing agent definition
if agent_id is not None:
logging.info(f"Agent with ID {agent_id} exists.")

existing_definition = await self.client.agents.get_agent(agent_id)

return existing_definition
else:
return None
except Exception as e:
# The Azure AI Projects SDK throws an exception when the agent doesn't exist
# (not returning None), so we catch it and proceed to create a new agent
if "ResourceNotFound" in str(e) or "404" in str(e):
logging.info(
f"Agent with ID {agent_name} not found. Will create a new one."
)
else:
# Log unexpected errors but still try to create a new agent
logging.warning(
f"Unexpected error while retrieving agent {agent_name}: {str(e)}. Attempting to create new agent."
)


async def create_foundry_agent(agent_name:str,
agent_description:str,
agent_instructions:str,
Expand Down
3 changes: 1 addition & 2 deletions src/backend/v3/orchestration/human_approval_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ def __init__(self, *args, **kwargs):
"""

final_append = """
The final answer should not include any offers of further conversation or assistance. The application will not all further interaction with the user.
The final answer should be a complete and final response to the user's original request.
DO NOT EVER OFFER TO HELP FURTHER IN THE FINAL ANSWER! Just provide the final answer and end with a polite closing.
"""

# kwargs["task_ledger_facts_prompt"] = ORCHESTRATOR_TASK_LEDGER_FACTS_PROMPT + facts_append
Expand Down
Loading