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