Skip to content

fix: 新增临时额外文本块#6679

Open
kawayiYokami wants to merge 5 commits intoAstrBotDevs:masterfrom
kawayiYokami:fix/keep-extra-user-content-parts-out-of-message-history
Open

fix: 新增临时额外文本块#6679
kawayiYokami wants to merge 5 commits intoAstrBotDevs:masterfrom
kawayiYokami:fix/keep-extra-user-content-parts-out-of-message-history

Conversation

@kawayiYokami
Copy link
Contributor

@kawayiYokami kawayiYokami commented Mar 20, 2026

摘要

  • 新增 transient_extra_user_content_parts,用于承载仅在组装当前轮最新用户消息时出现、且不会进入消息记录的临时内容块
  • 保留现有 extra_user_content_parts 的生产和消费逻辑不变,不改动框架内原有写入点
  • 这是一个小修复,不需要 schema 变更、数据迁移或删除现有记录即可上线

问题说明

当前 extra_user_content_parts 会进入正常消息记录。

但第三方插件有可能需要给“当前这一次请求”附加临时文本块,这类内容不应该被持久化到消息历史。
如果误用 extra_user_content_parts 来承载这类临时内容,就会把它们写入历史,并在后续轮次中继续被带入上下文。
这不仅是错误实现,还会持续浪费 token 和存储空间。

修改说明

本次修复采用增量方式处理:

  • 新增 transient_extra_user_content_parts
  • 不修改现有 extra_user_content_parts 的既有语义
  • 不改动框架内原有 extra_user_content_parts.append(...) 的调用点

运行时仅在组装消息时额外处理这个新字段:

  • transient_extra_user_content_parts 会被拼成一条临时的最新 user 消息
  • 这条消息会标记为 _no_save
  • 除了这一步之外,不额外让其他逻辑消费它

这样可以保证:

  • 插件仍然可以为当前请求追加临时内容块
  • 这些临时内容只出现在本轮最新用户消息中
  • 它们不会进入持久化消息历史
  • 现有框架内的 extra_user_content_parts 行为保持不变

测试

  • uv run ruff check astrbot/core/provider/entities.py
  • uv run ruff check astrbot/core/agent/runners/tool_loop_agent_runner.py
  • uv run ruff check astrbot/core/astr_main_agent.py

补充说明

这是一个不需要删除数据的小修复。
这个补丁不会主动清理已经被污染的旧历史记录,只提供一种正确的“临时额外内容块”承载方式,供后续插件使用。

Summary by Sourcery

Introduce transient user content blocks for per-request context without affecting persisted message history.

New Features:

  • Add a transient_extra_user_content_parts field on ProviderRequest to carry temporary user content blocks that only affect the current turn.

Enhancements:

  • Assemble transient_extra_user_content_parts into a separate, non-persisted user message when building the LLM message list.
  • Clarify the semantics of extra_user_content_parts in ProviderRequest to describe their persistence in message history.

@auto-assign auto-assign bot requested review from Fridemn and LIghtJUNction March 20, 2026 07:36
@dosubot dosubot bot added the size:S This PR changes 10-29 lines, ignoring generated files. label Mar 20, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses an issue where temporary extra_user_content_parts were incorrectly being saved into the persistent message history. The change ensures that these request-specific attachments or instructions are visible to the model for the current request but are not stored, preventing token waste, unnecessary storage consumption, and incorrect replay behavior in subsequent turns.

Highlights

  • Message History Persistence: Prevented extra_user_content_parts from being persisted in the message history by marking them as request-only _no_save messages.
  • User Prompt Handling: Ensured the normal user prompt is saved to history while still allowing the model to access the extra_user_content_parts for the current request.
  • Impact and Scope: Implemented the fix within the message-building path, requiring no schema changes, migrations, or data deletions.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
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:

  • Instead of mutating request.extra_user_content_parts around assemble_context, consider refactoring assemble_context to accept the extra parts as an argument (or using a small helper/context manager) so that the request object doesn't need to be temporarily mutated and deep-copied.
  • Double-check that Message(content=extra_user_content_parts) matches the expected content shape for the LLM (e.g., list of ContentPart vs. a single part) so that the extra user parts are interpreted identically to how they were when inlined into the original assembled context.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Instead of mutating `request.extra_user_content_parts` around `assemble_context`, consider refactoring `assemble_context` to accept the extra parts as an argument (or using a small helper/context manager) so that the request object doesn't need to be temporarily mutated and deep-copied.
