{"record":{"id":"dac80a6fc20a1b5e","repo":"datawhalechina/hello-agents","slug":"markdown","errorCode":null,"errorMessage":"缺少完整 Markdown","messagePattern":"缺少完整 Markdown","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"Co-creation-projects/meiguanxiHXX-historyReviewAgent/historical_review/debate_orchestrator.py","lineNumber":288,"sourceCode":"    last: dict[str, Any] | None = None\n    for ev in iter_debate_events(\n        topic,\n        llm=llm,\n        use_evidence_bundle=use_evidence_bundle,\n        debate_temperature=debate_temperature,\n        synthesizer_temperature=synthesizer_temperature,\n        llm_api_key=llm_api_key,\n        llm_base_url=llm_base_url,\n        llm_model=llm_model,\n        llm_max_tokens=llm_max_tokens,\n        llm_timeout=llm_timeout,\n    ):\n        last = ev\n    if not last or last.get(\"event\") != \"complete\":\n        raise RuntimeError(\"辩论未正常结束\")\n    md = last.get(\"markdown\")\n    if not isinstance(md, str):\n        raise RuntimeError(\"缺少完整 Markdown\")\n    return md\n\n\ndef debate_event_json(ev: dict[str, Any]) -> str:\n    \"\"\"序列化单条事件（SSE data 行）。\"\"\"\n    return json.dumps(ev, ensure_ascii=False)\n","sourceCodeStart":270,"sourceCodeEnd":295,"githubUrl":"https://github.com/datawhalechina/hello-agents/blob/606a07d341a47be773fab7f4b71177f53f96b2c3/Co-creation-projects/meiguanxiHXX-historyReviewAgent/historical_review/debate_orchestrator.py#L270-L295","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nmd = last.get(\"markdown\")\nif not isinstance(md, str):\n    raise RuntimeError(\"缺少完整 Markdown\")\n# after\nmd = last.get(\"markdown\")\nif not isinstance(md, str) or not md.strip():\n    raise RuntimeError(f\"缺少完整 Markdown，complete 事件负载: { {k: type(v).__name__ for k, v in last.items()} }\")","handlingStrategy":"type-guard","validationCode":"last = events[-1] if events else None\nif not (last and last.get(\"event\") == \"complete\"):\n    raise RuntimeError(\"辩论未正常结束\")\nmd = last.get(\"markdown\")","typeGuard":"def has_complete_markdown(ev: dict) -> bool:\n    return (\n        isinstance(ev, dict)\n        and ev.get(\"event\") == \"complete\"\n        and isinstance(ev.get(\"markdown\"), str)\n        and bool(ev[\"markdown\"].strip())\n    )","tryCatchPattern":"try:\n    md = collect_debate(...)\nexcept RuntimeError as e:\n    if \"缺少完整 Markdown\" in str(e):\n        # synthesis produced empty content — retry with higher max_tokens\n        raise\n    raise","preventionTips":["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"],"tags":["orchestrator","contract-violation","llm-failure","python","event-stream"],"backgroundTag":null,"analyzedSha":"606a07d341a47be773fab7f4b71177f53f96b2c3","analyzedAt":"2026-08-14T22:57:27.446Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}