mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Included existing agent definition in requests to Azure AI (#1285)
* Fixed instructions handling for existing Azure AI agents * Updated tool handling for existing agents * Small update * Added more comments
This commit is contained in:
committed by
GitHub
Unverified
parent
c341ee7ed2
commit
7e891fab39
@@ -42,6 +42,7 @@ from agent_framework._pydantic import AFBaseSettings
|
||||
from agent_framework.exceptions import ServiceInitializationError, ServiceResponseException
|
||||
from agent_framework.observability import use_observability
|
||||
from azure.ai.agents.models import (
|
||||
Agent,
|
||||
AgentsNamedToolChoice,
|
||||
AgentsNamedToolChoiceType,
|
||||
AgentsToolChoiceOptionMode,
|
||||
@@ -55,6 +56,7 @@ from azure.ai.agents.models import (
|
||||
CodeInterpreterToolDefinition,
|
||||
FileSearchTool,
|
||||
FunctionName,
|
||||
FunctionToolDefinition,
|
||||
ListSortOrder,
|
||||
McpTool,
|
||||
MessageDeltaChunk,
|
||||
@@ -251,6 +253,7 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
self.thread_id = thread_id
|
||||
self._should_delete_agent = False # Track whether we should delete the agent
|
||||
self._should_close_client = should_close_client # Track whether we should close client connection
|
||||
self._agent_definition: Agent | None = None # Cached definition for existing agent
|
||||
|
||||
async def setup_azure_ai_observability(self, enable_sensitive_data: bool | None = None) -> None:
|
||||
"""Use this method to setup tracing in your Azure AI Project.
|
||||
@@ -375,6 +378,7 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
args["response_format"] = run_options["response_format"]
|
||||
created_agent = await self.project_client.agents.create_agent(**args)
|
||||
self.agent_id = str(created_agent.id)
|
||||
self._agent_definition = created_agent
|
||||
self._should_delete_agent = True
|
||||
|
||||
return self.agent_id
|
||||
@@ -669,6 +673,26 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
self.agent_id = None
|
||||
self._should_delete_agent = False
|
||||
|
||||
async def _load_agent_definition_if_needed(self) -> Agent | None:
|
||||
"""Load and cache agent details if not already loaded."""
|
||||
if self._agent_definition is None and self.agent_id is not None:
|
||||
self._agent_definition = await self.project_client.agents.get_agent(self.agent_id)
|
||||
return self._agent_definition
|
||||
|
||||
def _prepare_tool_choice(self, chat_options: ChatOptions) -> None:
|
||||
"""Prepare the tools and tool choice for the chat options.
|
||||
|
||||
Args:
|
||||
chat_options: The chat options to prepare.
|
||||
"""
|
||||
chat_tool_mode = chat_options.tool_choice
|
||||
if chat_tool_mode is None or chat_tool_mode == ToolMode.NONE or chat_tool_mode == "none":
|
||||
chat_options.tools = None
|
||||
chat_options.tool_choice = ToolMode.NONE.mode
|
||||
return
|
||||
|
||||
chat_options.tool_choice = chat_tool_mode.mode if isinstance(chat_tool_mode, ToolMode) else chat_tool_mode
|
||||
|
||||
async def _create_run_options(
|
||||
self,
|
||||
messages: MutableSequence[ChatMessage],
|
||||
@@ -677,6 +701,8 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
) -> tuple[dict[str, Any], list[FunctionResultContent | FunctionApprovalResponseContent] | None]:
|
||||
run_options: dict[str, Any] = {**kwargs}
|
||||
|
||||
agent_definition = await self._load_agent_definition_if_needed()
|
||||
|
||||
if chat_options is not None:
|
||||
run_options["max_completion_tokens"] = chat_options.max_tokens
|
||||
if chat_options.model_id is not None:
|
||||
@@ -687,11 +713,21 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
run_options["temperature"] = chat_options.temperature
|
||||
run_options["parallel_tool_calls"] = chat_options.allow_multiple_tool_calls
|
||||
|
||||
tool_definitions: list[ToolDefinition | dict[str, Any]] = []
|
||||
|
||||
# Add tools from existing agent
|
||||
if agent_definition is not None:
|
||||
# Don't include function tools, since they will be passed through chat_options.tools
|
||||
agent_tools = [tool for tool in agent_definition.tools if not isinstance(tool, FunctionToolDefinition)]
|
||||
if agent_tools:
|
||||
tool_definitions.extend(agent_tools)
|
||||
if agent_definition.tool_resources:
|
||||
run_options["tool_resources"] = agent_definition.tool_resources
|
||||
|
||||
if chat_options.tool_choice is not None:
|
||||
if chat_options.tool_choice != "none" and chat_options.tools:
|
||||
tool_definitions = await self._prep_tools(chat_options.tools, run_options)
|
||||
if tool_definitions:
|
||||
run_options["tools"] = tool_definitions
|
||||
# Add run tools
|
||||
tool_definitions.extend(await self._prep_tools(chat_options.tools, run_options))
|
||||
|
||||
# Handle MCP tool resources for approval mode
|
||||
mcp_tools = [tool for tool in chat_options.tools if isinstance(tool, HostedMCPTool)]
|
||||
@@ -740,6 +776,9 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
function=FunctionName(name=chat_options.tool_choice.required_function_name),
|
||||
)
|
||||
|
||||
if tool_definitions:
|
||||
run_options["tools"] = tool_definitions
|
||||
|
||||
if chat_options.response_format is not None:
|
||||
run_options["response_format"] = ResponseFormatJsonSchemaType(
|
||||
json_schema=ResponseFormatJsonSchema(
|
||||
@@ -748,7 +787,7 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
)
|
||||
)
|
||||
|
||||
instructions: list[str] = [chat_options.instructions] if chat_options and chat_options.instructions else []
|
||||
instructions: list[str] = []
|
||||
required_action_results: list[FunctionResultContent | FunctionApprovalResponseContent] | None = None
|
||||
|
||||
additional_messages: list[ThreadMessageOptions] | None = None
|
||||
@@ -790,6 +829,10 @@ class AzureAIAgentClient(BaseChatClient):
|
||||
if additional_messages is not None:
|
||||
run_options["additional_messages"] = additional_messages
|
||||
|
||||
# Add instruction from existing agent at the beginning
|
||||
if agent_definition is not None and agent_definition.instructions:
|
||||
instructions.insert(0, agent_definition.instructions)
|
||||
|
||||
if len(instructions) > 0:
|
||||
run_options["instructions"] = "".join(instructions)
|
||||
|
||||
|
||||
@@ -81,11 +81,12 @@ def create_test_azure_ai_chat_client(
|
||||
client.project_client = mock_ai_project_client
|
||||
client.credential = None
|
||||
client.agent_id = agent_id
|
||||
client.agent_name = None
|
||||
client.agent_name = agent_name
|
||||
client.model_id = azure_ai_settings.model_deployment_name
|
||||
client.thread_id = thread_id
|
||||
client._should_delete_agent = should_delete_agent
|
||||
client._should_close_client = False
|
||||
client._should_delete_agent = should_delete_agent # type: ignore
|
||||
client._should_close_client = False # type: ignore
|
||||
client._agent_definition = None # type: ignore
|
||||
client.additional_properties = {}
|
||||
client.middleware = None
|
||||
|
||||
@@ -297,6 +298,9 @@ async def test_azure_ai_chat_client_tool_results_without_thread_error_via_public
|
||||
"""Test that tool results without thread ID raise error through public API."""
|
||||
chat_client = create_test_azure_ai_chat_client(mock_ai_project_client, agent_id="test-agent")
|
||||
|
||||
# Mock get_agent
|
||||
mock_ai_project_client.agents.get_agent = AsyncMock(return_value=None)
|
||||
|
||||
# Create messages with tool results but no thread/conversation ID
|
||||
messages = [
|
||||
ChatMessage(role=Role.USER, text="Hello"),
|
||||
@@ -315,6 +319,9 @@ async def test_azure_ai_chat_client_thread_management_through_public_api(mock_ai
|
||||
"""Test thread creation and management through public API."""
|
||||
chat_client = create_test_azure_ai_chat_client(mock_ai_project_client, agent_id="test-agent")
|
||||
|
||||
# Mock get_agent to avoid the async error
|
||||
mock_ai_project_client.agents.get_agent = AsyncMock(return_value=None)
|
||||
|
||||
mock_thread = MagicMock()
|
||||
mock_thread.id = "new-thread-456"
|
||||
mock_ai_project_client.agents.threads.create = AsyncMock(return_value=mock_thread)
|
||||
@@ -451,6 +458,9 @@ async def test_azure_ai_chat_client_create_run_options_with_image_content(mock_a
|
||||
|
||||
chat_client = create_test_azure_ai_chat_client(mock_ai_project_client, agent_id="test-agent")
|
||||
|
||||
# Mock get_agent
|
||||
mock_ai_project_client.agents.get_agent = AsyncMock(return_value=None)
|
||||
|
||||
image_content = UriContent(uri="https://example.com/image.jpg", media_type="image/jpeg")
|
||||
messages = [ChatMessage(role=Role.USER, contents=[image_content])]
|
||||
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from random import randint
|
||||
from typing import Annotated
|
||||
|
||||
from agent_framework import ChatAgent
|
||||
from agent_framework.azure import AzureAIAgentClient
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
from pydantic import Field
|
||||
|
||||
"""
|
||||
Azure AI Agent with Existing Agent Example
|
||||
@@ -19,14 +16,6 @@ agent IDs, showing agent reuse patterns for production scenarios.
|
||||
"""
|
||||
|
||||
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
"""Get the weather for a given location."""
|
||||
conditions = ["sunny", "cloudy", "rainy", "stormy"]
|
||||
return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C."
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== Azure AI Chat Client with Existing Agent ===")
|
||||
|
||||
@@ -35,24 +24,33 @@ async def main() -> None:
|
||||
AzureCliCredential() as credential,
|
||||
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as client,
|
||||
):
|
||||
# Create an agent that will persist
|
||||
created_agent = await client.agents.create_agent(
|
||||
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], name="WeatherAgent"
|
||||
azure_ai_agent = await client.agents.create_agent(
|
||||
model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
|
||||
# Create remote agent with default instructions
|
||||
# These instructions will persist on created agent for every run.
|
||||
instructions="End each response with [END].",
|
||||
)
|
||||
|
||||
chat_client = AzureAIAgentClient(project_client=client, agent_id=azure_ai_agent.id)
|
||||
|
||||
try:
|
||||
async with ChatAgent(
|
||||
# passing in the client is optional here, so if you take the agent_id from the portal
|
||||
# you can use it directly without the two lines above.
|
||||
chat_client=AzureAIAgentClient(project_client=client, agent_id=created_agent.id),
|
||||
instructions="You are a helpful weather agent.",
|
||||
tools=get_weather,
|
||||
chat_client=chat_client,
|
||||
# Instructions here are applicable only to this ChatAgent instance
|
||||
# These instructions will be combined with instructions on existing remote agent.
|
||||
# The final instructions during the execution will look like:
|
||||
# "'End each response with [END]. Respond with 'Hello World' only'"
|
||||
instructions="Respond with 'Hello World' only",
|
||||
) as agent:
|
||||
result = await agent.run("What's the weather like in Tokyo?")
|
||||
print(f"Result: {result}\n")
|
||||
query = "How are you?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
# Based on local and remote instructions, the result will be
|
||||
# 'Hello World [END]'.
|
||||
print(f"Agent: {result}\n")
|
||||
finally:
|
||||
# Clean up the agent manually
|
||||
await client.agents.delete_agent(created_agent.id)
|
||||
await client.agents.delete_agent(azure_ai_agent.id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user