M2b:删除旧 chat 体系——线性会话/叩心/情绪/纪念日模型与页面退役(迁移 003 双向验证),runs 去会话外键,测试全量修剪
This commit is contained in:
1 parent
5f36e3785c
commit
ad9c9e942a
22 files changed
+424
-427
No files matched your search
@@ -9,7 +9,6 @@ from app.api.v1.endpoints import (
|
||||
avatar,
|
||||
character_import,
|
||||
characters,
|
||||
chat,
|
||||
drive,
|
||||
events,
|
||||
evolution,
|
||||
@@ -30,7 +29,6 @@ from app.api.v1.endpoints import (
|
||||
sandbox_files,
|
||||
settings,
|
||||
skills,
|
||||
time,
|
||||
tools,
|
||||
world,
|
||||
)
|
||||
@@ -40,7 +38,6 @@ api_router.include_router(health.router)
|
||||
api_router.include_router(auth.router)
|
||||
api_router.include_router(characters.router)
|
||||
api_router.include_router(character_import.router)
|
||||
api_router.include_router(chat.router)
|
||||
api_router.include_router(runs.router)
|
||||
api_router.include_router(kernel_runs.router)
|
||||
api_router.include_router(embryo_drafts.router)
|
||||
@@ -66,4 +63,3 @@ api_router.include_router(goals.router)
|
||||
api_router.include_router(admin.router)
|
||||
api_router.include_router(audio.router)
|
||||
api_router.include_router(avatar.router)
|
||||
api_router.include_router(time.router)
|
||||
@@ -38,7 +38,6 @@ router = APIRouter(prefix="/runs", tags=["runs"])
|
||||
@router.get("", response_model=Page[RunResponse])
|
||||
async def list_runs(
|
||||
character_id: str | None = Query(default=None, description="按人物过滤。"),
|
||||
session_id: str | None = Query(default=None, description="按会话过滤。"),
|
||||
status: str | None = Query(default=None, description="按状态过滤。"),
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=20, ge=1, le=100),
|
||||
@@ -46,7 +45,6 @@ async def list_runs(
|
||||
"""Run 列表(按创建时间倒序);归档页运行时间河数据源。"""
|
||||
return await list_runs_response(
|
||||
character_id=character_id,
|
||||
session_id=session_id,
|
||||
status=status,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
|
||||
@@ -30,12 +30,10 @@ from app.core.logging import (
|
||||
from app.db.init_data import seed_default_data
|
||||
from app.db.session import AsyncSessionLocal, close_engine
|
||||
from app.integrations.sandbox.client import SandboxClient
|
||||
from app.modules.knocking.pivot import KnockingPivot
|
||||
from app.modules.safety.confirmation import InMemoryConfirmationStore
|
||||
from app.schemas.common import ErrorResponse
|
||||
from app.services.dispatcher import Dispatcher
|
||||
from app.services.event_store import DbEventStore
|
||||
from app.services.personality_emotion import handle_time_event
|
||||
from app.services.safety import load_governor_from_db
|
||||
|
||||
# 模块级初始化日志系统:import 本模块即完成 loguru 配置并接管标准 logging,
|
||||
@@ -117,12 +115,7 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
# 一炁进程内订阅分发器:订阅 EventStore 全量事件,按 glob pattern 分发给注册的 handler。
|
||||
# 各枢核心就绪后在 start() 前 register;lifespan 管理 start/stop。
|
||||
_app.state.dispatcher = Dispatcher(_app.state.event_store)
|
||||
# 灵核情绪状态机:订阅辰正 time.* 事件驱动 EmotionState(设计 §11,首个真实 handler)
|
||||
_app.state.dispatcher.register("time.*", handle_time_event)
|
||||
await _app.state.dispatcher.start()
|
||||
# 叩心枢主动对话守护进程:后台常驻循环,在 dispatcher 就绪后启动
|
||||
_app.state.knocking_pivot = KnockingPivot(_app.state.event_store)
|
||||
await _app.state.knocking_pivot.start()
|
||||
_app.state.confirmation_store = InMemoryConfirmationStore()
|
||||
if settings.use_db_confirmation_store:
|
||||
from app.modules.safety.confirmation import DbConfirmationStore
|
||||
@@ -177,9 +170,6 @@ async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
|
||||
|
||||
yield
|
||||
|
||||
# 叩心枢先停,再停 dispatcher
|
||||
if getattr(_app.state, "knocking_pivot", None) is not None:
|
||||
await _app.state.knocking_pivot.stop()
|
||||
# Dispatcher 先停(取消 pump/consumer task + unsubscribe all_queue),再关 http/engine
|
||||
if getattr(_app.state, "dispatcher", None) is not None:
|
||||
await _app.state.dispatcher.stop()
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from app.models.big_cycle import BigCycle
|
||||
from app.models.candidates import MemoryCandidate, ProfileCandidate
|
||||
from app.models.character import Character
|
||||
from app.models.chat import ChatMessage, ChatSession
|
||||
from app.models.drive import CuriosityQuestion, ExploratorySubgoal
|
||||
from app.models.evolution import EvolutionCandidate
|
||||
from app.models.governance import (
|
||||
@@ -28,12 +27,10 @@ from app.models.mirror_tree import (
|
||||
)
|
||||
from app.models.memory import Memory, MemoryLink
|
||||
from app.models.personality import PersonalityProposal, PersonalityVersion
|
||||
from app.models.personality_emotion import CharacterEmotionState
|
||||
from app.models.rbac import Permission, Role, RolePermission, UserRole
|
||||
from app.models.runtime import Event, Run
|
||||
from app.models.safety import AuditEvent, PendingConfirmationRecord
|
||||
from app.models.skills import Skill
|
||||
from app.models.time_anniversary import TimeAnniversary
|
||||
from app.models.user import User
|
||||
from app.models.world import CharacterWorldEntity, WorldRelation
|
||||
|
||||
@@ -42,11 +39,8 @@ __all__ = [
|
||||
"AuditEvent",
|
||||
"BigCycle",
|
||||
"Character",
|
||||
"CharacterEmotionState",
|
||||
"CharacterImportJob",
|
||||
"CharacterWorldEntity",
|
||||
"ChatMessage",
|
||||
"ChatSession",
|
||||
"CuriosityQuestion",
|
||||
"Event",
|
||||
"EvolutionCandidate",
|
||||
@@ -72,7 +66,6 @@ __all__ = [
|
||||
"RolePermission",
|
||||
"Run",
|
||||
"Skill",
|
||||
"TimeAnniversary",
|
||||
"User",
|
||||
"UserRole",
|
||||
"WorldEntity",
|
||||
|
||||
@@ -14,13 +14,11 @@ class Run(Base):
|
||||
__tablename__ = "runs"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(80), primary_key=True, default=new_run_id)
|
||||
# rewrite/1.0:照心试演(kind=rehearsal)无正式人物,character_id 允许为空
|
||||
# rewrite/1.0:照心试演(kind=rehearsal)无正式人物,character_id 允许为空;
|
||||
# 旧 chat_sessions 外键随线性会话模型退役移除
|
||||
character_id: Mapped[str | None] = mapped_column(
|
||||
String(80), ForeignKey("characters.id"), index=True, nullable=True
|
||||
)
|
||||
session_id: Mapped[str | None] = mapped_column(
|
||||
String(80), ForeignKey("chat_sessions.id"), index=True
|
||||
)
|
||||
status: Mapped[str] = mapped_column(String(40), nullable=False, default="queued", index=True)
|
||||
# run.kind:foreground / background_task / awakening / rehearsal(契约词典 §8.2)
|
||||
kind: Mapped[str] = mapped_column(
|
||||
|
||||
@@ -14,17 +14,14 @@ async def list_runs(
|
||||
session: AsyncSession,
|
||||
*,
|
||||
character_id: str | None = None,
|
||||
session_id: str | None = None,
|
||||
status: str | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> tuple[list[Run], int]:
|
||||
"""按创建时间倒序分页返回 Run;可选按人物/会话/状态过滤,返回 (条目, 总数)。"""
|
||||
"""按创建时间倒序分页返回 Run;可选按人物/状态过滤,返回 (条目, 总数)。"""
|
||||
conds = []
|
||||
if character_id is not None:
|
||||
conds.append(Run.character_id == character_id)
|
||||
if session_id is not None:
|
||||
conds.append(Run.session_id == session_id)
|
||||
if status is not None:
|
||||
conds.append(Run.status == status)
|
||||
count_stmt = select(func.count()).select_from(Run)
|
||||
@@ -127,11 +124,3 @@ async def summarize_by_trace(session: AsyncSession):
|
||||
return [(row[0], int(row[1]), row[2]) for row in result.all()]
|
||||
|
||||
|
||||
async def list_session_ids_by_runs(session: AsyncSession, run_ids: list[str]) -> list[str]:
|
||||
"""按 run_id 集合查询关联的 chat session_id 去重列表(链路追踪:run → 对话)。"""
|
||||
if not run_ids:
|
||||
return []
|
||||
result = await session.execute(
|
||||
select(Run.session_id).where(Run.id.in_(run_ids), Run.session_id.is_not(None))
|
||||
)
|
||||
return sorted({row[0] for row in result.all() if row[0]})
|
||||
@@ -69,7 +69,6 @@ class RunResponse(BaseModel):
|
||||
character_id: str | None = Field(
|
||||
default=None, description="所属人物 ID;照心试演(kind=rehearsal)无正式人物。"
|
||||
)
|
||||
session_id: str | None = Field(default=None, description="关联会话 ID。")
|
||||
status: str = Field(
|
||||
description="运行状态:queued / running / completed / failed / cancelled / interrupted。"
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user