内容
<div align="right">
**English** | [中文](README.zh.md)
</div>
<div align="center">
<img src="docs/assets/pharos-banner.svg" alt="Pharos — 团队文档库的导航" width="880">
# Pharos
[](https://github.com/Laurent00TT/pharos/actions/workflows/ci.yml)
[](LICENSE)


[](docs/learning/)
**将PDF、扫描件、docx、pptx和xlsx转化为本地知识库,您可以向其提问——具有企业访问控制和引用追溯功能。**
每个RAG组件都经过单独优化,然后合并到一个存储库中:多身份认证、观测性和systemd监督等功能一应俱全。
</div>
> **语言说明。** 代码、命令行界面和提交历史记录均为英文;文档(包括12部分的学习系列)以中文编写。本README是英文入口,涵盖了系统的概述、架构和运行方式。对于更深入的内容,您需要阅读中文文档(或使用机器翻译)。
---
> 亚历山大灯塔位于图书馆旁边,引导船只靠岸。 **Pharos的作用相同,但它照亮的是团队的文档库。**
这不是另一个简单的分割工具。这里的每个重要决策都经过测量、多次对抗性审查,并已在77个真实文档和7,652个分段上运行。安装只需一行命令:`pip install -e '.[dev]'`(src-layout,可编辑)。
## 两个出口,一个语义集
一个知识库,每个消费者都有一个入口:
| 出口 | 命令 | 使用者 | 功能 |
|---|---|---|---|
| **HTTP API** | `pharos serve` | curl、脚本、前端 | 封闭管道QA:`/v1/ask`运行检索→定位→DeepSeek→带有引用的答案,以及六个检索端点 |
| **MCP** | `pharos mcp` | 像Claude Code这样的代理 | 代理RAG:何时检索,如何重写,是否进行多跳——代理决定 |
两个入口共享一个语义集,由两条规则维持:工具合约具有唯一的源代码(`toolcore`,因此stdio和HTTP端不能偏移),身份由服务器端决定(代理不能编辑自己的权限)。
## 架构
<div align="center">
<img src="docs/assets/architecture.svg" alt="Pharos架构:消费者→守护进程→后端服务" width="100%">
</div>
**为什么使用常驻守护进程?**嵌入式Qdrant仅允许一个客户端并持有独家锁,而8B模型加载需要一两分钟。在早期的stdio-direct设计下,每个代理会话都会启动自己的进程——争夺锁并每次都重新加载模型。Pharos则相反:守护进程拥有重资源,MCP缩小为一个薄的HTTP适配器,启动时间以毫秒计,每个会话共享一个已经预热的后端。有关此设计的权衡,请参见 [docs/DESIGN.md](docs/DESIGN.md) (中文)。
<details>
<summary><b>生产环境:从单节点到三个独立可扩展层</b></summary>
<br/>
单节点问题在于GPU模型、嵌入式Qdrant和应用程序逻辑都生活在一个进程中,因此水平扩展立即遇到瓶颈。分离后,有三个层可以独立扩展:
- **`inference`** (FastAPI on `:8900`)— GPU前向传递独立出来,返回完整宽度的向量,客户端然后截断,两侧保持等效;
- **`pharos`** — 应用程序层完全摆脱了torch(设置`inference_url`,它永远不会加载模型),这使得 `--scale pharos=N` 成为可能;
- **Qdrant服务器模式** — 副本共享一个真实来源,前面有 `nginx` 进行负载均衡;`docker kill` 对任何副本都不会被注意到。
```bash
docker compose --env-file .env.compose up -d --scale pharos=3 # 入口:http://127.0.0.1:8080
```
有一点值得明确说明:**吞吐量上限是单个推理卡(GPU序列化)的速度,添加副本不会提高它。** 副本实际上为您带来的并发性是非GPU部分的并发性,加上崩溃隔离和滚动升级。完整的描述在 [docs/SCALE_OUT.md](docs/SCALE_OUT.md) (中文)。
</details>
## 入门指南
服务在systemd下运行 — 启动时启动,失败时重启。日常使用中,团队成员需要自己的API密钥,然后:
```bash
conda activate pharos
pip install -e '.[dev]' # src-layout,可编辑安装
python -m pharos ask "我们有关于X的内容吗?" # 封闭管道QA,答案带有引用
python -m pharos health
```
对于Claude Code的代理模式:复制 `.mcp.json.example` 到 `.mcp.json` (gitignored),填写您的 `PHAROS_API_KEY` 和本地路径,您就可以获得 `rag` 工具 — 前提是守护进程正在运行。没有守护进程的情况下,`pharos mcp --direct` 也有效:它使用stdio,加载GPU模型本身,并且依赖于没有常驻的服务。
构建索引,作为管理员(语料库是MinerU解析输出的目录):
```bash
sudo systemctl stop pharos # 单客户端锁,因此首先停止
python -m pharos index --corpus <parsed_dir> --dest ~/rag_real
sudo systemctl start pharos
```
## 想要了解RAG,而不仅仅是使用它?
存储库还包含一个 **[RAG学习和面试准备集](docs/learning/)**:该系统 — 反复被自己的数据所矛盾并反复修复 — 被拆分成 **12部分和4,122行** 的教程。它从分块、混合检索、权限模型和生成定位一直到代理使用、评估方法和多副本扩展。每个部分都带有代码锚点、测量数字、面试框架和您可以自己运行的实验。 **以中文编写。**
| 您想要的 | 哪些部分 |
|---|---|
| 每个RAG层的权衡,以及主流选项的外观 | [01 概述](docs/learning/01-rag-overview.md) · [02 分块](docs/learning/02-parsing-chunking.md) · [03 检索](docs/learning/03-retrieval.md) |
| 企业权限、生成定位、代理 | [04 ACL](docs/learning/04-acl-security.md) · [05 生成](docs/learning/05-generation-grounding.md) · [06 代理](docs/learning/06-agentic-mcp.md) |
| RAG应该如何评估(该系统最关心的问题) | [07 评估](docs/learning/07-evaluation.md) |
| 系统设计、单节点到副本、工程方法 | [08 服务](docs/learning/08-service-architecture.md) · [09 扩展](docs/learning/09-scale-out.md) · [10 故事](docs/learning/10-methodology-stories.md) |
| 面试突击 | [11 问题银行](docs/learning/11-interview-qa.md) |
## 安全模型
- **身份回答“谁在提问”,ACL回答“他们可以看到什么”。** X-API-Key首先解析为身份;然后,该身份的租户和主体被传递给检索层,每个请求进行权限过滤。整个链路都经过 `acl_regression` 中的五用户矩阵验证。
- **全程关闭。** 未识别或缺失的密钥为401。格式错误的密钥文件拒绝启动。绑定到非环回地址强制启用密钥模式 — 库不会在未受保护的情况下暴露在LAN上。默认情况下,文档如果没有附加权限,则对任何人不可见。
- **会话彼此隔离。** 跨调用去重按身份和会话分桶,因此一个人永远不会看到另一个人的状态。 **它也不会泄露详细信息:** 日志记录身份名称而非密钥,错误不会溢出内部信息,每个检索到的段落都标记为 `trust: untrusted` 以防止提示注入。
- 默认情况下绑定到 `127.0.0.1`。 HTTPS和公开暴露不是明确的目标 — 如果您需要远程访问,请进行隧道传输。
<details>
<summary><b>三种身份模式和配置</b></summary>
<br/>
| 模式 | 触发器 | 用途 | 身份来源 |
|---|---|---|---|
| **keys** | 设置 `PHAROS_KEYS_FILE` | **团队部署(默认)** | 每个请求的X-API-Key产生自己的身份(名称+租户+主体) |
| **legacy** | 仅设置 `PHAROS_API_KEY` | 单用户,或仅作为门槛 | 一个密钥,启动时绑定身份 |
| **open** | 两者均未设置 | 本地开发,仅限环回 | 启动时绑定的单一身份 |
```bash
python -m pharos keys new alice --tenant demo --admin # 发布身份;密钥仅打印一次
python -m pharos keys new bob --tenant demo --principals g_eng
# 在.env中设置PHAROS_KEYS_FILE=~/pharos.keys.json;添加PHAROS_HOST=0.0.0.0以访问LAN
sudo systemctl restart pharos
```
配置位于存储库根目录的单个 `.env` 中(参见 [.env.example](.env.example);所有前缀均为 `PHAROS_*`):`PHAROS_KEYS_FILE`、`PHAROS_CORPUS_DIR`、`PHAROS_INDEX_DIR`(默认为 `~/rag_real`)、`PHAROS_COLLECTION`、`PHAROS_PORT`(8787)、`PHAROS_HOST`(非环回绑定强制启用密钥模式)、`PHAROS_LOG_DIR` 和 `DEEPSEEK_API_KEY`(`/v1/ask` 需要)。完整的部署、密钥和操作程序在 [docs/OPERATIONS.md](docs/OPERATIONS.md) (中文)。
</details>
## 测试
```bash
python -m pytest tests -q # 完整CPU套件:产品层+引擎层,包括ACL谓词
python -m pytest tests -q --ignore=tests/engine # 仅产品层(假检索器+MockLLM;无GPU,无网络)
```
两者都不触及GPU、Qdrant服务器或网络, [CI](.github/workflows/ci.yml) 在每次推送时运行它们。套件大小、产品/引擎拆分和测量出处在 [docs/TESTING.md](docs/TESTING.md) §1中 — 该部分的唯一权威。GPU端到端零泄漏回归(`eval/acl_regression.py`,在WSL + 4090上)、并发基准测试、备份演练和安全审查记录都在同一文档中。
## 文档索引
以下所有文档均为中文。
| 文档 | 涵盖内容 |
|---|---|
| [docs/OVERVIEW.md](docs/OVERVIEW.md) | **系统概述(权威入口)**:整体图景、组件图、如何阅读本文档集 |
| [docs/learning/](docs/learning/) | **RAG学习和面试文档(12部分)**:从分块到评估,带有代码锚点和面试框架 |
| [docs/DESIGN.md](docs/DESIGN.md) | 目标、架构、关键决策(包括被拒绝的替代方案)、多身份和可观测性、风险 |
| [docs/IMPLEMENTATION.md](docs/IMPLEMENTATION.md) | 模块图、请求路径、锁定模型、检索层缝隙 |
| [docs/API.md](docs/API.md) | HTTP API合约:端点、参数、身份验证、状态状态机 |
| [docs/OPERATIONS.md](docs/OPERATIONS.md) | **团队操作手册**:部署、密钥、测量容量、备份和恢复、故障排除 |
| [docs/SCALE_OUT.md](docs/SCALE_OUT.md) | 从单节点到多个副本(阶段A-F):三层拆分、负载均衡、等效验证 |
| [docs/TESTING.md](docs/TESTING.md) | 测试矩阵及其背后的证据:单元测试、ACL回归、GPU烟雾、基准测试、演练、安全 |
| [docs/ROADMAP.md](docs/ROADMAP.md) | 本项目的发展方向,以及 **明确不构建的内容** (及原因) |
| [docs/COMPONENT_NOTES.md](docs/COMPONENT_NOTES.md) · [docs/PROVENANCE.md](docs/PROVENANCE.md) | 记录对继承组件的异议;出处和测量基准中断 |
组件级设计和评估位于 [docs/components/](docs/components/) (chunker / embedder / generator / mcp-server);方法论位于 [docs/methodology/](docs/methodology/)。
## License
[MIT](LICENSE).
<div align="center">
<br/>
<sub>Pharos · 自建多格式代理RAG · 本地4090 / WSL <code>pharos</code></sub>
</div>