Skip to content

fix: avoid premature context compression from persisted usage - #9865

Open
wcqqq1214 wants to merge 3 commits into
AstrBotDevs:masterfrom
wcqqq1214:fix/issue-9864-context-usage
Open

fix: avoid premature context compression from persisted usage#9865
wcqqq1214 wants to merge 3 commits into
AstrBotDevs:masterfrom
wcqqq1214:fix/issue-9864-context-usage

Conversation

@wcqqq1214

@wcqqq1214 wcqqq1214 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Fixes #9864.

Modifications / 改动点

  • Stop using persisted conversation.token_usage as the token count for a new request context.

  • Retain a runner-local provider prompt snapshot for the next tool-loop request only while the provider remains the same and the message-prefix and exposed-tool-schema fingerprints still match. The snapshot keeps provider-only overhead such as tool schemas while newly appended messages are estimated.

  • Fall back to full message estimation after a change to the snapshotted prefix or tool schema, or when no matching runtime snapshot exists.

  • Add regressions for stale persisted usage, a high provider prompt usage with a low messages-only estimate, and in-place mutations of an existing message or FunctionTool.parameters.

  • This is NOT a breaking change. / 这不是一个破坏性变更。

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

Verification steps:

  • uv run pytest tests/test_tool_loop_agent_runner.py tests/agent/test_context_manager.py tests/agent/test_token_counter.py -q — passed: 107 tests.
  • make pr-test-neo — passed: Ruff format and lint checks, 13 tests, and the startup smoke test.
  • The stale-usage regression proves that a 3,879,963 persisted value does not compact a roughly 96,000-token context. The tool-loop regression proves that a matching 900/1,000 provider prompt snapshot still triggers compression when messages-only estimation remains below 82%. The mutation regression proves that changing an existing message or tool schema invalidates the snapshot and re-estimates the request.

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

Correct context compression accounting so requests use current message estimates and only reuse validated provider prompt usage from the active tool loop.

Bug Fixes:

  • Prevent persisted conversation token usage from prematurely triggering context compression for new requests.
  • Preserve provider prompt usage across matching tool-loop requests while invalidating it when messages or tool schemas change.

Enhancements:

  • Add runtime context fingerprints to safely reuse provider-specific prompt overhead alongside newly appended messages.

Tests:

  • Add regressions covering stale persisted usage, provider prompt overhead during tool loops, and in-place message or tool-schema mutations.

@wcqqq1214

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 283a4906e9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 820 to 822
self.request_context_manager.process(
self.run_context.messages,
trusted_token_usage=token_usage,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Badge Preserve provider usage when guarding later loop requests

On a later tool-loop step where the provider-reported context exceeds the 82% threshold because of tool schemas, multimodal accounting, or tokenizer differences while the message-only estimate remains below it, this skips compression and can send an over-limit request. _iter_llm_responses() sends func_tool separately, EstimateTokenCounter examines only messages, and conversation.token_usage is refreshed after every completed response, so during a loop this value is a recent provider-derived baseline rather than merely unrelated persisted data. Retain a sane provider-informed guard for subsequent steps instead of dropping the usage entirely.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in da0cdb8. Persisted Conversation.token_usage remains excluded. The runner now keeps an in-memory snapshot of the last provider prompt only while the provider, tool set, and message prefix still match, then estimates newly appended messages. A regression test covers high provider prompt usage with a low messages-only estimate.

@wcqqq1214

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da0cdb8d93

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +834 to +837
and snapshot.func_tool is current_func_tool
and len(self.run_context.messages) >= len(snapshot.message_ids)
and all(
id(message) == message_id

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Badge Compare snapshot contents, not object identities

When a tool or on_tool_start/on_tool_end hook mutates an existing Message.content in place between loop steps, every id(message) still matches, so the old provider usage is reused even though the request prefix has changed. An expanded message can therefore be omitted from token accounting and produce an over-limit request; similarly, the ToolSet identity check misses in-place schema changes. Store and compare an immutable request fingerprint or otherwise invalidate the snapshot around mutable tool and hook execution.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in b053a87. The snapshot now stores immutable SHA-256 fingerprints for each request-prefix message and the exposed tool schema. Any in-place mutation falls back to estimating the current request. Added regression coverage for on_tool_end mutations of an existing message and FunctionTool.parameters.

@wcqqq1214

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: b053a87a0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@wcqqq1214
wcqqq1214 marked this pull request as ready for review August 28, 2026 19:37
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Aug 28, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/agent/runners/tool_loop_agent_runner.py" line_range="693-735" />
<code_context>
+    def _context_usage_fingerprints(
</code_context>
<issue_to_address>
**issue (broader_impact):** The snapshot fingerprint omits `req.extra_user_content_parts`, even though `_iter_llm_responses` sends those parts to the provider on every request. If a hook or caller mutates the extra content between tool-loop requests while messages, tools, and provider remain unchanged, the runner reuses the old full prompt usage and underestimates the current context.

**Triggers:** When `extra_user_content_parts` changes in place between two tool-loop requests.

**Suggested fix:** Include the provider-visible extra content parts in the snapshot fingerprint, or invalidate the snapshot whenever they change.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: astrbot/core/agent/runners/tool_loop_agent_runner.py:735


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.

Comment on lines +693 to +735
def _context_usage_fingerprints(
self,
messages: list[Message],
func_tool: ToolSet | None,
) -> tuple[tuple[str, ...], str | None]:
"""Build immutable fingerprints for a context-usage snapshot.

Args:
messages: Messages that form the provider request context.
func_tool: Tools exposed to the provider for that request.

Returns:
Immutable fingerprints for the messages and tool schema.
"""
json_dump_kwargs = {
"default": str,
"ensure_ascii": False,
"separators": (",", ":"),
"sort_keys": True,
}
message_fingerprints = tuple(
hashlib.sha256(
json.dumps(message.model_dump(), **json_dump_kwargs).encode()
).hexdigest()
for message in messages
)
tool_schema_fingerprint = None
if func_tool is not None:
tool_schema_fingerprint = hashlib.sha256(
json.dumps(
[
{
"active": getattr(tool, "active", True),
"description": tool.description,
"name": tool.name,
"parameters": tool.parameters,
}
for tool in func_tool.tools
],
**json_dump_kwargs,
).encode()
).hexdigest()
return message_fingerprints, tool_schema_fingerprint

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (broader_impact): The snapshot fingerprint omits req.extra_user_content_parts, even though _iter_llm_responses sends those parts to the provider on every request. If a hook or caller mutates the extra content between tool-loop requests while messages, tools, and provider remain unchanged, the runner reuses the old full prompt usage and underestimates the current context.

Triggers: When extra_user_content_parts changes in place between two tool-loop requests.

Suggested fix: Include the provider-visible extra content parts in the snapshot fingerprint, or invalidate the snapshot whenever they change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] AstrBot ChatUI 长对话在上下文占用很低时却被提前压缩/截断,导致严重的对话失忆。

1 participant