迷你 RAG:50 行代碼跑通文檔問答)
手寫一個(gè)迷你 RAG50 行代碼跑通文檔問答系列RAG 實(shí)戰(zhàn)系列 第01篇上一篇MCP vs A2A vs ACP2026 年 Agent 協(xié)議選型指南協(xié)議層系列完結(jié)預(yù)計(jì)閱讀12 分鐘讀完你能得到一個(gè)能跑、能改、能接你真實(shí)文檔的迷你 RAG——完整 50 行代碼在文末可直接復(fù)制引子每個(gè)想做 RAG 的人都死在第一周一位讀者的原話私信“想讓公司內(nèi)部文檔能問答。搜了三天教程LangChain 版本對(duì)不上、LlamaIndex 文檔看不懂、還得先裝 Docker 起個(gè) Milvus……我到現(xiàn)在一行代碼都沒跑起來。”這是 RAG 落地最真實(shí)的死法——不是死于技術(shù)難是死于框架太重。RAG 的本質(zhì)其實(shí)非常簡(jiǎn)單把你的文檔切成小塊 → 變成向量 → 用戶提問時(shí)找到最相關(guān)的幾塊 → 塞進(jìn) prompt 讓大模型回答。就這么四步。什么 LangChain、向量數(shù)據(jù)庫、Docker都是這四步的工業(yè)化包裝。包裝有價(jià)值但你不應(yīng)該先學(xué)包裝再學(xué)本質(zhì)。這篇用 50 行純 Python 跑通上面四步——不用 LangChain不用向量數(shù)據(jù)庫不用 Docker。跑通之后你會(huì)對(duì)后面三篇踩坑實(shí)錄、向量庫選型、Rerank里的每個(gè)概念都有體感。一、環(huán)境準(zhǔn)備2 分鐘裝好1.1 裝依賴只需要兩個(gè)包Python 3.9pip install sentence-transformers openai包干什么備注sentence-transformers文本向量化本地跑免費(fèi)會(huì)自動(dòng)帶上 torchopenai調(diào)大模型 API兼容 DeepSeek 等任何 OpenAI 格式接口1.2 驗(yàn)證安裝python -c import sentence_transformers; print(OK, sentence_transformers.__version__) # 輸出類似: OK 3.x.x —— 就緒1.3 準(zhǔn)備大模型 API Key用 DeepSeek 舉例換任何 OpenAI 格式的服務(wù)都行**1.**打開 https://platform.deepseek.com/**2.**API Keys → 創(chuàng)建 → 復(fù)制形如sk-xxxx第一次跑SentenceTransformer時(shí)會(huì)下載模型文件約 100MB一次性。國(guó)內(nèi)網(wǎng)絡(luò)慢的話設(shè)置鏡像export HF_ENDPOINThttps://hf-mirror.com環(huán)境就緒。下面進(jìn)入正題——先看全景再逐塊拆。二、全景50 行代碼長(zhǎng)什么樣先上完整結(jié)構(gòu)細(xì)節(jié)下節(jié)拆。整個(gè) RAG 就三個(gè)函數(shù) 一段準(zhǔn)備# ── 準(zhǔn)備文檔 嵌入模型 ────────────────── docs [...] # ① 你的文檔切成塊后的 model SentenceTransformer(...) # ② 嵌入模型本地 doc_vecs model.encode(docs) # ③ 全部向量化建索引 # ── 檢索函數(shù) ────────────────────────────── def retrieve(query, top_k): # ④ 問題向量化 → 余弦相似度 → 排序取前K # ── 生成函數(shù) ────────────────────────────── def answer(question): # ⑤ 檢索 → 拼上下文 → 調(diào)大模型沒有向量數(shù)據(jù)庫。因?yàn)?5 條文檔的向量檢索就是一個(gè)矩陣乘法——numpy一行搞定。向量數(shù)據(jù)庫解決的是百萬級(jí)向量的檢索性能你先用不到等用到時(shí)再看第 03 篇。對(duì)照上圖這 50 行覆蓋了管道的核心三層切分→向量化→檢索→生成。工業(yè)級(jí)管道里剩下的層清洗、重排、評(píng)估是后面三篇的主角。三、逐塊拆解每一行為什么這么寫3.1 文檔塊chunk 是什么docs [ RAG 是檢索增強(qiáng)生成的縮寫通過檢索外部知識(shí)來增強(qiáng)大模型的回答。, RAG 系統(tǒng)的核心組件包括文檔切分、向量化、向量存儲(chǔ)、檢索和生成。, RAG 相比微調(diào)的優(yōu)勢(shì)成本低、知識(shí)可隨時(shí)更新、不遺忘通用能力。, chunk 是 RAG 中的最小檢索單元通常包含 256-512 個(gè) token。, 余弦相似度是 RAG 中最常用的向量相似度度量方法。, ]為什么是一條一條而不是整篇文檔因?yàn)闄z索的粒度決定命中的精度你問chunk 多大合適如果索引的是整篇 5000 字文檔向量是全文語義的平均值跟問題的相關(guān)度就被稀釋了。切成小塊每塊向量語義集中命中率才高。這里的 5 條是演示數(shù)據(jù)手工切好的。接真實(shí)文檔時(shí)把你的 md/txt 讀進(jìn)來按段落或每 300-500 字切一刀即可——第 02 篇會(huì)專門講切分怎么踩坑。3.2 向量化本地嵌入模型model SentenceTransformer(BAAI/bge-small-zh-v1.5) doc_vecs model.encode(docs, normalize_embeddingsTrue)?****BAAI/bge-small-zh-v1.5中文小模型本地 CPU 可跑512 維。選它是中文 免費(fèi) 快三個(gè)條件的最優(yōu)解?****normalize_embeddingsTrue把向量歸一化成單位長(zhǎng)度。這一步是后面矩陣乘法 余弦相似度的前提——不是可選項(xiàng)是伏筆跑完doc_vecs是一個(gè)5 × 512的矩陣。你的向量索引建好了就一個(gè) numpy 數(shù)組。3.3 檢索一個(gè)矩陣乘法def retrieve(query: str, top_k: int 2): q_vec model.encode([query], normalize_embeddingsTrue) scores q_vec doc_vecs.T # 余弦相似度 top_idx scores[0].argsort()[::-1][:top_k] return [docs[i] for i in top_idx]這五行是 RAG 的心臟逐行看?q_vec doc_vecs.T1×512乘512×51×5得到問題和 5 個(gè)文檔塊的相似度。因?yàn)閮蛇叾細(xì)w一化了矩陣乘積就是余弦相似度——這就是 3.2 那個(gè)伏筆的回收?argsort()[::-1]按分?jǐn)?shù)從高到低排序?[:top_k]取前 K 個(gè)。K 是超參數(shù)迷你版取 2驗(yàn)證一下檢索好不好使print(retrieve(RAG 有什么優(yōu)點(diǎn))) # 期望輸出第一條是「RAG 相比微調(diào)的優(yōu)勢(shì)成本低、知識(shí)可隨時(shí)更新……」如果你的第一條不是它先檢查normalize_embeddingsTrue是不是漏了——沒歸一化時(shí)矩陣乘法算的是點(diǎn)積受向量長(zhǎng)度影響排序會(huì)錯(cuò)。3.4 生成拼上下文調(diào)大模型client OpenAI(api_keysk-xxx, base_urlhttps://api.deepseek.com) def answer(question: str) - str: context \n.join(f[{i1}] {c} for i, c in enumerate(retrieve(question))) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: f僅根據(jù)以下資料回答資料里沒有就明確說不知道。\n{context}}, {role: user, content: question}, ], ) return resp.choices[0].message.content三個(gè)細(xì)節(jié)決定回答質(zhì)量1.****system prompt 里那句資料里沒有就明確說不知道——防幻覺的最低成本手段。沒有它模型會(huì)用自己的參數(shù)知識(shí)補(bǔ)答案跟你的文檔沒關(guān)系**2.**帶[1] [2]編號(hào)——后續(xù)要求模型引用編號(hào)就得到最樸素的引用溯源**3.**只塞 top_k 個(gè)塊不塞全部——上下文不是越多越好第 02 篇講中間衰減時(shí)會(huì)展開3.5 跑起來if __name__ __main__: print(answer(RAG 的核心組件有哪些))預(yù)期輸出根據(jù)資料 [2]RAG 系統(tǒng)的核心組件包括文檔切分、向量化、向量存儲(chǔ)、檢索和生成。問一個(gè)資料里沒有的print(answer(RAG 支持圖片檢索嗎)) # 預(yù)期資料里沒有提到……而不是模型自己編一段第二個(gè)輸出比第一個(gè)更重要——它驗(yàn)證了知識(shí)邊界生效這正是 RAG 相比裸模型的全部意義。四、接你的真實(shí)文檔10 行改造迷你版到真實(shí)版就差一個(gè)讀文件 切分import re def load_chunks(path: str, size: int 300) - list: 讀文本文件按段落粗切成 ~size 字的塊 text open(path, encodingutf-8).read() paras [p.strip() for p in re.split(r\n\s*\n, text) if p.strip()] chunks, buf [], for p in paras: # 小段合并、大段切開 if len(buf) len(p) size: buf \n p else: chunks.append(buf.strip()); buf p chunks.append(buf.strip()) return [c for c in chunks if len(c) 20] docs load_chunks(你的文檔.md) # 替換這里 doc_vecs model.encode(docs, normalize_embeddingsTrue) # 重建索引之后retrieve/answer一行都不用改。這就是分層的紅利換數(shù)據(jù)不動(dòng)邏輯。千萬文檔塊也別慌1 萬塊 × 512 維的矩陣乘法在筆記本上也就百毫秒級(jí)。真正的瓶頸在每次都重算全部文檔向量——所以工業(yè)界把doc_vecs存起來這就叫向量庫第 03 篇。五、踩坑實(shí)錄跟做時(shí)最容易翻的 4 個(gè)坑坑 1模型下載 403/超時(shí)癥狀Connection error卡住。解法export HF_ENDPOINThttps://hf-mirror.com后重跑。坑 2檢索結(jié)果驢唇不對(duì)馬嘴癥狀retrieve第一條永遠(yuǎn)不對(duì)。十有八九是normalize_embeddingsTrue漏了點(diǎn)積冒充了余弦相似度。加上它???3回答是模型自己編的跟文檔無關(guān)癥狀問資料外的問題模型對(duì)答如流。解法檢查 system prompt 的僅根據(jù)資料回答沒有就說不知道是不是被你精簡(jiǎn)掉了。這句是防幻覺底線???4中文模型卻全是英文文檔或反之癥狀英文文檔用bge-small-zh檢索質(zhì)量暴跌。嵌入模型有語言域——中英混合場(chǎng)景換BAAI/bge-m3多語言稍大。這四個(gè)坑只是開胃菜。檢索效果差的真實(shí)原因有 5 大類chunk 邊界、中間衰減、嵌入換版……下一篇逐個(gè)拆。六、完整代碼復(fù)制即跑mini_rag.py — 50 行跑通 RAG 文檔問答 依賴: pip install sentence-transformers openai import re import numpy as np from sentence_transformers import SentenceTransformer from openai import OpenAI # ── ① 文檔塊換成 load_chunks(你的文檔.md) 即接真實(shí)數(shù)據(jù)── docs [ RAG 是檢索增強(qiáng)生成的縮寫通過檢索外部知識(shí)來增強(qiáng)大模型的回答。, RAG 系統(tǒng)的核心組件包括文檔切分、向量化、向量存儲(chǔ)、檢索和生成。, RAG 相比微調(diào)的優(yōu)勢(shì)成本低、知識(shí)可隨時(shí)更新、不遺忘通用能力。, chunk 是 RAG 中的最小檢索單元通常包含 256-512 個(gè) token。, 余弦相似度是 RAG 中最常用的向量相似度度量方法。, ] # ── ② 嵌入模型本地免費(fèi) ③ 建索引 ── model SentenceTransformer(BAAI/bge-small-zh-v1.5) doc_vecs model.encode(docs, normalize_embeddingsTrue) # ── ④ 檢索 ── def retrieve(query: str, top_k: int 2) - list: q_vec model.encode([query], normalize_embeddingsTrue) scores q_vec doc_vecs.T top_idx scores[0].argsort()[::-1][:top_k] return [docs[i] for i in top_idx] # ── ⑤ 生成 ── client OpenAI(api_keysk-替換成你的, base_urlhttps://api.deepseek.com) def answer(question: str) - str: context \n.join(f[{i1}] {c} for i, c in enumerate(retrieve(question))) resp client.chat.completions.create( modeldeepseek-chat, messages[ {role: system, content: f僅根據(jù)以下資料回答資料里沒有就明確說不知道。\n{context}}, {role: user, content: question}, ], ) return resp.choices[0].message.content # ── 接真實(shí)文檔的切分器可選── def load_chunks(path: str, size: int 300) - list: text open(path, encodingutf-8).read() paras [p.strip() for p in re.split(r\n\s*\n, text) if p.strip()] chunks, buf [], for p in paras: if len(buf) len(p) size: buf \n p else: chunks.append(buf.strip()); buf p chunks.append(buf.strip()) return [c for c in chunks if len(c) 20] if __name__ __main__: print(answer(RAG 的核心組件有哪些))收尾50 行代碼里藏著的其實(shí)是 RAG 的世界觀模型不必什么都知道——它只需要在回答的那一刻拿到對(duì)的那幾頁資料。迷你版的邊界也很清楚沒有重排、沒有混合檢索、沒有評(píng)估、向量重算是全量的。這些工業(yè)化的部分正是接下來三篇的內(nèi)容學(xué)AI大模型的正確順序千萬不要搞錯(cuò)了2026年AI風(fēng)口已來各行各業(yè)的AI滲透肉眼可見超多公司要么轉(zhuǎn)型做AI相關(guān)產(chǎn)品要么高薪挖AI技術(shù)人才機(jī)遇直接擺在眼前有往AI方向發(fā)展或者本身有后端編程基礎(chǔ)的朋友直接沖AI大模型應(yīng)用開發(fā)轉(zhuǎn)崗超合適就算暫時(shí)不打算轉(zhuǎn)崗了解大模型、RAG、Prompt、Agent這些熱門概念能上手做簡(jiǎn)單項(xiàng)目也絕對(duì)是求職加分王給大家整理了超全最新的AI大模型應(yīng)用開發(fā)學(xué)習(xí)清單和資料手把手幫你快速入門學(xué)習(xí)路線:?大模型基礎(chǔ)認(rèn)知—大模型核心原理、發(fā)展歷程、主流模型GPT、文心一言等特點(diǎn)解析?核心技術(shù)模塊—RAG檢索增強(qiáng)生成、Prompt工程實(shí)戰(zhàn)、Agent智能體開發(fā)邏輯?開發(fā)基礎(chǔ)能力—Python進(jìn)階、API接口調(diào)用、大模型開發(fā)框架LangChain等實(shí)操?應(yīng)用場(chǎng)景開發(fā)—智能問答系統(tǒng)、企業(yè)知識(shí)庫、AIGC內(nèi)容生成工具、行業(yè)定制化大模型應(yīng)用?項(xiàng)目落地流程—需求拆解、技術(shù)選型、模型調(diào)優(yōu)、測(cè)試上線、運(yùn)維迭代?面試求職沖刺—崗位JD解析、簡(jiǎn)歷AI項(xiàng)目包裝、高頻面試題匯總、模擬面經(jīng)以上6大模塊看似清晰好上手實(shí)則每個(gè)部分都有扎實(shí)的核心內(nèi)容需要吃透我把大模型的學(xué)習(xí)全流程已經(jīng)整理好了抓住AI時(shí)代風(fēng)口輕松解鎖職業(yè)新可能希望大家都能把握機(jī)遇實(shí)現(xiàn)薪資/職業(yè)躍遷這份完整版的大模型 AI 學(xué)習(xí)資料已經(jīng)上傳CSDN朋友們?nèi)绻枰梢晕⑿艗呙柘路紺SDN官方認(rèn)證二維碼免費(fèi)領(lǐng)取【保證100%免費(fèi)】