基于 RAG 的法律条文智能助手 - 方案与数据

项目目标

掌握法律智能问答系统的需求分析与 RAG 技术选型逻辑

学会法律条文数据爬取、清洗与结构化处理

实现 RAG 与 Lora 微调结合的模型优化方案

项目内容与重点

需求设计

每月更新最新法律条文

支持条款精准引用(如 “《劳动法》第 36 条”)

处理复杂查询(如劳动纠纷中的多条款关联分析)

技术选型:RAG vs 微调

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

对比维度 RAG 方案 微调方案
数据更新频率 支持动态更新知识库 需重新标注数据并训练模型
内容准确性 直接引用原文,避免生成失真(避免模型幻觉问题) 依赖标注数据的质量,易产生偏差
知识覆盖范围 适合大模型知识体系(可扩展至任意规模的领域知识,不受模型参数限制) 模型的知识完全来自训练数据,需海量标注数据
可解释性 支持条款溯源,符合法律严谨性 黑盒模型,解释性差(无法追踪生成内容的依据)
核心实现流程
# 流程图
    用户提问 → 问题解析 → RAG检索 → 生成答案 → 引用溯源
# 关键模块
    1. RAG检索层
        使用微调后的通用大模型(如劳动法领域适配模型)
        知识库构建:结构化法律条文(JSON格式)
    2. 数据更新模块
        定时爬取政府官网最新法规
        自动化解析条款(正则匹配 第[一二三四...]条 )
重点:RAG与领域微调的结合策略。
数据收集与整理

import json
import re  # python中用于正则表达式操作的标准库模块
import requests
from bs4 import BeautifulSoup  # BeautifulSoup 是一个用于解析HTML和XML文档的Python库

def fetch_and_parse(url):
    # 请求网页
    response = requests.get(url)
    # 设置网页编码格式
    response.encoding = 'utf-8'
    # 解析网页内容
    soup = BeautifulSoup(response.text, 'html.parser')
    # 提取正文内容
    content = soup.find_all('p')
    # 初始化存储数据
    data = []
    # 提取文本并格式化
    for para in content:
        text = para.get_text(strip=True)  # 获取文本并除去空格
        if text:  # 只处理非空文本
            # 根据需求格式化内容
            data.append(text)
    data_str = '\n'.join(data)  # 将容器类型中的多个元素通过指定字符拼接成一个字符串
    # print(data_str)
    return data_str

def extract_law_articles(data_str):
    # 使用正则表达式,匹配每个条款号及其内容
    pattern = re.compile(r'第([一二三四五六七八九十零百]+)条.*?(?=\n第|$)', re.DOTALL)
    # 初始化字典来存储条款号和内容
    lawarticles = {}
    # 搜索所有匹配项
    for match in pattern.finditer(data_str):
        articlenumber = match.group(1)  # group():在re模块中,用于提取匹配结果中捕获组的内容
        articlecontent = match.group(0).replace('第' + articlenumber + '条', '').strip()
        lawarticles[f"中华人民共和国劳动法 第{articlenumber}条"] = articlecontent
    # 转换字典为JSON字符串
    jsonstr = json.dumps(lawarticles, ensure_ascii=False, indent=4)
    return jsonstr

if __name__ == '__main__':
    # 请求页面
    url = "https://www.gov.cn/banshi/2005-05/25/content_905.htm"
    data_str = fetch_and_parse(url)
    json_str = extract_law_articles(data_str)
    print(json_str)

中华人民共和国劳动法

import json
import re
import requests
from bs4 import BeautifulSoup


def fetch_and_parse(url):
    # 请求网页
    response = requests.get(url)
    # 设置网页编码格式
    response.encoding = 'utf-8'
    # 解析网页内容
    soup = BeautifulSoup(response.text, 'html.parser')
    # 提取正文内容
    content = soup.find_all('p')
    # 初始化存储数据
    data = []
    for p in content:
        text = p.get_text().strip()
        # text = p.get_text(strip=True)
        if text:
            for line in text.split('\n'):
                data.append(line.strip())
    data_str = '\n'.join(data)
    return data_str
