refactor(ai_client,openai_schemas): migrate API response + _repair_minimax (Phase 5 part 2)
Phase 5: ChatMessage (part 2)
Before: 6 .get('content'/'role'/'tool_calls'/'tool_call_id') sites
After: 0
Delta: -6
Migrates:
1. _send_deepseek API response parsing (lines 2321-2324):
- message.get('content', '') -> message.content or ''
- message.get('tool_calls', []) -> [tc.to_dict() for tc in message.tool_calls]
- message.get('reasoning_content') -> kept as choice.get('message', {}).get('reasoning_content', '')
(reasoning_content is NOT a ChatMessage field)
2. _repair_minimax_history generator (line 2454):
- m.get('role') == 'tool' -> _CM.from_dict(m).role == 'tool'
- m.get('tool_call_id') -> _CM.from_dict(m).tool_call_id
Used inline conversion because the generator iterates over a
dict list and reads 2 fields. Inline conversion avoids an
intermediate list comprehension.
openai_schemas.py:
- ChatMessage.from_dict() now provides defaults for required fields
('role' -> 'assistant', 'content' -> '') when the input dict is
missing them. This handles the case where DeepSeek's API returns
an empty {} for 'message' (e.g., finish_reason='length' with no
content). Without this default, ChatMessage.__init__() raises
TypeError.
Tests: 46/46 pass (test_ai_client_result, test_ai_client_tool_loop,
test_deepseek_provider, test_openai_schemas, test_minimax_provider).
This commit is contained in:
+7
-5
@@ -2318,10 +2318,11 @@ def _send_deepseek(md_content: str, user_message: str, base_dir: str,
|
||||
_append_comms("IN", "response", {"round": round_idx, "text": "(No choices returned)", "usage": response_data.get("usage", {})})
|
||||
break
|
||||
choice = choices[0]
|
||||
message = choice.get("message", {})
|
||||
assistant_text = message.get("content", "")
|
||||
tool_calls_raw = message.get("tool_calls", [])
|
||||
reasoning_content = message.get("reasoning_content", "")
|
||||
from src.openai_schemas import ChatMessage as _CM
|
||||
message = _CM.from_dict(choice.get("message", {}))
|
||||
assistant_text = message.content or ""
|
||||
tool_calls_raw = [tc.to_dict() for tc in message.tool_calls] if message.tool_calls else []
|
||||
reasoning_content = choice.get("message", {}).get("reasoning_content", "")
|
||||
finish_reason = choice.get("finish_reason", "stop")
|
||||
usage = response_data.get("usage", {})
|
||||
|
||||
@@ -2451,7 +2452,8 @@ def _repair_minimax_history(history: list[Metadata]) -> None:
|
||||
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:])
|
||||
from src.openai_schemas import ChatMessage as _CM
|
||||
already_has = any(_CM.from_dict(m).role == "tool" and _CM.from_dict(m).tool_call_id == cid for m in history[-len(call_ids)-1:])
|
||||
if not already_has:
|
||||
history.append({
|
||||
"role": "tool",
|
||||
|
||||
@@ -78,7 +78,12 @@ class ChatMessage:
|
||||
tool_calls = None
|
||||
if raw_tool_calls is not None:
|
||||
tool_calls = tuple(ToolCall.from_dict(tc) for tc in raw_tool_calls)
|
||||
return cls(**{**_from_dict_filter(cls, data), "tool_calls": tool_calls})
|
||||
filtered = _from_dict_filter(cls, data)
|
||||
if "role" not in filtered:
|
||||
filtered["role"] = "assistant"
|
||||
if "content" not in filtered:
|
||||
filtered["content"] = ""
|
||||
return cls(**{**filtered, "tool_calls": tool_calls})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
Reference in New Issue
Block a user