138 lines
4.7 KiB
Python
138 lines
4.7 KiB
Python
"""运行时数据访问。"""
|
||
|
||
from sqlalchemy import func, select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.models.runtime import Event, Run
|
||
|
||
|
||
async def get_run(session: AsyncSession, run_id: str) -> Run | None:
|
||
return await session.get(Run, run_id)
|
||
|
||
|
||
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;可选按人物/会话/状态过滤,返回 (条目, 总数)。"""
|
||
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)
|
||
list_stmt = select(Run)
|
||
if conds:
|
||
count_stmt = count_stmt.where(*conds)
|
||
list_stmt = list_stmt.where(*conds)
|
||
total = int((await session.execute(count_stmt)).scalar_one())
|
||
result = await session.execute(
|
||
list_stmt.order_by(Run.created_at.desc()).offset((page - 1) * page_size).limit(page_size)
|
||
)
|
||
return list(result.scalars().all()), total
|
||
|
||
|
||
async def count_events(session: AsyncSession, run_id: str) -> int:
|
||
result = await session.execute(
|
||
select(func.count()).select_from(Event).where(Event.run_id == run_id)
|
||
)
|
||
return int(result.scalar_one())
|
||
|
||
|
||
async def next_event_sequence(session: AsyncSession, run_id: str) -> int:
|
||
result = await session.execute(select(func.max(Event.sequence)).where(Event.run_id == run_id))
|
||
current = result.scalar_one_or_none()
|
||
return int(current or 0) + 1
|
||
|
||
|
||
async def get_event(session: AsyncSession, event_id: str) -> Event | None:
|
||
return await session.get(Event, event_id)
|
||
|
||
|
||
async def has_active_runs(
|
||
session: AsyncSession,
|
||
character_id: str,
|
||
) -> bool:
|
||
"""检查指定角色是否有活跃的进行中 run(大周天任务中断防护用)。"""
|
||
result = await session.execute(
|
||
select(func.count()).select_from(Run).where(
|
||
Run.character_id == character_id,
|
||
Run.status == "running",
|
||
)
|
||
)
|
||
return int(result.scalar_one()) > 0
|
||
|
||
|
||
async def list_events_by_run(
|
||
session: AsyncSession,
|
||
run_id: str,
|
||
*,
|
||
visibility: str | None = None,
|
||
limit: int = 200,
|
||
offset: int = 0,
|
||
) -> tuple[list[Event], int]:
|
||
"""按 run_id 查询事件,按 sequence 升序;可选 visibility 过滤。返回 (条目, 总数)。"""
|
||
conditions = [Event.run_id == run_id]
|
||
if visibility is not None:
|
||
conditions.append(Event.visibility == visibility)
|
||
total = int(
|
||
(
|
||
await session.execute(select(func.count()).select_from(Event).where(*conditions))
|
||
).scalar_one()
|
||
)
|
||
result = await session.execute(
|
||
select(Event).where(*conditions).order_by(Event.sequence.asc()).offset(offset).limit(limit)
|
||
)
|
||
return list(result.scalars().all()), total
|
||
|
||
|
||
async def list_public_events_after(
|
||
session: AsyncSession, run_id: str, *, after_sequence: int = 0, limit: int = 100
|
||
) -> list[Event]:
|
||
result = await session.execute(
|
||
select(Event)
|
||
.where(
|
||
Event.run_id == run_id,
|
||
Event.visibility == "public",
|
||
Event.sequence > after_sequence,
|
||
)
|
||
.order_by(Event.sequence.asc())
|
||
.limit(limit)
|
||
)
|
||
return list(result.scalars().all())
|
||
|
||
|
||
async def list_events_by_trace(session: AsyncSession, trace_id: str) -> list[Event]:
|
||
"""链路追踪:按 trace_id 查询运行事件,按 created_at 升序(与审计/幂等归并排序)。"""
|
||
result = await session.execute(
|
||
select(Event).where(Event.trace_id == trace_id).order_by(Event.created_at.asc())
|
||
)
|
||
return list(result.scalars().all())
|
||
|
||
|
||
async def summarize_by_trace(session: AsyncSession):
|
||
"""GROUP BY trace_id:(trace_id, 计数, 最近时间),仅 trace_id 非空。供链路追踪列表聚合。"""
|
||
result = await session.execute(
|
||
select(Event.trace_id, func.count(), func.max(Event.created_at))
|
||
.where(Event.trace_id.is_not(None))
|
||
.group_by(Event.trace_id)
|
||
)
|
||
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]})
|