Skip to content

FunctionApprovalResponseContent maps incorrectly to messages if ChatMessage MesssageId is not set #7153

@PederHP

Description

@PederHP

Description

Encountered a bug when parallel tool calling and ApprovalRequiredAIFunction. If no MessageId is set ChatMessages, the fallback mapping would incorrectly map the parallel calls and results into separate messages. This would then fail with APIs that require strict ordering of tool calls and tool results.

A work around is to remember setting the MessageId, but the mapping logic should also be fixed.

Reproduction Steps

using Microsoft.Extensions.AI;

var chatClient = new ChatClientBuilder(innerClient)
  .UseFunctionInvocation()
  .Build();

var tool = new ApprovalRequiredAIFunction(
  AIFunctionFactory.Create(() => "result", "my_tool"));

var options = new ChatOptions { Tools = [tool] };

List<ChatMessage> messages =
[
  new(ChatRole.User, "Please use the tools"),

  // Assistant message with approval requests - NOTE: no MessageId set
  new(ChatRole.Assistant,
  [
	  new FunctionApprovalRequestContent("call_1", new FunctionCallContent("call_1", "my_tool")),
	  new FunctionApprovalRequestContent("call_2", new FunctionCallContent("call_2", "my_tool")),
  ]),

  // User approved both
  new(ChatRole.User,
  [
	  new FunctionApprovalResponseContent("call_1", approved: true, new FunctionCallContent("call_1", "my_tool")),
	  new FunctionApprovalResponseContent("call_2", approved: true, new FunctionCallContent("call_2", "my_tool")),
  ]),
];

// BUG: This will send TWO separate assistant messages to the inner client:
//   Assistant: [FunctionCallContent("call_1")]
//   Assistant: [FunctionCallContent("call_2")]
//   Tool: [FunctionResultContent("call_1"), FunctionResultContent("call_2")]
//
// EXPECTED: ONE assistant message with both FCCs:
//   Assistant: [FunctionCallContent("call_1"), FunctionCallContent("call_2")]
//   Tool: [FunctionResultContent("call_1"), FunctionResultContent("call_2")]
//
// This causes Anthropic to reject with:
// "unexpected tool_use_id found in tool_result blocks... Each tool_result block
//  must have a corresponding tool_use block in the previous message."

var response = await chatClient.GetResponseAsync(messages, options);

Expected behavior

Parallel tool calls should be mapped the same way whether MessageId is set or not

Actual behavior

Parallel tool calls are mapped incorrectly when MessageId is not set.

Regression?

No response

Known Workarounds

Set MessageId on ChatMessage

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-aiMicrosoft.Extensions.AI librariesbugThis issue describes a behavior which is not expected - a bug.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions