# weknora-java **Repository Path**: ym0609/weknora-java ## Basic Information - **Project Name**: weknora-java - **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-10-26 - **Last Updated**: 2026-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # WeKnora-Java 以 [WeKnora](https://github.com/Tencent/WeKnora)(腾讯开源的 RAG 知识库问答服务)为参照的 Java 复刻实现,**用于学习目的**。 **技术目标是"完整"而非"精简":** 377 个 HTTP 端点全部实现,验证同一个前端能否不改一行代码就接上另一个后端实现——这本身是一次有难度的学习练习。 --- ## ⚠️ 声明 **本项目仅供个人学习与技术研究使用。** - **非官方项目**,与腾讯公司及 WeKnora 官方团队无任何关联,未获授权、未获背书,也不代表官方立场。 - 目的是学习一个成熟 RAG 系统的工程结构(检索链路、Agent 运行时、文档解析、多租户模型),并练习 Java / Spring Boot / DDD 的落地方式。 - **不允许用于任何商业用途**。如需商用,请使用[原版 WeKnora](https://github.com/Tencent/WeKnora),并遵守其许可证条款。 - 原版 WeKnora 的著作权、商标权及其它一切权利归腾讯公司及相关权利人所有。 - 本项目不含原版任何代码资产。接口契约对齐原版,仅为验证"同一个前端能否连上另一个后端"这一学习目标,不构成对原版的分发或替代。 - 使用本项目产生的任何后果由使用者自行承担。作者不对可用性、安全性、合规性作任何明示或默示担保。 > 如果你是权利人,认为本项目有任何不妥之处,请通过 Issue 联系,我会立即处理。 --- ## 一分钟了解 | | Go 原版 | 本项目 | |---|---|---| | 语言 / 框架 | Go + Gin | Java 21 + Spring Boot 3.5 | | HTTP 端点 | 377 | **377(100%)** | | 代码量 | 29.7 万行 | 9.9 万行 | | 架构 | 分层 + 大量 service | **DDD 四层**,依赖单向 | | 数据库 | PostgreSQL + pgvector | 同(Flyway 管表结构,Hibernate 只校验) | **为什么代码量少一半:** 不是在"简化功能",而是 Java 的表达力、Spring Data 的样板消除,以及一部分能力用成熟的库代理(PDFBox / POI / jsoup)替掉了手写解析。 --- ## 它是什么 一个知识库问答系统。你把文档传进去,它把文档切片、向量化、建索引,之后用自然语言提问,它检索出相关片段、交给大模型回答,并标注引用来源。 ```mermaid flowchart LR A[上传文档] --> B[解析 PDF/Word/Excel/PPT...] B --> C[分块] C --> D[向量化 + 关键词索引] E[用户提问] --> F[混合检索
向量 + 关键词 + RRF 融合] D --> F F --> G[重排 + 去冗] G --> H[组装提示词] H --> I[大模型生成] I --> J[流式返回 + 引用标注] K[Agent 模式] -.-> F K -.-> L[工具调用
检索/读文档/抓网页/执行命令] L --> I ``` --- ## 快速开始 ### 前置条件 - JDK 21+ - PostgreSQL 14+(需要 `pgvector` 扩展) - Maven 3.9+ ### 1. 准备数据库 ```sql CREATE DATABASE weknora; \c weknora CREATE EXTENSION IF NOT EXISTS vector; ``` 表结构由 Flyway 自动创建,不需要手工建表。 ### 2. 配置 改 `weknora-boot/src/main/resources/application.yml`,或用环境变量覆盖: ```powershell $env:JWT_SECRET="至少32字节的随机串" ``` 数据库连接默认 `localhost:5433 / weknora / weknora / weknora`(端口刻意用 5433,避免和本机其它 PostgreSQL 的 5432 冲突)。 ### 3. 启动 ```powershell # 第一次:把依赖模块装进本地仓库 mvn -o install -DskipTests -pl weknora-domain,weknora-application,weknora-infrastructure,weknora-interfaces,weknora-docreader # 之后每次启动 mvn -o -pl weknora-boot spring-boot:run ``` > **不要加 `-am`**。`-am` 会对依赖模块也执行 `spring-boot:run`,而根聚合 pom 没有主类, > 报错信息里的项目名是 `weknora-java`(根项目)不是 `weknora-boot`,很容易看错。 看到这行就算起来了: ``` Tomcat started on port 8080 Started WeKnoraApplication in ~21 seconds ``` 访问 http://127.0.0.1:8080/actuator/health 应返回 `{"status":"UP"}`。 ### 4. 配模型 系统的问答能力来自大模型(自身不带模型)。启动后在**界面上配置**,或直接调 API: ```json // POST /api/v1/models —— 对话模型 { "name": "deepseek-chat", "type": "KnowledgeQA", "source": "deepseek", "is_default": true, "parameters": { "base_url": "https://api.deepseek.com", "api_key": "sk-..." } } // POST /api/v1/models —— 向量模型(必须另配,对话模型不提供 embedding) { "name": "text-embedding-v3", "type": "Embedding", "source": "aliyun", "is_default": true, "parameters": { "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1", "api_key": "sk-...", "dimension": 1024 } } ``` **任何 OpenAI 兼容的服务都能用**:DeepSeek、阿里云百炼、豆包、硅基流动、Moonshot、vLLM、Ollama(`/v1`)、OpenAI 官方。 ### 5. 联调数据(可选) ```powershell python scripts/seed_testdata.py # 建知识库、文档、标签、智能体、MCP 端点 ``` 会创建账号 `e2e-admin@example.com` / `WeKnora@2026`,含 4 个知识库、7 篇多格式文档(全部解析完成)、3 个标签、2 个智能体。 --- ## 架构 ``` weknora-domain 领域模型 + 端口 (零框架依赖:不碰 Spring / JPA / HTTP) weknora-application 用例编排 (只有业务逻辑) weknora-infrastructure 端口实现 (JPA、HTTP、文件、解析器) weknora-interfaces REST 控制器 + DTO (HTTP 契约) weknora-boot 组合根 (@Bean 装配、配置) weknora-docreader 独立文档解析服务 (可单独部署) ``` 依赖方向严格单向: ``` interfaces ──> application ──> domain ↑ infrastructure ─────┘(只依赖 domain) ``` **为什么 domain 一行 Spring 都不许有:** 领域逻辑是业务真相,不该被框架绑架。这条规则在开发中真的拦住过错误——我最初把 Agent 工具放在 infrastructure,它需要调 application 层的检索用例,编译器直接报错,逼着重构到正确的层。 **为什么用例不标 `@Component`:** 全部在 `weknora-boot` 的 `@Configuration` 里用 `@Bean` 显式装配。代价是漏注册会启动失败,好处是装配关系一眼可见。这个代价是值得的——漏注册的问题被"上下文门禁测试"拦住了(见下)。 --- ## 关键设计 ### 引用用 `S1` 句柄,不是 chunkId 检索结果在提示词里标成 `[S1] 《文档标题》`,模型回答时引用这些句柄,服务端再映射回真实的 chunk 位置。这样前端拿到的是稳定的引用编号,不依赖模型是否会背长 ID。 ### 混合检索用 RRF 融合,不是加权求和 向量分(余弦相似度 0~1)和关键词分(ts_rank_cd,无界)**量纲不可比**。RRF 只用排名,天然同量纲: ``` score = Σ weight_i / (k + rank_i) ``` ### Agent 的工具范围钉在工具实例里 `search_knowledge` 工具在创建时就绑定了"这次能查哪几个知识库"。如果做成全局单例、把范围当参数传,模型就能自由填参数——等于把"能查什么"的决定权交给了模型。 ### 沙箱是进程级,不是容器级 `shell_exec` 在隔离的工作目录里跑,有超时、输出上限、环境变量白名单。**它挡得住误操作,挡不住恶意攻击**(命令里写绝对路径仍能读写宿主文件)。所以这个工具**必须人工审批**才能执行。要开放给不可信用户,必须先换容器级沙箱。 ### 高危工具默认不下发 `ToolRegistry` 的规则是:**没有白名单时只放行"不需要审批"的工具**。需要审批的高危工具必须被显式点名才会交给模型。 这不是细节——如果把"没配白名单"当成"全部允许",一个新建的、还没配过工具的智能体就会直接拿到 shell 执行能力,而用户根本没意识到自己给了这个权限。 --- ## 功能覆盖 ### 已完整实现 | 域 | 说明 | |---|---| | 认证与租户 | 注册/登录/JWT/刷新、多租户切换、邀请、OIDC | | 知识库 | 增删改查、共享、复制、活动日志、置顶、收藏 | | 文档 | 上传、URL 抓取、手工录入、移动、复制、批量下载、清空 | | 解析 | **PDF / Word / Excel / PPT / Markdown / HTML / CSV / JSON / TXT** | | 检索 | 向量 + 关键词混合、RRF 融合、重排、MMR 去冗、上下文扩展 | | 问答 | SSE 流式、引用标注、多轮对话、会话分叉、消息搜索、导出 | | Agent | ReAct 循环、工具调用、审批门、function calling | | 评测 | 检索指标(Recall/Precision/MRR/NDCG)、数据集、多参数对比 | | MCP | 作为客户端连外部 MCP;**作为服务端对外提供**知识库检索 | | 模型管理 | 多厂商 CRUD、连通性探测、按租户选默认模型 | | 其他 | 标签、Wiki、记忆、数据源同步、系统配置、审计日志 | | 可观测性 | Langfuse / LiteFuse 链路追踪(OTLP 直连,默认关闭) | ### 明确不做 | 能力 | 原因 | |---|---| | **IM 消息运行时**(飞书/钉钉/企微回调) | 本项目定位本地运行,用不上。回调端点保留但返回 501,不假装成功 | | **容器级沙箱**(E2B / Cube / Docker) | 依赖外部容器编排服务,本地环境不可用。已提供进程级替代 | ### 已知限制 | 项 | 说明 | |---|---| | **Agent 的 function calling 未跑通真实模型** | 代码就绪,但只在架构层验证过。**这是下一轮第一件事** | | LLM 客户端只支持 OpenAI 兼容协议 | 不支持 Anthropic / Gemini 原生协议 | | 长对话没有压缩 | 超长对话会因上下文溢出失败 | | `/evaluation` 的 `dataset_id` 只读 JSON | parquet 需要先用 pyarrow 转一次(原因见偏差登记) | | 异步任务用进程内虚拟线程 | 进程重启会丢掉在途任务 | | 中文检索是子串匹配 | 没有中文分词,长句可能搜不到 | --- ## 质量保障 ### 三条验收标准(每次改动都要跑) ```powershell mvn -o clean test # 全量构建 + 全部测试 python scripts/api_missing.py # 端点对账,必须「缺失 0 条」 python scripts/bean_wiring_check.py # Bean 装配自检 ``` ### 为什么单测全绿不算数 本项目吃过三次亏——**单测都是绿的,应用却起不来**: 1. Spring Data 派生查询属性名写错(实体字段 `boolean temporary`,查询要写 `TemporaryFalse` 不是 `IsTemporaryFalse`) 2. 用例漏注册 `@Bean` 3. 测试探针上的 `@Configuration` 被组件扫描捡走,覆盖了真实 bean 所以有一道**上下文门禁测试**(`ApplicationContextGateTest`):在临时数据库上把**整个 Spring 上下文**起一遍,断言 46 个控制器都注册成功。这才是真正拦住「起不来」的那道门。 ### 三个自动化检查脚本 | 脚本 | 查什么 | |---|---| | `check_transactions.py` | `@Modifying` 查询是否都在事务里(漏标会在运行时抛 `TransactionRequiredException`,编译期看不出来) | | `check_route_coverage.py` | Go router 里有、但 swagger 清单里没有的路由(对账盲区) | | `strip_bom.py` | 源码文件是否被写入了 UTF-8 BOM | 第二条的价值:`api_missing.py` 只对 swagger 清单负责,**没有 swagger 注解的路由永远不在清单里**,于是对账永远说"缺失 0 条",而实际整块功能没做。这个脚本发现了 8 条这样的路由。 --- ## 文档索引 | 文档 | 内容 | |---|---| | [testdata/联调环境说明.md](testdata/联调环境说明.md) | **联调必读**:地址、账号、预置数据、注意事项 | | [docs/当前状态.md](docs/当前状态.md) | 现在做到哪、还剩什么、怎么验证 | | [docs/API等价清单.md](docs/API等价清单.md) | 377 条端点逐域状态(脚本生成) | | [docs/移植踩坑清单.md](docs/移植踩坑清单.md) | **19 条实测踩坑**,改代码前值得扫一眼 | | [docs/评测报告.md](docs/评测报告.md) | 检索指标与 7 组参数对比 | | `docs/*-移植偏差登记.md` | 各域与原版的行为差异(不是缺陷,是已知取舍) | | [tests/e2e-browser/README.md](tests/e2e-browser/README.md) | 浏览器 E2E 测试用法与 9 个踩坑记录 | | [docs/archive/](docs/archive/README.md) | **已归档**:开工阶段的计划类文档(内容过时,仅作历史留存) | --- ## 常用命令 ```powershell # 全量验证(改动后必跑) mvn -o clean test # 启动 mvn -o -pl weknora-boot spring-boot:run # 单独启动文档解析服务(remote 模式时) mvn -o -pl weknora-docreader spring-boot:run # 端口 8081 # 灌联调数据 python scripts/seed_testdata.py # 导出账号清单(Excel) python scripts/export_users.py # 端到端联调(真实模型) python scripts/e2e_smoke.py ``` --- ## 常见问题 ### 启动报 `Unable to find a suitable main class, ... on project weknora-java` 用了 `-am`。去掉它: ```powershell mvn -o -pl weknora-boot spring-boot:run # ✅ mvn -o -pl weknora-boot -am spring-boot:run # ❌ ``` ### 启动报 `Schema-validation: missing table` Flyway 没跑成功,或数据库是旧的。检查 `flyway_schema_history` 表;开发环境可以直接删库重建。 ### 改了代码但行为没变 Maven 增量编译可能跳过了整个模块。用 `mvn -o clean test` 而不是 `mvn -o test`。 ### `mvn install` 报权限错误 本地仓库路径在工作空间外。`~/.m2/settings.xml` 里的 `` 指向的就是它。 ### 问答说「知识库中没有找到相关内容」 按顺序排查: 1. 文档 `parse_status` 是 `completed` 吗(不是 `pending`/`failed`) 2. 配了 **Embedding 模型**吗(只有对话模型不够) 3. 检索阈值是不是太高(mock 嵌入模型下要调低到 0.01) ### 配了模型但回答还是模板文本 用的还是 `MockChatModel`(一个把检索结果照抄成回答的复读机)。它默认生效,用于验证链路。检查: - 模型列表里 `KnowledgeQA` 类型有模型吗 - 该模型 `is_default` 是 `true` 吗(或者它是第一个 active 的) --- ## 致谢与许可 原版 [WeKnora](https://github.com/Tencent/WeKnora) 由腾讯开源,本项目在学习其公开接口契约的过程中受益良多。 **再次说明:本项目仅供学习研究,禁止商业使用。** 原版的一切权利归腾讯公司及相关权利人所有;如需在生产环境使用,请采用原版并遵守其许可证。 本项目自身代码同样以学习共享为目的,不提供任何担保。