datawhalechina/hello-agents · error · RuntimeError
缺少完整 Markdown
Error message
缺少完整 Markdown
What it means
Immediately after confirming the last debate event is 'complete', the wrapper reads ev["markdown"] and raises RuntimeError('缺少完整 Markdown') when it is not a str. So the debate reported completion but the synthesis payload was malformed — the complete event carried no markdown field (None), a non-string value, or an empty/failed synthesis result was written into the event. It indicates a contract violation inside the orchestrator's final phase rather than a caller input problem.
Source
Thrown at Co-creation-projects/meiguanxiHXX-historyReviewAgent/historical_review/debate_orchestrator.py:288
last: dict[str, Any] | None = None
for ev in iter_debate_events(
topic,
llm=llm,
use_evidence_bundle=use_evidence_bundle,
debate_temperature=debate_temperature,
synthesizer_temperature=synthesizer_temperature,
llm_api_key=llm_api_key,
llm_base_url=llm_base_url,
llm_model=llm_model,
llm_max_tokens=llm_max_tokens,
llm_timeout=llm_timeout,
):
last = ev
if not last or last.get("event") != "complete":
raise RuntimeError("辩论未正常结束")
md = last.get("markdown")
if not isinstance(md, str):
raise RuntimeError("缺少完整 Markdown")
return md
def debate_event_json(ev: dict[str, Any]) -> str:
"""序列化单条事件(SSE data 行)。"""
return json.dumps(ev, ensure_ascii=False)
View on GitHub (pinned to 606a07d341)
Solutions
- Log the full last event dict — it shows whether markdown is missing, None, or under a different key.
- If markdown is empty because synthesis returned no text, raise llm_max_tokens / lower synthesizer_temperature and retry the debate.
- Align producer and consumer on the schema: ensure run_historical_debate only emits 'complete' with a non-empty string markdown, and the collector reads the same key.
- Retry once — empty completions from providers are often transient.
- Pin/patch both modules together (they ship in the same package) so a schema change cannot split them.
Example fix
# before
md = last.get("markdown")
if not isinstance(md, str):
raise RuntimeError("缺少完整 Markdown")
# after
md = last.get("markdown")
if not isinstance(md, str) or not md.strip():
raise RuntimeError(f"缺少完整 Markdown,complete 事件负载: { {k: type(v).__name__ for k, v in last.items()} }") Defensive patterns
Strategy: type-guard
Validate before calling
last = events[-1] if events else None
if not (last and last.get("event") == "complete"):
raise RuntimeError("辩论未正常结束")
md = last.get("markdown") Type guard
def has_complete_markdown(ev: dict) -> bool:
return (
isinstance(ev, dict)
and ev.get("event") == "complete"
and isinstance(ev.get("markdown"), str)
and bool(ev["markdown"].strip())
) Try / catch
try:
md = collect_debate(...)
except RuntimeError as e:
if "缺少完整 Markdown" in str(e):
# synthesis produced empty content — retry with higher max_tokens
raise
raise Prevention
- Only emit 'complete' when markdown is a non-empty string
- Pin producer and consumer of the event schema to the same package version
- Check finish_reason/empty completions in the synthesis phase before completing
When it happens
Trigger: The synthesis LLM call returned empty content and the complete event was still emitted with markdown=None; a code path constructs {"event": "complete"} without attaching markdown when synthesis produced nothing; version skew where the event schema changed (e.g. key renamed to 'content' or 'result'); JSON round-trip through SSE turned markdown into a non-string.
Common situations: Provider returns an empty completion for the final synthesis (finish_reason=length with zero text); a patched/older debate_orchestrator emits a different field name than the collector expects; content-filtered responses leaving synthesis blank; consuming events via the SSE JSON API where a proxy mangles the payload.
Related errors
AI-assisted analysis of datawhalechina/hello-agents@606a07d341 (2026-08-14).
Data as JSON: /api/errors/dac80a6fc20a1b5e.
Report an issue: GitHub.