用 Python 做一个“能对话”的AI检索问答(RAG迷你版)

用 Python 做一个“能对话”的AI检索问答(RAG迷你版)

很多人想做“AI客服/AI知识库”,但一上来就被大模型、向量库、复杂框架劝退。其实你完全可以先做一个迷你版 RAG(检索增强生成):不用联网、不用GPU、不用复杂依赖,用 Python 把“从本地文档里找答案”的闭环跑起来——这就是最接近落地的 AI 形态之一。

这篇给你一套能跑、能用、能扩展的示例:
- 把一组文本资料切块(chunk)
- 用 TF-IDF 做向量化检索(先不引入向量数据库)
- 根据用户问题检索最相关片段
- 拼成“带引用的回答”(更像真实知识库)


1)RAG 到底是什么?一句话讲清楚

> RAG = 先检索相关资料,再基于资料组织回答。

它的好处非常现实:
- 你不需要模型“背诵”所有知识
- 文档更新后,系统立刻生效
- 可以给出引用片段,降低胡说风险
- 很适合客服、产品文档、FAQ、内部SOP


2)我们要做的迷你系统长什么样?

输入:用户问题(如“退款多久到账?”)
输出:
- 最相关的 2–3 段资料片段
- 一段“总结式回答”
- 标注来源片段编号(可用于前端展示)


3)可运行代码:本地资料 → 检索 → 回答

> 你只要把 DOCS 换成你的真实文档(FAQ、说明书、工单知识)就能用。

# -- coding: utf-8 --
import re
from dataclasses import dataclass
from typing import List, Tuple

import numpy as np from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity

========= 1) 你的知识库文档(示例) =========

DOCS = [ "退款政策:数字类产品通常不支持无理由退款。若因重复扣款或系统错误导致支付异常,可提交订单号申请核查。", "下载与权限:会员可在有效期内下载资源。若下载按钮不可用,请先登录账号并检查会员状态是否有效。", "技术支持:提交工单时请提供截图、报错信息、浏览器版本与复现步骤。通常在24小时内回复。", "更新说明:资源会同步上游版本发布进行更新。更新频率与上游发布节奏一致。", "常见问题:若页面显示异常,建议清理缓存并刷新CDN。也可尝试更换浏览器或无痕模式。" ]

========= 2) 文档切块(chunk) =========

def chunk_text(text: str, max_len: int = 80) -> List[str]: # 简单按句子切,再合并到 max_len 左右 sents = re.split(r"[。!?\n]+", text) sents = [s.strip() for s in sents if s.strip()] chunks, buf = [], "" for s in sents: if len(buf) + len(s) + 1 <= max_len: buf = (buf + "。" + s) if buf else s else: chunks.append(buf) buf = s if buf: chunks.append(buf) return chunks

@dataclass class Chunk: doc_id: int chunk_id: int text: str

def build_chunks(docs: List[str]) -> List[Chunk]: all_chunks = [] for i, d in enumerate(docs): cs = chunk_text(d, max_len=80) for j, c in enumerate(cs): all_chunks.append(Chunk(doc_id=i, chunk_id=j, text=c)) return all_chunks

========= 3) 检索器(TF-IDF + 余弦相似度) =========

class MiniRAG: def init(self, docs: List[str]): self.chunks = build_chunks(docs) self.texts = [c.text for c in self.chunks] # 中文简单做法:字符级 TF-IDF,不依赖分词 self.vectorizer = TfidfVectorizer(analyzer="char", ngram_range=(2,4)) self.matrix = self.vectorizer.fit_transform(self.texts)

def retrieve(self, query: str, top_k: int = 3) -> List[Tuple[Chunk, float]]:
    qv = self.vectorizer.transform([query])
    sims = cosine_similarity(qv, self.matrix).ravel()
    idxs = np.argsort(-sims)[:top_k]
    return [(self.chunks[i], float(sims[i])) for i in idxs]

def answer(self, query: str, top_k: int = 3) -> str:
    hits = self.retrieve(query, top_k=top_k)

    # 把检索到的片段拼成“可读的回答”
    context_lines = []
    for h, score in hits:
        context_lines.append(
            f"[Doc{h.doc_id}-Chunk{h.chunk_id} | score={score:.3f}] {h.text}"
        )

    # 简易总结策略:把最相关片段作为主依据,附带建议
    main = hits[0][0].text if hits else "暂未在资料中找到明确答案。"
    tips = "如果仍未解决,建议提供截图/订单号/复现步骤以便进一步排查。"

    return (
        f"问题:{query}\n\n"
        f"回答(基于资料):\n{main}\n{tips}\n\n"
        f"参考片段:\n" + "\n".join(context_lines)
    )

if name == "main": rag = MiniRAG(DOCS) q = "下载按钮不能用怎么办?" print(rag.answer(q, top_k=3))


4)怎么把它变成“你自己的AI客服”?

你只需要做三件事:

A. 替换 DOCS 为你的真实资料

来源可以是: - FAQ 页面文本 - 产品说明书 - 工单SOP - 退款/会员/下载规则 - 常见报错处理流程

建议按主题拆成多个文档,越结构化越好。

B. 增加更像“客服”的回答模板

比如对“下载失败”: - 先给三步排查:登录/缓存/浏览器 - 再给升级路径:提交工单、附带信息

C. 做一个简单 API(接到你的网站)

rag.answer() 包到接口里即可: - 前端输入问题 - 后端返回文本 + 引用片段 - 前端可以把引用做成折叠卡片


5)为什么这个迷你版值得做?

因为它是“正确方向”的最小闭环:
- 先解决“从资料里找答案”
- 再升级到向量数据库(FAISS、Milvus、Pinecone)
- 再接入大模型做更强的总结与多轮对话

你不需要一开始就把所有高级组件堆满。能跑通、能上线、能迭代,才是 AI 真正的价值。


6)下一步升级建议(按优先级)

  1. 把 DOCS 改为读取本地文件夹(md/txt/html)
  2. 加入“阈值”:相似度低就回复“资料不足”避免瞎答
  3. 加入“多轮上下文”:记住上一轮问题
  4. 换更强的向量表示:句向量模型(效果更像“语义理解”)

评论 0