fix: 去除LLM直接生成图片占位符逻辑

This commit is contained in:
yangshilin
2026-03-19 11:18:58 +08:00
parent 67fb5d2cb6
commit f3629efec3
6 changed files with 160 additions and 19 deletions

View File

@@ -28,7 +28,7 @@ from app.core.dependencies import get_llm_provider
from app.agents.state_schema import MemoirStateSchema, SlotData, default_state
from app.agents.memoir.prompts import (
STAGE_TO_ORDER,
get_narrative_prompt,
get_narrative_json_prompt,
inject_image_placeholder_template,
)
from app.agents.memoir import MemoirOrchestrator
@@ -38,6 +38,7 @@ from app.agents.chat.prompts_profile import format_user_profile_context
from app.features.memoir.memoir_images.parser import (
build_initial_image_assets,
parse_image_placeholders,
parse_narrative_to_sections,
split_narrative_to_sections,
)
import hashlib
@@ -67,6 +68,14 @@ logger = get_logger(__name__)
_REDIS_CLIENTS: dict[bool, redis.Redis] = {}
def _is_json_narrative(text: str) -> bool:
"""检测 narrative 是否为 JSON 格式paragraphs 结构)"""
if not text or not text.strip():
return False
s = text.strip()
return s.startswith("{") and "paragraphs" in s
def _get_llm():
"""Celery 任务内获取 LangChain LLM通过 port"""
try:
@@ -328,7 +337,7 @@ def _save_narrative_to_sections(db: Session, chapter, narrative: str, title: str
img_settings = MemoirImageSettings.from_env()
prompt_service = MemoirImagePromptService(llm=None, settings=img_settings) if img_settings.enabled else None
segments = split_narrative_to_sections(narrative_to_parse)
segments = parse_narrative_to_sections(narrative_to_parse)
if not segments:
sec = ChapterSection(
id=str(uuid.uuid4()),
@@ -368,8 +377,11 @@ def _save_narrative_to_sections(db: Session, chapter, narrative: str, title: str
chapter.source_segments = list(set((chapter.source_segments or []) + (source_segments or [])))
return chapter
# 每 3 个 section 对应 1 张图片,其他 section 的 image_id 为空
def _should_have_image(order_idx: int) -> bool:
def _should_have_image(seg: dict, order_idx: int) -> bool:
"""有 placeholder_info 的段落配图;无则兼容旧格式(每 3 段 1 图)"""
ph = seg.get("placeholder_info")
if ph and ph.get("description"):
return True
return (order_idx % 3) == 2
def _placeholder_for_segment(seg: dict, order_idx: int) -> dict | None:
@@ -385,7 +397,7 @@ def _save_narrative_to_sections(db: Session, chapter, narrative: str, title: str
order_idx = order_base + i
content = (seg.get("content") or "").strip()
image_asset = None
if img_settings.enabled and _should_have_image(order_idx):
if img_settings.enabled and _should_have_image(seg, order_idx):
ph = _placeholder_for_segment(seg, order_idx)
style = prompt_service.CATEGORY_STYLE_MAP.get(category, img_settings.default_style) if prompt_service else img_settings.default_style
image_asset = build_initial_image_assets(
@@ -640,12 +652,14 @@ def process_memoir_segments(self, user_id: str, segment_ids: List[str]):
birth_year=birth_year,
llm=llm,
)
if existing_content:
if _is_json_narrative(new_narrative):
narrative = new_narrative
elif existing_content:
narrative = f"{existing_content}\n\n{new_narrative}"
else:
narrative = new_narrative
if existing_content and len(narrative) < len(existing_content) * 0.8:
if existing_content and not _is_json_narrative(narrative) and len(narrative) < len(existing_content) * 0.8:
logger.warning(
"内容长度异常: existing=%d, new=%d, category=%s. 回退为追加模式",
len(existing_content),
@@ -654,7 +668,8 @@ def process_memoir_segments(self, user_id: str, segment_ids: List[str]):
)
narrative = f"{existing_content}\n\n{combined_text}"
narrative = inject_placeholders(narrative)
if not _is_json_narrative(narrative):
narrative = inject_placeholders(narrative)
calculated_order_index = STAGE_TO_ORDER.get(chapter_category, 999)
chapter = _save_narrative_to_sections(
@@ -777,7 +792,7 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
)
if llm:
prompt = get_narrative_prompt(
prompt = get_narrative_json_prompt(
stage=stage,
slots={},
new_content=new_content,
@@ -785,24 +800,25 @@ def generate_chapter_content(self, user_id: str, stage: str, new_content: str):
)
response = llm.invoke(prompt)
new_narrative = response.content.strip()
# 追加而非替换
if existing_content:
if _is_json_narrative(new_narrative):
narrative = new_narrative
elif existing_content:
narrative = f"{existing_content}\n\n{new_narrative}"
else:
narrative = new_narrative
else:
narrative = f"{existing_content}\n\n{new_content}" if existing_content else new_content
# 安全检查:新内容不应比旧内容短
if existing_content and len(narrative) < len(existing_content) * 0.8:
# 安全检查:新内容不应比旧内容短(仅非 JSON 格式)
if existing_content and not _is_json_narrative(narrative) and len(narrative) < len(existing_content) * 0.8:
logger.warning(
f"内容长度异常: existing={len(existing_content)}, "
f"new={len(narrative)}, stage={stage}. 回退为追加模式"
)
narrative = f"{existing_content}\n\n{new_content}"
# 入库前:占位符位置用正则匹配后拼上固定模板
narrative = inject_image_placeholder_template(narrative)
if not _is_json_narrative(narrative):
narrative = inject_image_placeholder_template(narrative)
calculated_order_index = STAGE_TO_ORDER.get(stage, 999)
title = chapter.title if chapter else f"{stage} 回忆"
chapter = _save_narrative_to_sections(