Files
yawan-workbench/server/app/models.py
T

726 lines
35 KiB
Python
Raw 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.
"""雅文内容创造工作台 - 数据模型(SQLModel / SQLite)"""
from datetime import datetime
from typing import Optional
from sqlalchemy import UniqueConstraint
from sqlmodel import SQLModel, Field, JSON, Column
def now() -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# ---------- 组织 ----------
class Organization(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
slug: str = Field(index=True, unique=True)
enabled: bool = True
created_at: str = Field(default_factory=now)
# ---------- 内容标签 ----------
class ContentTag(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True) # 标签名:心法/技法…
category: str = "内容" # 大类:结构/内容/技法/产品/表现/辅助
definition: str = "" # 一句话定义
usage: str = "" # 典型用法
is_core: bool = False # ★核心
enabled: bool = True # 停用后不再出现在选项里
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "global" # private|shared|global
# ---------- 模型 Provider ----------
class ModelProvider(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
base_url: str
api_key: str = "" # 本地 SQLite 明文存储(本机单用户)
models: list = Field(default_factory=list, sa_column=Column(JSON))
default_model: str = ""
builtin: bool = False
note: str = ""
enabled: bool = True
# Provider 默认按组织共享;保留 0 代表系统级 Provider。
organization_id: int = Field(default=1, index=True)
class ModelCatalog(SQLModel, table=True):
"""面向用户的模型目录;Provider 凭据和目录能力完全分离。"""
id: Optional[int] = Field(default=None, primary_key=True)
provider_id: int = Field(index=True)
model_name: str = Field(index=True)
display_name: str = ""
capabilities: list = Field(default_factory=list, sa_column=Column(JSON))
capability_status: str = "untested" # untested|supported|partial|blocked
rate_version: str = "v1"
input_rate: float = 0.0
output_rate: float = 0.0
enabled: bool = True
builtin: bool = False
created_at: str = Field(default_factory=now)
updated_at: str = Field(default_factory=now)
# ---------- 系统设置 ----------
class AppSetting(SQLModel, table=True):
key: str = Field(primary_key=True)
value: str = ""
class AuditLog(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
actor_user_id: int = Field(default=0, index=True)
actor_display_name: str = ""
action: str = Field(index=True)
resource: str = Field(index=True)
result: str = "success"
created_at: str = Field(default_factory=now, index=True)
class SystemLog(SQLModel, table=True):
"""应用运行日志,与管理员操作审计严格分离。"""
id: Optional[int] = Field(default=None, primary_key=True)
level: str = Field(default="INFO", index=True)
source: str = Field(default="app", index=True)
message: str = ""
trace_id: str = Field(default="", index=True)
status: str = Field(default="ok", index=True)
created_at: str = Field(default_factory=now, index=True)
# ---------- 知识库 ----------
class KnowledgeBase(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
desc: str = ""
source_dir: str = "" # KB_DIR/<id>_<name>/
index_status: str = "empty" # empty|indexing|ready|needs_reindex|error
file_count: int = 0
chunk_count: int = 0
chunk_target: int = 0 # 分段目标长度:0=自动(500);2200=长文聚合;自定义任意 120~8000
chunk_mode: str = "auto" # auto=按长度聚合 | heading=按标题切分
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "global" # private|shared|global
created_at: str = Field(default_factory=now)
class KnowledgeBaseGrant(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
kb_id: int = Field(index=True)
user_id: int = Field(index=True)
permission: str = "use" # read|use|manage|export
enabled: bool = True
created_at: str = Field(default_factory=now)
class KnowledgeCategory(SQLModel, table=True):
"""知识库内的无限层级分类树。"""
id: Optional[int] = Field(default=None, primary_key=True)
kb_id: int = Field(index=True)
parent_id: Optional[int] = Field(default=None, index=True)
name: str
desc: str = ""
sort_order: int = 0
created_at: str = Field(default_factory=now)
class KnowledgeFile(SQLModel, table=True):
"""源文件与分类的关系;物理文件仍保存在知识库目录内。"""
id: Optional[int] = Field(default=None, primary_key=True)
kb_id: int = Field(index=True)
file_name: str = Field(index=True)
category_id: Optional[int] = Field(default=None, index=True)
ai_category_id: Optional[int] = Field(default=None, index=True)
classification_status: str = "unclassified" # unclassified|ai_suggested|human_confirmed
ai_confidence: float = 0.0
ai_reason: str = ""
content_hash: str = Field(default="", index=True) # 源文件内容指纹,重建索引后保持可追溯
revision: int = 1 # 同名文件内容变化时递增
created_at: str = Field(default_factory=now)
# ---------- 知识片段消费记录 ----------
class KnowledgeUsage(SQLModel, table=True):
"""同一个知识片段仅对消费它的 Agent 锁定,其他 Agent 不受影响。"""
__table_args__ = (
UniqueConstraint("agent_id", "kb_id", "chunk_id", name="uq_knowledge_usage_agent_chunk"),
)
id: Optional[int] = Field(default=None, primary_key=True)
agent_id: int = Field(index=True)
kb_id: int = Field(index=True)
chunk_id: str = Field(index=True)
file_name: str = ""
heading: str = ""
excerpt: str = ""
campaign_id: int = 0
segment_id: int = 0
status: str = "consumed" # reserved|consumed|available
banned: bool = False # 人工审查不通过且选择"不再使用"→ 检索永久排除
used_at: str = Field(default_factory=now)
released_at: str = ""
# ---------- 技能库 ----------
class AgentSkill(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
desc: str = ""
instructions: str = ""
enabled: bool = True
builtin: bool = False
version: int = 1
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "global"
created_at: str = Field(default_factory=now)
class AgentSkillBinding(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
agent_id: int = Field(index=True)
skill_id: int = Field(index=True)
class AgentSkillGrant(SQLModel, table=True):
"""技能的成员授权;技能挂接到 Agent 后仍遵循技能自身的访问边界。"""
id: Optional[int] = Field(default=None, primary_key=True)
skill_id: int = Field(index=True)
user_id: int = Field(index=True)
permission: str = "use" # read|use|manage|export
enabled: bool = True
created_at: str = Field(default_factory=now)
# ---------- 提示模板 ----------
class AgentPromptTemplate(SQLModel, table=True):
"""可复用、可版本化的提示模板;任务创建时会冻结完整快照。"""
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True, unique=True)
desc: str = ""
role_prompt: str = ""
task_template: str = ""
constraints: list = Field(default_factory=list, sa_column=Column(JSON))
examples: list = Field(default_factory=list, sa_column=Column(JSON))
output_schema: dict = Field(default_factory=dict, sa_column=Column(JSON))
style: str = ""
version: int = 1
enabled: bool = True
builtin: bool = False
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "global"
created_at: str = Field(default_factory=now)
updated_at: str = Field(default_factory=now)
# ---------- Agent ----------
class Agent(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
role: str = "production" # production|training_map|qc|service|coordinator|composer
desc: str = ""
content_tags: list = Field(default_factory=list, sa_column=Column(JSON)) # 生产 Agent 擅长标签
kb_ids: list = Field(default_factory=list, sa_column=Column(JSON)) # 知识库挂接
kb_category_ids: dict = Field(default_factory=dict, sa_column=Column(JSON))
fusion_agent_ids: list = Field(default_factory=list, sa_column=Column(JSON)) # 融合写法的生产 Agent
model_provider_id: Optional[int] = None # None=跟随全局默认
model_name: str = ""
model_catalog_id: Optional[int] = Field(default=None, index=True)
prompt_template_id: Optional[int] = Field(default=None, index=True)
# 允许该 Agent 使用的 Runtime 工具名称;空列表按系统内置工具兼容处理。
allowed_tools: list = Field(default_factory=list, sa_column=Column(JSON))
rules: str = "" # rules.md 内容
rules_version: int = 1
persona_json: dict = Field(default_factory=dict, sa_column=Column(JSON)) # Agent 独立人格档案(优先级低于绑定的数字人)
digital_human_id: Optional[int] = Field(default=None, index=True) # 绑定数字人:绑定时以数字人身份生成
runtime_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) # 发布冻结的运行配置(借鉴 Coze 草稿/发布分离)
status: str = "active" # active|disabled
builtin: bool = False
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "global" # private|shared|global
created_at: str = Field(default_factory=now)
class AgentGrant(SQLModel, table=True):
"""Agent 本身的成员授权;与 Agent 内部技能授权独立。"""
id: Optional[int] = Field(default=None, primary_key=True)
agent_id: int = Field(index=True)
user_id: int = Field(index=True)
permission: str = "use" # read|use|manage|export
enabled: bool = True
created_at: str = Field(default_factory=now)
# ---------- 数字人(蒸馏人格档案,可绑定到 Agent) ----------
class DigitalHuman(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str = Field(index=True)
desc: str = ""
persona: dict = Field(default_factory=dict, sa_column=Column(JSON)) # 十一维人格档案
# 蒸馏质量元数据(覆盖范围、证据来源、不确定性、情境规则);不混入 persona 字段,兼容旧档案。
persona_meta: dict = Field(default_factory=dict, sa_column=Column(JSON))
status: str = "distilling" # distilling 蒸馏中 | testing 待人工测试 | active 已生效
version: int = 1 # 每次重新蒸馏 +1
source_files: list = Field(default_factory=list, sa_column=Column(JSON)) # 蒸馏来源文件名
last_feedback: str = "" # 最近一次人工测试意见
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "private" # private=仅本人 | shared=授权成员 | global=全员可用
created_at: str = Field(default_factory=now)
class DigitalHumanGrant(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
dh_id: int = Field(index=True)
user_id: int = Field(index=True)
permission: str = "use" # read|use
enabled: bool = True
created_at: str = Field(default_factory=now)
class WeChatBotAccount(SQLModel, table=True):
"""微信 Bot 账号连接;凭证只由独立适配器持有,数据库不保存明文。"""
id: Optional[int] = Field(default=None, primary_key=True)
owner_user_id: int = Field(default=0, index=True)
organization_id: int = Field(default=1, index=True)
account_name: str = ""
provider: str = "ilink" # ilink 官方通道 | wechaty 开发通道
status: str = "disconnected" # pending_qr|connected|disconnected|error
qr_code: str = "" # 短期二维码 URL/内容,不保存长期凭证
external_account_id: str = "" # 适配器返回的脱敏账号标识
agent_id: Optional[int] = Field(default=None, index=True)
credential_ref: str = "" # 适配器凭证引用(不含密钥)
bot_secret_hash: str = "" # 每个账号独立的 Bot 密钥哈希
bot_secret_hint: str = "" # 仅显示末四位
last_error: str = ""
created_at: str = Field(default_factory=now)
updated_at: str = Field(default_factory=now)
# ---------- 画布(M2 完整实现,M1 存简版) ----------
class Canvas(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
nodes: list = Field(default_factory=list, sa_column=Column(JSON)) # [{node_id,agent_id,enabled}]
edges: list = Field(default_factory=list, sa_column=Column(JSON)) # [{from_node,to_node}]
supervisor_agent_id: Optional[int] = None # 全局总监 Agent:负责各段不跑偏,None=不启用
supervisor_mode: str = "on_drift" # always|on_drift|off —— 每段必审/疑似跑偏才审/关闭
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "global"
created_at: str = Field(default_factory=now)
class CanvasGrant(SQLModel, table=True):
"""画布授权是 Agent、关联知识库和记忆的统一使用边界。"""
id: Optional[int] = Field(default=None, primary_key=True)
canvas_id: int = Field(index=True)
user_id: int = Field(index=True)
permission: str = "use" # read|use|manage|export
enabled: bool = True
created_at: str = Field(default_factory=now)
# ---------- DSH Planner:工作流模板与计划版本 ----------
class WorkflowTemplate(SQLModel, table=True):
"""工作台管理的工作流模板;DSH Planner 只能在模板约束内生成 DAG。"""
id: Optional[int] = Field(default=None, primary_key=True)
key: str = Field(index=True, unique=True)
name: str
description: str = ""
# {mode, required_tags, optional_tags, max_segments, allow_auto_replan, risk_rules, default_kb_categories}
config: dict = Field(default_factory=dict, sa_column=Column(JSON))
enabled: bool = True
builtin: bool = False
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "global" # private|shared|global
created_at: str = Field(default_factory=now)
updated_at: str = Field(default_factory=now)
class CampaignPlanVersion(SQLModel, table=True):
"""DSH Planner 产出的不可变计划版本;批准后才允许 Worker 执行。"""
__table_args__ = (
UniqueConstraint("campaign_id", "version", name="uq_campaign_plan_version"),
)
id: Optional[int] = Field(default=None, primary_key=True)
campaign_id: int = Field(index=True)
version: int = 1
source: str = "dsh" # dsh|manual
template_id: int = Field(default=0, index=True)
template_key: str = "default-live-course"
status: str = Field(default="draft", index=True) # draft|waiting_review|approved|rejected|superseded|failed
plan: dict = Field(default_factory=dict, sa_column=Column(JSON))
validation: dict = Field(default_factory=dict, sa_column=Column(JSON))
summary: str = ""
risks: list = Field(default_factory=list, sa_column=Column(JSON))
advice: str = ""
created_by: int = 0
reviewed_by: int = 0
review_note: str = ""
created_at: str = Field(default_factory=now, index=True)
reviewed_at: str = ""
class ResourceRevision(SQLModel, table=True):
"""用户对全局资源的个人修改提案。
全局资源本体保持不变;普通用户编辑/删除时写入 pending 修订,管理员
审核后才会把内容合并到全局资源。这样同一资源可被多人使用而互不覆盖。
"""
id: Optional[int] = Field(default=None, primary_key=True)
resource_type: str = Field(index=True) # kb|agent|skill|canvas|memory|digital_human
resource_id: int = Field(index=True)
fork_resource_id: int = Field(default=0, index=True)
owner_user_id: int = Field(index=True)
organization_id: int = Field(default=1, index=True)
operation: str = "update" # update|delete
base_version: int = 1
base_fingerprint: str = ""
version: int = 1
payload: dict = Field(default_factory=dict, sa_column=Column(JSON))
status: str = Field(default="pending", index=True) # pending|approved|rejected
reviewer_user_id: int = 0
review_note: str = ""
created_at: str = Field(default_factory=now)
reviewed_at: str = ""
class ResourceFork(SQLModel, table=True):
"""个人副本与全局源资源的关系,用于版本提交和冲突检测。"""
id: Optional[int] = Field(default=None, primary_key=True)
resource_type: str = Field(index=True)
source_id: int = Field(index=True)
copy_id: int = Field(index=True)
owner_user_id: int = Field(index=True)
organization_id: int = Field(default=1, index=True)
base_version: int = 1
base_fingerprint: str = ""
created_at: str = Field(default_factory=now)
# ---------- 生产任务 ----------
class Campaign(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str # 如 2026-08-26-day5
mindmap_text: str = "" # 用户输入的导图/框架原文
objective: str = "" # 本次任务唯一业务目标
success_criteria: str = "" # 人工验收标准
canvas_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) # {nodes:[{node_id,agent_id,enabled}], edges:[{from,to}]}
canvas_name: str = "" # 画布名(快照来源)
duration_minutes: int = 0 # 直播总时长(分钟):0=未指定;拆解按此分配各段时间
qc_mode: str = "full" # 质检模式:full=出稿同步质检 | lazy=先出稿后台质检 | off=不质检
previous_campaign_id: Optional[int] = None # 关联上一天任务(跨稿对账)
task_spec: list = Field(default_factory=list, sa_column=Column(JSON)) # 内容段列表
pending_confirms: list = Field(default_factory=list, sa_column=Column(JSON)) # 待确认清单
notes: list = Field(default_factory=list, sa_column=Column(JSON)) # 拆解说明/压缩决策
approved_plan_version_id: Optional[int] = None # 已人工批准的 DSH Planner 计划版本;空=旧流程兼容
status: str = "draft" # draft|planning|plan_review|plan_failed|parsed|running|qc|human_review|composing|done|failed
has_failure: bool = False # ⚠️ 有待处理失败
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "private"
created_at: str = Field(default_factory=now)
finished_at: str = ""
# ---------- 内容段 ----------
class Segment(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
campaign_id: int = Field(index=True)
node_id: str = "" # 画布节点 id(M1 用 seg-N)
agent_id: int = 0
agent_assignment_mode: str = "auto" # manual 后标签修改不再覆盖用户选择
generation_history: list = Field(default_factory=list, sa_column=Column(JSON))
fusion_agent_ids: list = Field(default_factory=list, sa_column=Column(JSON)) # 创建任务时冻结的融合 Agent 快照
runtime_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON)) # 任务拆解时冻结的规则/技能/模型配置
content_tag: str = "" # 内容标签
title: str = ""
time_range: str = "" # 00:00-00:08
mindmap_node: str = "" # 导图节点原文
product_ref: str = "" # 产品节点(如有)
status: str = "pending" # pending|running|qc|human_review|approved|rework|failed|skipped
round: int = 1 # 第几轮
artifact_path: str = "" # 产物文件(md)
anchor_card: dict = Field(default_factory=dict, sa_column=Column(JSON))
qc_reports: list = Field(default_factory=list, sa_column=Column(JSON))
failure: dict = Field(default_factory=dict, sa_column=Column(JSON)) # 失败详情
references: list = Field(default_factory=list, sa_column=Column(JSON)) # RAG 引用来源(溯源核对)
kb_category_ids: list = Field(default_factory=list, sa_column=Column(JSON)) # 段级知识分类限定:空=用整个知识库
kb_category_recommendation: dict = Field(default_factory=dict, sa_column=Column(JSON)) # AI 推荐的真实分类及理由,需人工采用
updated_at: str = Field(default_factory=now)
# ---------- 质检报告 ----------
class QCReport(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
segment_id: int = Field(index=True)
gate: str = "" # structure|content|style|cross
checks: list = Field(default_factory=list, sa_column=Column(JSON))
verdict: str = "PASS" # PASS|FAIL
run_id: int = Field(default=0, index=True)
trace_id: str = Field(default="", index=True)
runtime_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON))
created_at: str = Field(default_factory=now)
# ---------- 调用日志 ----------
class LLMCall(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
run_id: int = Field(default=0, index=True)
campaign_id: int = 0
segment_id: int = 0
agent_id: int = 0
agent_name: str = ""
provider: str = ""
model: str = ""
status: str = "ok" # ok|error|rejected|timeout
error: str = ""
trace_id: str = Field(default="", index=True)
span_id: str = ""
attempt: int = 1
prompt_hash: str = ""
output_hash: str = ""
prompt_tokens: int = 0
completion_tokens: int = 0
duration_ms: int = 0
user_id: int = Field(default=0, index=True)
organization_id: int = Field(default=1, index=True)
usage_event_id: Optional[int] = Field(default=None, index=True)
billing_status: str = "unbilled" # unbilled|measured|estimated|settled|void
created_at: str = Field(default_factory=now)
# ---------- 不可变用量事件与配额 ----------
class UsageEvent(SQLModel, table=True):
"""每一次模型、Embedding、OCR 或导出行为的追加式计量记录。"""
id: Optional[int] = Field(default=None, primary_key=True)
user_id: int = Field(default=0, index=True)
organization_id: int = Field(default=1, index=True)
campaign_id: int = Field(default=0, index=True)
run_id: int = Field(default=0, index=True)
segment_id: int = Field(default=0, index=True)
trace_id: str = Field(default="", index=True)
llm_call_id: Optional[int] = Field(default=None, index=True)
agent_id: int = Field(default=0, index=True)
provider_id: int = Field(default=0, index=True)
provider: str = ""
model: str = ""
service_type: str = "llm" # llm|embedding|ocr|storage|export|rerank
unit: str = "token" # token|image|byte|gb_month|request
quantity: float = 0.0
input_quantity: int = 0
output_quantity: int = 0
input_rate: float = 0.0
output_rate: float = 0.0
amount_minor: int = 0 # CNY 分,避免浮点金额作为账本依据
currency: str = "CNY"
pricing_version: str = "v1"
status: str = "measured" # measured|estimated|settled|void
idempotency_key: str = Field(index=True, unique=True)
usage_metadata: dict = Field(default_factory=dict, sa_column=Column("metadata", JSON))
created_at: str = Field(default_factory=now, index=True)
class PointAccount(SQLModel, table=True):
"""用户点数余额;10 点数折算 1 Token。"""
id: Optional[int] = Field(default=None, primary_key=True)
user_id: int = Field(index=True)
organization_id: int = Field(default=1, index=True)
balance_points: int = 0
updated_at: str = Field(default_factory=now)
class PointTransaction(SQLModel, table=True):
"""点数不可变流水:管理员充值为 credit,模型消耗为 debit。"""
id: Optional[int] = Field(default=None, primary_key=True)
user_id: int = Field(index=True)
organization_id: int = Field(default=1, index=True)
delta_points: int
kind: str = "credit" # credit|debit|adjustment
reason: str = ""
source: str = "admin"
operator_user_id: int = 0
idempotency_key: str = Field(index=True, unique=True)
transaction_metadata: dict = Field(default_factory=dict, sa_column=Column("metadata", JSON))
created_at: str = Field(default_factory=now, index=True)
class UsageQuota(SQLModel, table=True):
"""用户级配额;0 表示该项不设上限,策略由管理员配置。"""
id: Optional[int] = Field(default=None, primary_key=True)
user_id: int = Field(index=True)
organization_id: int = Field(default=1, index=True)
period: str = "monthly" # daily|monthly
token_limit: int = 0
amount_limit_minor: int = 0
storage_limit_bytes: int = 0
concurrent_limit: int = 0
warning_ratio: float = 0.8
overage_policy: str = "warn" # warn|soft_limit|hard_limit
enabled: bool = True
updated_at: str = Field(default_factory=now)
# ---------- 通知 ----------
class Notification(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
type: str = "failure" # failure|pending_review|memory_pending|tag_pending|info
ref_id: int = 0
message: str = ""
status: str = "unread"
created_at: str = Field(default_factory=now)
class ProgressEvent(SQLModel, table=True):
"""Cross-process progress events shared by API workbench and DSH worker."""
id: Optional[int] = Field(default=None, primary_key=True)
user_id: int = Field(default=0, index=True)
event: str = Field(default="info", index=True)
payload: dict = Field(default_factory=dict, sa_column=Column(JSON))
created_at: str = Field(default_factory=now, index=True)
# ---------- 记忆待确认池 ----------
class PendingMemory(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
target_agent_id: int
type: str = "L3" # L1规则|L2纠错|L3偏好|L4成功
content: str = ""
source_ref: str = "" # 来源:哪次打回/哪场
status: str = "pending" # pending|approved|rejected|blocked
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "private"
created_at: str = Field(default_factory=now)
# ---------- 已确认记忆 ----------
class Memory(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
agent_id: int = Field(index=True)
layer: str = "L3"
title: str = ""
description: str = ""
content: str = ""
source_ref: str = ""
status: str = "active" # active|expired
privacy: str = "internal" # internal|public
tags: list = Field(default_factory=list, sa_column=Column(JSON))
timestamp: str = Field(default_factory=now) # 记忆沉淀日期
index_status: str = "indexed" # indexed|needs_reindex
usage_count: int = 0
last_used_at: str = ""
supersedes_id: int = 0 # 人工确认替代的旧记忆;正文保留为历史版本
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "private"
created_at: str = Field(default_factory=now)
class MemoryGrant(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
memory_id: int = Field(index=True)
user_id: int = Field(index=True)
permission: str = "use" # read|use|manage|export
enabled: bool = True
created_at: str = Field(default_factory=now)
# ---------- 组合产物 ----------
class Artifact(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
campaign_id: int = Field(index=True)
docx_path: str = ""
compose_report: dict = Field(default_factory=dict, sa_column=Column(JSON))
final_status: str = "composed"
run_id: int = Field(default=0, index=True)
trace_id: str = Field(default="", index=True)
runtime_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON))
organization_id: int = Field(default=1, index=True)
owner_user_id: int = Field(default=0, index=True)
visibility: str = "private"
created_at: str = Field(default_factory=now)
# ---------- 本地账号与会话 ----------
class AppUser(SQLModel, table=True):
"""本地工作台账号;密码只保存 PBKDF2 派生摘要。"""
id: Optional[int] = Field(default=None, primary_key=True)
username: str = Field(index=True, unique=True)
display_name: str = ""
password_hash: str = ""
role: str = "viewer" # admin|editor|reviewer|viewer
organization_id: int = Field(default=1, index=True)
enabled: bool = True
created_at: str = Field(default_factory=now)
updated_at: str = Field(default_factory=now)
class RoleDefinition(SQLModel, table=True):
"""管理员可维护的角色权限集合;AppUser.role 保存角色标识。"""
id: Optional[int] = Field(default=None, primary_key=True)
role: str = Field(index=True, unique=True)
label: str = ""
description: str = ""
permissions: list = Field(default_factory=list, sa_column=Column(JSON))
builtin: bool = True
organization_id: int = Field(default=1, index=True)
updated_at: str = Field(default_factory=now)
class AuthSession(SQLModel, table=True):
"""不透明令牌的可撤销会话,数据库只保存令牌哈希。"""
id: Optional[int] = Field(default=None, primary_key=True)
user_id: int = Field(index=True)
token_hash: str = Field(index=True, unique=True)
expires_at: str = ""
revoked_at: str = ""
created_at: str = Field(default_factory=now)
last_seen_at: str = Field(default_factory=now)
# ---------- 可恢复执行记录 ----------
class CampaignRun(SQLModel, table=True):
"""一次任务运行的持久化状态;服务重启后可从检查点恢复。"""
id: Optional[int] = Field(default=None, primary_key=True)
campaign_id: int = Field(index=True)
user_id: int = Field(default=0, index=True)
organization_id: int = Field(default=1, index=True)
status: str = "queued" # queued|running|paused|awaiting_review|completed|failed|cancelled
cancel_requested: bool = False
pause_requested: bool = False
checkpoint: dict = Field(default_factory=dict, sa_column=Column(JSON))
config_snapshot: dict = Field(default_factory=dict, sa_column=Column(JSON))
trace_id: str = Field(default="", index=True)
runtime_version: str = "v1"
error: str = ""
created_at: str = Field(default_factory=now)
started_at: str = ""
finished_at: str = ""
class RunEvent(SQLModel, table=True):
"""运行审计轨迹,供前端展示生成进度与诊断失败。"""
id: Optional[int] = Field(default=None, primary_key=True)
run_id: int = Field(index=True)
campaign_id: int = Field(index=True)
segment_id: int = 0
node_id: str = ""
trace_id: str = Field(default="", index=True)
span_id: str = ""
parent_span_id: str = ""
actor_agent_id: int = 0
actor_name: str = ""
tool_name: str = ""
event_type: str = "info"
status: str = ""
payload: dict = Field(default_factory=dict, sa_column=Column(JSON))
created_at: str = Field(default_factory=now)
# ---------- Agent Runtime 会话 ----------
class AgentSession(SQLModel, table=True):
"""单个 Agent 的可恢复会话;messages 保存 Agent loop 的上下文和工具结果。"""
id: Optional[int] = Field(default=None, primary_key=True)
run_id: int = Field(default=0, index=True)
campaign_id: int = Field(default=0, index=True)
segment_id: int = Field(default=0, index=True)
agent_id: int = Field(default=0, index=True)
agent_name: str = ""
parent_session_id: Optional[int] = Field(default=None, index=True)
phase: str = "production" # coordinator|production|qc|composer|handoff
trace_id: str = Field(default="", index=True)
status: str = "running" # running|waiting_approval|interrupted|completed|failed|cancelled
turn: int = 0
max_turns: int = 6
messages: list = Field(default_factory=list, sa_column=Column(JSON))
tool_state: dict = Field(default_factory=dict, sa_column=Column(JSON))
result_text: str = ""
result_meta: dict = Field(default_factory=dict, sa_column=Column(JSON))
error: str = ""
created_at: str = Field(default_factory=now)
updated_at: str = Field(default_factory=now)
finished_at: str = ""
# ---------- 持久任务队列(P0-3)----------
class TaskQueueItem(SQLModel, table=True):
"""可靠任务队列:租约 + 重试退避 + 死信。task_key 幂等防重复入队。"""
id: Optional[int] = Field(default=None, primary_key=True)
task_key: str = Field(index=True) # 幂等键(如 campaign-run:12)
payload: dict = Field(default_factory=dict, sa_column=Column(JSON))
status: str = Field(default="pending", index=True) # pending|running|done|dead|cancelled
attempt: int = 0
max_attempts: int = 3
worker_id: str = ""
lease_until: str = "" # 租约到期时间(ISO)
next_run_at: str = Field(default_factory=now) # 退避后可领取时间
error: str = ""
created_at: str = Field(default_factory=now)
updated_at: str = Field(default_factory=now)