嘘~ 正在从服务器偷取页面 . . .

【项目实战】基于RAG的法律条文智能助手


一、项目目标

  • 掌握法律智能问答系统的需求分析与RAG技术选型逻辑
  • 学会法律条文数据爬取、清洗与结构化处理
  • 实现RAG与Lora微调结合的模型优化方案

二、项目内容与重点

2.1 项目背景与需求设计

核心需求

  • 场景:法律条文智能问答系统,需满足:
    • 每月更新最新法律条文
    • 支持条款精准引用(如“《劳动法》第36条”)
    • 处理复杂查询(如劳动纠纷中的多条款关联分析)

技术选型:RAG vs 微调

对比维度 RAG方案 微调方案
数据更新频率 支持动态更新知识库 需要重新标注并训练模型
内容准确性 直接引用原文,避免生成失真 依赖标注质量,易产生偏差
知识覆盖范围 适合大规模知识体系 需要海量标注数据
可解释性 支持条款溯源,符合法律严谨性 黑盒模型,解释性差

重点: RAG在动态更新和可解释性上的优势。


2.2 核心实现流程

流程图:

用户提问 -> 问题解析 -> RAG检索 -> 生成答案 -> 引用溯源

关键模块
1.RAG检索层
- 使用微调后的通用大模型
- 知识库构建:结构化法律条文(JSON格式)
2.数据更新模块
- 定时爬取政府官网最新法规
- 自动化解析条款(正则匹配 第[一二三四…]条 )
重点: RAG与邻域微调的结合策略。

2.3 系统和环境

  • 系统:Windows11 + WSL2-Linux-Ubuntu22.04子系统
  • 硬件:GeForce RTX 4060 Ti 16GB

2.4 技术栈选型说明(LLM/向量数据库/框架选择)

  • LLM模型:Qwen1.5-4B-Chat
  • 嵌入式模型和检索模型:text2vec-base-chinese-sentence/BAAI\bge-reranker-large
  • RAG框架:LlamaIndex
  • 推理引擎:vLLM/LMDeploy
  • 前端界面框架:Streamlit界面深度集成

三、数据收集与整理

3.1 源数据获取

通过到政府相关法律界面爬取。

关键代码:

import requests  
from bs4 import BeautifulSoup

# 网页爬取与解析
def fetch_and_parse(url):
   soup = BeautifulSoup(response.text, 'html.parser')
   content = [para.get_text(strip=True) for para in soup.find_all('p')]    
   
   return '\n'.join(content)

3.2 数据清洗整理

我们获取到的数据还不能直接使用,需要进一步筛选整理,可以通过正则表达式。

关键代码:

# 条款提取(正则表达式)
pattern = re.compile(r'第([一二三四五六七八九十零百]+)条.*?(?=\n第|$)', re.DOTALL)
for match in pattern.finditer(data_str):
   lawarticles[f"法律名称 第{articlenumber}条"] = articlecontent

3.3 数据规范化

最后将数据按照要求统一规范格式。

规范格式如下:

{
 "中华人民共和国劳动法 第36条": "用人单位因生产经营需要...",
 "中华人民共和国劳动合同法 第10条": "建立劳动关系应当订立书面劳动合同..."
}

核心代码:

# 初始化字典来存储条款号和内容  
lawarticles = {}  
# 搜索所有匹配项  
for match in pattern.finditer(data_str):  
    articlenumber = match.group(1)  
    articlecontent = match.group(0).replace('第' + articlenumber + '条', '').strip()  
    lawarticles[f"中华人民共和国劳动合同法 第{articlenumber}条"] = articlecontent

# 转换字典为JSON字符串  
jsonstr = json.dumps(lawarticles, ensure_ascii=False, indent=4)

四、RAG基础实现与优化

4.1 RAG 基本流程

  1. 知识准备:收集并转换知识文档为文本数据,进行预处理和索引。
  2. 嵌入与索引:使用嵌入模型将文本转换为向量,并存储在向量数据库中。
  3. 查询检索:用户查询转换为向量,从数据库中检索相关知识。
  4. 提示增强:结合检索结果构建增强提示模版。
  5. 生成回答:大语言模型根据增强模版生成准确回答。

