Files
life-echo/api/app/agents/memoir/narrative_agent.py

120 lines
4.0 KiB
Python
Raw Normal View History

2026-03-19 10:38:11 +08:00
"""
NarrativeAgent生成创意标题和叙事改写
叙事正文走 `get_narrative_json_prompt` / `get_narrative_merge_json_prompt`传记作家式书面语 + 事实边界
2026-03-19 10:38:11 +08:00
"""
2026-03-19 14:36:14 +08:00
2026-03-19 10:38:11 +08:00
from __future__ import annotations
import json
2026-03-19 10:38:11 +08:00
from typing import Any, Dict, Optional
from app.agents.memoir.prompts import (
get_creative_title_json_prompt,
get_narrative_json_prompt,
get_narrative_merge_json_prompt,
2026-03-19 10:38:11 +08:00
)
from app.core.langchain_llm import invoke_json_object
2026-03-20 15:15:35 +08:00
from app.core.logging import get_logger
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.json_utils import extract_json_payload
2026-03-19 10:38:11 +08:00
logger = get_logger(__name__)
class NarrativeAgent:
"""生成章节标题和叙事正文"""
def generate_title(
self,
stage: str,
emotion: str,
slots: Dict[str, str],
user_profile: str = "",
birth_year: Optional[int] = None,
llm: Any = None,
) -> str:
"""生成创意标题。若无 LLM 则返回默认标题"""
if not llm:
return f"{stage} 回忆"
try:
prompt = get_creative_title_json_prompt(
2026-03-19 10:38:11 +08:00
stage=stage,
emotion=emotion,
slots=slots,
user_profile=user_profile,
birth_year=birth_year,
)
raw = invoke_json_object(
llm,
prompt,
max_tokens=256,
agent="NarrativeAgent.generate_title",
)
data = json.loads(extract_json_payload(raw))
title = (data.get("title") or "").strip() if isinstance(data, dict) else ""
if title:
return title.strip('"')
return f"{stage} 回忆"
2026-03-19 10:38:11 +08:00
except Exception as e:
logger.warning("NarrativeAgent 生成标题失败: {}", e)
2026-03-19 10:38:11 +08:00
return f"{stage} 回忆"
def generate_narrative(
self,
stage: str,
slots: Dict[str, str],
new_content: str,
existing_content: str = "",
user_profile: str = "",
birth_year: Optional[int] = None,
llm: Any = None,
background_voice: str = "default",
occupation: str = "",
2026-03-19 10:38:11 +08:00
) -> str:
"""将新对话改写为叙述。若无 LLM 则直接拼接。
`existing_content` 非空append 路径使用整篇合并提示输出覆盖全篇的有序段落
"""
2026-03-19 10:38:11 +08:00
if not llm:
if existing_content:
return f"{existing_content}\n\n{new_content}"
return new_content
try:
merge_mode = bool((existing_content or "").strip())
if merge_mode:
prompt = get_narrative_merge_json_prompt(
stage=stage,
slots=slots,
new_content=new_content,
existing_content=existing_content,
user_profile=user_profile,
birth_year=birth_year,
background_voice=background_voice,
occupation=occupation,
)
max_tokens = 8192
agent_name = "NarrativeAgent.generate_narrative_merge"
else:
prompt = get_narrative_json_prompt(
stage=stage,
slots=slots,
new_content=new_content,
existing_content=existing_content,
user_profile=user_profile,
birth_year=birth_year,
background_voice=background_voice,
occupation=occupation,
)
max_tokens = 4096
agent_name = "NarrativeAgent.generate_narrative"
return invoke_json_object(
llm,
prompt,
max_tokens=max_tokens,
agent=agent_name,
).strip()
2026-03-19 10:38:11 +08:00
except Exception as e:
logger.warning("NarrativeAgent 生成叙事失败: {}", e)
2026-03-19 10:38:11 +08:00
if existing_content:
return f"{existing_content}\n\n{new_content}"
return new_content