从零搭出能回答"自己文档"的 RAG:179 行代码,成本不到 1 毛

30 阅读4分钟

从零搭出能回答"自己文档"的 RAG:179 行代码,成本不到 1 毛

为什么做这个项目

我是研一学生,目标是 2027 年暑期实习。一直想做点"能真正回答问题"的 AI 项目,而不是 demo 级的调用示例。VisionRAG 的设想是做一个图文混合的检索问答系统:企业里有大量图文混排文档,传统关键词检索找不到图,纯文本 RAG 又丢图像信息——最终产品要让每个答案都能溯源到具体图片和段落。

这个目标太远,所以第一周我只做一件事:先让 RAG 跑起来(纯文本版 v0)。这篇文章记录这 5 天(实际用了 3 天)的过程、代码和踩过的坑。

什么是 RAG

RAG(Retrieval-Augmented Generation,检索增强生成)解决大模型的一个老毛病:知识停在训练时点,新资料它不知道,还会瞎编。做法是:先把资料切块、转成向量存起来(建索引);用户提问时,把问题也转成向量,找出最相关的几个片段;再把片段和问题一起交给大模型,让它"看着资料"回答。

五步链路:加载 → 分块 → 嵌入 → 检索 → 生成。

5 天过程

  • D1:uv 装环境、git 建仓、配 .env、第一次调通 DeepSeek API
  • D2:把项目拆成 config.py / llm.py / main.py 三层,首次跑通对话
  • D3:流式输出、温度参数、API 成本计费、防崩兜底、多轮记忆——还抓出一个"每句话调两次 API"的烧钱 bug
  • D4:写最小 RAG——4 篇短文分块嵌入建索引,余弦相似度检索,3 个测试问题 top-1 全部命中
  • D5:把检索片段拼进提示词生成答案,v0 端到端跑通——问"今天天气"它会老实说不知道
  • D6:整理结构、写 README、补 .env.example(还修了一个 gitignore 的坑)

技术核心:三段加起来不到 40 行

嵌入(embedding)就是把文字变成一串数字(向量),我管它叫"语义身份证":意思相近的句子,向量方向也接近。检索 = 拿问题的身份证,跟所有候选比方向像不像。

余弦相似度衡量两个向量方向有多像,公式就三行:

def cos_sim(a: list[float], b: list[float]) -> float:
    dot = sum(x * y for x, y in zip(a, b))
    na = math.sqrt(sum(x * x for x in a))
    nb = math.sqrt(sum(y * y for y in b))
    return dot / (na * nb)

生成环节的核心是提示词组装——把检索到的片段拼进模板,再让模型回答:

prompt = f"""请基于以下资料回答用户问题。如果资料里没有答案,请直接说不知道。

资料:
{context}

问题:{question}"""

最后那句话是关键:"没有答案就说不知道"。实测问"今天上海天气怎么样",模型回答"不知道。资料中没有上海天气的信息。"——RAG 系统最重要的品行不是"能答",而是"不乱编"。

踩过的坑(全是真金白银)

1. 每句话被调了两次 API(最烧钱):给聊天加防崩代码时,旧的调用没删,导致一句话被调两次 API——回答出现两份,花费 double。排查套路:现象(两次回答)→ 假设(两次调用)→ 读代码证实(run() 里有两个 stream_chat)。

2. 把 >>> 敲进了 PowerShell:终端和 Python 解释器是两回事。PS> 开头是操作系统命令;先输入 python 进入解释器,出现 >>> 才能敲 Python 代码。看起来弱智,但第一天真踩了。

3. .env.example 被 gitignore 吞了:.env(真密钥,不能上传)和 .env.example(空模板,应该上传)长得太像,规则 .env.* 把模板也误杀了。加一行 !.env.example(感叹号 = 例外)救回来。教训:"文件存在" ≠ "文件会被提交",改完 .gitignore 必查 git status。

4. 零宽空格(U+200B):一个看不见的字符:从网页复制代码时会带进一个不可见字符,导致 if __name__ == "__main__": 永远为 False。踩了两次才学会排查:len('__main__') 应该是 8,多 1 就是有脏字符。现在关键代码一律手敲。

数据说话

  • 代码量:179 行(8 个文件),完整跑通一个 RAG 问答
  • git 提交:11 次,每次提交都是一天的边界
  • 欠债清单:21 条——遇到不懂的先记下来,S2 阶段集中清偿。这是我学到的工程习惯:不装懂
  • 检索质量:3 个测试问题 top-1 全部命中(相似度 0.66 / 0.77 / 0.73)
  • API 成本:不到 1 毛(约 0.02~0.05 元,口径:按未命中缓存计费估算)

下一步

  • v1:检索质量优化——混合检索(关键词 + 向量)、Rerank、加评测(Recall@k、Faithfulness)
  • v2:多模态——图像索引 + VLM 问答
  • v3:服务化——FastAPI + Docker + 追踪

结尾

代码在 GitHub:github.com/huruxia/vis… 踩坑全集(DEBT.md):github.com/huruxia/vis…

感谢看完。有任何问题欢迎交流,尤其是分块策略和检索评测——那是 RAG 里最值得实验的部分。