[Modify] reset migration script

This commit is contained in:
Mark
2025-12-15 13:54:31 +08:00
parent 2b2b8902ac
commit d2a630addb
28 changed files with 4974 additions and 1836 deletions

View File

@@ -0,0 +1,224 @@
"""202512051405
Revision ID: 20a742ef1d93
Revises: fc9664b8e4a1
Create Date: 2025-12-05 14:24:33.545865
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '20a742ef1d93'
down_revision: Union[str, None] = 'fc9664b8e4a1'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def table_exists(table_name: str) -> bool:
"""检查表是否存在"""
bind = op.get_bind()
inspector = inspect(bind)
return table_name in inspector.get_table_names()
def column_exists(table_name: str, column_name: str) -> bool:
"""检查列是否存在"""
bind = op.get_bind()
inspector = inspect(bind)
if not table_exists(table_name):
return False
columns = [col['name'] for col in inspector.get_columns(table_name)]
return column_name in columns
def index_exists(table_name: str, index_name: str) -> bool:
"""检查索引是否存在"""
bind = op.get_bind()
inspector = inspect(bind)
if not table_exists(table_name):
return False
indexes = [idx['name'] for idx in inspector.get_indexes(table_name)]
return index_name in indexes
def constraint_exists(table_name: str, constraint_name: str) -> bool:
"""检查约束是否存在(外键、唯一约束等)"""
bind = op.get_bind()
inspector = inspect(bind)
if not table_exists(table_name):
return False
# 检查外键约束
foreign_keys = [fk['name'] for fk in inspector.get_foreign_keys(table_name) if fk['name']]
if constraint_name in foreign_keys:
return True
# 检查唯一约束
unique_constraints = [uc['name'] for uc in inspector.get_unique_constraints(table_name) if uc['name']]
if constraint_name in unique_constraints:
return True
# 检查检查约束
check_constraints = [cc['name'] for cc in inspector.get_check_constraints(table_name) if cc['name']]
if constraint_name in check_constraints:
return True
return False
def trigger_exists(trigger_name: str) -> bool:
"""检查触发器是否存在PostgreSQL"""
bind = op.get_bind()
result = bind.execute(sa.text(
"SELECT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = :trigger_name)"
), {"trigger_name": trigger_name})
return result.scalar()
def sequence_exists(sequence_name: str) -> bool:
"""检查序列是否存在PostgreSQL"""
bind = op.get_bind()
result = bind.execute(sa.text(
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relkind = 'S' AND relname = :sequence_name)"
), {"sequence_name": sequence_name})
return result.scalar()
def enum_exists(enum_name: str) -> bool:
"""检查枚举类型是否存在PostgreSQL"""
bind = op.get_bind()
result = bind.execute(sa.text(
"SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"
), {"enum_name": enum_name})
return result.scalar()
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('data_config', schema=None) as batch_op:
batch_op.alter_column('llm',
existing_type=sa.VARCHAR(length=255),
comment='LLM模型配置ID',
existing_comment='LS',
existing_nullable=True)
with op.batch_alter_table('end_users', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.UUID(),
server_default=None,
existing_nullable=False)
with op.batch_alter_table('knowledges', schema=None) as batch_op:
batch_op.alter_column('permission_id',
existing_type=sa.VARCHAR(),
comment='permission ID:Private|Share|Memory',
existing_comment='permission ID:Private|Share',
existing_nullable=True)
with op.batch_alter_table('memory_increments', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.UUID(),
server_default=None,
existing_nullable=False)
batch_op.alter_column('created_at',
existing_type=postgresql.TIMESTAMP(precision=0),
server_default=None,
existing_nullable=True)
batch_op.alter_column('updated_at',
existing_type=postgresql.TIMESTAMP(precision=0),
server_default=None,
existing_nullable=True)
# 为 model_configs 添加 tenant_id 列和相关约束
if table_exists('model_configs'):
# 添加列(分步骤处理 NOT NULL 约束)
if not column_exists('model_configs', 'tenant_id'):
# 步骤1: 先添加可空列
with op.batch_alter_table('model_configs', schema=None) as batch_op:
batch_op.add_column(sa.Column('tenant_id', sa.UUID(), nullable=True, comment='租户ID'))
# 步骤2: 为现有数据填充默认值
# 注意这里需要根据实际业务逻辑填充这里假设获取第一个租户ID
bind = op.get_bind()
result = bind.execute(sa.text("SELECT id FROM tenants LIMIT 1"))
first_tenant = result.scalar()
if first_tenant:
# 更新现有记录
bind.execute(
sa.text("UPDATE model_configs SET tenant_id = :tenant_id WHERE tenant_id IS NULL"),
{"tenant_id": first_tenant}
)
# 步骤3: 将列改为 NOT NULL
with op.batch_alter_table('model_configs', schema=None) as batch_op:
batch_op.alter_column('tenant_id', nullable=False)
# 创建索引
if not index_exists('model_configs', 'ix_model_configs_tenant_id'):
op.create_index('ix_model_configs_tenant_id', 'model_configs', ['tenant_id'], unique=False)
# 创建外键
if not constraint_exists('model_configs', 'model_configs_tenant_id_fkey'):
op.create_foreign_key('model_configs_tenant_id_fkey', 'model_configs', 'tenants', ['tenant_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 回滚 model_configs 的修改
if table_exists('model_configs'):
# 删除外键
if constraint_exists('model_configs', 'model_configs_tenant_id_fkey'):
op.drop_constraint('model_configs_tenant_id_fkey', 'model_configs', type_='foreignkey')
# 删除索引
if index_exists('model_configs', 'ix_model_configs_tenant_id'):
op.drop_index('ix_model_configs_tenant_id', table_name='model_configs')
# 删除列
if column_exists('model_configs', 'tenant_id'):
with op.batch_alter_table('model_configs', schema=None) as batch_op:
batch_op.drop_column('tenant_id')
with op.batch_alter_table('memory_increments', schema=None) as batch_op:
batch_op.alter_column('updated_at',
existing_type=postgresql.TIMESTAMP(precision=0),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_nullable=True)
batch_op.alter_column('created_at',
existing_type=postgresql.TIMESTAMP(precision=0),
server_default=sa.text('CURRENT_TIMESTAMP'),
existing_nullable=True)
batch_op.alter_column('id',
existing_type=sa.UUID(),
server_default=sa.text('gen_random_uuid()'),
existing_nullable=False)
with op.batch_alter_table('knowledges', schema=None) as batch_op:
batch_op.alter_column('permission_id',
existing_type=sa.VARCHAR(),
comment='permission ID:Private|Share',
existing_comment='permission ID:Private|Share|Memory',
existing_nullable=True)
with op.batch_alter_table('end_users', schema=None) as batch_op:
batch_op.alter_column('id',
existing_type=sa.UUID(),
server_default=sa.text('gen_random_uuid()'),
existing_nullable=False)
with op.batch_alter_table('data_config', schema=None) as batch_op:
batch_op.alter_column('llm',
existing_type=sa.VARCHAR(length=255),
comment='LS',
existing_comment='LLM模型配置ID',
existing_nullable=True)
# ### end Alembic commands ###

View File

@@ -1,83 +0,0 @@
"""20251118191055
Revision ID: 29c030316adf
Revises: 2df9cdfc5087
Create Date: 2025-11-18 19:10:56.550902
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '29c030316adf'
down_revision: Union[str, None] = '2df9cdfc5087'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('app_shares',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('source_app_id', sa.UUID(), nullable=False, comment='源应用ID'),
sa.Column('source_workspace_id', sa.UUID(), nullable=False, comment='源工作空间ID'),
sa.Column('target_workspace_id', sa.UUID(), nullable=False, comment='目标工作空间ID'),
sa.Column('shared_by', sa.UUID(), nullable=False, comment='分享者用户ID'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['shared_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['source_app_id'], ['apps.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['source_workspace_id'], ['workspaces.id'], ),
sa.ForeignKeyConstraint(['target_workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_app_shares_id'), 'app_shares', ['id'], unique=False)
op.execute('DROP TABLE IF EXISTS data_config')
op.add_column('conversations', sa.Column('workspace_id', sa.UUID(), nullable=False, comment='工作空间ID'))
op.create_foreign_key(None, 'conversations', 'workspaces', ['workspace_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'conversations', type_='foreignkey')
op.drop_column('conversations', 'workspace_id')
op.create_table('data_config',
sa.Column('config_id', sa.BIGINT(), sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=9223372036854775807, cycle=False, cache=1), autoincrement=True, nullable=False),
sa.Column('config_name', sa.VARCHAR(length=255), server_default=sa.text("'配置文件'::character varying"), autoincrement=False, nullable=False),
sa.Column('group_id', sa.VARCHAR(length=255), server_default=sa.text("'group_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('user_id', sa.VARCHAR(length=255), server_default=sa.text("'user_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('apply_id', sa.VARCHAR(length=255), server_default=sa.text("'apply_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('enable_llm_dedup_blockwise', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
sa.Column('enable_llm_disambiguation', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
sa.Column('deep_retrieval', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
sa.Column('t_type_strict', sa.NUMERIC(), server_default=sa.text('0.5'), autoincrement=False, nullable=False),
sa.Column('t_name_strict', sa.NUMERIC(), server_default=sa.text('0.8'), autoincrement=False, nullable=False),
sa.Column('t_overall', sa.NUMERIC(), server_default=sa.text('0.56'), autoincrement=False, nullable=False),
sa.Column('chunker_strategy', postgresql.ENUM('RecursiveChunker', 'TokenChunker', 'SemanticChunker', 'NeuralChunker', 'HybridChunker', 'LLMChunker', 'SentenceChunker', 'LateChunker', name='chunker_strategy_enum'), server_default=sa.text("'RecursiveChunker'::chunker_strategy_enum"), autoincrement=False, nullable=False, comment='选择分块策略: \r\n "RecursiveChunker",\r\n "TokenChunker",\r\n "SemanticChunker",\r\n "NeuralChunker",\r\n "HybridChunker",\r\n "LLMChunker",\r\n "SentenceChunker",\r\n "LateChunker"'),
sa.Column('statement_granularity', sa.VARCHAR(length=255), server_default=sa.text("'2'::character varying"), autoincrement=False, nullable=True),
sa.Column('include_dialogue_context', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True),
sa.Column('max_context', sa.VARCHAR(length=255), server_default=sa.text("'200'::character varying"), autoincrement=False, nullable=True),
sa.Column('λ_time', sa.NUMERIC(), server_default=sa.text('0.3'), autoincrement=False, nullable=True),
sa.Column('λ_mem', sa.NUMERIC(), server_default=sa.text('0.4'), autoincrement=False, nullable=True),
sa.Column('offset', sa.NUMERIC(precision=255, scale=0), server_default=sa.text('0.56'), autoincrement=False, nullable=True),
sa.Column('state', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True, precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True, precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('config_desc', sa.TEXT(), server_default=sa.text("'配置文件描述'::text"), autoincrement=False, nullable=False),
sa.Column('workspace_id', sa.VARCHAR(length=255), server_default=sa.text("'workspace_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('enable_self_reflexion', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('iteration_period', sa.VARCHAR(length=50), server_default=sa.text("'三小时'::character varying"), autoincrement=False, nullable=True),
sa.Column('baseline', sa.VARCHAR(length=50), server_default=sa.text("'时间'::character varying"), autoincrement=False, nullable=True),
sa.Column('reflexion_range', sa.VARCHAR(length=25), server_default=sa.text("'部分'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_enabled', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('pruning_scene', sa.VARCHAR(length=255), server_default=sa.text("'education'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_threshold', sa.REAL(), server_default=sa.text('0.3'), autoincrement=False, nullable=True),
sa.Column('id', sa.UUID(), autoincrement=False, nullable=True)
)
op.drop_index(op.f('ix_app_shares_id'), table_name='app_shares')
op.drop_table('app_shares')
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""init_db_table
Revision ID: 2a82ee37b707
Revises: d399fe451657
Create Date: 2025-11-14 11:14:20.773760
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '2a82ee37b707'
down_revision: Union[str, None] = 'd399fe451657'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,92 +0,0 @@
"""20251118112827
Revision ID: 2df9cdfc5087
Revises: 9971d42f0e8c
Create Date: 2025-11-18 11:28:27.981687
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '2df9cdfc5087'
down_revision: Union[str, None] = '9971d42f0e8c'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('conversations',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False, comment='应用ID'),
sa.Column('user_id', sa.String(), nullable=True, comment='用户ID外部系统'),
sa.Column('title', sa.String(length=255), nullable=True, comment='会话标题'),
sa.Column('summary', sa.Text(), nullable=True, comment='会话摘要'),
sa.Column('is_draft', sa.Boolean(), nullable=False, comment='是否为草稿会话'),
sa.Column('config_snapshot', sa.JSON(), nullable=True, comment='配置快照Agent配置、模型配置等'),
sa.Column('message_count', sa.Integer(), nullable=True, comment='消息数量'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否活跃'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_conversations_id'), 'conversations', ['id'], unique=False)
op.create_table('messages',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('conversation_id', sa.UUID(), nullable=False, comment='会话ID'),
sa.Column('role', sa.String(length=20), nullable=False, comment='角色: user/assistant/system'),
sa.Column('content', sa.Text(), nullable=False, comment='消息内容'),
sa.Column('meta_data', sa.JSON(), nullable=True, comment='消息元数据如模型、token使用等'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_messages_id'), 'messages', ['id'], unique=False)
op.execute('DROP TABLE IF EXISTS data_config')
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('data_config',
sa.Column('config_id', sa.BIGINT(), sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=9223372036854775807, cycle=False, cache=1), autoincrement=True, nullable=False),
sa.Column('config_name', sa.VARCHAR(length=255), server_default=sa.text("'配置文件'::character varying"), autoincrement=False, nullable=False),
sa.Column('group_id', sa.VARCHAR(length=255), server_default=sa.text("'group_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('user_id', sa.VARCHAR(length=255), server_default=sa.text("'user_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('apply_id', sa.VARCHAR(length=255), server_default=sa.text("'apply_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('enable_llm_dedup_blockwise', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
sa.Column('enable_llm_disambiguation', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
sa.Column('deep_retrieval', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
sa.Column('t_type_strict', sa.NUMERIC(), server_default=sa.text('0.5'), autoincrement=False, nullable=False),
sa.Column('t_name_strict', sa.NUMERIC(), server_default=sa.text('0.8'), autoincrement=False, nullable=False),
sa.Column('t_overall', sa.NUMERIC(), server_default=sa.text('0.56'), autoincrement=False, nullable=False),
sa.Column('chunker_strategy', postgresql.ENUM('RecursiveChunker', 'TokenChunker', 'SemanticChunker', 'NeuralChunker', 'HybridChunker', 'LLMChunker', 'SentenceChunker', 'LateChunker', name='chunker_strategy_enum'), server_default=sa.text("'RecursiveChunker'::chunker_strategy_enum"), autoincrement=False, nullable=False, comment='选择分块策略: \r\n "RecursiveChunker",\r\n "TokenChunker",\r\n "SemanticChunker",\r\n "NeuralChunker",\r\n "HybridChunker",\r\n "LLMChunker",\r\n "SentenceChunker",\r\n "LateChunker"'),
sa.Column('statement_granularity', sa.VARCHAR(length=255), server_default=sa.text("'2'::character varying"), autoincrement=False, nullable=True),
sa.Column('include_dialogue_context', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True),
sa.Column('max_context', sa.VARCHAR(length=255), server_default=sa.text("'200'::character varying"), autoincrement=False, nullable=True),
sa.Column('λ_time', sa.NUMERIC(), server_default=sa.text('0.3'), autoincrement=False, nullable=True),
sa.Column('λ_mem', sa.NUMERIC(), server_default=sa.text('0.4'), autoincrement=False, nullable=True),
sa.Column('offset', sa.NUMERIC(precision=255, scale=0), server_default=sa.text('0.56'), autoincrement=False, nullable=True),
sa.Column('state', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True, precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True, precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('config_desc', sa.TEXT(), server_default=sa.text("'配置文件描述'::text"), autoincrement=False, nullable=False),
sa.Column('workspace_id', sa.VARCHAR(length=255), server_default=sa.text("'workspace_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('enable_self_reflexion', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('iteration_period', sa.VARCHAR(length=50), server_default=sa.text("'三小时'::character varying"), autoincrement=False, nullable=True),
sa.Column('baseline', sa.VARCHAR(length=50), server_default=sa.text("'时间'::character varying"), autoincrement=False, nullable=True),
sa.Column('reflexion_range', sa.VARCHAR(length=25), server_default=sa.text("'部分'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_enabled', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('pruning_scene', sa.VARCHAR(length=255), server_default=sa.text("'education'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_threshold', sa.REAL(), server_default=sa.text('0.3'), autoincrement=False, nullable=True)
)
op.drop_index(op.f('ix_messages_id'), table_name='messages')
op.drop_table('messages')
op.drop_index(op.f('ix_conversations_id'), table_name='conversations')
op.drop_table('conversations')
# ### end Alembic commands ###

View File

@@ -1,33 +0,0 @@
"""add_version_name_to_app_releases
Revision ID: 2e8146c98e3d
Revises: 6e254c5f498e
Create Date: 2025-11-25 21:05:09.304036
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '2e8146c98e3d'
down_revision: Union[str, None] = '6e254c5f498e'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Step 1: Add version_name column as nullable
op.add_column('app_releases', sa.Column('version_name', sa.String(), nullable=True))
# Step 2: Fill existing rows with default value based on version number
op.execute("UPDATE app_releases SET version_name = 'v' || version::text WHERE version_name IS NULL")
# Step 3: Make the column NOT NULL
op.alter_column('app_releases', 'version_name', nullable=False)
def downgrade() -> None:
op.drop_column('app_releases', 'version_name')

View File

@@ -1,50 +0,0 @@
"""202511292027
Revision ID: 57c11f3c7aee
Revises: d1e56ecbf058
Create Date: 2025-11-29 20:28:03.325587
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '57c11f3c7aee'
down_revision: Union[str, None] = 'd1e56ecbf058'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 检查表是否存在再添加列
op.execute("""
DO $$
BEGIN
IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'data_config')
AND NOT EXISTS (SELECT FROM information_schema.columns
WHERE table_name = 'data_config' AND column_name = 'llm') THEN
ALTER TABLE data_config ADD COLUMN llm VARCHAR;
COMMENT ON COLUMN data_config.llm IS 'LLM模型配置ID';
END IF;
END $$;
""")
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 检查列是否存在再删除
op.execute("""
DO $$
BEGIN
IF EXISTS (SELECT FROM information_schema.columns
WHERE table_name = 'data_config' AND column_name = 'llm') THEN
ALTER TABLE data_config DROP COLUMN llm;
END IF;
END $$;
""")
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""init_db_table
Revision ID: 5de5ec651b01
Revises: b012ab0089b9
Create Date: 2025-11-13 10:11:50.992672
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '5de5ec651b01'
down_revision: Union[str, None] = 'b012ab0089b9'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_foreign_key('fk_apps_current_release_id', 'apps', 'app_releases', ['current_release_id'], ['id'], use_alter=True)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('fk_apps_current_release_id', 'apps', type_='foreignkey')
# ### end Alembic commands ###

View File

@@ -1,55 +0,0 @@
"""20251125181327
Revision ID: 6e254c5f498e
Revises: a09d0e19cde6
Create Date: 2025-11-25 18:13:36.873990
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '6e254c5f498e'
down_revision: Union[str, None] = 'a09d0e19cde6'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 检查表和列是否存在再进行操作
op.execute("""
DO $$
BEGIN
IF EXISTS (SELECT FROM information_schema.columns
WHERE table_name = 'data_config' AND column_name = 'llm_id') THEN
COMMENT ON COLUMN data_config.llm_id IS 'LLM模型配置ID';
END IF;
IF EXISTS (SELECT FROM information_schema.columns
WHERE table_name = 'data_config' AND column_name = 'embedding_id') THEN
COMMENT ON COLUMN data_config.embedding_id IS '嵌入模型配置ID';
END IF;
END $$;
""")
op.add_column('workspaces', sa.Column('storage_type', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('workspaces', 'storage_type')
op.alter_column('data_config', 'embedding_id',
existing_type=sa.VARCHAR(),
comment=None,
existing_comment='嵌入模型配置ID',
existing_nullable=True)
op.alter_column('data_config', 'llm_id',
existing_type=sa.VARCHAR(),
comment=None,
existing_comment='LLM模型配置ID',
existing_nullable=True)
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""20251124102652
Revision ID: 74bed22061db
Revises: 7df4df14dda4
Create Date: 2025-11-24 10:26:53.420966
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '74bed22061db'
down_revision: Union[str, None] = '7df4df14dda4'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""20251121190557
Revision ID: 7df4df14dda4
Revises: d00648d486ca
Create Date: 2025-11-21 19:05:58.333570
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '7df4df14dda4'
down_revision: Union[str, None] = 'd00648d486ca'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,26 +0,0 @@
"""merge heads
Revision ID: 8642a073ce2a
Revises: change_end_user_id_to_string, fbab88219447
Create Date: 2025-11-27 12:25:15.839201
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '8642a073ce2a'
down_revision: Union[str, None] = ('change_end_user_id_to_string', 'fbab88219447')
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
pass
def downgrade() -> None:
pass

View File

@@ -0,0 +1,132 @@
"""202512121400
Revision ID: 94a98e279951
Revises: 20a742ef1d93
Create Date: 2025-12-12 14:09:25.256171
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision: str = '94a98e279951'
down_revision: Union[str, None] = '20a742ef1d93'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def table_exists(table_name: str) -> bool:
"""检查表是否存在"""
bind = op.get_bind()
inspector = inspect(bind)
return table_name in inspector.get_table_names()
def column_exists(table_name: str, column_name: str) -> bool:
"""检查列是否存在"""
bind = op.get_bind()
inspector = inspect(bind)
if not table_exists(table_name):
return False
columns = [col['name'] for col in inspector.get_columns(table_name)]
return column_name in columns
def index_exists(table_name: str, index_name: str) -> bool:
"""检查索引是否存在"""
bind = op.get_bind()
inspector = inspect(bind)
if not table_exists(table_name):
return False
indexes = [idx['name'] for idx in inspector.get_indexes(table_name)]
return index_name in indexes
def constraint_exists(table_name: str, constraint_name: str) -> bool:
"""检查约束是否存在(外键、唯一约束等)"""
bind = op.get_bind()
inspector = inspect(bind)
if not table_exists(table_name):
return False
# 检查外键约束
foreign_keys = [fk['name'] for fk in inspector.get_foreign_keys(table_name) if fk['name']]
if constraint_name in foreign_keys:
return True
# 检查唯一约束
unique_constraints = [uc['name'] for uc in inspector.get_unique_constraints(table_name) if uc['name']]
if constraint_name in unique_constraints:
return True
# 检查检查约束
check_constraints = [cc['name'] for cc in inspector.get_check_constraints(table_name) if cc['name']]
if constraint_name in check_constraints:
return True
return False
def trigger_exists(trigger_name: str) -> bool:
"""检查触发器是否存在PostgreSQL"""
bind = op.get_bind()
result = bind.execute(sa.text(
"SELECT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = :trigger_name)"
), {"trigger_name": trigger_name})
return result.scalar()
def sequence_exists(sequence_name: str) -> bool:
"""检查序列是否存在PostgreSQL"""
bind = op.get_bind()
result = bind.execute(sa.text(
"SELECT EXISTS (SELECT 1 FROM pg_class WHERE relkind = 'S' AND relname = :sequence_name)"
), {"sequence_name": sequence_name})
return result.scalar()
def enum_exists(enum_name: str) -> bool:
"""检查枚举类型是否存在PostgreSQL"""
bind = op.get_bind()
result = bind.execute(sa.text(
"SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = :enum_name)"
), {"enum_name": enum_name})
return result.scalar()
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('api_key_logs', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_api_key_logs_endpoint'), ['endpoint'], unique=False)
batch_op.create_index(batch_op.f('ix_api_key_logs_status_code'), ['status_code'], unique=False)
with op.batch_alter_table('api_keys', schema=None) as batch_op:
batch_op.add_column(sa.Column('daily_request_limit', sa.Integer(), nullable=True, comment='日请求限制'))
batch_op.alter_column('rate_limit',
existing_type=sa.INTEGER(),
comment='QPS限制请求/秒)',
existing_comment='速率限制(请求/分钟)',
existing_nullable=True)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('api_keys', schema=None) as batch_op:
batch_op.alter_column('rate_limit',
existing_type=sa.INTEGER(),
comment='速率限制(请求/分钟)',
existing_comment='QPS限制请求/秒)',
existing_nullable=True)
batch_op.drop_column('daily_request_limit')
with op.batch_alter_table('api_key_logs', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_api_key_logs_status_code'))
batch_op.drop_index(batch_op.f('ix_api_key_logs_endpoint'))
# ### end Alembic commands ###

View File

@@ -1,101 +0,0 @@
"""20251117211522
Revision ID: 9971d42f0e8c
Revises: afe962cdb0b1
Create Date: 2025-11-17 21:15:22.986813
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '9971d42f0e8c'
down_revision: Union[str, None] = 'afe962cdb0b1'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 使用 IF EXISTS 避免表不存在时报错
op.execute('DROP TABLE IF EXISTS data_config_copy1')
op.execute('DROP TABLE IF EXISTS data_config')
op.add_column('agent_configs', sa.Column('model_parameters', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='模型参数配置temperature、max_tokens等'))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('agent_configs', 'model_parameters')
op.create_table('data_config',
sa.Column('config_id', sa.BIGINT(), sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=9223372036854775807, cycle=False, cache=1), autoincrement=True, nullable=False),
sa.Column('config_name', sa.VARCHAR(length=255), server_default=sa.text("'配置文件'::character varying"), autoincrement=False, nullable=False),
sa.Column('group_id', sa.VARCHAR(length=255), server_default=sa.text("'group_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('user_id', sa.VARCHAR(length=255), server_default=sa.text("'user_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('apply_id', sa.VARCHAR(length=255), server_default=sa.text("'apply_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('enable_llm_dedup_blockwise', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
sa.Column('enable_llm_disambiguation', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
sa.Column('deep_retrieval', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
sa.Column('t_type_strict', sa.NUMERIC(), server_default=sa.text('0.5'), autoincrement=False, nullable=False),
sa.Column('t_name_strict', sa.NUMERIC(), server_default=sa.text('0.8'), autoincrement=False, nullable=False),
sa.Column('t_overall', sa.NUMERIC(), server_default=sa.text('0.56'), autoincrement=False, nullable=False),
sa.Column('chunker_strategy', postgresql.ENUM('RecursiveChunker', 'TokenChunker', 'SemanticChunker', 'NeuralChunker', 'HybridChunker', 'LLMChunker', 'SentenceChunker', 'LateChunker', name='chunker_strategy_enum'), server_default=sa.text("'RecursiveChunker'::chunker_strategy_enum"), autoincrement=False, nullable=False, comment='选择分块策略: \r\n "RecursiveChunker",\r\n "TokenChunker",\r\n "SemanticChunker",\r\n "NeuralChunker",\r\n "HybridChunker",\r\n "LLMChunker",\r\n "SentenceChunker",\r\n "LateChunker"'),
sa.Column('statement_granularity', sa.VARCHAR(length=255), server_default=sa.text("'2'::character varying"), autoincrement=False, nullable=True),
sa.Column('include_dialogue_context', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True),
sa.Column('max_context', sa.VARCHAR(length=255), server_default=sa.text("'200'::character varying"), autoincrement=False, nullable=True),
sa.Column('λ_time', sa.NUMERIC(), server_default=sa.text('0.3'), autoincrement=False, nullable=True),
sa.Column('λ_mem', sa.NUMERIC(), server_default=sa.text('0.4'), autoincrement=False, nullable=True),
sa.Column('offset', sa.NUMERIC(precision=255, scale=0), server_default=sa.text('0.56'), autoincrement=False, nullable=True),
sa.Column('state', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True, precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True, precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('config_desc', sa.TEXT(), server_default=sa.text("'配置文件描述'::text"), autoincrement=False, nullable=False),
sa.Column('workspace_id', sa.VARCHAR(length=255), server_default=sa.text("'workspace_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('enable_self_reflexion', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('iteration_period', sa.VARCHAR(length=50), server_default=sa.text("'三小时'::character varying"), autoincrement=False, nullable=True),
sa.Column('baseline', sa.VARCHAR(length=50), server_default=sa.text("'时间'::character varying"), autoincrement=False, nullable=True),
sa.Column('reflexion_range', sa.VARCHAR(length=25), server_default=sa.text("'部分'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_enabled', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('pruning_scene', sa.VARCHAR(length=255), server_default=sa.text("'education'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_threshold', sa.REAL(), server_default=sa.text('0.3'), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('config_id', 'config_name', name=op.f('data_config_pkey')),
sa.UniqueConstraint('config_name', name=op.f('uk_config_name'), postgresql_include=[], postgresql_nulls_not_distinct=False)
)
op.create_table('data_config_copy1',
sa.Column('config_id', sa.BIGINT(), sa.Identity(always=True, start=1, increment=1, minvalue=1, maxvalue=9223372036854775807, cycle=False, cache=1), autoincrement=True, nullable=False),
sa.Column('config_name', sa.VARCHAR(length=255), server_default=sa.text("'配置文件'::character varying"), autoincrement=False, nullable=False),
sa.Column('group_id', sa.VARCHAR(length=255), server_default=sa.text("'group_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('user_id', sa.VARCHAR(length=255), server_default=sa.text("'user_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('apply_id', sa.VARCHAR(length=255), server_default=sa.text("'apply_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('enable_llm_dedup_blockwise', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
sa.Column('enable_llm_disambiguation', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=False),
sa.Column('deep_retrieval', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
sa.Column('t_type_strict', sa.NUMERIC(), server_default=sa.text('0.5'), autoincrement=False, nullable=False),
sa.Column('t_name_strict', sa.NUMERIC(), server_default=sa.text('0.8'), autoincrement=False, nullable=False),
sa.Column('t_overall', sa.NUMERIC(), server_default=sa.text('0.56'), autoincrement=False, nullable=False),
sa.Column('chunker_strategy', postgresql.ENUM('RecursiveChunker', 'TokenChunker', 'SemanticChunker', 'NeuralChunker', 'HybridChunker', 'LLMChunker', 'SentenceChunker', 'LateChunker', name='chunker_strategy_enum'), server_default=sa.text("'RecursiveChunker'::chunker_strategy_enum"), autoincrement=False, nullable=False, comment='选择分块策略: \r\n "RecursiveChunker",\r\n "TokenChunker",\r\n "SemanticChunker",\r\n "NeuralChunker",\r\n "HybridChunker",\r\n "LLMChunker",\r\n "SentenceChunker",\r\n "LateChunker"'),
sa.Column('statement_granularity', sa.VARCHAR(length=255), server_default=sa.text("'2'::character varying"), autoincrement=False, nullable=True),
sa.Column('include_dialogue_context', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True),
sa.Column('max_context', sa.VARCHAR(length=255), server_default=sa.text("'200'::character varying"), autoincrement=False, nullable=True),
sa.Column('λ_time', sa.NUMERIC(), server_default=sa.text('0.3'), autoincrement=False, nullable=True),
sa.Column('λ_mem', sa.NUMERIC(), server_default=sa.text('0.4'), autoincrement=False, nullable=True),
sa.Column('offset', sa.NUMERIC(precision=255, scale=0), server_default=sa.text('0.56'), autoincrement=False, nullable=True),
sa.Column('state', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True, precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True, precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=False),
sa.Column('config_desc', sa.TEXT(), server_default=sa.text("'配置文件描述'::text"), autoincrement=False, nullable=False),
sa.Column('workspace_id', sa.VARCHAR(length=255), server_default=sa.text("'workspace_id'::character varying"), autoincrement=False, nullable=False),
sa.Column('enable_self_reflexion', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('iteration_period', sa.VARCHAR(length=50), server_default=sa.text("'三小时'::character varying"), autoincrement=False, nullable=True),
sa.Column('baseline', sa.VARCHAR(length=50), server_default=sa.text("'时间'::character varying"), autoincrement=False, nullable=True),
sa.Column('reflexion_range', sa.VARCHAR(length=25), server_default=sa.text("'部分'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_enabled', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('pruning_scene', sa.VARCHAR(length=255), server_default=sa.text("'education'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_threshold', sa.REAL(), server_default=sa.text('0.3'), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('config_id', 'config_name', name=op.f('data_config_copy1_pkey')),
sa.UniqueConstraint('config_name', name=op.f('data_config_copy1_config_name_key'), postgresql_include=[], postgresql_nulls_not_distinct=False)
)
# ### end Alembic commands ###

View File

@@ -1,64 +0,0 @@
"""20251118215552
Revision ID: 9a887a617afb
Revises: 29c030316adf
Create Date: 2025-11-18 21:55:53.308600
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '9a887a617afb'
down_revision: Union[str, None] = '29c030316adf'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.execute('DROP TABLE IF EXISTS data_config')
op.add_column('app_releases', sa.Column('release_notes', sa.String(), nullable=True, comment='版本说明'))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('app_releases', 'release_notes')
op.create_table('data_config',
sa.Column('config_id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('config_name', sa.VARCHAR(), autoincrement=False, nullable=False),
sa.Column('config_desc', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('group_id', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('user_id', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('apply_id', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('id', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('enable_llm_dedup_blockwise', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True),
sa.Column('enable_llm_disambiguation', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True),
sa.Column('deep_retrieval', sa.BOOLEAN(), server_default=sa.text('true'), autoincrement=False, nullable=True),
sa.Column('t_type_strict', sa.DOUBLE_PRECISION(precision=53), server_default=sa.text('0.8'), autoincrement=False, nullable=True),
sa.Column('t_name_strict', sa.DOUBLE_PRECISION(precision=53), server_default=sa.text('0.8'), autoincrement=False, nullable=True),
sa.Column('t_overall', sa.DOUBLE_PRECISION(precision=53), server_default=sa.text('0.8'), autoincrement=False, nullable=True),
sa.Column('state', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('chunker_strategy', sa.VARCHAR(), server_default=sa.text("'RecursiveChunker'::character varying"), autoincrement=False, nullable=True),
sa.Column('pruning_enabled', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('pruning_scene', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('pruning_threshold', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('enable_self_reflexion', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('iteration_period', sa.VARCHAR(), server_default=sa.text("'三小时'::character varying"), autoincrement=False, nullable=True),
sa.Column('reflexion_range', sa.VARCHAR(), server_default=sa.text("'部分'::character varying"), autoincrement=False, nullable=True),
sa.Column('baseline', sa.VARCHAR(), server_default=sa.text("'时间'::character varying"), autoincrement=False, nullable=True),
sa.Column('statement_granularity', sa.INTEGER(), server_default=sa.text('2'), autoincrement=False, nullable=True),
sa.Column('include_dialogue_context', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=True),
sa.Column('max_context', sa.INTEGER(), server_default=sa.text('1000'), autoincrement=False, nullable=True),
sa.Column('λ_time', sa.DOUBLE_PRECISION(precision=53), server_default=sa.text('0.5'), autoincrement=False, nullable=True),
sa.Column('λ_mem', sa.DOUBLE_PRECISION(precision=53), server_default=sa.text('0.5'), autoincrement=False, nullable=True),
sa.Column('offset', sa.DOUBLE_PRECISION(precision=53), server_default=sa.text('0.0'), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('config_id', name=op.f('data_config_pkey'))
)
# ### end Alembic commands ###

View File

@@ -1,96 +0,0 @@
"""20251124172719
Revision ID: a09d0e19cde6
Revises: 74bed22061db
Create Date: 2025-11-24 17:27:19.696895
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'a09d0e19cde6'
down_revision: Union[str, None] = '74bed22061db'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('agent_invocations',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('caller_agent_id', sa.UUID(), nullable=False, comment='调用者 Agent ID'),
sa.Column('callee_agent_id', sa.UUID(), nullable=False, comment='被调用者 Agent ID'),
sa.Column('conversation_id', sa.UUID(), nullable=True, comment='关联会话 ID不使用外键约束避免循环依赖'),
sa.Column('parent_invocation_id', sa.UUID(), nullable=True, comment='父调用 ID用于追踪调用链'),
sa.Column('input_message', sa.Text(), nullable=False, comment='输入消息'),
sa.Column('output_message', sa.Text(), nullable=True, comment='输出消息'),
sa.Column('context', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='上下文信息'),
sa.Column('status', sa.String(length=20), nullable=False, comment='状态: pending|running|completed|failed'),
sa.Column('error_message', sa.Text(), nullable=True, comment='错误信息'),
sa.Column('started_at', sa.DateTime(), nullable=False),
sa.Column('completed_at', sa.DateTime(), nullable=True),
sa.Column('elapsed_time', sa.Float(), nullable=True, comment='耗时(秒)'),
sa.Column('token_usage', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='Token 使用情况'),
sa.Column('meta_data', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='额外元数据'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['callee_agent_id'], ['agent_configs.id'], ),
sa.ForeignKeyConstraint(['caller_agent_id'], ['agent_configs.id'], ),
sa.ForeignKeyConstraint(['parent_invocation_id'], ['agent_invocations.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_agent_invocations_callee_agent_id'), 'agent_invocations', ['callee_agent_id'], unique=False)
op.create_index(op.f('ix_agent_invocations_caller_agent_id'), 'agent_invocations', ['caller_agent_id'], unique=False)
op.create_index(op.f('ix_agent_invocations_conversation_id'), 'agent_invocations', ['conversation_id'], unique=False)
op.create_index(op.f('ix_agent_invocations_id'), 'agent_invocations', ['id'], unique=False)
op.create_index(op.f('ix_agent_invocations_parent_invocation_id'), 'agent_invocations', ['parent_invocation_id'], unique=False)
op.create_index(op.f('ix_agent_invocations_started_at'), 'agent_invocations', ['started_at'], unique=False)
op.create_index(op.f('ix_agent_invocations_status'), 'agent_invocations', ['status'], unique=False)
op.create_table('multi_agent_configs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False, comment='关联应用'),
sa.Column('master_agent_id', sa.UUID(), nullable=False, comment='主 Agent ID'),
sa.Column('orchestration_mode', sa.String(length=20), nullable=False, comment='协作模式: sequential|parallel|conditional|loop'),
sa.Column('sub_agents', postgresql.JSON(astext_type=sa.Text()), nullable=False, comment="子 Agent 列表: [{'agent_id': 'uuid', 'name': '...', 'role': '...', 'priority': 1}]"),
sa.Column('routing_rules', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment="路由规则: [{'condition': '...', 'target_agent_id': 'uuid', 'priority': 1}]"),
sa.Column('execution_config', postgresql.JSON(astext_type=sa.Text()), nullable=False, comment="执行配置: {'max_iterations': 5, 'timeout': 60, 'parallel_limit': 3}"),
sa.Column('aggregation_strategy', sa.String(length=20), nullable=False, comment='结果整合策略: merge|vote|priority|custom'),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.ForeignKeyConstraint(['master_agent_id'], ['agent_configs.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_multi_agent_configs_app_id'), 'multi_agent_configs', ['app_id'], unique=True)
op.create_index(op.f('ix_multi_agent_configs_id'), 'multi_agent_configs', ['id'], unique=False)
op.add_column('agent_configs', sa.Column('agent_role', sa.String(length=20), nullable=True, comment='Agent 角色: master|sub|standalone'))
op.add_column('agent_configs', sa.Column('agent_domain', sa.String(length=50), nullable=True, comment='专业领域: customer_service|technical_support|sales 等'))
op.add_column('agent_configs', sa.Column('parent_agent_id', sa.UUID(), nullable=True, comment='父 Agent ID'))
op.add_column('agent_configs', sa.Column('capabilities', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='Agent 能力列表'))
op.create_foreign_key(None, 'agent_configs', 'agent_configs', ['parent_agent_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'agent_configs', type_='foreignkey')
op.drop_column('agent_configs', 'capabilities')
op.drop_column('agent_configs', 'parent_agent_id')
op.drop_column('agent_configs', 'agent_domain')
op.drop_column('agent_configs', 'agent_role')
op.drop_index(op.f('ix_multi_agent_configs_id'), table_name='multi_agent_configs')
op.drop_index(op.f('ix_multi_agent_configs_app_id'), table_name='multi_agent_configs')
op.drop_table('multi_agent_configs')
op.drop_index(op.f('ix_agent_invocations_status'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_started_at'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_parent_invocation_id'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_id'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_conversation_id'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_caller_agent_id'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_callee_agent_id'), table_name='agent_invocations')
op.drop_table('agent_invocations')
# ### end Alembic commands ###

View File

@@ -1,30 +0,0 @@
"""20251117140813
Revision ID: afe962cdb0b1
Revises: fda52b5e7c38
Create Date: 2025-11-17 14:08:13.933632
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'afe962cdb0b1'
down_revision: Union[str, None] = 'fda52b5e7c38'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,440 +0,0 @@
"""init_db_table
Revision ID: b012ab0089b9
Revises:
Create Date: 2025-11-12 14:07:56.132912
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'b012ab0089b9'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('apps',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False, comment='workspaces.id'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('icon', sa.String(), nullable=True),
sa.Column('icon_type', sa.String(), nullable=True),
sa.Column('type', sa.String(), nullable=False),
sa.Column('visibility', sa.String(), nullable=True),
sa.Column('status', sa.String(), nullable=True),
sa.Column('tags', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('current_release_id', sa.UUID(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['current_release_id'], ['app_releases.id'], name='fk_apps_current_release_id', use_alter=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_apps_current_release_id'), 'apps', ['current_release_id'], unique=False)
op.create_index(op.f('ix_apps_id'), 'apps', ['id'], unique=False)
op.create_index(op.f('ix_apps_name'), 'apps', ['name'], unique=False)
op.create_index(op.f('ix_apps_type'), 'apps', ['type'], unique=False)
op.create_table('documents',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('kb_id', sa.UUID(), nullable=False, comment='knowledges.id'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('file_id', sa.UUID(), nullable=False, comment='files.id'),
sa.Column('file_name', sa.String(), nullable=False, comment='file name'),
sa.Column('file_ext', sa.String(), nullable=False, comment='file extension'),
sa.Column('file_size', sa.Integer(), nullable=True, comment='file size(byte)'),
sa.Column('file_meta', sa.JSON(), nullable=False),
sa.Column('parser_id', sa.String(), nullable=False, comment='default parser ID'),
sa.Column('parser_config', sa.JSON(), nullable=False, comment='default parser config'),
sa.Column('chunk_num', sa.Integer(), nullable=True, comment='chunk num'),
sa.Column('progress', sa.Float(), nullable=True),
sa.Column('progress_msg', sa.String(), nullable=True, comment='process message'),
sa.Column('process_begin_at', sa.DateTime(), nullable=True),
sa.Column('process_duration', sa.Float(), nullable=True),
sa.Column('run', sa.Integer(), nullable=True, comment='start to run processing or cancel.(1: run it; 2: cancel)'),
sa.Column('status', sa.Integer(), nullable=True, comment='is it validate(0: wasted, 1: validate)'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_documents_file_ext'), 'documents', ['file_ext'], unique=False)
op.create_index(op.f('ix_documents_file_name'), 'documents', ['file_name'], unique=False)
op.create_index(op.f('ix_documents_id'), 'documents', ['id'], unique=False)
op.create_index(op.f('ix_documents_parser_id'), 'documents', ['parser_id'], unique=False)
op.create_table('files',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('kb_id', sa.UUID(), nullable=False, comment='knowledges.id'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('parent_id', sa.UUID(), nullable=True, comment='parent folder id'),
sa.Column('file_name', sa.String(), nullable=False, comment='file name or folder name,default folder name is /'),
sa.Column('file_ext', sa.String(), nullable=False, comment='file extension:folder|pdf'),
sa.Column('file_size', sa.Integer(), nullable=True, comment='file size(byte)'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_files_file_ext'), 'files', ['file_ext'], unique=False)
op.create_index(op.f('ix_files_file_name'), 'files', ['file_name'], unique=False)
op.create_index(op.f('ix_files_id'), 'files', ['id'], unique=False)
op.create_table('generic_files',
sa.Column('id', sa.UUID(), nullable=False, comment='文件唯一标识'),
sa.Column('tenant_id', sa.UUID(), nullable=False, comment='租户ID'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='创建者用户ID'),
sa.Column('file_name', sa.String(), nullable=False, comment='原始文件名'),
sa.Column('file_ext', sa.String(), nullable=False, comment='文件扩展名'),
sa.Column('file_size', sa.Integer(), nullable=False, comment='文件大小(字节)'),
sa.Column('mime_type', sa.String(), nullable=True, comment='MIME类型'),
sa.Column('context', sa.String(), nullable=False, comment='上传上下文avatar/app_icon/knowledge_base/temp/attachment'),
sa.Column('storage_path', sa.String(), nullable=False, comment='文件存储路径'),
sa.Column('file_metadata', sa.JSON(), nullable=True, comment='业务元数据'),
sa.Column('status', sa.String(), nullable=True, comment='文件状态active/processing/deleted'),
sa.Column('is_public', sa.Boolean(), nullable=True, comment='是否公开访问'),
sa.Column('access_url', sa.String(), nullable=True, comment='访问URL'),
sa.Column('reference_count', sa.Integer(), nullable=True, comment='引用计数'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.Column('deleted_at', sa.DateTime(), nullable=True, comment='删除时间(软删除)'),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_created_at', 'generic_files', ['created_at'], unique=False)
op.create_index('idx_tenant_context', 'generic_files', ['tenant_id', 'context'], unique=False)
op.create_index('idx_tenant_status', 'generic_files', ['tenant_id', 'status'], unique=False)
op.create_index(op.f('ix_generic_files_context'), 'generic_files', ['context'], unique=False)
op.create_index(op.f('ix_generic_files_created_by'), 'generic_files', ['created_by'], unique=False)
op.create_index(op.f('ix_generic_files_file_ext'), 'generic_files', ['file_ext'], unique=False)
op.create_index(op.f('ix_generic_files_id'), 'generic_files', ['id'], unique=False)
op.create_index(op.f('ix_generic_files_status'), 'generic_files', ['status'], unique=False)
op.create_index(op.f('ix_generic_files_tenant_id'), 'generic_files', ['tenant_id'], unique=False)
op.create_table('model_configs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(), nullable=False, comment='模型显示名称'),
sa.Column('type', sa.String(), nullable=False, comment='模型类型'),
sa.Column('description', sa.String(), nullable=True, comment='模型描述'),
sa.Column('config', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='模型配置参数'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否激活'),
sa.Column('is_public', sa.Boolean(), nullable=False, comment='是否公开'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_model_configs_id'), 'model_configs', ['id'], unique=False)
op.create_index(op.f('ix_model_configs_type'), 'model_configs', ['type'], unique=False)
op.create_table('tenants',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tenants_id'), 'tenants', ['id'], unique=False)
op.create_index(op.f('ix_tenants_name'), 'tenants', ['name'], unique=False)
op.create_table('agent_configs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False),
sa.Column('system_prompt', sa.Text(), nullable=True),
sa.Column('default_model_config_id', sa.UUID(), nullable=True),
sa.Column('tools_config', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('memory_strategy', sa.String(), nullable=True),
sa.Column('conversation_prefs', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.ForeignKeyConstraint(['default_model_config_id'], ['model_configs.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_agent_configs_app_id'), 'agent_configs', ['app_id'], unique=True)
op.create_index(op.f('ix_agent_configs_default_model_config_id'), 'agent_configs', ['default_model_config_id'], unique=False)
op.create_index(op.f('ix_agent_configs_id'), 'agent_configs', ['id'], unique=False)
op.create_table('app_releases',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False),
sa.Column('version', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('avatar', sa.String(), nullable=True),
sa.Column('type', sa.String(), nullable=False),
sa.Column('visibility', sa.String(), nullable=True),
sa.Column('status', sa.String(), nullable=True),
sa.Column('config', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('default_model_config_id', sa.UUID(), nullable=True),
sa.Column('published_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('published_at', sa.DateTime(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.ForeignKeyConstraint(['default_model_config_id'], ['model_configs.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('app_id', 'version', name='uq_app_release_app_version')
)
op.create_index(op.f('ix_app_releases_app_id'), 'app_releases', ['app_id'], unique=False)
op.create_index(op.f('ix_app_releases_default_model_config_id'), 'app_releases', ['default_model_config_id'], unique=False)
op.create_index(op.f('ix_app_releases_id'), 'app_releases', ['id'], unique=False)
op.create_index(op.f('ix_app_releases_version'), 'app_releases', ['version'], unique=False)
op.create_table('model_api_keys',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('model_config_id', sa.UUID(), nullable=False, comment='模型配置ID'),
sa.Column('model_name', sa.String(), nullable=False, comment='模型实际名称'),
sa.Column('provider', sa.String(), nullable=False, comment='API Key提供商'),
sa.Column('api_key', sa.String(), nullable=False, comment='API密钥'),
sa.Column('api_base', sa.String(), nullable=True, comment='API基础URL'),
sa.Column('config', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='API Key特定配置'),
sa.Column('usage_count', sa.String(), nullable=True, comment='使用次数'),
sa.Column('last_used_at', sa.DateTime(), nullable=True, comment='最后使用时间'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否激活'),
sa.Column('priority', sa.String(), nullable=True, comment='优先级'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['model_config_id'], ['model_configs.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_model_api_keys_id'), 'model_api_keys', ['id'], unique=False)
op.create_table('users',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('username', sa.String(), nullable=False),
sa.Column('email', sa.String(), nullable=False),
sa.Column('hashed_password', sa.String(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('is_superuser', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('last_login_at', sa.DateTime(), nullable=True),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
op.create_table('workspaces',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('icon', sa.String(), nullable=True),
sa.Column('iconType', sa.String(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_workspaces_id'), 'workspaces', ['id'], unique=False)
op.create_index(op.f('ix_workspaces_name'), 'workspaces', ['name'], unique=False)
op.create_table('knowledges',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False, comment='workspaces.id'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('parent_id', sa.UUID(), nullable=True, comment='parent folder id when type is Folder'),
sa.Column('name', sa.String(), nullable=False, comment='KB name'),
sa.Column('description', sa.String(), nullable=True, comment='KB description'),
sa.Column('avatar', sa.String(), nullable=True, comment='avatar url'),
sa.Column('type', sa.String(), nullable=True, comment='Type:General|Web|Third-party|Folder'),
sa.Column('permission_id', sa.String(), nullable=True, comment='permission ID:Private|Share'),
sa.Column('embedding_id', sa.UUID(), nullable=True, comment='default embedding model ID'),
sa.Column('reranker_id', sa.UUID(), nullable=True, comment='default reranker model ID'),
sa.Column('llm_id', sa.UUID(), nullable=True, comment='default llm model ID'),
sa.Column('image2text_id', sa.UUID(), nullable=True, comment='default image2text model ID'),
sa.Column('doc_num', sa.Integer(), nullable=True, comment='doc num'),
sa.Column('chunk_num', sa.Integer(), nullable=True, comment='chunk num'),
sa.Column('parser_id', sa.String(), nullable=True, comment='default parser ID'),
sa.Column('parser_config', sa.JSON(), nullable=False, comment='default parser config'),
sa.Column('status', sa.Integer(), nullable=True, comment='is it validate(0: wasted, 1: validate)'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['embedding_id'], ['model_configs.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['image2text_id'], ['model_configs.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['llm_id'], ['model_configs.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['reranker_id'], ['model_configs.id'], ondelete='SET NULL'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_knowledges_id'), 'knowledges', ['id'], unique=False)
op.create_index(op.f('ix_knowledges_name'), 'knowledges', ['name'], unique=False)
op.create_index(op.f('ix_knowledges_parser_id'), 'knowledges', ['parser_id'], unique=False)
op.create_index(op.f('ix_knowledges_status'), 'knowledges', ['status'], unique=False)
op.create_table('workspace_invites',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False),
sa.Column('email', sa.String(), nullable=False),
sa.Column('role', sa.String(), nullable=False),
sa.Column('token_hash', sa.String(), nullable=False),
sa.Column('status', sa.String(), nullable=False),
sa.Column('expires_at', sa.DateTime(), nullable=False),
sa.Column('accepted_at', sa.DateTime(), nullable=True),
sa.Column('created_by_user_id', sa.UUID(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['created_by_user_id'], ['users.id'], ),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_workspace_invites_email'), 'workspace_invites', ['email'], unique=False)
op.create_index(op.f('ix_workspace_invites_id'), 'workspace_invites', ['id'], unique=False)
op.create_index(op.f('ix_workspace_invites_token_hash'), 'workspace_invites', ['token_hash'], unique=True)
op.create_table('workspace_members',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('user_id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False),
sa.Column('role', sa.String(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_workspace_members_id'), 'workspace_members', ['id'], unique=False)
op.create_table('knowledge_shares',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('source_kb_id', sa.UUID(), nullable=False, comment='source knowledges.id'),
sa.Column('source_workspace_id', sa.UUID(), nullable=False, comment='source workspaces.id'),
sa.Column('target_kb_id', sa.UUID(), nullable=False, comment='target knowledges.id'),
sa.Column('target_workspace_id', sa.UUID(), nullable=False, comment='target workspaces.id'),
sa.Column('shared_by', sa.UUID(), nullable=False, comment='shared users.id'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['shared_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['target_kb_id'], ['knowledges.id'], ),
sa.ForeignKeyConstraint(['target_workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_knowledge_shares_id'), 'knowledge_shares', ['id'], unique=False)
# ### end Alembic commands ###
# 创建或更新统计信息的函数
op.execute("""
CREATE OR REPLACE FUNCTION update_knowledge_stats()
RETURNS TRIGGER AS $$
BEGIN
-- 处理 documents 表的插入、更新或删除
IF TG_TABLE_NAME = 'documents' THEN
-- 更新 knowledges 表的 doc_num
UPDATE knowledges
SET doc_num = (
SELECT COUNT(*)
FROM documents
WHERE kb_id = knowledges.id AND status = 1
)
WHERE id = NEW.kb_id OR id = OLD.kb_id;
-- 更新 knowledges 表的 chunk_num
UPDATE knowledges
SET chunk_num = (
SELECT COALESCE(SUM(chunk_num), 0)
FROM documents
WHERE kb_id = knowledges.id AND status = 1
)
WHERE id = NEW.kb_id OR id = OLD.kb_id;
-- 通过 knowledge_shares 表同步统计信息
-- 1. 使用 source_kb_id 的 doc_num 更新 target_kb_id 的 doc_num
UPDATE knowledges AS target
SET doc_num = source.doc_num
FROM knowledge_shares ks
JOIN knowledges AS source ON source.id = ks.source_kb_id
WHERE ks.target_kb_id = target.id
AND (source.id = NEW.kb_id OR source.id = OLD.kb_id);
-- 2. 使用 source_kb_id 的 chunk_num 更新 target_kb_id 的 chunk_num
UPDATE knowledges AS target
SET chunk_num = source.chunk_num
FROM knowledge_shares ks
JOIN knowledges AS source ON source.id = ks.source_kb_id
WHERE ks.target_kb_id = target.id
AND (source.id = NEW.kb_id OR source.id = OLD.kb_id);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
""")
# 创建 documents 表上的触发器(如果不存在)
op.execute("""
DROP TRIGGER IF EXISTS tr_documents_update_stats ON documents;
CREATE TRIGGER tr_documents_update_stats
AFTER INSERT OR UPDATE OR DELETE ON documents
FOR EACH ROW
EXECUTE FUNCTION update_knowledge_stats();
""")
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_knowledge_shares_id'), table_name='knowledge_shares')
op.drop_table('knowledge_shares')
op.drop_index(op.f('ix_workspace_members_id'), table_name='workspace_members')
op.drop_table('workspace_members')
op.drop_index(op.f('ix_workspace_invites_token_hash'), table_name='workspace_invites')
op.drop_index(op.f('ix_workspace_invites_id'), table_name='workspace_invites')
op.drop_index(op.f('ix_workspace_invites_email'), table_name='workspace_invites')
op.drop_table('workspace_invites')
op.drop_index(op.f('ix_knowledges_status'), table_name='knowledges')
op.drop_index(op.f('ix_knowledges_parser_id'), table_name='knowledges')
op.drop_index(op.f('ix_knowledges_name'), table_name='knowledges')
op.drop_index(op.f('ix_knowledges_id'), table_name='knowledges')
op.drop_table('knowledges')
op.drop_index(op.f('ix_workspaces_name'), table_name='workspaces')
op.drop_index(op.f('ix_workspaces_id'), table_name='workspaces')
op.drop_table('workspaces')
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_table('users')
op.drop_index(op.f('ix_model_api_keys_id'), table_name='model_api_keys')
op.drop_table('model_api_keys')
op.drop_index(op.f('ix_app_releases_version'), table_name='app_releases')
op.drop_index(op.f('ix_app_releases_id'), table_name='app_releases')
op.drop_index(op.f('ix_app_releases_default_model_config_id'), table_name='app_releases')
op.drop_index(op.f('ix_app_releases_app_id'), table_name='app_releases')
op.drop_table('app_releases')
op.drop_index(op.f('ix_agent_configs_id'), table_name='agent_configs')
op.drop_index(op.f('ix_agent_configs_default_model_config_id'), table_name='agent_configs')
op.drop_index(op.f('ix_agent_configs_app_id'), table_name='agent_configs')
op.drop_table('agent_configs')
op.drop_index(op.f('ix_tenants_name'), table_name='tenants')
op.drop_index(op.f('ix_tenants_id'), table_name='tenants')
op.drop_table('tenants')
op.drop_index(op.f('ix_model_configs_type'), table_name='model_configs')
op.drop_index(op.f('ix_model_configs_id'), table_name='model_configs')
op.drop_table('model_configs')
op.drop_index(op.f('ix_generic_files_tenant_id'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_status'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_id'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_file_ext'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_created_by'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_context'), table_name='generic_files')
op.drop_index('idx_tenant_status', table_name='generic_files')
op.drop_index('idx_tenant_context', table_name='generic_files')
op.drop_index('idx_created_at', table_name='generic_files')
op.drop_table('generic_files')
op.drop_index(op.f('ix_files_id'), table_name='files')
op.drop_index(op.f('ix_files_file_name'), table_name='files')
op.drop_index(op.f('ix_files_file_ext'), table_name='files')
op.drop_table('files')
op.drop_index(op.f('ix_documents_parser_id'), table_name='documents')
op.drop_index(op.f('ix_documents_id'), table_name='documents')
op.drop_index(op.f('ix_documents_file_name'), table_name='documents')
op.drop_index(op.f('ix_documents_file_ext'), table_name='documents')
op.drop_table('documents')
op.drop_index(op.f('ix_apps_type'), table_name='apps')
op.drop_index(op.f('ix_apps_name'), table_name='apps')
op.drop_index(op.f('ix_apps_id'), table_name='apps')
op.drop_index(op.f('ix_apps_current_release_id'), table_name='apps')
op.drop_table('apps')
# ### end Alembic commands ###
# 删除触发器
op.execute("DROP TRIGGER IF EXISTS tr_documents_update_stats ON documents;")
# 删除函数
op.execute("DROP FUNCTION IF EXISTS update_knowledge_stats();")

View File

@@ -1,87 +0,0 @@
"""init_db_table
Revision ID: bf36acfdffe3
Revises: 5de5ec651b01
Create Date: 2025-11-13 11:31:57.671799
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'bf36acfdffe3'
down_revision: Union[str, None] = '5de5ec651b01'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade():
# 创建或更新统计信息的函数
op.execute("""
CREATE OR REPLACE FUNCTION update_knowledge_stats()
RETURNS TRIGGER AS $$
BEGIN
-- 处理 documents 表的插入、更新或删除
IF TG_TABLE_NAME = 'documents' THEN
-- 更新 knowledges 表的 doc_num
UPDATE knowledges
SET doc_num = (
SELECT COUNT(*)
FROM documents
WHERE kb_id = knowledges.id AND status = 1
)
WHERE id = NEW.kb_id OR id = OLD.kb_id;
-- 更新 knowledges 表的 chunk_num
UPDATE knowledges
SET chunk_num = (
SELECT COALESCE(SUM(chunk_num), 0)
FROM documents
WHERE kb_id = knowledges.id AND status = 1
)
WHERE id = NEW.kb_id OR id = OLD.kb_id;
-- 通过 knowledge_shares 表同步统计信息
-- 1. 使用 source_kb_id 的 doc_num 更新 target_kb_id 的 doc_num
UPDATE knowledges AS target
SET doc_num = source.doc_num
FROM knowledge_shares ks
JOIN knowledges AS source ON source.id = ks.source_kb_id
WHERE ks.target_kb_id = target.id
AND (source.id = NEW.kb_id OR source.id = OLD.kb_id);
-- 2. 使用 source_kb_id 的 chunk_num 更新 target_kb_id 的 chunk_num
UPDATE knowledges AS target
SET chunk_num = source.chunk_num
FROM knowledge_shares ks
JOIN knowledges AS source ON source.id = ks.source_kb_id
WHERE ks.target_kb_id = target.id
AND (source.id = NEW.kb_id OR source.id = OLD.kb_id);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
""")
# 创建 documents 表上的触发器(如果不存在)
op.execute("""
DROP TRIGGER IF EXISTS tr_documents_update_stats ON documents;
CREATE TRIGGER tr_documents_update_stats
AFTER INSERT OR UPDATE OR DELETE ON documents
FOR EACH ROW
EXECUTE FUNCTION update_knowledge_stats();
""")
def downgrade():
# 删除触发器
op.execute("DROP TRIGGER IF EXISTS tr_documents_update_stats ON documents;")
# 删除函数
op.execute("DROP FUNCTION IF EXISTS update_knowledge_stats();")

View File

@@ -1,56 +0,0 @@
"""change end_user_id to string
Revision ID: change_end_user_id_to_string
Revises: f210d1844b07
Create Date: 2025-11-27 11:39:07
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'change_end_user_id_to_string'
down_revision: Union[str, None] = 'f210d1844b07'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Change end_user_id from UUID to String
op.alter_column('end_users', 'end_user_id',
existing_type=postgresql.UUID(),
type_=sa.String(),
existing_nullable=False)
# Change other_id from UUID to String and make it nullable
op.alter_column('end_users', 'other_id',
existing_type=postgresql.UUID(),
type_=sa.String(),
nullable=True)
# Add index on end_user_id for better query performance
op.create_index(op.f('ix_end_users_end_user_id'), 'end_users', ['end_user_id'], unique=False)
def downgrade() -> None:
# Remove index
op.drop_index(op.f('ix_end_users_end_user_id'), table_name='end_users')
# Revert other_id back to UUID
# Note: This will fail if there are non-UUID strings in the column
op.alter_column('end_users', 'other_id',
existing_type=sa.String(),
type_=postgresql.UUID(),
nullable=False,
postgresql_using='other_id::uuid')
# Revert end_user_id back to UUID
# Note: This will fail if there are non-UUID strings in the column
op.alter_column('end_users', 'end_user_id',
existing_type=sa.String(),
type_=postgresql.UUID(),
existing_nullable=False,
postgresql_using='end_user_id::uuid')

View File

@@ -1,188 +0,0 @@
"""20251120202612
Revision ID: d00648d486ca
Revises: 9a887a617afb
Create Date: 2025-11-20 20:26:12.910101
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'd00648d486ca'
down_revision: Union[str, None] = '9a887a617afb'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('data_config',
sa.Column('config_id', sa.Integer(), autoincrement=True, nullable=False, comment='配置ID'),
sa.Column('config_name', sa.String(), nullable=False, comment='配置名称'),
sa.Column('config_desc', sa.String(), nullable=True, comment='配置描述'),
sa.Column('group_id', sa.String(), nullable=True, comment='组ID'),
sa.Column('user_id', sa.String(), nullable=True, comment='用户ID'),
sa.Column('apply_id', sa.String(), nullable=True, comment='应用ID'),
sa.Column('id', sa.String(), nullable=True, comment='模型配置 UUID'),
sa.Column('enable_llm_dedup_blockwise', sa.Boolean(), nullable=True, comment='启用LLM决策去重'),
sa.Column('enable_llm_disambiguation', sa.Boolean(), nullable=True, comment='启用LLM决策消歧'),
sa.Column('deep_retrieval', sa.Boolean(), nullable=True, comment='深度检索开关'),
sa.Column('t_type_strict', sa.Float(), nullable=True, comment='类型严格阈值'),
sa.Column('t_name_strict', sa.Float(), nullable=True, comment='名称严格阈值'),
sa.Column('t_overall', sa.Float(), nullable=True, comment='综合阈值'),
sa.Column('state', sa.Boolean(), nullable=True, comment='配置使用状态'),
sa.Column('chunker_strategy', sa.String(), nullable=True, comment='分块策略'),
sa.Column('pruning_enabled', sa.Boolean(), nullable=True, comment='是否启动智能语义剪枝'),
sa.Column('pruning_scene', sa.String(), nullable=True, comment='智能剪枝场景education/online_service/outbound'),
sa.Column('pruning_threshold', sa.Float(), nullable=True, comment='智能语义剪枝阈值0-0.9'),
sa.Column('enable_self_reflexion', sa.Boolean(), nullable=True, comment='是否启用自我反思'),
sa.Column('iteration_period', sa.String(), nullable=True, comment='反思迭代周期'),
sa.Column('reflexion_range', sa.String(), nullable=True, comment='反思范围:部分/全部'),
sa.Column('baseline', sa.String(), nullable=True, comment='基线:时间/事实/时间和事实'),
sa.Column('statement_granularity', sa.Integer(), nullable=True, comment='陈述提取颗粒度,挡位 1/2/3'),
sa.Column('include_dialogue_context', sa.Boolean(), nullable=True, comment='是否包含对话上下文'),
sa.Column('max_context', sa.Integer(), nullable=True, comment='对话语境中包含字符的最大数量'),
sa.Column('λ_time', sa.Float(), nullable=True, comment='最低保持度0-1 小数'),
sa.Column('λ_mem', sa.Float(), nullable=True, comment='遗忘率0-1 小数'),
sa.Column('offset', sa.Float(), nullable=True, comment='偏移度0-1 小数'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.PrimaryKeyConstraint('config_id')
)
op.create_table('retrieval_info',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('host_id', sa.UUID(), nullable=False),
sa.Column('retrieve_info', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_retrieval_info_id'), 'retrieval_info', ['id'], unique=False)
op.create_table('end_users',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False),
sa.Column('end_user_id', sa.UUID(), nullable=False),
sa.Column('other_id', sa.UUID(), nullable=False),
sa.Column('other_name', sa.String(), nullable=False),
sa.Column('other_address', sa.String(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_end_users_id'), 'end_users', ['id'], unique=False)
op.create_table('memory_increments',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False),
sa.Column('total_num', sa.Integer(), nullable=False),
sa.Column('created_at', sa.Date(), nullable=True),
sa.Column('updated_at', sa.Date(), nullable=True),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_memory_increments_id'), 'memory_increments', ['id'], unique=False)
op.create_index(op.f('ix_memory_increments_workspace_id'), 'memory_increments', ['workspace_id'], unique=False)
op.create_table('api_keys',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False, comment='API Key 名称'),
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
sa.Column('key_prefix', sa.String(length=20), nullable=False, comment='Key 前缀'),
sa.Column('key_hash', sa.String(length=255), nullable=False, comment='Key 哈希值'),
sa.Column('type', sa.String(length=50), nullable=False, comment='API Key 类型'),
sa.Column('scopes', postgresql.JSONB(astext_type=sa.Text()), nullable=False, comment='权限范围列表'),
sa.Column('workspace_id', sa.UUID(), nullable=False, comment='所属工作空间'),
sa.Column('resource_id', sa.UUID(), nullable=True, comment='关联资源ID'),
sa.Column('resource_type', sa.String(length=50), nullable=True, comment='资源类型'),
sa.Column('rate_limit', sa.Integer(), nullable=True, comment='速率限制(请求/分钟)'),
sa.Column('quota_limit', sa.Integer(), nullable=True, comment='配额限制(总请求数)'),
sa.Column('quota_used', sa.Integer(), nullable=True, comment='已使用配额'),
sa.Column('expires_at', sa.DateTime(), nullable=True, comment='过期时间'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否激活'),
sa.Column('last_used_at', sa.DateTime(), nullable=True, comment='最后使用时间'),
sa.Column('usage_count', sa.Integer(), nullable=True, comment='使用次数'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='创建者'),
sa.Column('created_at', sa.DateTime(), nullable=False, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=False, comment='更新时间'),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_api_keys_id'), 'api_keys', ['id'], unique=False)
op.create_index(op.f('ix_api_keys_key_hash'), 'api_keys', ['key_hash'], unique=True)
op.create_index(op.f('ix_api_keys_resource_id'), 'api_keys', ['resource_id'], unique=False)
op.create_index(op.f('ix_api_keys_type'), 'api_keys', ['type'], unique=False)
op.create_index(op.f('ix_api_keys_workspace_id'), 'api_keys', ['workspace_id'], unique=False)
op.create_table('release_shares',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('release_id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False),
sa.Column('is_enabled', sa.Boolean(), nullable=False, comment='是否启用公开分享'),
sa.Column('share_token', sa.String(), nullable=False, comment='公开访问的唯一标识'),
sa.Column('require_password', sa.Boolean(), nullable=False, comment='是否需要密码访问'),
sa.Column('password_hash', sa.String(), nullable=True, comment='访问密码哈希'),
sa.Column('allow_embed', sa.Boolean(), nullable=False, comment='是否允许嵌入'),
sa.Column('embed_domains', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='允许嵌入的域名白名单'),
sa.Column('view_count', sa.Integer(), nullable=False, comment='访问次数'),
sa.Column('last_accessed_at', sa.DateTime(), nullable=True, comment='最后访问时间'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='创建者'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['release_id'], ['app_releases.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('release_id', name='uq_release_share_release_id')
)
op.create_index(op.f('ix_release_shares_app_id'), 'release_shares', ['app_id'], unique=False)
op.create_index(op.f('ix_release_shares_id'), 'release_shares', ['id'], unique=False)
op.create_index(op.f('ix_release_shares_release_id'), 'release_shares', ['release_id'], unique=True)
op.create_index(op.f('ix_release_shares_share_token'), 'release_shares', ['share_token'], unique=True)
op.create_table('api_key_logs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('api_key_id', sa.UUID(), nullable=False, comment='API Key ID'),
sa.Column('endpoint', sa.String(length=255), nullable=False, comment='请求端点'),
sa.Column('method', sa.String(length=10), nullable=False, comment='HTTP 方法'),
sa.Column('ip_address', sa.String(length=50), nullable=True, comment='IP 地址'),
sa.Column('user_agent', sa.Text(), nullable=True, comment='User Agent'),
sa.Column('status_code', sa.Integer(), nullable=True, comment='响应状态码'),
sa.Column('response_time', sa.Integer(), nullable=True, comment='响应时间(毫秒)'),
sa.Column('tokens_used', sa.Integer(), nullable=True, comment='使用的 Token 数'),
sa.Column('created_at', sa.DateTime(), nullable=False, comment='创建时间'),
sa.ForeignKeyConstraint(['api_key_id'], ['api_keys.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_api_key_logs_api_key_id'), 'api_key_logs', ['api_key_id'], unique=False)
op.create_index(op.f('ix_api_key_logs_created_at'), 'api_key_logs', ['created_at'], unique=False)
op.create_index(op.f('ix_api_key_logs_id'), 'api_key_logs', ['id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_api_key_logs_id'), table_name='api_key_logs')
op.drop_index(op.f('ix_api_key_logs_created_at'), table_name='api_key_logs')
op.drop_index(op.f('ix_api_key_logs_api_key_id'), table_name='api_key_logs')
op.drop_table('api_key_logs')
op.drop_index(op.f('ix_release_shares_share_token'), table_name='release_shares')
op.drop_index(op.f('ix_release_shares_release_id'), table_name='release_shares')
op.drop_index(op.f('ix_release_shares_id'), table_name='release_shares')
op.drop_index(op.f('ix_release_shares_app_id'), table_name='release_shares')
op.drop_table('release_shares')
op.drop_index(op.f('ix_api_keys_workspace_id'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_type'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_resource_id'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_key_hash'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_id'), table_name='api_keys')
op.drop_table('api_keys')
op.drop_index(op.f('ix_memory_increments_workspace_id'), table_name='memory_increments')
op.drop_index(op.f('ix_memory_increments_id'), table_name='memory_increments')
op.drop_table('memory_increments')
op.drop_index(op.f('ix_end_users_id'), table_name='end_users')
op.drop_table('end_users')
op.drop_index(op.f('ix_retrieval_info_id'), table_name='retrieval_info')
op.drop_table('retrieval_info')
op.execute('DROP TABLE IF EXISTS data_config')
# ### end Alembic commands ###

View File

@@ -1,54 +0,0 @@
"""202511291902
Revision ID: d1e56ecbf058
Revises: e225ed10f5cb
Create Date: 2025-11-29 18:14:38.529466
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'd1e56ecbf058'
down_revision: Union[str, None] = 'e225ed10f5cb'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 先删除依赖的视图(如果存在)
op.execute("DROP VIEW IF EXISTS data_config_sorted CASCADE")
# 检查表和列是否存在再进行操作
op.execute("""
DO $$
BEGIN
IF EXISTS (SELECT FROM information_schema.columns
WHERE table_name = 'data_config' AND column_name = 'llm_id') THEN
ALTER TABLE data_config ALTER COLUMN llm_id SET DATA TYPE VARCHAR;
COMMENT ON COLUMN data_config.llm_id IS 'LLM模型配置ID';
END IF;
IF EXISTS (SELECT FROM information_schema.columns
WHERE table_name = 'data_config' AND column_name = 'llm') THEN
ALTER TABLE data_config DROP COLUMN llm;
END IF;
END $$;
""")
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('data_config', sa.Column('llm', sa.VARCHAR(), autoincrement=False, nullable=True, comment='LLM模型配置ID'))
op.alter_column('data_config', 'llm_id',
existing_type=sa.VARCHAR(),
comment='临时',
existing_comment='LLM模型配置ID',
existing_nullable=True)
# ### end Alembic commands ###

View File

@@ -1,40 +0,0 @@
"""202511132042
Revision ID: d399fe451657
Revises: bf36acfdffe3
Create Date: 2025-11-13 20:44:16.670898
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'd399fe451657'
down_revision: Union[str, None] = 'bf36acfdffe3'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('app_releases', sa.Column('icon', sa.String(), nullable=True))
op.add_column('app_releases', sa.Column('icon_type', sa.String(), nullable=True))
op.drop_column('app_releases', 'avatar')
op.drop_column('app_releases', 'status')
op.add_column('users', sa.Column('current_workspace_id', sa.UUID(), nullable=True))
op.create_foreign_key(None, 'users', 'workspaces', ['current_workspace_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'users', type_='foreignkey')
op.drop_column('users', 'current_workspace_id')
op.add_column('app_releases', sa.Column('status', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('app_releases', sa.Column('avatar', sa.VARCHAR(), autoincrement=False, nullable=True))
op.drop_column('app_releases', 'icon_type')
op.drop_column('app_releases', 'icon')
# ### end Alembic commands ###

View File

@@ -1,34 +0,0 @@
"""drop_end_user_id_column
Revision ID: e225ed10f5cb
Revises: 8642a073ce2a
Create Date: 2025-11-29 12:27:30.459770
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'e225ed10f5cb'
down_revision: Union[str, None] = '8642a073ce2a'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# Drop the index first
op.drop_index('ix_end_users_end_user_id', table_name='end_users')
# Drop the end_user_id column
op.drop_column('end_users', 'end_user_id')
def downgrade() -> None:
# Re-add the end_user_id column
op.add_column('end_users', sa.Column('end_user_id', sa.String(), nullable=False))
# Re-create the index
op.create_index('ix_end_users_end_user_id', 'end_users', ['end_user_id'], unique=False)

View File

@@ -1,54 +0,0 @@
"""202511261433
Revision ID: f210d1844b07
Revises: 2e8146c98e3d
Create Date: 2025-11-26 14:33:56.077776
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'f210d1844b07'
down_revision: Union[str, None] = '2e8146c98e3d'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('knowledges', 'status',
existing_type=sa.INTEGER(),
comment='is it validate(0: disable, 1: enable, 2:Soft-delete)',
existing_comment='is it validate(0: wasted, 1: validate)',
existing_nullable=True)
op.add_column('multi_agent_configs', sa.Column('master_agent_name', sa.String(length=100), nullable=True, comment='主 Agent 名称'))
op.alter_column('multi_agent_configs', 'master_agent_id',
existing_type=sa.UUID(),
comment='主 Agent 发布版本 ID',
existing_comment='主 Agent ID',
existing_nullable=False)
op.drop_constraint(op.f('multi_agent_configs_master_agent_id_fkey'), 'multi_agent_configs', type_='foreignkey')
op.create_foreign_key(None, 'multi_agent_configs', 'app_releases', ['master_agent_id'], ['id'])
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'multi_agent_configs', type_='foreignkey')
op.create_foreign_key(op.f('multi_agent_configs_master_agent_id_fkey'), 'multi_agent_configs', 'agent_configs', ['master_agent_id'], ['id'])
op.alter_column('multi_agent_configs', 'master_agent_id',
existing_type=sa.UUID(),
comment='主 Agent ID',
existing_comment='主 Agent 发布版本 ID',
existing_nullable=False)
op.drop_column('multi_agent_configs', 'master_agent_name')
op.alter_column('knowledges', 'status',
existing_type=sa.INTEGER(),
comment='is it validate(0: wasted, 1: validate)',
existing_comment='is it validate(0: disable, 1: enable, 2:Soft-delete)',
existing_nullable=True)
# ### end Alembic commands ###

View File

@@ -1,48 +0,0 @@
"""20251126213637
Revision ID: fbab88219447
Revises: f210d1844b07
Create Date: 2025-11-26 21:36:45.872344
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'fbab88219447'
down_revision: Union[str, None] = 'f210d1844b07'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 检查列是否存在再进行操作
op.execute("""
DO $$
BEGIN
IF EXISTS (SELECT FROM information_schema.columns
WHERE table_name = 'data_config' AND column_name = 'workspace_id') THEN
COMMENT ON COLUMN data_config.workspace_id IS '工作空间ID';
END IF;
END $$;
""")
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 检查列是否存在再进行操作
op.execute("""
DO $$
BEGIN
IF EXISTS (SELECT FROM information_schema.columns
WHERE table_name = 'data_config' AND column_name = 'workspace_id') THEN
COMMENT ON COLUMN data_config.workspace_id IS 'comment';
END IF;
END $$;
""")
# ### end Alembic commands ###

View File

@@ -0,0 +1,727 @@
"""init_db_table
Revision ID: fc9664b8e4a1
Revises:
Create Date: 2025-12-04 10:46:18.612574
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'fc9664b8e4a1'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('apps',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False, comment='workspaces.id'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('icon', sa.String(), nullable=True),
sa.Column('icon_type', sa.String(), nullable=True),
sa.Column('type', sa.String(), nullable=False),
sa.Column('visibility', sa.String(), nullable=True),
sa.Column('status', sa.String(), nullable=True),
sa.Column('tags', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('current_release_id', sa.UUID(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['current_release_id'], ['app_releases.id'], name='fk_apps_current_release_id', use_alter=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_apps_current_release_id'), 'apps', ['current_release_id'], unique=False)
op.create_index(op.f('ix_apps_id'), 'apps', ['id'], unique=False)
op.create_index(op.f('ix_apps_name'), 'apps', ['name'], unique=False)
op.create_index(op.f('ix_apps_type'), 'apps', ['type'], unique=False)
op.create_table('data_config',
sa.Column('config_id', sa.Integer(), autoincrement=True, nullable=False, comment='配置ID'),
sa.Column('config_name', sa.String(), nullable=False, comment='配置名称'),
sa.Column('config_desc', sa.String(), nullable=True, comment='配置描述'),
sa.Column('workspace_id', sa.UUID(), nullable=True, comment='工作空间ID'),
sa.Column('group_id', sa.String(), nullable=True, comment='组ID'),
sa.Column('user_id', sa.String(), nullable=True, comment='用户ID'),
sa.Column('apply_id', sa.String(), nullable=True, comment='应用ID'),
sa.Column('llm_id', sa.String(), nullable=True, comment='LLM模型配置ID'),
sa.Column('embedding_id', sa.String(), nullable=True, comment='嵌入模型配置ID'),
sa.Column('rerank_id', sa.String(), nullable=True, comment='重排序模型配置ID'),
sa.Column('llm', sa.String(), nullable=True, comment='LLM模型配置ID'),
sa.Column('enable_llm_dedup_blockwise', sa.Boolean(), nullable=True, comment='启用LLM决策去重'),
sa.Column('enable_llm_disambiguation', sa.Boolean(), nullable=True, comment='启用LLM决策消歧'),
sa.Column('deep_retrieval', sa.Boolean(), nullable=True, comment='深度检索开关'),
sa.Column('t_type_strict', sa.Float(), nullable=True, comment='类型严格阈值'),
sa.Column('t_name_strict', sa.Float(), nullable=True, comment='名称严格阈值'),
sa.Column('t_overall', sa.Float(), nullable=True, comment='综合阈值'),
sa.Column('state', sa.Boolean(), nullable=True, comment='配置使用状态'),
sa.Column('chunker_strategy', sa.String(), nullable=True, comment='分块策略'),
sa.Column('pruning_enabled', sa.Boolean(), nullable=True, comment='是否启动智能语义剪枝'),
sa.Column('pruning_scene', sa.String(), nullable=True, comment='智能剪枝场景education/online_service/outbound'),
sa.Column('pruning_threshold', sa.Float(), nullable=True, comment='智能语义剪枝阈值0-0.9'),
sa.Column('enable_self_reflexion', sa.Boolean(), nullable=True, comment='是否启用自我反思'),
sa.Column('iteration_period', sa.String(), nullable=True, comment='反思迭代周期'),
sa.Column('reflexion_range', sa.String(), nullable=True, comment='反思范围:部分/全部'),
sa.Column('baseline', sa.String(), nullable=True, comment='基线:时间/事实/时间和事实'),
sa.Column('statement_granularity', sa.Integer(), nullable=True, comment='陈述提取颗粒度,挡位 1/2/3'),
sa.Column('include_dialogue_context', sa.Boolean(), nullable=True, comment='是否包含对话上下文'),
sa.Column('max_context', sa.Integer(), nullable=True, comment='对话语境中包含字符的最大数量'),
sa.Column('lambda_time', sa.Float(), nullable=True, comment='最低保持度0-1 小数'),
sa.Column('lambda_mem', sa.Float(), nullable=True, comment='遗忘率0-1 小数'),
sa.Column('offset', sa.Float(), nullable=True, comment='偏移度0-1 小数'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.PrimaryKeyConstraint('config_id')
)
op.create_table('documents',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('kb_id', sa.UUID(), nullable=False, comment='knowledges.id'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('file_id', sa.UUID(), nullable=False, comment='files.id'),
sa.Column('file_name', sa.String(), nullable=False, comment='file name'),
sa.Column('file_ext', sa.String(), nullable=False, comment='file extension'),
sa.Column('file_size', sa.Integer(), nullable=True, comment='file size(byte)'),
sa.Column('file_meta', sa.JSON(), nullable=False),
sa.Column('parser_id', sa.String(), nullable=False, comment='default parser ID'),
sa.Column('parser_config', sa.JSON(), nullable=False, comment='default parser config'),
sa.Column('chunk_num', sa.Integer(), nullable=True, comment='chunk num'),
sa.Column('progress', sa.Float(), nullable=True),
sa.Column('progress_msg', sa.String(), nullable=True, comment='process message'),
sa.Column('process_begin_at', sa.DateTime(), nullable=True),
sa.Column('process_duration', sa.Float(), nullable=True),
sa.Column('run', sa.Integer(), nullable=True, comment='start to run processing or cancel.(1: run it; 2: cancel)'),
sa.Column('status', sa.Integer(), nullable=True, comment='is it validate(0: wasted, 1: validate)'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_documents_file_ext'), 'documents', ['file_ext'], unique=False)
op.create_index(op.f('ix_documents_file_name'), 'documents', ['file_name'], unique=False)
op.create_index(op.f('ix_documents_id'), 'documents', ['id'], unique=False)
op.create_index(op.f('ix_documents_parser_id'), 'documents', ['parser_id'], unique=False)
op.create_table('files',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('kb_id', sa.UUID(), nullable=False, comment='knowledges.id'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('parent_id', sa.UUID(), nullable=True, comment='parent folder id'),
sa.Column('file_name', sa.String(), nullable=False, comment='file name or folder name,default folder name is /'),
sa.Column('file_ext', sa.String(), nullable=False, comment='file extension:folder|pdf'),
sa.Column('file_size', sa.Integer(), nullable=True, comment='file size(byte)'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_files_file_ext'), 'files', ['file_ext'], unique=False)
op.create_index(op.f('ix_files_file_name'), 'files', ['file_name'], unique=False)
op.create_index(op.f('ix_files_id'), 'files', ['id'], unique=False)
op.create_table('generic_files',
sa.Column('id', sa.UUID(), nullable=False, comment='文件唯一标识'),
sa.Column('tenant_id', sa.UUID(), nullable=False, comment='租户ID'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='创建者用户ID'),
sa.Column('file_name', sa.String(), nullable=False, comment='原始文件名'),
sa.Column('file_ext', sa.String(), nullable=False, comment='文件扩展名'),
sa.Column('file_size', sa.Integer(), nullable=False, comment='文件大小(字节)'),
sa.Column('mime_type', sa.String(), nullable=True, comment='MIME类型'),
sa.Column('context', sa.String(), nullable=False, comment='上传上下文avatar/app_icon/knowledge_base/temp/attachment'),
sa.Column('storage_path', sa.String(), nullable=False, comment='文件存储路径'),
sa.Column('file_metadata', sa.JSON(), nullable=True, comment='业务元数据'),
sa.Column('status', sa.String(), nullable=True, comment='文件状态active/processing/deleted'),
sa.Column('is_public', sa.Boolean(), nullable=True, comment='是否公开访问'),
sa.Column('access_url', sa.String(), nullable=True, comment='访问URL'),
sa.Column('reference_count', sa.Integer(), nullable=True, comment='引用计数'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.Column('deleted_at', sa.DateTime(), nullable=True, comment='删除时间(软删除)'),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_created_at', 'generic_files', ['created_at'], unique=False)
op.create_index('idx_tenant_context', 'generic_files', ['tenant_id', 'context'], unique=False)
op.create_index('idx_tenant_status', 'generic_files', ['tenant_id', 'status'], unique=False)
op.create_index(op.f('ix_generic_files_context'), 'generic_files', ['context'], unique=False)
op.create_index(op.f('ix_generic_files_created_by'), 'generic_files', ['created_by'], unique=False)
op.create_index(op.f('ix_generic_files_file_ext'), 'generic_files', ['file_ext'], unique=False)
op.create_index(op.f('ix_generic_files_id'), 'generic_files', ['id'], unique=False)
op.create_index(op.f('ix_generic_files_status'), 'generic_files', ['status'], unique=False)
op.create_index(op.f('ix_generic_files_tenant_id'), 'generic_files', ['tenant_id'], unique=False)
op.create_table('model_configs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(), nullable=False, comment='模型显示名称'),
sa.Column('type', sa.String(), nullable=False, comment='模型类型'),
sa.Column('description', sa.String(), nullable=True, comment='模型描述'),
sa.Column('config', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='模型配置参数'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否激活'),
sa.Column('is_public', sa.Boolean(), nullable=False, comment='是否公开'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_model_configs_id'), 'model_configs', ['id'], unique=False)
op.create_index(op.f('ix_model_configs_type'), 'model_configs', ['type'], unique=False)
op.create_table('retrieval_info',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('host_id', sa.UUID(), nullable=False),
sa.Column('retrieve_info', sa.Text(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_retrieval_info_id'), 'retrieval_info', ['id'], unique=False)
op.create_table('tenants',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tenants_id'), 'tenants', ['id'], unique=False)
op.create_index(op.f('ix_tenants_name'), 'tenants', ['name'], unique=False)
op.create_table('agent_configs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False),
sa.Column('system_prompt', sa.Text(), nullable=True, comment='系统提示词'),
sa.Column('default_model_config_id', sa.UUID(), nullable=True, comment='默认模型配置ID'),
sa.Column('model_parameters', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='模型参数配置temperature、max_tokens等'),
sa.Column('knowledge_retrieval', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='知识库检索配置'),
sa.Column('memory', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='记忆配置'),
sa.Column('variables', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='变量配置'),
sa.Column('tools', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='工具配置'),
sa.Column('agent_role', sa.String(length=20), nullable=True, comment='Agent 角色: master|sub|standalone'),
sa.Column('agent_domain', sa.String(length=50), nullable=True, comment='专业领域: customer_service|technical_support|sales 等'),
sa.Column('parent_agent_id', sa.UUID(), nullable=True, comment='父 Agent ID'),
sa.Column('capabilities', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='Agent 能力列表'),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.ForeignKeyConstraint(['default_model_config_id'], ['model_configs.id'], ),
sa.ForeignKeyConstraint(['parent_agent_id'], ['agent_configs.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_agent_configs_app_id'), 'agent_configs', ['app_id'], unique=True)
op.create_index(op.f('ix_agent_configs_default_model_config_id'), 'agent_configs', ['default_model_config_id'], unique=False)
op.create_index(op.f('ix_agent_configs_id'), 'agent_configs', ['id'], unique=False)
op.create_table('app_releases',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False),
sa.Column('version', sa.Integer(), nullable=False),
sa.Column('version_name', sa.String(), nullable=False),
sa.Column('release_notes', sa.String(), nullable=True, comment='版本说明'),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('icon', sa.String(), nullable=True),
sa.Column('icon_type', sa.String(), nullable=True),
sa.Column('type', sa.String(), nullable=False),
sa.Column('visibility', sa.String(), nullable=True),
sa.Column('config', postgresql.JSON(astext_type=sa.Text()), nullable=True),
sa.Column('default_model_config_id', sa.UUID(), nullable=True),
sa.Column('published_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('published_at', sa.DateTime(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.ForeignKeyConstraint(['default_model_config_id'], ['model_configs.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('app_id', 'version', name='uq_app_release_app_version')
)
op.create_index(op.f('ix_app_releases_app_id'), 'app_releases', ['app_id'], unique=False)
op.create_index(op.f('ix_app_releases_default_model_config_id'), 'app_releases', ['default_model_config_id'], unique=False)
op.create_index(op.f('ix_app_releases_id'), 'app_releases', ['id'], unique=False)
op.create_index(op.f('ix_app_releases_version'), 'app_releases', ['version'], unique=False)
op.create_table('end_users',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False),
sa.Column('other_id', sa.String(), nullable=True),
sa.Column('other_name', sa.String(), nullable=False),
sa.Column('other_address', sa.String(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_end_users_id'), 'end_users', ['id'], unique=False)
op.create_table('model_api_keys',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('model_config_id', sa.UUID(), nullable=False, comment='模型配置ID'),
sa.Column('model_name', sa.String(), nullable=False, comment='模型实际名称'),
sa.Column('provider', sa.String(), nullable=False, comment='API Key提供商'),
sa.Column('api_key', sa.String(), nullable=False, comment='API密钥'),
sa.Column('api_base', sa.String(), nullable=True, comment='API基础URL'),
sa.Column('config', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='API Key特定配置'),
sa.Column('usage_count', sa.String(), nullable=True, comment='使用次数'),
sa.Column('last_used_at', sa.DateTime(), nullable=True, comment='最后使用时间'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否激活'),
sa.Column('priority', sa.String(), nullable=True, comment='优先级'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['model_config_id'], ['model_configs.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_model_api_keys_id'), 'model_api_keys', ['id'], unique=False)
op.create_table('workspaces',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('icon', sa.String(), nullable=True),
sa.Column('iconType', sa.String(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.Column('storage_type', sa.String(), nullable=True),
sa.Column('llm', sa.String(), nullable=True),
sa.Column('embedding', sa.String(), nullable=True),
sa.Column('rerank', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_workspaces_id'), 'workspaces', ['id'], unique=False)
op.create_index(op.f('ix_workspaces_name'), 'workspaces', ['name'], unique=False)
op.create_table('agent_invocations',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('caller_agent_id', sa.UUID(), nullable=False, comment='调用者 Agent ID'),
sa.Column('callee_agent_id', sa.UUID(), nullable=False, comment='被调用者 Agent ID'),
sa.Column('conversation_id', sa.UUID(), nullable=True, comment='关联会话 ID不使用外键约束避免循环依赖'),
sa.Column('parent_invocation_id', sa.UUID(), nullable=True, comment='父调用 ID用于追踪调用链'),
sa.Column('input_message', sa.Text(), nullable=False, comment='输入消息'),
sa.Column('output_message', sa.Text(), nullable=True, comment='输出消息'),
sa.Column('context', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='上下文信息'),
sa.Column('status', sa.String(length=20), nullable=False, comment='状态: pending|running|completed|failed'),
sa.Column('error_message', sa.Text(), nullable=True, comment='错误信息'),
sa.Column('started_at', sa.DateTime(), nullable=False),
sa.Column('completed_at', sa.DateTime(), nullable=True),
sa.Column('elapsed_time', sa.Float(), nullable=True, comment='耗时(秒)'),
sa.Column('token_usage', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='Token 使用情况'),
sa.Column('meta_data', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='额外元数据'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['callee_agent_id'], ['agent_configs.id'], ),
sa.ForeignKeyConstraint(['caller_agent_id'], ['agent_configs.id'], ),
sa.ForeignKeyConstraint(['parent_invocation_id'], ['agent_invocations.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_agent_invocations_callee_agent_id'), 'agent_invocations', ['callee_agent_id'], unique=False)
op.create_index(op.f('ix_agent_invocations_caller_agent_id'), 'agent_invocations', ['caller_agent_id'], unique=False)
op.create_index(op.f('ix_agent_invocations_conversation_id'), 'agent_invocations', ['conversation_id'], unique=False)
op.create_index(op.f('ix_agent_invocations_id'), 'agent_invocations', ['id'], unique=False)
op.create_index(op.f('ix_agent_invocations_parent_invocation_id'), 'agent_invocations', ['parent_invocation_id'], unique=False)
op.create_index(op.f('ix_agent_invocations_started_at'), 'agent_invocations', ['started_at'], unique=False)
op.create_index(op.f('ix_agent_invocations_status'), 'agent_invocations', ['status'], unique=False)
op.create_table('conversations',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False, comment='应用ID'),
sa.Column('workspace_id', sa.UUID(), nullable=False, comment='工作空间ID'),
sa.Column('user_id', sa.String(), nullable=True, comment='用户ID外部系统'),
sa.Column('title', sa.String(length=255), nullable=True, comment='会话标题'),
sa.Column('summary', sa.Text(), nullable=True, comment='会话摘要'),
sa.Column('is_draft', sa.Boolean(), nullable=False, comment='是否为草稿会话'),
sa.Column('config_snapshot', sa.JSON(), nullable=True, comment='配置快照Agent配置、模型配置等'),
sa.Column('message_count', sa.Integer(), nullable=True, comment='消息数量'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否活跃'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=True, comment='更新时间'),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_conversations_id'), 'conversations', ['id'], unique=False)
op.create_table('memory_increments',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False),
sa.Column('total_num', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_memory_increments_id'), 'memory_increments', ['id'], unique=False)
op.create_index(op.f('ix_memory_increments_workspace_id'), 'memory_increments', ['workspace_id'], unique=False)
op.create_table('multi_agent_configs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False, comment='关联应用'),
sa.Column('master_agent_id', sa.UUID(), nullable=False, comment='主 Agent 发布版本 ID'),
sa.Column('master_agent_name', sa.String(length=100), nullable=True, comment='主 Agent 名称'),
sa.Column('orchestration_mode', sa.String(length=20), nullable=False, comment='协作模式: sequential|parallel|conditional|loop'),
sa.Column('sub_agents', postgresql.JSON(astext_type=sa.Text()), nullable=False, comment="子 Agent 列表: [{'agent_id': 'uuid', 'name': '...', 'role': '...', 'priority': 1}]"),
sa.Column('routing_rules', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment="路由规则: [{'condition': '...', 'target_agent_id': 'uuid', 'priority': 1}]"),
sa.Column('execution_config', postgresql.JSON(astext_type=sa.Text()), nullable=False, comment="执行配置: {'max_iterations': 5, 'timeout': 60, 'parallel_limit': 3}"),
sa.Column('aggregation_strategy', sa.String(length=20), nullable=False, comment='结果整合策略: merge|vote|priority|custom'),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ),
sa.ForeignKeyConstraint(['master_agent_id'], ['app_releases.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_multi_agent_configs_app_id'), 'multi_agent_configs', ['app_id'], unique=True)
op.create_index(op.f('ix_multi_agent_configs_id'), 'multi_agent_configs', ['id'], unique=False)
op.create_table('users',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('username', sa.String(), nullable=False),
sa.Column('email', sa.String(), nullable=False),
sa.Column('hashed_password', sa.String(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('is_superuser', sa.Boolean(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('last_login_at', sa.DateTime(), nullable=True),
sa.Column('current_workspace_id', sa.UUID(), nullable=True),
sa.Column('tenant_id', sa.UUID(), nullable=False),
sa.ForeignKeyConstraint(['current_workspace_id'], ['workspaces.id'], ),
sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
op.create_table('api_keys',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('name', sa.String(length=255), nullable=False, comment='API Key 名称'),
sa.Column('description', sa.Text(), nullable=True, comment='描述'),
sa.Column('key_prefix', sa.String(length=20), nullable=False, comment='Key 前缀'),
sa.Column('key_hash', sa.String(length=255), nullable=False, comment='Key 哈希值'),
sa.Column('type', sa.String(length=50), nullable=False, comment='API Key 类型'),
sa.Column('scopes', postgresql.JSONB(astext_type=sa.Text()), nullable=False, comment='权限范围列表'),
sa.Column('workspace_id', sa.UUID(), nullable=False, comment='所属工作空间'),
sa.Column('resource_id', sa.UUID(), nullable=True, comment='关联资源ID'),
sa.Column('resource_type', sa.String(length=50), nullable=True, comment='资源类型'),
sa.Column('rate_limit', sa.Integer(), nullable=True, comment='速率限制(请求/分钟)'),
sa.Column('quota_limit', sa.Integer(), nullable=True, comment='配额限制(总请求数)'),
sa.Column('quota_used', sa.Integer(), nullable=True, comment='已使用配额'),
sa.Column('expires_at', sa.DateTime(), nullable=True, comment='过期时间'),
sa.Column('is_active', sa.Boolean(), nullable=False, comment='是否激活'),
sa.Column('last_used_at', sa.DateTime(), nullable=True, comment='最后使用时间'),
sa.Column('usage_count', sa.Integer(), nullable=True, comment='使用次数'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='创建者'),
sa.Column('created_at', sa.DateTime(), nullable=False, comment='创建时间'),
sa.Column('updated_at', sa.DateTime(), nullable=False, comment='更新时间'),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_api_keys_id'), 'api_keys', ['id'], unique=False)
op.create_index(op.f('ix_api_keys_key_hash'), 'api_keys', ['key_hash'], unique=True)
op.create_index(op.f('ix_api_keys_resource_id'), 'api_keys', ['resource_id'], unique=False)
op.create_index(op.f('ix_api_keys_type'), 'api_keys', ['type'], unique=False)
op.create_index(op.f('ix_api_keys_workspace_id'), 'api_keys', ['workspace_id'], unique=False)
op.create_table('app_shares',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('source_app_id', sa.UUID(), nullable=False, comment='源应用ID'),
sa.Column('source_workspace_id', sa.UUID(), nullable=False, comment='源工作空间ID'),
sa.Column('target_workspace_id', sa.UUID(), nullable=False, comment='目标工作空间ID'),
sa.Column('shared_by', sa.UUID(), nullable=False, comment='分享者用户ID'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['shared_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['source_app_id'], ['apps.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['source_workspace_id'], ['workspaces.id'], ),
sa.ForeignKeyConstraint(['target_workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_app_shares_id'), 'app_shares', ['id'], unique=False)
op.create_table('knowledges',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False, comment='workspaces.id'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='users.id'),
sa.Column('parent_id', sa.UUID(), nullable=True, comment='parent folder id when type is Folder'),
sa.Column('name', sa.String(), nullable=False, comment='KB name'),
sa.Column('description', sa.String(), nullable=True, comment='KB description'),
sa.Column('avatar', sa.String(), nullable=True, comment='avatar url'),
sa.Column('type', sa.String(), nullable=True, comment='Type:General|Web|Third-party|Folder'),
sa.Column('permission_id', sa.String(), nullable=True, comment='permission ID:Private|Share'),
sa.Column('embedding_id', sa.UUID(), nullable=True, comment='default embedding model ID'),
sa.Column('reranker_id', sa.UUID(), nullable=True, comment='default reranker model ID'),
sa.Column('llm_id', sa.UUID(), nullable=True, comment='default llm model ID'),
sa.Column('image2text_id', sa.UUID(), nullable=True, comment='default image2text model ID'),
sa.Column('doc_num', sa.Integer(), nullable=True, comment='doc num'),
sa.Column('chunk_num', sa.Integer(), nullable=True, comment='chunk num'),
sa.Column('parser_id', sa.String(), nullable=True, comment='default parser ID'),
sa.Column('parser_config', sa.JSON(), nullable=False, comment='default parser config'),
sa.Column('status', sa.Integer(), nullable=True, comment='is it validate(0: disable, 1: enable, 2:Soft-delete)'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['embedding_id'], ['model_configs.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['image2text_id'], ['model_configs.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['llm_id'], ['model_configs.id'], ondelete='SET NULL'),
sa.ForeignKeyConstraint(['reranker_id'], ['model_configs.id'], ondelete='SET NULL'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_knowledges_id'), 'knowledges', ['id'], unique=False)
op.create_index(op.f('ix_knowledges_name'), 'knowledges', ['name'], unique=False)
op.create_index(op.f('ix_knowledges_parser_id'), 'knowledges', ['parser_id'], unique=False)
op.create_index(op.f('ix_knowledges_status'), 'knowledges', ['status'], unique=False)
op.create_table('messages',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('conversation_id', sa.UUID(), nullable=False, comment='会话ID'),
sa.Column('role', sa.String(length=20), nullable=False, comment='角色: user/assistant/system'),
sa.Column('content', sa.Text(), nullable=False, comment='消息内容'),
sa.Column('meta_data', sa.JSON(), nullable=True, comment='消息元数据如模型、token使用等'),
sa.Column('created_at', sa.DateTime(), nullable=True, comment='创建时间'),
sa.ForeignKeyConstraint(['conversation_id'], ['conversations.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_messages_id'), 'messages', ['id'], unique=False)
op.create_table('release_shares',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('release_id', sa.UUID(), nullable=False),
sa.Column('app_id', sa.UUID(), nullable=False),
sa.Column('is_enabled', sa.Boolean(), nullable=False, comment='是否启用公开分享'),
sa.Column('share_token', sa.String(), nullable=False, comment='公开访问的唯一标识'),
sa.Column('require_password', sa.Boolean(), nullable=False, comment='是否需要密码访问'),
sa.Column('password_hash', sa.String(), nullable=True, comment='访问密码哈希'),
sa.Column('allow_embed', sa.Boolean(), nullable=False, comment='是否允许嵌入'),
sa.Column('embed_domains', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='允许嵌入的域名白名单'),
sa.Column('view_count', sa.Integer(), nullable=False, comment='访问次数'),
sa.Column('last_accessed_at', sa.DateTime(), nullable=True, comment='最后访问时间'),
sa.Column('created_by', sa.UUID(), nullable=False, comment='创建者'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['created_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['release_id'], ['app_releases.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('release_id', name='uq_release_share_release_id')
)
op.create_index(op.f('ix_release_shares_app_id'), 'release_shares', ['app_id'], unique=False)
op.create_index(op.f('ix_release_shares_id'), 'release_shares', ['id'], unique=False)
op.create_index(op.f('ix_release_shares_release_id'), 'release_shares', ['release_id'], unique=True)
op.create_index(op.f('ix_release_shares_share_token'), 'release_shares', ['share_token'], unique=True)
op.create_table('workspace_invites',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False),
sa.Column('email', sa.String(), nullable=False),
sa.Column('role', sa.String(), nullable=False),
sa.Column('token_hash', sa.String(), nullable=False),
sa.Column('status', sa.String(), nullable=False),
sa.Column('expires_at', sa.DateTime(), nullable=False),
sa.Column('accepted_at', sa.DateTime(), nullable=True),
sa.Column('created_by_user_id', sa.UUID(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['created_by_user_id'], ['users.id'], ),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_workspace_invites_email'), 'workspace_invites', ['email'], unique=False)
op.create_index(op.f('ix_workspace_invites_id'), 'workspace_invites', ['id'], unique=False)
op.create_index(op.f('ix_workspace_invites_token_hash'), 'workspace_invites', ['token_hash'], unique=True)
op.create_table('workspace_members',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('user_id', sa.UUID(), nullable=False),
sa.Column('workspace_id', sa.UUID(), nullable=False),
sa.Column('role', sa.String(), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
sa.ForeignKeyConstraint(['workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_workspace_members_id'), 'workspace_members', ['id'], unique=False)
op.create_table('api_key_logs',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('api_key_id', sa.UUID(), nullable=False, comment='API Key ID'),
sa.Column('endpoint', sa.String(length=255), nullable=False, comment='请求端点'),
sa.Column('method', sa.String(length=10), nullable=False, comment='HTTP 方法'),
sa.Column('ip_address', sa.String(length=50), nullable=True, comment='IP 地址'),
sa.Column('user_agent', sa.Text(), nullable=True, comment='User Agent'),
sa.Column('status_code', sa.Integer(), nullable=True, comment='响应状态码'),
sa.Column('response_time', sa.Integer(), nullable=True, comment='响应时间(毫秒)'),
sa.Column('tokens_used', sa.Integer(), nullable=True, comment='使用的 Token 数'),
sa.Column('created_at', sa.DateTime(), nullable=False, comment='创建时间'),
sa.ForeignKeyConstraint(['api_key_id'], ['api_keys.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_api_key_logs_api_key_id'), 'api_key_logs', ['api_key_id'], unique=False)
op.create_index(op.f('ix_api_key_logs_created_at'), 'api_key_logs', ['created_at'], unique=False)
op.create_index(op.f('ix_api_key_logs_id'), 'api_key_logs', ['id'], unique=False)
op.create_table('knowledge_shares',
sa.Column('id', sa.UUID(), nullable=False),
sa.Column('source_kb_id', sa.UUID(), nullable=False, comment='source knowledges.id'),
sa.Column('source_workspace_id', sa.UUID(), nullable=False, comment='source workspaces.id'),
sa.Column('target_kb_id', sa.UUID(), nullable=False, comment='target knowledges.id'),
sa.Column('target_workspace_id', sa.UUID(), nullable=False, comment='target workspaces.id'),
sa.Column('shared_by', sa.UUID(), nullable=False, comment='shared users.id'),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['shared_by'], ['users.id'], ),
sa.ForeignKeyConstraint(['target_kb_id'], ['knowledges.id'], ),
sa.ForeignKeyConstraint(['target_workspace_id'], ['workspaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_knowledge_shares_id'), 'knowledge_shares', ['id'], unique=False)
# ### end Alembic commands ###
# 创建或更新统计信息的函数
op.execute("""
CREATE OR REPLACE FUNCTION update_knowledge_stats()
RETURNS TRIGGER AS $$
BEGIN
-- 处理 documents 表的插入、更新或删除
IF TG_TABLE_NAME = 'documents' THEN
-- 更新 knowledges 表的 doc_num
UPDATE knowledges
SET doc_num = (
SELECT COUNT(*)
FROM documents
WHERE kb_id = knowledges.id AND status = 1
)
WHERE id = NEW.kb_id OR id = OLD.kb_id;
-- 更新 knowledges 表的 chunk_num
UPDATE knowledges
SET chunk_num = (
SELECT COALESCE(SUM(chunk_num), 0)
FROM documents
WHERE kb_id = knowledges.id AND status = 1
)
WHERE id = NEW.kb_id OR id = OLD.kb_id;
-- 通过 knowledge_shares 表同步统计信息
-- 1. 使用 source_kb_id 的 doc_num 更新 target_kb_id 的 doc_num
UPDATE knowledges AS target
SET doc_num = source.doc_num
FROM knowledge_shares ks
JOIN knowledges AS source ON source.id = ks.source_kb_id
WHERE ks.target_kb_id = target.id
AND (source.id = NEW.kb_id OR source.id = OLD.kb_id);
-- 2. 使用 source_kb_id 的 chunk_num 更新 target_kb_id 的 chunk_num
UPDATE knowledges AS target
SET chunk_num = source.chunk_num
FROM knowledge_shares ks
JOIN knowledges AS source ON source.id = ks.source_kb_id
WHERE ks.target_kb_id = target.id
AND (source.id = NEW.kb_id OR source.id = OLD.kb_id);
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
""")
# 创建 documents 表上的触发器
op.execute("""
CREATE TRIGGER tr_documents_update_stats
AFTER INSERT OR UPDATE OR DELETE ON documents
FOR EACH ROW
EXECUTE FUNCTION update_knowledge_stats();
""")
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_knowledge_shares_id'), table_name='knowledge_shares')
op.drop_table('knowledge_shares')
op.drop_index(op.f('ix_api_key_logs_id'), table_name='api_key_logs')
op.drop_index(op.f('ix_api_key_logs_created_at'), table_name='api_key_logs')
op.drop_index(op.f('ix_api_key_logs_api_key_id'), table_name='api_key_logs')
op.drop_table('api_key_logs')
op.drop_index(op.f('ix_workspace_members_id'), table_name='workspace_members')
op.drop_table('workspace_members')
op.drop_index(op.f('ix_workspace_invites_token_hash'), table_name='workspace_invites')
op.drop_index(op.f('ix_workspace_invites_id'), table_name='workspace_invites')
op.drop_index(op.f('ix_workspace_invites_email'), table_name='workspace_invites')
op.drop_table('workspace_invites')
op.drop_index(op.f('ix_release_shares_share_token'), table_name='release_shares')
op.drop_index(op.f('ix_release_shares_release_id'), table_name='release_shares')
op.drop_index(op.f('ix_release_shares_id'), table_name='release_shares')
op.drop_index(op.f('ix_release_shares_app_id'), table_name='release_shares')
op.drop_table('release_shares')
op.drop_index(op.f('ix_messages_id'), table_name='messages')
op.drop_table('messages')
op.drop_index(op.f('ix_knowledges_status'), table_name='knowledges')
op.drop_index(op.f('ix_knowledges_parser_id'), table_name='knowledges')
op.drop_index(op.f('ix_knowledges_name'), table_name='knowledges')
op.drop_index(op.f('ix_knowledges_id'), table_name='knowledges')
op.drop_table('knowledges')
op.drop_index(op.f('ix_app_shares_id'), table_name='app_shares')
op.drop_table('app_shares')
op.drop_index(op.f('ix_api_keys_workspace_id'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_type'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_resource_id'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_key_hash'), table_name='api_keys')
op.drop_index(op.f('ix_api_keys_id'), table_name='api_keys')
op.drop_table('api_keys')
op.drop_index(op.f('ix_users_username'), table_name='users')
op.drop_index(op.f('ix_users_id'), table_name='users')
op.drop_index(op.f('ix_users_email'), table_name='users')
op.drop_table('users')
op.drop_index(op.f('ix_multi_agent_configs_id'), table_name='multi_agent_configs')
op.drop_index(op.f('ix_multi_agent_configs_app_id'), table_name='multi_agent_configs')
op.drop_table('multi_agent_configs')
op.drop_index(op.f('ix_memory_increments_workspace_id'), table_name='memory_increments')
op.drop_index(op.f('ix_memory_increments_id'), table_name='memory_increments')
op.drop_table('memory_increments')
op.drop_index(op.f('ix_conversations_id'), table_name='conversations')
op.drop_table('conversations')
op.drop_index(op.f('ix_agent_invocations_status'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_started_at'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_parent_invocation_id'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_id'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_conversation_id'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_caller_agent_id'), table_name='agent_invocations')
op.drop_index(op.f('ix_agent_invocations_callee_agent_id'), table_name='agent_invocations')
op.drop_table('agent_invocations')
op.drop_index(op.f('ix_workspaces_name'), table_name='workspaces')
op.drop_index(op.f('ix_workspaces_id'), table_name='workspaces')
op.drop_table('workspaces')
op.drop_index(op.f('ix_model_api_keys_id'), table_name='model_api_keys')
op.drop_table('model_api_keys')
op.drop_index(op.f('ix_end_users_id'), table_name='end_users')
op.drop_table('end_users')
op.drop_index(op.f('ix_app_releases_version'), table_name='app_releases')
op.drop_index(op.f('ix_app_releases_id'), table_name='app_releases')
op.drop_index(op.f('ix_app_releases_default_model_config_id'), table_name='app_releases')
op.drop_index(op.f('ix_app_releases_app_id'), table_name='app_releases')
op.drop_table('app_releases')
op.drop_index(op.f('ix_agent_configs_id'), table_name='agent_configs')
op.drop_index(op.f('ix_agent_configs_default_model_config_id'), table_name='agent_configs')
op.drop_index(op.f('ix_agent_configs_app_id'), table_name='agent_configs')
op.drop_table('agent_configs')
op.drop_index(op.f('ix_tenants_name'), table_name='tenants')
op.drop_index(op.f('ix_tenants_id'), table_name='tenants')
op.drop_table('tenants')
op.drop_index(op.f('ix_retrieval_info_id'), table_name='retrieval_info')
op.drop_table('retrieval_info')
op.drop_index(op.f('ix_model_configs_type'), table_name='model_configs')
op.drop_index(op.f('ix_model_configs_id'), table_name='model_configs')
op.drop_table('model_configs')
op.drop_index(op.f('ix_generic_files_tenant_id'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_status'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_id'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_file_ext'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_created_by'), table_name='generic_files')
op.drop_index(op.f('ix_generic_files_context'), table_name='generic_files')
op.drop_index('idx_tenant_status', table_name='generic_files')
op.drop_index('idx_tenant_context', table_name='generic_files')
op.drop_index('idx_created_at', table_name='generic_files')
op.drop_table('generic_files')
op.drop_index(op.f('ix_files_id'), table_name='files')
op.drop_index(op.f('ix_files_file_name'), table_name='files')
op.drop_index(op.f('ix_files_file_ext'), table_name='files')
op.drop_table('files')
op.drop_index(op.f('ix_documents_parser_id'), table_name='documents')
op.drop_index(op.f('ix_documents_id'), table_name='documents')
op.drop_index(op.f('ix_documents_file_name'), table_name='documents')
op.drop_index(op.f('ix_documents_file_ext'), table_name='documents')
op.drop_table('documents')
op.drop_table('data_config')
op.drop_index(op.f('ix_apps_type'), table_name='apps')
op.drop_index(op.f('ix_apps_name'), table_name='apps')
op.drop_index(op.f('ix_apps_id'), table_name='apps')
op.drop_index(op.f('ix_apps_current_release_id'), table_name='apps')
op.drop_table('apps')
# ### end Alembic commands ###
# 删除触发器
op.execute("DROP TRIGGER IF EXISTS tr_documents_update_stats ON documents;")
# 删除函数
op.execute("DROP FUNCTION IF EXISTS update_knowledge_stats();")

View File

@@ -1,85 +0,0 @@
"""20251117114804
Revision ID: fda52b5e7c38
Revises: 2a82ee37b707
Create Date: 2025-11-17 11:48:05.399402
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'fda52b5e7c38'
down_revision: Union[str, None] = '2a82ee37b707'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 使用 IF EXISTS 避免表不存在时报错
op.execute('DROP TABLE IF EXISTS mappings_copy1')
op.execute('DROP TABLE IF EXISTS mappings')
op.add_column('agent_configs', sa.Column('knowledge_retrieval', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='知识库检索配置'))
op.add_column('agent_configs', sa.Column('memory', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='记忆配置'))
op.add_column('agent_configs', sa.Column('variables', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='变量配置'))
op.add_column('agent_configs', sa.Column('tools', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='工具配置'))
op.alter_column('agent_configs', 'system_prompt',
existing_type=sa.TEXT(),
comment='系统提示词',
existing_nullable=True)
op.alter_column('agent_configs', 'default_model_config_id',
existing_type=sa.UUID(),
comment='默认模型配置ID',
existing_nullable=True)
op.drop_column('agent_configs', 'conversation_prefs')
op.drop_column('agent_configs', 'memory_strategy')
op.drop_column('agent_configs', 'tools_config')
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('agent_configs', sa.Column('tools_config', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True))
op.add_column('agent_configs', sa.Column('memory_strategy', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('agent_configs', sa.Column('conversation_prefs', postgresql.JSON(astext_type=sa.Text()), autoincrement=False, nullable=True))
op.alter_column('agent_configs', 'default_model_config_id',
existing_type=sa.UUID(),
comment=None,
existing_comment='默认模型配置ID',
existing_nullable=True)
op.alter_column('agent_configs', 'system_prompt',
existing_type=sa.TEXT(),
comment=None,
existing_comment='系统提示词',
existing_nullable=True)
op.drop_column('agent_configs', 'tools')
op.drop_column('agent_configs', 'variables')
op.drop_column('agent_configs', 'memory')
op.drop_column('agent_configs', 'knowledge_retrieval')
op.create_table('mappings',
sa.Column('app_id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('host_id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('other_id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('other_name', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('other_address', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], name=op.f('app_id')),
sa.PrimaryKeyConstraint('app_id', 'host_id', 'other_id', name=op.f('mapping_pkey'))
)
op.create_table('mappings_copy1',
sa.Column('app_id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('host_id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('other_id', sa.UUID(), autoincrement=False, nullable=False),
sa.Column('other_name', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('other_address', sa.VARCHAR(length=255), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(precision=6), server_default=sa.text('CURRENT_TIMESTAMP'), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['app_id'], ['apps.id'], name=op.f('mappings_copy1_app_id_fkey')),
sa.PrimaryKeyConstraint('app_id', 'host_id', 'other_id', name=op.f('mappings_copy1_pkey'))
)
# ### end Alembic commands ###

3891
api/uv.lock generated Normal file

File diff suppressed because it is too large Load Diff