词嵌入模型构建本地知识库并实现RAG检索增强
要实现本地大模型调用检索本地知识库回答问题,其实就是目前大热的RAG(检索增强生成)技术,摸索一段时间,大概打通了,记录一下
一、本地部署模型
本地需要部署词嵌入模型和通用大模型
- 词嵌入模型采用
shaw/dmeta-embedding-zh:latest
- 大模型部署的是deepseek-r1:14b
使用ollama部署
ollama列表如下:
C:\Users\hg>ollama list
NAME ID SIZE MODIFIED
qwq:32b e5573aa53f69 19 GB 7 days ago
shaw/dmeta-embedding-zh:latest 55960d8a3a42 408 MB 4 weeks ago
deepseek-r1:14b ea35dfe18182 9.0 GB 4 weeks ago
deepseek-r1:32b 38056bbcbb2d 19 GB 6 weeks ago
qwen2.5:latest
二、生成本地知识库
目前我使用的本地知识库是一个word文档,内容是《道路交通安全法》,内容如下:
需要对word进行解析,并使用词嵌入模型生成文本向量库
1、文本处理增强
需要做的工作是将道交法条款提取出来,并保护但书条款
处理方法如下:
def legal_text_preprocess(text: str) -> str:
"""法律文档预处理"""
# 统一条款编号格式(将"第X条"标准化)
text = re.sub(r'第\s*([零一二三四五六七八九十百]+)\s*条', r'第\1条', text)
# 保护但书条款(但是...除外)
text = re.sub(r'(但是[^。]+。)', r'【但书】\1', text)
return text
2、提取条款编号
正则表达式匹配条款
def extract_article_number(text: str) -> str:
"""提取条款编号"""
match = re.search(r'第([零一二三四五六七八九十百]+)条', text)
return match.group(0) if match else "未标注条款"
3、核心,文本向量化
首先需要安装必要的库
# 安装核心依赖库
pip install python-docx langchain-ollama faiss-cpu langchain_community langchain_text_splitters
如果代码执行过程中,还有库未安装的报错,直接安装就行了
另外,faiss-cpu并未直接使用,是其他包调用了,提示有gpu版本的包可用,但是windows操作系统上并没有找到对应的包,所以最后安装了cpu版本
处理代码如下:
def build_vector_store(doc_path: str) -> FAISS:
"""构建法律文档向量库"""
# 1. 文档解析与预处理
doc = Document(doc_path)
raw_text = "\n".join([p.text for p in doc.paragraphs if p.text.strip()])
processed_text = legal_text_preprocess(raw_text)
# 2. 法律专用分块策略
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=384,
chunk_overlap=100,
separators=[
# r"(?m)^第[零一二三四五六七八九十百]+章", # 优先按条款分割
# r"(?m)^第[零一二三四五六七八九十百]+节", # 优先按条款分割
r"(?m)^第[零一二三四五六七八九十百]+条", # 优先按条款分割
"\n\n", # 次优先段落分隔
r"(?<=。)\s+(?=第)", # 条款间空白
"[。;]"
],
is_separator_regex=True,
keep_separator=True
)
text_chunks = text_splitter.split_text(processed_text)
# 3. 增强元数据标注
metadatas = [{
"source": "道交法",
"article": extract_article_number(chunk),
"content_hash": hash(chunk[:200]) # 内容校验码
} for chunk in text_chunks]
# 4. 生成向量库
embeddings = OllamaEmbeddings(
model="shaw/dmeta-embedding-zh",
base_url="http://localhost:11434"
)
vector_store = FAISS.from_texts(
texts=text_chunks,
embedding=embeddings,
metadatas=metadatas
)
vector_store.save_local("doc_vectors")
return vector_store
-
文档解析与预处理。使用python-docx将word解析为字符串,并且调用增强处理函数(legal_text_preprocess)处理文本
-
文本分块
text_splitter = RecursiveCharacterTextSplitter( chunk_size=384, chunk_overlap=100, separators=[ r"(?m)^第[零一二三四五六七八九十百]+条", # 优先按条款分割 "\n\n", # 次优先段落分隔 r"(?<=。)\s+(?=第)", # 条款间空白 "[。;]" ], is_separator_regex=True, keep_separator=True ) text_chunks = text_splitter.split_text(processed_text)
核心是用正则表达式对文本分块,块的最大长度不超过384个字符,可以重叠的部分不超过100个字符
-
增强元数据标注
metadatas = [{ "source": "道交法", "article": extract_article_number(chunk), "content_hash": hash(chunk[:200]) # 内容校验码 } for chunk in text_chunks]
其实就是对数据进行增强标注,最后分块的标注示例如下:
**元数据**: {'source': '道交法', 'article': '第五条', 'content_hash': -976191634281137236}
-
生成向量库
embeddings = OllamaEmbeddings( model="shaw/dmeta-embedding-zh", base_url="http://localhost:11434" ) vector_store = FAISS.from_texts( texts=text_chunks, embedding=embeddings, metadatas=metadatas ) vector_store.save_local("doc_vectors")
调用ollama部署的词嵌入模型,对文本进行分块和标注,并将向量保存在本地doc_vectors目录下,最后生成两个文件
- index.faiss
- index.pkl
个人觉得这个词嵌入模型划分的结果不太好,后面考虑换一个
4、查看生成的文本向量
实际上,文本向量是保存在doc_vectors目录下,但是也可以保存为可读的文件,代码如下
def export_chunks_to_md(vector_store, filename="knowledge_chunks.md"):
with open(filename, "w", encoding="utf-8") as f:
for i, (doc_id, document) in enumerate(vector_store.docstore._dict.items(), 1):
f.write(f"## 文本块 {i} (ID: {doc_id})\n")
f.write(f"**元数据**: {document.metadata}\n")
f.write(f"**内容**:\n{document.page_content}\n\n")
f.write("---\n\n")
print(f"已导出到 {os.path.abspath(filename)}")
调用方法:
vector_db = build_vector_store("./docs/道交法.docx")
export_chunks_to_md(vector_db) # 生成 knowledge_chunks.md
可以看看分块的结果:
个人感觉效果其实并不是十分理想,并没有分成一条一条的
三、RAG检索增强实现
其实就是限定大模型在本地知识库中检索
首先看看,在没有本地知识库的情况下,大模型对专用法律问题的回答
>>> 酒驾如何处罚?
<think>
嗯,关于酒驾的处罚,我需要好好理清楚。首先,我记得酒驾是指驾驶员血液中的酒精含量超过法定限制。不同国家和地区的规定可
能不一样,比如在中国,标准是每100毫升血液中不超过80毫克酒精。
那如果被查出酒驾会受到哪些处罚呢?我记得有几种后果:行政处罚、刑事责任和民事赔偿。具体来说,行政处罚包括罚款和吊销驾
照,甚至可能行政拘留。刑事责任方面,如果醉驾或者情节严重,可能会面临刑事犯罪的指控,比如危险驾驶罪,这通常意味着监禁
或 probation。
另外,酒驾还会带来其他的后果,比如保险费率上涨、车辆报废、职业影响等。这些都可能对个人的生活产生长期的影响。我还应该
考虑这些处罚的具体细节,比如罚款金额是多少,驾照吊销的时间有多长,刑事责任的量刑标准是什么等等。
或许我需要查阅相关法律条文或者可靠的资料来确认这些信息是否准确。例如,在中国,《道路交通安全法》对酒驾有明确规定,醉
驾会被视为危险驾驶罪,属于刑事犯罪,最低可能面临拘役和罚金。
总的来说,酒驾的处罚相当严重,不仅经济上会遭受损失,还会影响个人的自由和职业发展。因此,必须严格遵守法律,坚决杜绝酒
后驾车的行为。
</think>
在中国,酒驾是一种严重的违法行为,会受到多种严厉的处罚:
1. **行政处罚**:
- **罚款**:通常在1000元至5000元人民币之间。
- **吊销驾照**:首次酒驾将被吊销驾驶证,且五年内不得重新申领。
2. **刑事责任**:
- 若达到醉驾标准(血液酒精含量≥80mg/100ml),将被以危险驾驶罪定罪,面临拘役(通常一至六个月)和罚金。
3. **其他后果**:
- 保险费用增加。
- 可能导致车辆报废或无法获得新的车辆。
- 影响职业资格,如吊销从业资格证,影响从事需要驾照的工作。
- 留下刑事犯罪记录,可能对未来就业、出国等产生负面影响。
酒驾不仅危及自身和他人的安全,还将面临严重的法律后果。因此,坚决避免酒后驾驶,可以选择代驾或公共交通工具回家。
典型的一本正经的在胡说八道
进入RAG环节,先上完整代码
def init_rag_chain() -> RetrievalQA:
"""初始化法律问答链"""
# 1. 加载向量库
embeddings = OllamaEmbeddings(model="shaw/dmeta-embedding-zh")
vector_store = FAISS.load_local(
"doc_vectors",
embeddings=embeddings,
allow_dangerous_deserialization=True
)
# 2. 精确化检索配置
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={
"k": 5,
"score_threshold": 0.82,
# 关键修复:使用字典访问方式
"filter": lambda doc: doc.get("metadata", {}).get("article", "") != "未标注条款"
}
)
# 3. 法律专用大模型配置
llm = OllamaLLM(
model="deepseek-r1:14b",
temperature=0.2,
system="你是一名专业法律助理,严格根据《道路交通安全法》条款回答,禁止编造内容",
templates={
"response": "根据第{{metadata.article}}条:{{response}} (来源:{{metadata.article}})"
}
)
# 定义符合法律问答的提示模板
legal_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""请严格按以下格式回答:
条款内容: {context}
法律分析: 基于上述条款,具体分析如下:
{question} 的法定依据是...
关联条款: 另参见第X条、第Y条"""
)
return RetrievalQA.from_chain_type(
llm=llm,
retriever=retriever,
return_source_documents=True,
chain_type_kwargs={
"prompt": legal_prompt, # 使用模板实例
"document_prompt": PromptTemplate(
input_variables=["page_content"],
template="{page_content}"
)
}
)
1、加载向量库
其实就是用词嵌入模型载入已经生成的向量库
2、检索配置
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={
"k": 5,
"score_threshold": 0.82,
# 关键修复:使用字典访问方式
# "filter": lambda doc: doc.get("metadata", {}).get("article", "") != "未标注条款"
}
)
这里最后一条过滤原则,filter我注释起来了,实际上如果文本向量库准确的话,不应该注释
3、大模型提示词配置
首先是大模型的系统配置
llm = OllamaLLM(
model="deepseek-r1:14b",
temperature=0.2,
system="你是一名专业法律助理,严格根据《道路交通安全法》条款回答,禁止编造内容",
templates={
"response": "根据第{{metadata.article}}条:{{response}} (来源:{{metadata.article}})"
}
)
对系统的角色和回复的内容进行了严格的限制
然后是提示词模板:
# 定义符合法律问答的提示模板
legal_prompt = PromptTemplate(
input_variables=["context", "question"],
template="""请严格按以下格式回答:
条款内容: {context}
法律分析: 基于上述条款,具体分析如下:
{question} 的法定依据是...
关联条款: 另参见第X条、第Y条"""
)
4、问答示例
调用方法,询问道路交通安全相关的问题
from utils import build_vector_store, init_rag_chain, export_chunks_to_md
# vector_db = build_vector_store("./docs/道交法.docx")
# export_chunks_to_md(vector_db) # 生成 knowledge_chunks.md
# 初始化问答链
rag_chain = init_rag_chain()
# 执行查询示例
# question = "道路交通安全法的第二十三条是什么?"
question = "酒驾如何处罚?"
# question = "客车超员如何处罚?"
# question = "python写一段冒泡算法的程序"
result = rag_chain.invoke({"query": question})
# 输出结构化结果
print(f"答案:{result['result']}\n")
print("引用来源:")
for doc in result['source_documents']:
print(f"- {doc.page_content[:100]}... [来源:{doc.metadata['source']}]")
输出结果如下:
答案:<think>
嗯,用户问的是关于酒驾的处罚,我得仔细看看提供的法律条款。首先,在第九十条和第九十一条里提到了饮酒驾驶和醉酒驾驶的不同处罚。
根据第九十一条,饮酒后驾驶机动车会被暂扣驾驶证一到三个月,并罚款200到500元。如果醉酒驾驶,处理更严厉,包括约束至酒醒、拘留15天以下,以及暂扣驾照三到六个月,罚款500到2000元。
如果是营运车辆的驾驶员,处罚会更重。饮酒后会被暂扣三个月驾照,并罚款500元;醉酒则会被拘留15天,暂扣六个月驾照,并罚款2000元。特别需要注意的是,一年内两次醉驾会被吊销驾照,五年内不得驾驶营运机动车。
另外,第一百一十一条说明拘留的裁决权属于县一级公安机关,确保处罚执行到位。还有第一百一十条提到交警可以先扣留驾驶证,要求违法行为人在15天内处理,否则吊销驾照。
综合这些条款,酒驾和醉驾的处罚措施明确,包括罚款、暂扣或吊销驾照以及拘留等,体现了法律对交通违法的严格态度。
</think>
基于上述条款,具体分析如下:
**酒驾如何处罚?** 的法定依据是 **第九十一条**。该条规定了饮酒后驾驶机动车和醉酒后驾驶机动车的具体处罚措施,并区分了营运机动车与非营运机动车的不同处罚标准。
- **饮酒后驾驶机动车**:暂扣驾驶证一个月以上三个月以下,并处二百元以上五百元以下罚款。
- **醉酒后驾驶机动车**:约束至酒醒,拘留十五日以下,并暂扣驾驶证三个月以上六个月以下,罚款五百元以上二千元以下。
- **饮酒后驾驶营运机动车**:暂扣驾驶证三个月,并处五百元罚款。
- **醉酒后驾驶营运机动车**:约束至酒醒,拘留十五日以下,并暂扣驾驶证六个月,罚款二千元。
此外,一年内有两次醉酒驾驶行为的,吊销驾驶证,五年内不得驾驶营运机动车。
关联条款:
- 另参见 **第九十条**(关于机动车驾驶人违反道路通行规定的处罚)、
- **第一百一十条**(关于执行职务的交通警察扣留驾驶证的规定)、
- **第一百一十一条**(关于拘留裁决的规定)。
引用来源:
- 第九十条 机动车驾驶人违反道路交通安全法律、法规关于道路通行规定的,处警告或者二十元以上二百元以下罚款。本法另有规定的,依照规定处罚。
第九十一条 饮酒后驾驶机动车的,处暂扣一个月以上三个月以下机... [来源:道交法]
- 第一百一十五条 交通警察有下列行为之一的,依法给予行政处分:
(一)为不符合法定条件的机动车发放机动车登记证书、号牌、行驶证、检验合格标志的;
(二)批准不符合法定条件的机动车安装、使用警车、... [来源:道交法]
- 第一百零九条 当事人逾期不履行行政处罚决定的,作出行政处罚决定的行政机关可以采取下列措施:
(一)到期不缴纳罚款的,每日按罚款数额的百分之三加处罚款;
(二)申请人民法院强制执行。
第一百... [来源:道交法]
- 第七章 法律责任
第八十七条 公安机关交通管理部门及其交通警察对道路交通安全违法行为,应当及时纠正。
公安机关交通管理部门及其交通警察应当依据事实和本法的有关规定对道路交通安全违法行为予以处罚... [来源:道交法]
- 第一百一十一条 对违反本法规定予以拘留的行政处罚,由县、市公安局、公安分局或者相当于县一级的公安机关裁决。
第一百一十二条 公安机关交通管理部门扣留机动车、非机动车,应当当场出具凭证,并告知当事人... [来源:道交法]
以下是道交法91条的内容:
回答还是非常到位的
四、思考
上面应该是完成了一个完整的大模型RAG技术
但是我还是觉得有点问题
-
文本向量库不完美,并没有按道交法的条款逐项分割,后面准备试试用其他词嵌入模型
-
以上大模型的调用方式失去了大模型的通用问题回答能力,比如我让它用python写一段冒泡算法的程序,它回复如下:
答案:<think> 嗯,用户让我写一个关于冒泡排序算法的Python程序,并且要按照特定格式来回答。首先,我需要理解用户的需求是什么。看起来他们可能是在学习编程或者法律相关的知识,特别是涉及到软件开发和知识产权方面的法律条款。 用户提供的示例中提到了《中华人民共和国著作权法》和《计算机软件保护条例》,这让我想到,他们可能希望确保这个程序在编写时符合中国的相关法律法规,避免侵犯版权或其他法律问题。所以,我需要分析这些法律如何适用于这个问题。 冒泡排序是一种经典的排序算法,属于基础的计算机科学知识。根据《中华人民共和国著作权法》第二条,它保护的是文学、艺术和科学领域内的作品,包括计算机软件。因此,编写这个程序可能受到该法的保护,特别是原创性部分。 另外,《计算机软件保护条例》第十五条提到,软件开发者的开发者身份受法律保护,这意味着用户在编写代码时,他们的身份和权益需要得到尊重。同时,第二十条规定了复制、发布等行为需要获得授权,这提醒我要注意程序的使用和分发是否合法。 关联条款方面,除了上述提到的条款,可能还需要考虑《中华人民共和国民法典》中关于知识产权和个人信息保护的内容,确保在编写和使用该程序时不会侵犯他人的合法权益。 综上所述,我应该按照用户指定的格式,先写出冒泡排序的Python代码,然后分析相关的法律依据,并列出关联条款。这样不仅满足了编程需求,还符合用户的法律合规要求。 </think> 条款内容: 编写一段实现冒泡排序算法的Python程序 法律分析: 基于上述条款,具体分析如下: 编写一段实现冒泡排序算法的Python程序的法定依据是《中华人民共和国著作权法》第二条,该条规定计算机软件作为受保护的作品形式之一,其著作权自作品创作完成之日起产生。因此,编写和发布冒泡排序算法的Python程序属于软件开发行为,受到法律保护。 关联条款: 另参见第X条、第Y条 引用来源:
从回答来看,它是真的严格按照它是专业法律助理来回答的,这估计就是RAG的核心
但个人也可能还存在认识不到位的问题,后面再考虑持续优化