Files

726 lines
35 KiB
Python
Raw Permalink Normal View History

"""雅文内容创造工作台 - 数据模型(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)