# slim-rag **Repository Path**: polewt/slim-rag ## Basic Information - **Project Name**: slim-rag - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: release - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-02 - **Last Updated**: 2026-09-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Slim-RAG [English](README.md) | [简体中文](README_zh.md) 模块化、基于 provider 的检索增强生成(RAG, Retrieval-Augmented Generation) 系统,采用接口驱动架构。流水线的每个阶段都是一个独立 provider,可以自由 替换与组合。 ## 特性 - **七个可插拔阶段** —— 输入、分块、向量化、向量存储、检索、重排与生成各自 封装在一个抽象基类之后,替换其中一个不会影响其它阶段。 - **基于注册的发现机制** —— 实现通过 `@register_provider(stage, name)` 自动登记, 注册表可在运行时通过 `GET /providers` 查询。 - **检索完全本地化、离线** —— 向量由 sentence-transformers 在本机计算,索引与 检索过程中没有任何文档内容离开本机。 - **开箱即用的两种向量存储** —— 用于测试与演示的内存存储,以及带 HNSW 索引的 PostgreSQL + pgvector。 - **启动快速失败(fail-fast)** —— 服务开始接收流量之前就完成流水线装配、校验 与索引;配置有误会直接中断启动,而不是悄悄用一个空索引对外服务。 - **可重复的端到端检查** —— `scripts/smoke.py` 会在进程内与 HTTP 两个层面跑通 整条流水线,两种存储各测一遍。 ## 输出约定(Output Convention) **重要:所有运行时输出必须是纯 ASCII,不允许 Unicode 输出。** 本项目运行在 Windows 的 GBK 控制台编码下,因此服务自身在运行时写出的每一处 字符串都必须限定在 ASCII 范围内:stdout/stderr、日志记录、本项目抛出的异常 消息,以及服务产出的 API 字段。**不要**在 `print`/`logging` 调用、错误消息或 服务构造的 API 响应体中使用 emoji、中文标点或任何其它非 ASCII 字符。 **源码侧的规则正好相反:所有注释与 docstring 必须用简体中文书写。** 注释和 docstring 永远不会进入 stdout,控制台编码影响不到它们;中文也是本项目书写自身 文档的语言。只有标识符、API 名、工具指令(`# noqa`、`# type: ignore`、 `# isort: off`)以及技术术语保留原文写法。 两条规则都不覆盖的内容: - `doc/` 下的 Markdown 文档与本 README(表格和目录树可以使用制表符号)。 - 来自项目外部的文本:用户提问与 LLM 生成的回答原样透传,可以包含任意 Unicode。 ## 项目目标 构建一个这样的 RAG 服务: - 用户通过 HTTP API 提问 - 问题在本机转换为向量 - 由向量存储执行相似度检索(生产环境用 pgvector,默认使用内存存储) - 检索到的分块被组装进 prompt - 由外部 LLM 生成最终回答 ## 技术栈 | 层次 | 选型 | |------|------| | 语言 | Python 3.10+ / uv | | HTTP 框架 | FastAPI | | 向量化 | sentence-transformers(本地、离线) | | 向量库 | PostgreSQL + pgvector(或内存存储) | | LLM | 外部 API(OpenAI 兼容) | | 部署 | Docker Compose | ## 环境要求 - Python 3.10 或更高版本(见 `.python-version`) - [uv](https://docs.astral.sh/uv/):依赖管理与执行 - 一个 OpenAI 兼容的 LLM 端点及其 API key - 可选:Docker + Docker Compose(使用随附的 PostgreSQL 时) - 可选:装有 `pgvector` 扩展的 PostgreSQL 15+ 服务(不使用 Docker 时) 首次运行会从 Hugging Face 下载向量模型 (`sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2`,约 470 MB)。 若该域名不可达,见[常见问题](#常见问题)。 ## 架构 流水线被拆成 7 个独立的 provider 阶段: ``` 索引: InputProvider -> Chunker -> Embedder -> VectorStore 查询: Retriever -> Reranker -> Generator ``` 每个阶段都有一个抽象基类。具体实现通过 `@register_provider(stage, name)` 装饰器注册,只需改动 `config.py` 中的一行即可替换。 ### 各阶段与实现 | 阶段 | 接口 | 现有实现 | |------|------|----------| | 输入 | `InputProvider` | `TextInputProvider`、`FileInputProvider` | | 分块 | `Chunker` | `RecursiveChunker`、`FixedSizeChunker` | | 向量化 | `Embedder` | `SentenceTransformerEmbedder` | | 存储 | `VectorStore` | `InMemoryVectorStore`、`PGVectorStore` | | 检索 | `Retriever` | `VectorRetriever` | | 重排 | `Reranker` | `IdentityReranker`、`CrossEncoderReranker` | | 生成 | `Generator` | `OpenAIGenerator` | ### 索引与检索流程 `RAGPipeline.setup()` 校验配置并准备存储。当存储声明了固定维度(pgvector 就是 如此)时,会在**创建任何表之前**与 `Embedder.dimension` 比对,不一致直接中断 启动,而不是建出一张永远写不进去的表。 `RAGPipeline.index()` 加载文档、切分为分块、计算向量并写入。文档与分块的 id 都由其来源确定性地推导,且 `index()` 是**替换**该文档的分块而不是追加 —— 因此 同一份输入重复索引是幂等的,而编辑文件会原地更新它的分块。 `RAGPipeline.query()` 对问题做向量化、检索最近的若干分块、可选地重排,然后请 生成器给出回答。回答会带上它的来源,每条来源都能追溯到对应的分块。 `RAGPipeline.teardown()` 释放 provider 资源;它会记录并吞掉单个 provider 的 失败,避免某个 provider 出错阻断其余清理。 ## 文档 本项目的强制性约束索引在 [doc/constraint.md](doc/constraint.md)。**请先读这份 索引** —— 它列出了 `doc/constraints/` 下的每一份文档,而每份文档对其覆盖的代码 都是强制性的。 | 文档 | 说明 | |------|------| | [constraints/architecture.md](doc/constraints/architecture.md) | 整体架构、数据流、设计原则 | | [constraints/models.md](doc/constraints/models.md) | 数据模型规格(Document、Chunk、ScoredChunk、Answer) | | [constraints/registry.md](doc/constraints/registry.md) | provider 注册与发现机制 | | [constraints/pipeline.md](doc/constraints/pipeline.md) | 流水线编排与生命周期 | | [constraints/providers/input.md](doc/constraints/providers/input.md) | InputProvider 接口与约束 | | [constraints/providers/chunker.md](doc/constraints/providers/chunker.md) | Chunker 接口与约束 | | [constraints/providers/embedder.md](doc/constraints/providers/embedder.md) | Embedder 接口与约束 | | [constraints/providers/vector-store.md](doc/constraints/providers/vector-store.md) | VectorStore 接口与约束 | | [constraints/providers/retriever.md](doc/constraints/providers/retriever.md) | Retriever 接口与约束 | | [constraints/providers/reranker.md](doc/constraints/providers/reranker.md) | Reranker 接口与约束 | | [constraints/providers/generator.md](doc/constraints/providers/generator.md) | Generator 接口与约束 | 每份规格文档都包含:接口定义、功能约束、异步约束、错误处理、参数、示例实现, 以及面向新实现的检查清单。 另外两个目录存放**非强制**材料: | 目录 | 用途 | |------|------| | [doc/proposals/](doc/proposals/README.md) | 尚未实现的想法与需求 | | [doc/archive/](doc/archive/README.md) | 已实现的计划与被取代的约束,用于溯源 | ## API ### `POST /api/v1/query` 请求: ```json { "query": "What is RAG?", "top_k": 5 } ``` 响应: ```json { "answer": "RAG is a retrieval-augmented generation architecture...", "sources": [ { "content": "RAG combines retrieval and generation...", "similarity": 0.87 } ] } ``` ### `GET /health` 健康检查端点。启动完成后返回 `{"status": "ok"}`。 ### `GET /providers` 按阶段列出全部已注册的 provider,例如: ```json { "input": ["text", "file"], "chunker": ["recursive", "fixed"], "embedder": ["sentence-transformer"], "vector_store": ["memory", "pgvector"], "retriever": ["vector"], "reranker": ["identity", "cross-encoder"], "generator": ["openai"] } ``` ## 快速开始 ### 本地开发 ```bash # 安装依赖 uv sync # 复制环境变量模板并填入你的配置。 # 必须设置:OPENAI_API_KEY、OPENAI_BASE_URL、OPENAI_MODEL —— 任何使用 # OpenAI 协议的服务都可以(base_url 必须是它的 /v1 端点)。 cp .env.example .env # 启动 API 服务(默认使用内存存储) uv run uvicorn slim_rag.main:app --host 127.0.0.1 --port 8000 --loop slim_rag.main:selector_loop_factory # 健康检查 curl http://127.0.0.1:8000/health ``` `.env` 会被自动加载(见 `src/slim_rag/env.py`),uvicorn、`uv run slim_rag` 与容器三种入口都适用。真实环境变量优先于该文件,因此容器/CI 注入不受影响。 `--loop slim_rag.main:selector_loop_factory` 在 Linux 与 macOS 上是空操作,但只要 向量库换成 PostgreSQL,它在 Windows 上就是必需的:uvicorn 0.52 在 Windows 上会把 `ProactorEventLoop` 交给事件循环,而 psycopg 的异步模式无法在其上运行。(带 `--reload` 时 uvicorn 不会这么做,这也是不加该参数时现象时好时坏的原因。) 启动是快速失败的。服务启动时会从 `config.py` 装配流水线、调用 `setup()` 并索引 配置的文档,然后才开始接收流量。若向量库无法初始化或索引过程抛错,启动直接失败 而不是用空索引对外服务;该失败会以 ERROR 级别记录。 ### 冒烟检查 `scripts/smoke.py` 是可重复的端到端检查:它先跑进程内流水线 (`setup -> index -> query`,断言回答非空、且每条来源都能追溯回已加载的文档), 再驱动一个真实的 uvicorn 子进程,依次打 `/health`、`/providers` 与 `POST /api/v1/query`。 ```bash # 内存存储,进程内 + HTTP uv run python scripts/smoke.py # 同样流程但走 PostgreSQL + pgvector(临时表 smoke_chunks,先删后建) uv run python scripts/smoke.py --store pgvector # 只跑进程内部分 uv run python scripts/smoke.py --skip-http ``` 它输出纯 ASCII 日志,失败时以非 0 退出。 ### Docker Compose(含 PostgreSQL) ```bash # 复制并编辑环境变量 cp .env.example .env # 编辑 .env,设置 OPENAI_API_KEY 等变量 # 启动服务 docker compose up -d # API 位于 http://localhost:8000 ``` ## 配置 provider 的装配写在 `config.py`,但**切换向量库是靠环境变量,而不是改代码**: ```bash # .env SLIM_RAG_VECTOR_STORE=pgvector # memory(默认)或 pgvector POSTGRES_TABLE=rag_chunks POSTGRES_HOST=127.0.0.1 POSTGRES_PORT=5432 POSTGRES_DB=rag POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres ``` 代码会读取的全部环境变量: | 变量 | 默认值 | 用途 | |------|--------|------| | `SLIM_RAG_VECTOR_STORE` | `memory` | 装配哪个 `VectorStore`:`memory` 或 `pgvector` | | `POSTGRES_TABLE` | `rag_chunks` | `PGVectorStore` 使用的表名 | | `POSTGRES_HOST` / `POSTGRES_PORT` / `POSTGRES_DB` / `POSTGRES_USER` / `POSTGRES_PASSWORD` | `127.0.0.1` / `5432` / `rag` / `postgres` / `postgres` | 组装连接串(DSN)用的连接参数 | | `OPENAI_API_KEY` | (无) | LLM 端点的 API key | | `OPENAI_BASE_URL` | `https://api.openai.com/v1` | **OpenAI 兼容**的 `/v1` 端点 | | `OPENAI_MODEL` | `gpt-4o-mini` | 传给端点的模型名 | `PGVectorStore` 的 `dimension` 必须与 embedder 的实际输出维度一致(默认模型为 384)。启动时会校验,不一致则在创建任何表之前拒绝启动。 若要替换**其它**阶段的实现,改 `config.py` 顶部的 provider 实例: ```python # 例:改用固定长度分块 chunker = FixedSizeChunker(chunk_size=512, chunk_overlap=64) # 例:启用 cross-encoder 重排 reranker = CrossEncoderReranker(model_name="cross-encoder/ms-marco-MiniLM-L-6-v2") ``` ## 项目结构 ``` slim_rag/ ├── .env.example # 环境变量模板(全部 10 个变量) ├── .gitignore # 忽略 runtime/、local/、dev/ 与 Python 产物 ├── .python-version # Python 版本(3.10) ├── AGENTS.md # 面向 AI agent 的协作规范 ├── docker-compose.yml # Docker Compose 配置 ├── Dockerfile # API 服务镜像定义 ├── LICENSE # MIT 许可证 ├── pyproject.toml # 项目配置(uv,uv_build 构建后端) ├── README.md # 英文说明(本文件的中文版见 README_zh.md) ├── README_zh.md # 简体中文说明 ├── uv.lock # 依赖锁定文件 ├── doc/ │ ├── constraint.md # 全部强制约束的索引 │ ├── constraints/ # 规格正文 —— 代码必须与之一致 │ │ ├── architecture.md │ │ ├── models.md │ │ ├── pipeline.md │ │ ├── registry.md │ │ └── providers/ # 各阶段的 provider 规格 │ │ ├── input.md │ │ ├── chunker.md │ │ ├── embedder.md │ │ ├── vector-store.md │ │ ├── retriever.md │ │ ├── reranker.md │ │ └── generator.md │ ├── proposals/ # 尚未实现的想法与需求 │ └── archive/ # 已实现的计划,用于溯源 ├── scripts/ │ └── smoke.py # 可重复的端到端检查 └── src/slim_rag/ # 源码 ├── __init__.py # 包入口 ├── env.py # 自动加载 .env ├── main.py # FastAPI 应用 ├── config.py # provider 配置与装配 ├── pipeline.py # RAGPipeline 编排器 ├── registry.py # provider 注册机制 ├── models.py # 数据模型 └── providers/ ├── __init__.py ├── base.py # 7 个抽象基类 ├── input/ # InputProvider 实现 ├── chunker/ # Chunker 实现 ├── embedder/ # Embedder 实现 ├── vector_store/ # VectorStore 实现 ├── retriever/ # Retriever 实现 ├── reranker/ # Reranker 实现 └── generator/ # Generator 实现 ``` 生成产物(日志、导出文件、临时数据库)一律放在 `runtime/` 下,该目录已被 git 忽略,因此新克隆的仓库里并不存在。手写的草稿材料放在 `local/` 或 `dev/`,同样 被忽略。 ## 扩展 为既有阶段新增一个 provider: 1. 阅读 `doc/constraints/providers/` 下该阶段的规格 —— 其中列出了接口、约束与 检查清单。 2. 继承 `slim_rag/providers/base.py` 中的抽象基类。 3. 用 `@register_provider("", "")` 装饰它。 4. 在 `config.py` 中导入它(让装饰器执行),然后在此处选中它。 5. 运行 `python -m compileall -q src/slim_rag` 与 `uv run python scripts/smoke.py`。 新增一个**完整阶段**则需要改动 `base.py`、`pipeline.py`、`config.py` 与注册表, 并在 `doc/constraints/` 下补一份规格。 ## 常见问题 **`Psycopg cannot use the 'ProactorEventLoop' to run in async mode`** Windows 上 uvicorn 0.52 会装入一个 `ProactorEventLoop` 工厂,而 psycopg 的异步 模式拒绝它。用 `--loop slim_rag.main:selector_loop_factory` 启动服务。注意这个 症状是间歇性的:带 `--reload` 时 uvicorn 会跳过自己的事件循环设置,所以同一条 命令有时看起来是正常的。 **`PoolTimeout: couldn't get a connection after 30.00 sec`** 通常与上一条同源 —— 事件循环不对,连接池永远拿不到可用连接。往上翻日志找 psycopg 的那条报错。 **启动时报维度不匹配** `PGVectorStore.dimension` 与 `Embedder.dimension` 不一致。要么把 `config.py` 里的 `dimension` 参数改成与模型一致,要么更换模型。该校验在创建任何表之前执行。 **`type "vector" does not exist`** 目标数据库缺少 `pgvector` 扩展。以超级用户执行 `CREATE EXTENSION IF NOT EXISTS vector`,或直接使用自带该扩展的 `pgvector/pgvector` 镜像。 **Hugging Face 模型下载失败或卡住** 首次运行需要下载向量模型。网络受限时,可以用镜像 `HF_ENDPOINT=https://hf-mirror.com`,或预先下载并把 `config.py` 的 `model_name` 指向本地目录。完全离线的机器上,在模型已缓存后设置 `HF_HUB_OFFLINE=1`。 **LLM 请求返回 404 或协议错误** `OPENAI_BASE_URL` 必须是 **OpenAI 兼容**端点,通常以 `/v1` 结尾。像 `/anthropic` 这样的路径使用的是另一种协议,无法通过 OpenAI SDK 调用。 ## 设计原则 - 最小可用设计,不过度设计 - 检索在本地、离线完成 - 生成使用外部 LLM API - 只保留一个核心业务接口 - 接口驱动、基于 provider、完全可组合 - 运行时输出纯 ASCII(stdout、日志、错误消息、API 字段) - 全部源文件的注释与 docstring 使用简体中文 ## 路线图 尚未实现的想法放在 [doc/proposals/](doc/proposals/README.md)。当前在列的: | 提案 | 状态 | |------|------| | [xlsx-input.md](doc/proposals/xlsx-input.md) | 把 FAQ 型 Excel 工作簿作为一种输入来源;共用 Excel 层与依赖策略已定,索引规则待定 | ## 许可证 MIT 许可证,详见 [LICENSE](LICENSE)。