refactor(minimax): use send_openai_compatible helper (231 -> 41 lines)
This commit is contained in:
+37
-218
@@ -2230,224 +2230,43 @@ def _send_minimax(md_content: str, user_message: str, base_dir: str,
|
|||||||
qa_callback: Optional[Callable[[str], str]] = None,
|
qa_callback: Optional[Callable[[str], str]] = None,
|
||||||
stream_callback: Optional[Callable[[str], None]] = None,
|
stream_callback: Optional[Callable[[str], None]] = None,
|
||||||
patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> str:
|
patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> str:
|
||||||
"""
|
_ensure_minimax_client()
|
||||||
[C: src/ai_server.py:_handle_send]
|
from src.openai_compatible import OpenAICompatibleRequest, send_openai_compatible
|
||||||
"""
|
from src.vendor_capabilities import get_capabilities
|
||||||
openai = _require_warmed("openai")
|
with _minimax_history_lock:
|
||||||
requests = _require_warmed("requests")
|
_repair_minimax_history(_minimax_history)
|
||||||
try:
|
if discussion_history and not _minimax_history:
|
||||||
mcp_client.configure(file_items or [], [base_dir])
|
_minimax_history.append({"role": "user", "content": f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}"})
|
||||||
creds = _load_credentials()
|
else:
|
||||||
api_key = creds.get("minimax", {}).get("api_key")
|
_minimax_history.append({"role": "user", "content": user_message})
|
||||||
if not api_key:
|
messages = [{"role": "system", "content": f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"}]
|
||||||
raise ValueError("MiniMax API key not found in credentials.toml")
|
messages.extend(_minimax_history)
|
||||||
|
request = OpenAICompatibleRequest(
|
||||||
client = OpenAI(api_key=api_key, base_url="https://api.minimax.io/v1")
|
messages=messages,
|
||||||
|
model=_model,
|
||||||
with _minimax_history_lock:
|
temperature=_temperature,
|
||||||
_repair_minimax_history(_minimax_history)
|
top_p=_top_p,
|
||||||
if discussion_history and not _minimax_history:
|
max_tokens=min(_max_tokens, 8192),
|
||||||
user_content = f"[DISCUSSION HISTORY]\n\n{discussion_history}\n\n---\n\n{user_message}"
|
stream=stream,
|
||||||
else:
|
stream_callback=stream_callback,
|
||||||
user_content = user_message
|
)
|
||||||
_minimax_history.append({"role": "user", "content": user_content})
|
caps = get_capabilities("minimax", _model)
|
||||||
|
response = send_openai_compatible(_minimax_client, request, capabilities=caps)
|
||||||
all_text_parts: list[str] = []
|
reasoning_content = ""
|
||||||
_cumulative_tool_bytes = 0
|
if response.raw_response and hasattr(response.raw_response, "choices"):
|
||||||
|
choice = response.raw_response.choices[0]
|
||||||
for round_idx in range(MAX_TOOL_ROUNDS + 2):
|
if hasattr(choice.message, "reasoning_details") and choice.message.reasoning_details:
|
||||||
current_api_messages: list[dict[str, Any]] = []
|
reasoning_content = choice.message.reasoning_details[0].get("text", "") if choice.message.reasoning_details else ""
|
||||||
|
thinking_tags = ""
|
||||||
sys_msg = {"role": "system", "content": f"{_get_combined_system_prompt()}\n\n<context>\n{md_content}\n</context>"}
|
if reasoning_content:
|
||||||
current_api_messages.append(sys_msg)
|
thinking_tags = f"<thinking>\n{reasoning_content}\n</thinking>\n"
|
||||||
|
full_text = thinking_tags + response.text
|
||||||
with _minimax_history_lock:
|
with _minimax_history_lock:
|
||||||
dropped = _trim_minimax_history([sys_msg], _minimax_history)
|
msg_to_store: dict[str, Any] = {"role": "assistant", "content": response.text or None}
|
||||||
if dropped > 0:
|
if reasoning_content:
|
||||||
_append_comms("OUT", "request", {"message": f"[MINIMAX HISTORY TRIMMED: dropped {dropped} old messages]"})
|
msg_to_store["reasoning_content"] = reasoning_content
|
||||||
|
_minimax_history.append(msg_to_store)
|
||||||
for i, msg in enumerate(_minimax_history):
|
return full_text
|
||||||
role = msg.get("role")
|
|
||||||
api_msg = {"role": role}
|
|
||||||
|
|
||||||
content = msg.get("content")
|
|
||||||
if role == "assistant":
|
|
||||||
if msg.get("tool_calls"):
|
|
||||||
api_msg["content"] = content or None
|
|
||||||
api_msg["tool_calls"] = msg["tool_calls"]
|
|
||||||
else:
|
|
||||||
api_msg["content"] = content or ""
|
|
||||||
elif role == "tool":
|
|
||||||
api_msg["content"] = content or ""
|
|
||||||
api_msg["tool_call_id"] = msg.get("tool_call_id")
|
|
||||||
else:
|
|
||||||
api_msg["content"] = content or ""
|
|
||||||
|
|
||||||
current_api_messages.append(api_msg)
|
|
||||||
|
|
||||||
request_payload: dict[str, Any] = {
|
|
||||||
"model": _model,
|
|
||||||
"messages": current_api_messages,
|
|
||||||
"stream": stream,
|
|
||||||
"extra_body": {"reasoning_split": True},
|
|
||||||
}
|
|
||||||
|
|
||||||
if stream:
|
|
||||||
request_payload["stream_options"] = {"include_usage": True}
|
|
||||||
|
|
||||||
request_payload["temperature"] = 1.0
|
|
||||||
request_payload["top_p"] = _top_p
|
|
||||||
request_payload["max_tokens"] = min(_max_tokens, 8192)
|
|
||||||
|
|
||||||
tools = _get_deepseek_tools()
|
|
||||||
if tools:
|
|
||||||
request_payload["tools"] = tools
|
|
||||||
|
|
||||||
events.emit("request_start", payload={"provider": "minimax", "model": _model, "round": round_idx, "streaming": stream})
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = client.chat.completions.create(**request_payload, timeout=120)
|
|
||||||
except Exception as e:
|
|
||||||
raise _classify_minimax_error(e) from e
|
|
||||||
|
|
||||||
assistant_text = ""
|
|
||||||
tool_calls_raw = []
|
|
||||||
reasoning_content = ""
|
|
||||||
finish_reason = "stop"
|
|
||||||
usage = {}
|
|
||||||
|
|
||||||
if stream:
|
|
||||||
aggregated_content = ""
|
|
||||||
aggregated_tool_calls: list[dict[str, Any]] = []
|
|
||||||
aggregated_reasoning = ""
|
|
||||||
current_usage: dict[str, Any] = {}
|
|
||||||
final_finish_reason = "stop"
|
|
||||||
|
|
||||||
for chunk in response:
|
|
||||||
if not chunk.choices:
|
|
||||||
if chunk.usage:
|
|
||||||
current_usage = chunk.usage.model_dump()
|
|
||||||
continue
|
|
||||||
|
|
||||||
delta = chunk.choices[0].delta
|
|
||||||
if delta.content:
|
|
||||||
content_chunk = delta.content
|
|
||||||
aggregated_content += content_chunk
|
|
||||||
if stream_callback:
|
|
||||||
stream_callback(content_chunk)
|
|
||||||
|
|
||||||
if hasattr(delta, "reasoning_details") and delta.reasoning_details:
|
|
||||||
for detail in delta.reasoning_details:
|
|
||||||
if "text" in detail:
|
|
||||||
aggregated_reasoning += detail["text"]
|
|
||||||
|
|
||||||
if delta.tool_calls:
|
|
||||||
for tc_delta in delta.tool_calls:
|
|
||||||
idx = tc_delta.index
|
|
||||||
while len(aggregated_tool_calls) <= idx:
|
|
||||||
aggregated_tool_calls.append({"id": "", "type": "function", "function": {"name": "", "arguments": ""}})
|
|
||||||
target = aggregated_tool_calls[idx]
|
|
||||||
if tc_delta.id:
|
|
||||||
target["id"] = tc_delta.id
|
|
||||||
if tc_delta.function and tc_delta.function.name:
|
|
||||||
target["function"]["name"] += tc_delta.function.name
|
|
||||||
if tc_delta.function and tc_delta.function.arguments:
|
|
||||||
target["function"]["arguments"] += tc_delta.function.arguments
|
|
||||||
|
|
||||||
if chunk.choices[0].finish_reason:
|
|
||||||
final_finish_reason = chunk.choices[0].finish_reason
|
|
||||||
if chunk.usage:
|
|
||||||
current_usage = chunk.usage.model_dump()
|
|
||||||
|
|
||||||
assistant_text = aggregated_content
|
|
||||||
tool_calls_raw = aggregated_tool_calls
|
|
||||||
reasoning_content = aggregated_reasoning
|
|
||||||
finish_reason = final_finish_reason
|
|
||||||
usage = current_usage
|
|
||||||
else:
|
|
||||||
choice = response.choices[0]
|
|
||||||
message = choice.message
|
|
||||||
assistant_text = message.content or ""
|
|
||||||
tool_calls_raw = message.tool_calls or []
|
|
||||||
if hasattr(message, "reasoning_details") and message.reasoning_details:
|
|
||||||
reasoning_content = message.reasoning_details[0].get("text", "") if message.reasoning_details else ""
|
|
||||||
finish_reason = choice.finish_reason or "stop"
|
|
||||||
usage = response.usage.model_dump() if response.usage else {}
|
|
||||||
|
|
||||||
thinking_tags = ""
|
|
||||||
if reasoning_content:
|
|
||||||
thinking_tags = f"<thinking>\n{reasoning_content}\n</thinking>\n"
|
|
||||||
full_assistant_text = thinking_tags + assistant_text
|
|
||||||
|
|
||||||
with _minimax_history_lock:
|
|
||||||
msg_to_store: dict[str, Any] = {"role": "assistant", "content": assistant_text or None}
|
|
||||||
if reasoning_content:
|
|
||||||
msg_to_store["reasoning_content"] = reasoning_content
|
|
||||||
if tool_calls_raw:
|
|
||||||
msg_to_store["tool_calls"] = tool_calls_raw
|
|
||||||
_minimax_history.append(msg_to_store)
|
|
||||||
|
|
||||||
if full_assistant_text:
|
|
||||||
all_text_parts.append(full_assistant_text)
|
|
||||||
|
|
||||||
_append_comms("IN", "response", {
|
|
||||||
"round": round_idx,
|
|
||||||
"stop_reason": finish_reason,
|
|
||||||
"text": full_assistant_text,
|
|
||||||
"tool_calls": tool_calls_raw,
|
|
||||||
"usage": usage,
|
|
||||||
"streaming": stream
|
|
||||||
})
|
|
||||||
|
|
||||||
if finish_reason != "tool_calls" and not tool_calls_raw:
|
|
||||||
break
|
|
||||||
if round_idx > MAX_TOOL_ROUNDS:
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
results = asyncio.run_coroutine_threadsafe(
|
|
||||||
_execute_tool_calls_concurrently(tool_calls_raw, base_dir, pre_tool_callback, qa_callback, round_idx, "minimax", patch_callback),
|
|
||||||
loop
|
|
||||||
).result()
|
|
||||||
except RuntimeError:
|
|
||||||
results = asyncio.run(_execute_tool_calls_concurrently(tool_calls_raw, base_dir, pre_tool_callback, qa_callback, round_idx, "minimax", patch_callback))
|
|
||||||
|
|
||||||
tool_results_for_history: list[dict[str, Any]] = []
|
|
||||||
for i, (name, call_id, out, _) in enumerate(results):
|
|
||||||
if i == len(results) - 1:
|
|
||||||
if file_items:
|
|
||||||
file_items, changed = _reread_file_items(file_items)
|
|
||||||
ctx = _build_file_diff_text(changed)
|
|
||||||
if ctx:
|
|
||||||
out += f"\n\n{_get_context_marker()}\n\n{ctx}"
|
|
||||||
if round_idx == MAX_TOOL_ROUNDS:
|
|
||||||
out += "\n\n[SYSTEM: MAX ROUNDS. PROVIDE FINAL ANSWER.]"
|
|
||||||
|
|
||||||
truncated = _truncate_tool_output(out)
|
|
||||||
_cumulative_tool_bytes += len(truncated)
|
|
||||||
tool_results_for_history.append({
|
|
||||||
"role": "tool",
|
|
||||||
"tool_call_id": call_id,
|
|
||||||
"content": truncated,
|
|
||||||
})
|
|
||||||
_append_comms("IN", "tool_result", {"name": name, "id": call_id, "output": out})
|
|
||||||
events.emit("tool_execution", payload={"status": "completed", "tool": name, "result": out, "round": round_idx})
|
|
||||||
|
|
||||||
if _cumulative_tool_bytes > _MAX_TOOL_OUTPUT_BYTES:
|
|
||||||
tool_results_for_history.append({
|
|
||||||
"role": "user",
|
|
||||||
"content": f"SYSTEM WARNING: Cumulative tool output exceeded {_MAX_TOOL_OUTPUT_BYTES // 1000}KB budget. Provide your final answer now."
|
|
||||||
})
|
|
||||||
_append_comms("OUT", "request", {"message": f"[TOOL OUTPUT BUDGET EXCEEDED: {_cumulative_tool_bytes} bytes]"})
|
|
||||||
|
|
||||||
with _minimax_history_lock:
|
|
||||||
for tr in tool_results_for_history:
|
|
||||||
_minimax_history.append(tr)
|
|
||||||
|
|
||||||
return "\n\n".join(all_text_parts) if all_text_parts else "(No text returned)"
|
|
||||||
except Exception as e:
|
|
||||||
raise _classify_minimax_error(e) from e
|
|
||||||
|
|
||||||
#endregion: MiniMax Provider
|
#endregion: MiniMax Provider
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user