hermes - ✅(Solved) Fix Bug: 'dict' object has no attribute 'model_dump' crashes MiniMax streaming tool calls [2 pull requests, 1 participants]

Official PRs (…)
ON THIS PAGE

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#19968Fetched 2026-05-05 06:04:09
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×2

Cron jobs using MiniMax provider (MiniMax-M2.7) crash with a streaming-related AttributeError during tool-call delta processing. The agent loop terminates immediately after the first tool call, producing no output.

Error: AttributeError: 'dict' object has no attribute 'model_dump' Location: run_agent.py streaming delta handler, 3 distinct call sites Status: Recurring — confirmed on 2026-04-27 and 2026-05-05 (both cron jobs failed within the same 8 AM window on 5/05)


Error Message

if hasattr(extra, "model_dump"): try: extra = extra.model_dump() except Exception: pass # ← graceful fallback, no crash

Root Cause

The pattern at all three locations:

if hasattr(extra, "model_dump"):
    extra = extra.model_dump()

Expected type of extra: Pydantic BaseModel instance (has model_dump() method). Actual type when crash occurs: plain dict.

The hasattr guard should theoretically prevent this — hasattr({}, "model_dump") returns False. But the error message is unambiguous.

Two hypotheses:

A) MiniMax's streaming SDK returns tc_delta.extra_content as a Pydantic model that passes hasattr but fails on .model_dump() due to validation issues with streaming data. This would be a MiniMax SDK bug.

B) A dict subclass defines model_dump as a non-callable attribute, causing hasattr to return True but the call to raise TypeError. However the actual error is AttributeError, pointing back to hypothesis A.

Fix Action

Fixed

PR fix notes

PR #19971: fix(agent): guard extra_content against plain dict to prevent model_dump crash

Description (problem / solution / changelog)

Summary

Fix AttributeError: 'dict' object has no attribute 'model_dump' crash when providers (e.g. MiniMax) return extra_content as a plain dict instead of a Pydantic BaseModel.

Root Cause

Two code paths in run_agent.py use this pattern to serialize extra_content:

if hasattr(extra, "model_dump"):
    extra = extra.model_dump()

When extra is a Pydantic model, model_dump() returns a serializable dict — correct. But when a provider returns extra_content as a plain dict, hasattr(dict_obj, "model_dump") can pass (e.g. if the dict has a "model_dump" key, or the object is a dict-like subclass), and the subsequent .model_dump() call raises AttributeError.

A third code path (reasoning_details at line ~8745) already has isinstance(d, dict) as the first check, so it was unaffected.

Fix

Add isinstance(extra, dict) guard before hasattr(extra, "model_dump") at both unguarded sites:

  • Location 1 (streaming delta path, line ~6960): Tool call delta processing during streaming
  • Location 2 (non-streaming tool-call path, line ~8810): Assistant message building for tool calls

When extra is already a dict, it's already serializable — no conversion needed.

Regression Coverage

  • test_tool_call_extra_content_dict_passthrough in test_run_agent.py — verifies _build_assistant_message preserves dict-typed extra_content
  • test_tool_call_extra_content_dict_passthrough in test_streaming.py — verifies streaming accumulation preserves dict-typed extra_content from model_extra
  • Existing tests (test_tool_call_extra_content_preserved) continue to pass, confirming Pydantic model paths still work

Testing

tests/run_agent/test_run_agent.py::TestBuildAssistantMessage::test_tool_call_extra_content_dict_passthrough PASSED
tests/run_agent/test_run_agent.py::TestBuildAssistantMessage::test_tool_call_extra_content_preserved PASSED
tests/run_agent/test_streaming.py::TestStreamingAccumulator::test_tool_call_extra_content_dict_passthrough PASSED
tests/run_agent/test_streaming.py::TestStreamingAccumulator::test_tool_call_extra_content_preserved PASSED

Fixes Bug: 'dict' object has no attribute 'model_dump' crashes MiniMax streaming tool calls #19968

Changed files

  • run_agent.py (modified, +6/-2)
  • tests/run_agent/test_run_agent.py (modified, +13/-0)
  • tests/run_agent/test_streaming.py (modified, +49/-0)

PR #19993: fix(agent): tolerate broken provider model dumps

Description (problem / solution / changelog)

