-- Migration: Knowledge Base tables with optional pgvector support -- 5 tables: knowledge_categories, knowledge_items, knowledge_chunks, -- knowledge_versions, knowledge_usage -- -- pgvector is optional: if the extension is not installed, the embedding -- column and HNSW index are skipped. All other tables work normally. -- 行业分类树 CREATE TABLE IF NOT EXISTS knowledge_categories ( id TEXT PRIMARY KEY, name VARCHAR(100) NOT NULL, description TEXT, parent_id TEXT REFERENCES knowledge_categories(id) ON DELETE RESTRICT, icon VARCHAR(50), sort_order INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CHECK (id != parent_id) ); CREATE INDEX IF NOT EXISTS idx_kc_parent ON knowledge_categories(parent_id); -- 知识条目 CREATE TABLE IF NOT EXISTS knowledge_items ( id TEXT PRIMARY KEY, category_id TEXT NOT NULL REFERENCES knowledge_categories(id) ON DELETE RESTRICT, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, keywords TEXT[] DEFAULT '{}', related_questions TEXT[] DEFAULT '{}', priority INT DEFAULT 0, status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'archived', 'deprecated', 'draft')), version INT DEFAULT 1, source VARCHAR(50) DEFAULT 'manual', tags TEXT[] DEFAULT '{}', created_by TEXT NOT NULL REFERENCES accounts(id), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), CHECK (length(content) <= 100000) ); CREATE INDEX IF NOT EXISTS idx_ki_category ON knowledge_items(category_id); CREATE INDEX IF NOT EXISTS idx_ki_status_updated ON knowledge_items(status, updated_at DESC); CREATE INDEX IF NOT EXISTS idx_ki_keywords ON knowledge_items USING GIN(keywords); -- 知识分块(RAG 检索核心) -- 基础表不含 embedding 列;若 pgvector 可用则后续通过 DO 块添加 CREATE TABLE IF NOT EXISTS knowledge_chunks ( id TEXT PRIMARY KEY, item_id TEXT NOT NULL REFERENCES knowledge_items(id) ON DELETE CASCADE, chunk_index INT NOT NULL, content TEXT NOT NULL, keywords TEXT[] DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE UNIQUE INDEX IF NOT EXISTS idx_kchunks_item_idx ON knowledge_chunks(item_id, chunk_index); CREATE INDEX IF NOT EXISTS idx_kchunks_item ON knowledge_chunks(item_id); CREATE INDEX IF NOT EXISTS idx_kchunks_keywords ON knowledge_chunks USING GIN(keywords); -- 条件添加 embedding 列和 HNSW 索引(仅当 pgvector 可用时) DO $$ BEGIN PERFORM set_config('client_min_messages', 'warning', true); CREATE EXTENSION IF NOT EXISTS vector; IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = 'knowledge_chunks' AND column_name = 'embedding' ) THEN ALTER TABLE knowledge_chunks ADD COLUMN embedding vector(1536); END IF; IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_kchunks_embedding') THEN CREATE INDEX idx_kchunks_embedding ON knowledge_chunks USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 128); END IF; RAISE NOTICE 'pgvector enabled for knowledge base'; EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'pgvector not available, vector features disabled: %', SQLERRM; END $$; -- 版本快照 CREATE TABLE IF NOT EXISTS knowledge_versions ( id TEXT PRIMARY KEY, item_id TEXT NOT NULL REFERENCES knowledge_items(id) ON DELETE CASCADE, version INT NOT NULL, title VARCHAR(255) NOT NULL, content TEXT NOT NULL, keywords TEXT[] DEFAULT '{}', related_questions TEXT[] DEFAULT '{}', change_summary TEXT, created_by TEXT NOT NULL REFERENCES accounts(id), created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_kv_item ON knowledge_versions(item_id); -- 使用追踪 CREATE TABLE IF NOT EXISTS knowledge_usage ( id TEXT PRIMARY KEY, item_id TEXT REFERENCES knowledge_items(id) ON DELETE SET NULL, chunk_id TEXT REFERENCES knowledge_chunks(id) ON DELETE SET NULL, session_id VARCHAR(100), query_text TEXT, relevance_score FLOAT, was_injected BOOLEAN DEFAULT FALSE, agent_feedback VARCHAR(20) CHECK (agent_feedback IN ('positive', 'negative')), created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_ku_item ON knowledge_usage(item_id) WHERE item_id IS NOT NULL; CREATE INDEX IF NOT EXISTS idx_ku_created_brin ON knowledge_usage USING brin(created_at); -- 权限种子数据(使用 jsonb 操作避免 REPLACE 脆弱性) UPDATE roles SET permissions = ( SELECT '[' || string_agg('"' || elem || '"', ', ') || ']' FROM ( SELECT DISTINCT elem FROM json_array_elements_text(permissions::json) AS elem UNION ALL SELECT 'knowledge:read' UNION ALL SELECT 'knowledge:write' UNION ALL SELECT 'knowledge:admin' UNION ALL SELECT 'knowledge:search' ) sub ) WHERE id = 'super_admin' AND permissions NOT LIKE '%knowledge:read%'; UPDATE roles SET permissions = ( SELECT '[' || string_agg('"' || elem || '"', ', ') || ']' FROM ( SELECT DISTINCT elem FROM json_array_elements_text(permissions::json) AS elem UNION ALL SELECT 'knowledge:read' UNION ALL SELECT 'knowledge:write' UNION ALL SELECT 'knowledge:search' ) sub ) WHERE id = 'admin' AND permissions NOT LIKE '%knowledge:read%';