Files
adminandClaude Fable 5 e783a26be6 feat(priv): A1 实现私有资料问答 P1-P3——vstore chunk 索引 + BFF 私有问答端点 + 前端并行块与管理界面
- P1 vstore: chunks.py 切块(600字/8段/重叠1/上限200,表格原位聚合,outline
  不参与)+ChunkIndex 三文件原子写+无降级档;embed_batch_chunked 批量16x并发4;
  vault add/delete/rebuild_chunks 钩子(chunk 失败不阻断 doc 级);search-chunks/
  rebuild-chunks 端点+health chunk_count;rebuild CLI
- P2 BFF: privateqa.ask_private(同doc≤3去重/12k截断/40s超时/带引用短答);
  POST /api/qa/private 三态;schemas PrivateDocBlock 入 union;client.search_chunks;
  llm timeout 参数;main.py 注册
- P3 前端: _firePrivate 并行检索+竞态防护(pending/flush/防重入/跨轮归属);
  AssistantMessage private_doc 块;i18n/private.js;PrivateLibraryPane 挂 DocPane
  空态;removeLibrary(204 兜底)

自测: vstore pytest 26/27(1 失败为 T1 骨架笔误待修)+74 回归;前端 vitest
373/373;npm run build 0 error;sdk 222 passed(3 failed 为基线预存);红线 diff 空。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-07 22:22:22 +08:00

90 lines
3.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""settings.py —— vstore 环境变量驱动配置(dataclass,不引 pydantic)。
环境变量键:
- VSTORE_KEY:Fernet key,必填(缺失抛异常拒绝启动)
- VSTORE_ROOT:数据根目录(默认 data/vstore)
- VSTORE_VAULT_PATH / VSTORE_INDEX_PATH:可覆盖(本地路径或 http(s):// URL)
- VSTORE_EMBED_URL:ollama 嵌入服务(默认 http://127.0.0.1:11434)
- VSTORE_EMBED_MODEL:嵌入模型(默认 bge-m3:567m)
"""
from __future__ import annotations
import os
from dataclasses import dataclass, field
from pathlib import Path
# ---- 生成参数(2026-09-04 手工测试:上传不再设大小/数量/字符门槛)----
# EMBED_BODY_CAP 仅是嵌入分块预算(向量化截断),不是上传限制;
# 原文件全文始终加密入库,不受影响。
MAX_SECTIONS = 12 # 模版大纲裁剪上限(节数)
MAX_OUTLINE_CHUNKS = 3 # 大纲嵌入切块上限
EMBED_BODY_CAP = 200_000 # 正文嵌入分块预算(字符)
# ---- 检索参数 ----
EMBED_DIM = 1024 # bge-m3:567m 输出维度
DEFAULT_TOP_K = 5
MAX_TOP_K = 20
OUTLINE_EMBED_MAX_CHARS = 7500 # 大纲超过此长度按节切块(≤MAX_OUTLINE_CHUNKS)
FALLBACK_LATEST_N = 8 # 三档降级最后一档:最新 N 条
# ---- 嵌入服务 ----
DEFAULT_EMBED_URL = "http://127.0.0.1:11434"
DEFAULT_EMBED_MODEL = "bge-m3:567m"
EMBED_CONCURRENCY = 8 # embed_batch 并发上限
EMBED_TIMEOUT = 60.0
@dataclass
class VstorePaths:
"""vstore 数据路径(vault/index 可分别指向本地目录或远程 http(s):// 根)。"""
root: str = field(default_factory=lambda: os.environ.get("VSTORE_ROOT", "data/vstore"))
vault_path: str = field(default_factory=lambda: os.environ.get("VSTORE_VAULT_PATH", ""))
index_path: str = field(default_factory=lambda: os.environ.get("VSTORE_INDEX_PATH", ""))
chunk_path: str = field(default_factory=lambda: os.environ.get("VSTORE_CHUNK_PATH", ""))
embed_url: str = field(default_factory=lambda: os.environ.get("VSTORE_EMBED_URL", DEFAULT_EMBED_URL))
embed_model: str = field(default_factory=lambda: os.environ.get("VSTORE_EMBED_MODEL", DEFAULT_EMBED_MODEL))
def __post_init__(self) -> None:
# vault/index 未显式指定时落在 root 下
if not self.vault_path:
self.vault_path = str(Path(self.root) / "vault")
if not self.index_path:
self.index_path = str(Path(self.root) / "index")
if not self.chunk_path:
self.chunk_path = str(Path(self.root) / "chunks")
def get_fernet_key() -> bytes:
"""VSTORE_KEY(Fernet key,必填)。缺失抛异常(阻止启动)。
生成方式:
python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
"""
raw = os.environ.get("VSTORE_KEY", "")
if not raw:
raise RuntimeError(
"缺少环境变量 VSTORE_KEY(Fernet key,必填)。生成:"
'python -c "from cryptography.fernet import Fernet; '
'print(Fernet.generate_key().decode())"'
)
return raw.encode("utf-8")
_paths: VstorePaths | None = None
def get_paths() -> VstorePaths:
"""取全局路径配置(首次调用构造)。"""
global _paths
if _paths is None:
_paths = VstorePaths()
return _paths
def set_paths(paths: VstorePaths) -> None:
"""注入配置(测试/启动用)。"""
global _paths
_paths = paths