Files

79 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""问数据的一轮在服务端跑(docs/design/orchestration-round/00-总方案.md §1):这一轮的后台任务和它的事件日志。
由 app/db/models.py 末尾整模块导入,create_all 建表(都是新表,不改已有表)。这里不导入 app.db.models:models.py 导入本模块时
自己还没加载完,外键一律按表名写。
**为什么一轮一行任务、id 就是 conversation_turns 的 id。** 一轮问数据(建图 → 运行 → 复核 → 按复核重来、落库)以前是浏览器在推,
页面一断这一轮就没人管了。现在它是服务端的一个后台任务:发起、重试本轮、重新运行本轮、继续运行都还是「这一轮」,用户看到的
是同一轮里的几次尝试,所以任务按轮记、一轮一行,重试时把这一行重新排队(上一次的结局归档进 conversation_turns.meta.attempts)。
- ChatTurnJob:状态用后台任务的状态词(core/job_runtime:queued → running → succeeded / failed / cancelled / interrupted),
stage 是这一轮现在在哪一步(build 建图、run 运行、waiting 停在审批上等人、review 复核),run_id 是当前这次运行。request 是
这次要做什么(问题、限定的数据源、方式、重试的原因、放宽到几步……),principal 是发起的人(身份快照:服务重启后接着做时
按这个人的身份——遮罩、只读、对话归属都看它,见 app/chat/job.py)。attempt_seq 是这一次尝试在事件日志里从哪一条开始:
刷新、重开页面时从这里回放,不必把之前几次尝试的过程整条重放。
- ChatTurnEvent:这一轮的事件日志。每一步(助手的操作、心跳、查看、final、用量、运行 id、复核结论、重来、终局)按 seq
一条,页面先回放、再接实时,断了按 seq 补齐(GET /api/chat/turns/{id}/stream?after=)。不进运行的封存范围:运行自己的
事件仍在 run_events,这里只记「这一轮」的事。
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
from sqlalchemy import Float, ForeignKey, Integer, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from app.db.base import Base, current_owner, TimestampMixin, UTCDateTime, new_id
#: 这一轮现在在哪一步
STAGES = ("build", "run", "waiting", "review")
class ChatTurnJob(Base, TimestampMixin):
"""问数据一轮的后台任务(见模块说明)。"""
__tablename__ = "chat_turn_jobs"
#: 就是 conversation_turns.id
id: Mapped[str] = mapped_column(ForeignKey("conversation_turns.id", ondelete="CASCADE"), primary_key=True)
conversation_id: Mapped[str] = mapped_column(ForeignKey("conversations.id", ondelete="CASCADE"), index=True)
#: queued | running | succeeded | failed | cancelled | interrupted(core/job_runtime 的状态词)
status: Mapped[str] = mapped_column(String(20), default="queued", index=True)
#: 认领这一行的工作进程(app/cluster;缺省是建它的进程)。多进程时启动整理、接手只碰属主不在了的行
owner: Mapped[str | None] = mapped_column(String(120), default=current_owner)
stage: Mapped[str | None] = mapped_column(String(20), default=None)
#: ask | retry | rerun | max_steps | run | continue | resume(app/chat/job.py 的 MODES)
mode: Mapped[str] = mapped_column(String(20), default="ask")
run_id: Mapped[str | None] = mapped_column(String(32), default=None)
#: 这一次尝试里自动重来了几次(从 0 数,同 chat/turn.MAX_ATTEMPTS 的口径)
attempt: Mapped[int] = mapped_column(Integer, default=0)
request: Mapped[dict[str, Any]] = mapped_column(default=dict)
principal: Mapped[dict[str, Any]] = mapped_column(default=dict)
actor: Mapped[str | None] = mapped_column(String(100), default=None)
last_seq: Mapped[int] = mapped_column(Integer, default=0)
attempt_seq: Mapped[int] = mapped_column(Integer, default=0)
error: Mapped[str | None] = mapped_column(Text, default=None)
started_at: Mapped[datetime | None] = mapped_column(UTCDateTime, default=None)
finished_at: Mapped[datetime | None] = mapped_column(UTCDateTime, default=None)
#: 等不及的那次复核还在后台跑(从什么时候起):实时流在它回来之前不关。多进程时别的进程上的流也按它判断(属主不在了
#: 就不再等)
late_review_at: Mapped[datetime | None] = mapped_column(UTCDateTime, default=None)
class ChatTurnEvent(Base):
"""问数据一轮的事件日志里的一条(见模块说明)。"""
__tablename__ = "chat_turn_events"
__table_args__ = (UniqueConstraint("turn_id", "seq", name="uq_chat_turn_events_turn_seq"),)
id: Mapped[str] = mapped_column(String(32), primary_key=True, default=new_id)
turn_id: Mapped[str] = mapped_column(ForeignKey("conversation_turns.id", ondelete="CASCADE"), index=True)
seq: Mapped[int] = mapped_column(Integer)
type: Mapped[str] = mapped_column(String(40))
ts: Mapped[float] = mapped_column(Float)
data: Mapped[dict[str, Any]] = mapped_column(default=dict)
__all__ = ["STAGES", "ChatTurnEvent", "ChatTurnJob"]