Files
life-echo/api/app/agents/memoir/fidelity_check_agent.py
Kevin a3f61fcc0f feat(api+app): 对话阶段化、回忆录流水线与客户端会话体验
- DB: segments 用户输入文本(Alembic 0002)
- Chat: 阶段检测/阶段提示/回复限制,编排与访谈/画像 prompts 调整
- Memoir: 忠实度检查 agent,叙事与分类等链路更新
- Core: agent 日志、Alembic 启动、LangChain/日志/配置等
- Story: time_hints;Memory 检索与相关测试
- Expo: 助手头像、会话页与消息拆分、实时会话与文案/i18n
- Docs/scripts/tests: 迁移脚本、LLM JSON/记忆检索文档、新增单测
2026-03-26 12:13:36 +08:00

89 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
FidelityCheckAgent比较「用户口述」与叙事 JSON 输出,判定是否存在明显编造或越界。
失败时由流水线回退为口述正文(见 story_pipeline_sync
"""
from __future__ import annotations
import json
import re
from typing import Any
from app.core.config import settings
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__)
# 生成稿中出现的四位年份,若口述中未出现同串,仅打日志(不误杀)
_YEAR_4_RE = re.compile(r"(?<!\d)(19|20)\d{2}(?!\d)")
def _log_suspicious_years_not_in_oral(oral_text: str, narrative_json: str) -> None:
oral = oral_text or ""
gen = narrative_json or ""
for m in _YEAR_4_RE.finditer(gen):
y = m.group(0)
if y not in oral:
logger.debug(
"event=fidelity_heuristic_year_not_in_oral year={} oral_len={} gen_len={}",
y,
len(oral),
len(gen),
)
class FidelityCheckAgent:
"""叙事忠实度检查json_object失败时上层应回退为口述原文。"""
def passes(
self,
*,
oral_text: str,
narrative_json: str,
llm: Any,
) -> bool:
if not llm or not settings.memoir_fidelity_check_enabled:
return True
oral = (oral_text or "").strip()
gen = (narrative_json or "").strip()
if not oral or not gen:
return True
_log_suspicious_years_not_in_oral(oral, gen)
prompt = f"""你是事实核对员。比较下面两段文字。
【用户口述】(亲历内容)
{oral[:8000]}
【模型生成的 JSON 叙事】(应只含口述中已有事实的整理,不得添油加醋)
{gen[:16000]}
判断:生成稿是否出现**口述中明显没有**的具体人名、地名、时间、数字、事件经过、对话,或把摘录/档案里才有的信息写成了用户亲口经历?
若存在明显编造或越界pass=false若仅口语转书面、删赘词、合并指代pass=true。
**JSON 输出**:只输出一个合法 JSON 对象。
{{"pass": true, "reason": null}}
{{"pass": false, "reason": "一句话说明"}}
只输出 JSON不要其它文字。"""
try:
raw = invoke_json_object(
llm,
prompt,
max_tokens=settings.memoir_fidelity_check_max_tokens,
agent="FidelityCheckAgent.passes",
)
data = json.loads(extract_json_payload(raw))
ok = bool(data.get("pass", True))
if not ok:
logger.warning(
"event=fidelity_check_fail reason={}",
(data.get("reason") or "")[:200],
)
return ok
except Exception as e:
logger.warning("FidelityCheckAgent 解析失败,放行: {}", e)
return True