Forum Discussion
Issue with: Connected Agent Tool Forcing from an Orchestrator Agent
Hi Team,
I am trying to force tool selection for my Connected Agents from an Orchestrator Agent for my Multi-Agent Model. Not sure if that is possible
Apologies in advance for too much detail as I really need this to work! Please let me know if there is a flaw in my approach!
The main intention behind going towards Tool forcing was because with current set of instructions provided to my Orchestrator Agent, It was providing hallucinated responses from my Child Agents for each query.
I have an Orchestrator Agent which is connected to the following Child Agents (Each with detailed instructions)
- Child Agent 1 - Connects to SQL DB in Fabric to fetch information from Log tables.
- Child Agent 2 - Invokes OpenAPI Action tool for Azure Functions to run pipelines in Fabric.
I have provided details on 3 approaches.
Approach 1:
I have checked the MS docs "CONNECTED_AGENT" is a valid property for ToolChoiceType "https://learn.microsoft.com/en-us/python/api/azure-ai-agents/azure.ai.agents.models.agentsnamedtoolchoicetype?view=azure-python"
Installed the latest Python AI Agents SDK Beta version as it also supports "Connected Agents": https://pypi.org/project/azure-ai-agents/1.2.0b6/#create-an-agent-using-another-agents The following code is integrated into a streamlit UI code.
Python Code:
agents_client = AgentsClient(
endpoint=PROJECT_ENDPOINT,
credential=DefaultAzureCredential(
exclude_environment_credential=True,
exclude_managed_identity_credential=True
)
)
# -------------------------------------------------------------------
# UPDATE ORCHESTRATOR TOOLS (executed once)
# -------------------------------------------------------------------
fabric_tool = ConnectedAgentTool(
id=FABRIC_AGENT_ID,
name="Fabric_Agent",
description="Handles Fabric pipeline questions"
)
openapi_tool = ConnectedAgentTool(
id=OPENAPI_AGENT_ID,
name="Fabric_Pipeline_Trigger",
description="Handles OpenAPI pipeline triggers"
)
# Update orchestrator agent to include child agent tools
agents_client.update_agent(
agent_id=ORCH_AGENT_ID,
tools=[
fabric_tool.definitions[0],
openapi_tool.definitions[0]
],
instructions="""
You are the Master Orchestrator Agent.
Use:
- "Fabric_Agent" when the user's question includes:
"Ingestion", "Trigger", "source", "Connection"
- "Fabric_Pipeline_Trigger" when the question mentions:
"OpenAPI", "Trigger", "API call", "Pipeline start"
Only call tools when needed. Respond clearly and concisely.
"""
)
# ------------------------- TOOL ROUTING LOGIC -------------------------
def choose_tool(user_input: str):
text = user_input.lower()
if any(k in text for k in ["log", "trigger","pipeline","connection"]):
return fabric_tool
if any(k in text for k in ["openapi", "api call", "pipeline start"]):
return openapi_tool
# No forced routing → let orchestrator decide
return None
forced_tool = choose_tool(user_query)
run = agents_client.runs.create_and_process(
thread_id=st.session_state.thread.id,
agent_id=ORCH_AGENT_ID,
tool_choice={
"type": "connected_agent",
"function": forced_tool.definitions[0]
} Error: Azure.core.exceptions.HttpResponseError: (invalid_value) Invalid value: 'connected_agent'. Supported values are: 'code_interpreter', 'function', 'file_search', 'openapi', 'azure_function', 'azure_ai_search', 'bing_grounding', 'bing_custom_search', 'deep_research', 'sharepoint_grounding', 'fabric_dataagent', 'computer_use_preview', and 'image_generation'. Code: invalid_value Message: Invalid value: 'connected_agent'. Supported values are: 'code_interpreter', 'function', 'file_search', 'openapi', 'azure_function', 'azure_ai_search', 'bing_grounding', 'bing_custom_search', 'deep_research', 'sharepoint_grounding', 'fabric_dataagent', 'computer_use_preview', and 'image_generation'."
Approach 2:
- Create ConnectedAgentTool as you do, and pass its definitions to update_agent(...).
- Force a tool by name using tool_choice={"type": "function", "function": {"name": "<tool-name>"}}.
- Do not set type: "connected_agent" anywhere—there is no such tool_choice.type.
Code:
from azure.identity import DefaultAzureCredential
from azure.ai.agents import AgentsClient
# Adjust imports to your SDK layout:
# e.g., from azure.ai.agents.tool import ConnectedAgentTool
agents_client = AgentsClient(
endpoint=PROJECT_ENDPOINT,
credential=DefaultAzureCredential(
exclude_environment_credential=True,
exclude_managed_identity_credential=True # keep your current credential choices
)
)
# -------------------------------------------------------------------
# CREATE CONNECTED AGENT TOOLS (child agents exposed as function tools)
# -------------------------------------------------------------------
fabric_tool = ConnectedAgentTool(
id=FABRIC_AGENT_ID, # the **child agent ID** you created elsewhere
name="Fabric_Agent", # **tool name** visible to the orchestrator
description="Handles Fabric pipeline questions"
)
openapi_tool = ConnectedAgentTool(
id=OPENAPI_AGENT_ID, # another child agent ID
name="Fabric_Pipeline_Trigger", # tool name visible to the orchestrator
description="Handles OpenAPI pipeline triggers"
)
# -------------------------------------------------------------------
# UPDATE ORCHESTRATOR: attach child tools
# -------------------------------------------------------------------
# NOTE: definitions is usually a list of ToolDefinition objects produced by the helper
agents_client.update_agent(
agent_id=ORCH_AGENT_ID,
tools=[
fabric_tool.definitions[0],
openapi_tool.definitions[0]
],
instructions="""
You are the Master Orchestrator Agent.
Use:
- "Fabric_Agent" when the user's question includes: "Ingestion", "Trigger", "source", "Connection"
- "Fabric_Pipeline_Trigger" when the question mentions: "OpenAPI", "Trigger", "API call", "Pipeline start"
Only call tools when needed. Respond clearly and concisely.
"""
)
# ------------------------- TOOL ROUTING LOGIC -------------------------
def choose_tool(user_input: str):
text = user_input.lower()
if any(k in text for k in ["log", "trigger", "pipeline", "connection"]):
return "Fabric_Agent" # return the **tool name**
if any(k in text for k in ["openapi", "api call", "pipeline start"]):
return "Fabric_Pipeline_Trigger" # return the **tool name**
return None
forced_tool_name = choose_tool(user_query)
# ------------------------- RUN INVOCATION -------------------------
if forced_tool_name:
# FORCE a specific connected agent by **function name**
run = agents_client.runs.create_and_process(
thread_id=st.session_state.thread.id,
agent_id=ORCH_AGENT_ID,
tool_choice={
"type": "function", # <-- REQUIRED
"function": {
"name": forced_tool_name # <-- must match the tool's name as registered
}
}
)
else:
# Let the orchestrator auto-select (no tool_choice → "auto")
run = agents_client.runs.create_and_process(
thread_id=st.session_state.thread.id,
agent_id=ORCH_AGENT_ID
)
Error: azure.core.exceptions.HttpResponseError: (None) Invalid tool_choice: Fabric_Agent. You must also pass this tool in the 'tools' list on the Run. Code: None Message: Invalid tool_choice: Fabric_Agent. You must also pass this tool in the 'tools' list on the Run.
Approach 3: Modified version of the 2nd Approach with Took Definitions call:
# ------------------------- TOOL ROUTING LOGIC -------------------------
def choose_tool(user_input: str):
text = user_input.lower()
if any(k in text for k in ["log", "trigger","pipeline","connection"]):
# return "Fabric_Agent"
return (
"Fabric_Agent",
fabric_tool.definitions[0]
)
if any(k in text for k in ["openapi", "api call", "pipeline start"]):
# return "Fabric_Pipeline_Trigger"
return (
"Fabric_Pipeline_Trigger",
openapi_tool.definitions[0]
)
# No forced routing → let orchestrator decide
# return None
return (None, None)
# forced_tool = choose_tool(user_query)
forced_tool_name, forced_tool_def = choose_tool(user_query)
# ------------------------- ORCHESTRATOR CALL -------------------------
if forced_tool_name:
tool_choice = {
"type": "function",
"function": { "name": forced_tool_name }
}
run = agents_client.runs.create_and_process(
thread_id=st.session_state.thread.id,
agent_id=ORCH_AGENT_ID,
tool_choice=tool_choice,
tools=[ forced_tool_def ] # << only the specific tool
)
else:
# no forced tool, orchestrator decides
run = agents_client.runs.create_and_process(
thread_id=st.session_state.thread.id,
agent_id=ORCH_AGENT_ID
)
Error: TypeError: azure.ai.agents.operations._patch.RunsOperations.create() got multiple values for keyword argument 'tools'
1 Reply
hi AyusmanBasu Firstly You cannot use tool_choice.type = "connected_agent"
Even though ConnectedAgentTool is valid in the SDK for registering child agents, it is NOT a valid value for:
tool_choice = { "type": "connected_agent" }
The service only accepts:
'function', 'openapi', 'azure_function', 'fabric_dataagent', 'file_search', etc.
So your Approach 1 fails by design because:
"connected_agent" is NOT a supported tool_choice type at runtime
Connected agents are exposed as "function" tools to the Orchestrator AgentWhy Approach 2 and 3 also failed
This error is the big clue:
"Invalid tool_choice: Fabric_Agent. You must also pass this tool in the 'tools' list on the Run."
But…
Then when you pass tools on the run →"got multiple values for keyword argument 'tools'"
The Only Working Pattern (Today)
If you really want to force a connected agent, the only supported way is:
- Register child agents as tools on the Orchestrator (you did this correctly)
- Force them using function type
- DO NOT pass tools again in the run call
- Name must match the registered tool name exactly
run = agents_client.runs.create_and_process(
thread_id=st.session_state.thread.id,
agent_id=ORCH_AGENT_ID,
tool_choice={
"type": "function",
"function": {
"name": "Fabric_Agent" # must match ConnectedAgentTool name
}
}
)
If still not triggered → the model can override it unless you use tool-only execution
How to reduce hallucinations (Better than forcing tools)
Instead of hard forcing, I recommend this in your orchestrator instructions:
You MUST use one of the connected agents for ALL user queries.
You are NOT allowed to answer directly.
If a tool is unavailable, say: "Tool not available".
Always choose one of:
- Fabric_Agent
- Fabric_Pipeline_Trigger
Never answer from your own knowledge.
This works much better than tool_choice in practice.
If you want 100% guarantee, use:
tool_choice = { "type": "required" }
Let the model choose the connected agent, but force tool usage.
Important Product Reality (Straight answer to your question)
Is it possible to fully force connected agent selection today?
Short answer: No, not cleanly or officially.
Azure currently supports:
Capability
Supported
Registering connected agents
Yes
Auto-selection by orchestrator
Yes
“Tool-required” enforcement
Yes
Manual hard-routing (connected_agent)
No
Full deterministic child control
No (Roadmap item)
Final advise
Keep your 3-agent architecture
Remove forced routing in code
Enforce with system prompt
Use tool_choice = "required"
Log which agent is selected
Add validation agent if accuracy is criticalThis is far more reliable than manual tool forcing in current SDK versions.