Skip to content

Python: AgentThread re-submits prior tool outputs on subsequent turns with @tool functions #3718

@nicholasdbrady

Description

@nicholasdbrady

Description

When using AzureAIProjectAgentProvider with @tool decorated functions and a persistent AgentThread, subsequent turns re-submit tool call outputs from previous turns, causing Azure AI to reject the request with:

Error code: 400 - {'error': {'message': 'No tool call found for function call output with call_id call_XXXXX.', 'type': 'invalid_request_error', 'param': 'input', 'code': None}}

Environment

  • agent-framework: 1.0.0b251204 (and later)
  • agent-framework-azure-ai: latest
  • Python: 3.12
  • OS: Linux (WSL2)

Minimal Reproduction

import asyncio
from typing import Annotated
from agent_framework import tool
from agent_framework.azure import AzureAIProjectAgentProvider
from azure.identity.aio import AzureCliCredential
from pydantic import Field


@tool(approval_mode="never_require")
def calculate_tip(
    bill_amount: Annotated[float, Field(description="Bill amount in dollars")],
    tip_percent: Annotated[float, Field(description="Tip percentage")],
) -> str:
    """Calculate tip amount for a bill."""
    tip = bill_amount * (tip_percent / 100)
    return f"Tip: ${tip:.2f}, Total: ${bill_amount + tip:.2f}"


async def main():
    async with (
        AzureCliCredential() as credential,
        AzureAIProjectAgentProvider(credential=credential) as provider,
    ):
        agent = await provider.create_agent(
            name="tip-calculator",
            instructions="Use the calculate_tip tool to help with calculations.",
            tools=[calculate_tip],
        )

        # Single thread for multi-turn (BUG TRIGGER)
        thread = agent.get_new_thread()

        # Turn 1: Works fine
        result1 = await agent.run("Calculate 15% tip on an $85 bill", thread=thread)
        print(f"Turn 1: {result1.text}")

        # Turn 2: FAILS - re-submits tool output from Turn 1
        result2 = await agent.run("Now calculate 20% tip on the same $85 bill", thread=thread)
        print(f"Turn 2: {result2.text}")


asyncio.run(main())

Expected Behavior

Multi-turn conversations with @tool functions should work on the same AgentThread. Turn 2 should only submit the new user message, not re-submit tool outputs from Turn 1.

Actual Behavior

Turn 1 succeeds, but Turn 2 fails immediately with:

Error code: 400 - {'error': {'message': 'No tool call found for function call output with call_id call_XXXXX.', 'type': 'invalid_request_error', 'param': 'input', 'code': None}}

The error indicates that AgentThread accumulates function_call_output items from prior turns and re-submits them on subsequent turns. Azure AI rejects these because there's no corresponding function_call in the current request.

Analysis

The bug appears to be in how AgentThread manages conversation state when function tools are used:

  1. Turn 1: User message → Agent calls tool → Tool output submitted → Agent responds ✅
  2. Turn 2: User message + stale tool output from Turn 1 → Azure rejects (no matching call_id) ❌

The AgentThread should either:

  • Clear tool outputs after each turn completes
  • Not accumulate tool outputs in the thread state
  • Filter out stale tool outputs before submitting to Azure

Observed Patterns

Scenario Result
Hosted tools (CodeInterpreter, FileSearch, WebSearch) ✅ Works - multi-turn stable
Function tools (@tool) on same thread ❌ Fails on turn 2+
Function tools with fresh thread per turn ✅ Works - but loses context
SDK-level previous_response_id pattern ✅ Works - canonical solution

Workarounds

1. Use fresh thread per turn (loses context)

for message in messages:
    thread = agent.get_new_thread()  # Fresh thread each time
    result = await agent.run(message, thread=thread)

2. Use SDK-level previous_response_id pattern

# Bypass AgentThread, use azure-ai-projects SDK directly
response = await openai_client.responses.create(
    input=message,
    previous_response_id=last_response_id,
    extra_body={"agent": {"name": agent.name, "type": "agent_reference"}},
)
# Process tool calls, chain via response.id

3. Use hosted tools instead of function tools

Hosted tools (CodeInterpreter, FileSearch, WebSearch) work correctly with multi-turn on the same thread.

Impact

This bug makes @tool functions unusable for multi-turn conversations with AzureAIProjectAgentProvider, which is a core use case for the framework.

Related Issues

Metadata

Metadata

Labels

bugSomething isn't workingmodel clientsIssues related to the model client implementationspythonv1.0Features being tracked for the version 1.0 GA

Type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions