Skip to content

[Bug] CreateSkillPayloadTool工具的payload参数类型指定缺少必要字段 #6279

@saitewasreset

Description

@saitewasreset

What happened / 发生了什么

astrbot/core/computer/tools/neo_skills.py中定义了CreateSkillPayloadTool工具,其参数定义如下:

{
    "type": "object",
    "properties": {
        "payload": {
            "anyOf": [{"type": "object"}, {"type": "array"}],
            "description": (
                "Skill payload JSON. Typical schema: {skill_markdown, inputs, outputs, meta}. "
                "This only stores content and returns payload_ref; it does not create a candidate or release."
            ),
        },
        "kind": {
            "type": "string",
            "description": "Payload kind.",
            "default": "astrbot_skill_v1",
        },
    },
    "required": ["payload"],
}

注意payload参数,代码定义其类型可为objectarray,但对于array时的情况,其并未定义items属性指定数组的元素类型。

在使用Gemini API时,上述行为会导致错误:

google.genai.errors.ClientError: 400 Bad Request. {'message': '{\n  "error": {\n    "code": 400,\n    "message": "* GenerateContentRequest.tools[0].function_declarations[9].parameters.properties[payload].any_of[1].items: missing field.\\n",\n    "status": "INVALID_ARGUMENT"\n  }\n}\n', 'status': 'Bad Request'}

对应的传递给Google GenAI库的工具调用参数如下:

{
    "name": "astrbot_create_skill_payload",
    "description": "Step 1/3 for Neo skill authoring: create immutable payload content and return payload_ref. Use this to store skill_markdown and structured metadata; do NOT write local skill folders directly.",
    "parameters": {
        "type": "object",
        "required": [
            "payload"
        ],
        "properties": {
            "payload": {
                "anyOf": [
                    {
                        "type": "object"
                    },
                    {
                        "type": "array"
                    }
                ]
            },
            "kind": {
                "type": "string",
                "description": "Payload kind."
            }
        }
    }
}

根据报错信息,可知Gemini API期望payloadarray变体含有items字段。

查阅Gemini API文档12,可知items字段的定义如下:

items object (Schema)

Optional. Schema of the elements of Type.ARRAY.

其指定了当typearray时,数组元素的基本类型。虽然文档中标注为"Optional",但在typearray时,API 似乎要求必须指定该参数。

CreateSkillPayloadTool的功能由shipyard/shipyard-neo实现,其对应shipyard-neo的"5.1.1 创建 payload" API3

POST /v1/skills/payloads

请求体:

字段 类型 默认值 说明
payload object | array 必填 JSON 负载内容(仅支持 object/array)
kind string "generic" 负载类型标记

响应 201:

{
  "payload_ref": "blob:blob_abc123",
  "kind": "candidate_payload"
}

其中并未明确指定array的元素类型,但根据其实现的功能,似乎可以认为元素可以为任意object?故可考虑设定items字段为:

"items": {
    "type": "object"
}

Reproduce / 如何复现?

测试版本:v4.20.0 (a8ff2b3d9cb3f340def8b754854ceb0642a52a0d)

  1. 添加Gemini 提供商,配置google_gemini/gemini-2.5-flash/google_gemini/gemini-3.1-pro-preview模型
Image
  1. 在”配置文件--普通配置“的”使用电脑能力“中,选择"sandbox",沙箱环境驱动器选择"shipyard_neo"
Image
  1. 发送任意对话,可观察到空输出,后端显示错误
Image

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

  • AstrBot版本:v4.20.0 (a8ff2b3d9cb3f340def8b754854ceb0642a52a0d)
  • 部署方式:源码运行(uv run main.py
  • 使用的提供商:Gemini

OS

Linux

Logs / 报错日志

[21:44:02.444] [Core] [WARN] [v4.20.0] [runners.tool_loop_agent_runner:268]: Chat Model google_gemini/gemini-2.5-flash request error: 400 Bad Request. {'message': '{\n  "error": {\n    "code": 400,\n    "message": "* GenerateContentRequest.tools[0].function_declarations[9].parameters.properties[payload].any_of[1].items: missing field.\\n",\n    "status": "INVALID_ARGUMENT"\n  }\n}\n', 'status': 'Bad Request'}
Traceback (most recent call last):

  File "/home/saite/project/AstrBot/main.py", line 141, in <module>
    asyncio.run(main_async(args.webui_dir))
    │       │   │          │    └ None
    │       │   │          └ Namespace(webui_dir=None)
    │       │   └ <function main_async at 0x7fac6de3d800>
    │       └ <function run at 0x7fac6f521f80>
    └ <module 'asyncio' from '/home/saite/.pyenv/versions/3.12.11/lib/python3.12/asyncio/__init__.py'>

  File "/home/saite/.pyenv/versions/3.12.11/lib/python3.12/asyncio/runners.py", line 195, in run
    return runner.run(main)
           │      │   └ <coroutine object main_async at 0x7fac6bc1da80>
           │      └ <function Runner.run at 0x7fac6ed27420>
           └ <asyncio.runners.Runner object at 0x7fac07b55af0>
  File "/home/saite/.pyenv/versions/3.12.11/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           │    │     │                  └ <Task pending name='Task-1' coro=<main_async() running at /home/saite/project/AstrBot/main.py:121> wait_for=<_GatheringFuture...
           │    │     └ <function BaseEventLoop.run_until_complete at 0x7fac6ed25080>
           │    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
           └ <asyncio.runners.Runner object at 0x7fac07b55af0>
  File "/home/saite/.pyenv/versions/3.12.11/lib/python3.12/asyncio/base_events.py", line 678, in run_until_complete
    self.run_forever()
    │    └ <function BaseEventLoop.run_forever at 0x7fac6ed24fe0>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/home/saite/.pyenv/versions/3.12.11/lib/python3.12/asyncio/base_events.py", line 645, in run_forever
    self._run_once()
    │    └ <function BaseEventLoop._run_once at 0x7fac6ed26de0>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/home/saite/.pyenv/versions/3.12.11/lib/python3.12/asyncio/base_events.py", line 1999, in _run_once
    handle._run()
    │      └ <function Handle._run at 0x7fac6f4c1120>
    └ <Handle Task.task_wakeup(<Future finished result=None>)>
  File "/home/saite/.pyenv/versions/3.12.11/lib/python3.12/asyncio/events.py", line 88, in _run
    self._context.run(self._callback, *self._args)
    │    │            │    │           │    └ <member '_args' of 'Handle' objects>
    │    │            │    │           └ <Handle Task.task_wakeup(<Future finished result=None>)>
    │    │            │    └ <member '_callback' of 'Handle' objects>
    │    │            └ <Handle Task.task_wakeup(<Future finished result=None>)>
    │    └ <member '_context' of 'Handle' objects>
    └ <Handle Task.task_wakeup(<Future finished result=None>)>

  File "/home/saite/project/AstrBot/astrbot/core/pipeline/scheduler.py", line 87, in execute
    await self._process_stages(event)
          │    │               └ <astrbot.core.platform.sources.webchat.webchat_event.WebChatMessageEvent object at 0x7fac0447e5d0>
          │    └ <function PipelineScheduler._process_stages at 0x7fac07da51c0>
          └ <astrbot.core.pipeline.scheduler.PipelineScheduler object at 0x7fac6bdaaa80>

  File "/home/saite/project/AstrBot/astrbot/core/pipeline/scheduler.py", line 61, in _process_stages
    await self._process_stages(event, i + 1)
          │    │               │      └ 6
          │    │               └ <astrbot.core.platform.sources.webchat.webchat_event.WebChatMessageEvent object at 0x7fac0447e5d0>
          │    └ <function PipelineScheduler._process_stages at 0x7fac07da51c0>
          └ <astrbot.core.pipeline.scheduler.PipelineScheduler object at 0x7fac6bdaaa80>

  File "/home/saite/project/AstrBot/astrbot/core/pipeline/scheduler.py", line 72, in _process_stages
    await coroutine
          └ <coroutine object RespondStage.process at 0x7fac057ee6c0>

  File "/home/saite/project/AstrBot/astrbot/core/pipeline/respond/stage.py", line 201, in process
    await event.send_streaming(result.async_stream, realtime_segmenting)
          │     │              │      │             └ True
          │     │              │      └ <async_generator object run_agent at 0x7fac0443c4f0>
          │     │              └ MessageEventResult(chain=[], use_t2i_=None, type=None, result_type=<EventResultType.CONTINUE: 1>, result_content_type=<Result...
          │     └ <function WebChatMessageEvent.send_streaming at 0x7fac07e760c0>
          └ <astrbot.core.platform.sources.webchat.webchat_event.WebChatMessageEvent object at 0x7fac0447e5d0>

  File "/home/saite/project/AstrBot/astrbot/core/platform/sources/webchat/webchat_event.py", line 147, in send_streaming
    async for chain in generator:
                       └ <async_generator object run_agent at 0x7fac0443c4f0>

  File "/home/saite/project/AstrBot/astrbot/core/astr_agent_run_util.py", line 124, in run_agent
    async for resp in agent_runner.step():
                      │            └ <function ToolLoopAgentRunner.step at 0x7fac631c89a0>
                      └ <astrbot.core.agent.runners.tool_loop_agent_runner.ToolLoopAgentRunner object at 0x7fac0699a180>

  File "/home/saite/project/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 374, in step
    async for llm_response in self._iter_llm_responses_with_fallback():
                              │    └ <function ToolLoopAgentRunner._iter_llm_responses_with_fallback at 0x7fac631c85e0>
                              └ <astrbot.core.agent.runners.tool_loop_agent_runner.ToolLoopAgentRunner object at 0x7fac0699a180>

> File "/home/saite/project/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 243, in _iter_llm_responses_with_fallback
    async for resp in self._iter_llm_responses(include_model=idx == 0):
                      │    │                                 └ 0
                      │    └ <function ToolLoopAgentRunner._iter_llm_responses at 0x7fac631c8540>
                      └ <astrbot.core.agent.runners.tool_loop_agent_runner.ToolLoopAgentRunner object at 0x7fac0699a180>

  File "/home/saite/project/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 217, in _iter_llm_responses
    async for resp in stream:  # type: ignore
                      └ <async_generator object ProviderGoogleGenAI.text_chat_stream at 0x7fac05641840>

  File "/home/saite/project/AstrBot/astrbot/core/provider/sources/gemini_source.py", line 844, in text_chat_stream
    if await self._handle_api_error(e, keys):
             │    │                    └ ['<REDACTED>']
             │    └ <function ProviderGoogleGenAI._handle_api_error at 0x7fac06892700>
             └ <astrbot.core.provider.sources.gemini_source.ProviderGoogleGenAI object at 0x7fac069bcf80>

  File "/home/saite/project/AstrBot/astrbot/core/provider/sources/gemini_source.py", line 128, in _handle_api_error
    raise e
          └ ClientError('400 Bad Request. {\'message\': \'{\\n  "error": {\\n    "code": 400,\\n    "message": "* GenerateContentRequest....

  File "/home/saite/project/AstrBot/astrbot/core/provider/sources/gemini_source.py", line 840, in text_chat_stream
    async for response in self._query_stream(payloads, func_tool):
                          │    │             │         └ ToolSet(tools=[ExecuteShellTool(name='astrbot_execute_shell', description='Execute a command in the shell.', parameters={'typ...
                          │    │             └ {'messages': [{'role': 'system', 'content': '\n# Persona Instructions\n\nYou are a helpful and friendly assistant.\n\n[Shipya...
                          │    └ <function ProviderGoogleGenAI._query_stream at 0x7fac06892b60>
                          └ <astrbot.core.provider.sources.gemini_source.ProviderGoogleGenAI object at 0x7fac069bcf80>

  File "/home/saite/project/AstrBot/astrbot/core/provider/sources/gemini_source.py", line 639, in _query_stream
    result = await self.client.models.generate_content_stream(
                   │    │      └ <property object at 0x7fac6852d7b0>
                   │    └ <google.genai.client.AsyncClient object at 0x7fac068a3950>
                   └ <astrbot.core.provider.sources.gemini_source.ProviderGoogleGenAI object at 0x7fac069bcf80>

  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/google/genai/models.py", line 7699, in generate_content_stream
    response = await self._generate_content_stream(
                     │    └ <function AsyncModels._generate_content_stream at 0x7fac689332e0>
                     └ <google.genai.models.AsyncModels object at 0x7fac068a4bc0>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/google/genai/models.py", line 6423, in _generate_content_stream
    response_stream = await self._api_client.async_request_streamed(
                            │    │           └ <function BaseApiClient.async_request_streamed at 0x7fac68a99580>
                            │    └ <google.genai._api_client.BaseApiClient object at 0x7fac07c7ce30>
                            └ <google.genai.models.AsyncModels object at 0x7fac068a4bc0>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/google/genai/_api_client.py", line 1464, in async_request_streamed
    response = await self._async_request(
                     │    └ <function BaseApiClient._async_request at 0x7fac68a99260>
                     └ <google.genai._api_client.BaseApiClient object at 0x7fac07c7ce30>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/google/genai/_api_client.py", line 1380, in _async_request
    return await self._async_retry(  # type: ignore[no-any-return]
                 │    └ <AsyncRetrying object at 0x7fac068a0b90 (stop=<tenacity.stop.stop_after_attempt object at 0x7fac068a61b0>, wait=<tenacity.wai...
                 └ <google.genai._api_client.BaseApiClient object at 0x7fac07c7ce30>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 112, in __call__
    do = await self.iter(retry_state=retry_state)
               │    │                └ <RetryCallState 140376782464272: attempt #1; slept for 0.0; last result: failed (ClientError 400 Bad Request. {'message': '{\...
               │    └ <function AsyncRetrying.iter at 0x7fac689ffa60>
               └ <AsyncRetrying object at 0x7fac068a0b90 (stop=<tenacity.stop.stop_after_attempt object at 0x7fac068a61b0>, wait=<tenacity.wai...
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 157, in iter
    result = await action(retry_state)
                   │      └ <RetryCallState 140376782464272: attempt #1; slept for 0.0; last result: failed (ClientError 400 Bad Request. {'message': '{\...
                   └ <function wrap_to_async_func.<locals>.inner at 0x7fac043962a0>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/tenacity/_utils.py", line 111, in inner
    return call(*args, **kwargs)
           │     │       └ {}
           │     └ (<RetryCallState 140376782464272: attempt #1; slept for 0.0; last result: failed (ClientError 400 Bad Request. {'message': '{...
           └ <function BaseRetrying._post_stop_check_actions.<locals>.exc_check at 0x7fac04395da0>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/tenacity/__init__.py", line 413, in exc_check
    raise retry_exc.reraise()
          │         └ <function RetryError.reraise at 0x7fac689fcd60>
          └ RetryError(<Future at 0x7fac0440df70 state=finished raised ClientError>)
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/tenacity/__init__.py", line 184, in reraise
    raise self.last_attempt.result()
          │    │            └ <function Future.result at 0x7fac6f8efba0>
          │    └ <Future at 0x7fac0440df70 state=finished raised ClientError>
          └ RetryError(<Future at 0x7fac0440df70 state=finished raised ClientError>)
  File "/home/saite/.pyenv/versions/3.12.11/lib/python3.12/concurrent/futures/_base.py", line 449, in result
    return self.__get_result()
           └ None
  File "/home/saite/.pyenv/versions/3.12.11/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
    raise self._exception
          └ None
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/tenacity/asyncio/__init__.py", line 116, in __call__
    result = await fn(*args, **kwargs)
                   │   │       └ {}
                   │   └ (HttpRequest(headers={'Content-Type': 'application/json', 'x-goog-api-key': '<REDACTED>', 'user-...
                   └ <bound method BaseApiClient._async_request_once of <google.genai._api_client.BaseApiClient object at 0x7fac07c7ce30>>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/google/genai/_api_client.py", line 1296, in _async_request_once
    await errors.APIError.raise_for_async_response(response)
          │      │        │                        └ <ClientResponse(https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse) [400 ...
          │      │        └ <classmethod(<function APIError.raise_for_async_response at 0x7fac68a14680>)>
          │      └ <class 'google.genai.errors.APIError'>
          └ <module 'google.genai.errors' from '/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/google/genai/errors.py'>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/google/genai/errors.py", line 216, in raise_for_async_response
    await cls.raise_error_async(status_code, response_json, response)
          │   │                 │            │              └ <ClientResponse(https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse) [400 ...
          │   │                 │            └ {'message': '{\n  "error": {\n    "code": 400,\n    "message": "* GenerateContentRequest.tools[0].function_declarations[9].pa...
          │   │                 └ 400
          │   └ <classmethod(<function APIError.raise_error_async at 0x7fac68a147c0>)>
          └ <class 'google.genai.errors.APIError'>
  File "/home/saite/project/AstrBot/.venv/lib/python3.12/site-packages/google/genai/errors.py", line 238, in raise_error_async
    raise ClientError(status_code, response_json, response)
          │           │            │              └ <ClientResponse(https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent?alt=sse) [400 ...
          │           │            └ {'message': '{\n  "error": {\n    "code": 400,\n    "message": "* GenerateContentRequest.tools[0].function_declarations[9].pa...
          │           └ 400
          └ <class 'google.genai.errors.ClientError'>

google.genai.errors.ClientError: 400 Bad Request. {'message': '{\n  "error": {\n    "code": 400,\n    "message": "* GenerateContentRequest.tools[0].function_declarations[9].parameters.properties[payload].any_of[1].items: missing field.\\n",\n    "status": "INVALID_ARGUMENT"\n  }\n}\n', 'status': 'Bad Request'}
[21:44:02.449] [Core] [INFO] [result_decorate.stage:189]: 流式输出已启用,跳过结果装饰阶段
[21:44:26.088] [Core] [INFO] [routes.config:307]: Saving config, is_core=True

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

  • Yes!

Code of Conduct

Footnotes

  1. https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#function-declarations

  2. https://ai.google.dev/api/caching#Schema

  3. https://github.com/AstrBotDevs/shipyard-neo/blob/main/doc/bay_api_v1.md#5-skills-api%E6%8A%80%E8%83%BD%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:coreThe bug / feature is about astrbot's core, backendbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions