107 lines
3.4 KiB
Python
107 lines
3.4 KiB
Python
|
|
"""
|
|||
|
|
ChapterComposerOrchestrator — 读取 stories/evidence,生成章节 markdown。
|
|||
|
|
|
|||
|
|
Agent 只产出结构化结果,不直接写 DB。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
from __future__ import annotations
|
|||
|
|
|
|||
|
|
from typing import Any
|
|||
|
|
|
|||
|
|
from app.core.logging import get_logger
|
|||
|
|
|
|||
|
|
logger = get_logger(__name__)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ChapterComposerOrchestrator:
|
|||
|
|
"""
|
|||
|
|
生成章节大纲和章节 markdown。
|
|||
|
|
仅返回 markdown,不落库。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
def compose_chapter_markdown(
|
|||
|
|
self,
|
|||
|
|
*,
|
|||
|
|
title: str,
|
|||
|
|
category: str,
|
|||
|
|
evidence: dict,
|
|||
|
|
existing_markdown: str = "",
|
|||
|
|
user_profile: str = "",
|
|||
|
|
birth_year: int | None = None,
|
|||
|
|
llm: Any = None,
|
|||
|
|
) -> str:
|
|||
|
|
"""
|
|||
|
|
从 evidence 生成章节 markdown。
|
|||
|
|
若有 existing_markdown 则追加/合并。
|
|||
|
|
返回 markdown 正文,不写 DB。
|
|||
|
|
"""
|
|||
|
|
from app.agents.memoir.narrative_agent import NarrativeAgent
|
|||
|
|
|
|||
|
|
chunks = evidence.get("relevant_chunks", [])
|
|||
|
|
facts = evidence.get("relevant_facts", [])
|
|||
|
|
new_content = self._format_evidence_for_prompt(chunks, facts)
|
|||
|
|
|
|||
|
|
agent = NarrativeAgent()
|
|||
|
|
narrative = agent.generate_narrative(
|
|||
|
|
stage=category,
|
|||
|
|
slots={},
|
|||
|
|
new_content=new_content,
|
|||
|
|
existing_content=existing_markdown,
|
|||
|
|
user_profile=user_profile,
|
|||
|
|
birth_year=birth_year,
|
|||
|
|
llm=llm,
|
|||
|
|
)
|
|||
|
|
return self._to_markdown(narrative)
|
|||
|
|
|
|||
|
|
def _format_evidence_for_prompt(self, chunks: list, facts: list) -> str:
|
|||
|
|
"""将 evidence 格式化为 prompt 输入。"""
|
|||
|
|
parts = []
|
|||
|
|
for c in chunks[:10]:
|
|||
|
|
content = (
|
|||
|
|
c.get("content", "")
|
|||
|
|
if isinstance(c, dict)
|
|||
|
|
else getattr(c, "content", "")
|
|||
|
|
)
|
|||
|
|
if content:
|
|||
|
|
parts.append(content.strip())
|
|||
|
|
for f in facts[:5]:
|
|||
|
|
if isinstance(f, dict):
|
|||
|
|
subj = f.get("subject", "")
|
|||
|
|
pred = f.get("predicate", "")
|
|||
|
|
obj = f.get("object_json", "")
|
|||
|
|
if subj or pred:
|
|||
|
|
parts.append(f"{subj} {pred} {obj}")
|
|||
|
|
else:
|
|||
|
|
parts.append(
|
|||
|
|
f"{getattr(f, 'subject', '')} {getattr(f, 'predicate', '')}"
|
|||
|
|
)
|
|||
|
|
return "\n\n".join(parts) if parts else ""
|
|||
|
|
|
|||
|
|
def _to_markdown(self, narrative: str) -> str:
|
|||
|
|
"""将 narrative(JSON 或纯文本)转为 markdown。正文不含占位符。"""
|
|||
|
|
if not narrative or not narrative.strip():
|
|||
|
|
return ""
|
|||
|
|
if narrative.strip().startswith("{") and "paragraphs" in narrative:
|
|||
|
|
import json
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
data = json.loads(narrative)
|
|||
|
|
paras = data.get("paragraphs", [])
|
|||
|
|
if isinstance(paras, list):
|
|||
|
|
parts = []
|
|||
|
|
for p in paras:
|
|||
|
|
if isinstance(p, dict):
|
|||
|
|
text = p.get("content", p.get("text", ""))
|
|||
|
|
else:
|
|||
|
|
text = str(p)
|
|||
|
|
if text.strip():
|
|||
|
|
parts.append(text.strip())
|
|||
|
|
md = "\n\n".join(parts)
|
|||
|
|
else:
|
|||
|
|
md = narrative
|
|||
|
|
except json.JSONDecodeError:
|
|||
|
|
md = narrative
|
|||
|
|
else:
|
|||
|
|
md = narrative.strip()
|
|||
|
|
return md
|