From f769f8da4c789b44efc0cf08eec39a796c68a365 Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Tue, 8 Sep 2026 10:13:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(db):=20=E9=83=A8=E7=BD=B2=E4=B8=93?= =?UTF-8?q?=E5=B1=9E=E8=AE=B0=E5=BF=86=E4=B8=8E=E5=A4=9A=E7=BA=A7=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87=E6=95=B0=E6=8D=AE=E5=BA=93=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E4=B8=8Epgvector=E5=88=9D=E5=A7=8B=E5=8C=96Schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- deploy/postgres/context_init.sql | 167 +++++++++++++++++++++++++++++++ docker-compose.dev.yml | 22 ++-- internal/config/config.go | 48 +++++++++ 3 files changed, 226 insertions(+), 11 deletions(-) create mode 100644 deploy/postgres/context_init.sql diff --git a/deploy/postgres/context_init.sql b/deploy/postgres/context_init.sql new file mode 100644 index 00000000..a57c5c9f --- /dev/null +++ b/deploy/postgres/context_init.sql @@ -0,0 +1,167 @@ +-- ============================================================================= +-- 医疗器械商务与研发智能参谋系统 - 专属记忆与多级上下文数据库初始化脚本 +-- 运行环境:开发环境 (5433:5432) | 镜像:pgvector/pgvector:pg16 +-- 涵盖范围: +-- 1. 单用户偏好记忆 (User Preferences & Habits) +-- 2. 多用户与项目级单一事实黑板 (Project Blackboards & Decision Audit) +-- 3. 会话与多轮交互日志 (Chat Sessions & Multi-Turn Messages) +-- 4. CAS 内容寻址文件存储与引用去重 (Storage Blobs & File References) +-- 5. 动态向量化记忆切片 (Context Memory Chunks with Vector 1024 & Full-Text) +-- ============================================================================= + +CREATE EXTENSION IF NOT EXISTS vector; +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- ----------------------------------------------------------------------------- +-- 1. 用户与工作习惯偏好表 (User Profiles & Habits) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS users ( + id BIGSERIAL PRIMARY KEY, + account_id TEXT NOT NULL UNIQUE, + name TEXT NOT NULL DEFAULT '', + role TEXT NOT NULL DEFAULT 'engineer', -- pm, director, regulatory, engineer, sales + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS user_preferences ( + user_id BIGINT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + communication_style TEXT NOT NULL DEFAULT 'concise_structured', -- concise_structured, detailed_analytical, executive_summary + focus_areas TEXT[] NOT NULL DEFAULT '{}', -- 例如: {"cost_budget", "reg_compliance", "timeline"} + output_format_pref TEXT NOT NULL DEFAULT 'standard_report', -- standard_report, bilingual_brief, ppt_bullet + custom_instructions TEXT NOT NULL DEFAULT '', -- 用户明确要求的定制化引导 + memory_facts JSONB NOT NULL DEFAULT '{}'::jsonb, -- 异步蒸馏出的长期偏好画像事实 + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- ----------------------------------------------------------------------------- +-- 2. 项目与统一事实黑板表 (Projects & Blackboard SSOT) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS projects ( + id BIGSERIAL PRIMARY KEY, + code TEXT NOT NULL UNIQUE, + name TEXT NOT NULL, + customer_name TEXT NOT NULL DEFAULT '', + device_type TEXT NOT NULL DEFAULT '', + intended_use TEXT NOT NULL DEFAULT '', + target_market TEXT NOT NULL DEFAULT '', + current_stage TEXT NOT NULL DEFAULT 'V0.1_triage', + environment TEXT NOT NULL DEFAULT 'dev', + status TEXT NOT NULL DEFAULT 'active', + metadata_json JSONB NOT NULL DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS project_blackboards ( + id BIGSERIAL PRIMARY KEY, + project_id BIGINT NOT NULL UNIQUE REFERENCES projects(id) ON DELETE CASCADE, + facts JSONB NOT NULL DEFAULT '{}'::jsonb, + version INT NOT NULL DEFAULT 1, + last_updated_by TEXT NOT NULL DEFAULT 'system', + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +-- ----------------------------------------------------------------------------- +-- 3. 多用户协同活动流与决策审计表 (Project Activities & Decisions) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS project_activities ( + id BIGSERIAL PRIMARY KEY, + project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, + user_name TEXT NOT NULL DEFAULT '', + activity_type TEXT NOT NULL, -- chat, decision, operation, upload, download, stage_advance + title TEXT NOT NULL, + detail JSONB NOT NULL DEFAULT '{}'::jsonb, + weight NUMERIC(4,2) NOT NULL DEFAULT 1.00, -- 初始重要度权重 (决策为 1.0,闲聊为 0.2) + is_milestone BOOLEAN NOT NULL DEFAULT FALSE, -- 里程碑事件不衰减 + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_project_activities_pid_time ON project_activities(project_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_project_activities_uid ON project_activities(user_id); + +-- ----------------------------------------------------------------------------- +-- 4. 会话与多轮交互日志表 (Chat Sessions & Messages) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS chat_sessions ( + id BIGSERIAL PRIMARY KEY, + session_id TEXT NOT NULL UNIQUE, + project_id BIGINT REFERENCES projects(id) ON DELETE CASCADE, + user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, + title TEXT NOT NULL DEFAULT '新对话', + current_stage TEXT NOT NULL DEFAULT '', + pending_exp JSONB NOT NULL DEFAULT '{}'::jsonb, + status TEXT NOT NULL DEFAULT 'active', + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_chat_sessions_pid ON chat_sessions(project_id); +CREATE INDEX IF NOT EXISTS idx_chat_sessions_uid ON chat_sessions(user_id); + +CREATE TABLE IF NOT EXISTS chat_messages ( + id BIGSERIAL PRIMARY KEY, + session_id TEXT NOT NULL REFERENCES chat_sessions(session_id) ON DELETE CASCADE, + user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, + role TEXT NOT NULL, -- user, assistant, system + content TEXT NOT NULL, + intent TEXT NOT NULL DEFAULT '', + branch TEXT NOT NULL DEFAULT '', + metadata JSONB NOT NULL DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_chat_messages_sid_time ON chat_messages(session_id, created_at ASC); + +-- ----------------------------------------------------------------------------- +-- 5. CAS 物理文件寻址与引用去重表 (Storage Blobs & File References) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS storage_blobs ( + sha256 TEXT PRIMARY KEY, -- 文件 SHA-256 哈希值 + size_bytes BIGINT NOT NULL, -- 物理字节数 + mime_type TEXT NOT NULL DEFAULT 'application/octet-stream', + storage_path TEXT NOT NULL, -- 宿主机相对物理路径 (如 storage/blobs/ab/cd/xxx.bin) + ref_count INT NOT NULL DEFAULT 1, -- 被引用次数 + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS file_references ( + id BIGSERIAL PRIMARY KEY, + project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, + blob_sha256 TEXT NOT NULL REFERENCES storage_blobs(sha256) ON DELETE RESTRICT, + file_name TEXT NOT NULL, -- 用户原文件名 + file_category TEXT NOT NULL DEFAULT 'material', -- material, standard, cad, quote, deliverable + summary TEXT NOT NULL DEFAULT '', -- 自动提取的文档摘要 + metadata JSONB NOT NULL DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_file_references_proj ON file_references(project_id); +CREATE INDEX IF NOT EXISTS idx_file_references_blob ON file_references(blob_sha256); + +-- ----------------------------------------------------------------------------- +-- 6. 动态向量化记忆切片表 (Context Memory Chunks: User / Project / Agent) +-- ----------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS context_memory_chunks ( + id BIGSERIAL PRIMARY KEY, + domain TEXT NOT NULL, -- user_memory, project_doc, project_activity, chat_summary + project_id BIGINT REFERENCES projects(id) ON DELETE CASCADE, + user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, + reference_id TEXT NOT NULL DEFAULT '', -- 关联的外键标识 (如 file_id, session_id, activity_id) + title TEXT NOT NULL DEFAULT '', + content TEXT NOT NULL, + chunk_hash TEXT NOT NULL DEFAULT '', -- 文本哈希防重复向量化 + embedding vector(1024), -- bge-m3 1024 维向量 + tsv tsvector, -- 中文/英文全文检索分词 + importance_weight NUMERIC(4,2) NOT NULL DEFAULT 1.00, + metadata JSONB NOT NULL DEFAULT '{}'::jsonb, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_mem_chunks_domain_proj ON context_memory_chunks(domain, project_id, created_at DESC); +CREATE INDEX IF NOT EXISTS idx_mem_chunks_user ON context_memory_chunks(user_id, domain); +CREATE INDEX IF NOT EXISTS idx_mem_chunks_hash ON context_memory_chunks(chunk_hash); +CREATE INDEX IF NOT EXISTS idx_mem_chunks_tsv ON context_memory_chunks USING gin (tsv); +CREATE INDEX IF NOT EXISTS idx_mem_chunks_vec ON context_memory_chunks USING hnsw (embedding vector_cosine_ops); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index bc6ed967..22939956 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -9,20 +9,20 @@ name: agent-dev services: system-db: - image: postgres:16-alpine - container_name: shuimu-system-pg-dev + image: pgvector/pgvector:pg16 + container_name: shuimu-context-pg-dev command: ["postgres", "-c", "shared_buffers=256MB", "-c", "max_connections=100"] environment: - POSTGRES_USER: ${SYSTEM_DB_USER:-app} - POSTGRES_PASSWORD: ${SYSTEM_DB_PASSWORD:-dev_secret_db_pass} - POSTGRES_DB: ${SYSTEM_DB_NAME:-agent_system_dev} + POSTGRES_USER: ${CONTEXT_DB_USER:-${SYSTEM_DB_USER:-app}} + POSTGRES_PASSWORD: ${CONTEXT_DB_PASSWORD:-${SYSTEM_DB_PASSWORD:-dev_secret_db_pass}} + POSTGRES_DB: ${CONTEXT_DB_NAME:-${SYSTEM_DB_NAME:-agent_system_dev}} ports: - - "127.0.0.1:${SYSTEM_DB_PUBLISH_PORT:-5433}:5432" + - "127.0.0.1:${CONTEXT_DB_PUBLISH_PORT:-${SYSTEM_DB_PUBLISH_PORT:-5433}}:5432" volumes: - - dev-system-pgdata:/var/lib/postgresql/data - - ./deploy/postgres/system_init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro + - dev-context-pgdata:/var/lib/postgresql/data + - ./deploy/postgres/context_init.sql:/docker-entrypoint-initdb.d/01-init.sql:ro healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${SYSTEM_DB_USER:-app} -d ${SYSTEM_DB_NAME:-agent_system_dev}"] + test: ["CMD-SHELL", "pg_isready -U ${CONTEXT_DB_USER:-${SYSTEM_DB_USER:-app}} -d ${CONTEXT_DB_NAME:-${SYSTEM_DB_NAME:-agent_system_dev}}"] interval: 5s timeout: 5s retries: 20 @@ -115,7 +115,7 @@ networks: external: true volumes: - dev-system-pgdata: - name: agent-dev-system-pgdata + dev-context-pgdata: + name: agent-dev-context-pgdata dev-aux-sqlite-data: name: agent-dev-aux-sqlite-data diff --git a/internal/config/config.go b/internal/config/config.go index 445dce72..53a20b3b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -30,6 +30,13 @@ type Config struct { KbDbPassword string `json:"kb_db_password"` KbDbName string `json:"kb_db_name"` + // 专属记忆与多级上下文数据库配置 + ContextDbHost string `json:"context_db_host"` + ContextDbPort int `json:"context_db_port"` + ContextDbUser string `json:"context_db_user"` + ContextDbPassword string `json:"context_db_password"` + ContextDbName string `json:"context_db_name"` + // 向量化 Embedding 服务配置 EmbeddingEndpointURL string `json:"embedding_endpoint_url"` EmbeddingProvider string `json:"embedding_provider"` @@ -64,6 +71,12 @@ func Load(path string) (*Config, error) { KbDbPassword: "shared_kb_secret_pass", KbDbName: "regulations", + ContextDbHost: "127.0.0.1", + ContextDbPort: 5433, + ContextDbUser: "app", + ContextDbPassword: "dev_secret_db_pass", + ContextDbName: "agent_system_dev", + EmbeddingEndpointURL: "https://www.dmxapi.cn/v1/embeddings", EmbeddingProvider: "openai_compatible", EmbeddingModel: "bge-m3", @@ -148,6 +161,18 @@ func Load(path string) (*Config, error) { cfg.KbDbPassword = val case "KB_DB_NAME": cfg.KbDbName = val + case "CONTEXT_DB_HOST": + cfg.ContextDbHost = val + case "CONTEXT_DB_PORT": + if p, err := strconv.Atoi(val); err == nil { + cfg.ContextDbPort = p + } + case "CONTEXT_DB_USER": + cfg.ContextDbUser = val + case "CONTEXT_DB_PASSWORD": + cfg.ContextDbPassword = val + case "CONTEXT_DB_NAME": + cfg.ContextDbName = val case "EMBEDDING_ENDPOINT_URL": cfg.EmbeddingEndpointURL = val case "EMBEDDING_PROVIDER": @@ -214,6 +239,23 @@ func Load(path string) (*Config, error) { if v := os.Getenv("KB_DB_NAME"); v != "" { cfg.KbDbName = v } + if v := os.Getenv("CONTEXT_DB_HOST"); v != "" { + cfg.ContextDbHost = v + } + if v := os.Getenv("CONTEXT_DB_PORT"); v != "" { + if p, err := strconv.Atoi(v); err == nil { + cfg.ContextDbPort = p + } + } + if v := os.Getenv("CONTEXT_DB_USER"); v != "" { + cfg.ContextDbUser = v + } + if v := os.Getenv("CONTEXT_DB_PASSWORD"); v != "" { + cfg.ContextDbPassword = v + } + if v := os.Getenv("CONTEXT_DB_NAME"); v != "" { + cfg.ContextDbName = v + } if v := os.Getenv("EMBEDDING_ENDPOINT_URL"); v != "" { cfg.EmbeddingEndpointURL = v } @@ -240,3 +282,9 @@ func Load(path string) (*Config, error) { func (c *Config) KbDSN() string { return "postgres://" + c.KbDbUser + ":" + c.KbDbPassword + "@" + c.KbDbHost + ":" + strconv.Itoa(c.KbDbPort) + "/" + c.KbDbName + "?sslmode=disable" } + +// ContextDSN 返回专属记忆与多级上下文 PostgreSQL 连接字符串 +func (c *Config) ContextDSN() string { + return "postgres://" + c.ContextDbUser + ":" + c.ContextDbPassword + "@" + c.ContextDbHost + ":" + strconv.Itoa(c.ContextDbPort) + "/" + c.ContextDbName + "?sslmode=disable" +} +