fix(minimax): implement history repair and truncation to prevent tool sequence errors
- Add _repair_minimax_history to close dangling tool calls from interrupted sessions. - Add _trim_minimax_history to manage token limits and intelligently prune history. - Integrate repair and trimming into _send_minimax loop. - Resolves MiniMax error 2013 (tool call result does not follow tool call).
This commit is contained in:
+1
-1
@@ -283,5 +283,5 @@ This file tracks all major tracks for the project. Each track has its own detail
|
||||
|
||||
---
|
||||
|
||||
- [ ] **Track: Fix MiniMax history sequencing and truncation**
|
||||
- [x] **Track: Fix MiniMax history sequencing and truncation**
|
||||
*Link: [./tracks/minimax_history_fix_20260601/](./tracks/minimax_history_fix_20260601/)*
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# Implementation Plan: MiniMax History Fix
|
||||
|
||||
## Phase 1: Implementation
|
||||
- [ ] Task: Implement History Repair
|
||||
- [ ] Create `_repair_minimax_history(history: list[dict[str, Any]])` in `src/ai_client.py`.
|
||||
- [ ] Logic: Check if the last message is `assistant` with `tool_calls`. If so, append a `tool` message for each `tool_call_id` with an error message indicating the session was interrupted.
|
||||
- [ ] Task: Implement History Truncation
|
||||
- [ ] Create `_trim_minimax_history(system_blocks: list[dict[str, Any]], history: list[dict[str, Any]])` in `src/ai_client.py` (adapt from `_trim_anthropic_history`).
|
||||
- [ ] Logic: Iteratively remove oldest assistant/user pairs if the estimated tokens exceed the context limit.
|
||||
- [ ] Task: Integrate into `_send_minimax`
|
||||
- [ ] Call `_repair_minimax_history(_minimax_history)` within the initial `_minimax_history_lock` block.
|
||||
- [ ] Call `_trim_minimax_history` at the start of the `for round_idx` loop.
|
||||
- [ ] Log dropped messages to `_append_comms`.
|
||||
- [x] Task: Implement History Repair
|
||||
- [x] Create `_repair_minimax_history(history: list[dict[str, Any]])` in `src/ai_client.py`.
|
||||
- [x] Logic: Check if the last message is `assistant` with `tool_calls`. If so, append a `tool` message for each `tool_call_id` with an error message indicating the session was interrupted.
|
||||
- [x] Task: Implement History Truncation
|
||||
- [x] Create `_trim_minimax_history(system_blocks: list[dict[str, Any]], history: list[dict[str, Any]])` in `src/ai_client.py` (adapt from `_trim_anthropic_history`).
|
||||
- [x] Logic: Iteratively remove oldest assistant/user pairs if the estimated tokens exceed the context limit.
|
||||
- [x] Task: Integrate into `_send_minimax`
|
||||
- [x] Call `_repair_minimax_history(_minimax_history)` within the initial `_minimax_history_lock` block.
|
||||
- [x] Call `_trim_minimax_history` at the start of the `for round_idx` loop.
|
||||
- [x] Log dropped messages to `_append_comms`.
|
||||
|
||||
## Phase 2: Verification
|
||||
- [ ] Task: Verification
|
||||
- [ ] Verify that switching to MiniMax and executing a tool call, then forcibly stopping (e.g., clearing the app or throwing an error in a tool), allows the next request to succeed.
|
||||
- [ ] Task: Conductor - User Manual Verification 'Phase 2: Verification' (Protocol in workflow.md)
|
||||
- [x] Task: Verification
|
||||
- [x] Verify that switching to MiniMax and executing a tool call, then forcibly stopping (e.g., clearing the app or throwing an error in a tool), allows the next request to succeed.
|
||||
- [x] Task: Conductor - User Manual Verification 'Phase 2: Verification' (Protocol in workflow.md)
|
||||
@@ -2091,6 +2091,48 @@ def _list_minimax_models(api_key: str) -> list[str]:
|
||||
pass
|
||||
return ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"]
|
||||
|
||||
def _repair_minimax_history(history: list[dict[str, Any]]) -> None:
|
||||
if not history:
|
||||
return
|
||||
last = history[-1]
|
||||
if last.get("role") != "assistant":
|
||||
return
|
||||
tool_calls = last.get("tool_calls", [])
|
||||
if not tool_calls:
|
||||
return
|
||||
call_ids = []
|
||||
for tc in tool_calls:
|
||||
if hasattr(tc, "id"): call_ids.append(tc.id)
|
||||
elif isinstance(tc, dict) and tc.get("id"): call_ids.append(tc["id"])
|
||||
|
||||
for cid in call_ids:
|
||||
already_has = any(m.get("role") == "tool" and m.get("tool_call_id") == cid for m in history[-len(call_ids)-1:])
|
||||
if not already_has:
|
||||
history.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": cid,
|
||||
"content": "ERROR: Session was interrupted before tool result was recorded.",
|
||||
})
|
||||
|
||||
def _trim_minimax_history(system_blocks: list[dict[str, Any]], history: list[dict[str, Any]]) -> int:
|
||||
est = _estimate_prompt_tokens(system_blocks, history)
|
||||
limit = 180_000
|
||||
if est <= limit:
|
||||
return 0
|
||||
dropped = 0
|
||||
while len(history) > 3 and est > limit:
|
||||
if history[1].get("role") == "assistant" and len(history) > 2 and history[2].get("role") == "user":
|
||||
removed_asst = history.pop(1)
|
||||
removed_user = history.pop(1)
|
||||
dropped += 2
|
||||
est -= _estimate_message_tokens(removed_asst)
|
||||
est -= _estimate_message_tokens(removed_user)
|
||||
else:
|
||||
removed = history.pop(1)
|
||||
dropped += 1
|
||||
est -= _estimate_message_tokens(removed)
|
||||
return dropped
|
||||
|
||||
def _ensure_minimax_client() -> None:
|
||||
global _minimax_client
|
||||
if _minimax_client is None:
|
||||
@@ -2121,6 +2163,7 @@ def _send_minimax(md_content: str, user_message: str, base_dir: str,
|
||||
client = OpenAI(api_key=api_key, base_url="https://api.minimax.io/v1")
|
||||
|
||||
with _minimax_history_lock:
|
||||
_repair_minimax_history(_minimax_history)
|
||||
if discussion_history and not _minimax_history:
|
||||
user_content = f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}"
|
||||
else:
|
||||
@@ -2137,6 +2180,10 @@ def _send_minimax(md_content: str, user_message: str, base_dir: str,
|
||||
current_api_messages.append(sys_msg)
|
||||
|
||||
with _minimax_history_lock:
|
||||
dropped = _trim_minimax_history([sys_msg], _minimax_history)
|
||||
if dropped > 0:
|
||||
_append_comms("OUT", "request", {"message": f"[MINIMAX HISTORY TRIMMED: dropped {dropped} old messages]"})
|
||||
|
||||
for i, msg in enumerate(_minimax_history):
|
||||
role = msg.get("role")
|
||||
api_msg = {"role": role}
|
||||
|
||||
Reference in New Issue
Block a user