# ask-self-dev **Repository Path**: nanqq/ask-self ## Basic Information - **Project Name**: ask-self-dev - **Description**: 还在开发,非正式仓库 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-02 - **Last Updated**: 2026-10-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 问己 AskSelf · 个人知识库决策助手 > 把你的日记、笔记、记录,变成「会引用出处」的私人参谋。 --- ## 这是什么 问己(AskSelf)是一个**完全本地存储、隐私优先**的个人知识库问答系统。它把你散落在各处的 Markdown 材料(日记、网课笔记、工作记录、学习记录)统一录入本地向量库,当你提问时: - 自动判断「该不该查资料」——不是所有问题都去翻库; - 用 DeepSeek 给召回片段**批量打分**,过滤掉不相关的噪声证据; - 证据不够时先**改写问题重试**,还不行才去网上搜(而且只发问题本身); - 最终由大模型生成**带引用来源**的回答——每句话都能溯源到你的某篇日记,或某个网页。 核心理念:**模型全走 API,数据全留本地,隐私边界清清楚楚。** --- ## 它能帮你做什么 - **私人知识问答**:答案来自「你自己的记录」,而不是通用大模型的泛泛而谈。 - **决策辅助**:纠结「该不该换工作」「这段时间到底在焦虑什么」时,它从你的历史里找依据,而不是凭空编。 - **可追溯的回答**:每个结论都标注来源——`local`(你的本地文件)或 `web`(网络链接),界面里分色展示,一眼看清哪些是你的、哪些来自网上。 - **隐私优先**:原始材料只存在你电脑上;敏感片段可以标 `private: true` 直接排除出检索;网络兜底只发送问题文本,绝不上传任何日记内容。 - **一个网页全搞定**:单文件网页前端直连本地 API,无需额外框架;对话、API 配置、知识库配置、检索与回退四个页面一应俱全。 --- ## 工作流程 ``` 提问 → 意图判断 ─┬─ 不需检索 → 直接生成 └─ 需检索 → ChromaDB Top-K → DeepSeek 批量打分(≥阈值) → 证据 ≥2 条? ─是→ 生成 └否→ 查询改写(最多2轮) → 仍不足 → Tavily 网络回退 → 生成 ``` - **改写优先、搜索兜底**:能靠改写查到就不浪费网络调用。 - **引用分色**:本地证据与网络结果在界面上明显区分,结论可溯源。 --- ## 十分钟跑通 > 下面每个代码块都是**纯命令、无注释**,可直接整段复制(cmd / PowerShell / bash 均可)。 > 注意:`#` 开头的注释只在 bash 里生效,在 cmd / PowerShell 中会被当成参数传给程序。 **第 1 步 · 安装依赖**(用哪个解释器就一直用它,别混) ```bash .venv/Scripts/python.exe -m pip install -r requirements.txt ``` **第 2 步 · 配置 API Key** ```bash copy .env.example .env ``` 然后编辑 `.env`,填入 `DASHSCOPE_API_KEY` / `DEEPSEEK_API_KEY` / `TAVILY_API_KEY`。 **第 3 步 · 放入你的材料并建库** 把你的 Markdown 材料放进 `docs/`(命名约定见下)。 ⚠️ `docs/`、`chroma_db/` 等个人数据已被 `.gitignore` 排除,**不会**进入远程仓库——克隆本项目后需自行放入你的材料才能试跑,仓库仅保留源码与配置模板。 > 💡 也可以启动前端后,在「📚 知识库配置」页里**直接浏览 docs/、新建文件夹、新建 / 编辑 / 删除 Markdown**,比手动开文件夹更方便。 ```bash .venv/Scripts/python.exe src/ingest.py ``` (后续材料有增改时,改用 `--incremental` 省 token,详见下方「增量录入」。) **第 4 步 · 检索自测**(可选) ```bash .venv/Scripts/python.exe src/retrieve.py "我该不该换工作" ``` **第 5 步 · 检索范围自测**(可选,验证 `@` 引用背后的过滤逻辑) ```bash .venv/Scripts/python.exe src/retrieve.py "最近在忙什么" -s "日记(9月).md" -s "原电脑(七月).md" ``` **第 6 步 · 启动前端** ```bash .venv/Scripts/python.exe src/api.py ``` 启动后浏览器打开 **http://127.0.0.1:8000/**(服务也会把信息打印在终端里)。 > 网页前端(`webapp/index.html`)通过本地 API 与后端交互:对话、API 配置、知识库配置、检索与回退四个页面都在同一个页面里,由 `src/api.py` 一并托管。 --- ## 目录结构 ``` ask-self/ ├── docs/ # (gitignore) 你的 Markdown 材料,本地私有,不推送到远程 ├── diary/ # (gitignore) 日记目录,本地私有 ├── chroma_db/ # (gitignore) ChromaDB 持久化目录,本地私有 ├── screenshots/ # (gitignore) 实跑截图,本地私有 ├── .workbuddy/ # (gitignore) 智能体记忆/工作区,本地私有 ├── webapp/ │ └── index.html # 网页前端(单文件,直连本地 API,由 api.py 托管) ├── prototype/ │ └── index.html # 原型稿(设计参考,api.py 会优先托管 webapp) ├── src/ │ ├── config.py # 统一配置与共享客户端 │ ├── embedding.py # DashScope 嵌入封装(多模型降级轮询) │ ├── ingest.py # 解析 + 切块 + 嵌入 + 入库 │ ├── retrieve.py # 检索测试 │ ├── deepseek_rerank.py # DeepSeek 证据重排(批量打分) │ ├── web_search.py # Tavily 网络搜索回退 │ ├── agent.py # LangGraph 工作流 + run_agent 入口 │ └── api.py # FastAPI 包装层(/api/chat 等)+ 托管网页前端 ├── .env.example # 配置模板(无密钥),复制为 .env 后填 Key ├── .gitignore # 已排除上方标注 (gitignore) 的私有目录与 .env ├── requirements.txt └── README.md ``` --- ## docs/ 命名约定 分类信息通过**文件名前缀**推断,也可用 YAML frontmatter 覆盖。 | 前缀 | 类型 | 示例 | |------|------|------| | `diary-` | 日记 | `diary-2026-08-15.md` | | `course-` | 网课笔记 | `course-ml-ch3.md` | | `work-` | 工作记录 | `work-2026-Q3.md` | | `study-` | 学习记录 | `study-notes-007.md` | 不符合约定的文件名,`source_type` 默认设为 `docs`,不影响录入。 **frontmatter 增强**(可选): ```markdown --- type: diary date: 2026-08-15 tags: [情绪, 工作] private: true # 标记为私密:检索时跳过,不发送给任何 API --- ``` --- ## 隐私与仓库内容 | 数据 | 存储位置 | 是否发送到 API | |------|---------|---------------| | 原始 Markdown 文件 | 本地磁盘 | 否 | | ChromaDB 向量索引 | 本地磁盘 | 否 | | 切块后的文本片段 | 本地内存 | 是(DashScope 嵌入 / DeepSeek) | | 用户提问 | 本地内存 | 是(DashScope / DeepSeek / Tavily) | 敏感记录可在 frontmatter 加 `private: true`,检索时会被过滤。 此外,本项目 `.gitignore` 已排除 `docs/`、`diary/`、`screenshots/`、`chroma_db/`、`.workbuddy/` 与 `.env`,因此推送到远程的代码仓库**仅含源码与配置模板**,不含任何个人材料或密钥。 --- ## 本地 API 网页前端(`webapp/index.html`)由 `src/api.py` 一并托管,通过下列接口工作: | 方法 | 路径 | 说明 | |------|------|------| | `GET` | `/` | 返回网页前端页面(即 `webapp/index.html`) | | `GET` | `/api/health` | 探活 + 回传三个 Key 是否已配置(不返回 Key 内容) | | `POST` | `/api/chat` | 透传 `run_agent` 的返回字段 | | `GET` | `/api/config` | 查询三个 Key 是否已配置 | | `POST` | `/api/config` | 保存 Key 到 `.env` 并热更新(留空表示不修改) | | `GET` | `/api/kb` | 知识库统计:文件数 / 片段数 / 文件清单 | | `GET` | `/api/kb/tree` | `docs/` 目录树(供前端文件浏览器) | | `GET` | `/api/kb/file` | 读取 `docs/` 下某个文本文件 | | `POST` | `/api/kb/file` | 新建 / 覆盖写入 `docs/` 下文本文件 | | `POST` | `/api/kb/mkdir` | 在 `docs/` 下新建文件夹 | | `POST` | `/api/kb/delete` | 删除 `docs/` 下文件或文件夹 | | `POST` | `/api/ingest` | 录入索引:`mode=incremental`(默认,复用旧向量省 token)/ `mode=rebuild`(整体重建) | > 文件管理接口**严格限制在 `docs/` 内**(服务端对路径做 resolve + 越界校验,拦截 `..`、绝对路径等目录穿越),且只放行 `.md` / `.markdown` / `.txt` 后缀,不会读写项目其他文件。 > 在网页「📚 知识库配置」页可以直接浏览 `docs/`、新建文件夹、新建 / 编辑 / 删除 Markdown,改完点「重新加载知识库」即可让改动进入索引。 `/api/chat` 请求体字段与 `run_agent` 参数一一对应: ```json { "question": "我最近在焦虑什么?", "history": [], "top_k": 20, "jev_threshold": 7, "use_jev": true, "use_web_search": true } ``` 返回体(与 `run_agent` 完全一致,未做任何字段改写): ```json { "answer": "...", "citations": [{"type":"local","source":"docs/diary-2026-08-12.md","score":9.0,...}], "need_retrieval": true, "evidence_count": 3, "web_search_triggered": false } ``` - 服务默认绑定 `127.0.0.1:8000`,一个端口同时提供页面与接口。 - 调试用 URL 参数:`?nomotion=1` 关动画、`?theme=light` 切主题、`?q=你的问题` 自动提问(便于无头截图)。 - 改端口:`python src/api.py --port 8800`;改工作目录前无需额外配置(内部已注入 `sys.path`)。 --- ## 两个进阶用法 ### 在对话里用 `@` 圈定检索范围 聊天输入框里敲 **`@`** 会弹出知识库目录树,**文件夹和文件都能选**: - `↑↓` 移动、`Enter` 确认、`Esc` 取消,也可以直接打字过滤(如 `@日记`); - 选中后在输入框上方出现一个青色 **引用标记**,可点 `×` 删除,支持多选; - 发送后本次提问**只在你选中的文件 / 文件夹里检索**; - 该模式下**不触发网络回退**——答案必须来自你圈定的文件,避免拿网上内容充数。 > 引用只作用于"当前这一问",发送后自动清空。 > 接口层面:`POST /api/chat` 的请求体多一个可选字段 `sources`(字符串数组), > 元素既可以是文件(`docs/日记(9月).md`)也可以是文件夹(`日记补充(8月)`);不传 = 全库检索。 ### 增量录入(省 token) 「📚 知识库配置」页的 **增量录入** 按钮(命令行同理:`src/ingest.py --incremental`)会: - 给每个片段算**内容指纹**,与已入库的比对; - 指纹已存在(这段录过)→ **直接复用旧向量,不调嵌入 API**; - 只对**新增 / 变化的片段**调用 DashScope 嵌入; - 已删除文件的片段**自动清理**。 实测:内容完全没变时**一次嵌入 API 都不发**;只新增 1 个文件时**只嵌那 1 个片段**。 旁边的「强制重建」= 清空全部重新嵌入,仅在换嵌入模型或索引损坏时使用。 **⚠️ 改了内容但文件名没变,能识别到吗?—— 能。** 指纹(`chash`)是按**片段正文内容**算的,跟文件名**完全无关**。所以只要你动了内容, 不管文件名改不改,都会被识别出来: | 你的操作 | 系统行为 | |---|---| | 在文档里**新增**一段 | 只有那一段所在片段的指纹变了 → 只重嵌那 **1 个**片段 | | **修改**某段里的文字 | 该段落所在片段指纹变了 → 重嵌该片段 | | **删除**某段文字 | 重新扫描后该片段不复存在 → 从库里**清除**(库里不再保留这段) | | 只改**文件名**、内容一字不动 | 片段指纹不变 → 全部复用,**零嵌入**(但 `source` 元数据会更新为新文件名) | | 改**标点 / 空格 / 换行** | 也会被算成变化 → 该片段重嵌(指纹对任何字符改动都敏感) | **粒度提醒**:指纹是**按片段(chunk)**算的,不是按整篇文件。默认 `chunk_size=512`、 有 50 字重叠,所以改动一个片段会牵动它相邻的重叠部分,通常表现为「2 个相邻片段重嵌」。 一篇 10 个片段的日记里改一句话,实测是 **复用 8 / 重嵌 2**,而不是 10 个全重嵌。 一句话:**增量录入比对的是"片段内容",不是"文件"**——文件名没变完全不影响识别。 --- ## 常见问题 **Q:启动时报 `ModuleNotFoundError: No module named 'fastapi'`?** A:说明当前解释器里还没装 fastapi。常见于**换了虚拟环境**(比如从 conda base 切到项目自带的 `.venv`)。 把依赖补装到你要用的那个解释器即可: ```bash .venv/Scripts/python.exe -m pip install -r requirements.txt ``` ⚠️ 本项目同时存在 conda base 与 `.venv` 两套可用解释器,**建议只固定用其中一个**, (`.venv` 里是较新的 fastapi 0.142 / starlette 1.7,本项目按它验证), 否则很容易出现「在 A 里装、在 B 里跑」的错位。 **Q:报 `api.py: error: unrecognized arguments: # 打开 http://127.0.0.1:8000/`?** A:命令末尾带了 `#` 注释。`#` 注释**只在 bash 里有效**,在 cmd / PowerShell 中会被当作参数传给程序。 去掉 `#` 及其后面的内容即可,正确写法就是: ```bash .venv/Scripts/python.exe src/api.py ``` **Q:不想用证据重排怎么办?** A:在网页「🔍 检索与回退」页关闭「启用 DeepSeek 重排」开关(召回片段将全部送入生成)。 **Q:DashScope 某个嵌入模型额度用尽?** A:`embedding.py` 会按 `DASHSCOPE_EMBED_MODELS` 优先级自动降级到下一个模型。 ⚠️ 若降级到了**维度不同**的模型,需删除 `chroma_db/` 重新执行 `src/ingest.py` 建库。 **Q:Tavily 额度用尽?** A:在网页「🔍 检索与回退」页关闭「网络搜索回退」,系统会直接基于本地证据生成。 **Q:改了 docs/ 之后怎么让改动生效?** A:在网页「📚 知识库配置」页点 **增量录入**,或执行: ```bash .venv/Scripts/python.exe src/ingest.py --incremental ``` 增量模式会跳过内容没变的片段,只嵌入新增 / 修改过的,省 token 也更快。 注:`--keep` 是「保留已有数据仅追加」的旧参数,**已不推荐**——它会把同一篇文档的片段重复写一遍, 要增量请一律用 `--incremental`。