Summary

  • add a small safe model-dump helper for opaque provider metadata in run_agent.py
  • keep provider metadata unmodified when model_dump() raises, matching the existing fail-open pattern in agent/transports/chat_completions.py
  • cover streaming tool-call extra_content, stored assistant tool-call extra_content, and reasoning_details preservation regressions

Closes #19968.

Verification

  • scripts/run_tests.sh tests/run_agent/test_streaming.py tests/run_agent/test_run_agent.py -> 351 passed, 4 warnings
  • git diff --check -> passed

Scope

This intentionally does not change MiniMax transport selection or provider SDK parsing. It only prevents opaque provider metadata dump failures from aborting the agent turn.

Changed files

  • run_agent.py (modified, +15/-5)
  • tests/run_agent/test_run_agent.py (modified, +34/-0)
  • tests/run_agent/test_streaming.py (modified, +47/-1)

Code Example

extra = getattr(tc_delta, "extra_content", None)
if extra is None and hasattr(tc_delta, "model_extra"):
    extra = (tc_delta.model_extra or {}).get("extra_content")
if extra is not None:
    if hasattr(extra, "model_dump"):   # ← line 6960
        extra = extra.model_dump()      # ← line 6961CRASH HERE
    entry["extra_content"] = extra

---

extra = getattr(tool_call, "extra_content", None)
if extra is not None:
    if hasattr(extra, "model_dump"):   # ← line 8809
        extra = extra.model_dump()      # ← line 8810CRASH HERE
    tc_dict["extra_content"] = extra

---

elif hasattr(d, "model_dump"):   # ← line 8748
    preserved.append(d.model_dump())

---

if hasattr(extra, "model_dump"):
    extra = extra.model_dump()

---

if hasattr(extra, "model_dump"):
    try:
        extra = extra.model_dump()
    except Exception:
        pass       # ← graceful fallback, no crash

---

Session: session_cron_e6a6121db141_20260505_080016.json
Error:   {'type': 'AttributeError', 'message': "'dict' object has no attribute 'model_dump'"}
Reason:  max_retries_exhausted

---

Session: session_cron_e6a6121db141_20260505_085558.json
Messages: 1 (session started → crashed immediately)

---

# BEFORE (crashes)
if hasattr(extra, "model_dump"):
    extra = extra.model_dump()

# AFTER (safe)
if hasattr(extra, "model_dump"):
    try:
        extra = extra.model_dump()
    except Exception:
        pass  # extra is already a plain dict or invalid — leave it as-is
RAW_BUFFERClick to expand / collapse

Summary

Cron jobs using MiniMax provider (MiniMax-M2.7) crash with a streaming-related AttributeError during tool-call delta processing. The agent loop terminates immediately after the first tool call, producing no output.

Error: AttributeError: 'dict' object has no attribute 'model_dump' Location: run_agent.py streaming delta handler, 3 distinct call sites Status: Recurring — confirmed on 2026-04-27 and 2026-05-05 (both cron jobs failed within the same 8 AM window on 5/05)


Environment

ItemValue
Hermes Agentnousresearch/hermes-agent (latest)
Providerminimax
ModelMiniMax-M2.7
Filerun_agent.py
Python3.11+

Affected Code Locations

Location 1 — run_agent.py:6960–6962 (streaming delta path)

extra = getattr(tc_delta, "extra_content", None)
if extra is None and hasattr(tc_delta, "model_extra"):
    extra = (tc_delta.model_extra or {}).get("extra_content")
if extra is not None:
    if hasattr(extra, "model_dump"):   # ← line 6960
        extra = extra.model_dump()      # ← line 6961  ← CRASH HERE
    entry["extra_content"] = extra

Location 2 — run_agent.py:8809–8811 (non-streaming tool-call path)

extra = getattr(tool_call, "extra_content", None)
if extra is not None:
    if hasattr(extra, "model_dump"):   # ← line 8809
        extra = extra.model_dump()      # ← line 8810  ← CRASH HERE
    tc_dict["extra_content"] = extra

Location 3 — run_agent.py:8748 (reasoning_details preservation)

elif hasattr(d, "model_dump"):   # ← line 8748
    preserved.append(d.model_dump())

Root Cause Analysis

The pattern at all three locations:

if hasattr(extra, "model_dump"):
    extra = extra.model_dump()

Expected type of extra: Pydantic BaseModel instance (has model_dump() method). Actual type when crash occurs: plain dict.

