Skip to content

[Bug] 以anthropic提供商格式接入DeepSeek V4时,并行调用多个工具会导致400报错 #7871

@FFFold

Description

@FFFold

What happened / 发生了什么

DeepSeek V4 以anthropic提供商格式接入时,并行调用多个工具会导致400报错。

当 Assistant 在一次响应中并行发起多个 tool_use 时,AstrBot 的 Anthropic 适配层将后续多个 role="tool" 的返回结果转换成了多条独立的 User 消息。这可能导致 Anthropic api 校验失败,抛出 400 错误。

AstrBot 的 anthropic_source.py 将多个 role="tool" 的返回结果映射成了多条独立的 User 消息,导致 Anthropic 看到的序列如下:

Assistant: [tool_use_00, tool_use_01]   ← 两个并行工具调用
User:      [tool_result_00]             ← 被当成“下一条消息”
User:      [tool_result_01]             ← Anthropic api可能认为这条是“多余的用户消息”

因此 Anthropic 校验逻辑判定:tool_use_01(call_01_BhsvXomagk6BoekAmAaC5Gtp)之后没有紧跟的 tool_result,直接拒绝请求并返回 400。

修复方向:在 anthropic_source.py 的消息转换逻辑中,当遇到多个连续的 role="tool" 消息时,应将其合并为单条 User 消息,结构示例:

{
  "role": "user",
  "content": [
    {
      "type": "tool_result",
      "tool_use_id": "call_00_5EQ0V4NahGLAKFDZp8hAVwLT",
      "content": "...SKILL.md 内容..."
    },
    {
      "type": "tool_result",
      "tool_use_id": "call_01_BhsvXomagk6BoekAmAaC5Gtp",
      "content": "...pubmed.py 内容..."
    }
  ]
}

Reproduce / 如何复现?

  1. 配置使用 Anthropic 兼容格式的 deepseek-v4-flash
  2. 触发一次需要并行读取两个文件(或并行调用两个工具)的请求。
  3. 观察 API 返回 400 错误。

AstrBot version, deployment method (e.g., Windows Docker Desktop deployment), provider used, and messaging platform used. / AstrBot 版本、部署方式(如 Windows Docker Desktop 部署)、使用的提供商、使用的消息平台适配器

Docker部署,宿主机 Ubuntu server 24.04

OS

Linux

Logs / 报错日志

触发报错的结构:

[
  {
    "role": "assistant",
    "content": [
      {
        "type": "think",
        "think": "主人问有没有PubMed这个Skill...",
        "encrypted": "..."
      },
      {
        "type": "text",
        "text": "目前还没有正式发布的Skill呢~不过堇的本事可不少!..."
      }
    ],
    "tool_calls": [
      {
        "type": "function",
        "id": "call_00_sJcXYPcbhpxQYDPCjuVgDmf4",
        "function": {
          "name": "astrbot_list_skill_releases",
          "arguments": "{}"
        }
      }
    ]
  },
  {
    "role": "tool",
    "content": "{\"items\": [], \"total\": 0}",
    "tool_call_id": "call_00_sJcXYPcbhpxQYDPCjuVgDmf4"
  },
  {
    "role": "assistant",
    "content": [
      {
        "type": "think",
        "think": "目前没有已发布的技能。那我直接给主人列出我所有能调用的工具和能力吧!",
        "encrypted": "..."
      },
      {
        "type": "text",
        "text": "目前还没有正式发布的Skill呢~不过堇的本事可不少!..."
      }
    ],
    "tool_calls": [
      {
        "type": "function",
        "id": "call_00_5EQ0V4NahGLAKFDZp8hAVwLT",
        "function": {
          "name": "astrbot_file_read_tool",
          "arguments": "{\"path\": \"/workspace/skills/pubmed/SKILL.md\"}"
        }
      },
      {
        "type": "function",
        "id": "call_01_BhsvXomagk6BoekAmAaC5Gtp",
        "function": {
          "name": "astrbot_file_read_tool",
          "arguments": "{\"path\": \"/workspace/skills/pubmed/scripts/pubmed.py\"}"
        }
      }
    ]
  },
  {
    "role": "tool",
    "content": "---\nname: pubmed\ndescription: 你是一位拥有医学背景的文献检索专家...\n...(SKILL.md 完整内容)...",
    "tool_call_id": "call_00_5EQ0V4NahGLAKFDZp8hAVwLT"
  },
  {
    "role": "tool",
    "content": "#!/usr/bin/env python3\n...(pubmed.py 完整内容)...",
    "tool_call_id": "call_01_BhsvXomagk6BoekAmAaC5Gtp"
  },
  {
    "role": "assistant",
    "content": [
      {
        "type": "think",
        "think": "主人确实上传了PubMed的SKILL文件!...",
        "encrypted": null
      },
      {
        "type": "text",
        "text": "对不起主人!堇刚才说没有PubMed Skill..."
      }
    ]
  }
]

注: 报错似乎发生在最后一条 role="assistant" 消息被提交给 LLM 时。API 校验发现 call_01_BhsvXomagk6BoekAmAaC5Gtp 没有紧跟的 tool_result 块。

2026-04-28 16:59:55.306] [Core] [WARN] [v4.23.6] [runners.tool_loop_agent_runner:555]: Chat Model deepseek-a/deepseek-v4-flash request error: Error code: 400 - {'error': {'message': 'messages.43:`tool_use` ids were found without `tool_result` blocks immediately after: call_01_BhsvXomagk6BoekAmAaC5Gtp. 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'}}
Traceback (most recent call last):
  File "/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 510, in _iter_llm_responses_with_fallback
    async for attempt in retrying:
  File "/usr/local/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 170, in __anext__
    do = await self.iter(retry_state=self._retry_state)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 157, in iter
    result = await action(retry_state)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/tenacity/_utils.py", line 111, in inner
    return call(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/tenacity/__init__.py", line 393, in <lambda>
    self._add_action_func(lambda rs: rs.outcome.result())
                                     ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           ^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
  File "/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 514, in _iter_llm_responses_with_fallback
    async for resp in self._iter_llm_responses(
  File "/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 477, in _iter_llm_responses
    yield await self.provider.text_chat(**payload)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/AstrBot/astrbot/core/provider/sources/anthropic_source.py", line 581, in text_chat
    raise e
  File "/AstrBot/astrbot/core/provider/sources/anthropic_source.py", line 579, in text_chat
    llm_response = await self._query(payloads, func_tool)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/AstrBot/astrbot/core/provider/sources/anthropic_source.py", line 299, in _query
    completion = await self.client.messages.create(
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/anthropic/resources/messages/messages.py", line 2447, in create
    return await self._post(
           ^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/anthropic/_base_client.py", line 1996, in post
    return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/anthropic/_base_client.py", line 1781, in request
    raise self._make_status_error_from_response(err.response) from None
anthropic.BadRequestError: Error code: 400 - {'error': {'message': 'messages.43:`tool_use` ids were found without `tool_result` blocks immediately after: call_01_BhsvXomagk6BoekAmAaC5Gtp. 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'}}
[2026-04-28 16:59:55.327] [Core] [WARN] [v4.23.6] [runners.tool_loop_agent_runner:492]: Switched from deepseek-a/deepseek-v4-flash to fallback chat provider: 阿里百炼/deepseek-v4-flash

Are you willing to submit a PR? / 你愿意提交 PR 吗?

  • Yes!

Code of Conduct

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:providerThe bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner.bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions