Forum Discussion

AyusmanBasu's avatar
AyusmanBasu
Copper Contributor
Nov 18, 2025

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)

  1. Child Agent 1 - Connects to SQL DB in Fabric to fetch information from Log tables.
  2. 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: 

  1. Create ConnectedAgentTool as you do, and pass its definitions to update_agent(...).
  2. Force a tool by name using tool_choice={"type": "function", "function": {"name": "<tool-name>"}}.
  3. 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 Agent

     

    Why 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:

    1. Register child agents as tools on the Orchestrator (you did this correctly)
    2. Force them using function type
    3. DO NOT pass tools again in the run call
    4. 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 critical

    This is far more reliable than manual tool forcing in current SDK versions.

     

Resources