基本流程:

知识库 -> 数据处理 -> 向量化 -> 向量存储 -> 用户问题向量化 -> 向量检索 -> 合并检索数据和用户问题 -> 传入大模型生成答案

4.2 核心代码

4.2.1 模型初始化

创建一个 ModelInitializer 模型管理类,管理以及初始化我们的 LLM、Embedding、reranker 模型

核心代码:

# ========= def init_models =========
# 初始化LLM(使用OpenAILike)
llm = OpenAILike(  
    model=Config.LLM_MODEL_PATH,  
    api_base=config.API_BASE,  
    api_key=Config.API_KEY,  
    context_window=4096,  
    is_chat_model=True,  
    is_function_calling_model=False,  
)

# 初始化嵌入模型  
embed_model = HuggingFaceEmbedding(  
    model_name=Config.EMBED_MODEL_PATH,  
)

# 初始化重排序器  
reranker = SentenceTransformerRerank(  
    model=Config.RERANK_MODEL_PATH,  
    top_n=Config.RERANK_TOP_K  
)

# 全局设置  
Settings.embed_model = embed_model  
Settings.llm = llm

4.2.2 数据处理以及自定义node节点

创建一个 DataProcessor ,并定义两个方法,分别是
load_and_validate_json加载并验证法律JSON文件,以及 create_nodes 自定义节点方法。
load_and_validate_json 核心代码:

all_data.extend({  
                    "content": item,  
                    "metadata": {"source": json_file.name}  
                } for item in data)

加载JSON文件后,取两个值然存放到列表,最后返回列表的值。

create_nodes 核心代码:

nodes = []  
for entry in raw_data:  
    law_dict = entry["content"]  
    source_file = entry["metadata"]["source"]  
  
    for full_title, content in law_dict.items():  
        node_id = f"{source_file}::{full_title}"  
        parts = full_title.split(" ", 1)  
  
        node = TextNode(  
            text=content,  
            id_=node_id,  
            metadata={  
                "law_name": parts[0] if parts else "未知法律",  
                "article": parts[1] if len(parts) > 1 else "未知条款",  
                "full_title": full_title,  
                "source_file": source_file,  
                "content_type": "legal_article"  
            }  
        )        nodes.append(node)

接受load_and_validate_json 返回的列表后,进行遍历,然后提取所需信息,存放到列表中,最后返回列表的值。

我们测试输出生成的node节点前三条信息看看:

生成 205 个文本节点(示例ID:
[
TextNode(id_='中华人民共和国劳动法及劳动合同法.json::中华人民共和国劳动合同法 第一条', embedding=None, metadata={'law_name': '中华人民共和国劳动合同法', 'article': '第一条', 'full_title': '中华人民共和国劳动合同法 第一条', 'source_file': 'data1.json', 'content_type': 'legal_article'}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, metadata_template='{key}: {value}', metadata_separator='\n', text='为了完善劳动合同制度,明确劳动合同双方当事人的权利和义务,保护劳动者的合法权益,构建和发展和谐稳定的劳动关系,制定本法。', mimetype='text/plain', start_char_idx=None, end_char_idx=None, metadata_seperator='\n', text_template='{metadata_str}\n\n{content}'), 

TextNode(id_='data1.json::中华人民共和国劳动合同法 第二条', embedding=None, metadata={'law_name': '中华人民共和国劳动合同法', 'article': '第二条', 'full_title': '中华人民共和国劳动合同法 第二条', 'source_file': 'data1.json', 'content_type': 'legal_article'}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, metadata_template='{key}: {value}', metadata_separator='\n', text='中华人民共和国境内的企业、个体经济组织、民办非企业单位等组织(以下称用人单位)与劳动者建立劳动关系,订立、履行、变更、解除或者终止劳动合同,适用本法。\n国家机关、事业单位、社会团体和与其建立劳动关系的劳动者,订立、履行、变更、解除或者终止劳动合同,依照本法执行。', mimetype='text/plain', start_char_idx=None, end_char_idx=None, metadata_seperator='\n', text_template='{metadata_str}\n\n{content}'), 

TextNode(id_='data1.json::中华人民共和国劳动合同法 第三条', embedding=None, metadata={'law_name': '中华人民共和国劳动合同法', 'article': '第三条', 'full_title': '中华人民共和国劳动合同法 第三条', 'source_file': 'data1.json', 'content_type': 'legal_article'}, excluded_embed_metadata_keys=[], excluded_llm_metadata_keys=[], relationships={}, metadata_template='{key}: {value}', metadata_separator='\n', text='订立劳动合同,应当遵循合法、公平、平等自愿、协商一致、诚实信用的原则。\n依法订立的劳动合同具有约束力,用人单位与劳动者应当履行劳动合同约定的义务。', mimetype='text/plain', start_char_idx=None, end_char_idx=None, metadata_seperator='\n', text_template='{metadata_str}\n\n{content}')
]

4.2.3 向量化自定义node节点

我们创建好知识库数据的nodes节点后,准备开始用embedding模型进行向量化处理,创建索引并持久化保存到本地。

创建 VectorStoreManager 类来进行向量操作存储管理

核心代码:

# ========= def __init__ =========
self.chroma_client = chromadb.PersistentClient(path=Config.VECTOR_DB_DIR)  
self.collection = self.chroma_client.get_or_create_collection(  
    name=Config.COLLECTION_NAME,  
    metadata={"hnsw:space": "cosine"}  
)

# ========= def init_index =========
"""初始化或加载向量索引"""
if self.collection.count() == 0 and nodes:  
    print(f"创建新索引({len(nodes)}节点)...")  
    self._build_new_index(nodes, storage_context)  
else:  
    print("加载已有索引...")  
    storage_context = StorageContext.from_defaults(  
        persist_dir=Config.PERSIST_DIR,  
        vector_store=ChromaVectorStore(chroma_collection=self.collection)  
    )  
index = VectorStoreIndex.from_vector_store(  
    storage_context.vector_store,  
    storage_context=storage_context,  
    embed_model=Settings.embed_model  
)

传入nodes节点数据后,使用 chromadb 作为我们的向量存储库
我们运行然后输出向量库存存储情况:

# ========= 没有本地存储库输出显示 =========
创建新索引(205节点)...
Generating embeddings: 100%|██████████| 205/205 [00:12<00:00, 16.25it/s]

存储验证:
文档数量:205
示例节点ID:中华人民共和国劳动法及劳动合同法.json::中华人民共和国劳动合同法 第一条
索引加载耗时:12.92s

# ========= 已经创建本地存储库输出显示 =========
加载已有索引...

存储验证:
文档数量:205
示例节点ID:中华人民共和国劳动法及劳动合同法.json::中华人民共和国劳动合同法 第一条
索引加载耗时:0.17s

4.3 核心代码优化及效果对比

4.3.1 优化策略对比

主要优化有三个点,增加检索范围,添加重排序模型,设计提示词模板

优化维度 优化前 优化后
检索范围 固定Top3 初筛Top10 + 精排Top3
排序方式 余弦相似度 语义重排序模型
提示词设计 简单模板 强化约束的多条件模板

4.3.2 进行后大模型回答效果前后对比

使用模型:

  • LLM:Qwen1.5-4B-Chat
  • Embedding:text2vec-base-chinese-sentence
  • Rerank:BAAI\bge-reranker-large

优化核心代码:

# ========= 添加提示词模板 =========
QA_TEMPLATE = (  
    "<|im_start|>system\n"  
    "您是中国劳动法领域专业助手,必须严格遵循以下规则:\n"  
    "1.仅使用提供的法律条文回答问题\n"  
    "2.若问题与劳动法无关或超出知识库范围,明确告知无法回答\n"  
    "3.引用条文时标注出处\n\n"  
    "可用法律条文(共{context_count}条):\n{context_str}\n<|im_end|>\n"  
    "<|im_start|>user\n问题:{query_str}<|im_end|>\n"  
    "<|im_start|>assistant\n"  
)
response_template = PromptTemplate(QA_TEMPLATE)

# ========= 添加重排序模型检索内容重排序 =========
# 创建检索器和响应合成器 
retriever = index.as_retriever(  
    similarity_top_k=Config.TOP_K  # 扩大初始检索数量  
)  
response_synthesizer = get_response_synthesizer(  
    text_qa_template=response_template,  
    verbose=True  
)

# ========= while 内部修改逻辑 =========
# 1. 初始检索  
initial_nodes = retriever.retrieve(question)  
  
for node in initial_nodes:  
    node.node.metadata['initial_score'] = node.score  # 保存初始分数到元数据  
  
# 2. 重排序  
reranked_nodes = reranker.postprocess_nodes(  
    initial_nodes,  
    query_str=question  
)  
  
# 3. 合成答案  
response = response_synthesizer.synthesize(  
    question,  
    nodes=reranked_nodes  
)

测试问题:劳动合同试用期最长可以约定多久?

优化前,模型回答效果:

智能助手回答:
试用期最长不得超过六个月

支持依据:
[1] 中华人民共和国劳动法 第二十一条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动法
  条款内容:劳动合同可以约定试用期。试用期最长不得超过六个月。...
  相关度得分:0.9339

[2] 中华人民共和国劳动合同法 第十九条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动合同法
  条款内容:劳动合同期限三个月以上不满一年的,试用期不得超过一个月;劳动合同期限一年以上不满三年的,试用期不得超过二个月;三年以上固定期限和无固定期限的劳动合同,试用期不得超过六个月。
同一用人单位与同一劳动者只...
  相关度得分:0.9227

[3] 中华人民共和国劳动合同法 第十五条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动合同法
  条款内容:以完成一定工作任务为期限的劳动合同,是指用人单位与劳动者约定以某项工作的完成为合同期限的劳动合同。
用人单位与劳动者协商一致,可以订立以完成一定工作任务为期限的劳动合同。...
  相关度得分:0.9026

优化后,模型回答效果:

智能助手回答:
根据《中华人民共和国劳动合同法》第二十一条的规定,劳动合同可以约定试用期。试用期最长不得超过六个月。

支持依据:

[1] 中华人民共和国劳动法 第二十一条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动法
  初始相关度:0.9339
  重排序得分:0.9984
  条款内容:劳动合同可以约定试用期。试用期最长不得超过六个月。...

[2] 中华人民共和国劳动合同法 第十九条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动合同法
  初始相关度:0.9227
  重排序得分:0.9963
  条款内容:劳动合同期限三个月以上不满一年的,试用期不得超过一个月;劳动合同期限一年以上不满三年的,试用期不得超过二个月;三年以上固定期限和无固定期限的劳动合同,试用期不得超过六个月。
同一用人单位与同一劳动者只...

[3] 中华人民共和国劳动合同法 第十五条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动合同法
  初始相关度:0.9026
  重排序得分:0.0107
  条款内容:以完成一定工作任务为期限的劳动合同,是指用人单位与劳动者约定以某项工作的完成为合同期限的劳动合同。
用人单位与劳动者协商一致,可以订立以完成一定工作任务为期限的劳动合同。...

测试问题:哪种情况下,用人单位可以解除劳动合同?

优化前模型回答效果:

智能助手回答:
有下列情形之一的,用人单位可以解除劳动合同,但是应当提前三十日以书面形式通知劳动者本人:(一)劳动者患病或者非因工负伤,医疗期满后,不能从事原工作也不能从事由用人单位另行安排的工作的;(二)劳动者不能胜任工作,经过培训或者调整工作岗位,仍不能胜任工作的;(三)劳动合同订立时所依据的客观情况发生重大变化,致使原劳动合同无法履行,经当事人协商不能就变更劳动合同达成协议的。

支持依据:

[1] 中华人民共和国劳动法 第二十四条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动法
  条款内容:经劳动合同当事人协商一致,劳动合同可以解除。...
  相关度得分:0.9403

[2] 中华人民共和国劳动法 第二十六条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动法
  条款内容:有下列情形之一的,用人单位可以解除劳动合同,但是应当提前三十日以书面形式通知劳动者本人:
(一)劳动者患病或者非因工负伤,医疗期满后,不能从事原工作也不能从事由用人单位另行安排的工作的;
(二)劳动者...
  相关度得分:0.9398

[3] 中华人民共和国劳动合同法 第三十六条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动合同法
  条款内容:用人单位与劳动者协商一致,可以解除劳动合同。...
  相关度得分:0.9393

优化后模型回答效果:

智能助手回答:
根据《中华人民共和国劳动法》第二十六条规定,用人单位可以解除劳动合同的情形包括:
1. 劳动者患病或者非因工负伤,医疗期满后,不能从事原工作也不能从事由用人单位另行安排的工作的;
2. 劳动者不能胜任工作,经过培训或者调整工作岗位,仍不能胜任工作的;
3. 劳动合同订立时所依据的客观情况发生重大变化,致使原劳动合同无法履行,经当事人协商不能就变更劳动合同达成协议的。

支持依据:

[1] 中华人民共和国劳动法 第二十六条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动法
  初始相关度:0.9440
  重排序得分:0.9995
  条款内容:有下列情形之一的,用人单位可以解除劳动合同,但是应当提前三十日以书面形式通知劳动者本人:
(一)劳动者患病或者非因工负伤,医疗期满后,不能从事原工作也不能从事由用人单位另行安排的工作的;
(二)劳动者...

[2] 中华人民共和国劳动合同法 第三十六条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动合同法
  初始相关度:0.9411
  重排序得分:0.9926
  条款内容:用人单位与劳动者协商一致,可以解除劳动合同。...

[3] 中华人民共和国劳动法 第二十四条
  来源文件:中华人民共和国劳动法及劳动合同法.json
  法律名称:中华人民共和国劳动法
  初始相关度:0.9418
  重排序得分:0.9884
  条款内容:经劳动合同当事人协商一致,劳动合同可以解除。...

其他优化内容:可以更换参数量更大的大模型。

其实在上面添加的提示词模板内容对于参数量较小的模型来说是不能理解的,因此我们这次测试添加的提示词模板没什么作用,但是如果是参数量比较大的大模型,添加提示词模板还是很有效果的。


五、RAG效果测试评估体系

主要对RAG系统进行重排序召回率和端到端效果评估

5.1 重排序召回率

测试数据集示例:

# 劳动合同解除类  
{  
    "question": "劳动者可以立即解除劳动合同的情形有哪些?",  
    "relevant_ids": ["中华人民共和国劳动合同法 第三十八条"],  
    "confusing_ids": ["中华人民共和国劳动合同法 第三十九条", "中华人民共和国劳动法 第三十二条"]  
},  
{  
    "question": "用人单位单方解除劳动合同需要提前多久通知?",  
    "relevant_ids": ["中华人民共和国劳动合同法 第四十条"],  
    "confusing_ids": ["中华人民共和国劳动合同法 第三十七条", "中华人民共和国劳动法 第二十六条"]  
},  
  
# 工资与补偿类  
{  
    "question": "经济补偿金的计算标准是什么?",  
    "relevant_ids": ["中华人民共和国劳动合同法 第四十七条"],  
    "confusing_ids": ["中华人民共和国劳动合同法 第八十七条", "中华人民共和国劳动法 第二十八条"]  
},  
{  
    "question": "试用期工资最低标准是多少?",  
    "relevant_ids": ["中华人民共和国劳动合同法 第二十条"],  
    "confusing_ids": ["中华人民共和国劳动合同法 第十九条", "中华人民共和国劳动法 第四十八条"]  
}

测试核心代码:

"""计算单个问题的召回率"""  
retrieved_ids = [n.node.metadata["full_title"] for n in retrieved_nodes]  
hit = len(set(retrieved_ids) & set(relevant_ids))

"""批量评估召回率"""  
results = []  
for case in benchmark:  
    initial_nodes = self.retriever.retrieve(case["question"])  
    reranked_nodes = self.reranker.postprocess_nodes(  
        initial_nodes,  
        query_str=case["question"]  
    )    recall = self.calculate_recall(reranked_nodes, case["relevant_ids"])  
    results.append(recall)  
  
    print(f"\n问题:{case['question']}")  
    print(f"目标条款:{case['relevant_ids']}")  
    print(f"召回率:{recall:.1%}")

5.2 端到端条款命中率

测试数据集示例:

E2E_BENCHMARK = [  
    # 案例1:劳动合同解除  
    {  
        "question": "用人单位在哪些情况下不得解除劳动合同?",  
        "standard_answer": {  
            "条款": ["中华人民共和国劳动合同法 第四十二条"],  
            "标准答案": "根据《劳动合同法》第四十二条,用人单位不得解除劳动合同的情形包括:\n1. 从事接触职业病危害作业的劳动者未进行离岗前职业健康检查\n2. 在本单位患职业病或者因工负伤并被确认丧失/部分丧失劳动能力\n3. 患病或非因工负伤在规定的医疗期内\n4. 女职工在孕期、产期、哺乳期\n5. 连续工作满15年且距退休不足5年\n6. 法律、行政法规规定的其他情形\n违法解除需按第八十七条支付二倍经济补偿金",  
            "必备条件": ["职业病危害作业未检查", "孕期女职工", "连续工作满15年"]  
        }    },  
    # 案例2:工资支付  
    {  
        "question": "拖欠工资劳动者可以采取哪些措施?",  
        "standard_answer": {  
            "条款": ["中华人民共和国劳动合同法 第三十条", "中华人民共和国劳动法 第五十条"],  
            "标准答案": "劳动者可采取以下救济措施:\n1. 根据劳动合同法第三十条向法院申请支付令\n2. 依据劳动合同法第三十八条解除合同并要求经济补偿\n3. 向劳动行政部门投诉\n逾期未支付的,用人单位需按应付金额50%-100%加付赔偿金(劳动合同法第八十五条)",  
            "必备条件": ["支付令申请", "解除劳动合同", "行政投诉"]  
        }    }
]

测试核心代码:

try:  
    # 获取实际命中的条款  
    retrieved_clauses = [node.node.metadata["full_title"] for node in response.source_nodes]  
  
    # 获取标准答案要求的条款  
    required_clauses = standard["standard_answer"]["条款"]  
  
    # 计算命中情况  
    hit_clauses = list(set(retrieved_clauses) & set(required_clauses))  
    missed_clauses = list(set(required_clauses) - set(retrieved_clauses))  
  
    # 计算命中率  
    clause_hit = len(hit_clauses) / len(required_clauses) if required_clauses else 0.0  
  
    return {  
        "clause_score": clause_hit,  
        "hit_clauses": hit_clauses,  
        "missed_clauses": missed_clauses  
    }  
except Exception as e:  
    print(f"评估失败:{str(e)}")

评估测试我们给的初始检索数量TOP_K = 20 ,重排序保留数量RERANK_TOP_K = 5

最终评估效果:

=== 最终评估报告 ===
重排序召回率:94.7%
端到端条款命中率:83.3%

可以看到已经达到可以交付的要求。


六、部署与前端集成

因为我们需要将我们的整个RAG流程嵌入到前端界面中,所有我们就使用 streamlit 这个能够轻量化的高度定制的前端框架来构建我们的前端界面。

核心代码:

# ================== 界面组件 ==================
def _init_chat_interface(self):  
    if "messages" not in st.session_state:  
        st.session_state.messages = []  
  
    for msg in st.session_state.messages:  
        role = msg["role"]  
        content = msg.get("cleaned", msg["content"])  # 优先使用清理后的内容  
  
        with st.chat_message(role):  
            st.markdown(content)  
  
            # 如果是助手消息且包含思维链  
            if role == "assistant" and msg.get("think"):  
                with st.expander("📝 模型思考过程(历史对话)"):  
                    for think_content in msg["think"]:  
                        st.markdown(f'<span style="color: #808080">{think_content.strip()}</span>',  
                                    unsafe_allow_html=True)  
  
            # 如果是助手消息且有参考依据(需要保持原有参考依据逻辑)  
            if role == "assistant" and "reference_nodes" in msg:  
                self._show_reference_details(msg["reference_nodes"])

# ================== 显示回答  ================== 
with st.chat_message("assistant"):  
    # 提取思维链内容并清理响应文本  
    think_contents = re.findall(r'<think>(.*?)</think>', response_text, re.DOTALL)  
    cleaned_response = re.sub(r'<think>.*?</think>', '', response_text, flags=re.DOTALL).strip()  
  
    # 显示清理后的回答  
    st.markdown(cleaned_response)  
  
    # 如果有思维链内容则显示  
    if think_contents:  
        with st.expander("📝 模型思考过程(点击展开)"):  
            for content in think_contents:  
                st.markdown(f'<span style="color: #808080">{content.strip()}</span>',  
                            unsafe_allow_html=True)  
  
    # 显示参考依据(保持原有逻辑)  
    self._show_reference_details(filtered_nodes[:3])

通过运行命令来启动前端界面:

streamlit run web_ui.py

最终效果:
前端界面效果


七、RAG针对特定问题的微调修复

7.1 问题场景分析

案例: 系统混淆”用人单位解除合同”与”劳动者解除合同”两类问题
根本原因:
基础LLM缺乏法律主体识别能力

7.2 微调数据集构建

构建策略:

# 数据增强示例
{
   "instruction": "用人单位在哪些法定情形下可以单方解除劳动合同?",
   "input": "",
   "output": "根据《劳动合同法》第三十九条,用人单位可解除情形包括:1)试用期不符合录用条件;2)严重违反规章制度;3)严重失职造成重大损害;4)多重劳动关系影响工作;5)劳动合同无效;6)被追究刑事责任。"
},
{
   "instruction": "列举劳动者可以立即解除劳动合同的情形",
   "input": "",
   "output": "《劳动合同法》第三十八条规定,当用人单位:1)未提供劳动保护;2)拖欠工资;3)未缴社保;4)制度违法损害权益;5)合同无效时,劳动者可解除。若存在强迫劳动或危险作业,可立即解除。"
}

数据分布:

数据类型 数量 生成方式
主体对比问题 10 模板替换+人工校验
混淆场景负样本 10 主体置换生成对抗样本

7.3 模型微调方案

训练配置:

from peft import LoraConfig
lora_config = LoraConfig(
   r=32,
   lora_alpha=64,
   target_modules=["q_proj", "v_proj"],    lora_dropout=0.1,
   bias="none"
)

八、总结

项目文件结构:

LexRAGen/                       # 项目根目录
├── main.py                     # 主程序入口
├── web_ui.py                   # 前端界面集成
├── requirements.txt            # 依赖包列表
├── data/                       # 原始数据目录(需手动添加JSON文件)
├── chroma_db/                  # 向量数据库存储目录(自动生成)
├── storage/                    # 索引持久化目录(自动生成)
└── core/                       # 核心代码目录
    ├── __init__.py             # 标识为Python包
    ├── config.py               # 配置文件
    ├── models.py               # 模型初始化模块
    ├── data_processor.py       # 数据处理模块
    ├── vector_store.py         # 向量存储管理
    ├── evaluators.py           # 评估模块
    └── benchmark_data.py       # 测试数据集

九、附录与参考资料

项目仓库地址:https://github.com/ismoyuai/LexRAGen
LlamaIndex官方文档:https://docs.llamaindex.ai/en/stable/
LLM模型地址:https://www.modelscope.cn/models/Qwen/Qwen1.5-4B-Chat
LMDeploy官方文档:https://lmdeploy.readthedocs.io/zh-cn/latest/index.html
Streamlit官方文档:https://docs.streamlit.io/

更多实用文章和AI大模型应用开发文章欢迎到我个人博客来观看:墨宇Logic


文章作者: 墨宇Logic
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 墨宇Logic !
  目录