- Double-check that `Message(content=extra_user_content_parts)` matches the expected content shape for the LLM (e.g., list of `ContentPart` vs. a single part) so that the extra user parts are interpreted identically to how they were when inlined into the original assembled context.

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.

@dosubot dosubot bot added 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 Mar 20, 2026
Copy link
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 correctly addresses the issue of extra_user_content_parts being incorrectly persisted in message history. The approach of creating a separate, temporary message marked with _no_save is a good solution. The implementation is logical, though I have a minor suggestion to improve efficiency by removing an unnecessary deepcopy operation. Overall, this is a solid fix.

messages.append(m)
if request.prompt is not None:
m = await request.assemble_context()
extra_user_content_parts = copy.deepcopy(request.extra_user_content_parts)
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using copy.deepcopy here is not necessary and adds overhead. Since extra_user_content_parts is a local variable and you are not modifying its contents, a simple reference assignment is sufficient and safe. The try...finally block already ensures the original list reference is restored to request.extra_user_content_parts.

Suggested change
extra_user_content_parts = copy.deepcopy(request.extra_user_content_parts)
extra_user_content_parts = request.extra_user_content_parts

@kawayiYokami kawayiYokami changed the title fix: keep extra user content parts out of message history fix: 修复额外内容块进入消息记录 Mar 20, 2026
@kawayiYokami kawayiYokami changed the title fix: 修复额外内容块进入消息记录 fix: [可免审查]修复额外内容块进入消息记录 Mar 20, 2026
@Soulter
Copy link
Member

Soulter commented Mar 20, 2026

额外内容块应该也得放进消息记录的,比如一些时间信息。,可以让llm知道某条消息什么时候发的,如果临时去掉也会影响缓存命中率?

@kawayiYokami
Copy link
Contributor Author

kawayiYokami commented Mar 20, 2026

额外内容块应该也得放进消息记录的,比如一些时间信息。,可以让llm知道某条消息什么时候发的,如果临时去掉也会影响缓存命中率?

时间信息应该作为metadata放到消息记录里,而不是额外文本块。
然后在生成历史记录的时候,从metadata从读取重新生成。
需要的话应该再开一个pr

@Soulter
Copy link
Member

Soulter commented Mar 20, 2026

额外内容块应该也得放进消息记录的,比如一些时间信息。,可以让llm知道某条消息什么时候发的,如果临时去掉也会影响缓存命中率?

时间信息应该作为metadata放到消息记录里,而不是额外文本块。 然后在生成历史记录的时候,从metadata从读取重新生成

那和直接放到额外内容块有啥区别

@kawayiYokami
Copy link
Contributor Author

额外内容块应该也得放进消息记录的,比如一些时间信息。,可以让llm知道某条消息什么时候发的,如果临时去掉也会影响缓存命中率?

时间信息应该作为metadata放到消息记录里,而不是额外文本块。 然后在生成历史记录的时候,从metadata从读取重新生成

那和直接放到额外内容块有啥区别

因为额外块里面的信息不一定需要保留,如果都保留会带来大量垃圾信息。

@kawayiYokami
Copy link
Contributor Author

kawayiYokami commented Mar 20, 2026

额外内容块应该也得放进消息记录的,比如一些时间信息。,可以让llm知道某条消息什么时候发的,如果临时去掉也会影响缓存命中率?

时间信息应该作为metadata放到消息记录里,而不是额外文本块。 然后在生成历史记录的时候,从metadata从读取重新生成

那和直接放到额外内容块有啥区别

要不要分2个额外文本块,一个是不会入记录的,一个是会入记录的,这样你觉得呢?我马上改

@advent259141
Copy link
Member

这样做反而会破坏上游缓存命中,而且会导致上下文不一致的问题,不建议这么做

@kawayiYokami
Copy link
Contributor Author

kawayiYokami commented Mar 20, 2026

