初始化提交
This commit is contained in:
commit
887c1f449e
963 files changed
+166869
No files matched your search
@@ -0,0 +1,141 @@
|
||||
"""灵核情绪状态服务(设计 §11):拉模式计算 + 一炁订阅 time.* 处理器。
|
||||
|
||||
降级原则(设计 §12 / 辰正 §12):任何失败 → emotion_state=None,不阻塞对话/事件流。
|
||||
本模块是一炁订阅基础设施(计划 5 ``Dispatcher``)的第一个真实消费者。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from loguru import logger
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.db.session import AsyncSessionLocal
|
||||
from app.models.character import Character
|
||||
from app.models.chat import ChatMessage
|
||||
from app.models.personality_emotion import CharacterEmotionState
|
||||
from app.modules.personality.emotion import (
|
||||
EmotionState,
|
||||
apply_signal,
|
||||
decay,
|
||||
parse_baseline,
|
||||
to_prompt_text,
|
||||
)
|
||||
from app.modules.runtime.events import Event
|
||||
from app.repositories.personality import get_latest_personality, get_personality
|
||||
from app.repositories.personality_emotion import get_state, upsert_state
|
||||
|
||||
|
||||
async def _resolve_baseline(session: AsyncSession, character_id: str) -> EmotionState:
|
||||
"""从当前人格版本 ``emotion_baseline`` 解析 baseline。
|
||||
|
||||
character 不存在 / 无人格版本 / baseline 损坏 → 默认 neutral 0.2(parse_baseline 容错)。
|
||||
"""
|
||||
character = await session.get(Character, character_id)
|
||||
if character is None:
|
||||
return EmotionState()
|
||||
pv = None
|
||||
if character.current_personality_version_id:
|
||||
pv = await get_personality(session, character.current_personality_version_id)
|
||||
if pv is None:
|
||||
pv = await get_latest_personality(session, character_id)
|
||||
if pv is None:
|
||||
return EmotionState()
|
||||
return parse_baseline(pv.emotion_baseline)
|
||||
|
||||
|
||||
async def _count_user_turns_since(
|
||||
session: AsyncSession, character_id: str, since: datetime
|
||||
) -> float:
|
||||
"""自 since 以来该角色未删除的 user 消息数(近似对话回合)。"""
|
||||
result = await session.execute(
|
||||
select(func.count())
|
||||
.select_from(ChatMessage)
|
||||
.where(
|
||||
ChatMessage.character_id == character_id,
|
||||
ChatMessage.role == "user",
|
||||
ChatMessage.created_at > since,
|
||||
ChatMessage.deleted_at.is_(None),
|
||||
)
|
||||
)
|
||||
return float(result.scalar_one())
|
||||
|
||||
|
||||
async def compute_current_state(session: AsyncSession, character_id: str) -> EmotionState | None:
|
||||
"""拉模式:读最新 state + baseline,按自上次更新以来的 user 回合 decay。
|
||||
|
||||
- 无 state row:返回 baseline(不 decay)。
|
||||
- 有 state row:``decay(state, baseline, elapsed_turns)``。
|
||||
- 任何异常降级返回 None。
|
||||
"""
|
||||
try:
|
||||
baseline = await _resolve_baseline(session, character_id)
|
||||
row = await get_state(session, character_id)
|
||||
if row is None:
|
||||
return baseline
|
||||
current = EmotionState(mood=row.mood, intensity=row.intensity)
|
||||
# SQLite DateTime(timezone=True) 读回可能 naive,与 aware 比较前补 tz(同 time_sense 防御)。
|
||||
updated = row.updated_at
|
||||
if updated.tzinfo is None:
|
||||
updated = updated.replace(tzinfo=UTC)
|
||||
elapsed = await _count_user_turns_since(session, character_id, updated)
|
||||
return decay(current, baseline, elapsed)
|
||||
except Exception:
|
||||
logger.exception("灵核 compute_current_state 降级 character_id={}", character_id)
|
||||
return None
|
||||
|
||||
|
||||
async def compute_emotion_prompt(session: AsyncSession, character_id: str) -> str | None:
|
||||
"""拉模式 → system prompt 文本;失败返回 None(不阻塞对话)。"""
|
||||
state = await compute_current_state(session, character_id)
|
||||
if state is None:
|
||||
return None
|
||||
return to_prompt_text(state)
|
||||
|
||||
|
||||
async def apply_time_event(event: Event, session: AsyncSession) -> None:
|
||||
"""订阅处理器核心:辰正 time.* 事件 → 更新情绪状态。
|
||||
|
||||
- 读当前 state(无则用 baseline)
|
||||
- ``apply_signal`` 计算新状态
|
||||
- ``upsert_state`` + 同步 ``Character.current_mood_id``
|
||||
|
||||
无 character_id 的事件直接跳过;DB 异常由 ``handle_time_event`` 兜底。
|
||||
"""
|
||||
character_id = event.character_id
|
||||
if character_id is None:
|
||||
return
|
||||
baseline = await _resolve_baseline(session, character_id)
|
||||
row = await get_state(session, character_id)
|
||||
current = EmotionState(mood=row.mood, intensity=row.intensity) if row else baseline
|
||||
|
||||
new_state = apply_signal(current, event.type, event.payload, baseline)
|
||||
await upsert_state(
|
||||
session,
|
||||
CharacterEmotionState(
|
||||
character_id=character_id,
|
||||
mood=new_state.mood,
|
||||
intensity=new_state.intensity,
|
||||
baseline_mood=baseline.mood,
|
||||
baseline_intensity=baseline.intensity,
|
||||
last_trigger_event_id=event.id,
|
||||
),
|
||||
)
|
||||
character = await session.get(Character, character_id)
|
||||
if character is not None:
|
||||
character.current_mood_id = new_state.mood
|
||||
|
||||
|
||||
async def handle_time_event(event: Event) -> None:
|
||||
"""Dispatcher ``time.*`` 处理器:自管 session + 提交,降级不抛。
|
||||
|
||||
一炁订阅基础设施(计划 5)的第一个真实消费者,注册在 ``main.py`` lifespan。
|
||||
"""
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
await apply_time_event(event, session)
|
||||
await session.commit()
|
||||
except Exception:
|
||||
logger.exception("灵核 handle_time_event 降级 event_id={} type={}", event.id, event.type)
|
||||
Reference in new issue
Block a user