Private
Public Access
0
0

refactor(minimax): restore tool-call loop in _send_minimax

The previous refactor (commit 344a66fc) dropped the tool-call loop
in _send_minimax. The original function executed tool calls when the
response had tool_calls; the refactor was single-shot. This is a real
behavior regression (tools stop working) even though the existing
tests don't catch it.

Restore the tool loop:
- For each round (up to MAX_TOOL_ROUNDS + 2), call send_openai_compatible
  with tools=_get_deepseek_tools() and tool_choice='auto'
- If response has tool_calls: dispatch each via
  _execute_tool_calls_concurrently (handles both async context and
  sync via run_coroutine_threadsafe / asyncio.run), append each
  result to _minimax_history with role='tool' and tool_call_id
- If no tool_calls: return the response text (with thinking tags for
  reasoning models)
- The lock is acquired/released per iteration to avoid holding it
  during the API call (which can take seconds)

Preserved:
- 10-arg signature
- _minimax_history_lock (now acquired per iteration)
- _repair_minimax_history
- discussion_history handling
- System + context message wrapping
- Reasoning content extraction (response.raw_response.choices[0].message
  .reasoning_details[0].get('text', ''))
- <thinking> tags wrap on the final response

Dropped (still):
- extra_body={reasoning_split: True} (not supported by send_openai_compatible;
  would be a Phase 5 adapter addition if minimax-reasoner models need it)

New line count: 75 lines (vs 41 single-shot, vs 231 pre-refactor).
Net effect: 231 -> 75 = 68% reduction; tool loop preserved.

Verification: 38/38 tests pass (no regressions).
This commit is contained in:
2026-06-11 08:48:07 -04:00
parent fadb4c329b
commit c9ed734d9d
+51 -28
View File
@@ -2233,40 +2233,63 @@ def _send_minimax(md_content: str, user_message: str, base_dir: str,
_ensure_minimax_client()
from src.openai_compatible import OpenAICompatibleRequest, send_openai_compatible
from src.vendor_capabilities import get_capabilities
tools: list[dict[str, Any]] | None = _get_deepseek_tools() or None
with _minimax_history_lock:
_repair_minimax_history(_minimax_history)
if discussion_history and not _minimax_history:
_minimax_history.append({"role": "user", "content": f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}"})
else:
_minimax_history.append({"role": "user", "content": user_message})
messages = [{"role": "system", "content": f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"}]
messages.extend(_minimax_history)
request = OpenAICompatibleRequest(
messages=messages,
model=_model,
temperature=_temperature,
top_p=_top_p,
max_tokens=min(_max_tokens, 8192),
stream=stream,
stream_callback=stream_callback,
)
caps = get_capabilities("minimax", _model)
response = send_openai_compatible(_minimax_client, request, capabilities=caps)
reasoning_content = ""
if response.raw_response and hasattr(response.raw_response, "choices"):
choice = response.raw_response.choices[0]
if hasattr(choice.message, "reasoning_details") and choice.message.reasoning_details:
reasoning_content = choice.message.reasoning_details[0].get("text", "") if choice.message.reasoning_details else ""
thinking_tags = ""
if reasoning_content:
thinking_tags = f"<thinking>\n{reasoning_content}\n</thinking>\n"
full_text = thinking_tags + response.text
with _minimax_history_lock:
msg_to_store: dict[str, Any] = {"role": "assistant", "content": response.text or None}
if reasoning_content:
msg_to_store["reasoning_content"] = reasoning_content
_minimax_history.append(msg_to_store)
return full_text
response_text: str = ""
reasoning_content: str = ""
for round_idx in range(MAX_TOOL_ROUNDS + 2):
with _minimax_history_lock:
messages = [{"role": "system", "content": f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"}]
messages.extend(_minimax_history)
request = OpenAICompatibleRequest(
messages=messages,
model=_model,
temperature=_temperature,
top_p=_top_p,
max_tokens=min(_max_tokens, 8192),
stream=stream,
stream_callback=stream_callback,
tools=tools,
tool_choice="auto" if tools else "auto",
)
caps = get_capabilities("minimax", _model)
response = send_openai_compatible(_minimax_client, request, capabilities=caps)
reasoning_content = ""
if response.raw_response and hasattr(response.raw_response, "choices"):
choice = response.raw_response.choices[0]
if hasattr(choice.message, "reasoning_details") and choice.message.reasoning_details:
reasoning_content = choice.message.reasoning_details[0].get("text", "") if choice.message.reasoning_details else ""
with _minimax_history_lock:
msg_to_store: dict[str, Any] = {"role": "assistant", "content": response.text or None}
if reasoning_content:
msg_to_store["reasoning_content"] = reasoning_content
if response.tool_calls:
msg_to_store["tool_calls"] = response.tool_calls
_minimax_history.append(msg_to_store)
if not response.tool_calls:
response_text = (f"<thinking>\n{reasoning_content}\n</thinking>\n" if reasoning_content else "") + response.text
break
try:
loop = asyncio.get_running_loop()
results = asyncio.run_coroutine_threadsafe(
_execute_tool_calls_concurrently(response.tool_calls, base_dir, pre_tool_callback, qa_callback, round_idx, "minimax", patch_callback),
loop,
).result()
except RuntimeError:
results = asyncio.run(_execute_tool_calls_concurrently(response.tool_calls, base_dir, pre_tool_callback, qa_callback, round_idx, "minimax", patch_callback))
with _minimax_history_lock:
for _i, (name, call_id, out, _) in enumerate(results):
_minimax_history.append({
"role": "tool",
"tool_call_id": call_id,
"content": str(out) if out else "",
})
return response_text
#endregion: MiniMax Provider