这样做反而会破坏上游缓存命中,而且会导致上下文不一致的问题,不建议这么做

这是正确的做法,几乎所有软件的额外文本块都有临时部分,特别是rag,todo 相关的,都是只存在于最新消息。

这不会破坏缓存的,因为额外文本块在最后的消息,只要不破坏前面的就没问题,反而一直大量重复的信息出现在前面才是浪费token的。

@kawayiYokami kawayiYokami marked this pull request as draft March 20, 2026 12:42
@kawayiYokami kawayiYokami changed the title fix: [可免审查]修复额外内容块进入消息记录 fix: 新增临时额外文本块 Mar 20, 2026
@kawayiYokami kawayiYokami marked this pull request as ready for review March 20, 2026 12:58
@auto-assign auto-assign bot requested review from Raven95676 and anka-afk March 20, 2026 12:58
Copy link
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:

  • Consider providing a first-class way to construct a non-persisted Message (e.g., a constructor parameter or factory) instead of mutating the private _no_save attribute after instantiation, so that the no-save behavior is explicit and less coupled to internal implementation details.
  • If ProviderRequest instances can be reused across multiple runs, you may want to explicitly clear transient_extra_user_content_parts after assembling messages to avoid accidentally carrying transient content into subsequent requests.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider providing a first-class way to construct a non-persisted `Message` (e.g., a constructor parameter or factory) instead of mutating the private `_no_save` attribute after instantiation, so that the no-save behavior is explicit and less coupled to internal implementation details.
- If `ProviderRequest` instances can be reused across multiple runs, you may want to explicitly clear `transient_extra_user_content_parts` after assembling messages to avoid accidentally carrying transient content into subsequent requests.

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.

@kawayiYokami
Copy link
Contributor Author

比如第一轮后,历史是:

[
  {
    "role": "user",
    "content": [
      { "type": "text", "text": "帮我总结这张图" }, 
      { "type": "text", "text": "[发送时间: 2026-03-20 10:00]" },
      { "type": "text", "text": "当前任务" }, 
    ]
  },
  {
    "role": "assistant",
    "content": "这张图主要是在讲……"
  }
]

第二轮请求时:

[
  {
    "role": "user",
    "content": [
      { "type": "text", "text": "帮我总结这张图" },
      { "type": "text", "text": "[发送时间: 2026-03-20 10:00]" }  <-这里之前都可以有效缓存命中缓存的消息
    ]
  },
  {
    "role": "assistant",
    "content": "这张图主要是在讲……"
  },
  {
    "role": "user",
    "content": [
      { "type": "text", "text": "谢谢你" },
      { "type": "text", "text": "[发送时间: 2026-03-20 10:01]" },
      { "type": "text", "text": "当前任务" }, 
    ]
  },
]

第三轮

[
  {
    "role": "user",
    "content": [
      { "type": "text", "text": "帮我总结这张图" },
      { "type": "text", "text": "[发送时间: 2026-03-20 10:00]" }
    ]
  },
  {
    "role": "assistant",
    "content": "这张图主要是在讲……"
  },
  {
    "role": "user",
    "content": [
      { "type": "text", "text": "谢谢你" },
      { "type": "text", "text": "[发送时间: 2026-03-20 10:01]" } <-这里之前都可以有效缓存命中缓存的消息
    ]
  },
  {
    "role": "assistant",
    "content": "不客气。"
  },
  {
    "role": "user",
    "content": [
      { "type": "text", "text": "再帮我提炼成三句话" },
      { "type": "text", "text": "[发送时间: 2026-03-20 10:02]" },
      { "type": "text", "text": "当前任务" }
    ]
  }
]

@kawayiYokami
Copy link
Contributor Author

额外内容块应该也得放进消息记录的,比如一些时间信息。,可以让llm知道某条消息什么时候发的,如果临时去掉也会影响缓存命中率?

时间信息应该作为metadata放到消息记录里,而不是额外文本块。 然后在生成历史记录的时候,从metadata从读取重新生成

那和直接放到额外内容块有啥区别

新写法好了,但是我还不知道自带的系统有哪些是需要临时的,我看了下nosave的好像只有参考对话

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. size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants