Skip to content

fix(core): 将空 list modalities 视为未配置,修复图片无法传递到模型的问题#8451

Open
Sisyphbaous-DT-Project wants to merge 3 commits into
AstrBotDevs:masterfrom
Sisyphbaous-DT-Project:fix/modalities-empty-list-image-passing
Open

fix(core): 将空 list modalities 视为未配置,修复图片无法传递到模型的问题#8451
Sisyphbaous-DT-Project wants to merge 3 commits into
AstrBotDevs:masterfrom
Sisyphbaous-DT-Project:fix/modalities-empty-list-image-passing

Conversation

@Sisyphbaous-DT-Project
Copy link
Copy Markdown
Contributor

@Sisyphbaous-DT-Project Sisyphbaous-DT-Project commented May 31, 2026

问题描述

从 v4.24.5 升级到 v4.25.2 后,所有未在 WebUI 中手动配置模型能力(modalities) 的 provider 出现以下问题:

  1. QQ 群聊中发送图片(包括引用图片),多模态主模型完全收不到图片,无法进行识图
  2. 第三方插件通过 event.request_llm(image_urls=...) 主动调用 LLM 时,传入的图片被静默丢弃
  3. 配置了专用图片转述模型后,引用图片的 captioning 流程被跳过
  4. 部分场景下工具调用(tool_use)被错误清除

而 v4.24.5 中一切正常。

根本原因分析

问题的根源是 modalities 字段的语义不一致,由两个因素叠加导致:

因素一:配置迁移将未配置的 modalities 写为 []

migra_helper.py 在迁移 provider 配置时,对未设置 modalities 的 provider 统一写入空列表:

if "modalities" not in provider:
    provider["modalities"] = []

升级前 modalities 字段不存在时为 None,升级后被改写为 []

因素二:多处代码对 []None 的处理不一致

v4.25.2 新增的 _provider_supports_modality() 以及原有的 _assemble_request_context_for_provider() 等函数,对 []None 存在两种截然不同的分支:

modalities _provider_supports_modality("image") _assemble_request_context_for_provider
None False isinstance(None, list)False保留图片
[] False isinstance([], list)True"image" in []False清空图片

在 v4.24.5 中,modalities 通常为 None(字段未定义),图片能正常传递。升级后变为 [],上述函数进入「明确配置了不支持图片」的分支,导致 image_urls 被静默清空、工具被错误清除。

完整因果链:

migra_helper: modalities=None → modalities=[]
    ↓
_provider_supports_modality("image") → False
    ↓
_select_image_chat_provider() 找不到 fallback,但仍使用主模型
    ↓
_assemble_request_context_for_provider()
    → isinstance([], list) → True
    → "image" in [] → False
    → image_urls 被替换为 []
    ↓
模型请求中完全不包含图片内容

修复方案

空列表 [] 视为「未配置」,与 None 保持一致行为。只有当 modalities 为非空 list 时,才启用过滤/修复逻辑。

涉及以下 5 处修改(共 2 个文件):

astrbot/core/astr_main_agent.py

_provider_supports_modality() 对空列表返回 True(默认支持),而非 False

def _provider_supports_modality(provider: Provider, modality: str) -> bool:
    modalities = provider.provider_config.get("modalities", None)
    if modalities == []:
        return True  # 空列表视为未配置,默认支持,保持向后兼容
    return isinstance(modalities, list) and modality in modalities

astrbot/core/agent/runners/tool_loop_agent_runner.py

_assemble_request_context_for_provider() not isinstance(modalities, list)not modalities,空列表也走「保留图片」分支

_should_fix_modalities_for_provider() 增加 and modalities 条件,空列表不触发历史上下文修复

_func_tool_for_provider() 增加 and modalities 条件,空列表不清除工具

tool loop 中 cached images 处理: "image" in modalitiesnot modalities or "image" in modalities

验证步骤

1. 复现脚本验证

编写了独立的复现脚本 reproduce_issue.py,模拟从 ProviderRequest 到最终上下文的完整链路

修复前 modalities=[]image_urls[](已清空),修复后正确保留。

2. 单元测试

82 passed, 0 failed, 0 skipped

所有现有测试通过,其中包括 _provider_supports_modality 相关的边界条件测试。

3. 各场景行为对比

场景 modalities 修复前 修复后 预期
用户消息带图片 [] 图片被清空 ❌ 图片正常传递 ✅ 正常
引用图片消息 [] captioning 被跳过 ❌ 正常传原图 ✅ 正常
主动消息插件传图 [] 图片被清空 ❌ 图片正常传递 ✅ 正常
tool call 返回图片 [] cached images 不追加 ❌ 正常追加 ✅ 正常
明确配置 ["text"] ["text"] 图片被清空 ✅ 图片被清空 ✅ 正常
明确配置 ["text","image"] ["text","image"] 图片正常 ✅ 图片正常 ✅ 正常
  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

修复前(当前版本)
image
修复后
image

  • 复现脚本:编写了独立的复现脚本,模拟从 ProviderRequest 到最终上下文的完整链路。修复前 modalities=[]image_urls 被清空为 [],修复后正确保留所有传入的图片路径,Bug 不再复现。
  • 代码质量ruff checkruff format --check 全部通过,无格式或 lint 问题。
  • 单元测试pytest tests/unit/test_astr_main_agent.py 82 项全部通过,覆盖了 _provider_supports_modality 的边界条件测试。

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Treat empty provider modality lists as unconfigured to restore backward-compatible multimodal behavior and image handling.

Bug Fixes:

  • Ensure image URLs are preserved and passed through for providers whose modalities are unset or migrated to empty lists.
  • Prevent unintended clearing of tools when provider modalities are an empty list.
  • Allow cached images from tool calls to be appended for providers with empty modality lists instead of being treated as non-image-capable.

Enhancements:

  • Align modality handling across core agents so that only non-empty modality lists restrict supported capabilities.

migra_helper 将未配置的 modalities 迁移为空 list [],而新增的
_provider_supports_modality()、_assemble_request_context_for_provider()、
_should_fix_modalities_for_provider()、_func_tool_for_provider() 四个函数
对 [] 和 None 的处理不一致,导致所有未在 WebUI 手动配置 modalities 的
provider 无法传递图片、引用图片被跳过、工具被错误清除。

修改策略:将 [] 与 None 统一视为未配置状态,保持向后兼容。
仅在 modalities 为非空 list 时才启用过滤和修复逻辑。

- _provider_supports_modality: [] 返回 True,默认支持
- _assemble_request_context_for_provider: [] 不过滤图片
- _should_fix_modalities_for_provider: [] 不触发历史上下文修复
- _func_tool_for_provider: [] 不清除工具

复现脚本已确认 Bug 不再复现,82 个相关单元测试全部通过。
上一笔 commit 遗漏了 tool_loop_agent_runner.py 第 917 行对
cached images 的 modalities 检查,当 modalities=[] 时,tool call
返回的图片不会被追加到消息中,导致模型看不到 tool 结果中的图片。

修复方式与主修复一致:not modalities or image in modalities

复现脚本和 82 个单元测试全部通过。
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels May 31, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the handling of provider modalities to treat an empty list or None as unconfigured, defaulting to supporting all modalities for backward compatibility. However, in astrbot/core/astr_main_agent.py, the _provider_supports_modality function treats None and [] inconsistently, where None returns False and [] returns True. It is recommended to update this function to return True for both cases to ensure consistent behavior and prevent unnecessary fallback switches.

Comment thread astrbot/core/astr_main_agent.py
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The logic for treating an empty modalities list as "unconfigured" is duplicated in several places; consider extracting a small helper (e.g., is_unconfigured_modalities(modalities)) to centralize this backward-compatibility rule and avoid drift in future changes.
  • In _provider_supports_modality you check modalities == [] while other call sites use not modalities; aligning on one approach (and clearly documenting it) would make the semantics around empty lists vs. other falsy values easier to reason about.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The logic for treating an empty modalities list as "unconfigured" is duplicated in several places; consider extracting a small helper (e.g., `is_unconfigured_modalities(modalities)`) to centralize this backward-compatibility rule and avoid drift in future changes.
- In `_provider_supports_modality` you check `modalities == []` while other call sites use `not modalities`; aligning on one approach (and clearly documenting it) would make the semantics around empty lists vs. other falsy values easier to reason about.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

修复 modalities=[] 导致 cached images 分支变为可达后,暴露
了 test_tool_result_includes_all_calltoolresult_content 中
fake_save_image mock 不完整的问题:返回的 SimpleNamespace
缺少 mime_type 属性,而实际 tool_image_cache.save_image()
始终包含该字段。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. lgtm This PR has been approved by a maintainer size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants