数据库 - 新增迁移 0003:timeline_events.memory_source_id 外键 → memory_sources,便于按 ingest 源做时间线幂等 后端 - 记忆 - 新增 ingest 后 LLM 富化(摘要/事实/时间线),可配置开关与最大字符数 - 新增证据包组装:合并 chunk、摘要、事实、时间线、故事等检索结果;支持空 query 时是否仍带 rolling 等开关 - repo/retriever/service/router/schemas/summarizer/timeline/extractor 等扩展;文档 memory-retrieval.md 更新 后端 - 对话 WS - 增加 PING/PONG;分段 ASR 日志与空音频处理;转写失败与「无助手回复」错误提示更明确 - 助手多段回复持久化使用统一分隔符,与分段逻辑一致 后端 - Agent - reply_limits:按 [SPLIT] 与段落拆段,并保证非空 fallback,供 WS 与 TTS 多段下发 后端 - 回忆录任务 - transcript ingest 记录 source_id;任务成功结?
114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
"""
|
||
NarrativeAgent:生成创意标题和叙事改写。
|
||
叙事正文走 `get_narrative_json_prompt` / `get_narrative_merge_json_prompt`(传记作家式书面语 + 事实边界)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
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,
|
||
)
|
||
from app.core.langchain_llm import invoke_json_object
|
||
from app.core.logging import get_logger
|
||
from app.features.memoir.memoir_images.json_payload import extract_json_payload
|
||
|
||
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(
|
||
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} 回忆"
|
||
except Exception as e:
|
||
logger.warning("NarrativeAgent 生成标题失败: {}", e)
|
||
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,
|
||
) -> str:
|
||
"""将新对话改写为叙述。若无 LLM 则直接拼接。
|
||
|
||
若 `existing_content` 非空(append 路径),使用整篇合并提示,输出覆盖全篇的有序段落。
|
||
"""
|
||
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,
|
||
)
|
||
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,
|
||
)
|
||
max_tokens = 4096
|
||
agent_name = "NarrativeAgent.generate_narrative"
|
||
return invoke_json_object(
|
||
llm,
|
||
prompt,
|
||
max_tokens=max_tokens,
|
||
agent=agent_name,
|
||
).strip()
|
||
except Exception as e:
|
||
logger.warning("NarrativeAgent 生成叙事失败: {}", e)
|
||
if existing_content:
|
||
return f"{existing_content}\n\n{new_content}"
|
||
return new_content
|