feat: Add base project structure with API and web components

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

1
api/migrations/README Normal file
View File

@@ -0,0 +1 @@
Generic single-database configuration.

141
api/migrations/env.py Normal file
View File

@@ -0,0 +1,141 @@
import os
import sys
import importlib
import inspect
from logging.config import fileConfig
from dotenv import load_dotenv
load_dotenv() # Moved to top
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
# import app.models # <--- REMOVED THIS LINE
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Set the database URL from the environment variables
DB_USER = os.getenv("DB_USER")
DB_PASSWORD = os.getenv("DB_PASSWORD")
DB_HOST = os.getenv("DB_HOST")
DB_PORT = os.getenv("DB_PORT")
DB_NAME = os.getenv("DB_NAME")
DB_URL = f"postgresql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}:{DB_PORT}/{DB_NAME}"
config.set_main_option("sqlalchemy.url", DB_URL)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
from app.db import Base # <--- Keep this import for Base
target_metadata = Base.metadata
# target_metadata = None
# <--- NEW FUNCTION START ---
def import_all_models_from_package(package_name: str):
"""Dynamically imports all Python modules within a given package
to ensure SQLAlchemy models are registered with Base.metadata."""
# Add the project root to sys.path if not already there
# This is crucial for relative imports like 'app.db' to work
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
if project_root not in sys.path:
sys.path.insert(0, project_root)
try:
package = importlib.import_module(package_name)
package_dir = os.path.dirname(package.__file__)
except ImportError:
print(f"Warning: Could not import package {package_name}. Skipping model discovery.")
return
for root, _, files in os.walk(package_dir):
for file in files:
if file.endswith(".py") and file != "__init__.py":
module_path = os.path.join(root, file)
# Calculate relative path from package_dir to module_path
rel_path = os.path.relpath(module_path, package_dir)
# Convert file path to module name (e.g., 'user_model.py' -> 'user_model')
module_name = os.path.splitext(rel_path)[0].replace(os.sep, '.')
full_module_name = f"{package_name}.{module_name}"
try:
module = importlib.import_module(full_module_name)
# Optional: inspect module to ensure models inheriting from Base are loaded
# This step is mostly for verification; importing the module is usually enough
# for Base.metadata to pick up the models if they are defined correctly.
for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and issubclass(obj, Base) and obj != Base:
# Model found and registered with Base.metadata
pass
except Exception as e:
print(f"Warning: Could not import module {full_module_name}: {e}")
# Call the function to import all models
import_all_models_from_package('app.models') # <--- NEW CALL
# <--- NEW FUNCTION END ---
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -0,0 +1,26 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View File

@@ -0,0 +1,83 @@
"""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.drop_table('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

@@ -0,0 +1,30 @@
"""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

@@ -0,0 +1,92 @@
"""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.drop_table('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

@@ -0,0 +1,33 @@
"""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

@@ -0,0 +1,30 @@
"""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.add_column('data_config', sa.Column('llm', sa.String(), nullable=True, comment='LLM模型配置ID'))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('data_config', 'llm')
# ### end Alembic commands ###

View File

@@ -0,0 +1,30 @@
"""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

@@ -0,0 +1,48 @@
"""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.alter_column('data_config', 'llm_id',
existing_type=sa.VARCHAR(),
comment='LLM模型配置ID',
existing_nullable=True)
op.alter_column('data_config', 'embedding_id',
existing_type=sa.VARCHAR(),
comment='嵌入模型配置ID',
existing_nullable=True)
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

@@ -0,0 +1,30 @@
"""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

@@ -0,0 +1,30 @@
"""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

@@ -0,0 +1,26 @@
"""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,100 @@
"""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! ###
op.drop_table('data_config_copy1')
op.drop_table('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

@@ -0,0 +1,64 @@
"""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.drop_table('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

@@ -0,0 +1,96 @@
"""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

@@ -0,0 +1,30 @@
"""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

@@ -0,0 +1,439 @@
"""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("""
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

@@ -0,0 +1,86 @@
"""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("""
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

@@ -0,0 +1,56 @@
"""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

@@ -0,0 +1,188 @@
"""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.drop_table('data_config')
# ### end Alembic commands ###

View File

@@ -0,0 +1,44 @@
"""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.alter_column('data_config', 'llm_id',
existing_type=sa.VARCHAR(),
comment='LLM模型配置ID',
existing_comment='临时',
existing_nullable=True)
op.drop_column('data_config', 'llm')
# ### 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

@@ -0,0 +1,40 @@
"""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

@@ -0,0 +1,34 @@
"""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

@@ -0,0 +1,54 @@
"""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

@@ -0,0 +1,38 @@
"""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.alter_column('data_config', 'workspace_id',
existing_type=sa.UUID(),
comment='工作空间ID',
existing_comment='comment',
existing_nullable=True)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('data_config', 'workspace_id',
existing_type=sa.UUID(),
comment='comment',
existing_comment='工作空间ID',
existing_nullable=True)
# ### end Alembic commands ###

View File

@@ -0,0 +1,84 @@
"""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! ###
op.drop_table('mappings_copy1')
op.drop_table('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 ###