refactor(ai_client): migrate _extract_gemini_thoughts + _list_minimax_models (Phase 11 sites 7+8)
Site 7 (_extract_gemini_thoughts): try: getattr(resp, 'candidates', None) or [] ... chunks.append(p.text) except Exception: pass return ''.join(chunks).strip() Body: pass + empty default '' = SS violation (silent + data loss). Site 8 (_list_minimax_models): try: client.models.list() ... if found: return sorted(found) except Exception: pass return ['MiniMax-M2.7', 'MiniMax-M2.5', 'MiniMax-M2.1', 'MiniMax-M2'] Body: pass + hardcoded default = SS violation. New helpers: - _extract_gemini_thoughts_result(resp) -> Result[str] Returns Result(data=thinking_text) on success, Result(data='', errors=[ErrorInfo]) on attribute access failure. - _list_minimax_models_result(api_key) -> Result[list[str]] Returns Result(data=sorted_models) on success, Result(data=defaults, errors=[ErrorInfo]) on SDK failure. Defaults extracted to _MINIMAX_DEFAULT_MODELS module constant. Legacy wrappers delegate to _result helpers and return result.data. Audit: ai_client SS 7 -> 5. COMPLIANT 29 -> 31.
This commit is contained in:
+45
-13
@@ -1748,12 +1748,14 @@ def _send_cli_round_result(r_idx: int, adapter: Any, payload: Any, safety_settin
|
||||
errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=str(e), source="ai_client._send_cli_round_result", original=e)],
|
||||
)
|
||||
|
||||
def _extract_gemini_thoughts(resp: Any) -> str:
|
||||
"""
|
||||
Extracts concatenated thinking text from a Gemini response object's parts.
|
||||
Parts with thought=True are thinking segments; parts with thought=False or unset are visible text.
|
||||
The google-genai SDK filters thoughts out of resp.text, so we must scan parts directly.
|
||||
Returns "" if no thoughts are present.
|
||||
def _extract_gemini_thoughts_result(resp: Any) -> Result[str]:
|
||||
"""Extracts concatenated thinking text from a Gemini response object's parts.
|
||||
|
||||
Per the data-oriented convention: returns Result(data=thinking_text) on
|
||||
success, Result(data="", errors=[ErrorInfo]) if attribute access fails.
|
||||
The legacy caller (_extract_gemini_thoughts) returns result.data
|
||||
(preserving the original str signature; an empty string signals "no
|
||||
thoughts" to the caller).
|
||||
"""
|
||||
chunks: list[str] = []
|
||||
try:
|
||||
@@ -1765,8 +1767,22 @@ def _extract_gemini_thoughts(resp: Any) -> str:
|
||||
for p in parts:
|
||||
if getattr(p, "thought", False) and getattr(p, "text", None):
|
||||
chunks.append(p.text)
|
||||
except Exception: pass
|
||||
return "".join(chunks).strip()
|
||||
return Result(data="".join(chunks).strip())
|
||||
except Exception as e:
|
||||
return Result(
|
||||
data="",
|
||||
errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=f"failed to extract gemini thoughts: {e}", source="ai_client._extract_gemini_thoughts_result", original=e)],
|
||||
)
|
||||
|
||||
|
||||
def _extract_gemini_thoughts(resp: Any) -> str:
|
||||
"""
|
||||
Extracts concatenated thinking text from a Gemini response object's parts.
|
||||
Parts with thought=True are thinking segments; parts with thought=False or unset are visible text.
|
||||
The google-genai SDK filters thoughts out of resp.text, so we must scan parts directly.
|
||||
Returns "" if no thoughts are present.
|
||||
"""
|
||||
return _extract_gemini_thoughts_result(resp).data
|
||||
|
||||
def _get_gemini_history_list(chat: Any | None) -> list[Any]:
|
||||
if not chat: return []
|
||||
@@ -2402,8 +2418,17 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
|
||||
#region: MiniMax Provider
|
||||
|
||||
_MINIMAX_DEFAULT_MODELS: list[str] = ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"]
|
||||
|
||||
#TODO(Ed): This causes a pause on gui thread, this should be cached.
|
||||
def _list_minimax_models(api_key: str) -> list[str]:
|
||||
def _list_minimax_models_result(api_key: str) -> Result[list[str]]:
|
||||
"""List available MiniMax models via the OpenAI-compatible SDK.
|
||||
|
||||
Returns Result(data=sorted_models) on success, Result(data=defaults, errors=[ErrorInfo])
|
||||
on SDK failure. The legacy caller (_list_minimax_models) returns result.data
|
||||
(preserving the original list[str] signature; defaults are returned on failure
|
||||
to maintain the original behavior).
|
||||
"""
|
||||
try:
|
||||
openai = _require_warmed("openai")
|
||||
OpenAI = openai.OpenAI
|
||||
@@ -2413,10 +2438,17 @@ def _list_minimax_models(api_key: str) -> list[str]:
|
||||
models_list = client.models.list()
|
||||
found = [m.id for m in models_list]
|
||||
if found:
|
||||
return sorted(found)
|
||||
except Exception:
|
||||
pass
|
||||
return ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"]
|
||||
return Result(data=sorted(found))
|
||||
return Result(data=_MINIMAX_DEFAULT_MODELS)
|
||||
except Exception as e:
|
||||
return Result(
|
||||
data=_MINIMAX_DEFAULT_MODELS,
|
||||
errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=f"failed to list minimax models: {e}", source="ai_client._list_minimax_models_result", original=e)],
|
||||
)
|
||||
|
||||
|
||||
def _list_minimax_models(api_key: str) -> list[str]:
|
||||
return _list_minimax_models_result(api_key).data
|
||||
|
||||
def _repair_minimax_history(history: list[dict[str, Any]]) -> None:
|
||||
if not history: return
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Phase 11 sites 7+8: _extract_gemini_thoughts + _list_minimax_models Result helpers.
|
||||
|
||||
Site 7 (_extract_gemini_thoughts):
|
||||
try: candidates = getattr(resp, "candidates", None) or []
|
||||
for ... parts = getattr(content, "parts", None) or []
|
||||
... if thought: chunks.append(p.text)
|
||||
except Exception: pass
|
||||
return "".join(chunks).strip()
|
||||
|
||||
Body: pass + empty default '' = SS violation (silent + data loss).
|
||||
|
||||
Site 8 (_list_minimax_models):
|
||||
try: client = OpenAI(api_key=api_key, base_url=base_url)
|
||||
models_list = client.models.list()
|
||||
found = [m.id for m in models_list]
|
||||
if found: return sorted(found)
|
||||
except Exception: pass
|
||||
return ["MiniMax-M2.7", "MiniMax-M2.5", "MiniMax-M2.1", "MiniMax-M2"]
|
||||
|
||||
Body: pass + hardcoded default = SS violation.
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
|
||||
def test_phase11_sites78_extract_gemini_thoughts_result_exists():
|
||||
import src.ai_client
|
||||
assert hasattr(src.ai_client, "_extract_gemini_thoughts_result"), \
|
||||
"_extract_gemini_thoughts_result helper missing"
|
||||
|
||||
|
||||
def test_phase11_sites78_list_minimax_models_result_exists():
|
||||
import src.ai_client
|
||||
assert hasattr(src.ai_client, "_list_minimax_models_result"), \
|
||||
"_list_minimax_models_result helper missing"
|
||||
|
||||
|
||||
def test_phase11_sites78_helpers_return_result():
|
||||
import src.ai_client
|
||||
import inspect
|
||||
for name in ("_extract_gemini_thoughts_result",
|
||||
"_list_minimax_models_result"):
|
||||
fn = getattr(src.ai_client, name)
|
||||
sig = inspect.signature(fn)
|
||||
assert "Result" in str(sig.return_annotation), \
|
||||
f"{name} return must be Result, got {sig.return_annotation}"
|
||||
|
||||
|
||||
def test_phase11_sites78_legacy_preserved():
|
||||
import src.ai_client
|
||||
assert callable(getattr(src.ai_client, "_extract_gemini_thoughts", None))
|
||||
assert callable(getattr(src.ai_client, "_list_minimax_models", None))
|
||||
Reference in New Issue
Block a user