Skip to content

.NET: [Bug]: GroupChat tool approval flow causes HTTP 400 — tool message without preceding assistant tool_calls #5600

Description

@hmunikar

Description

When using GroupChatBuilder with ApprovalRequiredAIFunction tools, the workflow crashes with an HTTP 400 error after the approved tool executes and the agent attempts to continue the conversation on the next iteration.

HTTP 400 (invalid_request_error: )
Parameter: messages.[26].role
Invalid parameter: messages with role 'tool' must be a response to a preceeding message with 'tool_calls'.

Root Cause

In AIAgentHostExecutor.HandleUserInputResponseAsync(), when an approval response is received, it creates a ChatRole.User message with the ToolApprovalResponseContent and passes it to ContinueTurnAsync. The agent then executes the tool and produces a ChatRole.Tool result message.

When the group chat manager routes a subsequent turn back to the same agent, the conversation history sent to the LLM is missing the assistant message containing the original tool_calls (the one that triggered the approval request). The OpenAI API requires every tool role message to be preceded by an assistant message with a matching tool_calls entry.

Steps to Reproduce

  1. Run the GroupChatToolApproval sample (dotnet/samples/03-workflows/Agents/GroupChatToolApproval)
  2. Set MaximumIterationCount >= 4
  3. The QA agent runs tests, the DevOps agent checks staging, creates rollback plan, then calls DeployToProduction
  4. Approval is granted (auto-approved in sample)
  5. Tool executes successfully
  6. On the next iteration, the agent tries to respond but the LLM rejects the malformed history

Expected Behavior

The workflow should complete all iterations without error after tool approval.

Actual Behavior

The workflow crashes with HTTP 400 on the turn following tool approval execution.

Environment

  • .NET 10
  • Azure OpenAI (gpt-4.1-nano)
  • macOS

Code Sample

Error Messages / Stack Traces

Executor 'DevOpsEngineer_06ee58f4a394412681de5d8d53604ef9' failed with exception System.ClientModel.ClientResultException: HTTP 400 (invalid_request_error: )
Parameter: messages.[24].role

