diff --git a/astrbot/core/provider/sources/openai_source.py b/astrbot/core/provider/sources/openai_source.py index 117cfb4922..c578e26d28 100644 --- a/astrbot/core/provider/sources/openai_source.py +++ b/astrbot/core/provider/sources/openai_source.py @@ -560,7 +560,36 @@ def _is_empty(content: Any) -> bool: cleaned.append(msg) - payloads["messages"] = cleaned + # Drop orphaned tool messages whose assistant(tool_calls) was + # removed by context truncation / compression, preventing + # 400 "unexpected tool_use_id in tool_result" from the API. + valid_tc_ids = set() + final: list = [] + _orphan_count = 0 + for msg in cleaned: + if not isinstance(msg, dict): + final.append(msg) + continue + role = msg.get("role") + if role == "assistant" and msg.get("tool_calls"): + valid_tc_ids = { + tc["id"] + for tc in msg["tool_calls"] + if isinstance(tc, dict) and "id" in tc + } + final.append(msg) + elif role == "tool": + if msg.get("tool_call_id") in valid_tc_ids: + final.append(msg) + valid_tc_ids.discard(msg.get("tool_call_id")) + else: + _orphan_count += 1 + else: + valid_tc_ids = set() + final.append(msg) + if _orphan_count: + logger.debug("Filtered %d orphaned tool message(s)", _orphan_count) + payloads["messages"] = final async def _query(self, payloads: dict, tools: ToolSet | None) -> LLMResponse: if tools: