microsoft / microsoft/agent-framework
Python: [Bug]: Background Responses tool loop fails when chaining tool output to a completed background response
@eavanvalkenburg is already working on this.
Since Aug 26, 2026.
- Dominant language
- Python
- Stars
- 13.6k
- Forks
- 2.3k
- Avg merge
- 2d 45m
- Merged PRs (30d)
- 358
Description
Summary
OpenAIChatClient local tool invocation fails when the initial Responses request runs with background=True and store=True.
After the completed background response emits a valid function_call, Agent Framework executes the local tool and submits its function_call_output using previous_response_id. Azure rejects that follow-up with:
No tool call found for function call output with call_id ...
Retrieving the completed predecessor confirms that it contains the exact same call_id. The output is therefore not orphaned in the client payload.
The equivalent stored foreground chain succeeds. The same background-only failure also reproduces with the raw OpenAI Python SDK, so the underlying service defect is below Agent Framework and is tracked in Azure/azure-sdk-for-python#46092. I am filing this here because Agent Framework's background tool loop currently selects the affected continuation strategy and provides no supported fallback or early diagnostic.
Expected behavior
One of the following:
- A local tool loop started with
background=Truecompletes successfully. - Agent Framework continues after a background predecessor by replaying complete local history into a fresh stored background response without
previous_response_id. - If background responses with local tools are not supported, Agent Framework rejects the combination before submitting the costly background job and documents the limitation and workaround.
store=False is not a workaround because the Responses API requires storage for background requests.
Actual behavior
The initial background response reaches completed and contains a valid local function_call. The first tool-result follow-up fails with HTTP 400 before the model can consume the result.
Steps to reproduce
- Configure the three environment variables used by the sample:
AZURE_OPENAI_BASE_URL: an Azure OpenAI-compatible v1 base URL ending in/openai/v1AZURE_OPENAI_SCOPE: the Entra scope used by that endpointAZURE_OPENAI_MODEL: a deployed reasoning model, tested withgpt-5.4-nano
- Run the code sample.
- Wait for the initial background response to complete and emit
lookup_probe. - Observe the 400 when Agent Framework submits the local tool result.
- Change the initial
OpenAIChatOptions(background=True)tobackground=False. The same local tool loop completes.
Repeated result
Using an equivalent raw Responses transport harness:
| Variant | Result |
|---|---|
| Production-like structured background request without tools | 1/1 completed (27,347 input tokens) |
Stored foreground predecessor + previous_response_id |
6/6 completed |
Stored background predecessor + previous_response_id |
0/6 completed; 6/6 returned the exact 400 |
Fresh stored background response with output/tool-output replay and no previous_response_id |
5/6 completed; one unrelated polling timeout, no pairing error |
For all six background-chain failures, retrieving the predecessor returned a function_call whose call_id exactly matched both the submitted function_call_output and the call ID named in the error.
This makes the failure deterministic for the tested background continuation path, rather than a missing local tool result or a parallel-tool race.
Code Sample
import asyncio
import os
from agent_framework import AgentSession, tool
from agent_framework.openai import OpenAIChatClient, OpenAIChatOptions
from azure.identity.aio import DefaultAzureCredential, get_bearer_token_provider
@tool
def lookup_probe(step: int) -> str:
return f"probe-result-step-{step}"
async def main() -> None:
credential = DefaultAzureCredential()
client = OpenAIChatClient(
credential=get_bearer_token_provider(
credential,
os.environ["AZURE_OPENAI_SCOPE"],
),
base_url=os.environ["AZURE_OPENAI_BASE_URL"],
model=os.environ["AZURE_OPENAI_MODEL"],
)
agent = client.as_agent(
name="background_tool_probe",
instructions="Call lookup_probe with step=1 exactly once, then return its result.",
tools=[lookup_probe],
default_options=OpenAIChatOptions(
store=True,
reasoning={"effort": "medium", "summary": "auto"},
allow_multiple_tool_calls=False,
),
)
session = AgentSession()
try:
response = await agent.run(
"Run the probe.",
session=session,
options=OpenAIChatOptions(background=True),
)
while response.continuation_token is not None:
await asyncio.sleep(1)
response = await agent.run(
session=session,
options=OpenAIChatOptions(
continuation_token=response.continuation_token,
),
)
print(response.text)
finally:
await client.client.close()
await credential.close()
if __name__ == "__main__":
asyncio.run(main())
Error Messages / Stack Traces
openai.BadRequestError: Error code: 400 - {
'error': {
'message': 'No tool call found for function call output with call_id call_<redacted>.',
'type': 'invalid_request_error',
'param': 'input',
'code': None
}
}
The exception is surfaced by Agent Framework as a ChatClientException from the
OpenAIChatClient tool-invocation loop.
For every failed run:
emitted function_call call_id
== submitted function_call_output call_id
== call_id returned by responses.retrieve(previous_response_id)
== call_id named in the 400 response
Package Versions
agent-framework-core: 1.13.0; agent-framework-openai: 1.12.0; openai: 2.48.0; azure-identity: 1.25.3; pydantic: 2.13.4
Python Version
Python 3.14.6
Additional Context
Environment
- Windows 11
- Azure OpenAI Responses-compatible v1 endpoint reached through APIM
- Authentication:
DefaultAzureCredential/ Entra bearer token, no API key - Model tested:
gpt-5.4-nano - Initial and replayed background requests require
store=True parallel_tool_calls=False; the reproduction uses one deterministic local function
Why this is different from related Agent Framework reports
- microsoft/agent-framework#3304 was a framework bug involving stale conversation IDs and was fixed by #3312.
- microsoft/agent-framework#5041 reached a raw service reproduction and was closed on 2026-06-09 as fixed upstream. This report reproduces a narrower background-predecessor case on newer packages on 2026-08-06.
- microsoft/agent-framework#5546 concerns persisted hosted MCP history producing an orphan output. Here the predecessor is completed, uses a plain local function, and retrieval proves that the matching
function_callexists. - Azure/azure-sdk-for-python#46092 remains open and tracks the underlying Responses chaining defect.
Framework-specific request
I understand that the 400 originates below Agent Framework. The framework-specific problem is that OpenAIChatClient advertises background responses and local tool invocation together, then uses previous_response_id for the tool-result continuation after a completed background response.
Could the Python client do one of the following?
- Use full local-history replay without
previous_response_idafter a background predecessor. The replay must retain the original user input, every response output item including reasoning items, and each function output. - Expose a continuation strategy hook so applications can choose inline replay for this case.
- Detect this unsupported combination and fail before submission with an actionable message linking the upstream issue.
- Add an integration test that contrasts foreground and background predecessors using the same local function call.
I can provide sanitized per-run response IDs, call-ID evidence, and the full raw transport harness if useful.
Related issues
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.