The hasattr guard should theoretically prevent this — hasattr({}, "model_dump") returns False. But the error message is unambiguous.

Two hypotheses:

A) MiniMax's streaming SDK returns tc_delta.extra_content as a Pydantic model that passes hasattr but fails on .model_dump() due to validation issues with streaming data. This would be a MiniMax SDK bug.

B) A dict subclass defines model_dump as a non-callable attribute, causing hasattr to return True but the call to raise TypeError. However the actual error is AttributeError, pointing back to hypothesis A.

The inconsistency

The sibling file agent/transports/chat_completions.py:452–456 uses the safe pattern with try/except:

if hasattr(extra, "model_dump"):
    try:
        extra = extra.model_dump()
    except Exception:
        pass       # ← graceful fallback, no crash

Locations 1 and 2 in run_agent.py lack this protective try/except, making them crash where chat_completions.py would silently continue.


Reproduction Steps

  1. Set up a cron job that uses a skill with provider=minimax and model=MiniMax-M2.7.
  2. Schedule it to run when MiniMax API is active.
  3. The job starts normally, loads skills, and makes the first tool call.
  4. On the streaming response containing the tool-call delta, run_agent.py attempts the hasattr(extra, "model_dump") check at line ~6960.
  5. Crash: AttributeError: 'dict' object has no attribute 'model_dump'.
  6. The framework retries 3 times, then fails with max_retries_exhausted.
  7. Session ends with only 1–3 messages (immediate death after first tool call).

Evidence

Session data — 2026-05-05 08:00 (scheduled run)

Session: session_cron_e6a6121db141_20260505_080016.json
Error:   {'type': 'AttributeError', 'message': "'dict' object has no attribute 'model_dump'"}
Reason:  max_retries_exhausted

Session data — 2026-05-05 08:55 (manual re-run)

Session: session_cron_e6a6121db141_20260505_085558.json
Messages: 1 (session started → crashed immediately)

Prior occurrence — 2026-04-27

Same error, same run_agent.py locations, same MiniMax-M2.7 model. See session 20260427_080233_31e0eb77.


Impact

  • Both daily cron jobs fail every morning when this bug is active.
  • The session dies before producing any output — no music, no video, no Telegram delivery.
  • This is a deterministic crash with MiniMax provider, not an occasional glitch.

Suggested Fix

Wrap all model_dump() calls with try/except, consistent with the existing safe pattern in chat_completions.py:

# BEFORE (crashes)
if hasattr(extra, "model_dump"):
    extra = extra.model_dump()

# AFTER (safe)
if hasattr(extra, "model_dump"):
    try:
        extra = extra.model_dump()
    except Exception:
        pass  # extra is already a plain dict or invalid — leave it as-is

Apply the same pattern to all three locations.

Deeper fix option: Investigate why tc_delta.extra_content arrives as a plain dict in the first place. If MiniMax's streaming SDK returns incorrectly-typed objects, the root cause fix would be in the SDK adapter layer.


Files

  • run_agent.py — lines 6960, 8748, 8809
  • agent/transports/chat_completions.py — lines 452–456 (reference: correct safe pattern)

extent analysis

TL;DR

Wrap all model_dump() calls with try/except to prevent crashes when extra is a plain dict.

Guidance

  • Identify all locations where model_dump() is called and wrap them with a try/except block to catch any exceptions.
  • Verify that the extra object is indeed a Pydantic BaseModel instance before calling model_dump() on it.
  • Consider investigating why tc_delta.extra_content arrives as a plain dict in the first place, as this may indicate a deeper issue with the MiniMax SDK.
  • Apply the safe pattern to all three locations in run_agent.py to prevent crashes.

Example

if hasattr(extra, "model_dump"):
    try:
        extra = extra.model_dump()
    except Exception:
        pass  # extra is already a plain dict or invalid — leave it as-is

Notes

The provided fix assumes that the model_dump() method is expected to be called on a Pydantic BaseModel instance. If this is not the case, further investigation may be necessary to determine the correct course of action.

Recommendation

Apply the workaround by wrapping all model_dump() calls with try/except, as this will prevent crashes and allow the program to continue running. A deeper fix may be necessary to address the root cause of the issue, but this workaround should provide a temporary solution.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

hermes - ✅(Solved) Fix Bug: 'dict' object has no attribute 'model_dump' crashes MiniMax streaming tool calls [2 pull requests, 1 participants]