fix: 修复 run_agent() 共享引用污染和 send_message() 历史丢失#8443
Open
Sisyphbaous-DT-Project wants to merge 3 commits into
Open
fix: 修复 run_agent() 共享引用污染和 send_message() 历史丢失#8443Sisyphbaous-DT-Project wants to merge 3 commits into
Sisyphbaous-DT-Project wants to merge 3 commits into
Conversation
- run_agent() 传递事件结果链时使用 list() 浅拷贝切断共享引用, 防止输出增强等插件的原地修改污染 Provider 层的 LLMResponse, completion_text property 不再被截断或清空 - Context.send_message() 发送成功后自动追加 assistant 消息到 对话历史,解决分段消息和主动消息对 LLM 不可见的问题,含去重检查
Contributor
There was a problem hiding this comment.
Hey - I've found 2 issues, and left some high level feedback:
- The new
send_messagehistory persistence logic duplicates conversation-handling concerns insideContext; consider extracting this into a reusable helper or pushing it down intoconversation_managerso that all history updates follow a single code path. - In
send_message, you're catching a broadExceptionand only logging at debug level; for issues that cause history to stop updating it may be safer to log at warning/error or narrow the exception scope so silent failures are less likely. - The JSON encode/decode of
conv.historyinsidesend_messagetightly couples this method to the storage format; if possible, hide the serialization detail behind the conversation manager (e.g., expose history as a list and let it handle JSON) to make future format changes easier.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `send_message` history persistence logic duplicates conversation-handling concerns inside `Context`; consider extracting this into a reusable helper or pushing it down into `conversation_manager` so that all history updates follow a single code path.
- In `send_message`, you're catching a broad `Exception` and only logging at debug level; for issues that cause history to stop updating it may be safer to log at warning/error or narrow the exception scope so silent failures are less likely.
- The JSON encode/decode of `conv.history` inside `send_message` tightly couples this method to the storage format; if possible, hide the serialization detail behind the conversation manager (e.g., expose history as a list and let it handle JSON) to make future format changes easier.
## Individual Comments
### Comment 1
<location path="astrbot/core/star/context.py" line_range="477-486" />
<code_context>
+ try:
</code_context>
<issue_to_address>
**issue (bug_risk):** The broad `except Exception` here can hide real issues in history persistence.
Catching `Exception` around all history operations will swallow bugs in JSON parsing, retrieval, or updates and only emit a debug log, making data issues hard to debug. Narrow the try/except to the specific risky calls (e.g., `json.loads`), or at least use `logger.exception` and include identifiers like `unified_msg_origin`/`cid` to capture a stack trace and improve observability.
</issue_to_address>
### Comment 2
<location path="astrbot/core/star/context.py" line_range="475" />
<code_context>
if platform.meta().id == session.platform_name:
await platform.send_by_session(session, message_chain)
+
+ # Persist the sent message to conversation history so that
+ # LLM can see proactive / split messages in subsequent turns.
+ try:
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the new history persistence and deduplication logic into dedicated helper methods so `send_message` focuses only on sending messages.
You can keep the new behavior but reduce complexity in `send_message` by extracting the persistence logic and encapsulating the JSON/dedup rules.
### 1. Extract persistence out of `send_message`
Current core loop is doing both sending and history management. Move the persistence into a helper to make `send_message` focus on sending:
```python
for platform in self.platform_manager.platform_insts:
if platform.meta().id == session.platform_name:
await platform.send_by_session(session, message_chain)
await self._persist_sent_message_to_history(session, message_chain)
return True
```
New helper (same class):
```python
async def _persist_sent_message_to_history(self, session: MessageSesion, message_chain):
try:
import json
unified_msg_origin = str(session)
cid = await self.conversation_manager.get_curr_conversation_id(unified_msg_origin)
if not cid:
return
conv = await self.conversation_manager.get_conversation(unified_msg_origin, cid)
if not conv:
return
plain_text = message_chain.get_plain_text()
if not plain_text:
return
history = json.loads(conv.history) if conv.history else []
if self._is_last_assistant_message(history, plain_text):
return
history.append({"role": "assistant", "content": plain_text})
await self.conversation_manager.update_conversation(
unified_msg_origin=unified_msg_origin,
conversation_id=cid,
history=history,
)
except Exception as e:
logger.debug(f"send_message: failed to persist to history: {e}")
```
This keeps all behavior but flattens the control flow in `send_message`.
### 2. Extract the deduplication rule
The inline deduplication check is noisy. Moving it to a small helper improves readability and keeps the rule centralized:
```python
def _is_last_assistant_message(self, history, plain_text: str) -> bool:
if not history or not isinstance(history[-1], dict):
return False
last = history[-1]
return last.get("role") == "assistant" and last.get("content") == plain_text
```
Then the helper reads more declaratively:
```python
if self._is_last_assistant_message(history, plain_text):
return
```
These two extractions keep your new feature intact while reducing nesting, separating responsibilities, and making future changes to history/dedup behavior easier.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Contributor
There was a problem hiding this comment.
Code Review
This pull request converts message chains to lists in astr_agent_run_util.py and adds a mechanism in context.py to persist sent messages to the conversation history. The reviewer identified a potential race condition in send_message when multiple messages are sent concurrently, as the asynchronous history retrieval and update operations are not atomic. They suggested using asyncio.Lock to serialize history updates per session.
将 send_message 中的历史持久化逻辑提取到 _persist_sent_message_to_history 和 _is_last_assistant_message 两个私有方法,减少 send_message 的职责耦合。 将 broad except Exception + logger.debug 改为分级异常处理: - json.JSONDecodeError → logger.warning - Exception → logger.warning 均包含 session 和 cid 标识,便于排查。
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.
Modifications / 改动点
修复两个导致插件发出的消息在 LLM 对话历史中丢失或截断的核心 Bug。
问题 1:
run_agent()共享引用污染run_agent()在构造MessageEventResult时,将 Provider 层LLMResponse.result_chain.chain的 list 对象直接传递(未做拷贝)。当输出增强等插件在OnDecoratingResultEvent阶段对event.get_result().chain执行原地修改(clear()+extend())时,会直接污染 Provider 层的LLMResponse.result_chain,导致completion_textproperty(实时从result_chain计算)被永久截断或清空。根因:
astr_agent_run_util.py两处MessageEventResult(chain=...)直接传递了 list 引用。问题 2:
Context.send_message()不写对话历史Context.send_message()仅将消息投递给平台适配器,无任何逻辑将消息追加到对应会话的conversation.history。所有通过此 API 发出的消息(输出增强的前 N-1 分段、主动消息插件发出的消息、任何插件的主动发送)对 LLM 完全不可见。根因:
context.py中send_message()只调用platform.send_by_session(),未写入conversation_manager。修改的文件
astrbot/core/astr_agent_run_util.py— 两处chain=...改为chain=list(...),浅拷贝切断共享引用astrbot/core/star/context.py—send_message()发送成功后自动追加 assistant 消息到对话历史,含去重检查(跳过最后一条已是同内容 assistant 消息的情况)This is NOT a breaking change. / 这不是一个破坏性变更。
Screenshots or Test Results / 运行截图或测试结果
独立复现脚本
reproduce_bugs.py验证修复前后对比:修复前:
修复后:
Checklist / 检查清单
👀 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.txtandpyproject.toml./ 我确保没有引入新依赖库。
😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
Summary by Sourcery
Ensure assistant messages sent via the platform API are persisted to conversation history and prevent plugins from mutating underlying LLM response chains through shared list references.
Bug Fixes: