feat(server): add workbench API, DSH planner worker and RAG pipeline
This commit is contained in:
@@ -0,0 +1,725 @@
|
||||
"""雅文内容创造工作台 - 数据模型(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)
|
||||
Reference in New Issue
Block a user