# rag-platform **Repository Path**: johnnie_walker/rag-platform ## Basic Information - **Project Name**: rag-platform - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-24 - **Last Updated**: 2026-10-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # rag-platform 一个采用整洁架构(Clean Architecture)的 Java / Maven 聚合工程,围绕 RAG 的三类核心能力构建: | 能力 | 说明 | 入口 | | --- | --- | --- | | 简单问答 | 单跳混合检索(向量 + 关键词 + RRF 融合)+ 精排 + 单次生成,延迟最低 | `POST /api/v1/qa/ask`(mode=SIMPLE) | | 智能推理 | 问题分解 → 迭代检索 → 反思 → 汇总,返回完整推理轨迹 | `POST /api/v1/reasoning/ask` | | 知识图谱问答 | 实体链接 → 子图扩展 → 关联分块回捞 → 图谱增强生成 | `POST /api/v1/graph/ask` | 设计与实现围绕领域模型、端口与适配器三层组织:领域层表达业务规则, 应用层编排用例,接口层与基础设施层各自承担对外协议与外部依赖。 各层职责与依赖方向见 [docs/02-clean-architecture.md](docs/02-clean-architecture.md)。 ## 技术栈 Java 17、Maven 3.8+、Spring Boot 3.2、Spring MVC(含 SSE)、Jackson、JUnit 5、springdoc-openapi。 默认零外部依赖:向量库、关键词索引、图存储、仓库、审计与原始文件全部落在 SQLite(单数据文件), 模型侧使用离线实现(抽取式回答 + 哈希向量 + 词面重排)。 因此 `mvn spring-boot:run` 之后即可完成入库、检索、问答的完整闭环。 六类后端均可切换(Milvus / pgvector / sqlite-vec、Elasticsearch / SQLite FTS5、 PostgreSQL / SQLite、内存队列 / Chronicle 追加日志 / Redis(队列是唯一保留的内存模式)、 日志 / Micrometer / OpenTelemetry、 内置推理 / AgentScope Harness 2.0.3),三类模型调用(LLM / Embedding / Rerank) 同样可切换到 AgentScope core、OpenAI 扩展与 Anthropic 扩展,详见 [docs/06-backend-switching.md](docs/06-backend-switching.md)。 ## 快速开始 ```bash # 构建并运行全部单元测试与集成测试 mvn clean test # 打包并启动(默认 8080 端口) mvn -pl rag-bootstrap -am spring-boot:run ``` Swagger UI:`http://localhost:8080/swagger-ui.html`,健康检查:`/actuator/health`。 租户标识统一通过请求头 `X-TENANT-ID` 传递(缺省为 `default`):请求参数、请求体与请求路径 都不接受租户标识,下面所有示例里的 `X-TENANT-ID` 按需替换成目标租户即可。 **首次启动不需要手工建账号**:启动时会自动检查并补齐租户、管理员账号与一把可用的 API Key (`rag.bootstrap.*`,默认开启)。生成的凭据只写文件、不进日志: ```bash # 默认租户 default、管理员 admin;密码与 API Key 分别落到下面两个文件 cat data/bootstrap-admin.password # 未配置 rag.bootstrap.admin-password 时生成 cat data/bootstrap-admin.key # 配置里没有任何 api-keys 时生成 # 登录(账号登录需要配 rag.security.jwt.secret,否则登录接口会明确提示) curl -X POST http://localhost:8080/api/v1/auth/login -H 'X-TENANT-ID: default' \ -H 'Content-Type: application/json' -d '{"username":"admin","password":"<上面文件里的口令>"}' ``` 首次登录后请改密、把 API Key 迁到环境变量,并设 `rag.bootstrap.enabled=false`。 ### 1. 创建知识库 ```bash curl -X POST http://localhost:8080/api/v1/knowledge-bases \ -H 'X-TENANT-ID: demo' \ -H 'Content-Type: application/json' \ -d '{"name":"RAG 架构知识库","type":"HYBRID","embeddingModel":"local-hash","embeddingDimension":512}' ``` 返回体中的 `data.id` 即知识库 ID,下文以 `$KB` 表示。 ### 2. 入库文档 ```bash curl -X POST http://localhost:8080/api/v1/documents/ingest \ -H 'X-TENANT-ID: demo' \ -H 'Content-Type: application/json' \ -d '{"knowledgeBaseId":"'$KB'","title":"RAG 架构说明","contentType":"text/markdown", "content":"# 混合检索\n向量检索依赖嵌入模型。\n关键词检索基于 BM25。\n\n# 融合与精排\nRRF 融合包含向量排名与关键词排名。"}' ``` 响应会给出 `chunkCount`、`entityCount`、`relationCount`,即切分数与图谱规模。 ### 3. 三种问答 ```bash # 简单问答 curl -X POST http://localhost:8080/api/v1/qa/ask -H 'X-TENANT-ID: demo' -H 'Content-Type: application/json' \ -d '{"question":"关键词检索基于什么算法?","knowledgeBaseIds":["'$KB'"],"mode":"SIMPLE"}' # 智能推理(返回 trace.steps 推理轨迹) curl -X POST http://localhost:8080/api/v1/reasoning/ask -H 'X-TENANT-ID: demo' -H 'Content-Type: application/json' \ -d '{"question":"向量检索和关键词检索如何配合完成融合与精排?","knowledgeBaseIds":["'$KB'"]}' # 知识图谱问答 curl -X POST http://localhost:8080/api/v1/graph/ask -H 'X-TENANT-ID: demo' -H 'Content-Type: application/json' \ -d '{"question":"向量检索和嵌入模型是什么关系?","knowledgeBaseIds":["'$KB'"]}' ``` ### 4. 流式问答(SSE) ```bash curl -N -X POST http://localhost:8080/api/v1/qa/stream -H 'X-TENANT-ID: demo' -H 'Content-Type: application/json' \ -d '{"question":"RRF 融合的作用是什么?","knowledgeBaseIds":["'$KB'"],"mode":"SIMPLE"}' ``` 事件顺序为 `delta`(正文分片)→ `citations`(引用)→ `done`(统计),出错时为 `error`。 ### 5. 检索与诊断 ```bash # 只检索不生成 curl -X POST http://localhost:8080/api/v1/retrieval/search -H 'Content-Type: application/json' \ -d '{"query":"RRF 融合","knowledgeBaseIds":["'$KB'"],"mode":"HYBRID","topK":5}' # 分阶段命中与耗时(向量 / 关键词 / 图 / 融合 / 最终) curl -X POST http://localhost:8080/api/v1/retrieval/diagnostics -H 'Content-Type: application/json' \ -d '{"query":"RRF 融合","knowledgeBaseIds":["'$KB'"],"mode":"HYBRID_GRAPH"}' # 当前生效的适配器与可用性 curl http://localhost:8080/api/v1/system/adapters ``` ### 6. 账号、租户与目录(第一批治理能力) ```bash # 登录(返回 accessToken 与 refreshToken;/auth/login 与 /auth/refresh 在免认证白名单内) curl -X POST http://localhost:8080/api/v1/auth/login -H 'X-TENANT-ID: tenant-a' -H 'Content-Type: application/json' \ -d '{"username":"alice","password":"alice-password-1"}' # 建租户、在该租户下开通成员(租户一律走请求头;跨租户需要 platform-admin 之类的角色) curl -X POST http://localhost:8080/api/v1/tenants -H "X-API-Key: $RAG_API_KEY" -H 'X-TENANT-ID: tnt_xxx' \ -H 'Content-Type: application/json' -d '{"name":"终端交付租户"}' curl -X POST http://localhost:8080/api/v1/users -H "X-API-Key: $RAG_API_KEY" -H 'X-TENANT-ID: tnt_xxx' \ -H 'Content-Type: application/json' \ -d '{"username":"alice","password":"alice-password-1","roles":["admin"]}' # 建目录 → 批量把文档移入 → 按目录查询 curl -X POST "http://localhost:8080/api/v1/knowledge-bases/$KB/folders" -H "X-API-Key: $RAG_API_KEY" \ -H 'Content-Type: application/json' -d '{"name":"研发"}' curl -X POST http://localhost:8080/api/v1/documents/batch/move -H "X-API-Key: $RAG_API_KEY" \ -H 'Content-Type: application/json' -d '{"documentIds":["doc_a"],"folderId":"flr_xxx"}' curl "http://localhost:8080/api/v1/knowledge-bases/$KB/documents?folderId=flr_xxx" -H "X-API-Key: $RAG_API_KEY" # URL 导入(含协议白名单与内网地址拦截) curl -X POST "http://localhost:8080/api/v1/knowledge-bases/$KB/documents/import-url" \ -H "X-API-Key: $RAG_API_KEY" -H 'Content-Type: application/json' \ -d '{"url":"https://example.com/handbook","title":"远程手册"}' # 改完切分策略后让存量文档生效(reindex 只重建索引,不会重新切分) curl -X POST "http://localhost:8080/api/v1/documents/$KB/$DOC/rechunk" -H "X-API-Key: $RAG_API_KEY" ``` 能力清单、设计取舍与**当前还不具备的能力**, 见 [docs/09-tenant-user-auth-folder-batch.md](docs/09-tenant-user-auth-folder-batch.md)。 ### 7. 生产部署要点 多实例部署时务必显式配置令牌共享存储与固定密钥,否则会出现 "A 实例登出、B 实例仍认账"与"重启后已登录用户全部掉线": ```bash export RAG_TOKEN_STORE=redis # 刷新令牌、撤销名单、日请求计数共享 export RAG_JWT_SECRET=<32 字节以上随机串> # 多实例必须一致 export RAG_SECURITY_AUTH=chain # API Key 与 JWT 并存(账号登录需要 JWT) export RAG_FOLDER_ISOLATION=strict # 可选:受控目录仅授权者与管理员可见 export RAG_QUEUE_TYPE=redis # 异步入库队列 mvn -pl rag-bootstrap -am spring-boot:run ``` 单机部署(没有 Redis,但也**不想在重启时丢任务**)可以把队列换成 Chronicle: ```bash export RAG_QUEUE_TYPE=chronicle export RAG_QUEUE_DIR=./data/ingest-queue # 任务写入本机内存映射的追加日志 mvn -pl rag-bootstrap -am spring-boot:run # 该命令已在 pom 里带上必要的 JVM 参数 # 用 java -jar 启动时必须自行带上(Chronicle 在 Java 17 上需要开放的模块边界) java --add-exports=java.base/sun.nio.ch=ALL-UNNAMED \ --add-exports=java.base/jdk.internal.ref=ALL-UNNAMED \ --add-opens=java.base/java.lang=ALL-UNNAMED \ --add-opens=java.base/java.nio=ALL-UNNAMED \ --add-opens=java.base/sun.nio.ch=ALL-UNNAMED \ -jar rag-bootstrap/target/rag-platform.jar ``` 它与内存队列的差别是"重启不丢任务"(未确认任务在下次启动时被重新投递), 与 Redis 队列的差别是"不需要额外服务"。取舍细节见 [docs/06-backend-switching.md](docs/06-backend-switching.md) 的 5.2 节。 > 用 IDE 直接启动(Eclipse / IDEA 的运行按钮)时,上面的清单参数不会自动生效: > 需要把 `rag-bootstrap/pom.xml` 里 `jvmArguments` 的那几行原样填进运行配置的 VM options。 > 漏掉时应用不会"莫名其妙报栈",而是直接给出"缺少哪些参数、怎么加"的提示。 ```bash # 租户用量(已用 / 上限) curl "http://localhost:8080/api/v1/tenants/current/usage" -H "X-API-Key: $RAG_API_KEY" -H 'X-TENANT-ID: tenant-a' # 目录授权:把目录读权限授予 editor 角色 curl -X POST "http://localhost:8080/api/v1/knowledge-bases/$KB/folders/$FOLDER/grants" \ -H "X-API-Key: $RAG_API_KEY" -H 'Content-Type: application/json' \ -d '{"subjectType":"ROLE","subjectId":"editor","permission":"READ"}' ``` 完整的生产检查清单、令牌与配额的设计取舍、以及**仍然不具备的能力**, 见 [docs/10-production-readiness.md](docs/10-production-readiness.md)。 ## 模块结构 | 模块 | 职责 | 依赖方向 | | --- | --- | --- | | `rag-common` | 统一响应、错误码、通用工具(与框架无关) | 无内部依赖 | | `rag-domain` | 实体、值对象、领域服务、入站与出站端口 | 零依赖(含零框架) | | `rag-application` | 用例编排、检索管道、推理引擎、图谱问答编排 | `domain` + `common` | | `rag-infrastructure` | 向量库、BM25、图存储、模型、解析、切分、仓储适配器 | `domain` + `common` | | `rag-interfaces` | REST/SSE 适配器、请求响应模型、异常映射 | `application` + `domain` | | `rag-observability` | Micrometer 指标、OpenTelemetry 链路、组合适配器 | `domain` | | `rag-store-sql` | PostgreSQL / SQLite 持久化、pgvector、sqlite-vec、SQLite FTS5 | `domain` | | `rag-store-milvus` | Milvus 向量库(REST v2) | `domain` | | `rag-store-elasticsearch` | Elasticsearch / OpenSearch 关键词索引 | `domain` | | `rag-queue-redis` | Redis 可靠队列(BRPOPLPUSH 加死信) | `domain` | | `rag-queue-chronicle` | Chronicle 追加日志队列(内存映射文件 + 提交位点 + 死信,单机持久) | `domain` | | `rag-store-redis` | Redis 共享状态:刷新令牌、令牌撤销名单、租户用量计数 | `domain` | | `rag-agent` | 内置 ReAct 运行时、AgentScope Harness 2.0.3 适配器 | `domain` | | `rag-model-agentscope` | LLM / Embedding / Rerank 的 AgentScope core、OpenAI、Anthropic 适配器 | `domain` | | `rag-bootstrap` | Spring Boot 组装根、配置属性、提示词资源 | 全部模块 | 依赖方向严格单向:`interfaces` 依赖 `application`,`application` 依赖 `domain`, `infrastructure` 实现 `domain` 定义的端口。领域层不允许出现 Spring、Jackson、HTTP 等外部框架符号。 完整到包与文件的清单见 [docs/03-module-catalog.md](docs/03-module-catalog.md)。 ## 关键设计取舍 **1. 索引与正文分离。** 向量库和关键词索引只存分块 ID、向量或词频与少量过滤字段, 正文统一由分块仓储批量提供。收益是索引可替换、可重建,代价是每次检索多一跳查询。 **2. 用 RRF 而非加权分数。** 向量余弦相似度与 BM25 分数量纲不同,线性加权需要反复调参。 RRF 只用排名,天然免疫量纲差异,并保留每一路的原始排名供重排与诊断使用。 **3. 过召回加精排。** 召回阶段按 `overRetrievalFactor`(默认 5 倍)放宽候选池, 精排阶段压缩到 5 到 10 条进入提示词,这是召回率与上下文预算矛盾的标准解法。 **4. 父子分块。** 子块小、检索精度高,父块大、上下文完整。 命中子块后用父块正文生成,且父块不参与索引,避免内容越长越容易被误判为相关。 **5. 阈值是软约束。** 严格阈值下一条不中时自动放宽并交由重排裁决, 避免出现知识库明明有内容却检索不到这类最难排查的失效模式。 **6. 图谱权重用 PMI 校准。** 直接采用模型给出的关系强度会让权重趋同。 引入点互信息后,局部共现关系被抬升、泛化词关系被压低,权重落在 1 到 10 的可解释区间。 **7. 全链路可降级。** 模型不可用退化为抽取式回答,向量召回失败退化为关键词召回, 重排失败退回融合排序,图谱不可用不影响向量检索。每次降级都在响应 `degraded` 字段与日志中可见。 ## 配置 配置文件位于 `rag-bootstrap/src/main/resources/application.yml`,三个模型端点均支持环境变量注入。 | 配置项 | 说明 | 默认值 | | --- | --- | --- | | `rag.chat.base-url` / `api-key` / `model` | 对话模型(OpenAI 兼容协议) | 空 key,使用离线模板模型 | | `rag.embedding.base-url` / `api-key` / `model` / `dimension` | 向量模型 | 空 key,使用哈希向量(512 维) | | `rag.rerank.base-url` / `api-key` / `model` | 重排服务 | 空 key,使用词面重排 | | `rag.storage.graph-enabled` | 是否启用图谱能力 | `true` | | `rag.ingestion.core-pool-size` / `queue-capacity` | 异步入库线程池 | `4` / `256` | | `rag.storage.vector-store` | 向量库实现 | `sqlite-vec`(可选 `pgvector` / `milvus`) | | `rag.storage.keyword-index` | 关键词索引实现 | `sqlite`(可选 `elasticsearch`) | | `rag.storage.repository` | 持久化实现 | `sqlite`(可选 `postgres`) | | `rag.storage.graph-store` | 知识图谱实现 | `sqlite`(唯一实现) | | `rag.ingestion.queue` | 异步入库队列 | `memory`(唯一保留的内存模式;可选 `chronicle` 单机持久 / `redis` 多实例) | | `rag.observability.telemetry` | 可观测性实现 | `log`(可选 `micrometer` / `opentelemetry` / `composite`) | | `rag.agent.runtime` | 智能体运行时 | `builtin`(可选 `react` / `agentscope`) | | `rag.chat.adapter` | 对话模型调用实现 | `native`(可选 `agentscope-openai` / `agentscope-anthropic` / `agentscope-core`) | | `rag.embedding.adapter` | 向量模型调用实现 | `native`(可选 `agentscope-core`) | | `rag.rerank.adapter` | 重排调用实现 | `native`(可选 `agentscope-llm` / `agentscope-embedding`) | | `rag.security.authentication` | 认证方式 | `none`(开发;可选 `api-key` / `jwt` / `chain`) | | `rag.security.jwt.secret` | 令牌签名密钥(**生产必配**) | 空(未配置时启动用随机密钥并告警) | | `rag.security.jwt.access-token-ttl-seconds` | 访问令牌有效期 | `7200`(2 小时) | | `rag.security.jwt.refresh-token-ttl-seconds` | 刷新令牌有效期 | `1209600`(14 天) | | `rag.security.password-iterations` | PBKDF2 迭代次数 | `120000` | | `rag.bootstrap.enabled` | 启动时检查并创建租户/管理员账号/API Key | `true`(生产建议引导完成后关闭) | | `rag.bootstrap.tenant-id` / `admin-username` | 引导管理员所属租户与用户名 | `default` / `admin` | | `rag.bootstrap.admin-password` | 管理员初始密码 | 空(随机生成并写入 `data/bootstrap-admin.password`) | | `rag.bootstrap.reset-password` | 账号已存在时强制重置密码 | `false`(避免重启覆盖用户改过的密码) | | `rag.bootstrap.generate-api-key` / `api-key-file` | 未配置任何 API Key 时生成一把并落盘 | `true` / `data/bootstrap-admin.key` | | `rag.security.token-store` | 令牌与用量的共享存储 | `auto`(落到 `sql`;多实例用 `redis`) | | `rag.security.token-maintenance-interval-ms` | 过期令牌记录清理间隔 | `1800000`(30 分钟) | | `rag.security.folder-isolation` | 目录隔离模式 | `off`(可选 `strict`) | | `rag.redis.host` / `port` / `database` / `key-prefix` | 共享 Redis 连接 | `localhost` / `6379` / `0` / `rag` | | `rag.security.authorization` | 授权方式 | `rbac`(可选 `permit-all`,仅开发) | | `rag.security.audit` | 审计方式 | `both`(日志留存 + `rag_audit_event` 在线查询;可选 `log` / `sql`) | | `rag.ingestion.url-fetch.enabled` | 是否允许服务端抓取网页 | `true` | | `rag.ingestion.url-fetch.block-private-addresses` | 拦截内网地址(SSRF 防护) | `true` | | `rag.storage.file-storage` | 原始文件存储 | `local`(可选 `sqlite`:内容存库,适合单文件部署) | | `rag.storage.file.local-dir` | 本地文件目录 | `./data/files` | | `rag.storage.file.max-size-bytes` | 上传大小上限 | `67108864`(64MB) | 接入本地 vLLM 与 BGE 重排的示例: ```bash export RAG_CHAT_BASE_URL=http://localhost:8000/v1 RAG_CHAT_API_KEY=sk-local RAG_CHAT_MODEL=qwen2.5-14b-instruct export RAG_EMBEDDING_BASE_URL=http://localhost:8001/v1 RAG_EMBEDDING_API_KEY=sk-local RAG_EMBEDDING_MODEL=bge-m3 export RAG_RERANK_BASE_URL=http://localhost:8002 RAG_RERANK_API_KEY=sk-local mvn -pl rag-bootstrap -am spring-boot:run ``` ## 测试 ```bash mvn clean test ``` `rag-domain` 覆盖 RRF 融合、分块去重与重叠合并;`rag-bootstrap` 为端到端集成测试, 依次验证建库、入库(含图谱构建)、三条问答链路、文档与分块管理, 以及租户/用户/认证、目录、批量操作与 URL 导入,并同时充当降级路径的回归测试。 ## 扩展到生产 | 目标 | 做法 | | --- | --- | | 换向量库(Milvus / Qdrant / pgvector) | 实现 `VectorStore`,在 `InfrastructureConfiguration` 替换 Bean | | 换关键词索引(Elasticsearch / OpenSearch) | 实现 `KeywordIndex` | | 换图数据库(Neo4j) | 实现 `GraphStore` 的实体、关系、子图扩展方法 | | 换持久化(MySQL / PostgreSQL) | 实现四个 `*Repository`,建议用 ORM 映射而非直接持有领域对象引用 | | 异步入库改为分布式 | 实现 `TaskQueue`(Redis 队列或消息中间件) | | 接入可观测性平台 | 实现 `Telemetry`(Micrometer / OpenTelemetry) | 上述后端切换项均已提供可运行实现,做法与配置示例见 [docs/06-backend-switching.md](docs/06-backend-switching.md)。 ## 文档索引 - [参考实现分析](docs/01-reference-implementation-analysis.md) - [整洁架构设计说明](docs/02-clean-architecture.md) - [模块、包与文件清单](docs/03-module-catalog.md) - [REST API 参考](docs/04-api-reference.md) - [设计取舍记录](docs/05-design-decisions.md) - [后端可切换能力说明](docs/06-backend-switching.md) - [认证、权限与日志体系](docs/07-security-and-logging.md) - [文档、文件与分块管理](docs/08-document-and-chunk-management.md) - [租户、用户与认证、目录、批量操作、URL 导入](docs/09-tenant-user-auth-folder-batch.md) - [生产就绪:令牌、配额、目录授权与批量导入](docs/10-production-readiness.md)