Files
hunjing/backend/app/services/goal_persist.py
T
2026-08-21 16:00:13 +08:00

93 lines
3.3 KiB
Python

"""会话目标自动持久化服务。
从用户消息自动持久化会话目标到 goals 表。
如果已有该 character 的活跃 session 级目标,判断内容是否显著不同:
- 内容相似 → 复用
- 内容不同 → 完成旧目标,创建新目标
"""
from app.models.governance import Goal
from app.repositories import governance as governance_repo
_TOPIC_SHIFT_MIN_LENGTH = 20
async def auto_persist_session_goal(
session,
character_id: str,
content: str,
) -> Goal | None:
"""自动持久化会话目标:复用活跃的 session 目标或新建。
内容过短(<10 字)不产生目标。
如果已有活跃 session 目标但内容显著不同,先完成旧目标再新建。
Returns:
持久化后的 Goal(可能为 None)。
"""
goal_text = (content or "").strip()
if not goal_text or len(goal_text) < 10:
return None
existing = await governance_repo.list_active_session_goal(session, character_id)
if existing is not None:
# 判断是否话题转换:内容不再相似时完成旧目标
if _is_topic_shift(existing.title, goal_text):
existing.status = "completed"
existing.progress = 1.0
# 创建新目标
goal = Goal(
character_id=character_id,
title=goal_text[:120],
description=f"从对话自动提取的会话目标:{goal_text[:500]}",
level="session",
type="mission",
status="active",
priority=50,
)
await governance_repo.add_goal(session, goal)
return goal
return existing
# 新建 session 目标
goal = Goal(
character_id=character_id,
title=goal_text[:120],
description=f"从对话自动提取的会话目标:{goal_text[:500]}",
level="session",
type="mission",
status="active",
priority=50,
)
await governance_repo.add_goal(session, goal)
return goal
def _is_topic_shift(existing_title: str, new_content: str) -> bool:
"""判断新内容与旧目标标题是否话题转换。
简单规则:如果新内容较长且与旧标题共享的关键词很少,视为话题转换。
这是启发式方法,后续可升级为 LLM 判断。
"""
if len(new_content) < _TOPIC_SHIFT_MIN_LENGTH:
return False
# 提取关键词集合(按空格/标点分割)
import re
old_words = set(re.split(r"[\s,。!?、;:,\.!?;:]+", existing_title.lower()))
new_words = set(re.split(r"[\s,。!?、;:,\.!?;:]+", new_content.lower()))
# 排除常见的助词
stop_words = {"的", "了", "是", "在", "有", "和", "就", "不", "人", "都", "一",
"一个", "上", "也", "很", "到", "说", "要", "去", "你", "我", "他",
"这", "那", "吗", "啊", "呢", "吧", "地", "得"}
old_keywords = old_words - stop_words
new_keywords = new_words - stop_words
if not old_keywords or not new_keywords:
return False
overlap = old_keywords & new_keywords
# 如果旧关键词的一个都没有出现在新内容中,或者重合率极低
if not overlap:
return True
return len(overlap) / max(len(old_keywords), len(new_keywords)) < 0.15