chore: scaffold md-vector-db project structure

This commit is contained in:
2026-07-05 00:48:31 +08:00
parent 77d9ec6570
commit 3eb4b30858
11 changed files with 4655 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
__pycache__/
*.pyc
data/
.env
*.egg-info/
.pytest_cache/
+17
View File
@@ -0,0 +1,17 @@
chroma:
persist_dir: ./data
collection_name: markdown_docs
embed:
mode: local # local | api
local_model: BAAI/bge-small-zh-v1.5
api_base: "" # api 模式下填写
api_key: "" # api 模式下填写
chunk:
max_size: 1000 # 分块最大字符数
overlap: 100 # 相邻块重叠字符数
server:
host: 0.0.0.0
port: 8000
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,268 @@
# md-vector-db 设计文档
**日期**: 2026-07-05
**作者**: 刘航宇
**状态**: 已确认
## 1. 项目概述
构建一个 Markdown 文档向量数据库,支持文档入库、嵌入、检索,并通过 HTTP API 供其他项目调用。
### 核心目标
- 支持 ~1000 篇 Markdown 文档的存储与检索
- 混合嵌入方案:默认本地模型,可切换到 API
- HTTP API 对外暴露检索能力
- CLI 命令行管理工具
## 2. 技术栈
| 层 | 选型 | 理由 |
| --------- | -------------------------------------- | -------------------------------- |
| 语言 | Python 3.13 | 向量库生态最成熟 |
| 向量库 | ChromaDB | 嵌入式运行,也支持 HTTP 服务模式 |
| 嵌入模型 | `BAAI/bge-small-zh-v1.5`(本地默认) | 中文优化,仅 100MB |
| 嵌入 API | OpenAI 兼容协议 | 可切到 DashScope/硅基流动/OpenAI |
| HTTP 框架 | FastAPI + uvicorn | 轻量高性能 |
| 包管理 | uv | 用户标准工具链 |
## 3. 目录结构
```
md-vector-db/
├── pyproject.toml
├── config.yaml # 用户配置文件
├── src/
│ ├── core/
│ │ ├── __init__.py
│ │ ├── db.py # ChromaDB 初始化与配置
│ │ ├── embedder.py # 嵌入模型抽象(本地/API)
│ │ ├── ingest.py # Markdown 解析→分块→入库
│ │ └── search.py # 检索接口
│ ├── server/
│ │ ├── __init__.py
│ │ └── app.py # FastAPI 路由
│ └── cli/
│ ├── __init__.py
│ └── main.py # 命令行入口
├── data/ # ChromaDB 持久化目录
├── md_docs/ # 待入库的 Markdown 源文件
├── scripts/
│ └── serve.py # 启动 HTTP 服务
└── tests/
├── test_embedder.py
├── test_ingest.py
├── test_search.py
└── test_api.py
```
## 4. 核心模块设计
### 4.1 embedder.py — 嵌入层
```python
class Embedder:
def __init__(self, config: EmbedConfig):
if config.mode == "local":
self.model = SentenceTransformer(config.local_model)
elif config.mode == "api":
self.client = OpenAI(base_url=config.api_base, api_key=config.api_key)
def embed(self, texts: list[str]) -> list[list[float]]:
"""返回嵌入向量列表"""
@property
def dimension(self) -> int:
"""返回向量维度"""
```
- `local` 模式:首次运行自动下载 `BAAI/bge-small-zh-v1.5`
- `api` 模式:兼容 OpenAI/DashScope/硅基流动(OpenAI 协议)
- 切换方式:修改 `config.yaml` 中的 `embed.mode`
### 4.2 ingest.py — 文档入库
**分块策略(混合切分):**
1. 按 Markdown 标题(`#`/`##`/`###`)拆分章节
2. 若章节超过 1000 字符,按段落边界(`\n\n`)继续拆分
3. 每个 chunk 保留元数据:`source_file`, `section_title`, `heading_level`, `chunk_index`
**入库流程:**
```
Markdown 文件 → 读取 → 提取标题结构 → 分块
→ 逐块 embed → ChromaDB collection.add()
```
**去重:** 同一文件重复入库时,先删除旧 chunks 再重新插入(以 `source_file` 为键)。
### 4.3 search.py — 检索
```python
def search(query: str, top_k: int = 10, filters: dict | None = None) -> list[SearchResult]:
"""
- query 嵌入 → ChromaDB 相似度搜索
- 支持按 source_file、heading_level 过滤
- 返回: [(content, source_file, section_title, score), ...]
"""
```
### 4.4 db.py — 数据库层
```python
class VectorDB:
def __init__(self, persist_dir: str = "./data"):
self.client = chromadb.PersistentClient(path=persist_dir)
def get_or_create_collection(self, name: str) -> Collection:
...
```
- ChromaDB 数据持久化到 `data/` 目录
- 默认 collection 名称:`markdown_docs`
## 5. HTTP API
### 端点设计
| 方法 | 路径 | 说明 |
| ---------- | --------------------------------- | ------------------------------------- |
| `POST` | `/api/v1/ingest` | 入库 Markdown(传文件路径或原始内容) |
| `POST` | `/api/v1/search` | 语义检索 |
| `GET` | `/api/v1/collections` | 列出所有文档集合 |
| `DELETE` | `/api/v1/documents/{file_name}` | 删除指定文件的文档 |
| `GET` | `/api/v1/health` | 健康检查 |
### 请求/响应示例
**入库:**
```json
POST /api/v1/ingest
{
"file_path": "D:/docs/my-note.md"
}
// 或
{
"content": "# 标题\n正文内容...",
"file_name": "my-note.md"
}
{ "status": "ok", "chunks": 12, "file": "my-note.md" }
```
**检索:**
```json
POST /api/v1/search
{
"query": "如何配置向量数据库",
"top_k": 10
}
{
"results": [
{
"content": "...匹配片段...",
"source_file": "setup-guide.md",
"section_title": "向量数据库配置",
"heading_level": 2,
"score": 0.923
}
]
}
```
### 启动方式
```bash
# 嵌入式模式(直接调用 core 模块)
python -c "from src.core.search import search; print(search('关键词'))"
# HTTP 服务模式(供其他项目调用)
uv run scripts/serve.py --port 8000
```
## 6. CLI 工具
```bash
# 入库单个文件
uv run src/cli/main.py ingest path/to/file.md
# 入库整个目录
uv run src/cli/main.py ingest-dir ./md_docs/
# 命令行搜索
uv run src/cli/main.py search "关键词" --top-k 10
# 启动服务
uv run src/cli/main.py serve --port 8000
# 查看统计
uv run src/cli/main.py stats
```
## 7. 配置设计
```yaml
# config.yaml
chroma:
persist_dir: ./data
collection_name: markdown_docs
embed:
mode: local # local | api
local_model: BAAI/bge-small-zh-v1.5
api_base: "" # api 模式下填写
api_key: "" # api 模式下填写
chunk:
max_size: 1000 # 分块最大字符数
overlap: 100 # 相邻块重叠字符数
server:
host: 0.0.0.0
port: 8000
```
## 8. 依赖
```toml
[project]
name = "md-vector-db"
version = "0.1.0"
requires-python = ">=3.13"
dependencies = [
"chromadb>=0.5.0",
"sentence-transformers>=3.0.0",
"fastapi>=0.115.0",
"uvicorn[standard]>=0.30.0",
"pyyaml>=6.0",
"markdown-it-py>=3.0.0",
"typer>=0.12.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"httpx>=0.27.0",
]
```
## 9. 测试计划
| 测试 | 覆盖 |
| -------------------- | ------------------------------- |
| `test_embedder.py` | 本地/API 模式嵌入,维度验证 |
| `test_ingest.py` | Markdown 解析、分块、入库、去重 |
| `test_search.py` | 检索、元数据过滤 |
| `test_api.py` | FastAPI 端点集成测试 |
目标覆盖率:80%+
## 10. 自检清单
- [X] 无 TBD/TODO 占位符
- [X] 模块间接口定义清晰(core/server/cli 分层)
- [X] 功能范围明确(入库、检索、CLI、HTTP API
- [X] 无内部矛盾(本地模型+API 双模、嵌入+服务双模)
- [X] 已确定分块策略(混合切分:标题+段落)
+31
View File
@@ -0,0 +1,31 @@
[project]
name = "md-vector-db"
version = "0.1.0"
description = "Markdown 文档向量数据库,支持语义检索"
requires-python = ">=3.13"
dependencies = [
"chromadb>=0.5.0",
"sentence-transformers>=3.0.0",
"fastapi>=0.115.0",
"uvicorn[standard]>=0.30.0",
"pyyaml>=6.0",
"markdown-it-py>=3.0.0",
"typer>=0.12.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.0",
"httpx>=0.27.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
View File
View File
View File
View File
View File
Generated
+2605
View File
File diff suppressed because it is too large Load Diff