docs(persistence): 给 ORM 表/字段补充中文注释

通过 SQLAlchemy 的 comment= 给 5 张持久化表(users / threads_meta / runs /
run_events / feedback)的所有字段以及表本身加上中文注释,便于读代码、
生成文档与未来切到 Postgres 时直接落库为 COMMENT ON。

SQLite 引擎本身不支持 COMMENT ON,运行时不会改变 .schema 输出。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1445043649
2026-05-10 18:50:17 +08:00
parent ecc9339ede
commit 9ff790554d
5 changed files with 83 additions and 87 deletions
@@ -13,20 +13,16 @@ from deerflow.persistence.base import Base
class FeedbackRow(Base): class FeedbackRow(Base):
__tablename__ = "feedback" __tablename__ = "feedback"
__table_args__ = (UniqueConstraint("thread_id", "run_id", "user_id", name="uq_feedback_thread_run_user"),) __table_args__ = (
UniqueConstraint("thread_id", "run_id", "user_id", name="uq_feedback_thread_run_user"),
{"comment": "用户对运行结果的反馈(点赞/点踩 + 文字评论),(thread, run, user) 唯一"},
)
feedback_id: Mapped[str] = mapped_column(String(64), primary_key=True) feedback_id: Mapped[str] = mapped_column(String(64), primary_key=True, comment="反馈主键")
run_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) run_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="关联的运行 IDruns.run_id")
thread_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) thread_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="关联的会话 IDthreads_meta.thread_id")
user_id: Mapped[str | None] = mapped_column(String(64), index=True) user_id: Mapped[str | None] = mapped_column(String(64), index=True, comment="反馈作者;为 NULL 表示历史无主数据")
message_id: Mapped[str | None] = mapped_column(String(64)) message_id: Mapped[str | None] = mapped_column(String(64), comment="可选的 RunEventStore 事件 ID;为 NULL 表示针对整次运行而非单条消息")
# message_id is an optional RunEventStore event identifier — rating: Mapped[int] = mapped_column(nullable=False, comment="评分:+1 点赞,-1 点踩")
# allows feedback to target a specific message or the entire run comment: Mapped[str | None] = mapped_column(Text, comment="可选的文字评论")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC), comment="创建时间(UTC")
rating: Mapped[int] = mapped_column(nullable=False)
# +1 (thumbs-up) or -1 (thumbs-down)
comment: Mapped[str | None] = mapped_column(Text)
# Optional text feedback from the user
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC))
@@ -13,23 +13,25 @@ from deerflow.persistence.base import Base
class RunEventRow(Base): class RunEventRow(Base):
__tablename__ = "run_events" __tablename__ = "run_events"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True, comment="自增主键")
thread_id: Mapped[str] = mapped_column(String(64), nullable=False) thread_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="所属会话 IDthreads_meta.thread_id")
run_id: Mapped[str] = mapped_column(String(64), nullable=False) run_id: Mapped[str] = mapped_column(String(64), nullable=False, comment="所属运行 IDruns.run_id")
# Owner of the conversation this event belongs to. Nullable for data user_id: Mapped[str | None] = mapped_column(
# created before auth was introduced; populated by auth middleware on String(64),
# new writes and by the boot-time orphan migration on existing rows. nullable=True,
user_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) index=True,
event_type: Mapped[str] = mapped_column(String(32), nullable=False) comment="会话所有者;为 NULL 表示鉴权引入之前的历史数据,新写入由 auth 中间件填充,启动期 orphan 迁移会回填存量",
category: Mapped[str] = mapped_column(String(16), nullable=False) )
# "message" | "trace" | "lifecycle" event_type: Mapped[str] = mapped_column(String(32), nullable=False, comment="事件子类型(具体含义由 category 决定,如 ai_message_chunk、tool_call、run_started")
content: Mapped[str] = mapped_column(Text, default="") category: Mapped[str] = mapped_column(String(16), nullable=False, comment='事件大类:"message" 消息 / "trace" 追踪 / "lifecycle" 生命周期')
event_metadata: Mapped[dict] = mapped_column(JSON, default=dict) content: Mapped[str] = mapped_column(Text, default="", comment="事件文本内容(消息体、错误、状态字符串等)")
seq: Mapped[int] = mapped_column(nullable=False) event_metadata: Mapped[dict] = mapped_column(JSON, default=dict, comment="事件结构化元数据(JSON),随 event_type 而异")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC)) seq: Mapped[int] = mapped_column(nullable=False, comment="在 thread 内的全局递增序号;与 thread_id 组合唯一")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC), comment="创建时间(UTC")
__table_args__ = ( __table_args__ = (
UniqueConstraint("thread_id", "seq", name="uq_events_thread_seq"), UniqueConstraint("thread_id", "seq", name="uq_events_thread_seq"),
Index("ix_events_thread_cat_seq", "thread_id", "category", "seq"), Index("ix_events_thread_cat_seq", "thread_id", "category", "seq"),
Index("ix_events_run", "thread_id", "run_id", "seq"), Index("ix_events_run", "thread_id", "run_id", "seq"),
{"comment": "运行事件流(消息/追踪/生命周期事件按 seq 顺序追加,是消息回放与审计的真源)"},
) )
@@ -13,37 +13,43 @@ from deerflow.persistence.base import Base
class RunRow(Base): class RunRow(Base):
__tablename__ = "runs" __tablename__ = "runs"
run_id: Mapped[str] = mapped_column(String(64), primary_key=True) run_id: Mapped[str] = mapped_column(String(64), primary_key=True, comment="运行主键")
thread_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) thread_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True, comment="所属会话 IDthreads_meta.thread_id")
assistant_id: Mapped[str | None] = mapped_column(String(128)) assistant_id: Mapped[str | None] = mapped_column(String(128), comment="使用的 Assistant ID(自定义智能体名);为 NULL 表示默认 lead agent")
user_id: Mapped[str | None] = mapped_column(String(64), index=True) user_id: Mapped[str | None] = mapped_column(String(64), index=True, comment="发起本次运行的用户 ID")
status: Mapped[str] = mapped_column(String(20), default="pending") status: Mapped[str] = mapped_column(
# "pending" | "running" | "success" | "error" | "timeout" | "interrupted" String(20),
default="pending",
comment='运行状态:"pending" / "running" / "success" / "error" / "timeout" / "interrupted"',
)
model_name: Mapped[str | None] = mapped_column(String(128), comment="本次运行的主模型名(来自 config.yaml.models[*].name")
multitask_strategy: Mapped[str] = mapped_column(
String(20),
default="reject",
comment='并发策略:同一 thread 已有运行时怎么处理("reject" / "interrupt" / "rollback" / "enqueue"',
)
metadata_json: Mapped[dict] = mapped_column(JSON, default=dict, comment="运行级元数据(JSON),如 channel/source 等")
kwargs_json: Mapped[dict] = mapped_column(JSON, default=dict, comment="提交运行时的额外参数(JSON),如 thinking_enabled、tool 配置等")
error: Mapped[str | None] = mapped_column(Text, comment="运行失败时的错误文本;成功时为 NULL")
model_name: Mapped[str | None] = mapped_column(String(128)) message_count: Mapped[int] = mapped_column(default=0, comment="本次运行产生的消息总数(便利字段,避免列表页查 RunEventStore")
multitask_strategy: Mapped[str] = mapped_column(String(20), default="reject") first_human_message: Mapped[str | None] = mapped_column(Text, comment="首条用户消息文本预览(用于列表展示)")
metadata_json: Mapped[dict] = mapped_column(JSON, default=dict) last_ai_message: Mapped[str | None] = mapped_column(Text, comment="末条 AI 消息文本预览(用于列表展示)")
kwargs_json: Mapped[dict] = mapped_column(JSON, default=dict)
error: Mapped[str | None] = mapped_column(Text)
# Convenience fields (for listing pages without querying RunEventStore) total_input_tokens: Mapped[int] = mapped_column(default=0, comment="累计输入 token 数(运行结束时由 RunJournal 落盘)")
message_count: Mapped[int] = mapped_column(default=0) total_output_tokens: Mapped[int] = mapped_column(default=0, comment="累计输出 token 数")
first_human_message: Mapped[str | None] = mapped_column(Text) total_tokens: Mapped[int] = mapped_column(default=0, comment="累计 token 总数 = input + output")
last_ai_message: Mapped[str | None] = mapped_column(Text) llm_call_count: Mapped[int] = mapped_column(default=0, comment="累计 LLM 调用次数")
lead_agent_tokens: Mapped[int] = mapped_column(default=0, comment="主 agent 自身消耗的 token 数")
subagent_tokens: Mapped[int] = mapped_column(default=0, comment="子 agenttask 工具委派)消耗的 token 数")
middleware_tokens: Mapped[int] = mapped_column(default=0, comment="中间件(如 summarization、title)消耗的 token 数")
# Token usage (accumulated in-memory by RunJournal, written on run completion) follow_up_to_run_id: Mapped[str | None] = mapped_column(String(64), comment="续接的上一次运行 ID(用于'重新生成'/'继续'等链式调用)")
total_input_tokens: Mapped[int] = mapped_column(default=0)
total_output_tokens: Mapped[int] = mapped_column(default=0)
total_tokens: Mapped[int] = mapped_column(default=0)
llm_call_count: Mapped[int] = mapped_column(default=0)
lead_agent_tokens: Mapped[int] = mapped_column(default=0)
subagent_tokens: Mapped[int] = mapped_column(default=0)
middleware_tokens: Mapped[int] = mapped_column(default=0)
# Follow-up association created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC), comment="创建时间(UTC")
follow_up_to_run_id: Mapped[str | None] = mapped_column(String(64)) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC), comment="最近更新时间(UTC,写入时自动更新)")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC)) __table_args__ = (
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)) Index("ix_runs_thread_status", "thread_id", "status"),
{"comment": "运行(一次完整 agent 执行)的元数据 + 累计 token 指标"},
__table_args__ = (Index("ix_runs_thread_status", "thread_id", "status"),) )
@@ -13,11 +13,13 @@ from deerflow.persistence.base import Base
class ThreadMetaRow(Base): class ThreadMetaRow(Base):
__tablename__ = "threads_meta" __tablename__ = "threads_meta"
thread_id: Mapped[str] = mapped_column(String(64), primary_key=True) thread_id: Mapped[str] = mapped_column(String(64), primary_key=True, comment="会话主键(LangGraph thread_id")
assistant_id: Mapped[str | None] = mapped_column(String(128), index=True) assistant_id: Mapped[str | None] = mapped_column(String(128), index=True, comment="关联的 Assistant ID(自定义智能体名);为 NULL 表示默认 lead agent")
user_id: Mapped[str | None] = mapped_column(String(64), index=True) user_id: Mapped[str | None] = mapped_column(String(64), index=True, comment="会话所有者;为 NULL 表示历史无主数据")
display_name: Mapped[str | None] = mapped_column(String(256)) display_name: Mapped[str | None] = mapped_column(String(256), comment="会话显示名(自动生成的标题或用户手改)")
status: Mapped[str] = mapped_column(String(20), default="idle") status: Mapped[str] = mapped_column(String(20), default="idle", comment='会话状态:"idle" 空闲 / "busy" 正在产出')
metadata_json: Mapped[dict] = mapped_column(JSON, default=dict) metadata_json: Mapped[dict] = mapped_column(JSON, default=dict, comment="任意扩展元数据(JSON")
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC)) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC), comment="创建时间(UTC")
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC), comment="最近更新时间(UTC,写入时自动更新)")
__table_args__ = ({"comment": "会话元数据(每个 LangGraph thread 的概要信息)"},)
@@ -22,31 +22,20 @@ from deerflow.persistence.base import Base
class UserRow(Base): class UserRow(Base):
__tablename__ = "users" __tablename__ = "users"
# UUIDs are stored as 36-char strings for cross-backend portability. id: Mapped[str] = mapped_column(String(36), primary_key=True, comment="用户主键,UUID 字符串(36 字符),跨数据库可移植")
id: Mapped[str] = mapped_column(String(36), primary_key=True) email: Mapped[str] = mapped_column(String(320), unique=True, nullable=False, index=True, comment="登录邮箱,全局唯一")
password_hash: Mapped[str | None] = mapped_column(String(128), nullable=True, comment="本地账户的密码哈希;OAuth-only 用户为 NULL")
email: Mapped[str] = mapped_column(String(320), unique=True, nullable=False, index=True) system_role: Mapped[str] = mapped_column(String(16), nullable=False, default="user", comment='系统角色:"admin""user";用字符串以便未来扩展角色而不必 ALTER TABLE')
password_hash: Mapped[str | None] = mapped_column(String(128), nullable=True)
# "admin" | "user" — kept as plain string to avoid ALTER TABLE pain
# when new roles are introduced.
system_role: Mapped[str] = mapped_column(String(16), nullable=False, default="user")
created_at: Mapped[datetime] = mapped_column( created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), DateTime(timezone=True),
nullable=False, nullable=False,
default=lambda: datetime.now(UTC), default=lambda: datetime.now(UTC),
comment="账户创建时间(UTC",
) )
oauth_provider: Mapped[str | None] = mapped_column(String(32), nullable=True, comment="OAuth 提供商名(如 google/github);本地账户为 NULL")
# OAuth linkage (optional). A partial unique index enforces one oauth_id: Mapped[str | None] = mapped_column(String(128), nullable=True, comment="OAuth 提供商内的用户 ID;与 oauth_provider 组合需唯一")
# account per (provider, oauth_id) pair, leaving NULL/NULL rows needs_setup: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, comment="是否需要完成首次设置(admin 自动创建后改密码/邮箱)")
# unconstrained so plain password accounts can coexist. token_version: Mapped[int] = mapped_column(nullable=False, default=0, comment="JWT 令牌版本号;自增即吊销该用户所有旧令牌")
oauth_provider: Mapped[str | None] = mapped_column(String(32), nullable=True)
oauth_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
# Auth lifecycle flags
needs_setup: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
token_version: Mapped[int] = mapped_column(nullable=False, default=0)
__table_args__ = ( __table_args__ = (
Index( Index(
@@ -56,4 +45,5 @@ class UserRow(Base):
unique=True, unique=True,
sqlite_where=text("oauth_provider IS NOT NULL AND oauth_id IS NOT NULL"), sqlite_where=text("oauth_provider IS NOT NULL AND oauth_id IS NOT NULL"),
), ),
{"comment": "用户账户表(本地密码登录 + OAuth 联合登录)"},
) )