Skip to content

fix: handle Mistral Magistral structured content blocks - #4748

Closed
teknium1 wants to merge 1 commit into
mainfrom
hermes/hermes-97fe1e3c
Closed

fix: handle Mistral Magistral structured content blocks#4748
teknium1 wants to merge 1 commit into
mainfrom
hermes/hermes-97fe1e3c

Conversation

@teknium1

@teknium1 teknium1 commented Apr 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Mistral Magistral reasoning models (magistral-medium-latest, magistral-small-latest) and mistral-large-2512+ return message.content as a list of structured blocks instead of a plain string:

"content": [
  {"type": "thinking", "thinking": [{"type": "text", "text": "reasoning..."}]},
  {"type": "text", "text": "final answer"}
]

This happens in both streaming deltas and non-streaming responses, causing TypeError: sequence item 0: expected str instance, list found when the code tries to join content_parts.

Reported by Joseph C. on Discord.

Changes

  • Add _normalize_structured_content() helper that extracts text and thinking parts from Mistral structured blocks
  • Streaming path: normalize delta.content before appending to content_parts, route thinking blocks to reasoning_parts with proper callback firing
  • Non-streaming normalization: replace the simple list handler with the full helper that also extracts thinking blocks as reasoning_content (was silently dropping them)
  • _build_assistant_message: normalize list content early before string operations and regex matching
  • Length truncation/continuation: normalize content before string concatenation

Test plan

  • 25 new tests in tests/test_mistral_structured_content.py covering:
    • Helper function (13 tests): all block types, edge cases, string passthrough
    • _build_assistant_message (6 tests): list content, tool calls, deduplication
    • Non-streaming normalization (2 tests): list and dict content
    • Streaming simulation (4 tests): join safety, mixed chunks, thinking-only deltas
  • E2E test with real imports from worktree and isolated HERMES_HOME
  • Full suite: 5001 passed (4 pre-existing failures unrelated)

Mistral Magistral reasoning models (and mistral-large-2512+) return
message content as a list of typed blocks instead of a plain string:

  [{"type": "thinking", "thinking": [{"type": "text", "text": "..."}]},
   {"type": "text", "text": "final answer"}]

This happens in both streaming deltas and non-streaming responses,
causing TypeError: sequence item 0: expected str instance, list found
when the code tries to join content parts.

Changes:
- Add _normalize_structured_content() helper that extracts text and
  thinking parts from Mistral structured blocks
- Fix streaming path: normalize delta.content before appending to
  content_parts, route thinking to reasoning_parts
- Fix non-streaming normalization: use the helper to also extract
  thinking blocks as reasoning_content (was silently dropping them)
- Fix _build_assistant_message: normalize list content before
  string operations
- Fix length truncation/continuation paths: normalize content
  before string concatenation
- Add 25 tests covering the helper, streaming, non-streaming,
  and _build_assistant_message paths

Fixes the reported CLI/Discord bot crash when using magistral-latest
or magistral-medium-latest via api.mistral.ai.
@teknium1

Copy link
Copy Markdown
Contributor Author

Closing during PR triage — not pursuing this approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant