feat: Add base project structure with API and web components

This commit is contained in:
Ke Sun
2025-12-02 20:28:01 +08:00
parent f3de6d6cc9
commit c1adc62ec6
817 changed files with 111226 additions and 106 deletions

View File

@@ -0,0 +1,52 @@
from .tenant_model import Tenants
from .user_model import User
from .workspace_model import Workspace, WorkspaceMember, WorkspaceRole
from .knowledge_model import Knowledge
from .document_model import Document
from .file_model import File
from .generic_file_model import GenericFile
from .models_model import ModelConfig, ModelProvider, ModelType, ModelApiKey
from .knowledgeshare_model import KnowledgeShare
from .app_model import App
from .agent_app_config_model import AgentConfig
from .app_release_model import AppRelease
from .memory_increment_model import MemoryIncrement
from .end_user_model import EndUser
from .appshare_model import AppShare
from .release_share_model import ReleaseShare
from .conversation_model import Conversation, Message
from .api_key_model import ApiKey, ApiKeyLog, ApiKeyType
from .data_config_model import DataConfig
from .multi_agent_model import MultiAgentConfig, AgentInvocation
__all__ = [
"Tenants",
"User",
"Workspace",
"WorkspaceMember",
"WorkspaceRole",
"Knowledge",
"Document",
"File",
"GenericFile",
"ModelConfig",
"ModelProvider",
"ModelType",
"ModelApiKey",
"KnowledgeShare",
"App",
"AgentConfig",
"AppRelease",
"MemoryIncrement",
"EndUser",
"AppShare",
"ReleaseShare",
"Conversation",
"Message",
"ApiKey",
"ApiKeyLog",
"ApiKeyType",
"DataConfig",
"MultiAgentConfig",
"AgentInvocation"
]

View File

@@ -0,0 +1,44 @@
import datetime
import uuid
from sqlalchemy import Column, String, Boolean, DateTime, Text, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSON
from sqlalchemy.orm import relationship
from app.db import Base
class AgentConfig(Base):
__tablename__ = "agent_configs"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 一对一关联到 App
app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id"), nullable=False, unique=True, index=True)
# Agent 行为配置
system_prompt = Column(Text, nullable=True, comment="系统提示词")
default_model_config_id = Column(UUID(as_uuid=True), ForeignKey("model_configs.id"), nullable=True, index=True, comment="默认模型配置ID")
# 结构化配置(直接存储 JSON
model_parameters = Column(JSON, nullable=True, comment="模型参数配置temperature、max_tokens等")
knowledge_retrieval = Column(JSON, nullable=True, comment="知识库检索配置")
memory = Column(JSON, nullable=True, comment="记忆配置")
variables = Column(JSON, default=list, nullable=True, comment="变量配置")
tools = Column(JSON, default=dict, nullable=True, comment="工具配置")
# 多 Agent 相关字段
agent_role = Column(String(20), comment="Agent 角色: master|sub|standalone")
agent_domain = Column(String(50), comment="专业领域: customer_service|technical_support|sales 等")
parent_agent_id = Column(UUID(as_uuid=True), ForeignKey("agent_configs.id"), comment="父 Agent ID")
capabilities = Column(JSON, default=list, comment="Agent 能力列表")
# 状态与时间戳
is_active = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
# 关系
app = relationship("App", back_populates="agent_config")
parent_agent = relationship("AgentConfig", remote_side=[id], backref="sub_agents")
def __repr__(self):
return f"<AgentConfig(id={self.id}, app_id={self.app_id})>"

View File

@@ -0,0 +1,90 @@
"""API Key 数据模型"""
import datetime
import uuid
from enum import StrEnum
from sqlalchemy import Column, String, Boolean, DateTime, Integer, ForeignKey, Text
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship
from app.db import Base
class ApiKeyType(StrEnum):
"""API Key 类型"""
APP = "app" # 应用 API Key
RAG = "rag" # RAG API Key
MEMORY = "memory" # Memory API Key
GENERAL = "general" # 通用 API Key
class ApiKey(Base):
"""API Key 表"""
__tablename__ = "api_keys"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 基本信息
name = Column(String(255), nullable=False, comment="API Key 名称")
description = Column(Text, comment="描述")
key_prefix = Column(String(20), nullable=False, comment="Key 前缀")
key_hash = Column(String(255), nullable=False, unique=True, index=True, comment="Key 哈希值")
# 类型和权限
type = Column(String(50), nullable=False, index=True, comment="API Key 类型")
scopes = Column(JSONB, nullable=False, default=list, comment="权限范围列表")
# 关联资源
workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id", ondelete="CASCADE"), nullable=False, index=True, comment="所属工作空间")
resource_id = Column(UUID(as_uuid=True), index=True, comment="关联资源ID")
resource_type = Column(String(50), comment="资源类型")
# 限制和配额
rate_limit = Column(Integer, default=100, comment="速率限制(请求/分钟)")
quota_limit = Column(Integer, comment="配额限制(总请求数)")
quota_used = Column(Integer, default=0, comment="已使用配额")
# 有效期
expires_at = Column(DateTime, comment="过期时间")
# 状态
is_active = Column(Boolean, default=True, nullable=False, comment="是否激活")
last_used_at = Column(DateTime, comment="最后使用时间")
usage_count = Column(Integer, default=0, comment="使用次数")
# 审计
created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False, comment="创建者")
created_at = Column(DateTime, nullable=False, default=datetime.datetime.now, comment="创建时间")
updated_at = Column(DateTime, nullable=False, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
# 关系
workspace = relationship("Workspace", back_populates="api_keys")
creator = relationship("User", foreign_keys=[created_by])
logs = relationship("ApiKeyLog", back_populates="api_key", cascade="all, delete-orphan")
class ApiKeyLog(Base):
"""API Key 使用日志表"""
__tablename__ = "api_key_logs"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
api_key_id = Column(UUID(as_uuid=True), ForeignKey("api_keys.id", ondelete="CASCADE"), nullable=False, index=True, comment="API Key ID")
# 请求信息
endpoint = Column(String(255), nullable=False, comment="请求端点")
method = Column(String(10), nullable=False, comment="HTTP 方法")
ip_address = Column(String(50), comment="IP 地址")
user_agent = Column(Text, comment="User Agent")
# 响应信息
status_code = Column(Integer, comment="响应状态码")
response_time = Column(Integer, comment="响应时间(毫秒)")
# Token 使用
tokens_used = Column(Integer, comment="使用的 Token 数")
# 时间
created_at = Column(DateTime, nullable=False, default=datetime.datetime.now, index=True, comment="创建时间")
# 关系
api_key = relationship("ApiKey", back_populates="logs")

115
api/app/models/app_model.py Normal file
View File

@@ -0,0 +1,115 @@
import datetime
from enum import StrEnum
from re import LOCALE
import uuid
from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSON
from sqlalchemy.orm import relationship
from app.db import Base
class IconType(StrEnum):
"""图标类型枚举"""
LOCALE = "locale"
REMOTE = "remote"
# 可见性private | workspace | public
class AppVisibility(StrEnum):
"""可见性枚举"""
PRIVATE = "private"
WORKSPACE = "workspace"
PUBLIC = "public"
# 应用类型agent | workflow | multi_agent
class AppType(StrEnum):
"""应用类型枚举"""
AGENT = "agent"
WORKFLOW = "workflow"
MULTI_AGENT = "multi_agent"
# 应用状态draft | active | archived
class AppStatus(StrEnum):
"""应用状态枚举"""
DRAFT = "draft"
ACTIVE = "active"
ARCHIVED = "archived"
class App(Base):
__tablename__ = "apps"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
workspace_id = Column(UUID(as_uuid=True), nullable=False, comment="workspaces.id")
created_by = Column(UUID(as_uuid=True), nullable=False, comment="users.id")
name = Column(String, index=True, nullable=False)
description = Column(String, nullable=True)
icon = Column(String, nullable=True)
icon_type = Column(String, nullable=True)
# 应用类型agent | workflow 等
type = Column(String, index=True, nullable=False)
# 可见性private | workspace | public
visibility = Column(String, default="workspace")
# 状态draft | active | archived
status = Column(String, default="draft")
# 标签或扩展元数据
tags = Column(JSON, default=list)
# 当前已发布版本指针(发布后指向快照,不受编辑影响)
current_release_id = Column(
UUID(as_uuid=True),
ForeignKey("app_releases.id", use_alter=True, name="fk_apps_current_release_id"),
nullable=True,
index=True,
)
is_active = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
# 一对一Agent 配置(仅当 type=agent 时有效)
agent_config = relationship(
"AgentConfig",
back_populates="app",
uselist=False,
cascade="all, delete-orphan",
)
# 一对一:多 Agent 配置(仅当 type=multi_agent 时有效)
multi_agent_config = relationship(
"MultiAgentConfig",
back_populates="app",
uselist=False,
cascade="all, delete-orphan",
)
# 发布版本关联
current_release = relationship("AppRelease", foreign_keys=[current_release_id])
# 指定外键以避免与 current_release_id 造成歧义
releases = relationship(
"AppRelease",
back_populates="app",
cascade="all, delete-orphan",
foreign_keys="AppRelease.app_id",
)
# 会话关联
conversations = relationship(
"Conversation",
back_populates="app",
cascade="all, delete-orphan"
)
# 与 EndUser 的反向关系
end_users = relationship(
"EndUser",
back_populates="app",
cascade="all, delete-orphan",
)
def __repr__(self):
return f"<App(id={self.id}, name={self.name}, type={self.type})>"

View File

@@ -0,0 +1,68 @@
import datetime
import uuid
from sqlalchemy import Column, String, Boolean, DateTime, Integer, ForeignKey, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID, JSON
from sqlalchemy.orm import relationship
from app.db import Base
from app.models.app_model import IconType
class AppRelease(Base):
__tablename__ = "app_releases"
__table_args__ = (
UniqueConstraint("app_id", "version", name="uq_app_release_app_version"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id"), nullable=False, index=True)
# 版本号(按应用内递增)
version = Column(Integer, nullable=False, default=1, index=True)
# 版本号,显示用
version_name = Column(String, nullable=False)
# 版本说明
release_notes = Column(String, nullable=True, comment="版本说明")
# 基础信息快照(发布时冻结)
name = Column(String, nullable=False)
description = Column(String, nullable=True)
icon = Column(String, nullable=True)
icon_type = Column(String, nullable=True)
type = Column(String, nullable=False)
visibility = Column(String, default="private")
# 类型特定配置快照(针对 agent/workflow 等统一存放)
config = Column(JSON, default=dict)
# 便于查询的索引字段(例如 agent 的默认模型)
default_model_config_id = Column(UUID(as_uuid=True), ForeignKey("model_configs.id"), nullable=True, index=True)
# 发布信息
published_by = Column(UUID(as_uuid=True), nullable=False, comment="users.id")
published_at = Column(DateTime, default=datetime.datetime.now)
is_active = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
# 关系: 指定外键,避免与 App.current_release_id 引起歧义
app = relationship("App", back_populates="releases", foreign_keys=[app_id])
# 发布人关系 - 使用 primaryjoin 明确指定关联条件
publisher = relationship(
"User",
primaryjoin="AppRelease.published_by == User.id",
foreign_keys=[published_by],
lazy="joined",
viewonly=True # 只读关系,不会尝试更新
)
@property
def publisher_name(self) -> str:
"""发布人名称"""
if self.publisher:
return self.publisher.username or self.publisher.email or "未知用户"
return "未知用户"
def __repr__(self):
return f"<AppRelease(id={self.id}, app_id={self.app_id}, version={self.version})>"

View File

@@ -0,0 +1,28 @@
import datetime
import uuid
from sqlalchemy import Column, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from app.db import Base
from sqlalchemy.orm import relationship
class AppShare(Base):
"""应用分享模型
记录应用从一个工作空间分享到另一个工作空间的关系
"""
__tablename__ = "app_shares"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
source_app_id = Column(UUID(as_uuid=True), ForeignKey('apps.id', ondelete='CASCADE'), nullable=False, comment="源应用ID")
source_workspace_id = Column(UUID(as_uuid=True), ForeignKey('workspaces.id'), nullable=False, comment="源工作空间ID")
target_workspace_id = Column(UUID(as_uuid=True), ForeignKey('workspaces.id'), nullable=False, comment="目标工作空间ID")
shared_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False, comment="分享者用户ID")
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now)
# Relationships
source_app = relationship("App", foreign_keys=[source_app_id], backref="shares")
source_workspace = relationship("Workspace", foreign_keys=[source_workspace_id])
target_workspace = relationship("Workspace", foreign_keys=[target_workspace_id])
shared_user = relationship("User", backref="app_shares")

View File

@@ -0,0 +1,80 @@
"""
会话和消息模型
"""
import uuid
import datetime
from sqlalchemy import Column, String, DateTime, ForeignKey, Boolean, Integer, Text, JSON
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.db import Base
class Conversation(Base):
"""会话表
会话类型说明:
- 草稿会话 (is_draft=True): 使用应用的当前草稿配置,用于开发和测试
- 发布会话 (is_draft=False): 使用应用的当前发布版本配置,用于生产环境
工作空间隔离:
- 每个会话属于一个工作空间workspace_id
- 同一个应用在不同工作空间有独立的会话记录
- 支持应用分享后的会话隔离
"""
__tablename__ = "conversations"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 关联信息
app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id"), nullable=False, comment="应用ID")
workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id"), nullable=False, comment="工作空间ID")
user_id = Column(String, nullable=True, comment="用户ID外部系统")
# 会话信息
title = Column(String(255), comment="会话标题")
summary = Column(Text, comment="会话摘要")
# 会话类型True=草稿会话使用草稿配置False=发布会话(使用发布配置)
is_draft = Column(Boolean, default=True, nullable=False, comment="是否为草稿会话")
# 配置快照:保存创建会话时的完整配置,用于审计和问题追溯
config_snapshot = Column(JSON, comment="配置快照Agent配置、模型配置等")
# 统计信息
message_count = Column(Integer, default=0, comment="消息数量")
# 状态
is_active = Column(Boolean, default=True, nullable=False, comment="是否活跃")
# 时间戳
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
# 关联关系
app = relationship("App", back_populates="conversations")
workspace = relationship("Workspace")
messages = relationship("Message", back_populates="conversation", cascade="all, delete-orphan")
class Message(Base):
"""消息表"""
__tablename__ = "messages"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 关联信息
conversation_id = Column(UUID(as_uuid=True), ForeignKey("conversations.id"), nullable=False, comment="会话ID")
# 消息内容
role = Column(String(20), nullable=False, comment="角色: user/assistant/system")
content = Column(Text, nullable=False, comment="消息内容")
# 元数据(避免使用 metadata 保留字)
meta_data = Column(JSON, comment="消息元数据如模型、token使用等")
# 时间戳
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
# 关联关系
conversation = relationship("Conversation", back_populates="messages")

View File

@@ -0,0 +1,71 @@
import datetime
import uuid
from sqlalchemy import Column, String, Boolean, DateTime, Integer, Float
from sqlalchemy.dialects.postgresql import UUID
from app.db import Base
class DataConfig(Base):
"""数据配置表 - 用于存储记忆系统的配置参数"""
__tablename__ = "data_config"
# 主键
config_id = Column(Integer, primary_key=True, autoincrement=True, comment="配置ID")
# 基本信息
config_name = Column(String, nullable=False, comment="配置名称")
config_desc = Column(String, nullable=True, comment="配置描述")
# 组织信息
workspace_id = Column(UUID(as_uuid=True), nullable=True, comment="工作空间ID")
group_id = Column(String, nullable=True, comment="组ID")
user_id = Column(String, nullable=True, comment="用户ID")
apply_id = Column(String, nullable=True, comment="应用ID")
# 模型选择从workspace继承
llm_id = Column(String, nullable=True, comment="LLM模型配置ID")
embedding_id = Column(String, nullable=True, comment="嵌入模型配置ID")
rerank_id = Column(String, nullable=True, comment="重排序模型配置ID")
llm = Column(String, nullable=True, comment="LLM模型配置ID")
# 记忆萃取引擎配置
enable_llm_dedup_blockwise = Column(Boolean, default=True, comment="启用LLM决策去重")
enable_llm_disambiguation = Column(Boolean, default=True, comment="启用LLM决策消歧")
deep_retrieval = Column(Boolean, default=True, comment="深度检索开关")
# 阈值配置 (0-1 之间的浮点数)
t_type_strict = Column(Float, default=0.8, comment="类型严格阈值")
t_name_strict = Column(Float, default=0.8, comment="名称严格阈值")
t_overall = Column(Float, default=0.8, comment="综合阈值")
# 状态配置
state = Column(Boolean, default=False, comment="配置使用状态")
# 分块策略
chunker_strategy = Column(String, default="RecursiveChunker", comment="分块策略")
# 剪枝配置
pruning_enabled = Column(Boolean, default=False, comment="是否启动智能语义剪枝")
pruning_scene = Column(String, nullable=True, comment="智能剪枝场景education/online_service/outbound")
pruning_threshold = Column(Float, nullable=True, comment="智能语义剪枝阈值0-0.9")
# 自我反思配置
enable_self_reflexion = Column(Boolean, default=False, comment="是否启用自我反思")
iteration_period = Column(String, default="3", comment="反思迭代周期")
reflexion_range = Column(String, default="retrieval", comment="反思范围:部分/全部")
baseline = Column(String, default="time", comment="基线:时间/事实/时间和事实")
# 遗忘引擎配置
statement_granularity = Column(Integer, default=2, comment="陈述提取颗粒度,挡位 1/2/3")
include_dialogue_context = Column(Boolean, default=False, comment="是否包含对话上下文")
max_context = Column(Integer, default=1000, comment="对话语境中包含字符的最大数量")
lambda_time = Column("lambda_time", Float, default=0.5, comment="最低保持度0-1 小数")
lambda_mem = Column("lambda_mem", Float, default=0.5, comment="遗忘率0-1 小数")
offset = Column("offset", Float, default=0.0, comment="偏移度0-1 小数")
# 时间戳
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
def __repr__(self):
return f"<DataConfig(config_id={self.config_id}, config_name={self.config_name})>"

View File

@@ -0,0 +1,28 @@
import datetime
import uuid
from sqlalchemy import Column, Integer, String, JSON, DateTime, ForeignKey, Float
from sqlalchemy.dialects.postgresql import UUID
from app.db import Base
class Document(Base):
__tablename__ = "documents"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
kb_id = Column(UUID(as_uuid=True), nullable=False, comment="knowledges.id")
created_by = Column(UUID(as_uuid=True), nullable=False, comment="users.id")
file_id = Column(UUID(as_uuid=True), nullable=False, comment="files.id")
file_name = Column(String, index=True, nullable=False, comment="file name")
file_ext = Column(String, index=True, nullable=False, comment="file extension")
file_size = Column(Integer, default=0, comment="file size(byte)")
file_meta = Column(JSON, nullable=False, default={})
parser_id = Column(String, index=True, nullable=False, comment="default parser ID")
parser_config = Column(JSON, nullable=False, default={"layout_recognize": "DeepDOC", "chunk_token_num": 128, "delimiter": "\n"}, comment="default parser config")
chunk_num = Column(Integer, default=0, comment="chunk num")
progress = Column(Float, default=0)
progress_msg = Column(String, default="", comment="process message")
process_begin_at = Column(DateTime, default=datetime.datetime.now)
process_duration = Column(Float, default=0)
run = Column(Integer, default=0, comment="start to run processing or cancel.(1: run it; 2: cancel)")
status = Column(Integer, default=1, comment="is it validate(0: wasted, 1: validate)")
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now)

View File

@@ -0,0 +1,24 @@
import datetime
import uuid
from sqlalchemy import Column, String, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.db import Base
class EndUser(Base):
__tablename__ = "end_users"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, nullable=False, index=True)
app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id"), nullable=False)
# end_user_id = Column(String, nullable=False, index=True)
other_id = Column(String, nullable=True) # Store original user_id
other_name = Column(String, default="", nullable=False)
other_address = Column(String, default="", nullable=False)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
# 与 App 的反向关系
app = relationship(
"App",
back_populates="end_users"
)

View File

@@ -0,0 +1,17 @@
import datetime
import uuid
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from app.db import Base
class File(Base):
__tablename__ = "files"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
kb_id = Column(UUID(as_uuid=True), nullable=False, comment="knowledges.id")
created_by = Column(UUID(as_uuid=True), nullable=False, comment="users.id")
parent_id = Column(UUID(as_uuid=True), nullable=True, default=None, comment="parent folder id")
file_name = Column(String, index=True, nullable=False, comment="file name or folder name,default folder name is /")
file_ext = Column(String, index=True, nullable=False, comment="file extension:folder|pdf")
file_size = Column(Integer, default=0, comment="file size(byte)")
created_at = Column(DateTime, default=datetime.datetime.now)

View File

@@ -0,0 +1,52 @@
import datetime
import uuid
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Index, JSON
from sqlalchemy.dialects.postgresql import UUID
from app.db import Base
class GenericFile(Base):
"""
通用文件模型,支持多种上传上下文(头像、应用图标、知识库文件、临时文件等)
"""
__tablename__ = "generic_files"
# 主键和租户信息
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True, comment="文件唯一标识")
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True, comment="租户ID")
created_by = Column(UUID(as_uuid=True), nullable=False, index=True, comment="创建者用户ID")
# 文件基本信息
file_name = Column(String, nullable=False, comment="原始文件名")
file_ext = Column(String, nullable=False, index=True, comment="文件扩展名")
file_size = Column(Integer, nullable=False, comment="文件大小(字节)")
mime_type = Column(String, nullable=True, comment="MIME类型")
# 上传上下文
context = Column(String, nullable=False, index=True, comment="上传上下文avatar/app_icon/knowledge_base/temp/attachment")
# 存储信息
storage_path = Column(String, nullable=False, comment="文件存储路径")
# 元数据JSON格式存储业务相关信息
file_metadata = Column(JSON, nullable=True, default={}, comment="业务元数据")
# 状态和访问控制
status = Column(String, default="active", index=True, comment="文件状态active/processing/deleted")
is_public = Column(Boolean, default=False, comment="是否公开访问")
access_url = Column(String, nullable=True, comment="访问URL")
# 引用计数(用于判断文件是否可以删除)
reference_count = Column(Integer, default=0, comment="引用计数")
# 时间戳
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
deleted_at = Column(DateTime, nullable=True, comment="删除时间(软删除)")
# 复合索引
__table_args__ = (
Index('idx_tenant_context', 'tenant_id', 'context'),
Index('idx_tenant_status', 'tenant_id', 'status'),
Index('idx_created_at', 'created_at'),
)

View File

@@ -0,0 +1,69 @@
import datetime
import uuid
import enum
from sqlalchemy import Column, Integer, String, JSON, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from app.db import Base
from sqlalchemy.orm import relationship
class KnowledgeType(enum.StrEnum):
General = "General"
Web = "Web"
ThirdParty = "Third-party"
FOLDER = "Folder"
class ParserType(enum.StrEnum):
NAIVE = "naive"
QA = "qa"
MANUAL = "manual"
TABLE = "table"
PRESENTATION = "presentation"
LAWS = "laws"
PAPER = "paper"
RESUME = "resume"
BOOK = "book"
ONE = "one"
AUDIO = "audio"
EMAIL = "email"
TAG = "tag"
KG = "knowledge_graph"
class PermissionType(enum.StrEnum):
Private = "Private"
Share = "Share"
class Knowledge(Base):
__tablename__ = "knowledges"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
workspace_id = Column(UUID(as_uuid=True), nullable=False, comment="workspaces.id")
created_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False, comment="users.id")
parent_id = Column(UUID(as_uuid=True), nullable=True, default=None, comment="parent folder id when type is Folder")
name = Column(String, index=True, nullable=False, comment="KB name")
description = Column(String, comment="KB description")
avatar = Column(String, comment="avatar url")
type = Column(String, default="General", comment="Type:General|Web|Third-party|Folder")
permission_id = Column(String, default="Private", comment="permission ID:Private|Share")
embedding_id = Column(UUID(as_uuid=True), ForeignKey('model_configs.id', ondelete="SET NULL"), nullable=True, comment="default embedding model ID")
reranker_id = Column(UUID(as_uuid=True), ForeignKey('model_configs.id', ondelete="SET NULL"), nullable=True, comment="default reranker model ID")
llm_id = Column(UUID(as_uuid=True), ForeignKey('model_configs.id', ondelete="SET NULL"), nullable=True, comment="default llm model ID")
image2text_id = Column(UUID(as_uuid=True), ForeignKey('model_configs.id', ondelete="SET NULL"), nullable=True, comment="default image2text model ID")
doc_num = Column(Integer, default=0, comment="doc num")
chunk_num = Column(Integer, default=0, comment="chunk num")
parser_id = Column(String, index=True, default="naive", comment="default parser ID")
parser_config = Column(JSON, nullable=False,
default={"layout_recognize": "DeepDOC", "chunk_token_num": 128, "delimiter": "\n"},
comment="default parser config")
status = Column(Integer, index=True, default=1, comment="is it validate(0: disable, 1: enable, 2:Soft-delete)")
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now)
# Relationships
created_user = relationship("User", backref="created_user")
embedding = relationship("ModelConfig", foreign_keys=[embedding_id], uselist=False, backref="embedding")
reranker = relationship("ModelConfig", foreign_keys=[reranker_id], uselist=False, backref="reranker")
llm = relationship("ModelConfig", foreign_keys=[llm_id], uselist=False, backref="llm")
image2text = relationship("ModelConfig", foreign_keys=[image2text_id], uselist=False, backref="image2text")

View File

@@ -0,0 +1,24 @@
import datetime
import uuid
from sqlalchemy import Column, Integer, String, JSON, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from app.db import Base
from sqlalchemy.orm import relationship
class KnowledgeShare(Base):
__tablename__ = "knowledge_shares"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
source_kb_id = Column(UUID(as_uuid=True), nullable=False, comment="source knowledges.id")
source_workspace_id = Column(UUID(as_uuid=True), nullable=False, comment="source workspaces.id")
target_kb_id = Column(UUID(as_uuid=True), ForeignKey('knowledges.id'), nullable=False, comment="target knowledges.id")
target_workspace_id = Column(UUID(as_uuid=True), ForeignKey('workspaces.id'), nullable=False, comment="target workspaces.id")
shared_by = Column(UUID(as_uuid=True), ForeignKey('users.id'), nullable=False, comment="shared users.id")
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now)
# Relationships
target_kb = relationship("Knowledge", backref="target_kb")
target_workspace = relationship("Workspace", backref="target_workspace")
shared_user = relationship("User", backref="shared_user")

View File

@@ -0,0 +1,18 @@
import uuid
import datetime
from sqlalchemy import Column, ForeignKey, Integer, Date, DateTime
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.db import Base
class MemoryIncrement(Base):
__tablename__ = "memory_increments"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id"), index=True, nullable=False)
total_num = Column(Integer, default=0, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
# 与 App 的关系(指向映射类名,而非表名)
workspace = relationship("Workspace", back_populates="memory_increments")

View File

@@ -0,0 +1,104 @@
import datetime
import uuid
from enum import StrEnum
from typing import Optional, List
from sqlalchemy import Column, String, Boolean, DateTime, Text, ForeignKey, Enum as SQLEnum
from sqlalchemy.dialects.postgresql import UUID, JSON
from sqlalchemy.orm import relationship
from app.db import Base
class ModelType(StrEnum):
"""模型类型枚举"""
LLM = "llm"
CHAT = "chat"
EMBEDDING = "embedding"
RERANK = "rerank"
class ModelProvider(StrEnum):
"""模型提供商枚举"""
OPENAI = "openai"
# ANTHROPIC = "anthropic"
# GOOGLE = "google"
# BAIDU = "baidu"
DASHSCOPE = "dashscope"
# ZHIPU = "zhipu"
# MOONSHOT = "moonshot"
# DEEPSEEK = "deepseek"
OLLAMA = "ollama"
XINFERENCE = "xinference"
GPUSTACK = "gpustack"
BEDROCK = "bedrock"
class ModelConfig(Base):
"""模型配置表"""
__tablename__ = "model_configs"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
name = Column(String, nullable=False, comment="模型显示名称")
type = Column(String, nullable=False, index=True, comment="模型类型")
description = Column(String, comment="模型描述")
# 模型配置参数
config = Column(JSON, comment="模型配置参数")
# - temperature : 控制生成文本的随机性。值越高,输出越随机、越有创造性;值越低,输出越确定、越保守。
# - top_p : 一种替代 temperature 的采样方法,控制模型从概率最高的词中选择的范围。
# - presence_penalty : 对新出现的主题进行惩罚,鼓励模型谈论已经提到过的话题。
# - frequency_penalty : 对高频词进行惩罚,降低重复相同词语的可能性。
# - stop 或 stop_sequences : 一个或多个字符串序列,当模型生成这些序列时会停止输出。
# - 特定于提供商的参数 : 比如某些模型可能支持的 stream (流式输出) 开关、 seed (随机种子) 等。
# # 模型能力参数
# max_tokens = Column(String, comment="最大token数")
# context_length = Column(String, comment="上下文长度")
# 状态管理
is_active = Column(Boolean, default=True, nullable=False, comment="是否激活")
is_public = Column(Boolean, default=False, nullable=False, comment="是否公开")
# 时间戳
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
# 关联关系
api_keys = relationship("ModelApiKey", back_populates="model_config", cascade="all, delete-orphan")
def __repr__(self):
return f"<ModelConfig(id={self.id}, name={self.name}, type={self.type})>"
class ModelApiKey(Base):
"""模型API密钥表"""
__tablename__ = "model_api_keys"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
model_config_id = Column(UUID(as_uuid=True), ForeignKey("model_configs.id"), nullable=False, comment="模型配置ID")
# API Key 信息
model_name = Column(String, nullable=False, comment="模型实际名称")
provider = Column(String, nullable=False, comment="API Key提供商")
api_key = Column(String, nullable=False, comment="API密钥")
api_base = Column(String, comment="API基础URL")
# 配置参数
config = Column(JSON, comment="API Key特定配置")
# 使用统计
usage_count = Column(String, default="0", comment="使用次数")
last_used_at = Column(DateTime, comment="最后使用时间")
# 状态管理
is_active = Column(Boolean, default=True, nullable=False, comment="是否激活")
priority = Column(String, default="1", comment="优先级")
# 时间戳
created_at = Column(DateTime, default=datetime.datetime.now, comment="创建时间")
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now, comment="更新时间")
# 关联关系
model_config = relationship("ModelConfig", back_populates="api_keys")
def __repr__(self):
return f"<ModelApiKey(id={self.id}, model_name={self.model_name}, provider={self.provider}, model_config_id={self.model_config_id})>"

View File

@@ -0,0 +1,143 @@
"""多 Agent 相关数据模型"""
import datetime
import uuid
from sqlalchemy import Column, String, Boolean, DateTime, Integer, Float, Text, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSON
from sqlalchemy.orm import relationship
from app.db import Base
class MultiAgentConfig(Base):
"""多 Agent 配置表"""
__tablename__ = "multi_agent_configs"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 关联应用
app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id"), nullable=False, unique=True, index=True, comment="关联应用")
# 主 Agent (存储发布版本 ID)
master_agent_id = Column(UUID(as_uuid=True), ForeignKey("app_releases.id"), nullable=False, comment="主 Agent 发布版本 ID")
master_agent_name = Column(String(100), comment="主 Agent 名称")
# 协作模式
orchestration_mode = Column(
String(20),
nullable=False,
default="conditional",
comment="协作模式: sequential|parallel|conditional|loop"
)
# 子 Agent 列表
sub_agents = Column(
JSON,
nullable=False,
default=list,
comment="子 Agent 列表: [{'agent_id': 'uuid', 'name': '...', 'role': '...', 'priority': 1}]"
)
# 路由规则
routing_rules = Column(
JSON,
comment="路由规则: [{'condition': '...', 'target_agent_id': 'uuid', 'priority': 1}]"
)
# 执行配置
execution_config = Column(
JSON,
nullable=False,
default=dict,
comment="执行配置: {'max_iterations': 5, 'timeout': 60, 'parallel_limit': 3}"
)
# 结果整合策略
aggregation_strategy = Column(
String(20),
nullable=False,
default="merge",
comment="结果整合策略: merge|vote|priority|custom"
)
# 状态
is_active = Column(Boolean, default=True, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
# 关系
app = relationship("App")
master_agent_release = relationship("AppRelease", foreign_keys=[master_agent_id])
def __repr__(self):
return f"<MultiAgentConfig(id={self.id}, app_id={self.app_id}, mode={self.orchestration_mode})>"
class AgentInvocation(Base):
"""Agent 调用记录表"""
__tablename__ = "agent_invocations"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
# 调用关系
caller_agent_id = Column(
UUID(as_uuid=True),
ForeignKey("agent_configs.id"),
nullable=False,
index=True,
comment="调用者 Agent ID"
)
callee_agent_id = Column(
UUID(as_uuid=True),
ForeignKey("agent_configs.id"),
nullable=False,
index=True,
comment="被调用者 Agent ID"
)
# 关联信息
conversation_id = Column(
UUID(as_uuid=True),
index=True,
comment="关联会话 ID不使用外键约束避免循环依赖"
)
parent_invocation_id = Column(
UUID(as_uuid=True),
ForeignKey("agent_invocations.id"),
index=True,
comment="父调用 ID用于追踪调用链"
)
# 输入输出
input_message = Column(Text, nullable=False, comment="输入消息")
output_message = Column(Text, comment="输出消息")
context = Column(JSON, comment="上下文信息")
# 状态
status = Column(
String(20),
nullable=False,
default="pending",
index=True,
comment="状态: pending|running|completed|failed"
)
error_message = Column(Text, comment="错误信息")
# 性能指标
started_at = Column(DateTime, nullable=False, default=datetime.datetime.now, index=True)
completed_at = Column(DateTime)
elapsed_time = Column(Float, comment="耗时(秒)")
token_usage = Column(JSON, comment="Token 使用情况")
# 元数据
meta_data = Column(JSON, comment="额外元数据")
created_at = Column(DateTime, default=datetime.datetime.now)
# 关系
caller = relationship("AgentConfig", foreign_keys=[caller_agent_id])
callee = relationship("AgentConfig", foreign_keys=[callee_agent_id])
# conversation 不使用 relationship避免外键约束问题
parent_invocation = relationship("AgentInvocation", remote_side=[id], backref="child_invocations")
def __repr__(self):
return f"<AgentInvocation(id={self.id}, caller={self.caller_agent_id}, callee={self.callee_agent_id}, status={self.status})>"

View File

@@ -0,0 +1,47 @@
import datetime
import uuid
from sqlalchemy import Column, String, Boolean, DateTime, Integer, ForeignKey, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID, JSON
from sqlalchemy.orm import relationship
from app.db import Base
class ReleaseShare(Base):
"""应用发布版本分享配置"""
__tablename__ = "release_shares"
__table_args__ = (
UniqueConstraint("release_id", name="uq_release_share_release_id"),
)
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
release_id = Column(UUID(as_uuid=True), ForeignKey("app_releases.id", ondelete="CASCADE"), nullable=False, unique=True, index=True)
app_id = Column(UUID(as_uuid=True), ForeignKey("apps.id", ondelete="CASCADE"), nullable=False, index=True)
# 分享配置
is_enabled = Column(Boolean, default=True, nullable=False, comment="是否启用公开分享")
share_token = Column(String, nullable=False, unique=True, index=True, comment="公开访问的唯一标识")
# 访问控制
require_password = Column(Boolean, default=False, nullable=False, comment="是否需要密码访问")
password_hash = Column(String, nullable=True, comment="访问密码哈希")
# 嵌入配置
allow_embed = Column(Boolean, default=False, nullable=False, comment="是否允许嵌入")
embed_domains = Column(JSON, default=list, comment="允许嵌入的域名白名单")
# 统计数据
view_count = Column(Integer, default=0, nullable=False, comment="访问次数")
last_accessed_at = Column(DateTime, nullable=True, comment="最后访问时间")
# 元数据
created_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False, comment="创建者")
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
# 关系
release = relationship("AppRelease", backref="share")
app = relationship("App")
creator = relationship("User")
def __repr__(self):
return f"<ReleaseShare(id={self.id}, release_id={self.release_id}, share_token={self.share_token})>"

View File

@@ -0,0 +1,13 @@
import datetime
import uuid
from sqlalchemy import Column, DateTime, Text
from sqlalchemy.dialects.postgresql import UUID
from app.db import Base
class RetrievalInfo(Base):
__tablename__ = "retrieval_info"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, nullable=False, index=True)
host_id = Column(UUID(as_uuid=True), nullable=False)
retrieve_info = Column(Text, default="", nullable=True)
created_at = Column(DateTime, default=datetime.datetime.now)

View File

@@ -0,0 +1,23 @@
import datetime
import uuid
from sqlalchemy import Column, String, DateTime, Boolean
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.db import Base
class Tenants(Base):
__tablename__ = "tenants"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
name = Column(String, index=True, nullable=False)
description = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
is_active = Column(Boolean, default=True)
# Relationship to users - one tenant has many users
users = relationship("User", back_populates="tenant")
# Relationship to workspaces owned by the tenant
owned_workspaces = relationship("Workspace", back_populates="tenant")

View File

@@ -0,0 +1,30 @@
import datetime
import uuid
from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.db import Base
class User(Base):
__tablename__ = "users"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
username = Column(String, unique=True, index=True, nullable=False)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=False)
is_active = Column(Boolean, default=True, nullable=False)
is_superuser = Column(Boolean, default=False, nullable=False)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
last_login_at = Column(DateTime, nullable=True) # 最后登录时间,可为空
current_workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id"), nullable=True) # 当前工作空间ID可为空
# Foreign key to tenant - each user belongs to exactly one tenant
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=False)
# Relationship to workspace memberships - users collaborate in workspaces through membership
workspaces = relationship("WorkspaceMember", back_populates="user")
# Relationship to tenant - one-to-one relationship
tenant = relationship("Tenants", back_populates="users")

View File

@@ -0,0 +1,70 @@
import datetime
from enum import StrEnum
import uuid
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.db import Base
class WorkspaceRole(StrEnum):
manager = "manager"
member = "member"
class InviteStatus(StrEnum):
pending = "pending"
accepted = "accepted"
revoked = "revoked"
expired = "expired"
class Workspace(Base):
__tablename__ = "workspaces"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
name = Column(String, index=True, nullable=False)
icon = Column(String, nullable=True)
iconType = Column(String, nullable=True)
description = Column(String, nullable=True)
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id"), nullable=False) # belongs to tenant
storage_type = Column(String, nullable=True)
llm = Column(String, nullable=True)
embedding = Column(String, nullable=True)
rerank = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
is_active = Column(Boolean, default=True)
# Relationships
tenant = relationship("Tenants", back_populates="owned_workspaces") # belongs to tenant
members = relationship("WorkspaceMember", back_populates="workspace") # users collaborate through membership
api_keys = relationship("ApiKey", back_populates="workspace", cascade="all, delete-orphan") # API Keys
memory_increments = relationship("MemoryIncrement", back_populates="workspace")
class WorkspaceMember(Base):
__tablename__ = "workspace_members"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id"), nullable=False)
role = Column(String, nullable=False)
is_active = Column(Boolean, default=True)
user = relationship("User", back_populates="workspaces")
workspace = relationship("Workspace", back_populates="members")
class WorkspaceInvite(Base):
__tablename__ = "workspace_invites"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
workspace_id = Column(UUID(as_uuid=True), ForeignKey("workspaces.id"), nullable=False)
email = Column(String, nullable=False, index=True)
role = Column(String, nullable=False) # WorkspaceRole: manager or member
token_hash = Column(String, nullable=False, unique=True, index=True)
status = Column(String, nullable=False, default=InviteStatus.pending) # InviteStatus
expires_at = Column(DateTime, nullable=False)
accepted_at = Column(DateTime, nullable=True)
created_by_user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now, onupdate=datetime.datetime.now)
# Relationships
workspace = relationship("Workspace")
created_by = relationship("User", foreign_keys=[created_by_user_id])