Description
Summary
When sharing a single AzureAIClient instance across multiple ChatAgent instances in a workflow, the agent_name from the first agent persists and is used for all subsequent agents. This causes tools to not be called correctly by subsequent agents in the workflow chain.
Affected Package
- Package:
agent-framework-azure-ai
- Version:
1.0.0b260123 (and likely earlier versions)
- File:
agent_framework_azure_ai/_chat_client.py
- Lines: 1233-1245
Related GitHub Issues
No existing issues found for this specific bug. Related issues reviewed:
Bug Description
The AzureAIChatClient._update_agent_name_and_description() method only sets the agent_name if one doesn't already exist:
# File: agent_framework_azure_ai/_chat_client.py, lines 1233-1245
def _update_agent_name_and_description(self, agent_name: str | None, description: str | None) -> None:
"""Update the agent name in the chat client.
Args:
agent_name: The new name for the agent.
description: The new description for the agent.
"""
# This is a no-op in the base class, but can be overridden by subclasses
# to update the agent name in the client.
if agent_name and not self.agent_name: # <-- BUG: Only sets if not already set
self.agent_name = agent_name
if description and not self.agent_description:
self.agent_description = description
This method is called by ChatAgent.__init__() in agent_framework/_agents.py:
# File: agent_framework/_agents.py, lines 732-742
def _update_agent_name_and_description(self) -> None:
"""Update the agent name in the chat client."""
if hasattr(self.chat_client, "_update_agent_name_and_description") and callable(
self.chat_client._update_agent_name_and_description
):
self.chat_client._update_agent_name_and_description(self.name, self.description)
Root Cause
When multiple ChatAgent instances share the same AzureAIClient:
- The first
ChatAgent sets client.agent_name to its name
- Subsequent
ChatAgent instances cannot override this value
- All API requests use the first agent's name in the
extra_json payload
Impact
- Severity: High - Breaks tool calling in multi-executor workflows
- Symptom: MCP tools are connected but never invoked
- Model behavior: Instead of calling tools, the model outputs text "simulating" tool calls
When the wrong agent name is sent in the API request, the model may not recognize the available tools, causing it to generate text describing what it would do rather than actually invoking the tools.
Reproduction
Minimal Reproduction Code
"""Minimal reproduction of AzureAIClient agent_name persistence bug."""
import asyncio
import os
from agent_framework import ChatAgent, ChatMessage
from agent_framework_azure_ai import AzureAIClient
from azure.identity.aio import DefaultAzureCredential
async def main():
# Create a SHARED client (common pattern in workflows)
shared_client = AzureAIClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model_deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
# First agent sets the agent_name
async with ChatAgent(
chat_client=shared_client,
name="first-agent", # This name PERSISTS
instructions="You are the first agent.",
) as agent1:
await agent1.run("Hello")
# Second agent SHOULD use "second-agent" but uses "first-agent"
async with ChatAgent(
chat_client=shared_client,
name="second-agent", # This name is IGNORED!
instructions="You are the second agent.",
) as agent2:
# Enable debug logging to see the issue
# The API request will show: 'agent': {'name': 'first-agent'}
# instead of: 'agent': {'name': 'second-agent'}
await agent2.run("Hello")
# Verify the bug
print(f"Client agent_name: {shared_client.agent_name}") # Prints: first-agent
# Expected: second-agent (or dynamically updated per agent)
if __name__ == "__main__":
asyncio.run(main())
Workflow Reproduction (Real-World Scenario)
"""Real-world reproduction: Multi-executor workflow with MCP tools."""
import asyncio
import os
from agent_framework import (
ChatAgent,
ChatMessage,
Executor,
Workflow,
WorkflowBuilder,
WorkflowContext,
handler,
)
from agent_framework._mcp import MCPStreamableHTTPTool
from agent_framework_azure_ai import AzureAIClient
from azure.identity.aio import DefaultAzureCredential
class FirstExecutor(Executor):
"""First executor - sets the agent name in shared client."""
def __init__(self, chat_client: AzureAIClient):
self.chat_client = chat_client
super().__init__(id="first_executor")
@handler
async def handle(self, message: ChatMessage, ctx: WorkflowContext[dict]) -> None:
async with ChatAgent(
chat_client=self.chat_client,
name="first-agent", # This name persists in the client
instructions="Parse the input message.",
) as agent:
response = await agent.run(message)
await ctx.send_message({"parsed": response.text})
class SecondExecutorWithTools(Executor):
"""Second executor - tools won't work due to wrong agent name."""
def __init__(self, chat_client: AzureAIClient):
self.chat_client = chat_client
super().__init__(id="second_executor")
@handler
async def handle(self, payload: dict, ctx: WorkflowContext[dict]) -> None:
mcp_tool = MCPStreamableHTTPTool(
name="my-mcp-server",
url="http://localhost:8000/mcp",
)
async with mcp_tool:
async with ChatAgent(
chat_client=self.chat_client,
name="second-agent-with-tools", # IGNORED - uses "first-agent"
instructions="Use the available tools to look up data.",
) as agent:
# BUG: API request sends 'agent': {'name': 'first-agent'}
# Model may not recognize tools with wrong agent context
response = await agent.run(
"Look up invoice INV-001",
tools=mcp_tool,
)
await ctx.send_message({"result": response.text})
def build_workflow() -> Workflow:
"""Build workflow with SHARED client (causes bug)."""
# Single shared client instance
shared_client = AzureAIClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model_deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
first = FirstExecutor(chat_client=shared_client)
second = SecondExecutorWithTools(chat_client=shared_client)
return (
WorkflowBuilder(name="buggy-workflow", description="Demonstrates agent_name bug")
.set_start_executor(first)
.add_edge(first, second)
.build()
)
async def main():
workflow = build_workflow()
result = await workflow.run(ChatMessage(role="user", text="Test input"))
print(result)
if __name__ == "__main__":
asyncio.run(main())
Debug Output Showing the Bug
When running with debug logging enabled:
import logging
logging.basicConfig(level=logging.DEBUG)
First executor request (correct):
DEBUG:openai._base_client:Request options: {
...
'extra_json': {'agent': {'name': 'first-agent', ...}}
}
Second executor request (BUG - wrong agent name):
DEBUG:openai._base_client:Request options: {
...
'extra_json': {'agent': {'name': 'first-agent', ...}} # Should be 'second-agent-with-tools'!
}
Expected Behavior
Each ChatAgent should use its own name in API requests, regardless of whether the underlying AzureAIClient is shared.
Options:
- Always update: Change the condition from
if agent_name and not self.agent_name to always update when a new agent name is provided
- Per-request agent name: Pass the agent name per-request rather than storing it on the client
- Document the limitation: If sharing clients is unsupported, document that each executor needs its own client instance
Workaround
Use a factory function to create separate AzureAIClient instances for each executor:
"""Workaround: Use factory function instead of shared client."""
from typing import Callable
from agent_framework_azure_ai import AzureAIClient
from azure.identity.aio import DefaultAzureCredential
def build_workflow_with_factory() -> Workflow:
"""Build workflow using factory function (WORKAROUND)."""
# Factory creates NEW client for each executor
def create_client() -> AzureAIClient:
return AzureAIClient(
project_endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"],
model_deployment_name=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"],
credential=DefaultAzureCredential(),
)
# Each executor gets its own client instance
first = FirstExecutor(chat_client=create_client())
second = SecondExecutorWithTools(chat_client=create_client())
return (
WorkflowBuilder(name="working-workflow", description="Uses factory workaround")
.set_start_executor(first)
.add_edge(first, second)
.build()
)
Suggested Fix
Modify _update_agent_name_and_description to always update when a new name is provided:
# In agent_framework_azure_ai/_chat_client.py
def _update_agent_name_and_description(self, agent_name: str | None, description: str | None) -> None:
"""Update the agent name in the chat client.
Args:
agent_name: The new name for the agent.
description: The new description for the agent.
"""
# Always update if a new value is provided (FIX)
if agent_name:
self.agent_name = agent_name
if description:
self.agent_description = description
Or, pass the agent name per-request in the API call rather than storing it on the client.
Environment
- Python: 3.12
- agent-framework-core: 1.0.0b260123
- agent-framework-azure-ai: 1.0.0b260123
- Azure AI Foundry: v2 (project_endpoint)
- Transport: Responses API
Additional Context
This bug was discovered while building a multi-executor workflow for AP vendor email processing. The workflow has 4 executors:
RequestInterpreter - parses email
DataLookupAgent - queries MCP server for vendor/invoice data (tools not called)
ExtractionClassifier - classifies inquiry
ResponseGenerator - drafts response
The DataLookupAgent executor has MCP tools connected and available, but the model never invokes them. Instead, it outputs text like "Calling lookup_invoice_tool for invoice..." without actually making the tool call.
Debug logging revealed that all executors were sending 'agent': {'name': 'request-interpreter'} in their API requests, even though DataLookupAgent specifies name="data-lookup-agent" in its ChatAgent constructor.
The workaround (using a factory function to create separate clients) resolved the issue and tools are now called correctly.
Code Sample
Error Messages / Stack Traces
Package Versions
1.0.0b260123
Python Version
3.12
Additional Context
No response
Description
Summary
When sharing a single
AzureAIClientinstance across multipleChatAgentinstances in a workflow, theagent_namefrom the first agent persists and is used for all subsequent agents. This causes tools to not be called correctly by subsequent agents in the workflow chain.Affected Package
agent-framework-azure-ai1.0.0b260123(and likely earlier versions)agent_framework_azure_ai/_chat_client.pyRelated GitHub Issues
No existing issues found for this specific bug. Related issues reviewed:
Bug Description
The
AzureAIChatClient._update_agent_name_and_description()method only sets theagent_nameif one doesn't already exist:This method is called by
ChatAgent.__init__()inagent_framework/_agents.py:Root Cause
When multiple
ChatAgentinstances share the sameAzureAIClient:ChatAgentsetsclient.agent_nameto its nameChatAgentinstances cannot override this valueextra_jsonpayloadImpact
When the wrong agent name is sent in the API request, the model may not recognize the available tools, causing it to generate text describing what it would do rather than actually invoking the tools.
Reproduction
Minimal Reproduction Code
Workflow Reproduction (Real-World Scenario)
Debug Output Showing the Bug
When running with debug logging enabled:
First executor request (correct):
Second executor request (BUG - wrong agent name):
Expected Behavior
Each
ChatAgentshould use its ownnamein API requests, regardless of whether the underlyingAzureAIClientis shared.Options:
if agent_name and not self.agent_nameto always update when a new agent name is providedWorkaround
Use a factory function to create separate
AzureAIClientinstances for each executor:Suggested Fix
Modify
_update_agent_name_and_descriptionto always update when a new name is provided:Or, pass the agent name per-request in the API call rather than storing it on the client.
Environment
Additional Context
This bug was discovered while building a multi-executor workflow for AP vendor email processing. The workflow has 4 executors:
RequestInterpreter- parses emailDataLookupAgent- queries MCP server for vendor/invoice data (tools not called)ExtractionClassifier- classifies inquiryResponseGenerator- drafts responseThe
DataLookupAgentexecutor has MCP tools connected and available, but the model never invokes them. Instead, it outputs text like "Calling lookup_invoice_tool for invoice..." without actually making the tool call.Debug logging revealed that all executors were sending
'agent': {'name': 'request-interpreter'}in their API requests, even thoughDataLookupAgentspecifiesname="data-lookup-agent"in itsChatAgentconstructor.The workaround (using a factory function to create separate clients) resolved the issue and tools are now called correctly.
Code Sample
Error Messages / Stack Traces
Package Versions
1.0.0b260123
Python Version
3.12
Additional Context
No response