Skip to content

Conversation

@huangyajie
Copy link

Added automatic cleanup of incomplete tool-call messages. In miloco_server/schema/chat_history_schema.py, ChatHistoryMessages now detects assistant messages with tool_calls that are missing corresponding tool replies (typical when the user hits Stop mid-flow) and drops them before reusing or serializing history. This prevents the LLM from rejecting the conversation with “tool_calls must be followed by tool messages.”

@CLAassistant
Copy link

CLAassistant commented Nov 20, 2025

CLA assistant check
All committers have signed the CLA.

@MiaoChangyu
Copy link
Collaborator

@huangyajie Let me try to understand what you mean. When the model intends to call Method A and Method B, suppose it has just finished calling Method A, and then the user interrupts the request. This results in the context containing Request A, Request B, and Result A. If you then send a follow-up query to the model, it may lead to certain errors.
Are you looking to fix this issue?

我尝试理解下你的意思,当模型想调用方法a和方法b时,此时刚调用完a,然后用户中断了请求,导致上下文中有请求a、请求b,结果a。再追问发给模型可能导致一些错误。 你是想修复这个问题吗

@MiaoChangyu
Copy link
Collaborator

@huangyajie How is the performance? It seems that every read or write operation requires an analysis of the entire message content?性能情况怎么样,看起来每次读写都要对整个message内容进行一次分析?

@huangyajie
Copy link
Author

huangyajie commented Nov 21, 2025

1,我尝试理解下你的意思,当模型想调用方法a和方法b时,此时刚调用完a,然后用户中断了请求,导致上下文中有请求a、请求b,结果a。再追问发给模型可能导致一些错误。 你是想修复这个问题吗
---是的,目前版本存在这个问题,是必现的,出现后,只能重启服务才能恢复
2,性能情况怎么样,看起来每次读写都要对整个message内容进行一次分析?
---是的,这个可以现考虑下是否有其它更高效的方法,我正常测试使用,性能没有什么问题

break

matched_ids: set[str] = set()
for tool_msg in tool_msgs:
Copy link
Collaborator

Choose a reason for hiding this comment

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

如果tool和tool_call的数量一样,是不是就没必要一个个去找了?

Copy link
Author

Choose a reason for hiding this comment

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

数字相等本身不够,仍需确认这些 tool 消息就是紧跟该 assistant 的、且 tool_call_id 对得上。否则可能误把前一次/后一次的 tool 消息算进来,依然会触发 400

@MiaoChangyu
Copy link
Collaborator

@huangyajie

  1. 出现这个问题后,切换一个新的对话,不是就重建了一个上下文吗?此时能不能恢复。一定得重启吗?
  2. 可以再想想怎么设计高效下,另外有个comment

整体上看是一个很好的修改,有新的消息及时回复下我哈

@huangyajie
Copy link
Author

是的,目前版本存在这个问题,是必现的,出现后,只能重启服务才能恢复

@huangyajie

  1. 出现这个问题后,切换一个新的对话,不是就重建了一个上下文吗?此时能不能恢复。一定得重启吗?
  2. 可以再想想怎么设计高效下,另外有个comment

整体上看是一个很好的修改,有新的消息及时回复下我哈

好的,这2个问题,我再确认下

@huangyajie
Copy link
Author

是的,目前版本存在这个问题,是必现的,出现后,只能重启服务才能恢复

@huangyajie

  1. 出现这个问题后,切换一个新的对话,不是就重建了一个上下文吗?此时能不能恢复。一定得重启吗?
  2. 可以再想想怎么设计高效下,另外有个comment

整体上看是一个很好的修改,有新的消息及时回复下我哈

问题1:切换一个新的对话可以恢复
问题2:调整清理策略,看下是否有问题 ?
(1)仅增量处理尾部:记录 _last_sanitized_len,只扫描新增尾段(加一条回看),避免每次全量遍历。
(2)保留未完成调用即裁剪:尾段遇到带 tool_calls 的 assistant 且后续 tool 消息未覆盖其所有 tool_call_id 时,丢掉该 assistant、紧跟的 tool 消息,以及紧邻之前的触发 user 消息,防止断开的调用残留。
(3)处理完整则保留:若尾段的调用配套齐全,保持消息不变并更新已清理位置。
(4) 每次调用 _sanitize_incomplete_tool_calls 的时间复杂度是 O(k),k 为自上次清理后新增的消息数(含回看 1 条)。首次运行或 _last_sanitized_len 为 0 时 k = n(全量),最坏 O(n)
代码调整如下:
image

… assistant tool calls along with their triggering user and tool messages to keep history valid
@MiaoChangyu
Copy link
Collaborator

@huangyajie 新的代码提交进去了吗,我这边没问题了,准备merge吧

@huangyajie
Copy link
Author

@huangyajie 新的代码提交进去了吗,我这边没问题了,准备merge吧

提交过了
image

@MiaoChangyu
Copy link
Collaborator

@yangbaofu007 辛苦看看,再点一个approve

if tool_call_ids:
tool_msgs: list[ChatCompletionMessageParam] = []
j = i + 1
while j < len(self._messages):
Copy link
Collaborator

Choose a reason for hiding this comment

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

代码嵌套比较深,这一层的while循环应该是可以去掉的,直接用66行while循环来统一迭代

Copy link
Author

Choose a reason for hiding this comment

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

内层 while j < len(self._messages) 不是多余的,它和外层 while i < len(self._messages) 作用不同:
a.外层遍历尾段每一条消息,遇到 assistant 才进入检查。
b.内层只在发现带 tool_calls 的 assistant 时,向后连续收集紧跟的 tool 消息,并把索引跳到第一条非 tool 消息(i = j)。这样才能一次性判断这一组调用是否完整。
如果去掉内层、依靠外层循环逐条前进,就无法在一次判断里拿到整组连续的 tool 消息,也无法把 i 一步跳过这些消息,逻辑会变复杂且容易漏判/误删。

@yangbaofu007
Copy link
Collaborator

@huangyajie 这是很好的一个修改,代码实现上可以再优化一下,不用那么深的嵌套。

@huangyajie
Copy link
Author

@yangbaofu007 你好,虽然存在嵌套,但外层循环的 i 会在内层循环结束后赋值为 j,与共用一个循环是性能一样的。
image

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants