helloworld入门【从0到1,LangChain+RAG全链路实战AI知识库】

下面给出一份"能直接跑"的最小可运行范例(Python 3.9+),覆盖

  1. 文档切片 → 2. 向量化入库 → 3. 多路召回 → 4. 重排 → 5. 带护栏的生成 → 6. 多轮记忆。
    全部用开源组件,不依赖付费 Embedding API;GPU 仅需 6 GB 显存即可跑 bge-base-zh-v1.5。
    代码按文件拆分,复制即用;config 里换路径即可接入你自己的语料。
bash 复制代码
rag_mini/
├── config.py          # 全局参数
├── ingest.py          # 建库脚本(一次性)
├── retriever.py       # 多路召回 + 重排
├── chain.py           # 带护栏的 LangChain 链
├── chat.py            # 终端交互(多轮)
└── requirements.txt

requirements.txt

ini 复制代码
langchain==0.2.14
langchain-community==0.2.12
sentence-transformers==2.7.0
faiss-cpu==1.8.0
transformers>=4.39.0
torch>=2.1.0
colbert-ir==0.2.0         # 用于 late-interaction 重排
numpy pandas tqdm

config.py

python 复制代码
import os

# 基础路径
BASE_DIR   = os.path.dirname(__file__)
DOCS_DIR   = os.path.join(BASE_DIR, "docs")          # 放原始文档
FAISS_DB   = os.path.join(BASE_DIR, "faiss_index")
COLBERT_CKPT = "ckpts/colbertv2.0-zh"               # 可换 smaller 版本

# 向量模型(本地)
EMBED_MODEL = "BAAI/bge-base-zh-v1.5"

# 生成模型(任何 HuggingFace 格式均可)
LLM_MODEL   = "THUDM/chatglm3-6b"                   # 显存 12 GB 可跑,CPU 也能跑但慢

# 召回参数
TOP_K_VEC = 20      # 向量粗排
TOP_K_BM25 = 20
TOP_K_FINAL = 5     # 重排后送入 LLM

ingest.py ------ 文档解析 + 切片 + 向量化 + 入库

python 复制代码
import os, json, torch
from langchain.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.embeddings import HuggingFaceBgeEmbeddings
from config import DOCS_DIR, FAISS_DB, EMBED_MODEL

device = 'cuda' if torch.cuda.is_available() else 'cpu'

def load_docs():
    loader = DirectoryLoader(DOCS_DIR, glob="**/*.txt", loader_cls=TextLoader, loader_kwargs='utf-8')
    return loader.load()

def split_docs(docs):
    splitter = RecursiveCharacterTextSplitter(
        separators=["\n\n", "\n", "。", ";"],
        chunk_size=512,
        chunk_overlap=64,
        length_function=len
    )
    return splitter.split_documents(docs)

def build_index():
    docs  = load_docs()
    splits = split_docs(docs)
    emb = HuggingFaceBgeEmbeddings(
        model_name=EMBED_MODEL,
        model_kwargs={'device': device},
        encode_kwargs={'normalize_embeddings': True}
    )
    vs = FAISS.from_documents(splits, emb)
    vs.save_local(FAISS_DB)
    print(f"finished! {len(splits)} chunks saved.")

if __name__ == '__main__':
    build_index()

retriever.py ------ 多路召回(向量 + BM25)+ ColBERT 重排

python 复制代码
import os, torch, json
from langchain.vectorstores import FAISS
from langchain.embeddings import HuggingFaceBgeEmbeddings
from langchain.retrievers import BM25Retriever, EnsembleRetriever
from colbert import ColBERT
from config import FAISS_DB, EMBED_MODEL, COLBERT_CKPT, TOP_K_VEC, TOP_K_BM25, TOP_K_FINAL

device = 'cuda' if torch.cuda.is_available() else 'cpu'

class ColBERTReranker:
    def __init__(self, ckpt: str, max_len=512):
        self.colbert = ColBERT.from_pretrained(ckpt).to(device).eval()
        self.max_len = max_len

    def rerank(self, query: str, passages: list[str]) -> list[float]:
        scores = self.colbert.score_pairs([(query, p) for p in passages])
        return scores

class HybridRetriever:
    def __init__(self):
        # 1. 向量
        emb = HuggingFaceBgeEmbeddings(
            model_name=EMBED_MODEL,
            model_kwargs={'device': device}
        )
        self.vectorstore = FAISS.load_local(FAISS_DB, emb, allow_dangerous_deserialization=True)
        vec_ret = self.vectorstore.as_retriever(search_kwargs={"k": TOP_K_VEC})

        # 2. BM25
        all_docs = self.vectorstore.similarity_search(" ", k=10000)  # 取出全部
        bm25_ret = BM25Retriever.from_documents(all_docs, k=TOP_K_BM25)

        # 3. 加权融合
        self.ensemble = EnsembleRetriever(
            retrievers=[vec_ret, bm25_ret],
            weights=[0.6, 0.4]
        )

        # 4. 重排
        self.reranker = ColBERTReranker(COLBERT_CKPT)

    def get_relevant(self, query: str):
        docs = self.ensemble.get_relevant_documents(query)
        passages = [d.page_content for d in docs]
        scores = self.reranker.rerank(query, passages)
        # 取 TOP_K_FINAL
        top_idx = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:TOP_K_FINAL]
        return [docs[i] for i in top_idx]

chain.py ------ 带护栏的 RAG 链(自定义提示 + 置信度过滤)

python 复制代码
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.llms import HuggingFacePipeline
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
from retriever import HybridRetriever
from config import LLM_MODEL
import torch, re

device = 'cuda' if torch.cuda.is_available() else 'cpu'

def load_llm():
    tok = AutoTokenizer.from_pretrained(LLM_MODEL, trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained(
        LLM_MODEL,
        torch_dtype=torch.float16 if device=='cuda' else torch.float32,
        trust_remote_code=True
    ).to(device).eval()
    pipe = pipeline(
        "text2text-generation" if "chatglm" in LLM_MODEL.lower() else "text-generation",
        model=model, tokenizer=tok,
        max_new_tokens=512, temperature=0.1, top_p=0.9, do_sample=True
    )
    return HuggingFacePipeline(pipeline=pipe)

prompt = PromptTemplate(
    input_variables=["context", "question"],
    template="""已知信息:
{context}

请根据上述信息回答用户问题。如果信息中找不到答案,请严格回复"知识库未涵盖"。
问题:{question}"""
)

class RagChain:
    def __init__(self):
        self.retriever = HybridRetriever()
        self.llm = load_llm()
        self.chain   = LLMChain(prompt=prompt, llm=self.llm)

    def answer(self, question: str) -> str:
        docs = self.retriever.get_relevant(question)
        context = "\n".join([d.page_content for d in docs])
        ans = self.chain.run(context=context, question=question).strip()
        # 简单护栏:出现特定关键词即拦截
        if "知识库未涵盖" in ans or len(ans) < 6:
            return "抱歉,知识库未找到相关内容。"
        return ans

chat.py ------ 终端多轮对话(ConversationBufferWindow)

python 复制代码
from langchain.memory import ConversationBufferWindowMemory
from chain import RagChain
from config import LLM_MODEL

MEMORY_KEY = "history"
memory = ConversationBufferWindowMemory(k=3, memory_key=MEMORY_KEY, input_key="question")

rag = RagChain()

def main():
    print("=== 输入 exit 退出 ===")
    while True:
        q = input("User: ")
        if q.strip().lower() == "exit":
            break
        # 把历史拼到 prompt
        history = memory.load_memory_variables({})[MEMORY_KEY]
        full_q = f"{history}\nUser: {q}" if history else q
        ans = rag.answer(full_q)
        print("Assistant:", ans)
        # 更新记忆
        memory.save_context({"question": q}, {"answer": ans})

if __name__ == '__main__':
    main()

使用步骤(Linux / macOS / WSL 通用)

  1. 准备语料
    .txt 文件放到 docs/ 目录,一层子目录即可;支持后续追加,再跑 ingest.py 会增量覆盖。

  2. 一键安装

    bash 复制代码
    python -m venv venv && source venv/bin/activate
    pip install -r requirements.txt
  3. 建库(仅需一次)

    bash 复制代码
    python ingest.py          # 生成 faiss_index/ 文件夹
  4. 终端聊天

    bash 复制代码
    python chat.py

    示例:

    makefile 复制代码
    User: 如何申请开票?
    Assistant: 登录后台→费用中心→左侧"发票管理"→点击"申请开票"即可。  
    User: 需要多久?
    Assistant: 知识库未找到相关内容。

进阶玩法(代码已留接口)

  • ColBERT 换成更小 colbert-mini,CPU 也能重排;
  • chain.py 里加 langchain.output_parsers.EnumOutputParser 做结构化输出;
  • memory 换成 ConversationSummaryBufferMemory 可支持 10+ 轮不爆显存;
  • 接入 FastAPI:把 RagChain().answer() 包成 /chat 接口即可。

至此,一个"开源可商用"的 LangChain + RAG 骨架就完整跑通了;接下来只需堆数据、堆评估、堆运营,就能让大模型真正"说人话、说行话"。祝迭代愉快!

相关推荐
带刺的坐椅12 小时前
LangChain4j 比 SolonAI 强在哪?弱在哪?
java·ai·langchain·solon·mcp
Kay_Liang13 小时前
大语言模型如何精准调用函数—— Function Calling 系统笔记
java·大数据·spring boot·笔记·ai·langchain·tools
沐雪架构师1 天前
乐学LangChain(1):总体架构和核心组件
langchain·学习方法
马尚来1 天前
helloworld入门【从0到1,LangChain+RAG全链路实战AI知识库】
langchain
马尚来1 天前
从0到1,LangChain+RAG全链路实战AI知识库
langchain
叼菠萝1 天前
AI 应用开发三剑客系列:LangChain 如何撑起 LLM 应用开发基石?
python·langchain
MichaelIp2 天前
基于MCP协议的多AGENT文章自动编写系统
语言模型·langchain·prompt·ai写作·llamaindex·langgraph·mcp
玲小珑2 天前
LangChain.js 完全开发手册(十六)实战综合项目二:AI 驱动的代码助手
前端·langchain·ai编程
viperrrrrrrrrr72 天前
Agent向量存储中的记忆衰退与记忆过载解决方案
langchain·大模型·agent·rag