M2b:导入改造产出可审阅灵胚(启镜物化世谱)与组装管线(符文展开/火候进入 IR),Kernel 打通采样参数链路

This commit is contained in:
chenxin committed 2026-08-21 23:43:29 +08:00
1 parent e6a3b7121f
commit 5f36e3785c
86 files changed
+424 -14455

No files matched your search

@@ -1,29 +1,49 @@
"""角色卡导入 / 导出 API。"""
"""角色卡导入 / 导出 API。
rewrite/1.0 M2b:导入产出**可审阅灵胚**(含 character_book 暂存),不再直接建人物;
经照心编辑与启镜(POST /embryo-drafts/{id}/activate)后才物化人物与世谱。
"""
import json
from fastapi import APIRouter, Depends, File, Response, UploadFile
from app.api.v1.deps import get_current_user
from app.domains import jingsheng
from app.schemas.auth import CurrentUser
from app.schemas.character import ImportCardResponse
from app.services.card_import import export_character_card, import_character_card_response
from app.services.card_import import card_to_embryo_payload, export_character_card
router = APIRouter(prefix="/characters", tags=["characters"])
@router.post("/import-card", response_model=ImportCardResponse)
@router.post("/import-card", status_code=201)
async def import_character_card_api(
file: UploadFile = File(..., description="角色卡文件,支持 .png / .json / .charx。"),
current_user: CurrentUser = Depends(get_current_user),
) -> ImportCardResponse:
"""导入酒馆角色卡(V1/V2/V3),建立人物 + 世谱(character_book)。
) -> dict:
"""导入酒馆角色卡(V1/V2/V3)→ 生成可审阅灵胚。
``personality`` 写入道体 ``personality_summary`` 作初始值;
``character_book`` 转为该角色的世谱书 + 条目。
卡内容整体进入灵胚道体草稿(identity_baseline.import_card),
``character_book`` 在启镜时物化为该人物的世谱书与条目。
"""
raw = await file.read()
return await import_character_card_response(raw, owner_scope=current_user.owner_scope)
payload = card_to_embryo_payload(raw)
book_entry_count = payload.pop("book_entry_count")
embryo = await jingsheng.create_embryo(
owner_scope=current_user.owner_scope,
source="imported",
name=payload["name"],
description=payload["description"],
identity_baseline=payload["identity_baseline"],
)
return {
"embryo_id": embryo.id,
"status": embryo.status,
"name": embryo.name,
"source": embryo.source,
"book_entry_count": book_entry_count,
"next": "照心编辑 PATCH /embryo-drafts/{id},启镜 POST /embryo-drafts/{id}/activate",
}
@router.get("/{character_id}/export-card")
-137
View File
@@ -1,137 +0,0 @@
"""聊天 API。"""
from fastapi import APIRouter, Depends, Header, Query, Request, Response, status
from app.api.v1.deps import get_current_user
from app.schemas.auth import CurrentUser
from app.schemas.chat import (
ChatMessageListResponse,
ChatMessageResponse,
ChatSessionListResponse,
ChatSessionResponse,
CreateSessionRequest,
SelectVersionRequest,
SendMessageRequest,
SendMessageResponse,
UpdateSessionRequest,
)
from app.services.chat import (
create_session,
delete_session,
get_session_response,
list_message_responses,
list_session_responses,
select_message_version,
send_message,
update_session_response,
)
from app.services.idempotency import run_idempotent
router = APIRouter(prefix="/chat", tags=["chat"])
@router.post("/sessions", response_model=ChatSessionResponse)
async def create_chat_session(
payload: CreateSessionRequest,
current_user: CurrentUser = Depends(get_current_user),
) -> ChatSessionResponse:
return await create_session(payload, owner_scope=current_user.owner_scope)
@router.get("/sessions", response_model=ChatSessionListResponse)
async def list_chat_sessions(
current_user: CurrentUser = Depends(get_current_user),
status: str | None = Query(default=None, description="按状态过滤:active / archived"),
limit: int = Query(default=50, ge=1, le=100),
offset: int = Query(default=0, ge=0),
) -> ChatSessionListResponse:
return await list_session_responses(
owner_scope=current_user.owner_scope, status=status, limit=limit, offset=offset
)
@router.get("/sessions/{session_id}", response_model=ChatSessionResponse)
async def get_chat_session(
session_id: str,
current_user: CurrentUser = Depends(get_current_user),
) -> ChatSessionResponse:
return await get_session_response(session_id, owner_scope=current_user.owner_scope)
@router.patch("/sessions/{session_id}", response_model=ChatSessionResponse)
async def update_chat_session(
session_id: str,
payload: UpdateSessionRequest,
current_user: CurrentUser = Depends(get_current_user),
) -> ChatSessionResponse:
return await update_session_response(session_id, payload, owner_scope=current_user.owner_scope)
@router.delete("/sessions/{session_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_chat_session(
session_id: str,
current_user: CurrentUser = Depends(get_current_user),
) -> Response:
await delete_session(session_id, owner_scope=current_user.owner_scope)
return Response(status_code=status.HTTP_204_NO_CONTENT)
@router.post("/sessions/{session_id}/messages", response_model=SendMessageResponse)
async def send_chat_message(
session_id: str,
payload: SendMessageRequest,
request: Request,
idempotency_key: str | None = Header(default=None, alias="Idempotency-Key"),
current_user: CurrentUser = Depends(get_current_user),
) -> SendMessageResponse:
return await run_idempotent(
key=idempotency_key,
method="POST",
path=str(request.url.path),
payload=payload,
response_type=SendMessageResponse,
resource_type="run",
handler=lambda session: send_message(
session,
session_id,
payload,
owner_scope=current_user.owner_scope,
tool_runtime=getattr(request.app.state, "tool_runtime", None),
confirmation_registry=getattr(request.app.state, "confirmation_registry", None),
confirmation_store=getattr(request.app.state, "confirmation_store", None),
event_store=getattr(request.app.state, "event_store", None),
agent_run_registry=getattr(request.app.state, "agent_run_registry", None),
python_skill_catalog=getattr(request.app.state, "python_skill_catalog", None),
),
)
@router.get("/sessions/{session_id}/messages", response_model=ChatMessageListResponse)
async def list_chat_messages(
session_id: str,
current_user: CurrentUser = Depends(get_current_user),
limit: int = Query(default=100, ge=1, le=200),
offset: int = Query(default=0, ge=0),
) -> ChatMessageListResponse:
return await list_message_responses(
session_id, owner_scope=current_user.owner_scope, limit=limit, offset=offset
)
@router.patch(
"/sessions/{session_id}/messages/{message_id}/version",
response_model=ChatMessageResponse,
)
async def select_message_version_endpoint(
session_id: str,
message_id: str,
payload: SelectVersionRequest,
current_user: CurrentUser = Depends(get_current_user),
) -> ChatMessageResponse:
"""选择/激活消息的某个历史版本。"""
return await select_message_version(
session_id,
message_id,
payload,
owner_scope=current_user.owner_scope,
)
-113
View File
@@ -1,113 +0,0 @@
"""辰正时态 API(设计 §10)。
挂载于 /api/v1/time。事件不走 REST(走 EventStore 订阅)。路由薄层:参数校验 → 调
service → 转响应;session 由 service 自管(deps.py 惯例)。
"""
from fastapi import APIRouter, Depends, Request
from app.api.v1.deps import get_current_user, get_event_store
from app.core.config import settings
from app.schemas.auth import CurrentUser
from app.schemas.time import (
AcquaintanceResponse,
AnniversaryCreateRequest,
AnniversaryResponse,
AnniversaryUpdateRequest,
SnapshotResponse,
TimeConfigResponse,
)
from app.services.time_sense import (
create_anniversary_for_active,
delete_anniversary_or_404,
get_acquaintance_for_active,
get_anniversary_or_404,
get_live_snapshot,
list_anniversaries_for_active,
update_anniversary_or_404,
)
router = APIRouter(prefix="/time", tags=["time"])
@router.get("/snapshot", response_model=SnapshotResponse)
async def get_snapshot(
request: Request, current_user: CurrentUser = Depends(get_current_user)
) -> SnapshotResponse:
"""当前 TemporalSnapshot + ModulationFactors + context_text。"""
result = await get_live_snapshot(get_event_store(request), owner_scope=current_user.owner_scope)
return SnapshotResponse(
snapshot=result.snapshot,
factors=result.factors,
context_text=result.context_text,
)
@router.get("/anniversaries", response_model=list[AnniversaryResponse])
async def list_anniversaries_api(
current_user: CurrentUser = Depends(get_current_user),
) -> list[AnniversaryResponse]:
items = await list_anniversaries_for_active(owner_scope=current_user.owner_scope)
return [AnniversaryResponse.model_validate(a) for a in items]
@router.post("/anniversaries", response_model=AnniversaryResponse, status_code=201)
async def create_anniversary_api(
payload: AnniversaryCreateRequest, current_user: CurrentUser = Depends(get_current_user)
) -> AnniversaryResponse:
created = await create_anniversary_for_active(payload, owner_scope=current_user.owner_scope)
return AnniversaryResponse.model_validate(created)
@router.get("/anniversaries/{anniv_id}", response_model=AnniversaryResponse)
async def get_anniversary_api(
anniv_id: str, current_user: CurrentUser = Depends(get_current_user)
) -> AnniversaryResponse:
return AnniversaryResponse.model_validate(
await get_anniversary_or_404(anniv_id, owner_scope=current_user.owner_scope)
)
@router.patch("/anniversaries/{anniv_id}", response_model=AnniversaryResponse)
async def update_anniversary_api(
anniv_id: str,
payload: AnniversaryUpdateRequest,
current_user: CurrentUser = Depends(get_current_user),
) -> AnniversaryResponse:
return AnniversaryResponse.model_validate(
await update_anniversary_or_404(anniv_id, payload, owner_scope=current_user.owner_scope)
)
@router.delete("/anniversaries/{anniv_id}", status_code=204)
async def delete_anniversary_api(
anniv_id: str, current_user: CurrentUser = Depends(get_current_user)
) -> None:
await delete_anniversary_or_404(anniv_id, owner_scope=current_user.owner_scope)
@router.get("/acquaintance", response_model=AcquaintanceResponse)
async def get_acquaintance_api(
request: Request, current_user: CurrentUser = Depends(get_current_user)
) -> AcquaintanceResponse:
acq = await get_acquaintance_for_active(
get_event_store(request), owner_scope=current_user.owner_scope
)
return AcquaintanceResponse(total_days=acq.total_days, natural_language=acq.natural_language)
@router.get("/config", response_model=TimeConfigResponse)
async def get_time_config_api(
current_user: CurrentUser = Depends(get_current_user), # noqa: ARG001
) -> TimeConfigResponse:
return TimeConfigResponse(
user_timezone=settings.user_timezone,
home_timezone=settings.home_timezone,
default_city=settings.default_city,
offline_active_minutes=settings.offline_active_minutes,
offline_short_absence_minutes=settings.offline_short_absence_minutes,
offline_long_absence_minutes=settings.offline_long_absence_minutes,
offline_reunion_impact_minutes=settings.offline_reunion_impact_minutes,
care_enabled=settings.care_enabled,
jetlag_threshold_minutes=settings.jetlag_threshold_minutes,
)