From f61f631d71411861c848a042a581dc12b695ca4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E8=88=AA=E5=AE=87?= <3364451258@qq.com> Date: Sun, 5 Jul 2026 01:47:38 +0800 Subject: [PATCH] docs: update CLAUDE.md to reflect current architecture --- CLAUDE.md | 89 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index eef6618..3f3ce92 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,73 +10,84 @@ Markdown 文档向量数据库 — 将 .md 文件分块 → 嵌入 → 存入 Ch ```bash uv sync # 安装依赖 -uv run pytest tests/ -v # 全部测试 (31 个) +uv run pytest tests/ -v # 全部测试 (46 个) uv run pytest tests/test_api.py -v # 单个测试模块 uv run pytest tests/ -v -k "test_search" # 按名称过滤 -# CLI -uv run python -m src.cli.main ingest # 入库单文件 -uv run python -m src.cli.main ingest-dir ./md_docs/ # 批量入库 -uv run python -m src.cli.main search "关键词" # 搜索 -uv run python -m src.cli.main stats # 统计 -uv run python -m src.cli.main serve --port 8000 # 启动 HTTP 服务 +# CLI(所有命令支持 --config/-c 指定配置文件) +uv run python -m src.cli.main ingest +uv run python -m src.cli.main ingest-dir ./md_docs/ +uv run python -m src.cli.main search "关键词" -k 5 +uv run python -m src.cli.main stats +uv run python -m src.cli.main serve --port 8000 ``` ## 架构 ``` -core/ # 核心逻辑(不依赖 server/cli) -├── config.py # YAML → dataclass (AppConfig) -├── db.py # VectorDB: ChromaDB PersistentClient 封装 -├── embedder.py # Embedder: 本地模型(BGE-small-zh) + API 双模 -├── ingest.py # MarkdownSplitter(混合分块) + DocumentIngestor -└── search.py # Searcher: 语义检索 + 源文件管理 +core/ # 核心逻辑(不依赖 server/cli) +├── config.py # YAML → dataclass, load_dotenv() 加载 .env +├── db.py # VectorDB: 线程安全的 ChromaDB 封装 +├── embedder.py # 策略模式: LocalEmbedder / OpenAIEmbedder / DashscopeEmbedder +├── ingest.py # MarkdownSplitter(混合分块) + DocumentIngestor(分批嵌入) +└── search.py # Searcher: 语义检索 + 源文件管理 -server/app.py # FastAPI HTTP 层,懒加载单例 -cli/main.py # Typer CLI,5 个命令 +server/ # FastAPI HTTP 层 +├── app.py # Depends(get_state) 依赖注入, 速率限制中间件 +├── deps.py # AppState: 集中管理 db/embedder/searcher/ingestor +└── auth.py # API Key 认证 (MD_VECTOR_API_KEY) + 速率限制器 + +cli/main.py # Typer CLI,5 个命令 + --config 选项 ``` -**数据流**: MD 文件 → `MarkdownSplitter.split()` (标题→段落分块) → `Embedder.embed()` → `ChromaDB collection.add()` → `Searcher.search()` 查询 +**数据流**: MD 文件 → `MarkdownSplitter.split()` → `batch_embed()` → `ChromaDB collection.add()` → `Searcher.search()` **依赖方向**: `config` ← `db` ← `embedder` ← `ingest`/`search` ← `server`/`cli` ## 关键实现细节 -### 嵌入模型回退策略 (embedder.py) +### 嵌入 Provider 架构 (embedder.py) -本地模式优先从缓存加载(`local_files_only=True`);若模型未下载,自动切换到 `hf-mirror.com` 下载后恢复离线。模块级 `os.environ.setdefault("HF_HUB_OFFLINE", "1")` 避免 Windows SSL 证书问题。 +策略模式,接口 `Embedder(Protocol)`: -### 混合分块策略 (ingest.py) +| Provider | 类 | API 格式 | 默认模型 | +|----------|-----|---------|----------| +| `local` | `LocalEmbedder` | sentence-transformers | BAAI/bge-small-zh-v1.5 | +| `openai` | `OpenAIEmbedder` | OpenAI 兼容 | text-embedding-3-small | +| `dashscope` | `DashscopeEmbedder` | 阿里云自定义 HTTP | text-embedding-v4 | -`MarkdownSplitter`: 先按 `#`/`##`/`###` 标题拆分 → 章节超 `max_size`(1000) 则按 `\n\n` 段落拆 → 单个段落仍超限则按句号/感叹号硬切,所有拆分保留 overlap。 +`config.api_base` / `config.model` 可覆盖默认值。`batch_embed()` 分批嵌入(每批 32 条)防 OOM。本地模型未缓存时自动通过 `hf-mirror.com` 下载。 -### 配置加载 (config.py) +### 线程安全 (db.py + ingest.py + search.py) -`load_config()` 从 YAML 读取 → 文件不存在返回全默认值 `AppConfig()`。`AppConfig.__init__(**kwargs)` 将字典递归分发到子 dataclass (`ChromaConfig`/`EmbedConfig`/`ChunkConfig`/`ServerConfig`)。 +`VectorDB._write_lock` (`threading.Lock`) 保护所有写操作(`collection.add` / `delete` / `get+delete`)。 -### 服务端单例 (server/app.py) +### 认证与安全 (auth.py + app.py) -`VectorDB`、`Embedder`、`Searcher`、`DocumentIngestor` 使用模块级懒加载单例,避免 import 时就加载模型。通过 `MD_VECTOR_DB_DATA_DIR` 和 `MD_VECTOR_DB_COLLECTION` 环境变量覆盖配置。 +- **API Key**: 环境变量 `MD_VECTOR_API_KEY` → `verify_api_key` 依赖注入到 ingest/search/delete 端点;未设置则跳过 +- **速率限制**: `RateLimiter` 中间件,默认 60s 窗口内最多 30 请求 +- **路径遍历防护**: `_is_safe_path()` 拒绝绝对路径和 `..` 穿越 +- **错误信息**: 500 返回通用消息,详细错误记入 `logger.exception` -### Windows 终端编码 (cli/main.py) +### 配置系统 (config.py) -`sys.stdout` 强制重编码为 UTF-8(`io.TextIOWrapper`),避免 emoji 在 GBK 终端上报 `UnicodeEncodeError`。 +`config.py` 顶部 `load_dotenv()` 自动加载 `.env`。`EMBED_API_KEY` 从环境变量读取。`DEFAULT_CONFIG_PATH = "config.yaml"` 统一引用。 + +### 服务层 (deps.py + app.py) + +`AppState` 类集中管理 `db`/`embedder`/`searcher`/`ingestor` 单例。端点通过 `FastAPI Depends(get_state)` 获取,可测试、可替换。`is_healthy()` 真实验证 ChromaDB 和 Embedder 可用性。 ## 配置 -`config.yaml` 控制: -- `chroma.persist_dir` / `collection_name` — ChromaDB 存储 -- `embed.mode` — `local`(BGE-small-zh-v1.5) 或 `api`(OpenAI 兼容) -- `chunk.max_size` / `overlap` — 分块参数 -- `server.host` / `port` — HTTP 服务 +`config.yaml` + `.env` 联合控制。复制 `.env.example` 为 `.env` 填入密钥。 ## HTTP API -| 方法 | 路径 | 说明 | -|------|------|------| -| GET | `/` | 重定向到 /docs (Swagger) | -| GET | `/api/v1/health` | 健康检查 | -| GET | `/api/v1/collections` | 列出集合和源文件 | -| POST | `/api/v1/ingest` | 入库 (file_path 或 content+file_name) | -| POST | `/api/v1/search` | 语义检索 (query + top_k) | -| DELETE | `/api/v1/documents/{file_name}` | 按文件名删除 | +| 方法 | 路径 | 认证 | 说明 | +|------|------|------|------| +| GET | `/` | - | 重定向到 /docs | +| GET | `/api/v1/health` | - | 真实健康检查 (ChromaDB + Embedder) | +| GET | `/api/v1/collections` | - | 集合和源文件列表 | +| POST | `/api/v1/ingest` | API Key | 入库 (file_path 或 content+file_name) | +| POST | `/api/v1/search` | API Key | 语义检索 (query + top_k 1-100) | +| DELETE | `/api/v1/documents/{file_name}` | API Key | 按文件名删除 |