[GH-ISSUE #967] [BUG] Anthropic API format throws and error on parallel tool use results // Anthropic API格式在并行工具使用结果上抛出错误) #586

Closed
opened 2026-10-26 21:23:38 +00:00 by bsakaguchi · 4 comments
Owner

Originally created by @A0A on GitHub (Aug 26, 2025).
Original GitHub issue: https://github.com/deepseek-ai/DeepSeek-V3/issues/967

Describe the bug
Using the python Anthropic SDK, returning the results of parallel tool calls produces this error:

anthropic.BadRequestError: Error code: 400 - {'error': {'message': 'messages.3:tool_useids were found withouttool_resultblocks immediately after: call_1_809f60b6-1f6a-4c00-ac1f-decff9e93b43, call_2_5fd0c971-fbdd-4403-af08-9343b0ad7111. Eachtool_useblock must have a correspondingtool_result block in the next message.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}

Anthropic's message format contains an array of "blocks" in each message, and parallel tool calls arrive together as multiple blocks in that array. When the results of each tool call are sent back to the Deepseek API as blocks in a single array in the next tool call, the above error occurs. This error also occurs if tool call results are sent back in sequential messages, one per block.

To Reproduce

  1. Implement an agent with tools
  2. Use the Anthropic Python SDK to invoke Deepseek API as described here with the following prompt: "This is a test of your functionality. Call 3 of your tools simultaneously (in parallel).
  3. Execute the tool calls and return the results in the next invocation of the API.

Expected behavior
I would expect the api to proceed with the conversation based on the content of the tool call results. I've verified that the exact same code works when used to invoke Anthropic platform with Claude Sonnet 4.

Screenshots
Not applicable

Additional context
None

由Deepseek v3.1从英文翻译为中文

问题描述
使用Python Anthropic SDK时,返回并行工具调用的结果会产生以下错误:

anthropic.BadRequestError: Error code: 400 - {'error': {'message': 'messages.3:tool_useids were found withouttool_resultblocks immediately after: call_1_809f60b6-1f6a-4c00-ac1f-decff9e93b43, call_2_5fd0c971-fbdd-4403-af08-9343b0ad7111. Eachtool_useblock must have a correspondingtool_resultblock in the next message.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}

Anthropic的消息格式在每个消息中包含一个"blocks"数组,并行工具调用会作为该数组中的多个块一起到达。当每个工具调用的结果作为单个数组中的块在下一次工具调用中发送回Deepseek API时,会出现上述错误。如果工具调用结果在一个块一个消息中顺序发送回,也会出现此错误。

重现步骤

  1. 实现一个带有工具的代理
  2. 使用Anthropic Python SDK按照此处的描述调用Deepseek API,使用以下提示:"这是一个功能测试。同时(并行)调用你的3个工具。"
  3. 执行工具调用并在API的下一次调用中返回结果

预期行为
我期望API能根据工具调用结果的内容继续对话。我已经验证过完全相同的代码在调用Anthropic平台的Claude Sonnet 4时能够正常工作。

截图
不适用

附加信息
无

Originally created by @A0A on GitHub (Aug 26, 2025). Original GitHub issue: https://github.com/deepseek-ai/DeepSeek-V3/issues/967 **Describe the bug** Using the python Anthropic SDK, returning the results of parallel tool calls produces this error: `anthropic.BadRequestError: Error code: 400 - {'error': {'message': 'messages.3:`tool_use` ids were found without `tool_result` blocks immediately after: call_1_809f60b6-1f6a-4c00-ac1f-decff9e93b43, call_2_5fd0c971-fbdd-4403-af08-9343b0ad7111. Each `tool_use` block must have a corresponding `tool_result` block in the next message.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}` Anthropic's message format contains an array of "blocks" in each message, and parallel tool calls arrive together as multiple blocks in that array. When the results of each tool call are sent back to the Deepseek API as blocks in a single array in the next tool call, the above error occurs. This error also occurs if tool call results are sent back in sequential messages, one per block. **To Reproduce** 1. Implement an agent with tools 2. Use the Anthropic Python SDK to invoke Deepseek API as described [here](https://api-docs.deepseek.com/guides/anthropic_api) with the following prompt: "This is a test of your functionality. Call 3 of your tools simultaneously (in parallel). 3. Execute the tool calls and return the results in the next invocation of the API. **Expected behavior** I would expect the api to proceed with the conversation based on the content of the tool call results. I've verified that the exact same code works when used to invoke Anthropic platform with Claude Sonnet 4. **Screenshots** Not applicable **Additional context** None 由Deepseek v3.1从英文翻译为中文 **问题描述** 使用Python Anthropic SDK时,返回并行工具调用的结果会产生以下错误: `anthropic.BadRequestError: Error code: 400 - {'error': {'message': 'messages.3:`tool_use` ids were found without `tool_result` blocks immediately after: call_1_809f60b6-1f6a-4c00-ac1f-decff9e93b43, call_2_5fd0c971-fbdd-4403-af08-9343b0ad7111. Each `tool_use` block must have a corresponding `tool_result`block in the next message.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_request_error'}}` Anthropic的消息格式在每个消息中包含一个"blocks"数组,并行工具调用会作为该数组中的多个块一起到达。当每个工具调用的结果作为单个数组中的块在下一次工具调用中发送回Deepseek API时,会出现上述错误。如果工具调用结果在一个块一个消息中顺序发送回,也会出现此错误。 **重现步骤** 1. 实现一个带有工具的代理 2. 使用Anthropic Python SDK按照[此处](https://api-docs.deepseek.com/guides/anthropic_api)的描述调用Deepseek API,使用以下提示:"这是一个功能测试。同时(并行)调用你的3个工具。" 3. 执行工具调用并在API的下一次调用中返回结果 **预期行为** 我期望API能根据工具调用结果的内容继续对话。我已经验证过完全相同的代码在调用Anthropic平台的Claude Sonnet 4时能够正常工作。 **截图** 不适用 **附加信息** 无
bsakaguchi 2026-10-26 21:23:38 +00:00
Author
Owner

@onelife commented on GitHub (Aug 31, 2025):

I encountered the same issue with the following code snippet, which works with the Anthropic API.
I suppose the difference is that the Anthropic API only considers the last "tool use" block when parsing the last "tool result" block in context, whereas the DeepSeek API considers all "tool use" blocks and expects them to have corresponding "tool result" blocks.
The Anthropic API method reduces payload size and cost.

def process_query(query):
    
    messages = [{'role': 'user', 'content': query}]
    
    response = client.messages.create(
        max_tokens = 2024,
        model = 'deepseek-chat', 
        tools = tools,
        messages = messages,
    )

    process_query = True
    while process_query:
        assistant_content = []

        for content in response.content:
            if content.type == 'text':

                print(content.text)
                assistant_content.append(content)
                
                if len(response.content) == 1:
                    process_query = False

            elif content.type == 'tool_use':

                assistant_content = assistant_content.copy()
                assistant_content.append(content)
                messages.append({'role': 'assistant', 'content': assistant_content})

                tool_id = content.id
                tool_args = content.input
                tool_name = content.name
                print(f"Calling tool {tool_name} with args {tool_args}")

                result = execute_tool(tool_name, tool_args)
                user_content = [{"type": "tool_result", "tool_use_id": tool_id, "content": result}]  # <=== single block
                messages.append({"role": "user", "content": user_content})
                response = client.messages.create(
                    max_tokens = 2024,
                    model = 'deepseek-chat', 
                    tools = tools,
                    messages = messages,
                ) 
                
                if len(response.content) == 1 and response.content[0].type == "text":
                    print(response.content[0].text)
                    process_query = False

Hi @A0A, before this issue is fixed. You may change your code to add all "tool result" blocks into context as follows: the code snippet below works for me.

def process_query(query):
    
    messages = [{'role': 'user', 'content': query}]
    
    response = client.messages.create(
        max_tokens = 2024,
        model = 'deepseek-chat', 
        tools = tools,
        messages = messages,
    )
    
    process_query = True
    while process_query:
        assistant_content = []
        user_content = []

        for content in response.content:
            if content.type == 'text':
                
                print(content.text)
                assistant_content.append(content)
                
                if len(response.content) == 1:
                    process_query = False
            
            elif content.type == 'tool_use':

                assistant_content = assistant_content.copy()
                assistant_content.append(content)
                messages.append({'role': 'assistant', 'content': assistant_content})
                
                tool_id = content.id
                tool_args = content.input
                tool_name = content.name
                print(f"Calling tool {tool_name} with args {tool_args}")
                
                result = execute_tool(tool_name, tool_args)
                user_content = user_content.copy()
                user_content.append({"type": "tool_result", "tool_use_id": tool_id, "content": result})  # <=== multiple blocks
                messages.append({"role": "user", "content": user_content})
                response = client.messages.create(
                    max_tokens = 2024,
                    model = 'deepseek-chat', 
                    tools = tools,
                    messages = messages,
                ) 
                
                if len(response.content) == 1 and response.content[0].type == "text":
                    print(response.content[0].text)
                    process_query = False
<!-- gh-comment-id:3239944462 --> @onelife commented on GitHub (Aug 31, 2025): I encountered the same issue with the following code snippet, which works with the Anthropic API. I suppose the difference is that the Anthropic API only considers the last "tool use" block when parsing the last "tool result" block in context, whereas the DeepSeek API considers all "tool use" blocks and expects them to have corresponding "tool result" blocks. The Anthropic API method reduces payload size and cost. ``` def process_query(query): messages = [{'role': 'user', 'content': query}] response = client.messages.create( max_tokens = 2024, model = 'deepseek-chat', tools = tools, messages = messages, ) process_query = True while process_query: assistant_content = [] for content in response.content: if content.type == 'text': print(content.text) assistant_content.append(content) if len(response.content) == 1: process_query = False elif content.type == 'tool_use': assistant_content = assistant_content.copy() assistant_content.append(content) messages.append({'role': 'assistant', 'content': assistant_content}) tool_id = content.id tool_args = content.input tool_name = content.name print(f"Calling tool {tool_name} with args {tool_args}") result = execute_tool(tool_name, tool_args) user_content = [{"type": "tool_result", "tool_use_id": tool_id, "content": result}] # <=== single block messages.append({"role": "user", "content": user_content}) response = client.messages.create( max_tokens = 2024, model = 'deepseek-chat', tools = tools, messages = messages, ) if len(response.content) == 1 and response.content[0].type == "text": print(response.content[0].text) process_query = False ``` Hi @A0A, before this issue is fixed. You may change your code to add all "tool result" blocks into context as follows: the code snippet below works for me. ``` def process_query(query): messages = [{'role': 'user', 'content': query}] response = client.messages.create( max_tokens = 2024, model = 'deepseek-chat', tools = tools, messages = messages, ) process_query = True while process_query: assistant_content = [] user_content = [] for content in response.content: if content.type == 'text': print(content.text) assistant_content.append(content) if len(response.content) == 1: process_query = False elif content.type == 'tool_use': assistant_content = assistant_content.copy() assistant_content.append(content) messages.append({'role': 'assistant', 'content': assistant_content}) tool_id = content.id tool_args = content.input tool_name = content.name print(f"Calling tool {tool_name} with args {tool_args}") result = execute_tool(tool_name, tool_args) user_content = user_content.copy() user_content.append({"type": "tool_result", "tool_use_id": tool_id, "content": result}) # <=== multiple blocks messages.append({"role": "user", "content": user_content}) response = client.messages.create( max_tokens = 2024, model = 'deepseek-chat', tools = tools, messages = messages, ) if len(response.content) == 1 and response.content[0].type == "text": print(response.content[0].text) process_query = False ```
Author
Owner

@A0A commented on GitHub (Aug 31, 2025):

Nice catch, thank you!

<!-- gh-comment-id:3240468289 --> @A0A commented on GitHub (Aug 31, 2025): Nice catch, thank you!
Author
Owner

@github-actions[bot] commented on GitHub (Oct 2, 2025):

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you believe this issue is still relevant, please leave a comment to keep it open. Thank you for your contributions!

<!-- gh-comment-id:3358585399 --> @github-actions[bot] commented on GitHub (Oct 2, 2025): This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you believe this issue is still relevant, please leave a comment to keep it open. Thank you for your contributions!
Author
Owner

@github-actions[bot] commented on GitHub (Oct 17, 2025):

false

<!-- gh-comment-id:3413321226 --> @github-actions[bot] commented on GitHub (Oct 17, 2025): false
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
deepseek-ai/DeepSeek-V3#586
No description provided.