def extract_law_articles(data_str):
    # 使用正则表达式,匹配每个条款号及其内容
    pattern = re.compile(r'第([一二三四五六七八九十零百]+)条.*?(?=\n第|$)', re.DOTALL)
    # 初始化字典来存储条款号和内容
    lawarticles = {}
    # 搜索所有匹配项
    for match in pattern.finditer(data_str):
        articlenumber = match.group(1)  # group():在re模块中,用于提取匹配结果中捕获组的内容
        articlecontent = match.group(0).replace('第' + articlenumber + '条', '').strip()
        lawarticles[f"中华人民共和国劳动合同法 第{articlenumber}条"] = articlecontent
    # 转换字典为JSON字符串
    jsonstr = json.dumps(lawarticles, ensure_ascii=False, indent=4)
    return jsonstr

if __name__ == '__main__':
    url = "https://www.gov.cn/ziliao/flfg/2007-06/29/content_669394.htm"
    data_str = fetch_and_parse(url)
    json_str = extract_law_articles(data_str)
    print(json_str)

中华人民国和过劳动合同

Lora 微调优化

微调场景
适用情况:
小众领域(如劳动仲裁)需提升问答专业性
需结合RAG知识库的问答对进行增强训练
微调步骤
1. 准备少量高质量问答数据(示例):
2. 使用Lora轻量化微调大模型,提升领域理解能力
重点:小样本微调与RAG的协同优化

基于 RAG 的法律条文智能助手 - 实现与部署

优化策略对比

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

基础代码

import json
import chromadb
import time
from pathlib import Path  # Python 中用于面向对象处理文件系统路径的类,提供跨平台支持(自动处理不同操作系统的路径分隔符)
from typing import List,Dict  # 用于类型注释,表示函数返回的类型
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import Settings,VectorStoreIndex,StorageContext
# VectorStoreIndex(向量存储索引):将文档转换为向量并存储在向量数据库中(文档自动分块和向量化)
# StorageContext(存储上下文):管理索引的存储后端,包括向量存储、文档存储和索引存储
# Settings(全局设置):LlamaIndex 的全局配置中心,统一管理各种组件的默认设置
from llama_index.core.schema import TextNode
from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.vector_stores.chroma import ChromaVectorStore  # ChroaVectorStore用于将向量索引存储在Chroma数据库并进行高效相似性搜索的能力
from llama_index.core import PromptTemplate

