Files
life-echo/api/app/features/memoir/memoir_images/prompting.py

361 lines
13 KiB
Python
Raw Normal View History

import json
import re
from typing import Any, Optional
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
from app.core.config import settings
from app.core.json_utils import extract_json_payload
from app.core.langchain_llm import invoke_json_object
2026-03-20 15:15:35 +08:00
from app.core.logging import get_logger
from .settings import MemoirImageSettings
logger = get_logger(__name__)
_CJK_RE = re.compile(r"[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]")
class MemoirImagePromptService:
CATEGORY_STYLE_MAP = {
"childhood": "watercolor",
"family": "watercolor",
"career_early": "realistic",
"career_achievement": "realistic",
"career_challenge": "realistic",
"beliefs": "editorial illustration",
"summary": "editorial illustration",
}
CATEGORY_FALLBACK_SUBJECT_MAP = {
"childhood": "childhood memory",
"education": "school memory",
"career_early": "early career memory",
"career_achievement": "career achievement memory",
"career_challenge": "career challenge memory",
"family": "family memory",
"beliefs": "reflective life memory",
"summary": "memoir summary scene",
}
def __init__(self, llm: Optional[Any], settings: MemoirImageSettings):
self.llm = llm
self.settings = settings
def build_prompt(
self,
chapter_title: str,
chapter_category: str,
description: str,
context_excerpt: str,
) -> dict[str, str]:
2026-03-19 14:36:14 +08:00
style = self.CATEGORY_STYLE_MAP.get(
chapter_category, self.settings.default_style
)
prompt_context = f"{chapter_category}: {chapter_title}"
llm_input = {
"chapter_title": chapter_title,
"chapter_category": chapter_category,
"description": description,
"context_excerpt": context_excerpt,
"default_style": style,
"default_size": self.settings.default_size,
}
if self.llm:
raw_response = None
try:
prompt_text = (
"Return JSON only with keys prompt, style, size. "
"Convert the memoir scene into an image-generation prompt. "
"The API uses response_format=json_object.\n"
+ json.dumps(llm_input, ensure_ascii=False)
)
raw_response = invoke_json_object(
self.llm,
prompt_text,
max_tokens=512,
agent="MemoirImagePromptService.build_prompt",
)
2026-03-11 13:46:07 +08:00
parsed = json.loads(extract_json_payload(raw_response))
return {
2026-03-19 14:36:14 +08:00
"prompt": _ensure_style_in_prompt(
parsed["prompt"], parsed.get("style", style)
),
"style": parsed.get("style", style),
"size": parsed.get("size", self.settings.default_size),
"prompt_context": prompt_context,
}
except Exception as exc:
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
if settings.image_prompt_fallback_disabled:
raise
logger.warning(
"图片 prompt 生成回退到默认模板: chapter_category={}, title={}, error={}",
chapter_category,
chapter_title,
exc,
)
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
elif settings.image_prompt_fallback_disabled:
raise RuntimeError(
"MemoirImagePromptService.build_prompt requires LLM when "
"image_prompt_fallback_disabled is True"
)
return {
"prompt": _ensure_style_in_prompt(
self._build_fallback_prompt(
chapter_category=chapter_category,
description=description,
context_excerpt=context_excerpt,
style=style,
),
style,
),
"style": style,
"size": self.settings.default_size,
"prompt_context": prompt_context,
}
def build_cover_prompt(
self,
chapter_title: str,
chapter_category: str,
context_excerpt: str,
) -> dict[str, str]:
"""生成章节封面图的 image-generation prompt。"""
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
excerpt = (context_excerpt or "").strip()
if settings.image_prompt_fallback_disabled and not excerpt:
raise RuntimeError(
"Chapter cover prompt requires non-empty context_excerpt when "
"image_prompt_fallback_disabled is True"
)
2026-03-19 14:36:14 +08:00
style = self.CATEGORY_STYLE_MAP.get(
chapter_category, self.settings.default_style
)
prompt_context = f"{chapter_category}: {chapter_title}"
llm_input = {
"chapter_title": chapter_title,
"chapter_category": chapter_category,
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
"context_excerpt": excerpt,
"default_style": style,
"default_size": self.settings.default_size,
}
if self.llm:
try:
prompt_text = (
"Return JSON only with keys prompt, style, size. "
"Create an image-generation prompt for a memoir chapter COVER. "
"Emphasize: hero composition, evocative scene, chapter cover aesthetic. "
"The API uses response_format=json_object.\n"
+ json.dumps(llm_input, ensure_ascii=False)
)
raw = invoke_json_object(
self.llm,
prompt_text,
max_tokens=512,
agent="MemoirImagePromptService.build_cover_prompt",
)
parsed = json.loads(extract_json_payload(raw))
return {
"prompt": _ensure_style_in_prompt(
parsed["prompt"], parsed.get("style", style)
),
"style": parsed.get("style", style),
"size": parsed.get("size", self.settings.default_size),
"prompt_context": prompt_context,
}
except Exception as exc:
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
if settings.image_prompt_fallback_disabled:
raise
logger.warning(
"封面 prompt 生成回退到默认模板: chapter_category={}, title={}, error={}",
chapter_category,
chapter_title,
exc,
)
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
elif settings.image_prompt_fallback_disabled:
raise RuntimeError(
"MemoirImagePromptService.build_cover_prompt requires LLM when "
"image_prompt_fallback_disabled is True"
)
return {
"prompt": _ensure_style_in_prompt(
self._build_cover_fallback_prompt(
chapter_category=chapter_category,
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
context_excerpt=excerpt,
style=style,
),
style,
),
"style": style,
"size": self.settings.default_size,
"prompt_context": prompt_context,
}
refactor(agents): 抽取阶段常量与对话上下文;快档 LLM;图片 prompt 可禁止回退 访谈与阶段 - 新增 app/agents/stage_constants.py:集中 CHAT_STAGES、章节分类/顺序、阶段到默认 memoir 类别等,与 MemoirState 默认槽位顺序对齐;减少散落在 prompts 内的重复常量。 - 新增 app/agents/chat/prompt_context.py:以 ChatPromptContext 汇总 guided 系统提示所需字段(阶段、槽位、轮次、人设、记忆证据、回复长度模式、背景声线、职业等),统一走 get_guided_conversation_prompt。 - 大幅收敛 app/agents/chat/prompts_conversation.py;调整 prompts.py、stage_prompts.py、stage_detection.py;同步 interview_agent、profile_agent、helpers 与 state_schema,使对话侧构造提示的方式一致、可测。 回忆录流水线 - memoir/prompts.py 删除已迁至 stage_constants / 独立模板的大段常量与图片占位相关逻辑;classification / extraction / fidelity / narrative agents 与 orchest(全量历史仍可用于计数,注入模型时按轮次与字符上限截断)、image_prompt_fallback_disabled。 - dependencies 增加 get_llm_provider_fast(LRU 缓存,可与默认共用密钥与 base_url)。 任务与编排 - memoir_tasks:prepare_batches 注入 llm_fast;开启独立快档模型时打结构化日志。 - chapter_cover_tasks、story_image_tasks:与图片 prompt / JSON 工具路径或策略变更对齐(import 与行为一致)。 - story_pipeline_sync 等小处同步。 其它核心 - langchain_llm、text_normalize 随上述调用链微调。 开发者体验 - .cursor/settings.json:启用 redis-development、postman 插件。 测试 - 新增 test_image_prompt_policy:覆盖「禁止回退」等图片 prompt 策略。 - 更新 test_interview_prompts、test_interview_reply_length、test_experience_regressions、test_json_and_memory_utils,匹配新常量位置、json_utils 与对话/长度行为。
2026-04-02 12:00:00 +08:00
def build_story_primary_prompt(
self,
story_title: str,
story_stage: str | None,
prompt_brief: str,
style_profile: str | None,
) -> dict[str, str]:
"""生成 story 主插图的 image-generation promptLLM / fallback 策略与章节配图一致)。
`story_stage` `Story.stage` 一致通常为章节 category career_early也可能为
访谈五阶段名childhood/career/二者都参与默认画风推断
"""
from app.agents.stage_constants import STAGE_TO_DEFAULT_CATEGORY
brief = (prompt_brief or "").strip()
if settings.image_prompt_fallback_disabled and not brief:
raise RuntimeError(
"Story image prompt requires non-empty prompt_brief when "
"image_prompt_fallback_disabled is True"
)
stage_key = (story_stage or "").strip()
if stage_key in self.CATEGORY_STYLE_MAP:
cat = stage_key
else:
cat = STAGE_TO_DEFAULT_CATEGORY.get(stage_key, "summary")
explicit_style = (style_profile or "").strip()
style = explicit_style or self.CATEGORY_STYLE_MAP.get(
cat, self.settings.default_style
)
prompt_context = f"story:{stage_key}:{story_title}"
llm_input = {
"story_title": story_title,
"story_stage": stage_key,
"prompt_brief": brief,
"default_style": style,
"default_size": self.settings.default_size,
}
if self.llm:
try:
prompt_text = (
"Return JSON only with keys prompt, style, size. "
"Convert into an image-generation prompt for the PRIMARY hero illustration "
"of a personal memoir story (one focal scene, emotionally resonant). "
"The API uses response_format=json_object.\n"
+ json.dumps(llm_input, ensure_ascii=False)
)
raw_response = invoke_json_object(
self.llm,
prompt_text,
max_tokens=512,
agent="MemoirImagePromptService.build_story_primary_prompt",
)
parsed = json.loads(extract_json_payload(raw_response))
return {
"prompt": _ensure_style_in_prompt(
parsed["prompt"], parsed.get("style", style)
),
"style": parsed.get("style", style),
"size": parsed.get("size", self.settings.default_size),
"prompt_context": prompt_context,
}
except Exception as exc:
if settings.image_prompt_fallback_disabled:
raise
logger.warning(
"story 主图 prompt 生成回退到默认模板: stage={}, title={}, error={}",
story_stage,
story_title,
exc,
)
elif settings.image_prompt_fallback_disabled:
raise RuntimeError(
"MemoirImagePromptService.build_story_primary_prompt requires LLM when "
"image_prompt_fallback_disabled is True"
)
from .image_placeholder_template import IMAGE_PLACEHOLDER_TEMPLATE
if brief:
body = brief
else:
joined = "".join(
filter(
None,
[(story_title or "").strip(), stage_key or None],
)
)
body = joined or "人生故事"
prompt_line = f"{IMAGE_PLACEHOLDER_TEMPLATE}{body}"
return {
"prompt": _ensure_style_in_prompt(prompt_line, style),
"style": style,
"size": self.settings.default_size,
"prompt_context": prompt_context,
}
def _build_cover_fallback_prompt(
self,
chapter_category: str,
context_excerpt: str,
style: str,
) -> str:
subject = self.CATEGORY_FALLBACK_SUBJECT_MAP.get(
chapter_category, "memoir scene"
)
if _contains_cjk(context_excerpt):
return (
f"A {style} chapter cover illustration of a {subject}, "
"hero composition, evocative scene, emotionally resonant, "
"cinematic framing, natural lighting, no text overlay."
)
details = (context_excerpt or "").strip()[:500]
if not details:
details = "A personal life story scene with authentic emotional detail"
return (
f"A {style} chapter cover illustration of a {subject}. "
f"Scene hint: {details}. "
"Hero composition, evocative scene, cinematic framing, no text overlay."
)
def _build_fallback_prompt(
self,
chapter_category: str,
description: str,
context_excerpt: str,
style: str,
) -> str:
2026-03-19 14:36:14 +08:00
subject = self.CATEGORY_FALLBACK_SUBJECT_MAP.get(
chapter_category, "memoir scene"
)
if _contains_cjk(description) or _contains_cjk(context_excerpt):
return (
f"A {style} illustration of a {subject}, emotionally resonant, cinematic composition, "
"authentic everyday details, natural lighting, expressive environment, no text overlay."
)
2026-03-19 14:36:14 +08:00
details = ". ".join(
part.strip() for part in (description, context_excerpt) if part.strip()
)
if not details:
details = "A personal life story scene with authentic emotional detail"
return (
f"A {style} illustration of a {subject}. "
f"Scene details: {details}. "
"Cinematic composition, authentic emotions, natural lighting, no text overlay."
)
def _contains_cjk(value: str) -> bool:
return bool(_CJK_RE.search(value or ""))
def _ensure_style_in_prompt(prompt: str, style: str) -> str:
cleaned_prompt = (prompt or "").strip()
cleaned_style = (style or "").strip()
if not cleaned_style:
return cleaned_prompt
if cleaned_style.lower() in cleaned_prompt.lower():
return cleaned_prompt
if not cleaned_prompt:
return cleaned_style
return f"{cleaned_style}, {cleaned_prompt}"