Skip to content

个人 AI 知识库问答助手构建全流程 ​

项目目标与需求场景 ​

平时积累了上百篇 Markdown 笔记和开发记录,但经常遇到“记得曾经解决过某个依赖冲突,却想不起具体在哪个工程哪个文件”的尴尬情况。传统的基于关键词全文搜索对同义词、意图泛化检索支持较弱。

目标:构建一个完全本地可运行、轻量级的 RAG (Retrieval-Augmented Generation) 问答助手。

  • 输入自然语言问题(如:“Mac 下端口被占用怎么排查?”)
  • 自动命中知识库相关章节
  • 结合大模型生成精准回复并附带原文引用来源。

整体架构设计 ​

text
 ┌──────────────────────┐
 │ 本地 Markdown 笔记库  │
 └──────────┬───────────┘
            │ 1. 递归扫描与分块 (Recursive Chunking)
            ▼
 ┌──────────────────────┐
 │  Text Chunks (片段)  │
 └──────────┬───────────┘
            │ 2. 生成 Embedding 向量
            ▼
 ┌──────────────────────┐
 │  Local Vector Store  │  ◄─── 3. 用户提问自然语言 Query
 └──────────┬───────────┘
            │ 4. Cosine 相似度 Top-3 召回
            ▼
 ┌──────────────────────┐
 │  Context Augmented   │  (将召回片段拼入 Prompt)
 └──────────┬───────────┘
            │ 5. 模型生成带引用的回答
            ▼
 ┌──────────────────────┐
 │  前端 Vue 3 对话界面 │
 └──────────────────────┘

关键技术实现 ​

1. 语义感知的 Markdown 分块策略 ​

普通的固定长度切割(如每 500 字一刀切)很容易破坏代码块或切断段落上下文。 我们采用按标题层级(H1/H2/H3)拆分的语义分块策略:

typescript
export interface DocumentChunk {
  id: string
  filePath: string
  heading: string
  content: string
}

export function chunkMarkdown(filePath: string, rawText: string): DocumentChunk[] {
  const sections = rawText.split(/(?=^#{1,3}\s)/m)
  return sections
    .map((sec, idx) => {
      const match = sec.match(/^#{1,3}\s+(.+)$/m)
      const heading = match ? match[1] : '前言'
      return {
        id: `${filePath}#${idx}`,
        filePath,
        heading,
        content: sec.trim()
      }
    })
    .filter(chunk => chunk.content.length > 50)
}

2. 向量生成与轻量本地索引 ​

针对个人几百篇文档规模(数千个 chunk),无需部署重型的 Milvus 或 Pinecone 服务,使用纯前端内存/本地 JSON 文件索引搭配余弦相似度即可实现毫秒级召回:

typescript
function cosineSimilarity(vecA: number[], vecB: number[]): number {
  let dotProduct = 0
  let normA = 0
  let normB = 0
  for (let i = 0; i < vecA.length; i++) {
    dotProduct += vecA[i] * vecB[i]
    normA += vecA[i] ** 2
    normB += vecB[i] ** 2
  }
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB) || 1)
}

3. Prompt 模板与防幻觉指令 ​

text
你是一个专业技术知识库助手。请根据下方提供的【参考文档片段】准确回答用户的问题。
回答必须严格遵循:
1. 仅依据提供的参考内容作答,不得编造任何未提及的事实、参数或命令。
2. 给出结论时,在段落末尾标注对应引用文件路径。
3. 若参考内容中没有相关信息,请明确回答“本地知识库中未检索到相关记录”。

【参考文档片段】:
---
{{retrieved_context}}
---

【用户问题】:
{{user_query}}

踩坑与性能调优 ​

  1. 代码块被截断问题:如果分块落在代码块内部,会导致缺少语言声明和闭合反引号,影响召回后模型阅读。通过正则先标记代码块范围,确保分块边界不跨越未闭合代码块。
  2. 多余空行与图片噪音:Markdown 中的 Base64 图片或长外链链接会白白消耗 Token 且稀释文本语义。在向量化前通过预处理器将其替换为占位符 [图片: caption]。
  3. 混合检索加权:单纯的语义向量检索在搜索特定错误码(如 EADDRINUSE、0x80070005)时,精确度有时不如传统的 BM25 / 精确词频匹配。将 BM25 关键词评分 + 向量余弦相似度 按照 0.4 : 0.6 线性加权,准确率大幅提升。

验证与效果 ​

  • 索引 120 篇个人笔记,生成约 900 个 chunk,本地初始化向量构建耗时 18 秒。
  • 检索耗时稳定在 12ms 以内。
  • 经过 50 个真实开发问题测试,Top-3 召回率达到 92%,有效解决了日常快速定位历史排查方案的难题。

基于 VitePress 构建 | 记录真实开发与 AI 协作过程