QA_TEMPLATE = (
    "<|im_start|>system\n"
    "你是一个专业的法律助手,请严格根据以下法律条文回答问题:\n"
    "相关法律条文:\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)

# =================== 配置区 ====================
class Config:
    EMBED_MODEL_PATH = r"E:\ai_model_data\model\embedding_model\sungw111\text2vec-base-chinese-sentence"
    LLM_MODEL_PATH = r"E:\ai_model_data\model\Qwen\Qwen2___5-0___5B-Instruct"

    DATA_DIR = r"D:\PycharmProjects\rag_laber_law\data"
    VECTOR_DB_DIR = r"D:\PycharmProjects\rag_laber_law\chroma_db"
    PERSIST_DIR = r"D:\PycharmProjects\rag_laber_law\storage"

    COLLECTION_NAME = "chinese_labor_laws"
    TOP_K = 10  # 扩大初始检索数量

配置

# ================== 初始化模型并验证 ==================
def init_models():
    # 初始化Embedding模型
    embed_model = HuggingFaceEmbedding(
        model_name = Config.EMBED_MODEL_PATH,
        # encode_kwargs = {  # 自定义编码过程中的参数
        #     'normalize_embeddings': True ,  # 是否对生成的向量进行归一化
        #     'device': 'cuda' if hasattr(Settings, 'device') else 'cpu'
        # }
    )
    Settings.embed_model = embed_model
    # 初始化LLM模型
    llm = HuggingFaceLLM(
        model_name = Config.LLM_MODEL_PATH,
        tokenizer_name = Config.LLM_MODEL_PATH,
        model_kwargs = {"trust_remote_code": True},
        tokenizer_kwargs = {"trust_remote_code": True},
        generate_kwargs = {"temperature": 0.3}
    )
    Settings.llm = llm
    # 验证模型
    test_embedding = embed_model.get_text_embedding("测试文本")
    print(f"Embedding维度验证:{len(test_embedding)}")
    return embed_model,llm

init_models

# ================== 数据处理 ==================
def load_and_validate_json_files(data_dir: str) -> List[Dict]:
    """加载并验证JSON法律文件"""
    json_files = list(Path(data_dir).glob("*.json"))  # glob支持通配符匹配,返回生成器
    assert json_files,f"未找到JSON文件于{data_dir}"  # assert 是一个用于调试的关键字,用于在代码中设置检查点,验证某个条件是否为 True。如果条件为 False,则会触发 AssertionError 异常

    all_data = []
    for json_file in json_files:
        with open(json_file,'r',encoding='utf-8') as f:
            try:
                data = json.load(f)
                print(data)
                # 验证数据结构
                if not isinstance(data,list):  # isinstance用于检查一个对象是否属于指定的类型或类型元组中的某一个类型
                    raise ValueError(f"文件 {json_file.name} 根元素应为列表")  # raise用于主动引发异常的关键字
                for item in data:
                    if not isinstance(item,dict):
                        raise ValueError(f"文件 {json_file.name} 包含非字典元素")
                    for k,v in item.items():
                        if not isinstance(v,str):
                            raise ValueError(f"文件 {json_file.name} 中键 '{k}' 的值不是字符串")
                all_data.extend(
                    {
                        "content": item,
                        "metadata": {"source": json_file.name}
                    } for item in data
                )
            except Exception as e:
                raise RuntimeError(f"加载文件 {json_file} 失败: {str(e)}")
    print(f"成功加载 {len(all_data)} 个法律文件条目")
    return all_data

load_and_validate_json_files

# ====================== 创建节点 =====================
def create_nodes(raw_data: List[Dict]) -> List[TextNode]:
    # 添加id稳定性保障
    nodes = []
    for entry in raw_data:
        law_dict = entry["content"]
        source_file = entry["metadata"]["source"]
        for full_title, content in law_dict.items():
            # 生成稳定id(避免重复)
            node_id = f"{source_file}::{full_title}"
            parts = full_title.split(" ",1)
            law_name = parts[0] if len(parts) > 0 else "未知法律"
            article = parts[1] if len(parts) > 1 else "未知条款"
            node = TextNode(
                text = content,
                id = node_id,
                metadata = {
                    "law_name": law_name,
                    "article": article,
                    "full_title": full_title,
                    "source_file": source_file,
                    "content_type": "legal_article"
                }
            )
            nodes.append(node)
    print(f"生成 {len(nodes)} 个文本节点(ID示例:{nodes[0].id_})")
    return nodes

create_nodes

# ================== 向量存储 ==================
def init_vector_store(nodes: List[TextNode]) -> VectorStoreIndex:
    chroma_client = chromadb.PersistentClient(path=Config.VECTOR_DB_DIR)
    chroma_collection = chroma_client.get_or_create_collection(
        name = Config.COLLECTION_NAME,
        metadata = {"hnsw:space": "cosine"},  # 指定余弦相似度计算
    )
    # 确保存储上下文正确初始化
    storage_context = StorageContext.from_defaults(
        vector_store=ChromaVectorStore(chroma_collection=chroma_collection)
    )
    # 判断是否需要新建索引
    if chroma_collection.count() == 0 and nodes is not None:
        print(f"创建新索引({len(nodes)}个节点)...")

        # 显式将节点添加到存储上下文
        storage_context.docstore.add_documents(nodes)

        index = VectorStoreIndex(
            nodes,
            storage_context=storage_context,
            show_progress=True
        )
        # 双重持久化保障
        storage_context.persist(persist_dir=Config.PERSIST_DIR)
        index.storage_context.persist(persist_dir=Config.PERSIST_DIR)  # <-- 新增
    else:
        print("加载已有索引...")
        storage_context = StorageContext.from_defaults(
            persist_dir=Config.PERSIST_DIR,
            vector_store=ChromaVectorStore(chroma_collection=chroma_collection)
        )
        index = VectorStoreIndex.from_vector_store(
            storage_context.vector_store,
            storage_context=storage_context,
            embed_model=Settings.embed_model
        )

    # 安全验证
    print("\n存储验证结果:")
    doc_count = len(storage_context.docstore.docs)
    print(f"DocStore记录数:{doc_count}")

    if doc_count > 0:
        sample_key = next(iter(storage_context.docstore.docs.keys()))
        print(f"示例节点ID:{sample_key}")
    else:
        print("警告:文档存储为空,请检查节点添加逻辑!")
        
    return index

init_vector_store

# ================== 主程序 ==================
def main():
    embed_model,llm = init_models()
    # 仅当需要更新数据时执行
    if not Path(Config.VECTOR_DB_DIR).exists():
        print("\n初始化数据...")
        raw_data = load_and_validate_json_files(Config.DATA_DIR)
        nodes = create_nodes(raw_data)
    else:
        nodes = None  # 已有数据时不加载
    print("\n初始化向量存储...")
    start_time = time.time()
    index = init_vector_store(nodes)
    print(f"索引加载耗时:{time.time() - start_time:.2f}s")
    # 创建查询引擎
    query_engine = index.as_query_engine(
        similarity_top_k = Config.TOP_K,
        text_qa_template = response_template,
        verbose = True
    )
    # 示例查询
    while True:
        question = input("\n请输入劳动法相关问题(输入q退出): ")
        if question.lower() == 'q':
            break

        # 执行查询
        response = query_engine.query(question)

        # 显示结果
        print(f"\n智能助手回答:\n{response.response}")
        print("\n支持依据:")
        for idx, node in enumerate(response.source_nodes, 1):
            meta = node.metadata
            print(f"\n[{idx}] {meta['full_title']}")
            print(f"  来源文件:{meta['source_file']}")
            print(f"  法律名称:{meta['law_name']}")
            print(f"  条款内容:{node.text[:100]}...")
            print(f"  相关度得分:{node.score:.4f}")

if __name__ == "__main__":
    main()

main

本地持久化存储转码

import json

def load_and_print_json(file_path):
    # 读取JSON文件
    with open(file_path, 'r', encoding='utf-8') as file:
        data = json.load(file)
        # print(data)
    # # 遍历数据并打印中文内容
    # for key, value in data['docstore/data'].items():
    #     # 获取中文标题
    #     full_title = value['__data__']['metadata']['full_title']
    #     # 获取文本内容
    #     text = value['__data__']['text']
    #     print(f"标题:{full_title}")
    #     print(f"内容:{text}\n")
    # 将JSON数据格式化并打印
    formatted_json = json.dumps(data, ensure_ascii=False, indent=4)
    print(formatted_json)

# 使用函数
file_path = r'D:\PycharmProjects\rag_laber_law\storage\docstore.json'
load_and_print_json(file_path)

read_json

"docstore/data": {
        "6bce5145-5233-4ef9-ba73-46af7e4a278d": {
            "__data__": {
                "id_": "6bce5145-5233-4ef9-ba73-46af7e4a278d",
                "embedding": null,
                "metadata": {
                    "law_name": "中华人民共和国劳动法",
                    "article": "第一条",
                    "full_title": "中华人民共和国劳动法 第一条",
                    "source_file": "file.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": null,
                "end_char_idx": null,
                "metadata_seperator": "\n",
                "text_template": "{metadata_str}\n\n{content}",
                "class_name": "TextNode"
            },
            "__type__": "1"
        },

节点展示

重排序优化实践

技术原理

两阶段检索机制显著提升召回精度:

初筛阶段:使用向量检索获取候选集

精排阶段:通过专用重排序模型计算语义相关性

# 创建检索器和响应合成器(将查询引擎修改为创建检索器和响应合成器)
retriever = index.as_retriever(  # 检索器:从索引中检索与用户查询最相关的文档片段
    similarity_top_k=Config.TOP_K  # 控制召回数量 )
response_synthesizer = get_response_synthesizer(  # 响应合成器:基于检索器返回的节点(一般会将节点进行重排序)和用户的问题,使大模型生成最终答案
    text_qa_template=response_template,  # 自定义响应模板
    verbose=True  # 显示详细生成过程 )

# 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)
初始检索与重排序评分机制差异

初始检索(向量相似度)

评分原理:基于 BGE-samll 等双编码器模型的余弦相似度

特定:快速计算,分数范围通常为 0-1

示例计算:

查询向量 = [0.2, 0.5, ..., 0.7] # 维度768
文档向量 = [0.3, 0.6, ..., 0.6]
相似度 = cosine_similarity(查询向量, 文档向量) # 0.9276

重排序(交叉编码器)

评分原理:使用 BGE-reranker 等交叉编码器计算 query-doc 交互

特点:计算代价高,分数范围可能为任意实数(需 sigmoid 处理)

示例计算:

model_input = "[CLS]劳动合同解除通知期[SEP]劳动合同期满终止条款[SEP]"
logits = model(model_input) # 输出原始分数如1.2
score = 1 / (1 + exp(-logits)) # 转换为0.3520
重排序优化后代码

import json
import chromadb
import time
from pathlib import Path  # Python 中用于面向对象处理文件系统路径的类,提供跨平台支持(自动处理不同操作系统的路径分隔符)
from typing import List,Dict  # 用于类型注释,表示函数返回的类型
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import Settings, VectorStoreIndex, StorageContext, get_response_synthesizer
# VectorStoreIndex(向量存储索引):将文档转换为向量并存储在向量数据库中(文档自动分块和向量化)
# StorageContext(存储上下文):管理索引的存储后端,包括向量存储、文档存储和索引存储
# Settings(全局设置):LlamaIndex 的全局配置中心,统一管理各种组件的默认设置
from llama_index.core.schema import TextNode
from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.vector_stores.chroma import ChromaVectorStore  # ChroaVectorStore用于将向量索引存储在Chroma数据库并进行高效相似性搜索的能力
from llama_index.core import PromptTemplate
from llama_index.core.postprocessor import SentenceTransformerRerank  # 新增重排序组件


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)

# =================== 配置区 ====================
class Config:
    EMBED_MODEL_PATH = r"E:\ai_model_data\model\embedding_model\sungw111\text2vec-base-chinese-sentence"
    LLM_MODEL_PATH = r"E:\ai_model_data\model\Qwen\Qwen2___5-0___5B-Instruct"
    RERANK_MODEL_PATH = r"E:\ai_model_data\model\ReRanker\BAAI\bge-reranker-large"  # 新增重排序模型路径

    DATA_DIR = r"D:\PycharmProjects\rag_laber_law\data"
    VECTOR_DB_DIR = r"D:\PycharmProjects\rag_laber_law\chroma_db"
    PERSIST_DIR = r"D:\PycharmProjects\rag_laber_law\storage"

    COLLECTION_NAME = "chinese_labor_laws"
    TOP_K = 10  # 扩大初始检索数量
    RERANK_TOP_K = 3  # 重排序后保留数量

配置

# ================== 初始化模型并验证 ==================
def init_models():
    # 初始化Embedding模型
    embed_model = HuggingFaceEmbedding(
        model_name = Config.EMBED_MODEL_PATH,
        # encode_kwargs = {  # 自定义编码过程中的参数
        #     'normalize_embeddings': True ,  # 是否对生成的向量进行归一化
        #     'device': 'cuda' if hasattr(Settings, 'device') else 'cpu'
        # }
    )
    Settings.embed_model = embed_model
    # 初始化LLM模型
    llm = HuggingFaceLLM(
        model_name = Config.LLM_MODEL_PATH,
        tokenizer_name = Config.LLM_MODEL_PATH,
        model_kwargs = {"trust_remote_code": True},
        tokenizer_kwargs = {"trust_remote_code": True},
        generate_kwargs = {"temperature": 0.3}
    )
    Settings.llm = llm
    # 初始化重排序模型(新增)
    reranker = SentenceTransformerRerank(
        model = Config.RERANK_MODEL_PATH,
        top_n = Config.RERANK_TOP_K
    )
    # 验证模型
    test_embedding = embed_model.get_text_embedding("测试文本")
    print(f"Embedding维度验证:{len(test_embedding)}")
    return embed_model,llm,reranker

init_models

# ================== 向量存储 ==================
def init_vector_store(nodes: List[TextNode]) -> VectorStoreIndex:
    chroma_client = chromadb.PersistentClient(path=Config.VECTOR_DB_DIR)
    chroma_collection = chroma_client.get_or_create_collection(
        name = Config.COLLECTION_NAME,
        metadata = {"hnsw:space": "cosine"},  # 指定余弦相似度计算
    )
    # 确保存储上下文正确初始化
    storage_context = StorageContext.from_defaults(
        vector_store=ChromaVectorStore(chroma_collection=chroma_collection)
    )
    # 判断是否需要新建索引
    if chroma_collection.count() == 0 and nodes is not None:
        print(f"创建新索引({len(nodes)}个节点)...")

        # 显式将节点添加到存储上下文
        storage_context.docstore.add_documents(nodes)

        index = VectorStoreIndex(
            nodes,
            storage_context=storage_context,
            show_progress=True
        )
        # 双重持久化保障
        storage_context.persist(persist_dir=Config.PERSIST_DIR)
        index.storage_context.persist(persist_dir=Config.PERSIST_DIR)  # <-- 新增
    else:
        print("加载已有索引...")
        storage_context = StorageContext.from_defaults(
            persist_dir=Config.PERSIST_DIR,
            vector_store=ChromaVectorStore(chroma_collection=chroma_collection)
        )
        index = VectorStoreIndex.from_vector_store(
            storage_context.vector_store,
            storage_context=storage_context,
            embed_model=Settings.embed_model
        )

    # 安全验证
    print("\n存储验证结果:")
    doc_count = len(storage_context.docstore.docs)
    print(f"DocStore记录数:{doc_count}")

    if doc_count > 0:
        sample_key = next(iter(storage_context.docstore.docs.keys()))
        print(f"示例节点ID:{sample_key}")
    else:
        print("警告:文档存储为空,请检查节点添加逻辑!")

    return index

init_vector_store

#新增过滤函数
def is_legal_question(text: str) -> bool:
    """判断问题是否属于法律咨询"""
    legal_keywords = ["劳动法", "合同", "工资", "工伤", "解除", "赔偿"]
    return any(keyword in text for keyword in legal_keywords)


# ================== 主程序 ==================
def main():
    embed_model,llm,reranker = init_models()
    # 仅当需要更新数据时执行
    if not Path(Config.VECTOR_DB_DIR).exists():
        print("\n初始化数据...")
        # raw_data = load_and_validate_json_files(Config.DATA_DIR)
        # nodes = create_nodes(raw_data)
    else:
        nodes = None  # 已有数据时不加载
    print("\n初始化向量存储...")
    start_time = time.time()
    index = init_vector_store(nodes)
    print(f"索引加载耗时:{time.time() - start_time:.2f}s")
    # # 创建查询引擎
    # query_engine = index.as_query_engine(
    #     similarity_top_k = Config.TOP_K,
    #     text_qa_template = response_template,
    #     verbose = True
    # )
    # 创建检索器和响应合成器(将查询引擎修改为创建检索器和响应合成器)
    retriever = index.as_retriever(  # 检索器:从索引中检索与用户查询最相关的文档片段
        similarity_top_k=Config.TOP_K  # 控制召回数量
    )
    response_synthesizer = get_response_synthesizer(  # 响应合成器:基于检索器返回的节点(一般会将节点进行重排序)和用户的问题,使大模型生成最终答案
        text_qa_template=response_template,  # 自定义响应模板
        verbose=True  # 显示详细生成过程
    )
    # 示例查询
    while True:
        question = input("\n请输入劳动法相关问题(输入q退出): ")
        if question.lower() == 'q':
            break

        # 添加问答类型判断(关键修改)
        # if not is_legal_question(question):  # 新增判断函数
        #     print("\n您好!我是劳动法咨询助手,专注解答《劳动法》《劳动合同法》等相关问题。")
        #     continue

        # 执行检索-重排序-回答流程(新增重排序相关步骤)
        start_time = time.time()

        # 1. 初始检索
        initial_nodes = retriever.retrieve(question)
        retrieval_time = time.time() - start_time

        for node in initial_nodes:
            node.node.metadata['initial_score'] = node.score  # 保存初始分数到元数据
        # 2. 重排序
        reranked_nodes = reranker.postprocess_nodes(
            initial_nodes,
            query_str=question
        )
        rerank_time = time.time() - start_time - retrieval_time

        # ★★★★★ 添加过滤逻辑在此处 ★★★★★
        MIN_RERANK_SCORE = 0.4  # 设置阈值
        # 执行过滤
        filtered_nodes = [
            node for node in reranked_nodes
            if node.score > MIN_RERANK_SCORE
        ]
        # 一般对模型的回复做限制就从filtered_nodes的返回值下手
        # print("原始分数样例:", [node.score for node in reranked_nodes[:3]])
        # print("重排序过滤后的结果:", filtered_nodes)
        # 空结果处理
        if not filtered_nodes:
            print("你的问题未匹配到相关资料!")
            continue
        # 3. 合成答案
        response = response_synthesizer.synthesize(
            question,
            nodes=reranked_nodes
        )
        synthesis_time = time.time() - start_time - retrieval_time - rerank_time

        # 显示结果(修改显示逻辑)
        print(f"\n智能助手回答:\n{response.response}")
        print("\n支持依据:")
        for idx, node in enumerate(reranked_nodes, 1):
            # 兼容新版API的分数获取方式
            initial_score = node.metadata.get('initial_score', node.score)  # 获取初始分数
            rerank_score = node.score  # 重排序后的分数

            meta = node.node.metadata
            print(f"\n[{idx}] {meta['full_title']}")
            print(f"  来源文件:{meta['source_file']}")
            print(f"  法律名称:{meta['law_name']}")
            print(f"  初始相关度:{node.node.metadata['initial_score']:.4f}")
            print(f"  重排序得分:{node.score:.4f}")
            print(f"  条款内容:{node.node.text[:100]}...")

        print(f"\n[性能分析] 检索: {retrieval_time:.2f}s | 重排序: {rerank_time:.2f}s | 合成: {synthesis_time:.2f}s")

if __name__ == "__main__":
    main()

main

vLLM 高性能推理集成

HuggingFace 引擎运行大模型较慢

部署配置
# 启动vLLM服务
python -m vllm.entrypoints.openai.api_server \
--model DeepSeek-R1-Distill-Qwen-1___5B \
--port 8000 \
--tensor-parallel-size 2 # GPU并行数

# -m vllm.entrypoints.openai.api_server  调用vLLM的OpenAI兼容API服务模块
# --port 8000  指定服务监听的端口号(默认8000)
系统集成
from llama_index.llms.openai_like import OpenAILikeLLM
class VLLMConfig:
    API_BASE = "http://localhost:8000/v1"
    MODEL_NAME = "DeepSeek-R1-Distill-Qwen-1___5B"
    TIMEOUT = 60
def init_vllm_llm():
    return OpenAILikeLLM(
        model=VLLMConfig.MODEL_NAME,
        api_base=VLLMConfig.API_BASE,
        temperature=0.3,
        max_tokens=1024,
        additional_kwargs={"stop": ["<|im_end|>"]}
    )
# 替换原有LLM组件
Settings.llm = init_vllm_llm()
性能对比
指标 HuggingFace 推理 vLLM 加速 提升幅度
生成速度(tokens/s) 45 320 7.1x
显存占用(7B 模型) 8.2GB 5.1GB -38%
长文本响应延迟 12.4s 3.8s 3.26x

Streamlit 可视化系统

界面组件设计
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