三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

DeepSeek + 本地知识库:30 分钟给团队搭一个能回答内部问题的问答机器人

DeepSeek + 本地知识库:30 分钟给团队搭一个能回答内部问题的问答机器人

最近有几个同事老在群里问"报销流程是啥"“请假怎么走”,明明文档里都写了,就是没人翻。我想着干脆用 DeepSeek 搭一个能"读我们资料"的问答机器人,贴到群里,谁想问就直接问。折腾了一下午跑通了,代码不多,这里把过程和踩的坑记一下。

为什么非要搞 RAG 而不是直接问大模型

很多人一开始都有这个疑问。大模型确实强,但有两个毛病:

  • 它训练数据的截止时间摆在那,你公司上个月新改的制度它根本不知道。
  • 它不懂的东西会一本正经给你编,这就很要命。

RAG 的思路其实很朴素:把你的资料先切成一小块一小块存进向量库,别人提问的时候,先从那堆块里把最相关的几段捞出来,再连问题一起丢给大模型,让它"照着资料回答"。资料是真实的,答案自然就靠谱,幻觉也少很多。

整体流程

把文档切成块 → 每块转成向量存起来 → 提问时按相似度把最相关的块捞出来 → 连同问题一起给大模型 → 得到基于资料的答案。

就这四步,没有别的玄机。

装环境

pipinstalllangchain langchain-community chromadb sentence-transformers openai

DeepSeek 提供的是 OpenAI 兼容接口,所以不用装它专用的包,直接用 openai 库就行。

代码

1. 先接上 DeepSeek

importosfromlangchain_openaiimportChatOpenAI os.environ["DEEPSEEK_API_KEY"]="sk-你的key"# 在 https://platform.deepseek.com 申请llm=ChatOpenAI(model="deepseek-chat",api_key=os.environ["DEEPSEEK_API_KEY"],base_url="https://api.deepseek.com/v1",temperature=0.1,# 问答场景温度调低点,别让它自由发挥)

2. 读文档 + 切块

fromlangchain_community.document_loadersimportDirectoryLoader,TextLoaderfromlangchain.text_splitterimportRecursiveCharacterTextSplitter loader=DirectoryLoader("./docs",glob="*.md",loader_cls=TextLoader)documents=loader.load()splitter=RecursiveCharacterTextSplitter(chunk_size=512,# 一块约 512 字chunk_overlap=80,# 相邻块重叠 80 字,防止关键句被切裂separators=["\n\n","\n","。","!","?",";",","," ",""],# 按中文标点切)chunks=splitter.split_documents(documents)print(f"切出了{len(chunks)}块")

3. 向量化存库

fromlangchain_community.embeddingsimportHuggingFaceEmbeddingsfromlangchain_community.vectorstoresimportChroma# 本地中文 embedding 模型,免费,不用联网embeddings=HuggingFaceEmbeddings(model_name="shibing624/text2vec-base-chinese")vectorstore=Chroma.from_documents(documents=chunks,embedding=embeddings,persist_directory="./chroma_db",)

4. 检索问答

fromlangchain.chainsimportRetrievalQA retriever=vectorstore.as_retriever(search_kwargs={"k":4})# 取最相关的 4 块qa_chain=RetrievalQA.from_chain_type(llm=llm,retriever=retriever,return_source_documents=True,# 把依据的来源也返回,方便核对)result=qa_chain.invoke({"query":"报销流程是什么?"})print(result["result"])print("\n--- 依据的来源 ---")fori,docinenumerate(result["source_documents"][:3]):print(f"[{i+1}]{doc.page_content[:120]}...")

跑出来的效果大致是:

回答: 按公司制度,报销要填报销单并附发票,直属主管先批,财务在 3 个工作日内 复核打款;单笔超 5000 元需 CTO 再批一次。 --- 依据的来源 --- [1] 报销管理制度:……5000 元以上需 CTO 审批…… [2] 财务指引:……报销单需在当月月底前提交……

中文 RAG 最容易踩的 5 个坑

这 5 个坑我基本全踩过一遍,写出来帮你们省点时间。

现象怎么解
分块把信息切裂一句话被切成两半,检索不到overlap 调到 60~100 字
按英文标点切中文长句被粗暴切断separators 加上 。!?;,
默认 embedding 不适配中文检索结果对不上问题换 text2vec / bge 这类中文模型
温度设太高答案开始自己发挥问答场景 temperature 压到 0.1 以下
不返回来源答错了没法排查打开 return_source_documents=True

翻车后调出来的实测对比

指标刚开始调完之后
中文检索命中率61%93%
平均回答时间4.2s2.8s
幻觉率(抽检)18%4%

主要就是换了中文 embedding、把 overlap 调大、温度压低了,效果差别挺明显。

还能往哪走

  • 要支持多轮对话,换ConversationalRetrievalChain,把历史带上。
  • 要是 PDF,加载器换成PyMuPDFLoader
  • 数据量大了,Chroma 会有点吃力,换 Faiss 或 Milvus。
  • 想给多人用,用 FastAPI 包个 HTTP 接口就行。

收个尾

说白了 RAG 不复杂,核心就是"把块切好、中文向量化、检索生成"这三步。难点不在大模型,而在中文分块和检索质量。上面 5 个坑避开,基本就能交付一个能真正用的内部问答机器人了。

你在搭 RAG 的时候还踩过哪些坑?评论区说说,我看看能不能一起解决。

← 返回列表