WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+230
View File
@@ -0,0 +1,230 @@
DROP TABLE IF EXISTS tenants;
DROP TABLE IF EXISTS models;
DROP TABLE IF EXISTS knowledge_bases;
DROP TABLE IF EXISTS knowledges;
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS chunks;
CREATE TABLE tenants (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
retriever_engines JSON NOT NULL,
status VARCHAR(50) DEFAULT 'active',
business VARCHAR(255) NOT NULL,
storage_quota BIGINT NOT NULL DEFAULT 10737418240,
storage_used BIGINT NOT NULL DEFAULT 0,
agent_config JSON DEFAULT NULL COMMENT 'Tenant-level agent configuration in JSON format',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=10000;
CREATE TABLE models (
id VARCHAR(64) PRIMARY KEY,
tenant_id INT NOT NULL,
name VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL DEFAULT '',
type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
description TEXT,
parameters JSON NOT NULL,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
status VARCHAR(50) NOT NULL DEFAULT 'active',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE INDEX idx_models_tenant_source_type ON models(tenant_id, source, type);
CREATE TABLE knowledge_bases (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
tenant_id INT NOT NULL,
chunking_config JSON NOT NULL,
image_processing_config JSON NOT NULL,
embedding_model_id VARCHAR(64) NOT NULL,
summary_model_id VARCHAR(64) NOT NULL,
rerank_model_id VARCHAR(64) NOT NULL,
cos_config JSON NOT NULL,
vlm_config JSON NOT NULL,
extract_config JSON NULL,
auto_tag_config JSON NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE INDEX idx_knowledge_bases_tenant_name ON knowledge_bases(tenant_id, name);
CREATE TABLE knowledges (
id VARCHAR(36) PRIMARY KEY,
tenant_id INT NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
type VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
source VARCHAR(2048) NOT NULL,
parse_status VARCHAR(50) NOT NULL DEFAULT 'unprocessed',
enable_status VARCHAR(50) NOT NULL DEFAULT 'enabled',
embedding_model_id VARCHAR(64),
file_name VARCHAR(255),
file_type VARCHAR(50),
file_size BIGINT,
file_path TEXT,
file_hash VARCHAR(64),
storage_size BIGINT NOT NULL DEFAULT 0,
metadata JSON,
custom_metadata JSON NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
processed_at TIMESTAMP,
error_message TEXT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE INDEX idx_knowledges_tenant_id ON knowledges(tenant_id, knowledge_base_id);
CREATE TABLE sessions (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
title VARCHAR(255),
description TEXT,
knowledge_base_id VARCHAR(36),
max_rounds INT NOT NULL DEFAULT 5,
enable_rewrite BOOLEAN NOT NULL DEFAULT TRUE,
fallback_strategy VARCHAR(255) NOT NULL DEFAULT 'fixed',
fallback_response VARCHAR(255) NOT NULL DEFAULT '很抱歉,我暂时无法回答这个问题。',
keyword_threshold FLOAT NOT NULL DEFAULT 0.5,
vector_threshold FLOAT NOT NULL DEFAULT 0.5,
rerank_model_id VARCHAR(64),
embedding_top_k INTEGER NOT NULL DEFAULT 10,
rerank_top_k INTEGER NOT NULL DEFAULT 10,
rerank_threshold FLOAT NOT NULL DEFAULT 0.65,
summary_model_id VARCHAR(64),
summary_parameters JSON NOT NULL,
agent_config JSON DEFAULT NULL COMMENT 'Session-level agent configuration in JSON format',
context_config JSON DEFAULT NULL COMMENT 'LLM context management configuration (separate from message storage)',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE INDEX idx_sessions_tenant_id ON sessions(tenant_id);
CREATE TABLE messages (
id VARCHAR(36) PRIMARY KEY,
request_id VARCHAR(36) NOT NULL,
session_id VARCHAR(36) NOT NULL,
role VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
knowledge_references JSON NOT NULL,
agent_steps JSON DEFAULT NULL COMMENT 'Agent execution steps (reasoning process and tool calls)',
is_completed BOOLEAN NOT NULL DEFAULT FALSE,
agent_id VARCHAR(36) NOT NULL DEFAULT '',
agent_tenant_id INTEGER NOT NULL DEFAULT 0,
model_id VARCHAR(64) NOT NULL DEFAULT '',
execution_context JSON NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE INDEX idx_messages_session_role ON messages(session_id, role);
CREATE INDEX idx_messages_agent_id ON messages(agent_id);
CREATE TABLE message_suggestion_sets (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
session_id VARCHAR(36) NOT NULL,
assistant_message_id VARCHAR(36) NOT NULL,
agent_id VARCHAR(36) NOT NULL DEFAULT '',
agent_tenant_id INTEGER NOT NULL DEFAULT 0,
placement VARCHAR(32) NOT NULL,
config_hash VARCHAR(64) NOT NULL,
locale VARCHAR(16) NOT NULL DEFAULT '',
status VARCHAR(16) NOT NULL,
allow_regenerate BOOLEAN NOT NULL DEFAULT FALSE,
suppression_reason VARCHAR(64) NOT NULL DEFAULT '',
questions JSON NOT NULL,
model_id VARCHAR(64) NOT NULL DEFAULT '',
prompt_tokens INTEGER NOT NULL DEFAULT 0,
completion_tokens INTEGER NOT NULL DEFAULT 0,
latency_ms BIGINT NOT NULL DEFAULT 0,
error_code VARCHAR(64) NOT NULL DEFAULT '',
lease_until TIMESTAMP NULL DEFAULT NULL,
generated_at TIMESTAMP NULL DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY idx_message_suggestion_sets_cache_key
(tenant_id, assistant_message_id, placement, config_hash, locale),
KEY idx_message_suggestion_sets_session (tenant_id, session_id, created_at),
KEY idx_message_suggestion_sets_status (status, lease_until)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE message_suggestion_events (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
tenant_id INTEGER NOT NULL,
session_id VARCHAR(36) NOT NULL,
suggestion_set_id VARCHAR(36) NOT NULL,
question_id VARCHAR(64) NOT NULL DEFAULT '',
event_type VARCHAR(32) NOT NULL,
actor_id VARCHAR(512) NOT NULL DEFAULT '',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_message_suggestion_events_set (suggestion_set_id, created_at),
KEY idx_message_suggestion_events_session (tenant_id, session_id, created_at),
KEY idx_message_suggestion_events_type (event_type, created_at),
CONSTRAINT fk_message_suggestion_events_set
FOREIGN KEY (suggestion_set_id) REFERENCES message_suggestion_sets(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE chunks (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL,
content TEXT NOT NULL,
source_content TEXT NOT NULL,
content_revision INT NOT NULL DEFAULT 0,
index_status VARCHAR(16) NOT NULL DEFAULT 'ready',
last_editor_id VARCHAR(64) NOT NULL DEFAULT '',
context_header TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
start_at INTEGER NOT NULL,
end_at INTEGER NOT NULL,
pre_chunk_id VARCHAR(36),
next_chunk_id VARCHAR(36),
chunk_type VARCHAR(20) NOT NULL DEFAULT 'text',
parent_chunk_id VARCHAR(36),
image_info TEXT,
relation_chunks JSON,
indirect_relation_chunks JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE INDEX idx_chunks_tenant_knowledge ON chunks(tenant_id, knowledge_id);
CREATE INDEX idx_chunks_parent_id ON chunks(parent_chunk_id);
CREATE INDEX idx_chunks_chunk_type ON chunks(chunk_type);
CREATE TABLE chunk_revisions (
id VARCHAR(36) PRIMARY KEY,
tenant_id BIGINT NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL,
chunk_id VARCHAR(36) NOT NULL,
revision INT NOT NULL,
content TEXT NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
editor_id VARCHAR(64) NOT NULL DEFAULT '',
edit_source VARCHAR(16) NOT NULL DEFAULT 'user',
edited_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY idx_chunk_revisions_chunk_revision (chunk_id, revision),
KEY idx_chunk_revisions_tenant_chunk (tenant_id, chunk_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1 @@
DROP TABLE IF EXISTS wiki_ingest_checkpoints;
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS wiki_ingest_checkpoints (
tenant_id BIGINT UNSIGNED NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL,
fingerprint VARCHAR(64) NOT NULL,
stage VARCHAR(32) NOT NULL,
item_key VARCHAR(64) NOT NULL,
payload LONGTEXT NOT NULL,
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (tenant_id, knowledge_base_id, knowledge_id, fingerprint, stage, item_key)
);
@@ -0,0 +1 @@
DROP TABLE IF EXISTS wiki_recovery_budgets;
@@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS wiki_recovery_budgets (
tenant_id BIGINT NOT NULL,
dead_letter_id BIGINT NOT NULL,
attempt_number INTEGER NOT NULL,
pending_op_id BIGINT NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL,
model_id VARCHAR(36) NOT NULL,
embedding_model_id VARCHAR(36) NOT NULL DEFAULT '',
source_fingerprint VARCHAR(64) NOT NULL,
model_fingerprint VARCHAR(64) NOT NULL,
reviewed_release VARCHAR(255) NOT NULL,
reviewed_api_image VARCHAR(255) NOT NULL,
max_provider_requests BIGINT NOT NULL CHECK (max_provider_requests > 0),
reserved_provider_requests BIGINT NOT NULL DEFAULT 0 CHECK (reserved_provider_requests >= 0 AND reserved_provider_requests <= max_provider_requests),
deadline_at DATETIME(6) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'active',
stop_reason VARCHAR(128) NOT NULL DEFAULT '',
last_phase VARCHAR(64) NOT NULL DEFAULT '',
paused_payload TEXT NOT NULL,
verified_progress TEXT NOT NULL,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (tenant_id, dead_letter_id, attempt_number, pending_op_id),
UNIQUE (tenant_id, pending_op_id)
);
@@ -0,0 +1,12 @@
SET @access_role_permission_version_sql = (
SELECT IF(COUNT(*) > 0,
'ALTER TABLE workbench_access_roles DROP COLUMN permission_version',
'SELECT 1')
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'workbench_access_roles'
AND COLUMN_NAME = 'permission_version'
);
PREPARE access_role_permission_version_stmt FROM @access_role_permission_version_sql;
EXECUTE access_role_permission_version_stmt;
DEALLOCATE PREPARE access_role_permission_version_stmt;
@@ -0,0 +1,25 @@
-- The MySQL baseline predates the access-role table.
CREATE TABLE IF NOT EXISTS workbench_access_roles (
id VARCHAR(36) PRIMARY KEY,
tenant_id BIGINT NOT NULL,
name VARCHAR(100) NOT NULL,
description TEXT NOT NULL,
permissions JSON NOT NULL,
version INTEGER NOT NULL DEFAULT 1,
created_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (tenant_id, name)
);
-- Preserve existing granular roles when applying after an emergency patch.
SET @access_role_permission_version_sql = (
SELECT IF(COUNT(*) = 0,
'ALTER TABLE workbench_access_roles ADD COLUMN permission_version INTEGER NOT NULL DEFAULT 0',
'SELECT 1')
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'workbench_access_roles'
AND COLUMN_NAME = 'permission_version'
);
PREPARE access_role_permission_version_stmt FROM @access_role_permission_version_sql;
EXECUTE access_role_permission_version_stmt;
DEALLOCATE PREPARE access_role_permission_version_stmt;
+215
View File
@@ -0,0 +1,215 @@
-- Create extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pg_search;
-- Create tenant table
CREATE TABLE IF NOT EXISTS tenants (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
retriever_engines JSONB NOT NULL DEFAULT '[]',
status VARCHAR(50) DEFAULT 'active',
business VARCHAR(255) NOT NULL,
storage_quota BIGINT NOT NULL DEFAULT 10737418240, -- 默认10GB配额(Bytes)
storage_used BIGINT NOT NULL DEFAULT 0, -- 已使用的存储空间(Bytes)
agent_config JSONB DEFAULT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
COMMENT ON COLUMN tenants.agent_config IS 'Tenant-level agent configuration in JSON format';
-- Set the starting value for tenants id sequence
ALTER SEQUENCE tenants_id_seq RESTART WITH 10000;
-- Add indexes
CREATE INDEX IF NOT EXISTS idx_tenants_status ON tenants(status);
-- Create model table
CREATE TABLE IF NOT EXISTS models (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL DEFAULT '',
type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
description TEXT,
parameters JSONB NOT NULL,
is_default BOOLEAN NOT NULL DEFAULT false,
status VARCHAR(50) NOT NULL DEFAULT 'active',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Add indexes for models
CREATE INDEX IF NOT EXISTS idx_models_type ON models(type);
CREATE INDEX IF NOT EXISTS idx_models_source ON models(source);
-- Create knowledge_base table
CREATE TABLE IF NOT EXISTS knowledge_bases (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
description TEXT,
tenant_id INTEGER NOT NULL,
chunking_config JSONB NOT NULL DEFAULT '{"chunk_size": 512, "chunk_overlap": 50, "split_markers": ["\n\n", "\n", "。"], "keep_separator": true}',
image_processing_config JSONB NOT NULL DEFAULT '{"enable_multimodal": false, "model_id": ""}',
embedding_model_id VARCHAR(64) NOT NULL,
summary_model_id VARCHAR(64) NOT NULL,
rerank_model_id VARCHAR(64) NOT NULL,
cos_config JSONB NOT NULL DEFAULT '{}',
vlm_config JSONB NOT NULL DEFAULT '{}',
extract_config JSONB NULL DEFAULT NULL,
auto_tag_config JSONB NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Add indexes for knowledge_bases
CREATE INDEX IF NOT EXISTS idx_knowledge_bases_tenant_id ON knowledge_bases(tenant_id);
-- Create knowledge table
CREATE TABLE IF NOT EXISTS knowledges (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
type VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
source VARCHAR(2048) NOT NULL,
parse_status VARCHAR(50) NOT NULL DEFAULT 'unprocessed',
enable_status VARCHAR(50) NOT NULL DEFAULT 'enabled',
embedding_model_id VARCHAR(64),
file_name VARCHAR(255),
file_type VARCHAR(50),
file_size BIGINT,
file_path TEXT,
file_hash VARCHAR(64),
storage_size BIGINT NOT NULL DEFAULT 0, -- 存储大小(Byte)
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP WITH TIME ZONE,
error_message TEXT,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Add indexes for knowledge
CREATE INDEX IF NOT EXISTS idx_knowledges_tenant_id ON knowledges(tenant_id);
CREATE INDEX IF NOT EXISTS idx_knowledges_base_id ON knowledges(knowledge_base_id);
CREATE INDEX IF NOT EXISTS idx_knowledges_parse_status ON knowledges(parse_status);
CREATE INDEX IF NOT EXISTS idx_knowledges_enable_status ON knowledges(enable_status);
-- Create session table
CREATE TABLE IF NOT EXISTS sessions (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
title VARCHAR(255),
description TEXT,
knowledge_base_id VARCHAR(36),
max_rounds INTEGER NOT NULL DEFAULT 5,
enable_rewrite BOOLEAN NOT NULL DEFAULT true,
fallback_strategy VARCHAR(255) NOT NULL DEFAULT 'fixed',
fallback_response TEXT NOT NULL DEFAULT '很抱歉,我暂时无法回答这个问题。',
keyword_threshold FLOAT NOT NULL DEFAULT 0.5,
vector_threshold FLOAT NOT NULL DEFAULT 0.5,
rerank_model_id VARCHAR(64),
embedding_top_k INTEGER NOT NULL DEFAULT 10,
rerank_top_k INTEGER NOT NULL DEFAULT 10,
rerank_threshold FLOAT NOT NULL DEFAULT 0.65,
summary_model_id VARCHAR(64),
summary_parameters JSONB NOT NULL DEFAULT '{}',
agent_config JSONB DEFAULT NULL,
context_config JSONB DEFAULT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
COMMENT ON COLUMN sessions.agent_config IS 'Session-level agent configuration in JSON format';
COMMENT ON COLUMN sessions.context_config IS 'LLM context management configuration (separate from message storage)';
-- Create Index for sessions
CREATE INDEX IF NOT EXISTS idx_sessions_tenant_id ON sessions(tenant_id);
-- Create message table
CREATE TABLE IF NOT EXISTS messages (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
request_id VARCHAR(36) NOT NULL,
session_id VARCHAR(36) NOT NULL,
role VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
knowledge_references JSONB NOT NULL DEFAULT '[]',
agent_steps JSONB DEFAULT NULL,
is_completed BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
COMMENT ON COLUMN messages.agent_steps IS 'Agent execution steps (reasoning process and tool calls)';
-- Create Index for messages
CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);
CREATE TABLE IF NOT EXISTS chunks (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL,
content TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT true,
start_at INTEGER NOT NULL,
end_at INTEGER NOT NULL,
pre_chunk_id VARCHAR(36),
next_chunk_id VARCHAR(36),
chunk_type VARCHAR(20) NOT NULL DEFAULT 'text',
parent_chunk_id VARCHAR(36),
image_info TEXT,
relation_chunks JSONB,
indirect_relation_chunks JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX IF NOT EXISTS idx_chunks_tenant_kg ON chunks(tenant_id, knowledge_id);
CREATE INDEX IF NOT EXISTS idx_chunks_parent_id ON chunks(parent_chunk_id);
CREATE INDEX IF NOT EXISTS idx_chunks_chunk_type ON chunks(chunk_type);
CREATE TABLE IF NOT EXISTS embeddings (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
source_id VARCHAR(64) NOT NULL,
source_type INTEGER NOT NULL,
chunk_id VARCHAR(64),
knowledge_id VARCHAR(64),
knowledge_base_id VARCHAR(64),
content TEXT,
dimension INTEGER NOT NULL,
embedding halfvec
);
CREATE UNIQUE INDEX IF NOT EXISTS embeddings_unique_source ON embeddings(source_id, source_type);
CREATE INDEX IF NOT EXISTS embeddings_search_idx ON embeddings
USING bm25 (id, knowledge_base_id, content, knowledge_id, chunk_id)
WITH (
key_field = 'id',
text_fields = '{
"content": {
"tokenizer": {"type": "chinese_lindera"}
}
}'
);
CREATE INDEX ON embeddings USING hnsw ((embedding::halfvec(3584)) halfvec_cosine_ops) WITH (m = 16, ef_construction = 64) WHERE (dimension = 3584);
CREATE INDEX ON embeddings USING hnsw ((embedding::halfvec(798)) halfvec_cosine_ops) WITH (m = 16, ef_construction = 64) WHERE (dimension = 798);
@@ -0,0 +1,69 @@
-- 迁移脚本:从PostgreSQL迁移到ParadeDB
-- 注意:在执行此脚本前,请确保已经备份了数据
-- 1. 导出数据(在PostgreSQL中执行)
-- pg_dump -U postgres -h localhost -p 5432 -d your_database > backup.sql
-- 2. 导入数据(在ParadeDB中执行)
-- psql -U postgres -h localhost -p 5432 -d your_database < backup.sql
-- 3. 验证数据
-- Insert some sample data
-- INSERT INTO tenants (id, name, description, status, api_key)
-- VALUES
-- (1, 'Demo Tenant', 'This is a demo tenant for testing', 'active', 'sk-00000001abcdefg123456')
-- ON CONFLICT DO NOTHING;
-- SELECT setval('tenants_id_seq', (SELECT MAX(id) FROM tenants));
-- -- Create knowledge base
-- INSERT INTO knowledge_bases (id, name, description, tenant_id, chunking_config, image_processing_config, embedding_model_id)
-- VALUES
-- ('kb-00000001', 'Default Knowledge Base', 'Default knowledge base for testing', 1, '{"chunk_size": 512, "chunk_overlap": 50, "separators": ["\n\n", "\n", "。"], "keep_separator": true}', '{"enable_multimodal": false, "model_id": ""}', 'model-embedding-00000001'),
-- ('kb-00000002', 'Test Knowledge Base', 'Test knowledge base for development', 1, '{"chunk_size": 512, "chunk_overlap": 50, "separators": ["\n\n", "\n", "。"], "keep_separator": true}', '{"enable_multimodal": false, "model_id": ""}', 'model-embedding-00000001'),
-- ('kb-00000003', 'Test Knowledge Base 2', 'Test knowledge base for development 2', 1, '{"chunk_size": 512, "chunk_overlap": 50, "separators": ["\n\n", "\n", "。"], "keep_separator": true}', '{"enable_multimodal": false, "model_id": ""}', 'model-embedding-00000001')
-- ON CONFLICT DO NOTHING;
SELECT COUNT(*) FROM tenants;
SELECT COUNT(*) FROM models;
SELECT COUNT(*) FROM knowledge_bases;
SELECT COUNT(*) FROM knowledges;
-- 测试中文全文搜索
-- 创建文档表
CREATE TABLE chinese_documents (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT,
published_date DATE
);
-- 在表上创建 BM25 索引,使用结巴分词器支持中文
CREATE INDEX idx_documents_bm25 ON chinese_documents
USING bm25 (id, content)
WITH (
key_field = 'id',
text_fields = '{
"content": {
"tokenizer": {"type": "chinese_lindera"}
}
}'
);
INSERT INTO chinese_documents (title, content, published_date)
VALUES
('人工智能的发展', '人工智能技术正在快速发展,影响了我们生活的方方面面。大语言模型是最近的一个重要突破。', '2023-01-15'),
('机器学习基础', '机器学习是人工智能的一个重要分支,包括监督学习、无监督学习和强化学习等方法。', '2023-02-20'),
('深度学习应用', '深度学习在图像识别、自然语言处理和语音识别等领域有广泛应用。', '2023-03-10'),
('自然语言处理技术', '自然语言处理允许计算机理解、解释和生成人类语言,是人工智能的核心技术之一。', '2023-04-05'),
('计算机视觉入门', '计算机视觉让机器能够"看到"并理解视觉世界,广泛应用于安防、医疗等领域。', '2023-05-12');
INSERT INTO chinese_documents (title, content, published_date)
VALUES
('hello world', 'hello world', '2023-05-12');
+34
View File
@@ -0,0 +1,34 @@
DROP TABLE IF EXISTS tenant_invitations;
DROP TABLE IF EXISTS tenant_api_keys;
DROP TABLE IF EXISTS wiki_page_revisions;
DROP TABLE IF EXISTS wiki_page_issues;
DROP TABLE IF EXISTS wiki_pages;
DROP TABLE IF EXISTS wiki_folders;
DROP TABLE IF EXISTS user_kb_pins;
DROP TABLE IF EXISTS user_resource_favorites;
DROP TABLE IF EXISTS tenant_disabled_shared_agents;
DROP TABLE IF EXISTS agent_shares;
DROP TABLE IF EXISTS organization_join_requests;
DROP TABLE IF EXISTS kb_shares;
DROP TABLE IF EXISTS organization_tenant_members;
DROP TABLE IF EXISTS organizations;
DROP TABLE IF EXISTS custom_agents;
DROP TABLE IF EXISTS mcp_tool_approvals;
DROP TABLE IF EXISTS mcp_services;
DROP TABLE IF EXISTS knowledge_tags;
DROP TABLE IF EXISTS auth_tokens;
DROP TABLE IF EXISTS audit_logs;
DROP TABLE IF EXISTS tenant_members;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS chunks;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS temporary_documents;
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS knowledges;
DROP TABLE IF EXISTS knowledge_bases;
DROP TABLE IF EXISTS storage_backends;
DROP TABLE IF EXISTS resource_access_grants;
DROP TABLE IF EXISTS resource_bindings;
DROP TABLE IF EXISTS resources;
DROP TABLE IF EXISTS models;
DROP TABLE IF EXISTS tenants;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS wiki_log_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
action VARCHAR(32) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL DEFAULT '',
doc_title TEXT NOT NULL DEFAULT '',
summary TEXT NOT NULL DEFAULT '',
pages_affected TEXT NOT NULL DEFAULT '[]',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_wiki_log_entries_kb_id_desc
ON wiki_log_entries (knowledge_base_id, id DESC);
CREATE INDEX IF NOT EXISTS idx_wiki_log_entries_tenant_id
ON wiki_log_entries (tenant_id);
@@ -0,0 +1,3 @@
DROP TABLE IF EXISTS wiki_log_entries;
DELETE FROM wiki_pages WHERE page_type = 'log';
@@ -0,0 +1,8 @@
-- Fold the relative directory back into file_name before dropping the column.
UPDATE knowledges
SET file_name = folder_path || '/' || file_name
WHERE folder_path <> '' AND file_name <> '';
DROP INDEX IF EXISTS idx_knowledges_folder_path;
ALTER TABLE knowledges DROP COLUMN folder_path;
@@ -0,0 +1,23 @@
-- Folder tree support for knowledge bases (SQLite / lite mode).
--
-- Mirrors migrations/versioned/000079_knowledge_folder_path.up.sql.
ALTER TABLE knowledges ADD COLUMN folder_path VARCHAR(1024) NOT NULL DEFAULT '';
-- Backfill legacy folder uploads, which encoded their relative directory in
-- file_name. SQLite has no regex, so the directory prefix is isolated with the
-- rtrim trick: trimming every non-slash character off the right leaves exactly
-- the prefix up to and including the last '/'.
UPDATE knowledges
SET folder_path = substr(
rtrim(file_name, replace(file_name, '/', '')),
1,
length(rtrim(file_name, replace(file_name, '/', ''))) - 1
),
file_name = substr(
file_name,
length(rtrim(file_name, replace(file_name, '/', ''))) + 1
)
WHERE file_name LIKE '%/%';
CREATE INDEX IF NOT EXISTS idx_knowledges_folder_path
ON knowledges (tenant_id, knowledge_base_id, folder_path);
@@ -0,0 +1 @@
ALTER TABLE knowledge_bases DROP COLUMN auto_tag_config;
@@ -0,0 +1 @@
ALTER TABLE knowledge_bases ADD COLUMN auto_tag_config TEXT;
+13
View File
@@ -0,0 +1,13 @@
DROP TABLE IF EXISTS memory_item_embeddings;
DROP INDEX IF EXISTS idx_mem_affinity_scope;
DROP TABLE IF EXISTS memory_doc_affinity;
DROP INDEX IF EXISTS idx_mem_topic_scope;
DROP TABLE IF EXISTS memory_topic_stats;
DROP INDEX IF EXISTS idx_memory_items_key;
DROP INDEX IF EXISTS idx_memory_items_scope;
DROP TABLE IF EXISTS memory_items;
DROP INDEX IF EXISTS idx_mem_tomb_fp;
DROP INDEX IF EXISTS idx_memory_tombstones_scope;
DROP TABLE IF EXISTS memory_tombstones;
DROP INDEX IF EXISTS idx_memory_subjects_scope;
DROP TABLE IF EXISTS memory_subjects;
+118
View File
@@ -0,0 +1,118 @@
-- Cross-session long-term memory (Lite). Mirrors migrations/versioned/000084.
-- Row ids are generated in Go, so there is no server-side default here.
CREATE TABLE IF NOT EXISTS memory_subjects (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
subject_id VARCHAR(512) NOT NULL,
enabled BOOLEAN NOT NULL DEFAULT 1,
block_text TEXT NOT NULL DEFAULT '',
block_updated_at DATETIME,
item_count INTEGER NOT NULL DEFAULT 0,
last_extracted_at DATETIME,
extract_cursor DATETIME,
pending_sessions TEXT,
extract_scheduled_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_subjects_scope
ON memory_subjects (tenant_id, subject_id);
CREATE TABLE IF NOT EXISTS memory_items (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
subject_id VARCHAR(512) NOT NULL,
kind VARCHAR(32) NOT NULL,
content TEXT NOT NULL,
topic VARCHAR(255) NOT NULL DEFAULT '',
normalized_key VARCHAR(255) NOT NULL DEFAULT '',
importance INTEGER NOT NULL DEFAULT 3,
origin VARCHAR(16) NOT NULL DEFAULT 'extracted',
status VARCHAR(16) NOT NULL DEFAULT 'active',
source_session_id VARCHAR(36),
source_message_id VARCHAR(36),
valid_from DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
invalid_at DATETIME,
expires_at DATETIME,
superseded_by VARCHAR(36),
last_used_at DATETIME,
use_count INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_memory_items_scope
ON memory_items (tenant_id, subject_id, status);
CREATE INDEX IF NOT EXISTS idx_memory_items_key
ON memory_items (tenant_id, subject_id, normalized_key);
CREATE TABLE IF NOT EXISTS memory_tombstones (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
subject_id VARCHAR(512) NOT NULL,
topic VARCHAR(255) NOT NULL DEFAULT '',
fingerprint VARCHAR(64) NOT NULL,
source_message_id VARCHAR(36),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_memory_tombstones_scope
ON memory_tombstones (tenant_id, subject_id);
CREATE UNIQUE INDEX IF NOT EXISTS idx_mem_tomb_fp
ON memory_tombstones (tenant_id, subject_id, fingerprint);
CREATE TABLE IF NOT EXISTS memory_topic_stats (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
subject_id VARCHAR(512) NOT NULL,
normalized_key VARCHAR(255) NOT NULL,
topic VARCHAR(255) NOT NULL DEFAULT '',
hits INTEGER NOT NULL DEFAULT 0,
last_seen_at DATETIME,
promoted_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_mem_topic_scope
ON memory_topic_stats (tenant_id, subject_id, normalized_key);
CREATE TABLE IF NOT EXISTS memory_doc_affinity (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
subject_id VARCHAR(512) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL DEFAULT '',
title VARCHAR(512) NOT NULL DEFAULT '',
hits INTEGER NOT NULL DEFAULT 0,
last_used_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_mem_affinity_scope
ON memory_doc_affinity (tenant_id, subject_id, knowledge_id);
ALTER TABLE tenants ADD COLUMN memory_config TEXT;
ALTER TABLE messages ADD COLUMN used_memories TEXT;
ALTER TABLE memory_subjects ADD COLUMN consolidated_at DATETIME;
ALTER TABLE memory_subjects ADD COLUMN forced_consolidated_at DATETIME;
ALTER TABLE memory_topic_stats ADD COLUMN aliases TEXT NOT NULL DEFAULT '[]';
CREATE TABLE IF NOT EXISTS memory_item_embeddings (
item_id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
subject_id VARCHAR(512) NOT NULL,
model_id VARCHAR(64) NOT NULL DEFAULT '',
dims INTEGER NOT NULL DEFAULT 0,
vector BLOB,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_mem_emb_scope
ON memory_item_embeddings (tenant_id, subject_id);
@@ -0,0 +1,15 @@
DROP INDEX IF EXISTS idx_tenant_invitations_token;
DROP INDEX IF EXISTS idx_tenant_invitations_unique_pending;
-- Legacy unique index cannot coexist with multiple share-link rows.
DELETE FROM tenant_invitations
WHERE invitee_user_id = ''
AND deleted_at IS NULL;
CREATE UNIQUE INDEX IF NOT EXISTS idx_tenant_invitations_unique_pending
ON tenant_invitations(tenant_id, invitee_user_id)
WHERE status = 'pending' AND deleted_at IS NULL;
ALTER TABLE tenant_invitations DROP COLUMN accepted_count;
ALTER TABLE tenant_invitations DROP COLUMN token;
ALTER TABLE messages DROP COLUMN attachments;
@@ -0,0 +1,21 @@
-- Mirrors versioned migrations:
-- 000034_add_attachments messages.attachments
-- 000054_invitation_tokens tenant_invitations.token / accepted_count
ALTER TABLE messages ADD COLUMN attachments TEXT NOT NULL DEFAULT '[]';
ALTER TABLE tenant_invitations ADD COLUMN token VARCHAR(64) NOT NULL DEFAULT '';
ALTER TABLE tenant_invitations ADD COLUMN accepted_count INTEGER NOT NULL DEFAULT 0;
-- Share-link rows use invitee_user_id=''; relax the pending uniqueness index
-- so multiple share links can coexist on one tenant (see versioned 000054).
DROP INDEX IF EXISTS idx_tenant_invitations_unique_pending;
CREATE UNIQUE INDEX IF NOT EXISTS idx_tenant_invitations_unique_pending
ON tenant_invitations(tenant_id, invitee_user_id)
WHERE status = 'pending'
AND deleted_at IS NULL
AND invitee_user_id <> '';
CREATE UNIQUE INDEX IF NOT EXISTS idx_tenant_invitations_token
ON tenant_invitations(token)
WHERE token <> '' AND deleted_at IS NULL;
@@ -0,0 +1,8 @@
DROP INDEX IF EXISTS idx_task_dead_letters_task_type;
DROP INDEX IF EXISTS idx_task_dead_letters_tenant;
DROP INDEX IF EXISTS idx_task_dead_letters_scope;
DROP INDEX IF EXISTS idx_task_pending_ops_tenant;
DROP INDEX IF EXISTS idx_task_pending_ops_scope;
DROP TABLE IF EXISTS task_dead_letters;
DROP TABLE IF EXISTS task_pending_ops;
@@ -0,0 +1,44 @@
-- Mirrors versioned migration 000041_task_queue_and_wiki_indexes:
-- durable pending-op queue and dead-letter archive for SQLite.
CREATE TABLE IF NOT EXISTS task_pending_ops (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
task_type VARCHAR(64) NOT NULL,
scope VARCHAR(32) NOT NULL,
scope_id VARCHAR(64) NOT NULL,
op VARCHAR(32) NOT NULL,
dedup_key VARCHAR(128) NOT NULL DEFAULT '',
payload TEXT NOT NULL DEFAULT '{}',
fail_count INTEGER NOT NULL DEFAULT 0,
enqueued_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
claimed_at DATETIME
);
CREATE INDEX IF NOT EXISTS idx_task_pending_ops_scope
ON task_pending_ops (task_type, scope, scope_id, id);
CREATE INDEX IF NOT EXISTS idx_task_pending_ops_tenant
ON task_pending_ops (tenant_id);
CREATE TABLE IF NOT EXISTS task_dead_letters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tenant_id INTEGER NOT NULL,
task_type VARCHAR(64) NOT NULL,
scope VARCHAR(32) NOT NULL,
scope_id VARCHAR(64) NOT NULL,
related_id VARCHAR(64) NOT NULL DEFAULT '',
payload TEXT NOT NULL,
last_error TEXT NOT NULL DEFAULT '',
fail_count INTEGER NOT NULL,
failed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_task_dead_letters_scope
ON task_dead_letters (scope, scope_id, failed_at DESC);
CREATE INDEX IF NOT EXISTS idx_task_dead_letters_tenant
ON task_dead_letters (tenant_id, failed_at DESC);
CREATE INDEX IF NOT EXISTS idx_task_dead_letters_task_type
ON task_dead_letters (task_type, failed_at DESC);
@@ -0,0 +1,4 @@
DROP INDEX IF EXISTS idx_system_settings_category;
DROP TABLE IF EXISTS system_settings;
DROP INDEX IF EXISTS idx_users_is_system_admin;
ALTER TABLE users DROP COLUMN is_system_admin;
@@ -0,0 +1,22 @@
-- Mirrors versioned migration 000053_system_admin_and_settings:
-- users.is_system_admin + system_settings table.
ALTER TABLE users ADD COLUMN is_system_admin BOOLEAN NOT NULL DEFAULT 0;
CREATE INDEX IF NOT EXISTS idx_users_is_system_admin ON users (is_system_admin);
CREATE TABLE IF NOT EXISTS system_settings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key VARCHAR(128) NOT NULL UNIQUE,
value TEXT NOT NULL,
value_type VARCHAR(16) NOT NULL,
category VARCHAR(32) NOT NULL,
description TEXT NOT NULL DEFAULT '',
is_secret BOOLEAN NOT NULL DEFAULT 0,
requires_restart BOOLEAN NOT NULL DEFAULT 0,
last_modified_by VARCHAR(36) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_system_settings_category
ON system_settings (category);
@@ -0,0 +1,6 @@
ALTER TABLE knowledges DROP COLUMN pending_subtasks_count;
DROP INDEX IF EXISTS idx_kpspan_parent;
DROP INDEX IF EXISTS idx_kpspan_status_started;
DROP INDEX IF EXISTS idx_kpspan_knowledge_attempt;
DROP TABLE IF EXISTS knowledge_processing_spans;
@@ -0,0 +1,39 @@
-- Mirrors versioned migrations:
-- 000055_knowledge_processing_spans knowledge_processing_spans table
-- 000056_knowledge_pending_subtasks knowledges.pending_subtasks_count
-- 000066_expand_knowledge_span_name name VARCHAR(255)
CREATE TABLE IF NOT EXISTS knowledge_processing_spans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
knowledge_id VARCHAR(64) NOT NULL,
attempt INTEGER NOT NULL DEFAULT 1,
span_id VARCHAR(64) NOT NULL,
parent_span_id VARCHAR(64),
name VARCHAR(255) NOT NULL,
kind VARCHAR(16) NOT NULL,
status VARCHAR(16) NOT NULL,
input TEXT,
output TEXT,
metadata TEXT,
error_code VARCHAR(64),
error_message TEXT,
error_detail TEXT,
started_at DATETIME,
finished_at DATETIME,
duration_ms INTEGER,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_kpspan_attempt_span UNIQUE (knowledge_id, attempt, span_id)
);
CREATE INDEX IF NOT EXISTS idx_kpspan_knowledge_attempt
ON knowledge_processing_spans (knowledge_id, attempt);
CREATE INDEX IF NOT EXISTS idx_kpspan_status_started
ON knowledge_processing_spans (status, started_at);
CREATE INDEX IF NOT EXISTS idx_kpspan_parent
ON knowledge_processing_spans (parent_span_id)
WHERE parent_span_id IS NOT NULL;
ALTER TABLE knowledges ADD COLUMN pending_subtasks_count INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1 @@
ALTER TABLE embed_channels DROP COLUMN allow_memory;
@@ -0,0 +1,3 @@
-- Mirrors versioned migration 000060_embed_channels: embed_channels.allow_memory.
ALTER TABLE embed_channels ADD COLUMN allow_memory BOOLEAN NOT NULL DEFAULT 0;
@@ -0,0 +1,18 @@
ALTER TABLE knowledges ADD COLUMN tag_id VARCHAR(36);
CREATE INDEX IF NOT EXISTS idx_knowledges_tag ON knowledges(tag_id);
UPDATE knowledges
SET tag_id = (
SELECT ktr.tag_id
FROM knowledge_tag_relations ktr
WHERE ktr.knowledge_id = knowledges.id
ORDER BY ktr.created_at ASC
LIMIT 1
)
WHERE EXISTS (
SELECT 1 FROM knowledge_tag_relations ktr WHERE ktr.knowledge_id = knowledges.id
);
DROP INDEX IF EXISTS idx_ktr_tag;
DROP INDEX IF EXISTS idx_ktr_knowledge;
DROP TABLE IF EXISTS knowledge_tag_relations;
@@ -0,0 +1,25 @@
-- Mirrors versioned migration 000063_knowledge_multi_tags:
-- many-to-many join table between knowledges and knowledge_tags.
CREATE TABLE IF NOT EXISTS knowledge_tag_relations (
knowledge_id VARCHAR(36) NOT NULL,
tag_id VARCHAR(36) NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (knowledge_id, tag_id)
);
CREATE INDEX IF NOT EXISTS idx_ktr_knowledge
ON knowledge_tag_relations (knowledge_id);
CREATE INDEX IF NOT EXISTS idx_ktr_tag
ON knowledge_tag_relations (tag_id);
-- Migrate existing single-tag data into the join table.
INSERT INTO knowledge_tag_relations (knowledge_id, tag_id, created_at)
SELECT id, tag_id, updated_at
FROM knowledges
WHERE tag_id IS NOT NULL AND tag_id != ''
AND deleted_at IS NULL;
DROP INDEX IF EXISTS idx_knowledges_tag;
ALTER TABLE knowledges DROP COLUMN tag_id;
@@ -0,0 +1,9 @@
DROP INDEX IF EXISTS idx_mcp_oauth_tokens_principal;
DROP INDEX IF EXISTS idx_mcp_oauth_tokens_tenant_principal_svc;
CREATE UNIQUE INDEX IF NOT EXISTS idx_mcp_oauth_tokens_tenant_user_svc
ON mcp_oauth_tokens(tenant_id, user_id, service_id);
ALTER TABLE mcp_oauth_tokens DROP COLUMN principal_id;
ALTER TABLE mcp_oauth_tokens DROP COLUMN principal_type;
ALTER TABLE tenants DROP COLUMN api_principal_config;
@@ -0,0 +1,22 @@
-- Mirrors versioned migration 000064_principal_model:
-- principal identity for MCP OAuth tokens and tenant API config.
ALTER TABLE tenants ADD COLUMN api_principal_config TEXT;
ALTER TABLE mcp_oauth_tokens ADD COLUMN principal_type VARCHAR(32);
ALTER TABLE mcp_oauth_tokens ADD COLUMN principal_id VARCHAR(512);
UPDATE mcp_oauth_tokens
SET principal_type = 'web_user',
principal_id = user_id
WHERE (principal_type IS NULL OR principal_type = '')
AND user_id IS NOT NULL
AND user_id <> '';
DROP INDEX IF EXISTS idx_mcp_oauth_tokens_tenant_user_svc;
CREATE UNIQUE INDEX IF NOT EXISTS idx_mcp_oauth_tokens_tenant_principal_svc
ON mcp_oauth_tokens(tenant_id, principal_type, principal_id, service_id);
CREATE INDEX IF NOT EXISTS idx_mcp_oauth_tokens_principal
ON mcp_oauth_tokens(principal_type, principal_id);
@@ -0,0 +1 @@
ALTER TABLE messages DROP COLUMN usage;
@@ -0,0 +1,4 @@
-- Mirrors versioned migration 000085_message_usage:
-- per-turn LLM token usage persisted with the assistant message.
ALTER TABLE messages ADD COLUMN usage TEXT;
@@ -0,0 +1,4 @@
DROP TABLE IF EXISTS workbench_events;
DROP TABLE IF EXISTS workbench_jobs;
DROP TABLE IF EXISTS workbench_revisions;
DROP TABLE IF EXISTS workbench_resources;
@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS workbench_resources (id TEXT PRIMARY KEY, tenant_id INTEGER NOT NULL, owner_id TEXT NOT NULL, owner_name TEXT NOT NULL DEFAULT '', kind TEXT NOT NULL, name TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'draft', version INTEGER NOT NULL DEFAULT 1, data TEXT NOT NULL DEFAULT '{}', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);
CREATE INDEX IF NOT EXISTS wb_resource_scope ON workbench_resources(tenant_id,kind,owner_id);
CREATE TABLE IF NOT EXISTS workbench_revisions (id TEXT PRIMARY KEY, tenant_id INTEGER NOT NULL, resource_id TEXT NOT NULL, version INTEGER NOT NULL, actor_id TEXT NOT NULL, data TEXT NOT NULL DEFAULT '{}', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(resource_id,version));
CREATE TABLE IF NOT EXISTS workbench_jobs (id TEXT PRIMARY KEY, tenant_id INTEGER NOT NULL, owner_id TEXT NOT NULL, resource_id TEXT NOT NULL, kind TEXT NOT NULL, action TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'queued', progress INTEGER NOT NULL DEFAULT 0, stage TEXT NOT NULL DEFAULT '', error TEXT NOT NULL DEFAULT '', request TEXT NOT NULL DEFAULT '{}', lease_token TEXT NOT NULL DEFAULT '', lease_until DATETIME, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);
CREATE INDEX IF NOT EXISTS wb_job_queue ON workbench_jobs(status,created_at);
CREATE TABLE IF NOT EXISTS workbench_events (id TEXT PRIMARY KEY, tenant_id INTEGER NOT NULL, owner_id TEXT NOT NULL, resource_id TEXT NOT NULL DEFAULT '', action TEXT NOT NULL, message TEXT NOT NULL DEFAULT '', level TEXT NOT NULL DEFAULT 'info', read INTEGER NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP);
CREATE INDEX IF NOT EXISTS wb_event_inbox ON workbench_events(tenant_id,owner_id,read,created_at);
@@ -0,0 +1 @@
ALTER TABLE workbench_jobs DROP COLUMN generation;
@@ -0,0 +1 @@
ALTER TABLE workbench_jobs ADD COLUMN generation TEXT NOT NULL DEFAULT '{}';
@@ -0,0 +1,7 @@
CREATE UNIQUE INDEX uq_tenant_skill_catalog_legacy_check ON tenant_skill_catalog(tenant_id, name) WHERE deleted_at IS NULL;
DROP INDEX uq_tenant_instruction_skill_owner_name;
DROP INDEX uq_tenant_skill_catalog_name;
CREATE UNIQUE INDEX uq_tenant_skill_catalog_name ON tenant_skill_catalog(tenant_id, name) WHERE deleted_at IS NULL;
DROP INDEX uq_tenant_skill_catalog_legacy_check;
ALTER TABLE tenant_skill_catalog DROP COLUMN instruction_only;
ALTER TABLE tenant_skill_catalog DROP COLUMN created_by;
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS tenant_skill_catalog (
id TEXT PRIMARY KEY, tenant_id INTEGER NOT NULL, name TEXT NOT NULL,
version TEXT, description TEXT, instructions TEXT, bundle_ref TEXT, bundle_sha256 TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, deleted_at DATETIME
);
ALTER TABLE tenant_skill_catalog ADD COLUMN instruction_only INTEGER NOT NULL DEFAULT 0;
ALTER TABLE tenant_skill_catalog ADD COLUMN created_by TEXT NOT NULL DEFAULT '';
DROP INDEX IF EXISTS uq_tenant_skill_catalog_name;
CREATE UNIQUE INDEX uq_tenant_skill_catalog_name ON tenant_skill_catalog(tenant_id, name) WHERE deleted_at IS NULL AND instruction_only = 0;
CREATE UNIQUE INDEX uq_tenant_instruction_skill_owner_name ON tenant_skill_catalog(tenant_id, created_by, name) WHERE deleted_at IS NULL AND instruction_only = 1;
@@ -0,0 +1 @@
DROP TABLE IF EXISTS wiki_ingest_checkpoints;
@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS wiki_ingest_checkpoints (
tenant_id INTEGER NOT NULL,
knowledge_base_id TEXT NOT NULL,
knowledge_id TEXT NOT NULL,
fingerprint TEXT NOT NULL,
stage TEXT NOT NULL,
item_key TEXT NOT NULL,
payload TEXT NOT NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (tenant_id, knowledge_base_id, knowledge_id, fingerprint, stage, item_key)
);
@@ -0,0 +1 @@
DROP TABLE IF EXISTS wiki_recovery_budgets;
@@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS wiki_recovery_budgets (
tenant_id BIGINT NOT NULL,
dead_letter_id BIGINT NOT NULL,
attempt_number INTEGER NOT NULL,
pending_op_id BIGINT NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL,
model_id VARCHAR(36) NOT NULL,
embedding_model_id VARCHAR(36) NOT NULL DEFAULT '',
source_fingerprint VARCHAR(64) NOT NULL,
model_fingerprint VARCHAR(64) NOT NULL,
reviewed_release VARCHAR(255) NOT NULL,
reviewed_api_image VARCHAR(255) NOT NULL,
max_provider_requests BIGINT NOT NULL CHECK (max_provider_requests > 0),
reserved_provider_requests BIGINT NOT NULL DEFAULT 0 CHECK (reserved_provider_requests >= 0 AND reserved_provider_requests <= max_provider_requests),
deadline_at DATETIME NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'active',
stop_reason VARCHAR(128) NOT NULL DEFAULT '',
last_phase VARCHAR(64) NOT NULL DEFAULT '',
paused_payload TEXT NOT NULL,
verified_progress TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (tenant_id, dead_letter_id, attempt_number, pending_op_id),
UNIQUE (tenant_id, pending_op_id)
);
@@ -0,0 +1 @@
ALTER TABLE workbench_access_roles DROP COLUMN permission_version;
@@ -0,0 +1,15 @@
-- The SQLite baseline predates the access-role table.
CREATE TABLE IF NOT EXISTS workbench_access_roles (
id TEXT PRIMARY KEY,
tenant_id INTEGER NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
permissions TEXT NOT NULL DEFAULT '[]',
version INTEGER NOT NULL DEFAULT 1,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (tenant_id, name)
);
-- Preserve legacy role expansion until the role is explicitly saved.
ALTER TABLE workbench_access_roles
ADD COLUMN permission_version INTEGER NOT NULL DEFAULT 0;
@@ -0,0 +1,9 @@
DROP INDEX IF EXISTS idx_memory_extraction_due;
ALTER TABLE memory_subjects DROP COLUMN extract_attempt_id;
ALTER TABLE memory_subjects DROP COLUMN extract_lease_until;
ALTER TABLE memory_subjects DROP COLUMN extract_started_at;
ALTER TABLE memory_subjects DROP COLUMN extract_next_at;
ALTER TABLE memory_subjects DROP COLUMN extract_failures;
ALTER TABLE memory_subjects DROP COLUMN extract_chat_model_id;
ALTER TABLE memory_subjects DROP COLUMN extract_language;
ALTER TABLE memory_subjects DROP COLUMN session_cursors;
@@ -0,0 +1,9 @@
ALTER TABLE memory_subjects ADD COLUMN session_cursors JSON;
ALTER TABLE memory_subjects ADD COLUMN extract_attempt_id VARCHAR(36) NOT NULL DEFAULT '';
ALTER TABLE memory_subjects ADD COLUMN extract_lease_until DATETIME;
ALTER TABLE memory_subjects ADD COLUMN extract_started_at DATETIME;
ALTER TABLE memory_subjects ADD COLUMN extract_next_at DATETIME;
ALTER TABLE memory_subjects ADD COLUMN extract_failures INTEGER NOT NULL DEFAULT 0;
ALTER TABLE memory_subjects ADD COLUMN extract_chat_model_id VARCHAR(64) NOT NULL DEFAULT '';
ALTER TABLE memory_subjects ADD COLUMN extract_language VARCHAR(64) NOT NULL DEFAULT '';
CREATE INDEX idx_memory_extraction_due ON memory_subjects(extract_next_at);
+55
View File
@@ -0,0 +1,55 @@
-- Drop indexes for chunks
DROP INDEX IF EXISTS idx_chunks_tenant_kg;
DROP INDEX IF EXISTS idx_chunks_parent_id;
DROP INDEX IF EXISTS idx_chunks_chunk_type;
-- Drop chunks table
DROP TABLE IF EXISTS chunks;
-- Drop indexes for messages
DROP INDEX IF EXISTS idx_messages_session_id;
-- Drop messages table
DROP TABLE IF EXISTS messages;
-- Drop indexes for sessions
DROP INDEX IF EXISTS idx_sessions_tenant_id;
-- Drop sessions table
DROP TABLE IF EXISTS sessions;
-- Drop indexes for knowledges
DROP INDEX IF EXISTS idx_knowledges_tenant_id;
DROP INDEX IF EXISTS idx_knowledges_base_id;
DROP INDEX IF EXISTS idx_knowledges_parse_status;
DROP INDEX IF EXISTS idx_knowledges_enable_status;
-- Drop knowledges table
DROP TABLE IF EXISTS knowledges;
-- Drop indexes for knowledge_bases
DROP INDEX IF EXISTS idx_knowledge_bases_tenant_id;
-- Drop knowledge_bases table
DROP TABLE IF EXISTS knowledge_bases;
-- Drop indexes for models
DROP INDEX IF EXISTS idx_models_type;
DROP INDEX IF EXISTS idx_models_source;
-- Drop models table
DROP TABLE IF EXISTS models;
-- Drop indexes for tenants
DROP INDEX IF EXISTS idx_tenants_api_key;
DROP INDEX IF EXISTS idx_tenants_status;
-- Drop tenants table
DROP TABLE IF EXISTS tenants;
-- Note: Extensions are not dropped as they may be used by other databases/schemas
-- If you want to drop extensions, uncomment the following lines:
-- DROP EXTENSION IF EXISTS pg_search;
-- DROP EXTENSION IF EXISTS pg_trgm;
-- DROP EXTENSION IF EXISTS vector;
-- DROP EXTENSION IF EXISTS "uuid-ossp";
+205
View File
@@ -0,0 +1,205 @@
-- Migration: 000000_init
-- Description: Initialize database schema
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Starting initial database setup...'; END $$;
-- Create extensions
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Creating extensions...'; END $$;
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- Create tenant table
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Creating table: tenants'; END $$;
CREATE TABLE IF NOT EXISTS tenants (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
api_key VARCHAR(64) NOT NULL,
retriever_engines JSONB NOT NULL DEFAULT '[]',
status VARCHAR(50) DEFAULT 'active',
business VARCHAR(255) NOT NULL,
storage_quota BIGINT NOT NULL DEFAULT 10737418240, -- 默认10GB配额(Bytes)
storage_used BIGINT NOT NULL DEFAULT 0, -- 已使用的存储空间(Bytes)
agent_config JSONB DEFAULT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Set the starting value for tenants id sequence (only if current value is less than 10000)
DO $$
DECLARE
current_val BIGINT;
BEGIN
SELECT last_value INTO current_val FROM tenants_id_seq;
IF current_val < 10000 THEN
ALTER SEQUENCE tenants_id_seq RESTART WITH 10000;
RAISE NOTICE '[Migration 000000] Set tenants_id_seq to start at 10000';
ELSE
RAISE NOTICE '[Migration 000000] tenants_id_seq already at % (>= 10000), skipping', current_val;
END IF;
EXCEPTION
WHEN undefined_table THEN
-- Sequence doesn't exist yet, will be created with table
RAISE NOTICE '[Migration 000000] tenants_id_seq not found, will be created with table';
END $$;
-- Add indexes
CREATE INDEX IF NOT EXISTS idx_tenants_api_key ON tenants(api_key);
CREATE INDEX IF NOT EXISTS idx_tenants_status ON tenants(status);
-- Create model table
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Creating table: models'; END $$;
CREATE TABLE IF NOT EXISTS models (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL DEFAULT '',
type VARCHAR(50) NOT NULL,
source VARCHAR(50) NOT NULL,
description TEXT,
parameters JSONB NOT NULL,
is_default BOOLEAN NOT NULL DEFAULT false,
status VARCHAR(50) NOT NULL DEFAULT 'active',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Add indexes for models
CREATE INDEX IF NOT EXISTS idx_models_type ON models(type);
CREATE INDEX IF NOT EXISTS idx_models_source ON models(source);
-- Create knowledge_base table
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Creating table: knowledge_bases'; END $$;
CREATE TABLE IF NOT EXISTS knowledge_bases (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
description TEXT,
tenant_id INTEGER NOT NULL,
chunking_config JSONB NOT NULL DEFAULT '{"chunk_size": 512, "chunk_overlap": 50, "split_markers": ["\n\n", "\n", "。"], "keep_separator": true}',
image_processing_config JSONB NOT NULL DEFAULT '{"enable_multimodal": false, "model_id": ""}',
embedding_model_id VARCHAR(64) NOT NULL,
summary_model_id VARCHAR(64) NOT NULL,
rerank_model_id VARCHAR(64) NOT NULL,
cos_config JSONB NOT NULL DEFAULT '{}',
vlm_config JSONB NOT NULL DEFAULT '{}',
extract_config JSONB NULL DEFAULT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Add indexes for knowledge_bases
CREATE INDEX IF NOT EXISTS idx_knowledge_bases_tenant_id ON knowledge_bases(tenant_id);
-- Create knowledge table
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Creating table: knowledges'; END $$;
CREATE TABLE IF NOT EXISTS knowledges (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
type VARCHAR(50) NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
source VARCHAR(2048) NOT NULL,
parse_status VARCHAR(50) NOT NULL DEFAULT 'unprocessed',
enable_status VARCHAR(50) NOT NULL DEFAULT 'enabled',
embedding_model_id VARCHAR(64),
file_name VARCHAR(255),
file_type VARCHAR(50),
file_size BIGINT,
file_path TEXT,
file_hash VARCHAR(64),
storage_size BIGINT NOT NULL DEFAULT 0, -- 存储大小(Byte)
metadata JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP WITH TIME ZONE,
error_message TEXT,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Add indexes for knowledge
CREATE INDEX IF NOT EXISTS idx_knowledges_tenant_id ON knowledges(tenant_id);
CREATE INDEX IF NOT EXISTS idx_knowledges_base_id ON knowledges(knowledge_base_id);
CREATE INDEX IF NOT EXISTS idx_knowledges_parse_status ON knowledges(parse_status);
CREATE INDEX IF NOT EXISTS idx_knowledges_enable_status ON knowledges(enable_status);
-- Create session table
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Creating table: sessions'; END $$;
CREATE TABLE IF NOT EXISTS sessions (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
title VARCHAR(255),
description TEXT,
knowledge_base_id VARCHAR(36),
max_rounds INTEGER NOT NULL DEFAULT 5,
enable_rewrite BOOLEAN NOT NULL DEFAULT true,
fallback_strategy VARCHAR(255) NOT NULL DEFAULT 'fixed',
fallback_response TEXT NOT NULL DEFAULT '很抱歉,我暂时无法回答这个问题。',
keyword_threshold FLOAT NOT NULL DEFAULT 0.5,
vector_threshold FLOAT NOT NULL DEFAULT 0.5,
rerank_model_id VARCHAR(64),
embedding_top_k INTEGER NOT NULL DEFAULT 10,
rerank_top_k INTEGER NOT NULL DEFAULT 10,
rerank_threshold FLOAT NOT NULL DEFAULT 0.65,
summary_model_id VARCHAR(64),
summary_parameters JSONB NOT NULL DEFAULT '{}',
agent_config JSONB DEFAULT NULL,
context_config JSONB DEFAULT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Create Index for sessions
CREATE INDEX IF NOT EXISTS idx_sessions_tenant_id ON sessions(tenant_id);
-- Create message table
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Creating table: messages'; END $$;
CREATE TABLE IF NOT EXISTS messages (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
request_id VARCHAR(36) NOT NULL,
session_id VARCHAR(36) NOT NULL,
role VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
knowledge_references JSONB NOT NULL DEFAULT '[]',
agent_steps JSONB DEFAULT NULL,
is_completed BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Create Index for messages
CREATE INDEX IF NOT EXISTS idx_messages_session_id ON messages(session_id);
-- Create chunks table
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Creating table: chunks'; END $$;
CREATE TABLE IF NOT EXISTS chunks (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id INTEGER NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
knowledge_id VARCHAR(36) NOT NULL,
content TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
is_enabled BOOLEAN NOT NULL DEFAULT true,
start_at INTEGER NOT NULL,
end_at INTEGER NOT NULL,
pre_chunk_id VARCHAR(36),
next_chunk_id VARCHAR(36),
chunk_type VARCHAR(20) NOT NULL DEFAULT 'text',
parent_chunk_id VARCHAR(36),
image_info TEXT,
relation_chunks JSONB,
indirect_relation_chunks JSONB,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX IF NOT EXISTS idx_chunks_tenant_kg ON chunks(tenant_id, knowledge_id);
CREATE INDEX IF NOT EXISTS idx_chunks_parent_id ON chunks(parent_chunk_id);
CREATE INDEX IF NOT EXISTS idx_chunks_chunk_type ON chunks(chunk_type);
DO $$ BEGIN RAISE NOTICE '[Migration 000000] Initial database setup completed successfully!'; END $$;
+130
View File
@@ -0,0 +1,130 @@
BEGIN;
DROP INDEX IF EXISTS idx_knowledges_summary_status;
ALTER TABLE knowledges DROP COLUMN IF EXISTS summary_status;
ALTER TABLE knowledge_bases
DROP COLUMN IF EXISTS question_generation_config;
ALTER TABLE users
DROP COLUMN IF EXISTS can_access_all_tenants;
ALTER TABLE knowledge_bases
ADD COLUMN IF NOT EXISTS rerank_model_id VARCHAR(64);
UPDATE models m
JOIN knowledges k ON m.id = k.id
SET m.tenant_id = 0
WHERE m.tenant_id = k.tenant_id;
-- Drop index on content_hash
DROP INDEX IF EXISTS idx_chunks_content_hash;
-- Drop content_hash column
ALTER TABLE chunks
DROP COLUMN IF EXISTS content_hash;
-- Drop status column
ALTER TABLE chunks
DROP COLUMN IF EXISTS status;
-- Drop indexes and columns referencing tags
DROP INDEX IF EXISTS idx_chunks_tag;
ALTER TABLE chunks DROP COLUMN IF EXISTS tag_id;
DROP INDEX IF EXISTS idx_knowledges_tag;
ALTER TABLE knowledges DROP COLUMN IF EXISTS tag_id;
-- Drop tag table
DROP INDEX IF EXISTS idx_knowledge_tags_kb_name;
DROP INDEX IF EXISTS idx_knowledge_tags_kb;
DROP TABLE IF EXISTS knowledge_tags;
ALTER TABLE chunks
DROP COLUMN IF EXISTS metadata;
ALTER TABLE knowledge_bases
DROP COLUMN IF EXISTS faq_config,
DROP COLUMN IF EXISTS type;
-- Drop index
DROP INDEX IF EXISTS idx_models_is_builtin;
-- Remove is_builtin column
ALTER TABLE models
DROP COLUMN IF EXISTS is_builtin;
-- Remove check constraint
ALTER TABLE mcp_services
DROP CONSTRAINT IF EXISTS chk_mcp_transport_config;
-- Make url column required again
ALTER TABLE mcp_services
ALTER COLUMN url SET NOT NULL;
-- Remove stdio_config and env_vars columns
ALTER TABLE mcp_services
DROP COLUMN IF EXISTS env_vars,
DROP COLUMN IF EXISTS stdio_config;
-- Remove web_search_config column
ALTER TABLE tenants
DROP COLUMN IF EXISTS web_search_config;
-- Drop trigger
DROP TRIGGER IF EXISTS trigger_mcp_services_updated_at ON mcp_services;
-- Drop function
DROP FUNCTION IF EXISTS update_mcp_services_updated_at();
-- Drop indexes
DROP INDEX IF EXISTS idx_mcp_services_tenant_id;
DROP INDEX IF EXISTS idx_mcp_services_enabled;
DROP INDEX IF EXISTS idx_mcp_services_deleted_at;
-- Drop table
DROP TABLE IF EXISTS mcp_services;
-- This migration performs a data cleanup (soft delete) which is not safely reversible.
-- Down migration is a no-op.
-- Drop foreign key constraints first
ALTER TABLE auth_tokens DROP CONSTRAINT IF EXISTS fk_auth_tokens_user;
ALTER TABLE users DROP CONSTRAINT IF EXISTS fk_users_tenant;
-- Drop tables
DROP TABLE IF EXISTS auth_tokens;
DROP TABLE IF EXISTS users;
-- Drop is_temporary column from knowledge_bases
ALTER TABLE knowledge_bases
DROP COLUMN IF EXISTS is_temporary;
-- Drop context_config column from tenants
ALTER TABLE tenants
DROP COLUMN IF EXISTS context_config;
-- Drop conversation_config column from tenants
ALTER TABLE tenants
DROP COLUMN IF EXISTS conversation_config;
-- Drop JSONB indexes if they exist
DROP INDEX IF EXISTS idx_messages_agent_steps;
DROP INDEX IF EXISTS idx_sessions_context_config;
DROP INDEX IF EXISTS idx_sessions_agent_config;
DROP INDEX IF EXISTS idx_tenants_agent_config;
-- Drop columns if they exist
ALTER TABLE messages
DROP COLUMN IF EXISTS agent_steps;
ALTER TABLE sessions
DROP COLUMN IF EXISTS context_config;
ALTER TABLE sessions
DROP COLUMN IF EXISTS agent_config;
ALTER TABLE tenants
DROP COLUMN IF EXISTS agent_config;
COMMIT;
+408
View File
@@ -0,0 +1,408 @@
-- Migration: 000001_agent
-- Description: Add user authentication, agent config, MCP services and other enhancements
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Starting agent and authentication migration...'; END $$;
-- ============================================================================
-- Section 1: User Authentication Tables
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Creating table: users'; END $$;
CREATE TABLE IF NOT EXISTS users (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
username VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
avatar VARCHAR(500),
tenant_id INTEGER,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Add unique constraints if not exists
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_username_key') THEN
ALTER TABLE users ADD CONSTRAINT users_username_key UNIQUE (username);
RAISE NOTICE '[Migration 000001] Added unique constraint on users.username';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_email_key') THEN
ALTER TABLE users ADD CONSTRAINT users_email_key UNIQUE (email);
RAISE NOTICE '[Migration 000001] Added unique constraint on users.email';
END IF;
END $$;
COMMENT ON TABLE users IS 'User accounts in the system';
COMMENT ON COLUMN users.id IS 'Unique identifier of the user';
COMMENT ON COLUMN users.username IS 'Username of the user';
COMMENT ON COLUMN users.email IS 'Email address of the user';
COMMENT ON COLUMN users.password_hash IS 'Hashed password of the user';
COMMENT ON COLUMN users.avatar IS 'Avatar URL of the user';
COMMENT ON COLUMN users.tenant_id IS 'Tenant ID that the user belongs to';
COMMENT ON COLUMN users.is_active IS 'Whether the user is active';
-- Add indexes for users
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
CREATE INDEX IF NOT EXISTS idx_users_tenant_id ON users(tenant_id);
CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users(deleted_at);
-- Add foreign key constraint for tenant_id
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_users_tenant') THEN
ALTER TABLE users ADD CONSTRAINT fk_users_tenant
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE SET NULL;
RAISE NOTICE '[Migration 000001] Added foreign key constraint fk_users_tenant';
END IF;
END $$;
-- Add can_access_all_tenants column to users
ALTER TABLE users ADD COLUMN IF NOT EXISTS can_access_all_tenants BOOLEAN NOT NULL DEFAULT FALSE;
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Creating table: auth_tokens'; END $$;
CREATE TABLE IF NOT EXISTS auth_tokens (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(36) NOT NULL,
token TEXT NOT NULL,
token_type VARCHAR(50) NOT NULL,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
is_revoked BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE auth_tokens IS 'Authentication tokens for users';
COMMENT ON COLUMN auth_tokens.id IS 'Unique identifier of the token';
COMMENT ON COLUMN auth_tokens.user_id IS 'User ID that owns this token';
COMMENT ON COLUMN auth_tokens.token IS 'Token value (JWT or other format)';
COMMENT ON COLUMN auth_tokens.token_type IS 'Token type (access_token, refresh_token)';
COMMENT ON COLUMN auth_tokens.expires_at IS 'Token expiration time';
COMMENT ON COLUMN auth_tokens.is_revoked IS 'Whether the token is revoked';
-- Add indexes for auth_tokens
CREATE INDEX IF NOT EXISTS idx_auth_tokens_user_id ON auth_tokens(user_id);
CREATE INDEX IF NOT EXISTS idx_auth_tokens_token ON auth_tokens(token);
CREATE INDEX IF NOT EXISTS idx_auth_tokens_token_type ON auth_tokens(token_type);
CREATE INDEX IF NOT EXISTS idx_auth_tokens_expires_at ON auth_tokens(expires_at);
-- Add foreign key constraint for auth_tokens
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_auth_tokens_user') THEN
ALTER TABLE auth_tokens ADD CONSTRAINT fk_auth_tokens_user
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
RAISE NOTICE '[Migration 000001] Added foreign key constraint fk_auth_tokens_user';
END IF;
END $$;
-- ============================================================================
-- Section 2: Tenant Configuration Enhancements
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Adding tenant configuration columns...'; END $$;
-- Add context_config column to tenants
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS context_config JSONB;
COMMENT ON COLUMN tenants.context_config IS 'Global Context configuration for this tenant (default for all sessions)';
-- Add conversation_config column to tenants
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS conversation_config JSONB;
COMMENT ON COLUMN tenants.conversation_config IS 'Global Conversation configuration for this tenant (default for normal mode sessions)';
-- Add web_search_config column to tenants
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS web_search_config JSONB DEFAULT NULL;
COMMENT ON COLUMN tenants.web_search_config IS 'Web search configuration for the tenant';
-- Ensure agent_config exists and is JSONB type
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'tenants' AND column_name = 'agent_config'
) THEN
ALTER TABLE tenants ADD COLUMN agent_config JSONB DEFAULT NULL;
RAISE NOTICE '[Migration 000001] Added agent_config column to tenants table';
ELSE
-- If field exists but type is JSON, convert to JSONB
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'tenants' AND column_name = 'agent_config' AND data_type = 'json'
) THEN
ALTER TABLE tenants ALTER COLUMN agent_config TYPE JSONB USING agent_config::jsonb;
RAISE NOTICE '[Migration 000001] Converted tenants.agent_config from JSON to JSONB';
END IF;
END IF;
END $$;
COMMENT ON COLUMN tenants.agent_config IS 'Tenant-level agent configuration in JSON format';
-- ============================================================================
-- Section 3: Session Configuration Enhancements
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Adding session configuration columns...'; END $$;
-- Add agent_config column to sessions
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS agent_config JSONB DEFAULT NULL;
COMMENT ON COLUMN sessions.agent_config IS 'Session-level agent configuration in JSON format';
-- Add context_config column to sessions
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS context_config JSONB DEFAULT NULL;
COMMENT ON COLUMN sessions.context_config IS 'LLM context management configuration (separate from message storage)';
-- ============================================================================
-- Section 4: Messages Enhancements
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Adding messages enhancements...'; END $$;
-- Add agent_steps column to messages
ALTER TABLE messages ADD COLUMN IF NOT EXISTS agent_steps JSONB DEFAULT NULL;
COMMENT ON COLUMN messages.agent_steps IS 'Agent execution steps (reasoning process and tool calls)';
-- ============================================================================
-- Section 5: Knowledge Base Enhancements
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Adding knowledge base enhancements...'; END $$;
-- Add is_temporary column to knowledge_bases
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS is_temporary BOOLEAN NOT NULL DEFAULT false;
COMMENT ON COLUMN knowledge_bases.is_temporary IS 'Whether this knowledge base is temporary (ephemeral) and should be hidden from UI';
-- Add type and faq_config columns
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS type VARCHAR(32) NOT NULL DEFAULT 'document';
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS faq_config JSONB;
-- Add question_generation_config column
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS question_generation_config JSONB NULL;
-- Update existing rows with default type
UPDATE knowledge_bases SET type = 'document' WHERE type IS NULL OR type = '';
-- Drop rerank_model_id column if exists (moved to session level)
ALTER TABLE knowledge_bases DROP COLUMN IF EXISTS rerank_model_id;
-- ============================================================================
-- Section 6: Knowledges Enhancements
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Adding knowledges enhancements...'; END $$;
-- Add tag_id column
ALTER TABLE knowledges ADD COLUMN IF NOT EXISTS tag_id VARCHAR(36);
CREATE INDEX IF NOT EXISTS idx_knowledges_tag ON knowledges(tag_id);
-- Add summary_status column
ALTER TABLE knowledges ADD COLUMN IF NOT EXISTS summary_status VARCHAR(32) DEFAULT 'none';
CREATE INDEX IF NOT EXISTS idx_knowledges_summary_status ON knowledges(summary_status);
-- ============================================================================
-- Section 7: Chunks Enhancements
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Adding chunks enhancements...'; END $$;
-- Add metadata column
ALTER TABLE chunks ADD COLUMN IF NOT EXISTS metadata JSONB;
-- Add tag_id column
ALTER TABLE chunks ADD COLUMN IF NOT EXISTS tag_id VARCHAR(36);
CREATE INDEX IF NOT EXISTS idx_chunks_tag ON chunks(tag_id);
-- Add status field to track chunk processing state
ALTER TABLE chunks ADD COLUMN IF NOT EXISTS status INT NOT NULL DEFAULT 0;
-- Add content_hash field for quick content matching
ALTER TABLE chunks ADD COLUMN IF NOT EXISTS content_hash VARCHAR(64);
CREATE INDEX IF NOT EXISTS idx_chunks_content_hash ON chunks(content_hash);
-- ============================================================================
-- Section 8: Embeddings Enhancements
-- ============================================================================
-- move embeddings to 000002 migrations
-- ============================================================================
-- Section 9: Models Enhancements
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Adding models enhancements...'; END $$;
-- Add is_builtin column
ALTER TABLE models ADD COLUMN IF NOT EXISTS is_builtin BOOLEAN NOT NULL DEFAULT false;
CREATE INDEX IF NOT EXISTS idx_models_is_builtin ON models(is_builtin);
-- ============================================================================
-- Section 10: Knowledge Tags Table
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Creating table: knowledge_tags'; END $$;
CREATE TABLE IF NOT EXISTS knowledge_tags (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
knowledge_base_id VARCHAR(36) NOT NULL,
name VARCHAR(128) NOT NULL,
color VARCHAR(32),
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_tags_kb_name ON knowledge_tags(tenant_id, knowledge_base_id, name);
CREATE INDEX IF NOT EXISTS idx_knowledge_tags_kb ON knowledge_tags(tenant_id, knowledge_base_id);
-- ============================================================================
-- Section 11: MCP Services Table
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Creating table: mcp_services'; END $$;
CREATE TABLE IF NOT EXISTS mcp_services (
id VARCHAR(36) PRIMARY KEY,
tenant_id INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
enabled BOOLEAN DEFAULT true,
transport_type VARCHAR(50) NOT NULL,
url VARCHAR(512),
headers JSONB,
auth_config JSONB,
advanced_config JSONB,
stdio_config JSONB,
env_vars JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_mcp_services_tenant_id ON mcp_services(tenant_id);
CREATE INDEX IF NOT EXISTS idx_mcp_services_enabled ON mcp_services(enabled);
CREATE INDEX IF NOT EXISTS idx_mcp_services_deleted_at ON mcp_services(deleted_at);
COMMENT ON TABLE mcp_services IS 'MCP service configurations';
-- Create or replace trigger function for updated_at
CREATE OR REPLACE FUNCTION update_mcp_services_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Create trigger if not exists
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_mcp_services_updated_at') THEN
CREATE TRIGGER trigger_mcp_services_updated_at
BEFORE UPDATE ON mcp_services
FOR EACH ROW
EXECUTE FUNCTION update_mcp_services_updated_at();
RAISE NOTICE '[Migration 000001] Created trigger trigger_mcp_services_updated_at';
END IF;
END $$;
-- ============================================================================
-- Section 12: GIN Indexes for JSONB Fields
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Creating GIN indexes for JSONB fields...'; END $$;
DO $$
BEGIN
-- For tenants.agent_config
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_tenants_agent_config') THEN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'tenants' AND column_name = 'agent_config' AND data_type = 'jsonb'
) THEN
CREATE INDEX idx_tenants_agent_config ON tenants USING GIN (agent_config);
RAISE NOTICE '[Migration 000001] Created index idx_tenants_agent_config';
END IF;
END IF;
-- For sessions.agent_config
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_sessions_agent_config') THEN
CREATE INDEX idx_sessions_agent_config ON sessions USING GIN (agent_config);
RAISE NOTICE '[Migration 000001] Created index idx_sessions_agent_config';
END IF;
-- For sessions.context_config
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_sessions_context_config') THEN
CREATE INDEX idx_sessions_context_config ON sessions USING GIN (context_config);
RAISE NOTICE '[Migration 000001] Created index idx_sessions_context_config';
END IF;
-- For messages.agent_steps
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_messages_agent_steps') THEN
CREATE INDEX idx_messages_agent_steps ON messages USING GIN (agent_steps);
RAISE NOTICE '[Migration 000001] Created index idx_messages_agent_steps';
END IF;
END $$;
-- ============================================================================
-- Section 13: Data Migrations
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Running data migrations...'; END $$;
-- Clean up unreferenced models (soft delete)
DO $$
DECLARE
affected_rows INTEGER;
BEGIN
WITH referenced_models AS (
SELECT embedding_model_id AS model_id FROM knowledge_bases WHERE deleted_at IS NULL AND embedding_model_id != ''
UNION
SELECT summary_model_id FROM knowledge_bases WHERE deleted_at IS NULL AND summary_model_id != ''
UNION
SELECT vlm_config ->> 'model_id'
FROM knowledge_bases
WHERE deleted_at IS NULL
AND vlm_config ->> 'model_id' IS NOT NULL
AND vlm_config ->> 'model_id' != ''
UNION
SELECT embedding_model_id FROM knowledges WHERE deleted_at IS NULL AND embedding_model_id IS NOT NULL AND embedding_model_id != ''
)
UPDATE models m
SET deleted_at = CURRENT_TIMESTAMP
WHERE m.deleted_at IS NULL
AND m.is_default = FALSE
AND m.tenant_id != 0
AND m.id NOT IN (SELECT model_id FROM referenced_models WHERE model_id IS NOT NULL);
GET DIAGNOSTICS affected_rows = ROW_COUNT;
IF affected_rows > 0 THEN
RAISE NOTICE '[Migration 000001] Soft deleted % unreferenced models', affected_rows;
END IF;
END $$;
-- Update models tenant_id from knowledge_bases references
DO $$
DECLARE
affected_rows INTEGER;
BEGIN
WITH tenant_source AS (
SELECT kb.embedding_model_id AS model_id, kb.tenant_id
FROM knowledge_bases kb
WHERE kb.tenant_id IS NOT NULL AND kb.embedding_model_id IS NOT NULL AND kb.embedding_model_id <> ''
UNION
SELECT kb.summary_model_id AS model_id, kb.tenant_id
FROM knowledge_bases kb
WHERE kb.tenant_id IS NOT NULL AND kb.summary_model_id IS NOT NULL AND kb.summary_model_id <> ''
)
UPDATE models m
SET tenant_id = ts.tenant_id
FROM tenant_source ts
WHERE m.id = ts.model_id AND m.tenant_id = 0;
GET DIAGNOSTICS affected_rows = ROW_COUNT;
IF affected_rows > 0 THEN
RAISE NOTICE '[Migration 000001] Updated tenant_id for % models', affected_rows;
END IF;
END $$;
DO $$ BEGIN RAISE NOTICE '[Migration 000001] Migration completed successfully!'; END $$;
@@ -0,0 +1,14 @@
-- Drop indexes for embeddings
DROP INDEX IF EXISTS idx_embeddings_knowledge_base_id;
-- Drop index
DROP INDEX IF EXISTS idx_embeddings_is_enabled;
DROP INDEX IF EXISTS embeddings_unique_source;
DROP INDEX IF EXISTS embeddings_search_idx;
DROP INDEX IF EXISTS embeddings_embedding_idx_3584;
DROP INDEX IF EXISTS embeddings_embedding_idx_798;
DROP INDEX IF EXISTS embeddings_embedding_idx;
-- Drop embeddings table
DROP TABLE IF EXISTS embeddings;
@@ -0,0 +1,94 @@
-- Migration: embeddings (conditional)
-- Description: Create embeddings table and indexes (only for postgres retrieve driver)
DO $$
BEGIN
-- Check if we should skip this migration
IF current_setting('app.skip_embedding', true) = 'true' THEN
RAISE NOTICE 'Skipping migration embeddings (app.skip_embedding=true)';
RETURN;
END IF;
-- If we reach here, proceed with migration
RAISE NOTICE '[Conditional Migration: embeddings] Creating embeddings table...';
-- Create required extensions
CREATE EXTENSION IF NOT EXISTS vector;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS pg_search;
-- Create embeddings table
RAISE NOTICE '[Conditional Migration: embeddings] Creating indexes for embeddings (this may take a while)...';
CREATE TABLE IF NOT EXISTS embeddings (
id SERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
source_id VARCHAR(64) NOT NULL,
source_type INTEGER NOT NULL,
chunk_id VARCHAR(64),
knowledge_id VARCHAR(64),
knowledge_base_id VARCHAR(64),
content TEXT,
dimension INTEGER NOT NULL,
embedding halfvec
);
CREATE UNIQUE INDEX IF NOT EXISTS embeddings_unique_source ON embeddings(source_id, source_type);
-- Create BM25 search index (check if exists first)
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'embeddings_search_idx') THEN
CREATE INDEX embeddings_search_idx ON embeddings
USING bm25 (id, knowledge_base_id, content, knowledge_id, chunk_id)
WITH (
key_field = 'id',
text_fields = '{
"content": {
"tokenizer": {"type": "chinese_lindera"}
}
}'
);
RAISE NOTICE '[Conditional Migration: embeddings] Created BM25 index embeddings_search_idx';
ELSE
RAISE NOTICE '[Conditional Migration: embeddings] BM25 index embeddings_search_idx already exists';
END IF;
-- Create HNSW indexes for vector search (check if exists first)
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'embeddings_embedding_idx' OR indexname LIKE 'embeddings_embedding%3584%') THEN
CREATE INDEX embeddings_embedding_idx_3584 ON embeddings
USING hnsw ((embedding::halfvec(3584)) halfvec_cosine_ops)
WITH (m = 16, ef_construction = 64)
WHERE (dimension = 3584);
RAISE NOTICE '[Conditional Migration: embeddings] Created HNSW index for dimension 3584';
ELSE
RAISE NOTICE '[Conditional Migration: embeddings] HNSW index for dimension 3584 already exists';
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'embeddings_embedding_idx_798' OR indexname LIKE 'embeddings_embedding%798%') THEN
CREATE INDEX embeddings_embedding_idx_798 ON embeddings
USING hnsw ((embedding::halfvec(798)) halfvec_cosine_ops)
WITH (m = 16, ef_construction = 64)
WHERE (dimension = 798);
RAISE NOTICE '[Conditional Migration: embeddings] Created HNSW index for dimension 798';
ELSE
RAISE NOTICE '[Conditional Migration: embeddings] HNSW index for dimension 798 already exists';
END IF;
RAISE NOTICE '[Migration 000002] Adding embeddings enhancements...';
-- Add is_enabled column
ALTER TABLE embeddings ADD COLUMN IF NOT EXISTS is_enabled BOOLEAN DEFAULT TRUE;
CREATE INDEX IF NOT EXISTS idx_embeddings_is_enabled ON embeddings(is_enabled);
-- Add index for knowledge_base_id
CREATE INDEX IF NOT EXISTS idx_embeddings_knowledge_base_id ON embeddings(knowledge_base_id);
-- Reindex BM25 search index (idempotent - will rebuild if exists)
IF EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'embeddings_search_idx') THEN
REINDEX INDEX embeddings_search_idx;
RAISE NOTICE '[Migration 000002] Reindexed embeddings_search_idx';
END IF;
RAISE NOTICE '[Conditional Migration: embeddings] Embeddings table setup completed successfully!';
END $$;
@@ -0,0 +1,11 @@
-- Migration: chunk_flags (rollback)
-- Description: Remove flags column from chunks table
DO $$
BEGIN
RAISE NOTICE '[Migration 000003] Removing flags column from chunks table...';
ALTER TABLE chunks DROP COLUMN IF EXISTS flags;
RAISE NOTICE '[Migration 000003] Flags column removed successfully';
END $$;
@@ -0,0 +1,13 @@
-- Migration: chunk_flags
-- Description: Add flags column to chunks table for managing multiple boolean states
DO $$
BEGIN
RAISE NOTICE '[Migration 000003] Adding flags column to chunks table...';
-- Add flags column with default value 1 (ChunkFlagRecommended)
-- This means all existing chunks will be recommended by default
ALTER TABLE chunks ADD COLUMN IF NOT EXISTS flags INTEGER NOT NULL DEFAULT 1;
RAISE NOTICE '[Migration 000003] Flags column added successfully';
END $$;
@@ -0,0 +1,12 @@
-- Migration: drop_vlm_model_id (rollback)
-- Description: Re-add vlm_model_id column to knowledge_bases table
DO $$
BEGIN
RAISE NOTICE '[Migration 000004 Rollback] Re-adding vlm_model_id column to knowledge_bases table...';
-- Re-add vlm_model_id column (nullable to avoid issues)
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS vlm_model_id VARCHAR(64);
RAISE NOTICE '[Migration 000004 Rollback] vlm_model_id column re-added successfully';
END $$;
@@ -0,0 +1,12 @@
-- Migration: drop_vlm_model_id
-- Description: Drop vlm_model_id column from knowledge_bases table (field moved to vlm_config JSON)
DO $$
BEGIN
RAISE NOTICE '[Migration 000004] Dropping vlm_model_id column from knowledge_bases table...';
-- Drop vlm_model_id column if exists (this field was moved to vlm_config JSON)
ALTER TABLE knowledge_bases DROP COLUMN IF EXISTS vlm_model_id;
RAISE NOTICE '[Migration 000004] vlm_model_id column dropped successfully';
END $$;
@@ -0,0 +1,3 @@
-- Remove mentioned_items column from messages table
ALTER TABLE messages DROP COLUMN IF EXISTS mentioned_items;
@@ -0,0 +1,7 @@
-- Add mentioned_items column to messages table
-- This column stores @mentioned knowledge bases and files when user sends a message
ALTER TABLE messages ADD COLUMN IF NOT EXISTS mentioned_items JSONB DEFAULT '[]';
-- Add comment for the column
COMMENT ON COLUMN messages.mentioned_items IS 'Stores @mentioned knowledge bases and files (id, name, type) when user sends a message';
@@ -0,0 +1,18 @@
-- Migration: 000006_custom_agents (rollback)
-- Description: Remove custom agents table and related changes
DO $$ BEGIN RAISE NOTICE '[Migration 000006 DOWN] Starting custom agents rollback...'; END $$;
-- Remove agent_id column from sessions table
DO $$ BEGIN RAISE NOTICE '[Migration 000006 DOWN] Removing agent_id column from sessions table'; END $$;
DROP INDEX IF EXISTS idx_sessions_agent_id;
ALTER TABLE sessions DROP COLUMN IF EXISTS agent_id;
-- Drop custom_agents table (includes built-in agents created during migration)
DO $$ BEGIN RAISE NOTICE '[Migration 000006 DOWN] Dropping table: custom_agents'; END $$;
DROP INDEX IF EXISTS idx_custom_agents_tenant_id;
DROP INDEX IF EXISTS idx_custom_agents_is_builtin;
DROP INDEX IF EXISTS idx_custom_agents_deleted_at;
DROP TABLE IF EXISTS custom_agents;
DO $$ BEGIN RAISE NOTICE '[Migration 000006 DOWN] Custom agents rollback completed!'; END $$;
@@ -0,0 +1,176 @@
-- Migration: 000006_custom_agents
-- Description: Add custom agents table for GPTs-like agent configuration and migrate tenant config to built-in agents
DO $$ BEGIN RAISE NOTICE '[Migration 000006] Starting custom agents setup...'; END $$;
-- Create custom_agents table with composite primary key (id, tenant_id)
-- This allows the same agent ID to exist for different tenants (e.g., 'builtin-normal' for each tenant)
DO $$ BEGIN RAISE NOTICE '[Migration 000006] Creating table: custom_agents'; END $$;
CREATE TABLE IF NOT EXISTS custom_agents (
id VARCHAR(36) NOT NULL DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
description TEXT,
avatar VARCHAR(64),
is_builtin BOOLEAN NOT NULL DEFAULT false,
tenant_id INTEGER NOT NULL,
created_by VARCHAR(36),
config JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
PRIMARY KEY (id, tenant_id)
);
-- Add indexes for custom_agents
CREATE INDEX IF NOT EXISTS idx_custom_agents_tenant_id ON custom_agents(tenant_id);
CREATE INDEX IF NOT EXISTS idx_custom_agents_is_builtin ON custom_agents(is_builtin);
CREATE INDEX IF NOT EXISTS idx_custom_agents_deleted_at ON custom_agents(deleted_at);
-- Add agent_id column to sessions table to track which agent was used
DO $$ BEGIN RAISE NOTICE '[Migration 000006] Adding agent_id column to sessions table'; END $$;
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS agent_id VARCHAR(36);
CREATE INDEX IF NOT EXISTS idx_sessions_agent_id ON sessions(agent_id);
-- Helper function to unify prompt placeholders from Go template format to simple format
CREATE OR REPLACE FUNCTION unify_prompt_placeholder(input TEXT) RETURNS TEXT AS $$
DECLARE
result TEXT := COALESCE(input, '');
replacements TEXT[][] := ARRAY[
-- Go template variables -> simple placeholders
['{{.Query}}', '{{query}}'],
['{{.Answer}}', '{{answer}}'],
['{{.CurrentTime}}', '{{current_time}}'],
['{{.CurrentWeek}}', '{{current_week}}'],
['{{.Yesterday}}', '{{yesterday}}'],
['{{.Contexts}}', '{{contexts}}'],
-- Go template control structures -> simple placeholders or remove
['{{range .Contexts}}', '{{contexts}}'],
-- Remove Go template syntax
['{{if .Contexts}}', ''],
['{{else}}', ''],
['{{.}}', '']
];
r TEXT[];
BEGIN
FOREACH r SLICE 1 IN ARRAY replacements LOOP
result := REPLACE(result, r[1], r[2]);
END LOOP;
-- Handle {{range .Conversation}}...{{end}} block specially
-- Replace the entire block with just {{conversation}}
-- The pattern matches: {{range .Conversation}} followed by any content until {{end}}
result := regexp_replace(
result,
'\{\{range \.Conversation\}\}[\s\S]*?\{\{end\}\}',
'{{conversation}}',
'g'
);
-- Clean up any remaining {{end}} tags
result := REPLACE(result, '{{end}}', '');
RETURN result;
END;
$$ LANGUAGE plpgsql;
-- Migrate tenant AgentConfig and ConversationConfig to built-in custom agents
DO $$ BEGIN RAISE NOTICE '[Migration 000006] Migrating tenant config to built-in agents...'; END $$;
-- Insert builtin-quick-answer agent for tenants with ConversationConfig
INSERT INTO custom_agents (id, name, description, avatar, is_builtin, tenant_id, config, created_at, updated_at)
SELECT
'builtin-quick-answer',
'快速问答',
'基于知识库的 RAG 问答,快速准确地回答问题',
'💬',
true,
t.id,
jsonb_build_object(
'agent_mode', 'quick-answer',
'system_prompt', unify_prompt_placeholder(t.conversation_config->>'prompt'),
'context_template', unify_prompt_placeholder(t.conversation_config->>'context_template'),
'model_id', COALESCE(t.conversation_config->>'summary_model_id', ''),
'rerank_model_id', COALESCE(t.conversation_config->>'rerank_model_id', ''),
'temperature', COALESCE((t.conversation_config->>'temperature')::float, 0.7),
'max_completion_tokens', COALESCE((t.conversation_config->>'max_completion_tokens')::int, 2048),
'max_iterations', 10,
'allowed_tools', '[]'::jsonb,
'reflection_enabled', false,
'kb_selection_mode', 'all',
'knowledge_bases', '[]'::jsonb,
'web_search_enabled', false,
'web_search_max_results', COALESCE((t.web_search_config->>'max_results')::int, 5),
'multi_turn_enabled', COALESCE((t.conversation_config->>'multi_turn_enabled')::bool, true),
'history_turns', COALESCE((t.conversation_config->>'max_rounds')::int, 5),
'embedding_top_k', COALESCE((t.conversation_config->>'embedding_top_k')::int, 10),
'keyword_threshold', COALESCE((t.conversation_config->>'keyword_threshold')::float, 0.3),
'vector_threshold', COALESCE((t.conversation_config->>'vector_threshold')::float, 0.5),
'rerank_top_k', COALESCE((t.conversation_config->>'rerank_top_k')::int, 5),
'rerank_threshold', COALESCE((t.conversation_config->>'rerank_threshold')::float, 0.5),
'enable_query_expansion', COALESCE((t.conversation_config->>'enable_query_expansion')::bool, true),
'enable_rewrite', COALESCE((t.conversation_config->>'enable_rewrite')::bool, true),
'rewrite_prompt_system', unify_prompt_placeholder(t.conversation_config->>'rewrite_prompt_system'),
'rewrite_prompt_user', unify_prompt_placeholder(t.conversation_config->>'rewrite_prompt_user'),
'fallback_strategy', COALESCE(t.conversation_config->>'fallback_strategy', 'model'),
'fallback_response', unify_prompt_placeholder(t.conversation_config->>'fallback_response'),
'fallback_prompt', unify_prompt_placeholder(t.conversation_config->>'fallback_prompt')
),
NOW(),
NOW()
FROM tenants t
WHERE t.conversation_config IS NOT NULL
AND t.deleted_at IS NULL
ON CONFLICT (id, tenant_id) DO UPDATE SET
config = EXCLUDED.config,
updated_at = NOW();
-- Insert builtin-smart-reasoning agent for tenants with AgentConfig
INSERT INTO custom_agents (id, name, description, avatar, is_builtin, tenant_id, config, created_at, updated_at)
SELECT
'builtin-smart-reasoning',
'智能推理',
'ReAct 推理框架,支持多步思考和工具调用',
'🤖',
true,
t.id,
jsonb_build_object(
'agent_mode', 'smart-reasoning',
'system_prompt', unify_prompt_placeholder(t.agent_config->>'system_prompt_web_disabled'),
'system_prompt_web_enabled', unify_prompt_placeholder(t.agent_config->>'system_prompt_web_enabled'),
'context_template', '',
'model_id', COALESCE(t.conversation_config->>'summary_model_id', ''),
'rerank_model_id', COALESCE(t.conversation_config->>'rerank_model_id', ''),
'temperature', COALESCE((t.agent_config->>'temperature')::float, 0.7),
'max_completion_tokens', 2048,
'max_iterations', COALESCE((t.agent_config->>'max_iterations')::int, 50),
'allowed_tools', COALESCE(t.agent_config->'allowed_tools', '["thinking", "todo_write", "knowledge_search", "grep_chunks", "list_knowledge_chunks", "query_knowledge_graph", "get_document_info"]'::jsonb),
'reflection_enabled', COALESCE((t.agent_config->>'reflection_enabled')::bool, false),
'mcp_selection_mode', 'all',
'mcp_services', '[]'::jsonb,
'kb_selection_mode', 'all',
'knowledge_bases', COALESCE(t.agent_config->'knowledge_bases', '[]'::jsonb),
'web_search_enabled', COALESCE((t.agent_config->>'web_search_enabled')::bool, true),
'web_search_max_results', COALESCE((t.agent_config->>'web_search_max_results')::int, COALESCE((t.web_search_config->>'max_results')::int, 5)),
'multi_turn_enabled', COALESCE((t.agent_config->>'multi_turn_enabled')::bool, true),
'history_turns', COALESCE((t.agent_config->>'history_turns')::int, 5),
'embedding_top_k', 10,
'keyword_threshold', 0.3,
'vector_threshold', 0.5,
'rerank_top_k', 5,
'rerank_threshold', 0.5,
'enable_query_expansion', false,
'enable_rewrite', false,
'rewrite_prompt_system', '',
'rewrite_prompt_user', '',
'fallback_strategy', 'model',
'fallback_response', '',
'fallback_prompt', ''
),
NOW(),
NOW()
FROM tenants t
WHERE t.agent_config IS NOT NULL
AND t.deleted_at IS NULL
ON CONFLICT (id, tenant_id) DO UPDATE SET
config = EXCLUDED.config,
updated_at = NOW();
@@ -0,0 +1,12 @@
-- Remove tag_id column from embeddings table
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'embeddings' AND column_name = 'tag_id'
) THEN
DROP INDEX IF EXISTS idx_embeddings_tag_id;
ALTER TABLE embeddings DROP COLUMN tag_id;
RAISE NOTICE '[Migration 000007 Rollback] Removed tag_id column from embeddings table';
END IF;
END $$;
@@ -0,0 +1,20 @@
-- Add tag_id column to embeddings table for FAQ priority filtering
DO $$
BEGIN
-- Check if table exists first
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'embeddings') THEN
-- Add tag_id column if not exists
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'embeddings' AND column_name = 'tag_id'
) THEN
ALTER TABLE embeddings ADD COLUMN tag_id VARCHAR(36);
CREATE INDEX IF NOT EXISTS idx_embeddings_tag_id ON embeddings(tag_id);
RAISE NOTICE '[Migration 000007] Added tag_id column and index to embeddings table';
ELSE
RAISE NOTICE '[Migration 000007] tag_id column already exists in embeddings table, skipping';
END IF;
ELSE
RAISE NOTICE '[Migration 000007] embeddings table does not exist, skipping';
END IF;
END $$;
@@ -0,0 +1,60 @@
-- Rollback: This migration cannot be fully rolled back as we don't know which entries
-- were originally untagged. We can only clear the tag_id for entries that reference
-- a "未分类" tag, but this may affect entries that were intentionally tagged.
-- WARNING: This rollback is destructive and should only be used if absolutely necessary.
-- It will set tag_id to empty string for all chunks, knowledges, and embeddings that reference "未分类" tags.
DO $$
DECLARE
kb_record RECORD;
untagged_tag_id VARCHAR(36);
updated_chunks INT;
updated_knowledges INT;
BEGIN
RAISE NOTICE '[Migration 000008 Rollback] WARNING: This rollback will clear tag_id for all entries referencing "未分类" tags';
-- Find all "未分类" tags
FOR kb_record IN
SELECT id, tenant_id, knowledge_base_id
FROM knowledge_tags
WHERE name = '未分类'
LOOP
untagged_tag_id := kb_record.id;
-- Clear tag_id for chunks referencing this tag (both faq and document types)
UPDATE chunks
SET tag_id = '', updated_at = NOW()
WHERE tag_id = untagged_tag_id
AND chunk_type IN ('faq', 'document');
GET DIAGNOSTICS updated_chunks = ROW_COUNT;
RAISE NOTICE '[Migration 000008 Rollback] Cleared tag_id for % chunks referencing tag %',
updated_chunks, untagged_tag_id;
-- Clear tag_id for knowledges referencing this tag
UPDATE knowledges
SET tag_id = '', updated_at = NOW()
WHERE tag_id = untagged_tag_id;
GET DIAGNOSTICS updated_knowledges = ROW_COUNT;
RAISE NOTICE '[Migration 000008 Rollback] Cleared tag_id for % knowledges referencing tag %',
updated_knowledges, untagged_tag_id;
-- Clear tag_id in embeddings if column exists
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'embeddings' AND column_name = 'tag_id'
) THEN
UPDATE embeddings
SET tag_id = ''
WHERE tag_id = untagged_tag_id;
END IF;
-- Delete the "未分类" tag
DELETE FROM knowledge_tags WHERE id = untagged_tag_id;
RAISE NOTICE '[Migration 000008 Rollback] Deleted "未分类" tag: %', untagged_tag_id;
END LOOP;
RAISE NOTICE '[Migration 000008 Rollback] Completed rollback';
END $$;
@@ -0,0 +1,105 @@
-- Migration: Create "未分类" tag for each knowledge base that has untagged entries
-- and update chunks, knowledges, and embeddings to reference the new tag
DO $$
BEGIN
IF current_setting('app.skip_embedding', true) = 'true' THEN
RAISE NOTICE 'Skipping pg_search update (app.skip_embedding=true)';
RETURN;
END IF;
ALTER EXTENSION pg_search UPDATE;
END $$;
DO $$
DECLARE
kb_record RECORD;
new_tag_id VARCHAR(36);
updated_chunks INT;
updated_knowledges INT;
updated_embeddings INT;
BEGIN
-- Find all knowledge bases that have untagged chunks or knowledges
FOR kb_record IN
SELECT DISTINCT tenant_id, knowledge_base_id
FROM (
-- Untagged chunks (FAQ or document)
SELECT c.tenant_id, c.knowledge_base_id
FROM chunks c
WHERE c.chunk_type IN ('faq', 'document')
AND (c.tag_id = '' OR c.tag_id IS NULL)
UNION
-- Untagged knowledges (documents)
SELECT k.tenant_id, k.knowledge_base_id
FROM knowledges k
WHERE k.deleted_at IS NULL
AND (k.tag_id = '' OR k.tag_id IS NULL)
) AS untagged
LOOP
-- Check if "未分类" tag already exists for this knowledge base
SELECT id INTO new_tag_id
FROM knowledge_tags
WHERE tenant_id = kb_record.tenant_id
AND knowledge_base_id = kb_record.knowledge_base_id
AND name = '未分类'
LIMIT 1;
-- If not exists, create the tag
IF new_tag_id IS NULL THEN
new_tag_id := gen_random_uuid()::VARCHAR(36);
INSERT INTO knowledge_tags (id, tenant_id, knowledge_base_id, name, color, sort_order, created_at, updated_at)
VALUES (new_tag_id, kb_record.tenant_id, kb_record.knowledge_base_id, '未分类', '', 0, NOW(), NOW());
RAISE NOTICE '[Migration 000008] Created "未分类" tag (id: %) for tenant_id: %, kb_id: %',
new_tag_id, kb_record.tenant_id, kb_record.knowledge_base_id;
ELSE
RAISE NOTICE '[Migration 000008] "未分类" tag already exists (id: %) for tenant_id: %, kb_id: %',
new_tag_id, kb_record.tenant_id, kb_record.knowledge_base_id;
END IF;
-- Update chunks with empty tag_id (both faq and document types)
UPDATE chunks
SET tag_id = new_tag_id, updated_at = NOW()
WHERE tenant_id = kb_record.tenant_id
AND knowledge_base_id = kb_record.knowledge_base_id
AND chunk_type IN ('faq', 'document')
AND (tag_id = '' OR tag_id IS NULL);
GET DIAGNOSTICS updated_chunks = ROW_COUNT;
RAISE NOTICE '[Migration 000008] Updated % chunks for tenant_id: %, kb_id: %',
updated_chunks, kb_record.tenant_id, kb_record.knowledge_base_id;
-- Update knowledges with empty tag_id (documents)
UPDATE knowledges
SET tag_id = new_tag_id, updated_at = NOW()
WHERE tenant_id = kb_record.tenant_id
AND knowledge_base_id = kb_record.knowledge_base_id
AND deleted_at IS NULL
AND (tag_id = '' OR tag_id IS NULL);
GET DIAGNOSTICS updated_knowledges = ROW_COUNT;
RAISE NOTICE '[Migration 000008] Updated % knowledges for tenant_id: %, kb_id: %',
updated_knowledges, kb_record.tenant_id, kb_record.knowledge_base_id;
-- Update embeddings with empty tag_id (if embeddings table exists and has tag_id column)
IF EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'embeddings' AND column_name = 'tag_id'
) THEN
UPDATE embeddings
SET tag_id = new_tag_id
WHERE knowledge_base_id = kb_record.knowledge_base_id
AND (tag_id = '' OR tag_id IS NULL)
AND chunk_id IN (
SELECT id FROM chunks
WHERE tenant_id = kb_record.tenant_id
AND knowledge_base_id = kb_record.knowledge_base_id
AND chunk_type IN ('faq', 'document')
);
GET DIAGNOSTICS updated_embeddings = ROW_COUNT;
RAISE NOTICE '[Migration 000008] Updated % embeddings for kb_id: %',
updated_embeddings, kb_record.knowledge_base_id;
END IF;
END LOOP;
RAISE NOTICE '[Migration 000008] Completed migration of untagged entries';
END $$;
@@ -0,0 +1,3 @@
-- Remove last_faq_import_result column from knowledge table
ALTER TABLE knowledges DROP COLUMN IF EXISTS last_faq_import_result;
@@ -0,0 +1,4 @@
-- Add last_faq_import_result column to knowledge table
-- This field stores the latest FAQ import result for FAQ type knowledge
ALTER TABLE knowledges ADD COLUMN IF NOT EXISTS last_faq_import_result JSON DEFAULT NULL;
@@ -0,0 +1,11 @@
-- Migration 000010 Down: Remove seq_id from chunks and knowledge_tags tables
-- Remove seq_id from chunks
DROP INDEX IF EXISTS idx_chunks_seq_id;
ALTER TABLE chunks DROP COLUMN IF EXISTS seq_id;
DROP SEQUENCE IF EXISTS chunks_seq_id_seq;
-- Remove seq_id from knowledge_tags
DROP INDEX IF EXISTS idx_knowledge_tags_seq_id;
ALTER TABLE knowledge_tags DROP COLUMN IF EXISTS seq_id;
DROP SEQUENCE IF EXISTS knowledge_tags_seq_id_seq;
@@ -0,0 +1,52 @@
-- Migration 000010: Add seq_id (auto-increment integer ID) to chunks and knowledge_tags tables
-- This provides integer IDs for FAQ entries and tags for external API usage
-- ============================================================================
-- Section 1: Add seq_id to chunks table
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000010] Adding seq_id column to chunks table'; END $$;
-- Create sequence for chunks with starting value > 72528124
CREATE SEQUENCE IF NOT EXISTS chunks_seq_id_seq START WITH 100000000;
-- Add seq_id column to chunks table
ALTER TABLE chunks ADD COLUMN IF NOT EXISTS seq_id BIGINT;
-- Set default value using sequence
ALTER TABLE chunks ALTER COLUMN seq_id SET DEFAULT nextval('chunks_seq_id_seq');
-- Populate existing rows with sequence values
UPDATE chunks SET seq_id = nextval('chunks_seq_id_seq') WHERE seq_id IS NULL;
-- Make seq_id NOT NULL after populating
ALTER TABLE chunks ALTER COLUMN seq_id SET NOT NULL;
-- Create unique index on seq_id
CREATE UNIQUE INDEX IF NOT EXISTS idx_chunks_seq_id ON chunks(seq_id);
-- ============================================================================
-- Section 2: Add seq_id to knowledge_tags table
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000010] Adding seq_id column to knowledge_tags table'; END $$;
-- Create sequence for knowledge_tags with starting value > 2924026
CREATE SEQUENCE IF NOT EXISTS knowledge_tags_seq_id_seq START WITH 10000000;
-- Add seq_id column to knowledge_tags table
ALTER TABLE knowledge_tags ADD COLUMN IF NOT EXISTS seq_id BIGINT;
-- Set default value using sequence
ALTER TABLE knowledge_tags ALTER COLUMN seq_id SET DEFAULT nextval('knowledge_tags_seq_id_seq');
-- Populate existing rows with sequence values
UPDATE knowledge_tags SET seq_id = nextval('knowledge_tags_seq_id_seq') WHERE seq_id IS NULL;
-- Make seq_id NOT NULL after populating
ALTER TABLE knowledge_tags ALTER COLUMN seq_id SET NOT NULL;
-- Create unique index on seq_id
CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_tags_seq_id ON knowledge_tags(seq_id);
DO $$ BEGIN RAISE NOTICE '[Migration 000010] seq_id columns added successfully!'; END $$;
@@ -0,0 +1,2 @@
-- Migration 000011 Down: pg_search extension update cannot be rolled back
-- No-op: ALTER EXTENSION UPDATE has no reverse operation
@@ -0,0 +1,12 @@
-- Migration 000011: Update pg_search extension to latest version
-- Equivalent to: psql -c 'ALTER EXTENSION pg_search UPDATE;'
DO $$
BEGIN
IF current_setting('app.skip_embedding', true) = 'true' THEN
RAISE NOTICE 'Skipping pg_search update (app.skip_embedding=true)';
RETURN;
END IF;
ALTER EXTENSION pg_search UPDATE;
END $$;
@@ -0,0 +1,15 @@
-- Migration: 000012_organizations (down, merged 000012, 000013, 000014)
DO $$ BEGIN RAISE NOTICE '[Migration 000012] Rolling back organization and agent_share tables...'; END $$;
-- Rollback 000014: tenant_disabled_shared_agents first (no FK to organizations)
DROP INDEX IF EXISTS idx_tenant_disabled_shared_agents_tenant_id;
DROP TABLE IF EXISTS tenant_disabled_shared_agents;
-- Rollback 000012/000013: organization-related tables (order matters for FK)
DROP TABLE IF EXISTS agent_shares;
DROP TABLE IF EXISTS organization_join_requests;
DROP TABLE IF EXISTS kb_shares;
DROP TABLE IF EXISTS organization_members;
DROP TABLE IF EXISTS organizations;
DO $$ BEGIN RAISE NOTICE '[Migration 000012] Rollback completed successfully!'; END $$;
@@ -0,0 +1,146 @@
-- Migration: 000012_organizations (merged 000012, 000013, 000014)
-- Description: Organization tables, kb_shares, join requests, agent_shares, tenant_disabled_shared_agents
DO $$ BEGIN RAISE NOTICE '[Migration 000012] Starting organization tables setup...'; END $$;
-- Create organizations table
CREATE TABLE IF NOT EXISTS organizations (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
description TEXT,
owner_id VARCHAR(36) NOT NULL,
invite_code VARCHAR(32),
require_approval BOOLEAN DEFAULT FALSE,
invite_code_expires_at TIMESTAMP WITH TIME ZONE,
invite_code_validity_days SMALLINT NOT NULL DEFAULT 7,
avatar VARCHAR(512) DEFAULT '',
searchable BOOLEAN NOT NULL DEFAULT FALSE,
member_limit INTEGER NOT NULL DEFAULT 50,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_organizations_invite_code ON organizations(invite_code) WHERE invite_code IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_organizations_owner_id ON organizations(owner_id);
CREATE INDEX IF NOT EXISTS idx_organizations_deleted_at ON organizations(deleted_at);
COMMENT ON TABLE organizations IS 'Organizations for cross-tenant collaboration';
COMMENT ON COLUMN organizations.owner_id IS 'User ID of the organization owner';
COMMENT ON COLUMN organizations.invite_code IS 'Unique invitation code for joining the organization';
COMMENT ON COLUMN organizations.require_approval IS 'Whether joining this organization requires admin approval';
COMMENT ON COLUMN organizations.invite_code_expires_at IS 'When the current invite code expires; NULL means no expiry (legacy)';
COMMENT ON COLUMN organizations.invite_code_validity_days IS 'Invite link validity in days: 0=never expire, 1/7/30 days';
COMMENT ON COLUMN organizations.searchable IS 'When true, space appears in search and can be joined by org ID';
COMMENT ON COLUMN organizations.member_limit IS 'Max members allowed; 0 means no limit';
-- Create organization_members table
CREATE TABLE IF NOT EXISTS organization_members (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id VARCHAR(36) NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
user_id VARCHAR(36) NOT NULL,
tenant_id INTEGER NOT NULL,
role VARCHAR(32) NOT NULL DEFAULT 'viewer',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_org_members_org_user ON organization_members(organization_id, user_id);
CREATE INDEX IF NOT EXISTS idx_org_members_user_id ON organization_members(user_id);
CREATE INDEX IF NOT EXISTS idx_org_members_tenant_id ON organization_members(tenant_id);
CREATE INDEX IF NOT EXISTS idx_org_members_role ON organization_members(role);
COMMENT ON TABLE organization_members IS 'Members of organizations with their roles';
COMMENT ON COLUMN organization_members.role IS 'Member role: admin, editor, or viewer';
COMMENT ON COLUMN organization_members.tenant_id IS 'The tenant ID that the member belongs to';
-- Create kb_shares table (knowledge base sharing)
CREATE TABLE IF NOT EXISTS kb_shares (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
knowledge_base_id VARCHAR(36) NOT NULL REFERENCES knowledge_bases(id) ON DELETE CASCADE,
organization_id VARCHAR(36) NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
shared_by_user_id VARCHAR(36) NOT NULL,
source_tenant_id INTEGER NOT NULL,
permission VARCHAR(32) NOT NULL DEFAULT 'viewer',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_kb_shares_kb_org ON kb_shares(knowledge_base_id, organization_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_kb_shares_kb_id ON kb_shares(knowledge_base_id);
CREATE INDEX IF NOT EXISTS idx_kb_shares_org_id ON kb_shares(organization_id);
CREATE INDEX IF NOT EXISTS idx_kb_shares_source_tenant ON kb_shares(source_tenant_id);
CREATE INDEX IF NOT EXISTS idx_kb_shares_deleted_at ON kb_shares(deleted_at);
COMMENT ON TABLE kb_shares IS 'Knowledge base sharing records to organizations';
COMMENT ON COLUMN kb_shares.source_tenant_id IS 'Original tenant ID of the knowledge base for cross-tenant embedding model access';
COMMENT ON COLUMN kb_shares.permission IS 'Access permission level: admin, editor, or viewer';
-- Create organization_join_requests table
CREATE TABLE IF NOT EXISTS organization_join_requests (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id VARCHAR(36) NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
user_id VARCHAR(36) NOT NULL,
tenant_id INTEGER NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'pending',
requested_role VARCHAR(32) NOT NULL DEFAULT 'viewer',
request_type VARCHAR(32) NOT NULL DEFAULT 'join',
prev_role VARCHAR(32),
message TEXT,
reviewed_by VARCHAR(36),
reviewed_at TIMESTAMP WITH TIME ZONE,
review_message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_org_join_requests_org_user_pending ON organization_join_requests(organization_id, user_id) WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_org_join_requests_org_id ON organization_join_requests(organization_id);
CREATE INDEX IF NOT EXISTS idx_org_join_requests_user_id ON organization_join_requests(user_id);
CREATE INDEX IF NOT EXISTS idx_org_join_requests_status ON organization_join_requests(status);
CREATE INDEX IF NOT EXISTS idx_org_join_requests_type ON organization_join_requests(request_type);
COMMENT ON TABLE organization_join_requests IS 'Join requests for organizations that require approval';
COMMENT ON COLUMN organization_join_requests.status IS 'Request status: pending, approved, rejected';
COMMENT ON COLUMN organization_join_requests.requested_role IS 'Role requested by the applicant: admin, editor, viewer';
COMMENT ON COLUMN organization_join_requests.request_type IS 'join for new member, upgrade for role upgrade';
COMMENT ON COLUMN organization_join_requests.message IS 'Optional message from the requester';
COMMENT ON COLUMN organization_join_requests.reviewed_by IS 'User ID of the admin who reviewed the request';
COMMENT ON COLUMN organization_join_requests.review_message IS 'Optional message from the reviewer';
-- Agent shares (merged from 000013; model_shares omitted, dropped in 000014)
CREATE TABLE IF NOT EXISTS agent_shares (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
agent_id VARCHAR(36) NOT NULL,
organization_id VARCHAR(36) NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
shared_by_user_id VARCHAR(36) NOT NULL,
source_tenant_id INTEGER NOT NULL,
permission VARCHAR(32) NOT NULL DEFAULT 'viewer',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE,
FOREIGN KEY (agent_id, source_tenant_id) REFERENCES custom_agents(id, tenant_id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_shares_agent_org ON agent_shares(agent_id, source_tenant_id, organization_id) WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_agent_shares_agent_id ON agent_shares(agent_id);
CREATE INDEX IF NOT EXISTS idx_agent_shares_org_id ON agent_shares(organization_id);
CREATE INDEX IF NOT EXISTS idx_agent_shares_source_tenant ON agent_shares(source_tenant_id);
CREATE INDEX IF NOT EXISTS idx_agent_shares_deleted_at ON agent_shares(deleted_at);
COMMENT ON TABLE agent_shares IS 'Custom agent sharing records to organizations';
COMMENT ON COLUMN agent_shares.source_tenant_id IS 'Original tenant ID of the agent';
COMMENT ON COLUMN agent_shares.permission IS 'Access permission: viewer or editor';
-- Per-tenant "disabled" list for shared agents (merged from 000014)
DO $$ BEGIN RAISE NOTICE '[Migration 000012] Creating table: tenant_disabled_shared_agents'; END $$;
CREATE TABLE IF NOT EXISTS tenant_disabled_shared_agents (
tenant_id BIGINT NOT NULL,
agent_id VARCHAR(36) NOT NULL,
source_tenant_id BIGINT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (tenant_id, agent_id, source_tenant_id)
);
CREATE INDEX IF NOT EXISTS idx_tenant_disabled_shared_agents_tenant_id ON tenant_disabled_shared_agents(tenant_id);
DO $$ BEGIN RAISE NOTICE '[Migration 000012] Organization tables and tenant_disabled_shared_agents setup completed successfully!'; END $$;
@@ -0,0 +1,3 @@
-- Rollback engine config columns
ALTER TABLE tenants DROP COLUMN IF EXISTS storage_engine_config;
ALTER TABLE tenants DROP COLUMN IF EXISTS parser_engine_config;
@@ -0,0 +1,8 @@
-- Description: Add parser_engine_config and storage_engine_config to tenants for UI-configured overrides.
DO $$ BEGIN RAISE NOTICE '[Migration 000013] Adding engine config columns to tenants'; END $$;
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS parser_engine_config JSONB DEFAULT NULL;
COMMENT ON COLUMN tenants.parser_engine_config IS 'Parser engine overrides (mineru_endpoint, mineru_api_key, etc.); takes precedence over env when parsing';
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS storage_engine_config JSONB DEFAULT NULL;
COMMENT ON COLUMN tenants.storage_engine_config IS 'Storage engine parameters for Local, MinIO, COS; used for document/file storage and docreader';
@@ -0,0 +1,16 @@
-- Rollback: copy provider back to cos_config if needed, then drop new column
UPDATE knowledge_bases
SET cos_config = jsonb_set(
COALESCE(cos_config, '{}'::jsonb),
'{provider}',
to_jsonb(storage_provider_config->>'provider')
)
WHERE storage_provider_config IS NOT NULL
AND storage_provider_config->>'provider' IS NOT NULL
AND storage_provider_config->>'provider' != ''
AND storage_provider_config->>'provider' != '__pending_env__'
AND (cos_config IS NULL
OR cos_config->>'provider' IS NULL
OR cos_config->>'provider' = '');
ALTER TABLE knowledge_bases DROP COLUMN IF EXISTS storage_provider_config;
@@ -0,0 +1,31 @@
-- Description: Add storage_provider_config column to knowledge_bases, migrating from legacy cos_config.
-- This separates the storage provider selection (KB-level) from storage credentials (tenant-level).
DO $$ BEGIN RAISE NOTICE '[Migration 000014] Adding storage_provider_config to knowledge_bases'; END $$;
-- Step 1: Add new column
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS storage_provider_config JSONB DEFAULT NULL;
COMMENT ON COLUMN knowledge_bases.storage_provider_config IS 'Storage provider config for this KB. Only stores provider name; credentials come from tenant StorageEngineConfig.';
-- Step 2: Migrate existing provider from legacy cos_config
UPDATE knowledge_bases
SET storage_provider_config = jsonb_build_object('provider', cos_config->>'provider')
WHERE cos_config IS NOT NULL
AND cos_config->>'provider' IS NOT NULL
AND cos_config->>'provider' != ''
AND (storage_provider_config IS NULL
OR storage_provider_config->>'provider' IS NULL
OR storage_provider_config->>'provider' = '');
-- Step 3: For KBs that have documents but no provider set, mark them with the
-- sentinel value so the application can fill in the actual STORAGE_TYPE on startup.
-- We use 'pending_migration' as a marker; the app replaces it with the real env value.
UPDATE knowledge_bases kb
SET storage_provider_config = '{"provider": "__pending_env__"}'
WHERE (kb.storage_provider_config IS NULL
OR kb.storage_provider_config->>'provider' IS NULL
OR kb.storage_provider_config->>'provider' = '')
AND EXISTS (
SELECT 1 FROM knowledges k
WHERE k.knowledge_base_id = kb.id
AND k.deleted_at IS NULL
);
@@ -0,0 +1,2 @@
-- Remove is_fallback column from messages table
ALTER TABLE messages DROP COLUMN IF EXISTS is_fallback;
@@ -0,0 +1,3 @@
-- Add is_fallback column to messages table
-- Tracks whether a response was generated using fallback logic (no knowledge base match found)
ALTER TABLE messages ADD COLUMN IF NOT EXISTS is_fallback BOOLEAN DEFAULT FALSE;
@@ -0,0 +1,3 @@
-- Remove pin (置顶) support from knowledge bases
ALTER TABLE knowledge_bases DROP COLUMN IF EXISTS pinned_at;
ALTER TABLE knowledge_bases DROP COLUMN IF EXISTS is_pinned;
@@ -0,0 +1,3 @@
-- Add pin (置顶) support for knowledge bases
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS is_pinned BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE knowledge_bases ADD COLUMN IF NOT EXISTS pinned_at TIMESTAMP WITH TIME ZONE NULL;
@@ -0,0 +1,14 @@
-- ============================================================================
-- Migration 000017 DOWN: Remove is_builtin from mcp_services
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000017 DOWN] Removing is_builtin column from mcp_services...'; END $$;
-- Drop index
DROP INDEX IF EXISTS idx_mcp_services_is_builtin;
-- Remove is_builtin column
ALTER TABLE mcp_services
DROP COLUMN IF EXISTS is_builtin;
DO $$ BEGIN RAISE NOTICE '[Migration 000017 DOWN] is_builtin column removed from mcp_services'; END $$;
@@ -0,0 +1,11 @@
-- ============================================================================
-- Migration 000017: Add is_builtin support for MCP services
-- ============================================================================
DO $$ BEGIN RAISE NOTICE '[Migration 000017] Adding is_builtin column to mcp_services...'; END $$;
-- Add is_builtin column to mcp_services
ALTER TABLE mcp_services ADD COLUMN IF NOT EXISTS is_builtin BOOLEAN NOT NULL DEFAULT false;
CREATE INDEX IF NOT EXISTS idx_mcp_services_is_builtin ON mcp_services(is_builtin);
DO $$ BEGIN RAISE NOTICE '[Migration 000017] is_builtin column added to mcp_services'; END $$;
@@ -0,0 +1,2 @@
-- Migration 000018 DOWN: Revert tenant api_key column to varchar(64)
ALTER TABLE tenants ALTER COLUMN api_key TYPE varchar(64);
@@ -0,0 +1,2 @@
-- Migration 000018: Extend tenant api_key column to support encrypted values
ALTER TABLE tenants ALTER COLUMN api_key TYPE varchar(256);
@@ -0,0 +1,2 @@
-- Remove agent_duration_ms column from messages table
ALTER TABLE messages DROP COLUMN IF EXISTS agent_duration_ms;
@@ -0,0 +1,3 @@
-- Add agent_duration_ms column to messages table
-- Stores the total agent execution duration in milliseconds (from query start to answer start)
ALTER TABLE messages ADD COLUMN IF NOT EXISTS agent_duration_ms BIGINT DEFAULT 0;
@@ -0,0 +1,9 @@
-- Remove retrieval_config column from tenants table
ALTER TABLE tenants DROP COLUMN IF EXISTS retrieval_config;
-- Remove chat_history_config column from tenants table
ALTER TABLE tenants DROP COLUMN IF EXISTS chat_history_config;
-- Remove knowledge_id column from messages table
DROP INDEX IF EXISTS idx_messages_knowledge_id;
ALTER TABLE messages DROP COLUMN IF EXISTS knowledge_id;
@@ -0,0 +1,9 @@
-- Add knowledge_id column to messages table for linking messages to chat history knowledge base entries
ALTER TABLE messages ADD COLUMN IF NOT EXISTS knowledge_id VARCHAR(36);
CREATE INDEX IF NOT EXISTS idx_messages_knowledge_id ON messages(knowledge_id);
-- Add chat_history_config JSONB column to tenants table
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS chat_history_config JSONB;
-- Add retrieval_config JSONB column to tenants table
ALTER TABLE tenants ADD COLUMN IF NOT EXISTS retrieval_config JSONB;
@@ -0,0 +1,3 @@
ALTER TABLE im_channel_sessions DROP COLUMN IF EXISTS im_channel_id;
DROP TABLE IF EXISTS im_channels;
DROP TABLE IF EXISTS im_channel_sessions;
@@ -0,0 +1,77 @@
-- Migration: 000021_im_channel_sessions
-- Description: Create IM channel session mapping and IM channel configuration tables
DO $$ BEGIN RAISE NOTICE '[Migration 000021] Creating IM channel integration tables'; END $$;
CREATE TABLE IF NOT EXISTS im_channel_sessions (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
platform VARCHAR(20) NOT NULL,
user_id VARCHAR(128) NOT NULL,
chat_id VARCHAR(128) NOT NULL DEFAULT '',
session_id VARCHAR(36) NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
tenant_id BIGINT NOT NULL,
agent_id VARCHAR(36) DEFAULT '',
status VARCHAR(20) NOT NULL DEFAULT 'active',
metadata JSONB DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
-- Partial unique index: only enforce uniqueness for non-deleted rows
CREATE UNIQUE INDEX IF NOT EXISTS idx_channel_lookup
ON im_channel_sessions (platform, user_id, chat_id, tenant_id)
WHERE deleted_at IS NULL;
-- Index for tenant-based queries
CREATE INDEX IF NOT EXISTS idx_im_channel_tenant ON im_channel_sessions (tenant_id);
-- Index for session-based queries
CREATE INDEX IF NOT EXISTS idx_im_channel_session ON im_channel_sessions (session_id);
-- Partial index for soft deletes (only index deleted rows)
CREATE INDEX IF NOT EXISTS idx_im_channel_deleted ON im_channel_sessions (deleted_at) WHERE deleted_at IS NOT NULL;
COMMENT ON TABLE im_channel_sessions IS 'Maps IM platform channels to WeKnora conversation sessions';
COMMENT ON COLUMN im_channel_sessions.platform IS 'IM platform identifier: wecom, feishu, etc.';
COMMENT ON COLUMN im_channel_sessions.user_id IS 'Platform-specific user identifier';
COMMENT ON COLUMN im_channel_sessions.chat_id IS 'Platform-specific chat/group identifier, empty for direct messages';
COMMENT ON COLUMN im_channel_sessions.session_id IS 'Associated WeKnora session ID';
COMMENT ON COLUMN im_channel_sessions.tenant_id IS 'Tenant that owns this channel mapping';
COMMENT ON COLUMN im_channel_sessions.agent_id IS 'Custom agent ID used for this channel, empty for default';
COMMENT ON COLUMN im_channel_sessions.status IS 'Channel status: active, paused, expired';
COMMENT ON COLUMN im_channel_sessions.metadata IS 'Platform-specific extra data (JSON)';
DO $$ BEGIN RAISE NOTICE '[Migration 000021] Creating table: im_channels'; END $$;
CREATE TABLE IF NOT EXISTS im_channels (
id VARCHAR(36) PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id BIGINT NOT NULL,
agent_id VARCHAR(36) NOT NULL,
platform VARCHAR(20) NOT NULL,
name VARCHAR(255) NOT NULL DEFAULT '',
enabled BOOLEAN NOT NULL DEFAULT true,
mode VARCHAR(20) NOT NULL DEFAULT 'websocket',
output_mode VARCHAR(20) NOT NULL DEFAULT 'stream',
credentials JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP WITH TIME ZONE
);
CREATE INDEX IF NOT EXISTS idx_im_channels_tenant ON im_channels (tenant_id);
CREATE INDEX IF NOT EXISTS idx_im_channels_agent ON im_channels (agent_id);
CREATE INDEX IF NOT EXISTS idx_im_channels_deleted ON im_channels (deleted_at) WHERE deleted_at IS NOT NULL;
COMMENT ON TABLE im_channels IS 'IM platform channel configurations bound to agents';
COMMENT ON COLUMN im_channels.agent_id IS 'Agent ID this channel is bound to';
COMMENT ON COLUMN im_channels.platform IS 'IM platform: wecom, feishu';
COMMENT ON COLUMN im_channels.name IS 'User-defined channel name for identification';
COMMENT ON COLUMN im_channels.mode IS 'Connection mode: webhook or websocket';
COMMENT ON COLUMN im_channels.output_mode IS 'Output mode: stream (real-time) or full (wait for complete answer)';
COMMENT ON COLUMN im_channels.credentials IS 'Platform credentials (JSONB): WeCom webhook={corp_id,agent_secret,token,encoding_aes_key,corp_agent_id}, WeCom ws={bot_id,bot_secret}, Feishu={app_id,app_secret,verification_token,encrypt_key}';
-- Add im_channel_id column to im_channel_sessions for linking
ALTER TABLE im_channel_sessions ADD COLUMN IF NOT EXISTS im_channel_id VARCHAR(36) DEFAULT '';
CREATE INDEX IF NOT EXISTS idx_im_channel_sessions_channel ON im_channel_sessions (im_channel_id) WHERE im_channel_id != '';
DO $$ BEGIN RAISE NOTICE '[Migration 000021] IM channel integration setup completed successfully!'; END $$;
@@ -0,0 +1 @@
ALTER TABLE messages DROP COLUMN IF EXISTS images;
@@ -0,0 +1 @@
ALTER TABLE messages ADD COLUMN IF NOT EXISTS images JSONB DEFAULT '[]';
@@ -0,0 +1 @@
ALTER TABLE im_channels DROP COLUMN IF EXISTS knowledge_base_id;
@@ -0,0 +1,3 @@
-- Add knowledge_base_id column to im_channels table.
-- When set, file messages received on this channel will be saved to the specified knowledge base.
ALTER TABLE im_channels ADD COLUMN IF NOT EXISTS knowledge_base_id VARCHAR(36) DEFAULT '';
@@ -0,0 +1,2 @@
DROP INDEX IF EXISTS idx_im_channels_bot_identity;
ALTER TABLE im_channels DROP COLUMN IF EXISTS bot_identity;
@@ -0,0 +1,17 @@
-- Migration: 000024_im_channel_bot_identity
-- Description: Add bot_identity column to im_channels for duplicate bot prevention.
-- bot_identity is a computed unique key derived from credentials, ensuring each bot
-- can only be connected to one active (non-deleted) channel.
DO $$ BEGIN RAISE NOTICE '[Migration 000024] Adding bot_identity column to im_channels'; END $$;
ALTER TABLE im_channels ADD COLUMN IF NOT EXISTS bot_identity VARCHAR(255) NOT NULL DEFAULT '';
-- Partial unique index: only enforce uniqueness for non-deleted rows with a non-empty bot_identity.
-- Empty bot_identity (unknown credential format) is excluded from the constraint.
CREATE UNIQUE INDEX IF NOT EXISTS idx_im_channels_bot_identity
ON im_channels (bot_identity)
WHERE deleted_at IS NULL AND bot_identity != '';
COMMENT ON COLUMN im_channels.bot_identity IS 'Unique bot identity derived from credentials (e.g. wecom:ws:{bot_id}, feishu:{app_id}). Used to prevent duplicate bot bindings.';
DO $$ BEGIN RAISE NOTICE '[Migration 000024] bot_identity column and unique index created successfully'; END $$;
@@ -0,0 +1,4 @@
-- Migration: 000025_message_channel (rollback)
-- Description: Remove channel column from messages and knowledge
ALTER TABLE messages DROP COLUMN IF EXISTS channel;
ALTER TABLE knowledges DROP COLUMN IF EXISTS channel;

Some files were not shown because too many files have changed in this diff Show More