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:
ed
2026-06-25 20:19:27 -04:00
parent 8df841fdfa
commit 6a2f2cfa37
2 changed files with 13 additions and 6 deletions
+6 -1
View File
@@ -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)