fix: reorder fake tool call messages at tail to correct timing - #9451
Open
Rail1bc wants to merge 1 commit into
Open
fix: reorder fake tool call messages at tail to correct timing#9451Rail1bc wants to merge 1 commit into
Rail1bc wants to merge 1 commit into
Conversation
…nAI provider
When plugins inject assistant(tool_calls) + tool(result) pairs into
contexts to force tool invocation (fake tool call),
_prepare_chat_payload deepcopies contexts (with pairs at tail) then
appends user_msg, resulting in:
..., assistant(tc), tool, user
LLM sees assistant "predicting" user query. Reorder to:
..., user, assistant(tc), tool
Co-Authored-By: deepseek-v4-flash <deepseek-ai@claude-code-best.win>
📄 Knowledge reviewDosu skipped reviewing this PR because your organization has used its |
Contributor
There was a problem hiding this comment.
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/provider/sources/openai_source.py" line_range="534" />
<code_context>
+ ProviderOpenAIOfficial._reorder_tailing_tool_call_user(payloads)
+
+ @staticmethod
+ def _reorder_tailing_tool_call_user(payloads: dict) -> None:
+ """重排因伪造工具调用导致尾部 assistant(tc) → tool → user 乱序的消息。
+
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring `_reorder_tailing_tool_call_user` to use an index-based scan with a separate pair-validation helper instead of pop-based mutation to clarify its control flow and data handling.
You can simplify `_reorder_tailing_tool_call_user` by making it index-based and separating pair validation from mutation. This reduces control-flow nesting, avoids repeated `pop()`/reverse operations, and makes the behavior easier to reason about.
### 1. Extract a small validator helper
```python
@staticmethod
def _is_valid_tool_pair(asst_msg: dict, tool_msg: dict) -> bool:
if asst_msg.get("role") != "assistant" or tool_msg.get("role") != "tool":
return False
tool_calls = asst_msg.get("tool_calls")
if not tool_calls:
return False
tc_ids = {tc.get("id") for tc in tool_calls if isinstance(tc, dict)}
return tool_msg.get("tool_call_id") in tc_ids
```
### 2. Use indices + slicing instead of pops + reverse
```python
@staticmethod
def _reorder_tailing_tool_call_user(payloads: dict) -> None:
messages = payloads.get("messages")
if not isinstance(messages, list) or len(messages) < 2:
return
# 必须以 user 结尾
if messages[-1].get("role") != "user":
return
# 从尾部向前扫描匹配的 assistant/tool 成对消息
end = len(messages) - 1 # index of tailing user
i = end - 1
while i >= 1:
asst_msg = messages[i - 1]
tool_msg = messages[i]
if not ProviderOpenAIOfficial._is_valid_tool_pair(asst_msg, tool_msg):
break
i -= 2 # 每次向前跳过一对
# 没有成对工具调用,保持原样
if i == end - 1:
return
# 现在 messages 结构为: [ ... prefix ..., pair_start..pair_end, user ]
prefix = messages[:i + 1]
pairs = messages[i + 1:end] # contiguous assistant/tool pairs
user_msg = messages[end]
# 重排:prefix + [user] + pairs
payloads["messages"] = prefix + [user_msg] + pairs
```
This keeps all behavior but removes the mutable stack-like operations and nested breaks, making the tail reordering logic more straightforward and testable.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation / 动机
通过向
req.contexts尾部注入assistant(tool_calls) → tool(result)消息对,可以强制 LLM 认为自己已经调用了某个工具并拿到了结果,从而:这是一种通用手法,已在 LivingMemory 插件中长期记忆注入中使用,未来可能被更多插件用于各种确定性工具调用场景。
但在
OpenAI_Provider._prepare_chat_payload()中,处理顺序是:deepcopy(contexts)—— 此时尾部已含伪造工具调用对append(new_record)—— 追加当前用户消息最终发给 LLM API 的 messages 序列为:
LLM 视角下,assistant 在用户提问之前就"预知"了查询内容。正确顺序应为:
该问题影响所有使用该手法的插件,以及所有走
OpenAI_Provider的 LLM 路径Refs: #9450
Modifications / 改动点
astrbot/core/provider/sources/openai_source.py在
_sanitize_assistant_messages()末尾新增检测与重排逻辑:user消息assistant(tool_calls) + tool成对消息(验证tool_call_id匹配)user → assistant₁, tool₁ → ... → assistant_N, tool_N支持多插件各自注入多轮伪造对的场景。
req对象,不破坏其他on_llm_requesthandler_query/_query_streaming全部路径tool后不会直接跟user,不会误杀此为轻量妥协修复。未来若实现专用的上下文操作钩子或伪造工具调用钩子,可考虑移除此方法。
Test Results / 测试结果
逻辑验证(手动):
..., asst(tc), tool, user→..., user, asst(tc), tool..., asst₁, tool₁, asst₂, tool₂, user→..., user, asst₁, tool₁, asst₂, tool₂..., asst(tc), tool, asst(content), user→ 不变,不误杀Checklist / 检查清单
Summary by Sourcery
Ensure OpenAI provider sends user messages before any injected fake tool-call assistant/tool pairs to maintain correct conversation chronology.
Bug Fixes:
Enhancements: