M2b:删除旧 chat 体系——线性会话/叩心/情绪/纪念日模型与页面退役(迁移 003 双向验证),runs 去会话外键,测试全量修剪

This commit is contained in:
chenxin committed 2026-08-21 23:43:31 +08:00
1 parent 5f36e3785c
commit ad9c9e942a
22 files changed
+424 -427

No files matched your search

+2 -13
View File
@@ -262,14 +262,11 @@ async def get_trace(trace_id: str) -> TraceDetail:
*[audit.character_id for audit in audits if audit.character_id],
}
)
session_ids = (
sorted(await runtime_repo.list_session_ids_by_runs(session, run_ids)) if run_ids else []
)
return TraceDetail(
trace_id=trace_id,
character_ids=character_ids,
run_ids=run_ids,
session_ids=session_ids,
session_ids=[],
items=items,
)
@@ -342,15 +339,7 @@ async def list_traces(*, limit: int = 50) -> TraceListResponse:
).all():
_entry(tid)["character_ids"].add(cid)
# 对话归属(events.run_id → runs.session_id)
for tid, sid in (
await session.execute(
select(Event.trace_id, Run.session_id)
.join(Run, Event.run_id == Run.id)
.where(Event.trace_id.is_not(None), Run.session_id.is_not(None))
)
).all():
_entry(tid)["session_ids"].add(sid)
# 对话归属随线性会话模型退役;镜树归属随 M2c 司命台重设计接入(tree_id 聚合)
total = len(merged)
ordered = sorted(merged.values(), key=lambda e: e["last_seen"], reverse=True)
+5 -41
View File
@@ -10,7 +10,6 @@ from app.core.time import utc_now
from app.db.session import AsyncSessionLocal
from app.models.character import Character
from app.models.personality import PersonalityVersion
from app.models.time_anniversary import TimeAnniversary
from app.modules.character import (
MODEL_POLICY_CREATE_DEFAULTS,
SYSTEM_TEMPLATE_OWNER_SCOPE,
@@ -19,7 +18,6 @@ from app.modules.character import (
)
from app.repositories import characters as character_repo
from app.repositories import personality as personality_repo
from app.repositories import time_anniversaries as anniversary_repo
from app.repositories.json_utils import dumps, loads_dict
from app.schemas.character import (
ActivateCharacterResponse,
@@ -232,7 +230,7 @@ async def create_character_from_template(
session: AsyncSession, template: Character, owner_scope: str
) -> Character:
# 模板的 birthday 仅在兜底(空/缺失)时不继承;显式设置的有效生日原样保留,
# 由 _ensure_default_birthday 写入子角色的 profile 与 time_anniversaries 表。
# 由 _ensure_default_birthday 写入子角色的 profile。
profile = loads_dict(template.profile_json)
birthday = profile.get("birthday")
if not (isinstance(birthday, str) and birthday.strip()):
@@ -388,51 +386,17 @@ async def _audit_character(session: AsyncSession, character: Character, action:
async def _ensure_default_birthday(session: AsyncSession, character: Character) -> None:
"""birthday 缺失时用 created_at 兜底:写 profile.birthday 并建一条生日纪念日。
"""birthday 缺失时用 created_at 兜底:仅写 profile.birthday。
- 仅在 profile.birthday 为空/缺失时触发默认兜底;用户或模板已设则不覆盖。
- 无论 birthday 是否已设,只要角色尚无 birthday 纪念日,就建一条。
- profile.birthday 与 TimeAnniversary.date 取 birthday 值(显式)或 created_at 的 ISO 日期。
- 须在 character flush 后调用(依赖 id 与 created_at);同一事务内随外层 commit 落库。
纪念日表(time_anniversaries)随旧时间模型退役删除;辰正(M8 重实现)落地后
生日提醒改由时态感知按 profile.birthday 计算,不再预写行。
"""
profile = loads_dict(character.profile_json)
birthday = profile.get("birthday")
if isinstance(birthday, str) and birthday.strip():
# birthday 已设但可能无对应纪念日(如从模板继承),仅 ISO 格式生日补建
existing = await anniversary_repo.list_anniversaries(session, character.id)
if not any(a.kind == "birthday" for a in existing):
try:
datetime.strptime(birthday, "%Y-%m-%d")
except (ValueError, TypeError):
pass
else:
anniversary = TimeAnniversary(
character_id=character.id,
name=f"{character.display_name or character.name}·生辰",
calendar="solar",
date=birthday,
recurs_yearly=True,
kind="birthday",
subject_type="character_self",
source="auto",
)
await anniversary_repo.create_anniversary(session, anniversary)
return
birthday_iso = character.created_at.strftime("%Y-%m-%d")
profile["birthday"] = birthday_iso
profile["birthday"] = character.created_at.strftime("%Y-%m-%d")
character.profile_json = dumps(profile)
anniversary = TimeAnniversary(
character_id=character.id,
name=f"{character.display_name or character.name}·生辰",
calendar="solar",
date=birthday_iso,
recurs_yearly=True,
kind="birthday",
subject_type="character_self",
source="auto",
)
await anniversary_repo.create_anniversary(session, anniversary)
async def create_character(
session: AsyncSession,
+1 -5
View File
@@ -28,14 +28,12 @@ async def create_run(
session: AsyncSession,
*,
character_id: str,
session_id: str | None,
model_hint: str,
goal: str = "回复用户消息",
origin: str = "chat",
) -> Run:
run = Run(
character_id=character_id,
session_id=session_id,
status="running",
goal=goal,
origin=origin,
@@ -175,17 +173,15 @@ async def get_run_response(run_id: str) -> RunResponse:
async def list_runs_response(
*,
character_id: str | None = None,
session_id: str | None = None,
status: str | None = None,
page: int = 1,
page_size: int = 20,
) -> Page[RunResponse]:
"""Run 列表(按创建时间倒序);可选按人物/会话/状态过滤。归档页运行时间河数据源。"""
"""Run 列表(按创建时间倒序);可选按人物/状态过滤。归档页运行时间河数据源。"""
async with AsyncSessionLocal() as session:
items, total = await runtime_repo.list_runs(
session,
character_id=character_id,
session_id=session_id,
status=status,
page=page,
page_size=page_size,
-39
View File
@@ -6,7 +6,6 @@ from typing import Any
from app.core.time import to_iso, utc_now
from app.models.candidates import MemoryCandidate, ProfileCandidate
from app.models.character import Character
from app.models.chat import ChatMessage, ChatSession
from app.models.governance import (
AgentRosterEntry,
GenerationPreferences,
@@ -33,7 +32,6 @@ from app.schemas.admin import (
)
from app.schemas.candidates import MemoryCandidateResponse, ProfileCandidateResponse
from app.schemas.character import CharacterResponse, CharacterSummaryResponse
from app.schemas.chat import ChatMessageResponse, ChatSessionResponse
from app.schemas.imports import ImportItemResponse, ImportJobResponse
from app.schemas.memory import MemoryResponse
from app.schemas.personality import PersonalityProposalResponse, PersonalityResponse
@@ -125,42 +123,6 @@ def proposal_to_response(proposal: PersonalityProposal) -> PersonalityProposalRe
)
def chat_session_to_response(
chat_session: ChatSession,
*,
message_count: int = 0,
last_message_preview: str | None = None,
) -> ChatSessionResponse:
return ChatSessionResponse(
session_id=chat_session.id,
character_id=chat_session.character_id,
title=chat_session.title,
status=chat_session.status,
created_at=to_iso(chat_session.created_at) or "",
updated_at=to_iso(chat_session.updated_at) or "",
message_count=message_count,
last_message_preview=last_message_preview,
)
def chat_message_to_response(message: ChatMessage) -> ChatMessageResponse:
return ChatMessageResponse(
message_id=message.id,
session_id=message.session_id,
character_id=message.character_id,
role=message.role,
content=message.content,
modality=message.modality,
run_id=message.run_id,
metadata=loads_dict(message.metadata_json),
sensitivity=message.sensitivity,
versions=loads_list(message.versions_json),
active_version_idx=message.active_version_idx,
created_at=to_iso(message.created_at) or "",
deleted_at=to_iso(message.deleted_at),
)
def memory_to_response(memory: Memory) -> MemoryResponse:
return MemoryResponse(
id=memory.id,
@@ -185,7 +147,6 @@ def run_to_response(run: Run) -> RunResponse:
return RunResponse(
id=run.id,
character_id=run.character_id,
session_id=run.session_id,
status=run.status,
kind=run.kind,
goal=run.goal,