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.
The workflow should complete all iterations without error after tool approval.
The workflow crashes with HTTP 400 on the turn following tool approval execution.
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
Microsoft.Agents.AI.Abstractions 1.3.0, Microsoft.Agents.AI 1.3.0, Microsoft.Agents.AI.Workflows 1.3.0
Description
When using
GroupChatBuilderwithApprovalRequiredAIFunctiontools, 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 aChatRole.Usermessage with theToolApprovalResponseContentand passes it toContinueTurnAsync. The agent then executes the tool and produces aChatRole.Toolresult 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 everytoolrole message to be preceded by an assistant message with a matchingtool_callsentry.Steps to Reproduce
GroupChatToolApprovalsample (dotnet/samples/03-workflows/Agents/GroupChatToolApproval)MaximumIterationCount >= 4DeployToProductionExpected 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
Code Sample
Error Messages / Stack Traces
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.cs—HandleUserInputResponseAsyncandHandleFunctionResultAsyncdotnet/samples/03-workflows/Agents/GroupChatToolApproval/Program.cs