Aaryanverma/graybox

GitHub: Aaryanverma/graybox

Gray Box 是一个本地优先的个人长期记忆库,通过 LLM 自动将随手笔记整理为相互链接的 Markdown wiki,并提供带来源引用的问答能力。

Stars: 14 | Forks: 0

Gray Box **一个本地优先的长期记忆库,帮你留住那些原本会遗忘的任何事情。** 由 [Aaryan Verma](https://linkedin.com/in/aaryanverma) 用 ❤︎ 制作 像使用笔记本一样与它交流。它会在后台悄悄地将你的笔记转化为一个生动且相互链接的 wiki —— 并提供带有证据的问答。 `Markdown 存储` · `无需数据库` · `支持任何 LLM` ![Build Status](https://static.pigsec.cn/wp-content/uploads/repos/cas/96/961ecbbbf6b9c6da2ef90dc16b38cd41fe9f3dde0881cf199a780512e101a580.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?color=green)](https://opensource.org/licenses/MIT) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/Aaryanverma/graybox)
📹 视频演示

Gray Box Demo

## 这是什么? 你不断会产生想要留住的信息 —— 工作会议中做出的决定、队友的名字和角色、别人欠你的任务,但也可能是朋友的生日、别人推荐的书、看医生的笔记,或者是你洗澡时冒出的灵感。这些信息大部分都会蒸发遗忘。**Gray Box** 是一个小巧的、本地优先的工具,它可以: 1. **捕捉**任何你输入或粘贴的内容,即时且不带评判 —— 无论是工作笔记、个人日记、项目想法还是其他任何东西。 2. **整理**在后台进行 —— 自动提取出人物、项目、任务和决定,生成相互链接的独立 Markdown 页面。 3. **回答**关于你已捕捉内容的任何问题,并准确引用答案来源于哪条笔记或页面 —— 在不知道时会拒绝猜测。 它附带的页面类型偏向于工作场景(`project`、`meeting`、`decision`、`task`),因为这是最初的用例,但它的运行机制并没有特定局限于工作 —— `person` 页面并不在意是同事还是朋友,而 `topic`/`journal` 页面用于爱好或个人反思时,与用于工作概念一样好。你可以为一个工作场景使用一个 workspace,为个人生活使用另一个(参见 [Workspaces](#workspaces)),或者将两者混合在一起 —— 这由你决定。 不需要向量数据库。没有云端锁定。没有专有格式。磁盘上只有 `.md` 文件,你(或任何其他工具)可以永远读取它们。 ## 目录 - [为什么这样构建](#why-its-built-this-way) - [工作原理](#how-it-works) - [安装说明](#installation) - [快速开始](#quick-start) - [交互式 TUI](#interactive-tui) - [Workspaces](#workspaces) - [命令参考](#command-reference) - [配置](#configuration) - [Wiki 页面长什么样](#what-a-wiki-page-looks-like) - [设计原则](#design-principles) - [路线图](#roadmap) ## 为什么这样构建 | 选择 | 理由 | |---|---| | 📁 **纯 Markdown + YAML frontmatter** | 永远可读、可 grep、可 diff,兼容任何编辑器或静态网站生成器。无供应商锁定。 | | 🚫 **默认无向量数据库** | 对于个人规模(几百到几千页面),关键词搜索速度快、透明且可调试。Embeddings 是一个*可选的*升级,而不是先决条件 —— 并且启用时,会采用与其他一切内容相同的 0–1 分数量化,因此只需一个 `min_score` 阈值即可控制各处的相关性。 | | 🔒 **不可变收件箱** | 你的原始笔记永远不会被整理器编辑或删除。如果 AI 提取错了什么,你最初的文字总是还在那里,作为最后的保障。 | | 🔌 **可插拔的 LLM** | 底层由 [LiteLLM](https://github.com/BerriAI/litellm) 驱动,因此你可以将其指向 OpenAI、Anthropic、Gemini、Mistral、Ollama 或任何自托管模型 —— 只需更改一个配置值即可。 | | 🧩 **小型的单一用途模块** | 捕捉、整理、搜索和检索互不干涉内部逻辑。你可以替换其中任何一个,而不影响其余部分。 | | 🕸️ **真正的图谱,而不仅仅是 RAG 索引** | 页面带有 `related`/`backlinks`/`sources`,检索时会通过它们进行一跳的遍历 —— 因此,即使答案的证据存在于*被链接的*页面上,而非匹配查询的页面上,也依然能被找到。 | ## 工作原理 ``` flowchart LR A["✍️ You type a note"] --> B["📥 Capture Agent"] B -->|"writes verbatim, never edited"| C[("inbox/*.md")] C --> D["🧭 Organizer Agent"] D -->|"LLM extracts entities,\ntasks & decisions"| E[("wiki/*.md")] E --> F["🔍 Search + 1-hop graph expansion"] G["❓ You ask a question"] --> H["📚 Retrieval Agent"] H --> F F -->|"relevant pages, with scores"| H H -->|"cited, grounded answer"| I["💬 Answer + sources"] C -.->|"fallback if wiki has nothing yet"| H ``` **用通俗的话解释各个 agent:** - **Capture** —— 刻意*几乎不做事*。它将你的文本原封不动地写入 `inbox/`。没有解析,没有 AI,没有延迟。这一步永远不应成为让你丢失想法的原因。 - **Organizer** —— 按需运行(`organize`)。它读取未处理的收件箱项目,要求 LLM 以 JSON 格式提取结构化事实(人物、项目、任务、决定、关系),然后由*确定性的 Python 代码* —— 而非 LLM —— 创建/合并实际的 wiki 页面并维护反向链接。这使得写入操作可预测且可审计。 - **Retrieval** —— 当你执行 `ask` 或 `chat` 时,它会优先搜索 wiki 页面(关键词 + 可选的语义搜索),通过 `related`/`backlinks` 展开一跳,以便让虽然链接但词面上不匹配的页面也能浮现出来,然后要求 LLM **仅使用该上下文**进行回答,并提供类似 `[person/aaryan]` 的内联引用。如果没有找到相关内容,它会诚实地表示无能为力,而不是胡编乱造 —— 仅作为最后手段才会回退到原始收件箱搜索。 - **Curate** —— 用于人工介入修复(`merge`、`edit`、`delete`)organizer 的错误:重复页面、错误的标题/类型、幻觉实体。这里没有任何操作会自动运行;每个动作都是显式的命令,而且这里也不会调用 LLM。 ## 安装说明 ``` pip install graybox # 用于 tests pip install graybox[test] ``` ``` git clone https://github.com/Aaryanverma/graybox cd graybox pip install -e . ``` 然后将其指向一个 LLM。按照你的提供商期望的方式设置 API key —— 例如,在 `.env` 文件中(由 `config.yaml` 中的 `env_file:` 引用),或者将其设置为像 `GRAYBOX_LLM_API_KEY` 这样的环境变量。应用根目录默认位于当前终端目录下的 `.graybox` 中,而不是你的主文件夹。 ## 快速开始 体验 Gray Box 最快的方法就是直接运行它,不加任何参数,让交互式菜单引导你: ``` graybox ``` 这会让你进入一个全屏终端 UI —— 使用方向键移动,按 Enter 选择 —— 在这里,`capture`、`organize`、`ask`、`chat`、`search`、`pages`、`dupes`、`dashboard` 以及 workspace 切换都只需敲击一下键盘即可。这是感受整个流程(capture → organize → ask)的推荐方式,无需记忆任何命令。
如果你更倾向于编写脚本或将其接入其他工具,上述每个操作也都提供了纯 CLI 子命令: ``` # 1. 捕捉你脑海中的任何想法 —— 无需结构化 graybox capture "Talked to Aaryan about the Atlas migration; he owns the DB cutover, due next Friday" # 2. 将捕捉的内容转化为结构化、相互链接的 wiki 页面 graybox organize # 3. 查看它构建的内容 graybox pages # 4. 提出问题 —— 获取带引用的回答 graybox ask "who owns the Atlas DB cutover and when is it due?" # 5. 或者进行持续的对话,而不是一次性提问 graybox chat ``` 想在写入任何内容之前预览 `organize` *将会*做什么吗? ``` graybox organize --dry-run ``` 这依然会调用 LLM,并向你准确展示哪些页面会被创建(`new`)或更新(`updated`)—— 但不会向磁盘写入任何内容,也不会将任何内容标记为已处理,因此随后的真实运行会紧接在模拟运行之后继续进行。 ## Workspaces 上述所有操作都在一个 **workspace** 内进行 —— 这是一个完全隔离的收件箱 + wiki + 已处理/已遗忘状态。开箱即用时你拥有一个名为 `personal` 的 workspace(可通过 `config.yaml` 中的 `default_workspace` 进行配置),但你可以根据需要创建任意数量的 workspace —— 例如 `personal` 和 `work` —— 以防不相关的知识库相互渗透干扰。在 `work` 中创建的 wiki 页面对处于激活状态的 `personal` 是不可见的(在进行 `ask`/`search`/`pages` 时),反之亦然。 ``` # 查看你创建的每个 workspace,并标记当前活动的 workspace graybox workspace-list # 创建一个新的空白 workspace 并立即切换到它 graybox workspace-create work --description "Day job knowledge base" # 随时来回切换 —— 不会触碰或丢失任何数据 graybox workspace-switch personal graybox workspace-switch work # 省略名称以改用交互式选择器 graybox workspace-switch ``` 有几点值得了解: - **隔离是实质性的,而非装饰性的。** `capture`、`organize`、`ask`、`chat`、`search`、`pages`、`dupes` 和 `dashboard` 全都在当前激活的 workspace 上运行 —— 可以通过 `graybox workspace-switch ` 设置(保存在 `config.yaml` 的 `active_workspace` 键中),或者通过指定指向不同配置文件的 `--config` 在每次调用时覆盖。 - **跨 workspace 搜索是可选行为。** `graybox ask "" --all`、`graybox chat --all` 和 `graybox search "" --all` 会一次性搜索*所有* workspace,并标记每个结果/引用来自于哪个 workspace(例如 `work/project/atlas-migration`),而不是仅仅搜索当前激活的 workspace。在这种模式下会跳过图谱展开,因为 `related`/`backlinks` 引用并未限定 workspace。 - **每个 workspace 可以位于磁盘上的任何位置。** `workspace-create --path ` 会将 workspace 的数据存储在自定义位置(例如,对于将 `work` workspace 保存在公司管理的同步文件夹中非常有用),同时依然将其注册在相同的 `graybox workspace-switch` 工作流中。如果省略 `--path`,默认会保存在 `/workspaces//` 下。 - **交互式 TUI**(不带参数运行 `graybox`)拥有专门的 `switch-workspace` 和 `create-workspace` 菜单项,它们映射了上述的 CLI 命令,包括同样的可选自定义路径提示。 - **绝不会有任何内容在 workspace 之间被静默合并。** 如果同一个事实需要存在于两个 workspace 中,请在两者中都进行 capture —— 设计上它们之间没有自动同步。 ## 命令参考 | 命令 | 作用 | |---|---| | `graybox capture ""` | 立即向不可变的收件箱保存一条笔记。省略文本可从 stdin 读取;使用 `--file ` 可导入整个文件。 | | `graybox organize` | 将所有未处理的收件箱项目处理成 wiki 页面。添加 `--dry-run` 可在不写入的情况下进行预览。 | | `graybox ask ""` | 搜索 wiki(关键词 + 可选的语义搜索 + 一跳图谱展开),要求 LLM 仅根据找到的内容进行回答,并输出答案及其来源。 | | `graybox chat` | 多轮问答会话 —— 无需每次都返回主菜单即可追问。对话历史会被串联到搜索和 LLM prompt 中,因此代词/省略语(如“什么时候到期?”、“为什么选他?”)会根据上一轮的内容进行解析。事实来源规则依然适用:历史记录仅用于解析引用,绝不自行提供事实。使用 `--all` 搜索所有 workspace。 | | `graybox search ""` | 对 wiki 页面进行快速的本地关键词搜索 —— 无需调用 LLM。使用 `--top-k N` 控制结果数量。 | | `graybox pages` | 列出所有 wiki 页面。使用 `--type project\|person\|meeting\|technology\|topic\|task\|action\|decision` 进行筛选。 | | `graybox status` | 快速摘要:workspace 路径、收件箱数量、页面数量、当前激活的 LLM 模型。 | | `graybox dashboard` | 生成一个独立且只读的 HTML 仪表板(`/exports/dashboard.html`),包含任务看板、过滤器以及展示你 wiki 交叉链接的力导向图。绝不会向 `inbox/` 或 `wiki/` 回写内容。[查看示例](assets/dashboard.png)| | `graybox dupes` | 标记*看起来*像是重复项的 wiki 页面(模糊名称匹配)。仅供参考 —— 不会自动合并任何内容。`--type` 用于限定范围,`--threshold`(0–1,越接近 1 表示越相似;默认:`retrieval.dedup_threshold`,0.85)用于调整灵敏度。 | | `graybox merge ` | 将 `` 合并到 `` 中 —— 对齐 notes、sources、aliases、tags 和 links,在发生冲突时保留 `` 的 title/type/status/summary,删除被丢弃的页面,并重新连接指向它的所有其他页面的链接。`--dry-run` 可预览。 | | `graybox edit ` | 修复被错误提取的页面:`--title`、`--type`、`--status`、`--alias`(可重复)。重命名或更改类型会移动该页面,并重新连接对它的每一个引用。`--dry-run` 可预览。 | | `graybox delete ` | 删除幻觉/错误创建的页面并从 wiki 的其他部分去除指向它的悬空链接。报告它追溯到了哪些收件箱项目。`--dry-run` 可预览。 | | `graybox forget ` | 撤销一次糟糕的记录。默认情况下它是一个软删除标记 —— 原始文件保留在磁盘上,但会从 `search`、`pages` 计数和未来的 `organize` 运行中排除。`--purge` 还会删除原始文件(不可逆)。`--scrub` 会额外将已经从该笔记中提取的内容从它们所在的 wiki 页面中清除掉。`--reason "..."` 用于记录原因。 | | `graybox rebuild-index` | 为语义搜索重建 embedding 索引(仅在 `embeddings.enabled: true` 时相关)。为在开启 embeddings 之前写入的页面进行补录。 | | `graybox refresh-summaries` | 根据累积的笔记重新整合每个页面的摘要,防止生命周期较长的页面内容过时。支持 `--type`、`--dry-run`、`--min-notes`、`--verbose`。 | | `graybox workspace-list` | 列出所有的 workspace,并标记出当前激活的 workspace。 | | `graybox workspace-switch [name]` | 切换激活的 workspace。省略 `name` 可呼出交互式选择器。 | | `graybox workspace-create [name]` | 创建一个新的、空的 workspace 并切换过去。`--description "..."` 用于添加备注;`--path ` 用于将其数据存储在默认的 `/workspaces//` 之外的位置。省略 `name` 可呼出交互式提示。 | 在交互式 TUI 中(不带参数运行 `graybox`),**capture** 界面允许你按下 `F` 通过路径导入文件,而不是直接输入笔记 —— 等同于 `graybox capture --file `。Workspace 的创建也接受可选的自定义路径,因此 workspace 可以位于磁盘上的任何位置,并且该路径会被记住以便后续切换。这里还提供了一个 **dupes** 界面用于只读浏览;目前 `merge`、`edit`、`delete` 和 `forget` 仅支持 CLI 操作,因为它们需要结构化参数,很难干净地映射到基于按键的 TUI 上。 每个命令都接受 `--config ` 来使用除 `./config.yaml` 之外的配置文件。 ## 配置 项目根目录下的 `config.yaml` 控制着一切。环境变量始终优先于文件,而文件始终优先于内置默认值。 ``` root: ".graybox" # app root inside the current terminal directory default_workspace: "personal" # workspace created/used the very first time you run any command active_workspace: "personal" # which workspace is currently active — kept in sync by `workspace-switch` env_file: "creds.env" # optional .env file for API keys llm: model_name: "openai/gpt-5.6" # any LiteLLM-supported model string temperature: 0 base_url: "" max_tokens: 1024 retrieval: top_k: 5 # how many wiki pages to pull into context per question min_score: 0.4 # relevance threshold — same 0–1 scale for keyword AND semantic search dedup_threshold: 0.85 # similarity threshold for organize/dupes/merge (0-1, closer to 1 = more similar) embeddings: enabled: false # opt-in: turn on for semantic/paraphrase recall model_name: "openai/text-embedding-3-small" # any LiteLLM-supported embedding model string base_url: "" ``` 这三个阈值 —— `min_score`、`dedup_threshold` 以及(开启 embeddings 时的)语义相似度得分 —— 都被统一到了**同一**个 `0.0`–`1.0` 的尺度上,越接近 `1.0` 始终意味着“更相似/更有信心”,越接近 `0.0` 始终意味着“匹配微弱或无匹配”。无论你在调整哪个旋钮,都只需要遵循同一个心智模型: - **`min_score`** 决定了一个 wiki/收件箱页面是否*与问题足够相关*(由 `ask`/`chat`/`search` 使用,同时适用于关键词和语义打分)。一个能有力回答明确问题的页面得分通常在 `0.6`–`1.0`;`~0.35`–`0.5` 是一个合理的“勉强相关”底线。 - **`dedup_threshold`** 决定了两个*名称*是否属于*同一个现实世界实体*(由 `organize` 在决定是否复用现有页面时使用,也由 `dupes`/`merge` 使用)。同一个名字的拼写错误或昵称变体得分约为 `~0.85`–`1.0`;不相关的名称得分会远低于此,因此这个阈值需要保持在一个较高的水平,以避免误合并不同的人物/项目。 **关键环境变量覆盖项:** | 变量 | 覆盖项 | |---|---| | `GRAYBOX_ROOT` / `GRAYBOX_WORKSPACE` | `root`(包含 `workspaces/` 的应用根目录;`GRAYBOX_WORKSPACE` 是同一设置的旧版别名 —— 它**不会**选择当前激活的是哪个 workspace) | | `GRAYBOX_ACTIVE_WORKSPACE` | `active_workspace`(当前激活的 workspace —— 效果等同于 `graybox workspace-switch `) | | `GRAYBOX_DEFAULT_WORKSPACE` | `default_workspace`(首次运行时创建/使用的 workspace) | | `GRAYBOX_LLM_MODEL` | `llm.model_name` | | `GRAYBOX_LLM_BASE_URL` | `llm.base_url` | | `GRAYBOX_LLM_API_KEY` | `llm.api_key` | | `GRAYBOX_TEMPERATURE` | `llm.temperature` | | `GRAYBOX_TOP_K` | `retrieval.top_k` | | `GRAYBOX_MIN_SCORE` | `retrieval.min_score` | | `GRAYBOX_DEDUP_THRESHOLD` | `retrieval.dedup_threshold` | 由于它构建在 LiteLLM 之上,`model_name` 可以接受类似 `openai/gpt-5.6`、`anthropic/claude-sonnet-5`、`gemini/gemini-3.1`、`ollama/llama3` 的字符串,或者任何自托管/代理 endpoint。`embeddings.model_name`(例如 `openai/text-embedding-3-small`)也是如此。 ## Wiki 页面长什么样 每个页面都是带有 YAML frontmatter 的纯 Markdown 文件 —— 可以在任何文本编辑器中打开,不需要特殊的工具: ``` --- id: aaryan-verma type: person title: Aaryan Verma created: 2026-07-25T07:10:00Z updated: 2026-07-25T07:10:00Z aliases: [] related: [project/atlas-migration] backlinks: [task/db-cutover] sources: [20260725T071000-9f3a] tags: [] status: "" --- # Aaryan Verma ## 摘要 Data scientist by profession. ## 笔记 - (2026-07-25T07:10:00Z) Data scientist by profession. _(source: inbox/20260725T071000-9f3a)_ ## 相关 - [[project/atlas-migration]] ## 反向链接 - [[task/db-cutover]] ## 来源 - inbox/20260725T071000-9f3a ``` 每个事实都可以追溯到 `sources:` 条目中的一个收件箱项目 ID —— 因此你总能找到任何声明背后的原始笔记。 ## 设计原则 - **Capture 绝不能失败或丢失数据。** 这是整个系统中唯一对“自作聪明”零容忍的部分。 - **LLM 只负责推理 —— 它绝不直接接触文件系统。** 所有的页面创建、slug 处理、合并和反向链接维护均由确定性的 Python 代码完成,因此行为可审计,且不会在多次运行之间出现飘忽不定的情况。 - **回答要么有理有据,要么坦诚相告,绝不捏造。** 如果检索 agent 找不到支持的上下文,它会直截了当地说明,而不是瞎猜 —— 助手宁愿显得无用也不愿出错。这在 `chat` 模式下同样成立:对话历史或许能解析出你*正在问什么*,但它绝不会自行提供事实依据。 - **文件系统优先,Embeddings 其次。** 向量搜索是针对释义性问题的有效升级(例如当你的笔记只写了“数据科学家”时,提问“这个项目里负责数据的人是谁?”),但它是可选的,且采用与其他一切完全相同的评分尺度,而不是引入第二个需要学习的阈值。 ## 常见问题
这和笔记应用、wiki 工具或通用的 RAG 聊天机器人有什么不同?
有几点决定了这个项目的*形态*,但我们并不声称它对所有人来说都是正确的形态: - **你不需要自己去构建任何结构。** 大多数笔记/wiki 工具假设你会刻意地去创建页面、打标签并建立链接。在这里,你只需按照脑海中浮现的样子记录下松散、凌乱的文字,LLM 就会在后台为你进行实体提取和交叉链接。如果你已经很享受手工梳理笔记的过程,那么像 Obsidian 这样的专业编辑器可能更适合你 —— 而且由于 Gray Box 在磁盘上仅仅是 Markdown + YAML frontmatter,你甚至可以用这样的编辑器打开 Gray Box 的 workspace 来浏览它。 - **它是一个由具有类型的 fact 组成的图谱,而不是一堆可搜索的文档。** 一个页面就是一个 `person`、`project`、`task`、`decision` 等 —— 它们之间存在着真实的 `related`/`backlinks` 关系 —— 而不是按相似度排序的不透明文本块。检索过程可以遍历这些关系(距离匹配你问题的页面仅一跳之遥的答案证据也能被找到),这是一种不同于典型的分块和 embedding RAG 设置的检索模型。 - **默认使用关键词 + 图谱搜索;Embeddings 是可选的。** 大多数“AI 第二大脑”工具从一开始就依赖向量数据库。在这里,普通的关键词搜索加上链接图谱即可处理大部分问题,而无需额外的 API 调用或基础设施;如果你希望获得具备释义级别的检索能力,可以使用语义搜索,但它是刻意设计为可选的,而不是强制依赖项(具体涉及什么请参见下文)。 - **一切皆可永久检查。** 每一个页面都是一个带有 YAML frontmatter 的 `.md` 文件,每一个事实都可以通过 `sources:` 字段追溯到具体的笔记,没有任何数据以必须专门使用此工具才能读取的格式存储。如果你明天停止使用 Gray Box,你的知识库就只是一个装满 Markdown 文件的文件夹。 以上这些并不代表它能取代那些为不同目标而生的工具 —— 实时协作、富文本 WYSIWYG 编辑或团队级 wiki 都不是它的目标。它的目标完全聚焦于个人的、对其自身生活和工作持续滚动的记忆,并以尽可能低的摩擦力进行捕捉。
我需要用到向量数据库吗?
不需要。默认采用关键词搜索(`search_engine.py` 的 `coverage_scorer`)加上 wiki 自身的链接图谱,这也是本项目的核心构建基础。Embeddings 完全通过 `config.yaml` 中的 `embeddings.enabled: true` 可选启用,即使关闭它,整个系统 —— capture、organize、curate、dashboard —— 也能正常运作。
我开启了 embeddings.enabled: true —— 向量存储在哪里?
没有向量数据库,这是刻意为之。在你拨动开关之前,值得了解一下这到底意味着什么: - Embeddings 以**纯 JSON** 的形式存储在 `/.state/embeddings.json` 中 —— 每个页面对应一个条目,其中保存了 LiteLLM 返回的原始浮点向量以及内容哈希(因此对于未更改的页面会跳过重新索引)。 - 搜索采用的是**线性扫描**:每一次 `ask`/`chat` 都会在 Python 中计算你的问题 embedding 与*每一个*已存储页面向量之间的余弦相似度,没有使用任何索引(没有 HNSW,没有 IVF,也没有任何类似 FAISS/Chroma/pgvector 的结构)。参见 `embedding_index.py` 中的 `EmbeddingIndex.search()`。 - 这在**个人规模**下 —— 几百到几千个页面 —— 是完全没有问题的。对几千个短浮点向量进行线性扫描只需几毫秒的时间;你根本察觉不到。 - 如果不进行改变,它**无法**扩展到数万个页面,否则就会成为拖慢 `ask`/`chat` 的瓶颈。它没有 ANN 索引,除了 LiteLLM 单次调用的批处理外没有任何批处理机制,存储格式也无法在不重写的情况下直接接入 FAISS/Chroma/pgvector。如果你的 workspace 变得如此庞大,`embedding_index.py` 将是唯一需要修改的模块 —— 其他一切(capture/organize/curate/storage)都与底层的搜索机制毫无耦合。 - **费用影响**:每次 `organize` 运行都会为每个新写入/更改的页面进行一次 embedding API 调用(`ensure_indexed`),而每次 `ask`/`chat` 都会针对*每个问题*进行一次 embedding 调用。这还是在你的 LLM completion 调用之外的。如果你使用的是按量计费的 API,当开启 embeddings 时,你的每个问题的 API 调用次数大约会翻倍。 - 关闭(或不开启)`embeddings.enabled` 不会花费你任何成本 —— `ask`/`chat` 依然可用,它们只是单纯依赖关键词搜索 + 图谱展开,瞬间完成且免费。 简而言之:开启 embeddings 能让你获得具备释义级别的检索能力(例如,匹配到只写了“数据科学家”的笔记的提问“这个项目里负责数据的人是谁?”),代价是每个问题需要额外的 API 调用,以及简单而非高可扩展的搜索策略。对于个人知识库来说,这种权衡是值得的;但对于多租户产品而言,这就不是正确的架构了。
每次记录笔记时都会调用 LLM 吗?
不会。`capture` 永远不会触碰 LLM —— 它纯粹是磁盘 I/O(`capture.py` → `write_inbox_item`)。只有在你显式调用 `organize`(实体/关系提取)、`ask`/`chat`(生成答案,如果开启的话还包括一次 embedding 调用)或 `refresh-summaries` 时,LLM 才会运行。这是刻意为之的:那个绝不能失败或丢失你想法的唯一步骤(capture)对网络调用或 LLM 配置的正确性零依赖。
如果 LLM 提取错了怎么办 —— 比如幻觉出某个人、搞错了任务负责人,或者生成了重复的页面?
没有任何内容会被自动纠正,但也没有任何内容会被破坏: - 无论 organizer 对你的笔记做了什么,你在 `/` 中的原始笔记都原封不动 —— 即使 `organize` 搞混了,`capture` 的不可变性保证依然成立。 - `graybox dupes` 会标记出可能的重复页面(模糊名称匹配);`graybox merge` 可以修复它们,自动合并 notes/sources/links 并重连所有引用。 - `graybox edit` 可修复错误的 title/type/status/alias,如果页面的 ref 因此发生了改变,它还会重新连接相关引用。 - `graybox delete` 可移除完全是幻觉的页面,并准确报告它追溯到了哪些收件箱项目,方便你查看导致错误提取的原因。 - `graybox forget --scrub` 如果你认为某条源笔记根本不该被整理,它可以追溯并从它们所在的页面中剥离出由于该糟糕记录所提取出的笔记。 `curate.py` 的所有内容都是确定性的 Python 代码,完全不涉及 LLM,正是为了确保在修复 organizer 错误时不会冒着引入*新*错误的风险。
它会对我撒谎/瞎编答案吗?
`ask`/`chat` 的设计原则是宁可拒绝也不猜测。检索 prompt(`RETRIEVAL_SYSTEM`/`CHAT_RETRIEVAL_PROMPT_TMPL`)明确指示模型只能根据检索到的上下文进行回答,如果上下文中不包含答案就直截了当地说明 —— 如果搜索确实没有找到任何相关内容,甚至根本不会调用 LLM;你会直接收到 `NO_EVIDENCE_MSG`。这是一种基于 prompt/架构的保证,而非数学保证 —— 没有任何基于 LLM 的系统能保证零幻觉 —— 但这种设计在积极地防范这种情况,而不是碰运气,并且真实答案中的每一项声明都会带有内联引用,你可以自行去对照源笔记进行验证。
我的数据私密吗?会有任何数据离开我的电脑吗?
一切都以普通文件的形式存储在本地 —— Gray Box 本身不会向任何地方上传任何内容。它进行的唯一网络调用是发送给你在 `config.yaml` 中配置的 LLM/embedding 提供商(通过 LiteLLM),且仅发送该特定 `organize`/`ask`/`chat`/embedding 调用所需的 prompt/笔记。只要将 `llm.model_name` 和 `embeddings.model_name` 指向完全本地的模型(例如 `ollama/llama3`),就不会有任何笔记内容离开你的电脑。
多个人可以同时使用同一个 workspace 吗?
不能同时使用 —— 没有锁机制、合并冲突解决机制,也没有多写入者的设计。每个 workspace 专为一个个人(或一个本地进程)的知识库而设计。在单台机器上拥有多个 *workspace*,或者通过你自己的工具(例如同步文件夹)进行同步是没有问题的,因为彼此完全隔离;但不支持向*同一个* workspace 目录同时进行写入操作。
为什么用 Markdown 文件而不是 SQLite 或真正的数据库?
为了长久保存和可检视性。一个带有 YAML frontmatter 的 `.md` 文件几乎可以被任何工具永久打开,无需任何特殊组件 —— 你可以 `grep` 整个知识库,在任何基于文本的 diff 工具里对比页面的历史,或者仅仅通过保留这些文件就能完全从 Gray Box 迁移出去。数据库在规模化扩展时速度更快,但本项目明确针对个人规模(几百到几千个页面)进行了优化,在这种规模下,速度优势无关紧要,而持久性/透明度优势却显得至关重要。
## 路线图 - [ ] Dashboard 改进 - [ ] 决策智能与记忆时间轴 - [ ] 会议摘要 - [ ] 自动每日日记摘要 - [ ] 具有类型的关联边(因果/依赖遍历 —— “是什么阻碍了 X”,“为什么决定了 Y” —— 超越简单的共现链接)
*构建为可能是可行方案中最小、最简洁的存在 —— 而不是功能最全的。*
标签:DLL 劫持, Markdown, 个人知识库, 大语言模型, 本地优先, 知识管理, 笔记应用, 逆向工具, 防御加固