# 基于Claude cli的知识库问答系统 **Repository Path**: zxj6/Knowell ## Basic Information - **Project Name**: 基于Claude cli的知识库问答系统 - **Description**: 把指定文件夹当作个人知识库,通过本机已登录的 Claude Code CLI 主动搜索、阅读、回答问题。Vue 提问 → Spring Boot 中转 → spawn claude 子进程 → 流式回传答案。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-14 - **Last Updated**: 2026-05-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Knowell > 把任意文件夹变成一个会自己读文件的本地知识库 — 由你本机已登录的 **Claude Code CLI** 驱动。 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Java](https://img.shields.io/badge/Java-17%2B-007396)](https://adoptium.net/) [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.3.5-6DB33F)](https://spring.io/projects/spring-boot) [![Vue](https://img.shields.io/badge/Vue-3.5-4FC08D)](https://vuejs.org/) [![Vite](https://img.shields.io/badge/Vite-5-646CFF)](https://vitejs.dev/) Vue 前端提问 → Spring Boot 后端中转 → spawn 本机 `claude` 子进程 → 流式回传答案。 不需要写 prompt 把文件塞进上下文,也不需要先做 embedding。Claude 自己用 `Glob` / `Grep` / `Read` 去找文件、读文件、给出答案 — 就像你在 IDE 里用 Claude Code 那样。 --- ## 目录 - [它解决了什么问题](#它解决了什么问题) - [为什么不是 RAG / 向量库](#为什么不是-rag--向量库) - [快速开始](#快速开始) - [使用示例](#使用示例) - [架构](#架构) - [影子索引 — 让 Grep 能搜 PDF](#影子索引--让-grep-能搜-pdf) - [配置](#配置) - [安全模型](#安全模型) - [项目结构](#项目结构) - [常见问题](#常见问题) - [Roadmap](#roadmap) - [开发与贡献](#开发与贡献) - [License](#license) --- ## 它解决了什么问题 把一堆杂乱的本地文档(政务办事手册、技术笔记、发票、合同 ……)做成一个能用自然语言提问的助手,但同时: - 不想把数据传到任何在线服务 - 不想给每个文档预先做 chunking + embedding - 不想维护向量库 - 文档随时新增/修改,索引要跟着变 Knowell 让 Claude 自己当那个"在文件夹里翻东西的人"。你问"这份合同的违约金条款是什么",Claude 会先用 `Glob` 找包含"合同"的文件名,没找到就用 `Grep` 搜内容,搜到了再用 `Read` 把合同 PDF 读出来,最后告诉你答案以及出处。 --- ## 为什么不是 RAG / 向量库 | 维度 | 经典 RAG | Knowell | |------|---------|---------| | 索引代价 | 文档变化要重新 embedding | 启动时只对二进制文档抽文本(影子索引),纯文本零开销 | | 召回方式 | 向量相似度,Top-K 拼进 prompt | Agent 自己 Grep/Glob/Read,看到啥决定下一步 | | 长尾问题 | 切片越细,信息越碎,跨文件关联差 | 整个文件可以一次读完,跨文件靠多轮工具调用 | | 引用 | 要额外存原文映射 | 直接是文件路径 + 行号 | | 黑盒程度 | 高 — 不知道为什么没召回 | 低 — 看 Tool Use 流程就知道 Claude 找过哪些文件 | 代价是单次问答的延迟和 token 消耗都比 RAG 高,不适合高 QPS 场景。**它的位置是"个人/小团队的本地参考资料库"**,不是"百万级文档检索系统"。 --- ## 快速开始 ### 前置依赖 | 软件 | 版本 | 说明 | |------|------|------| | JDK | 17+(GraalVM JDK 21 已验证) | 后端运行时 | | Maven | 3.8+ | 后端构建 | | Node.js | 18+ | 前端构建 | | **Claude Code CLI** | 最新 | 必须能从命令行运行 `claude --version`;[官方安装](https://docs.anthropic.com/en/docs/claude-code/quickstart) | > **注意**:Knowell 不直接调用云端 API,它复用你本机已登录的 Claude Code 会话。请先确认 `claude` 命令在你的 shell 能跑通且已完成登录(`claude /login`)。 ### 三步启动 ```bash # 1. 克隆 git clone https://gitee.com/zxj6/Knowell.git cd Knowell # 2. 启动后端(默认 :8080) cd backend mvn spring-boot:run # 3. 启动前端(新开终端,默认 :5173) cd frontend npm install npm run dev ``` 打开 http://localhost:5173,把你的文档丢进 `knowledge-base/` 目录,问任何问题即可。 > 影子索引会在后端启动时自动构建。控制台看到 `✓ 影子索引就绪: 新建=N, ...` 表示就位。运行时往 `knowledge-base/` 加文件会被 `WatchService` 自动捕获并补索引,不用重启。 ### Windows IDEA 启动小坑 如果 IDEA 报 `YAMLException: MalformedInputException`,做以下任一即可: - File → Settings → Editor → File Encodings → 全部设为 UTF-8 - 或在 Run Configuration 加 VM option `-Dfile.encoding=UTF-8` --- ## 使用示例 下面是两个虚构的示例,演示 Knowell 的回答风格(工具调用 + 行级引用): ``` > Spring Boot 项目里 SSE 的超时怎么配置? 🔍 搜索内容 pattern: "SseEmitter|async.*timeout" 📄 读取文件 notes/spring-boot-sse.md SSE 长连接的超时由两层控制: 1. SseEmitter 构造参数:new SseEmitter(180_000L) 表示 3 分钟 2. Spring MVC 异步请求超时:spring.mvc.async.request-timeout=300000 建议把 (1) 设得比 (2) 小,以便业务侧先于框架处理超时。 来源:notes/spring-boot-sse.md:12-23 ``` ``` > ripgrep 默认会跳过哪些文件? 🔍 搜索内容 pattern: "ripgrep|binary|skip" 📄 读取文件 notes/cli-tools/ripgrep.md ripgrep 默认跳过: 1. .gitignore / .ignore 命中的文件 2. 二进制文件(通过字节启发式检测) 3. 隐藏文件(.开头),除非加 --hidden 用 --no-ignore + -uu 可以让它"什么都搜"。 来源:notes/cli-tools/ripgrep.md:5-14 ``` > 实际效果取决于你 `knowledge-base/` 里放了什么文件、文档结构如何。Knowell 不会修改也不会上传你的文档。 --- ## 架构 ``` ┌─────────────────┐ /api/ask ┌─────────────────────┐ │ Vue 3 + Vite │ ─────POST─────► │ Spring Boot 8080 │ │ (5173) │ │ │ │ fetch + SSE │ ◄────SSE 流───── │ AskController │ └─────────────────┘ │ AskService │ │ │ │ │ ▼ ProcessBuilder │ ClaudeCliService │ │ │ │ │ ▼ spawn │ │ ┌───────────────┐ │ │ │ claude CLI │ │ │ │ -p │ │ │ │ --output │ │ │ │ stream-json │ │ │ │ cwd=knowledge-base/ │ │ └───────┬───────┘ │ │ │ JSONL │ │ ▼ │ │ Glob/Grep/Read 工具调用 │ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ knowledge-base/ │ │ ├── *.md / *.txt │ │ ├── *.pdf / *.docx │ │ └── .knowell-index/ │← Tika 抽出的影子 .txt └─────────────────────┘ ``` **两条关键边**: 1. **前端 ↔ 后端**:HTTP POST 起一条 SSE 长连接,后端实时把 `text` / `tool` / `done` / `error` 事件推过去。前端没用 `EventSource`(它只支持 GET),用 `fetch + ReadableStream` 自己解析 SSE。 2. **后端 ↔ Claude CLI**:`ProcessBuilder` spawn `claude -p --output-format stream-json --verbose`,逐行读 stdout(JSONL),解析后再通过 SSE 透传给前端。多轮对话靠 CLI 的 `--resume ` 续接。 设计取舍: - **不用 Anthropic Java SDK** — 直接 fork CLI 子进程,工具调用复用 Claude Code 现成的实现(Glob/Grep/Read 都是 CLI 内置工具),不重造轮子。 - **不存 user message history** — Claude CLI 用它自己的 `session_id` 维护历史,后端只在 Caffeine 里记一个 knowellSessionId → claudeSessionId 的映射。 - **SSE 不要被代理缓冲** — Vite dev server 的代理配置里关掉了缓冲,确保 token 实时到达浏览器。生产部署若用 Nginx,需要 `proxy_buffering off;`。 --- ## 影子索引 — 让 Grep 能搜 PDF Claude Code 的 `Grep` 工具底层是 [ripgrep](https://github.com/BurntSushi/ripgrep),只能扫纯文本字节流。PDF/docx/xlsx 是二进制(压缩 + 字形对象),ripgrep 默认直接跳过这些文件,导致你问"发票里写的金额"时 Grep 永远返回 0 命中。 Knowell 在后端 `IndexBuilder` 里解决这个问题: ``` 启动时: walk knowledge-base/ for each file: if 扩展名 ∈ {pdf, doc, docx, xls, xlsx, ppt, pptx, rtf, html, epub}: 用 Apache Tika 抽出纯文本 写到 knowledge-base/.knowell-index/<同构路径>.txt (同时记下 mtime;下次启动 mtime 没变就跳过) 运行时: Java NIO WatchService 监听 knowledge-base/ 树 新增/修改 → 重新抽 删除 → 清掉影子文件 ``` 然后在 `system-prompt-append` 里告诉 Claude: > 二进制文档在 `.knowell-index/` 下有同名 `.txt` 影子文件。Grep 命中影子后,要去 Read **原文件**(去掉 `.knowell-index/` 前缀和 `.txt` 后缀);永远不要把影子路径暴露给用户。 效果: - ✅ `Grep "违约金"` 能命中 `合同.pdf.txt` - ✅ Claude 回复时引用的是 `合同.pdf`,而非影子路径 - ✅ 用户感知不到这一层,但能搜到原本搜不到的内容 **为什么不让 Grep 直接走 Tika?** Grep 是 CLI 内置工具,我们碰不到它的源码;退一步,也不希望每次搜索都触发解析(慢)。预生成影子文件 = 一次抽取多次复用。 --- ## 配置 后端配置全部在 `backend/src/main/resources/application.yml`: | 配置项 | 默认 | 作用 | |--------|------|------| | `knowell.knowledge-base-path` | 必填 | 知识库根目录,是 `claude` 子进程的 cwd | | `knowell.session-ttl-minutes` | 30 | 多轮会话 TTL(超时后下次问会开新 session) | | `knowell.claude.cli-path` | `claude` | `claude` 可执行路径;走 PATH 或写绝对路径 | | `knowell.claude.max-turns` | 8 | 单次问答最大 agent 循环轮数(防死循环) | | `knowell.claude.timeout-seconds` | 240 | 子进程整体超时 | | `knowell.claude.system-prompt-append` | 见 yml | 追加到 Claude 系统提示词,定义工作目录、引用规则、影子索引映射等 | | `knowell.claude.allowed-tools` | `[Read, Grep, Glob]` | 工具白名单 | | `knowell.claude.disallowed-tools` | 见 yml | 工具黑名单(双保险),默认禁 `Edit / Write / Bash / WebFetch / WebSearch` 等 | 修改后重启后端生效。前端在 `frontend/vite.config.js` 里把 `/api` 代理到 `localhost:8080`,改后端端口时记得同步。 --- ## 安全模型 这是个**给本机/局域网内可信用户**使用的工具,不是公网服务。但即便如此,做了几层防御: | 层 | 做法 | 防的是什么 | |----|------|----------| | 工具白名单 | `allowed-tools` 只开 Read/Grep/Glob | Claude 想写文件/执行命令也写不了 | | 工具黑名单 | `disallowed-tools` 显式禁 Edit/Write/Bash/WebFetch/WebSearch | 白名单遗漏时双保险 | | 目录隔离 | 子进程 cwd 固定为 `knowledge-base/` | 限制 Read/Glob 的搜索范围(注意:不是 chroot,Claude 仍能用绝对路径访问外部) | | 资源限制 | `max-turns` + `timeout-seconds` | 死循环 / 超长输出 | | IP 限流 | `RateLimitFilter` 默认 30 次/分钟/IP | 同一 IP 刷接口 | | 内容隔离 | 影子索引和原文件都在 `knowledge-base/` 内,不出根目录 | 抽取过程不外泄 | **⚠️ 不做的**: - 不验证用户身份。所有调 `/api/ask` 的请求都被视为可信。 - 不审查 Claude 的输出。如果你的知识库里有敏感信息,Claude 就会照实回答。 - 不限制问题内容。 **部署到多人/对外环境前必须做**: 1. 加身份认证(Spring Security / 反向代理 BasicAuth / OAuth) 2. 知识库内容预审,确保没有不想被检索到的东西 3. 监控 `claude` 进程的 token 消耗(防滥用) --- ## 项目结构 ``` Knowell/ ├── backend/ Spring Boot 后端 │ ├── pom.xml │ └── src/main/ │ ├── java/com/knowell/ │ │ ├── KnowellApplication.java 入口 │ │ ├── config/ │ │ │ ├── KnowellProperties.java yml → POJO 映射 │ │ │ └── WebConfig.java CORS │ │ ├── controller/ │ │ │ ├── AskController.java POST /api/ask (SSE) │ │ │ ├── GlobalExceptionHandler.java │ │ │ └── RateLimitFilter.java IP 限流 │ │ ├── dto/ │ │ │ ├── AskRequest.java │ │ │ └── SseEvent.java │ │ └── service/ │ │ ├── AskService.java 编排 + SSE 推送 │ │ ├── ClaudeCliService.java spawn claude 子进程,解析 stream-json │ │ ├── IndexBuilder.java Tika 影子索引 + WatchService │ │ └── SessionManager.java knowellSid ↔ claudeSid 映射 │ └── resources/ │ └── application.yml ├── frontend/ Vue 3 + Vite │ ├── package.json │ ├── vite.config.js │ └── src/ │ ├── App.vue │ ├── main.js │ ├── views/ChatView.vue 主聊天页 + 欢迎语 │ ├── components/ │ │ ├── MessageList.vue │ │ ├── MessageItem.vue 用户/助手气泡 + 工具调用展示 │ │ └── InputBox.vue │ └── composables/ │ └── useSseChat.js fetch + ReadableStream 解析 SSE ├── knowledge-base/ ← 把文档丢这里(不进 git) │ └── .knowell-index/ ← IndexBuilder 自动生成的影子目录 ├── .gitignore knowledge-base/ 整体 ignore ├── .ignore ripgrep 专用,反向打开 knowledge-base 让 Grep 能搜 ├── LICENSE MIT └── README.md ``` --- ## 常见问题
启动后端报 "claude: command not found" 或 "无法启动子进程" `claude` 必须在 PATH 里,或者在 `application.yml` 里把 `knowell.claude.cli-path` 改成绝对路径(如 `C:/Users/you/.local/bin/claude.exe`)。验证:在跟 Java 进程相同的 shell 环境下跑 `claude --version` 能输出版本号即可。
问问题没反应,前端一直转圈 1. 后端日志看有没有 `[claude stderr]` 输出 — 通常是 CLI 自己报错。 2. 看是不是 Claude CLI 没登录:在终端跑一次 `claude /login`。 3. SSE 被反向代理缓冲了 — Nginx 加 `proxy_buffering off;` 和 `proxy_cache off;`。 4. 防火墙挡了 8080 → 5173 的代理。
Grep 搜不到 PDF 里的内容 确认后端启动日志有 `✓ 影子索引就绪: ...`。如果失败数 > 0,看 WARN 日志里 Tika 报什么错,常见是 PDF 加密或损坏。也可以手动检查 `knowledge-base/.knowell-index/` 下有没有对应的 `.txt` 文件。
知识库文件改了,Claude 还在用旧内容 `IndexBuilder` 会通过 `WatchService` 自动捕获变更(500ms 去抖),但 Claude 的多轮会话有 30 分钟 TTL,期间它可能从缓存历史里取内容。点前端"新建会话"重置 session,或等 TTL 过期。
每次提问都很慢 / 想优化延迟 主要时间花在 Claude 的 agent loop 上(几次 tool 调用 + 模型推理)。可优化方向: - 调小 `max-turns`(默认 8,简单问题 4 足够) - `system-prompt-append` 里给更具体的工作流指引,减少不必要的探索 - 文档分得太散会拖慢 Glob/Grep,合并同类文档能加速
能不能给前端套个登录? 可以,但项目本身没集成。建议: - 简单做:Nginx 反向代理 + BasicAuth - 标准做:Spring Security + JWT,在 `AskController` 上加 `@PreAuthorize` - 企业做:接 OAuth2/SSO
支持哪些文件格式? - **纯文本**(md/txt/json/csv/yaml/源代码 ……):Grep 直接搜,Read 直接读 - **二进制文档**(pdf/doc/docx/xls/xlsx/ppt/pptx/rtf/html/epub):走影子索引 → Grep 能搜,Read 由 Claude Code 用多模态能力直接读 - **图片**(png/jpg):Read 可以用多模态识别,但 Grep 搜不到 - **音视频**:不支持 要增加扩展名:改 `IndexBuilder.java` 里 `BINARY_EXTS`。
--- ## Roadmap - [ ] 文件上传 UI(目前只能往 `knowledge-base/` 拖文件) - [ ] 引用渲染:把 `文件:行号` 变成可点击的跳转 - [ ] OCR:扫描件 PDF 走 OCR(Tika 默认不开) - [ ] Docker compose 一键启动 - [ ] 多知识库切换 - [ ] 后端可观测性(Micrometer + Prometheus) - [ ] 系统消息可配置(欢迎语/示例问题) - [ ] 国际化(目前 UI 是中文) 欢迎提 Issue 或 PR 推进任意一项。 --- ## 开发与贡献 ### 本地开发 后端用 IntelliJ IDEA 打开 `backend/` 目录最舒服,直接 Run `KnowellApplication`。前端任意编辑器 + `npm run dev`,Vite HMR 秒级生效。 修改 `application.yml` 需重启后端。修改 `IndexBuilder.java` 后想验证索引重建,可以直接删掉 `knowledge-base/.knowell-index/` 再启动。 ### 提交规范 按 [Conventional Commits](https://www.conventionalcommits.org/): ``` feat(backend): ... 新功能 fix(frontend): ... bug 修复 docs: ... 文档 refactor(service): ... 重构 chore: ... 构建/依赖/工具 ``` ### 提 PR 前自查 - [ ] 后端编过(`mvn -DskipTests compile`) - [ ] 前端构建过(`npm run build`) - [ ] 没有把私人文档误提交进 `knowledge-base/`(`.gitignore` 已挡) - [ ] 没引入需要外网调用的依赖(本项目以"完全本地化"为卖点) - [ ] 改了配置项的话,README 的配置表也同步更新 ### Bug 报告 提 Issue 时尽量带上: - 操作系统 + JDK 版本 + Node 版本 + Claude CLI 版本(`claude --version`) - 后端日志(从 `Started KnowellApplication` 开始) - 复现步骤,如果跟某种文件格式相关,提供一个最小可复现样本 --- ## License [MIT](LICENSE) © 2026 zhangxingju