Invalid parameter: messages with role 'tool' must be a response to a preceeding message with 'tool_calls'.
   at OpenAI.ClientPipelineExtensions.ProcessMessageAsync(ClientPipeline pipeline, PipelineMessage message, RequestOptions options)
   at OpenAI.Chat.ChatClient.CompleteChatAsync(BinaryContent content, RequestOptions options)
   at OpenAI.Chat.ChatClient.<>c__DisplayClass20_0.<<CompleteChatStreamingAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at OpenAI.AsyncSseUpdateCollection`1.GetRawPagesAsync()+MoveNext()
   at OpenAI.AsyncSseUpdateCollection`1.GetRawPagesAsync()+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at System.ClientModel.AsyncCollectionResult`1.GetAsyncEnumerator(CancellationToken cancellationToken)+MoveNext()
   at System.ClientModel.AsyncCollectionResult`1.GetAsyncEnumerator(CancellationToken cancellationToken)+MoveNext()
   at System.ClientModel.AsyncCollectionResult`1.GetAsyncEnumerator(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at Microsoft.Extensions.AI.OpenAIChatClient.FromOpenAIStreamingChatCompletionAsync(IAsyncEnumerable`1 updates, ChatCompletionOptions options, CancellationToken cancellationToken)+MoveNext()
   at Microsoft.Extensions.AI.OpenAIChatClient.FromOpenAIStreamingChatCompletionAsync(IAsyncEnumerable`1 updates, ChatCompletionOptions options, CancellationToken cancellationToken)+MoveNext()
   at Microsoft.Extensions.AI.FunctionInvokingChatClient.GetStreamingResponseAsync(IEnumerable`1 messages, ChatOptions options, CancellationToken cancellationToken)+MoveNext()
   at Microsoft.Extensions.AI.FunctionInvokingChatClient.GetStreamingResponseAsync(IEnumerable`1 messages, ChatOptions options, CancellationToken cancellationToken)+MoveNext()
   at Microsoft.Extensions.AI.FunctionInvokingChatClient.GetStreamingResponseAsync(IEnumerable`1 messages, ChatOptions options, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at Microsoft.Agents.AI.ChatClientAgent.RunCoreStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs:line 336
   at Microsoft.Agents.AI.ChatClientAgent.RunCoreStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs:line 341
   at Microsoft.Agents.AI.ChatClientAgent.RunCoreStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at Microsoft.Agents.AI.AIAgent.RunStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs:line 472
   at Microsoft.Agents.AI.AIAgent.RunStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs:line 472
   at Microsoft.Agents.AI.AIAgent.RunStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at Microsoft.Agents.AI.Workflows.Specialized.AIAgentHostExecutor.InvokeAgentAsync(IEnumerable`1 messages, IWorkflowContext context, Boolean emitUpdateEvents, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs:line 215
   at Microsoft.Agents.AI.Workflows.Specialized.AIAgentHostExecutor.InvokeAgentAsync(IEnumerable`1 messages, IWorkflowContext context, Boolean emitUpdateEvents, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs:line 215
   at Microsoft.Agents.AI.Workflows.Specialized.AIAgentHostExecutor.ContinueTurnAsync(List`1 messages, IWorkflowContext context, Boolean emitEvents, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs:line 182
   at Microsoft.Agents.AI.Workflows.ChatProtocolExecutor.<>c__DisplayClass13_0.<<TakeTurnAsync>g__InvokeTakeTurnAsync|0>d.MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/ChatProtocolExecutor.cs:line 143
--- End of stack trace from previous location ---
   at Microsoft.Agents.AI.Workflows.StatefulExecutor`1.InvokeWithStateAsync(Func`4 invocation, IWorkflowContext context, Boolean skipCache, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/StatefulExecutor.cs:line 122
   at Microsoft.Agents.AI.Workflows.RouteBuilder.<>c__DisplayClass12_0`1.<<AddHandler>g__WrappedHandlerAsync|0>d.MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/RouteBuilder.cs:line 250
--- End of stack trace from previous location ---
   at Microsoft.Agents.AI.Workflows.Execution.MessageRouter.RouteMessageAsync(Object message, IWorkflowContext context, Boolean requireRoute, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/MessageRouter.cs:line 137.
System.Reflection.TargetInvocationException: Error invoking handler for Microsoft.Agents.AI.Workflows.TurnToken
 ---> System.ClientModel.ClientResultException: HTTP 400 (invalid_request_error: )
Parameter: messages.[24].role

Invalid parameter: messages with role 'tool' must be a response to a preceeding message with 'tool_calls'.
   at OpenAI.ClientPipelineExtensions.ProcessMessageAsync(ClientPipeline pipeline, PipelineMessage message, RequestOptions options)
   at OpenAI.Chat.ChatClient.CompleteChatAsync(BinaryContent content, RequestOptions options)
   at OpenAI.Chat.ChatClient.<>c__DisplayClass20_0.<<CompleteChatStreamingAsync>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at OpenAI.AsyncSseUpdateCollection`1.GetRawPagesAsync()+MoveNext()
   at OpenAI.AsyncSseUpdateCollection`1.GetRawPagesAsync()+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at System.ClientModel.AsyncCollectionResult`1.GetAsyncEnumerator(CancellationToken cancellationToken)+MoveNext()
   at System.ClientModel.AsyncCollectionResult`1.GetAsyncEnumerator(CancellationToken cancellationToken)+MoveNext()
   at System.ClientModel.AsyncCollectionResult`1.GetAsyncEnumerator(CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at Microsoft.Extensions.AI.OpenAIChatClient.FromOpenAIStreamingChatCompletionAsync(IAsyncEnumerable`1 updates, ChatCompletionOptions options, CancellationToken cancellationToken)+MoveNext()
   at Microsoft.Extensions.AI.OpenAIChatClient.FromOpenAIStreamingChatCompletionAsync(IAsyncEnumerable`1 updates, ChatCompletionOptions options, CancellationToken cancellationToken)+MoveNext()
   at Microsoft.Extensions.AI.FunctionInvokingChatClient.GetStreamingResponseAsync(IEnumerable`1 messages, ChatOptions options, CancellationToken cancellationToken)+MoveNext()
   at Microsoft.Extensions.AI.FunctionInvokingChatClient.GetStreamingResponseAsync(IEnumerable`1 messages, ChatOptions options, CancellationToken cancellationToken)+MoveNext()
   at Microsoft.Extensions.AI.FunctionInvokingChatClient.GetStreamingResponseAsync(IEnumerable`1 messages, ChatOptions options, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at Microsoft.Agents.AI.ChatClientAgent.RunCoreStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs:line 336
   at Microsoft.Agents.AI.ChatClientAgent.RunCoreStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs:line 341
   at Microsoft.Agents.AI.ChatClientAgent.RunCoreStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at Microsoft.Agents.AI.AIAgent.RunStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs:line 472
   at Microsoft.Agents.AI.AIAgent.RunStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs:line 472
   at Microsoft.Agents.AI.AIAgent.RunStreamingAsync(IEnumerable`1 messages, AgentSession session, AgentRunOptions options, CancellationToken cancellationToken)+System.Threading.Tasks.Sources.IValueTaskSource<System.Boolean>.GetResult()
   at Microsoft.Agents.AI.Workflows.Specialized.AIAgentHostExecutor.InvokeAgentAsync(IEnumerable`1 messages, IWorkflowContext context, Boolean emitUpdateEvents, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs:line 215
   at Microsoft.Agents.AI.Workflows.Specialized.AIAgentHostExecutor.InvokeAgentAsync(IEnumerable`1 messages, IWorkflowContext context, Boolean emitUpdateEvents, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs:line 215
   at Microsoft.Agents.AI.Workflows.Specialized.AIAgentHostExecutor.ContinueTurnAsync(List`1 messages, IWorkflowContext context, Boolean emitEvents, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs:line 182
   at Microsoft.Agents.AI.Workflows.ChatProtocolExecutor.<>c__DisplayClass13_0.<<TakeTurnAsync>g__InvokeTakeTurnAsync|0>d.MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/ChatProtocolExecutor.cs:line 143
--- End of stack trace from previous location ---
   at Microsoft.Agents.AI.Workflows.StatefulExecutor`1.InvokeWithStateAsync(Func`4 invocation, IWorkflowContext context, Boolean skipCache, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/StatefulExecutor.cs:line 122
   at Microsoft.Agents.AI.Workflows.RouteBuilder.<>c__DisplayClass12_0`1.<<AddHandler>g__WrappedHandlerAsync|0>d.MoveNext() in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/RouteBuilder.cs:line 250
--- End of stack trace from previous location ---
   at Microsoft.Agents.AI.Workflows.Execution.MessageRouter.RouteMessageAsync(Object message, IWorkflowContext context, Boolean requireRoute, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/MessageRouter.cs:line 137
   --- End of inner exception stack trace ---
   at Microsoft.Agents.AI.Workflows.Executor.ExecuteCoreAsync(Object message, TypeId messageType, IWorkflowContext context, WorkflowTelemetryContext telemetryContext, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/Executor.cs:line 287
   at Microsoft.Agents.AI.Workflows.InProc.InProcessRunner.DeliverMessagesAsync(String receiverId, ConcurrentQueue`1 envelopes, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs:line 266
   at Microsoft.Agents.AI.Workflows.InProc.InProcessRunner.DeliverMessagesAsync(String receiverId, ConcurrentQueue`1 envelopes, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs:line 279
   at Microsoft.Agents.AI.Workflows.InProc.InProcessRunner.RunSuperstepAsync(StepContext currentStep, CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs:line 322
   at Microsoft.Agents.AI.Workflows.InProc.InProcessRunner.Microsoft.Agents.AI.Workflows.Execution.ISuperStepRunner.RunSuperStepAsync(CancellationToken cancellationToken) in /Users/harimunikar/Projects/agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs:line 232

Package Versions

Microsoft.Agents.AI.Abstractions 1.3.0, Microsoft.Agents.AI 1.3.0, Microsoft.Agents.AI.Workflows 1.3.0

.NET Version

.NET 10

Additional Context

  • dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.csHandleUserInputResponseAsync and HandleFunctionResultAsync
  • dotnet/samples/03-workflows/Agents/GroupChatToolApproval/Program.cs

Metadata

Metadata

Assignees

Labels

.NETUsage: [Issues, PRs], Target: .NetbugUsage: [Issues], Target: all issues (Legacy, prefer issue type: bug)

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions