-- ============================================================================= -- Vignette · 03_kb.sql -- kb 스키마: 정적 지식 KB (DSM/이론/technique/taxonomy) — app 과 물리 분리 -- ⑤ KNOWLEDGE 계층. 3-AI 가 같은 물리 테이블을 다른 정책(visible_to+sensitivity)으로 검색. -- 근거: MEMORY_KNOWLEDGE_PERSONA_DESIGN.md §3.6, §4.3 / MASTERPLAN §3.6 -- 전제: 02_schema.sql (app.technique_label_def 존재). 적용 순서: 02 → 03(본 파일). -- 원칙: app 으로의 FK 없음(read-only 독립). license_class C/D = 외부 LLM 차단. -- ============================================================================= CREATE SCHEMA IF NOT EXISTS kb; CREATE EXTENSION IF NOT EXISTS vector; CREATE EXTENSION IF NOT EXISTS pg_trgm; SET search_path TO kb, public; -- ============================================================================= -- 1. 출처 + 라이선스 (DSM-5 저작권 1차 통제, 설계서 §3.6) -- ============================================================================= CREATE TABLE IF NOT EXISTS kb.source ( source_id TEXT PRIMARY KEY, -- 'dsm5','theory','taxonomy_0615' title TEXT NOT NULL, kb_kind TEXT NOT NULL CHECK (kb_kind IN ('diagnostic','theory','technique','taxonomy', 'supervisor_pattern','template','ko_context','microskill')), license_class CHAR(1) NOT NULL CHECK (license_class IN ('A','B','C','D')), -- C=저작권민감 D=미성년파생 origin_path TEXT, citation TEXT, external_llm_ok BOOLEAN NOT NULL DEFAULT FALSE, -- C/D면 false → 국내 라우팅 created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- ============================================================================= -- 2. 문서 + 버전 (graphify content_hash 증분 인덱싱 차용) -- ============================================================================= CREATE TABLE IF NOT EXISTS kb.document ( doc_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, source_id TEXT NOT NULL REFERENCES kb.source(source_id), doc_uri TEXT NOT NULL, version INT NOT NULL DEFAULT 1, content_hash TEXT NOT NULL, -- SHA256 → 변경감지(증분) superseded_by BIGINT REFERENCES kb.document(doc_id), is_active BOOLEAN NOT NULL DEFAULT TRUE, indexed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (source_id, doc_uri, version) ); CREATE INDEX IF NOT EXISTS idx_kb_doc_active ON kb.document(source_id) WHERE is_active; -- ============================================================================= -- 3. 청크 + 멀티벡터 (검색 본체, 설계서 §3.6) -- visible_to + sensitivity 가 3-AI 정보비대칭을 DB WHERE 로 강제. -- ============================================================================= CREATE TABLE IF NOT EXISTS kb.chunk ( chunk_id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, doc_id BIGINT NOT NULL REFERENCES kb.document(doc_id) ON DELETE CASCADE, source_id TEXT NOT NULL, -- 비정규화(필터 가속) kb_kind TEXT NOT NULL, -- 비정규화 seq INT NOT NULL, heading_path TEXT, chunk_text TEXT NOT NULL, -- 표시·LLM 주입(prefix 미포함) context_prefix TEXT, -- Contextual Retrieval 주입문 embedding vector(1024), -- BGE-M3 dense sparse_vec JSONB, -- BGE-M3 sparse colbert_vecs JSONB, -- 멀티벡터(단일 호출 산출) visible_to TEXT[] NOT NULL DEFAULT '{client,counselor,evaluator}', sensitivity SMALLINT NOT NULL DEFAULT 0 CHECK (sensitivity BETWEEN 0 AND 3), -- 0공개 1내부 2평가전용 3원천격리 label_id BIGINT REFERENCES app.technique_label_def(label_id), -- taxonomy 정답라벨 meta JSONB DEFAULT '{}', -- {dsm_category,theory,bias_weight,...} token_count INT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); -- dense ANN CREATE INDEX IF NOT EXISTS idx_chunk_hnsw ON kb.chunk USING hnsw (embedding vector_cosine_ops) WITH (m = 16, ef_construction = 64); -- sparse/BM25 (한국어: simple + pg_trgm 병용, M10 완화) CREATE INDEX IF NOT EXISTS idx_chunk_fts ON kb.chunk USING gin (to_tsvector('simple', chunk_text)); CREATE INDEX IF NOT EXISTS idx_chunk_trgm ON kb.chunk USING gin (chunk_text gin_trgm_ops); -- 정보비대칭 사전필터 CREATE INDEX IF NOT EXISTS idx_chunk_visible ON kb.chunk USING gin (visible_to); CREATE INDEX IF NOT EXISTS idx_chunk_route ON kb.chunk (kb_kind, sensitivity); CREATE INDEX IF NOT EXISTS idx_chunk_source ON kb.chunk (source_id); -- ============================================================================= -- 4. KB 검색 로그 (환각측정·캐시검증, 람다 cost.json 차용, 설계서 §3.7) -- ============================================================================= CREATE TABLE IF NOT EXISTS kb.retrieval_log ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, session_id UUID, turn_id UUID, ai_role TEXT NOT NULL, -- 'client'|'counselor'|'evaluator' query_text TEXT, policy TEXT NOT NULL, -- 4-튜플 정책명 hit_chunk_ids BIGINT[], rerank_scores REAL[], top1_score REAL, -- CRAG 게이트 used_in_answer BOOLEAN, latency_ms INT, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_kb_retr_session ON kb.retrieval_log(session_id);