大模型的開發應用(十三):基于RAG的法律助手項目(上):總體流程簡易實現

RAG法律助手項目(上):總體流程簡易實現

  • 1 項目介紹
    • 1.1 方案選型
    • 1.2 知識文檔
  • 2 文檔解析
  • 3 知識庫構建
    • 3.1 構建知識節點
    • 3.2 嵌入向量初始化
    • 3.2 向量存儲
  • 4 查詢
    • 4.1 初始化大模型
    • 4.2 模型響應
    • 4.2 本文程序存在的問題
  • 完整代碼

1 項目介紹

本項目是制作一款專注于勞動糾紛的法律助手,使用RAG技術實現。

1.1 方案選型

制作一個基于大模型的專家系統,優先使用 RAG 來實現,因為相比于微調,RAG 有以下幾點優勢:

在這里插入圖片描述

當然,有的時候 RAG 會和微調一起使用,當面對的專業領域非常的小眾和冷門,原始模型根本無法理解用戶問題,那么需要先微調,讓模型對本專業的問題有一定的理解能力。簡單來講,就是先讓模型理解你想問什么,再看知識庫里有沒有。

本項目的基模型,我們選擇 Qwen1.5-1.8B-Chat,問幾個問題看它對勞動法是不是一點都不懂:

from modelscope import AutoModelForCausalLM, AutoTokenizermodel_name = "/data/coding/models/Qwen/Qwen1.5-1.8B-Chat"model = AutoModelForCausalLM.from_pretrained(model_name,torch_dtype="auto",device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)prompt = "我在目前這家公司工作三年了,現在公司想開除我,請問會如何賠償我?"
messages = [{"role": "system", "content": "You are Qwen, created by Alibaba Cloud. You are a helpful assistant."},{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(messages,tokenize=False,add_generation_prompt=True
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)generated_ids = model.generate(**model_inputs,max_new_tokens=512
)
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]print(response)

輸出:

在許多情況下,公司可能會根據您的職位、工作經驗、績效表現以及相關的法律法規和公司政策來決定是否解雇您。以下是一些可能的賠償方案:1. 薪資補償:如果公司在解除勞動合同前已向您支付了合理的工資或薪酬,并且沒有明確表示要解雇您,那么通常您將獲得一份工資補償。具體金額取決于您的職位等級、工作年限、月度/年度基本工資標準等因素。這種補償通常是基于您在過去三年的工作經歷和實際貢獻的。2. 績效獎金:如果您是公司的關鍵員工或者有顯著的工作成果,公司可能會在解除合同之前向您提供一定的績效獎金或其他形式的經濟激勵。這可能包括現金獎勵、股權激勵計劃(如股票期權)、福利金等。3. 停職期間工資:對于短期離職或臨時離職的員工,公司通常會提供一定期限的停職期間工資。這種工資通常包括基本工資和崗位補貼,但通常不包括年終獎或其他具有激勵性的補償。4. 其他福利:除了上述補償外,公司還可能會提供一些其他福利,如健康保險、退休金計劃、培訓機會、休假制度等。這些福利可以幫助您維持生活質量和職業發展。5. 解除通知:公司一般會在與您達成協議后,為您的個人文件(如簡歷、員工手冊、勞動合同)制作一份解除勞動關系的通知書,告知您因個人原因無法繼續履行工作職責,并要求您配合公司完成交接工作,如有需要可以領取相應的離職手續材料。6. 法律援助:如果公司認為您的離職行為違反了相關的勞動法規或公司的規章制度,他們可能會提起訴訟請求法院裁決您承擔違約責任并停止支付相關補償或費用。請注意,以上只是可能的賠償方案,具體的賠償數額應根據法律程序和公司的具體規定進行計算。建議您在收到解除勞動合同通知書時,盡快聯系人力資源部門或者法律專業人士,以獲取更準確的法律建議和處理方式。同時,也要了解自己的權利和義務,盡可能保留好相關證據,以便在后續過程中維護自己的權益。

可以看到,模型還是能理解你問的東西是什么,這種情況下就不需要微調了。只不過回復看起來并不那么專業,我們希望專業的回復,應該能直接給出賠償方案,以及相關的法律依據,這個時候就比較適合用 RAG 來實現。

當然,真實工作中,模型不可能選那么小的,一般都在3B以上。另外,這兩年的大模型能力非常強,它們在預訓練的時候,訓練數據集也必然包含《勞動法》和《勞動合同法》,不需要我們自己插入這方面的知識庫。我們這邊只是為了演示RAG如何構建知識庫,重要的是流程,而不是效果。

1.2 知識文檔

目前國內的勞動糾紛涉及的法律只有兩部,分別是《勞動法》和《勞動合同法》。

我國的《勞動法》是在1994年頒布實施,經過兩次修正,《勞動合同法》是在2007年頒布,經過一次修正,這兩款法律我們都使用最新的版本。

2 文檔解析

法律條款的文檔處理起來還是比較簡單的,一是因為法律條款中全部都是文字,沒有表格、圖片、流程圖等復雜的數據格式,二是因為每個條款都是單獨起一段,像下面一樣,格式非常整齊:

在這里插入圖片描述

我們把《勞動法》和《勞動合同法》中的每一條都拎出來,然后做成 JSON 文件,形式如下:

file1.json

在這里插入圖片描述

file2.json

在這里插入圖片描述

之所以要把每一條單獨拎出來,是因為我們想讓每條稱為一個單獨的知識點。可以用 AI 模型生成一段代碼,把PDF文件中的內容處理成上面的形式。

3 知識庫構建

3.1 構建知識節點

import json
from pathlib import Path
from typing import List
from llama_index.core.schema import TextNodedef load_and_create_nodes(data_dir: str) -> List[TextNode]:"""加載JSON法律文件并直接轉換為TextNode節點"""json_files = list(Path(data_dir).glob("*.json"))assert json_files, f"未找到JSON文件于 {data_dir}"nodes = []total_entries = 0for json_file in json_files:with open(json_file, 'r', encoding='utf-8') as f:try:data = json.load(f)# 驗證數據結構if not isinstance(data, list):raise ValueError(f"文件 {json_file.name} 根元素應為列表")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}' 的值不是字符串")# 處理字典中的鍵值對 (每個item只有一個鍵值對)for full_title, content in item.items():# 生成穩定ID (文件 + 標題)node_id = f"{json_file.name}::{full_title}"# 解析法律名稱和條款號parts = full_title.split(" ", 1)law_name = parts[0] if len(parts) > 0 else "未知法律"article = parts[1] if len(parts) > 1 else "未知條款"# 創建TextNode節點node = TextNode(text=content,id_=node_id,metadata={"law_name": law_name,"article": article,"full_title": full_title,"source_file": json_file.name,"content_type": "legal_article"})nodes.append(node)total_entries += 1except Exception as e:raise RuntimeError(f"處理文件 {json_file} 失敗: {str(e)}")print(f"成功轉換 {total_entries} 個法律條款為文本節點")if nodes:print(f"id示例:{nodes[0].id_}")print(f"文本示例:{nodes[0].text}")print(f"元數據示例:{nodes[0].metadata}")return nodesif __name__ == "__main__":nodes = load_and_create_nodes("/data/coding/data")

輸出:

成功轉換 205 個法律條款為文本節點
id示例:file1.json::中華人民共和國勞動法 第一條
文本示例:為了保護勞動者的合法權益,調整勞動關系,建立和維護適應社會主義市場經濟的勞動制度,促進經濟發展和社會進步,根據憲法,制定本法。
元數據示例:{'law_name': '中華人民共和國勞動法', 'article': '第一條', 'full_title': '中華人民共和國勞動法 第一條', 'source_file': 'file1.json', 'content_type': 'legal_article'}

3.2 嵌入向量初始化

初始化嵌入向量的代碼比較簡單:

class Config:EMBED_MODEL_PATH = "/data/coding/models/sungw111/text2vec-base-chinese-sentence"DATA_DIR = "/data/coding/data"VECTOR_DB_DIR = "/data/coding/chroma_db"PERSIST_DIR = "/data/coding/storage"COLLECTION_NAME = "chinese_labor_laws"TOP_K = 3from llama_index.embeddings.huggingface import HuggingFaceEmbedding
def init_embedding_model():"""初始化模型并驗證"""# Embedding模型embed_model = HuggingFaceEmbedding(model_name=Config.EMBED_MODEL_PATH,# 在一些比較老版本的 llama-index-embeddings-huggingface 中,需要加下面的參數,當前版本(0.5.4)不需要# encode_kwargs = {#     'normalize_embeddings': True,#     'device': 'cuda' if hasattr(Settings, 'device') else 'cpu'# })Settings.embed_model = embed_model# 驗證模型test_embedding = embed_model.get_text_embedding("測試文本")print(f"Embedding維度驗證:{len(test_embedding)}")return embed_model

3.2 向量存儲

import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import VectorStoreIndex, StorageContext, Settingsclass Config:EMBED_MODEL_PATH = "/data/coding/models/sungw111/text2vec-base-chinese-sentence"DATA_DIR = "/data/coding/data"VECTOR_DB_DIR = "/data/coding/chroma_db"PERSIST_DIR = "/data/coding/storage"COLLECTION_NAME = "chinese_labor_laws"TOP_K = 3def 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"})# 判斷是否需要新建索引if chroma_collection.count() == 0 and nodes is not None:print(f"創建新索引({len(nodes)}個節點)...")# 創建存儲上下文storage_context = StorageContext.from_defaults(# 將 ChromaDB 的集合(collection)封裝為 LlamaIndex 可識別的向量存儲接口,以支持索引構建與查詢。# 后續通過 VectorStoreIndex 構建索引時,會使用該 ChromaVectorStore 實例來添加或搜索向量。vector_store=ChromaVectorStore(chroma_collection=chroma_collection) )# 創建 StorageContext 對象的作用是為 LlamaIndex 提供一個統一的數據存儲管理上下文,# 用于協調向量存儲(vector store)、文檔存儲(docstore)和索引之間的數據流動與持久化操作。# 將文本節點存入文檔存儲(元數據+文本內容)storage_context.docstore.add_documents(nodes)  # 創建索引,將節點向量化并創建可搜索的索引結構index = VectorStoreIndex(nodes,storage_context=storage_context,show_progress=True)# 在創建 VectorStoreIndex 對象時需要傳入該 StorageContext 對象,以確保索引知道如何訪問向量和文檔。# 雙重持久化保障,將存儲上下文和索引對象保存到 Config.PERSIST_DIR 目錄(雙重保證)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 indexif __name__ == "__main__":nodes = load_and_create_nodes("/data/coding/data")# 初始化模型embed_model = init_embedding_model()print()import time print("\n初始化向量存儲...")start_time = time.time()index = init_vector_store(nodes)print(f"索引加載耗時:{time.time()-start_time:.2f}s")

上面的代碼看不懂不要緊,我們只需要用的時候會改配置套代碼就行。

輸出:

成功轉換 205 個法律條款為文本節點
id示例:file1.json::中華人民共和國勞動法 第一條
文本示例:為了保護勞動者的合法權益,調整勞動關系,建立和維護適應社會主義市場經濟的勞動制度,促進經濟發展和社會進步,根據憲法,制定本法。
元數據示例:{'law_name': '中華人民共和國勞動法', 'article': '第一條', 'full_title': '中華人民共和國勞動法 第一條', 'source_file': 'file1.json', 'content_type': 'legal_article'}Embedding維度驗證:768初始化向量存儲...
創建新索引(205個節點)...
Generating embeddings: 100%|████████████████████████████████████████████████████| 205/205 [00:01<00:00, 111.22it/s]存儲驗證結果:
DocStore記錄數:205
示例節點ID:file1.json::中華人民共和國勞動法 第一條
索引加載耗時:2.54s

程序運行結束后,在當前目錄下可以看大兩個新的文件夾,即向量數據庫目錄存儲上下文與索引目錄,如下圖所示:
在這里插入圖片描述

storage 目錄下有4個json文件,其中 docstore.json 存儲了文檔節點的元數據和文本內容(包括節點ID、文本內容、元數據),index_store.json 存儲了LlamaIndex 索引的元數據信息(如索引結構、索引ID),graph_store.json 存儲了圖索引數據(如關系圖譜的邊、節點關系),image__vector_store.json 則保存了圖像嵌入向量,后三個了解即可。

這里最重要的是 docstore.json,內容如下:
在這里插入圖片描述

這里用了utf-8編碼,我們看不見,可以寫一個腳本打印一下:

import jsondef 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():print(key)print(value)print('-'*80)# 獲取中文標題full_title = value['__data__']['metadata']['full_title']# 獲取文本內容text = value['__data__']['text']print(f"標題:{full_title}")print(f"內容:{text}\n")break# # 將JSON數據格式化并打印# formatted_json = json.dumps(data, ensure_ascii=False, indent=4)# print(formatted_json)# 使用函數
file_path = '/data/coding/storage/docstore.json'  
load_and_print_json(file_path)

輸出:

ile1.json::中華人民共和國勞動法 第一條
{'__data__': {'id_': 'file1.json::中華人民共和國勞動法 第一條', 'embedding': None, 'metadata': {'law_name': '中華人民共和國勞動法', 'article': '第一條', 'full_title': '中華人民共和國勞動法 第一條', 'source_file': 'file1.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}', 'class_name': 'TextNode'}, '__type__': '1'}
--------------------------------------------------------------------------------
標題:中華人民共和國勞動法 第一條
內容:為了保護勞動者的合法權益,調整勞動關系,建立和維護適應社會主義市場經濟的勞動制度,促進經濟發展和社會進步,根據憲法,制定本法。

4 查詢

4.1 初始化大模型

初始化大模型的代碼如下:

import time
from llama_index.core import PromptTemplate
from llama_index.llms.huggingface import HuggingFaceLLMConfig.LLM_MODEL_PATH = "/data/coding/models/Qwen/Qwen1.5-1.8B-Chat"def init_llm_model():# 初始化大語言模型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}        # 要讓回答偏向于知識庫,要讓模型減少隨機性,因此把temperature設置低一些,不要高于0.3)Settings.llm = llmreturn llm

4.2 模型響應

模型響應主要就是創建一個查詢引擎,然后進行查詢,我們里把前面的步驟都串起來,這樣對整個過程能有更好的理解:


def main():embed_model, llm = init_embedding_model(), init_llm_model()# 僅當需要更新數據時執行if not Path(Config.VECTOR_DB_DIR).exists():print("\n初始化數據...")nodes = load_and_create_nodes(Config.DATA_DIR)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.metadataprint(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()

輸出:

Embedding維度驗證:768初始化向量存儲...
加載已有索引...
Loading llama_index.core.storage.kvstore.simple_kvstore from /data/coding/storage/docstore.json.
Loading llama_index.core.storage.kvstore.simple_kvstore from /data/coding/storage/index_store.json.存儲驗證結果:
DocStore記錄數:205
示例節點ID:file1.json::中華人民共和國勞動法 第一條
索引加載耗時:0.63s請輸入勞動法相關問題(輸入q退出): 勞動合同試用期最長可以多久?智能助手回答:
試用期最長不得超過六個月。支持依據:[1] 中華人民共和國勞動法 第二十一條來源文件:file1.json法律名稱:中華人民共和國勞動法條款內容:勞動合同可以約定試用期。試用期最長不得超過六個月。...相關度得分:0.9302[2] 中華人民共和國勞動合同法 第十九條來源文件:file2.json法律名稱:中華人民共和國勞動合同法條款內容:勞動合同期限三個月以上不滿一年的,試用期不得超過一個月;勞動合同期限一年以上不滿三年的,試用期不得超過二個月;三年以上固定期限和無固定期限的勞動合同,試用期不得超過六個月。
同一用人單位與同一勞動者只...相關度得分:0.9180[3] 中華人民共和國勞動法 第二十條來源文件:file1.json法律名稱:中華人民共和國勞動法條款內容:勞動合同的期限分為有固定期限、無固定期限和以完成一定的工作為期限。
勞動者在同一用人單位連續工作滿十年以上,當事人雙方同意續延勞動合同的,如果勞動者提出訂立無固定期限的勞動合同,應當訂立無固定期限的勞...相關度得分:0.8955請輸入勞動法相關問題(輸入q退出): 

這里有點亂,下面的截圖看的比較清楚:
在這里插入圖片描述

可以看到,模型給出的回復相當靠譜,我們還把相似度前三的節點內容給打印出來了,作為回答的依據。但是,我們可以看到,第三條依據似乎和我們的輸入并不相關,這是單純采用余弦相似度會產生的問題。

4.2 本文程序存在的問題

我們再試一下方案選型時使用的問題:我在目前這家公司工作三年了,現在公司想開除我,請問會如何賠償我?
結果如下:

在這里插入圖片描述
在這里插入圖片描述

可以看到,三條依據中,有兩條并不是我們想要的(紅框框出),第一條講的是勞動者接觸勞動合同的情形,第三條講的是被開除之后工會能給出什么樣的幫助。這里第一條不相關,我估計是嵌入模型分不清 “勞動者接觸勞動合同” 和 “用人單位接觸勞動合同” 的區別,把“員工辭職”和“單位開除員工”認為是兩件相似的事情,分不清主動和被動。第三條講的倒是被開除的事情,但沒講賠償,也就是說,相關但答非所問。

另外,模型并沒有回復完就停止了,導致沒能輸出賠償方案,我估計是 llama_index.llms.huggingface 的相關包存在 Bug。

再來看一個例子:
在這里插入圖片描述
在這里插入圖片描述

這里只有第二個依據是我們需要的,依據[1] 是講公司在什么情況下可以開除員工的,依據[3] 講的是被開除時,工會能提供什么幫助。我們的程序并沒有把《勞動法》中的原文打印完整,下面是完整原文:

  1. “中華人民共和國勞動法 第二十六條”: "有下列情形之一的,用人單位可以解除勞動合同,但是應當提前三十日以書面形式通知勞動者本人:
  • (一)勞動者患病或者非因工負傷,醫療期滿后,不能從事原工作也不能從事由用人單位另行安排的工作的;
  • (二)勞動者不能勝任工作,經過培訓或者調整工作崗位,仍不能勝任工作的;
  • (三)勞動合同訂立時所依據的客觀情況發生重大變化,致使原勞動合同無法履行,經當事人協商不能就變更勞動合同達成協議的。",
  1. “中華人民共和國勞動法 第二十四條”: “經勞動合同當事人協商一致,勞動合同可以解除。”

  2. “中華人民共和國勞動法 第三十條”: “用人單位解除勞動合同,工會認為不適當的,有權提出意見。如果用人單位違反法律、法規或者勞動合同,工會有權要求重新處理;勞動者申請仲裁或者提起訴訟的,工會應當依法給予支持和幫助。”

明明檢索的結果不正確,大模型依然按照這個來回答了。

既然給出的依據和我們的問題無關,那為何算出的相關度得分會這么高?主要原因是問題和知識節點的內容都是中文,并且都是語義通順的句子,只要滿足這兩點,相似度(相關度得分)都能達到0.7以上,接近0.9都有可能。我們來問一個技術問題看看:

在這里插入圖片描述

總結
通過前面的例子,可以看到,我們當前的系統存在以下幾個問題:

    1. 分不清主動和被動的區別;
    1. 檢索出的結果相關,但答非所問;
    1. 與問題不相關的節點,相似度(相關度得分)卻很高;
    1. 模型的回復不完整,沒回復完就停了。

這些問題我們會在下一篇文章中解決,本文重點是介紹流程。

完整代碼

將本文中的代碼整個整理,整理后的完整代碼如下:

import time
import json
from typing import List
from pathlib import Pathfrom llama_index.core import PromptTemplate
from llama_index.core.schema import TextNode
from llama_index.core import VectorStoreIndex, StorageContext, Settingsimport chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.embeddings.huggingface import HuggingFaceEmbeddingclass Config:EMBED_MODEL_PATH = "/data/coding/models/sungw111/text2vec-base-chinese-sentence"LLM_MODEL_PATH = "/data/coding/models/Qwen/Qwen1.5-1.8B-Chat"DATA_DIR = "/data/coding/data"VECTOR_DB_DIR = "/data/coding/chroma_db"PERSIST_DIR = "/data/coding/storage"COLLECTION_NAME = "chinese_labor_laws"TOP_K = 3def load_and_create_nodes(data_dir: str) -> List[TextNode]:"""加載JSON法律文件并直接轉換為TextNode節點"""json_files = list(Path(data_dir).glob("*.json"))assert json_files, f"未找到JSON文件于 {data_dir}"nodes = []total_entries = 0for json_file in json_files:with open(json_file, 'r', encoding='utf-8') as f:try:data = json.load(f)# 驗證數據結構if not isinstance(data, list):raise ValueError(f"文件 {json_file.name} 根元素應為列表")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}' 的值不是字符串")# 處理字典中的鍵值對 (每個item只有一個鍵值對)for full_title, content in item.items():# 生成穩定ID (文件 + 標題)node_id = f"{json_file.name}::{full_title}"# 解析法律名稱和條款號parts = full_title.split(" ", 1)law_name = parts[0] if len(parts) > 0 else "未知法律"article = parts[1] if len(parts) > 1 else "未知條款"# 創建TextNode節點node = TextNode(text=content,id_=node_id,metadata={"law_name": law_name,"article": article,"full_title": full_title,"source_file": json_file.name,"content_type": "legal_article"})nodes.append(node)total_entries += 1except Exception as e:raise RuntimeError(f"處理文件 {json_file} 失敗: {str(e)}")print(f"成功轉換 {total_entries} 個法律條款為文本節點")if nodes:print(f"id示例:{nodes[0].id_}")print(f"文本示例:{nodes[0].text}")print(f"元數據示例:{nodes[0].metadata}")return nodesdef 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"})# 判斷是否需要新建索引if chroma_collection.count() == 0 and nodes is not None:print(f"創建新索引({len(nodes)}個節點)...")# 創建存儲上下文storage_context = StorageContext.from_defaults(# 將 ChromaDB 的集合(collection)封裝為 LlamaIndex 可識別的向量存儲接口,以支持索引構建與查詢。# 后續通過 VectorStoreIndex 構建索引時,會使用該 ChromaVectorStore 實例來添加或搜索向量。vector_store=ChromaVectorStore(chroma_collection=chroma_collection) )# 創建 StorageContext 對象的作用是為 LlamaIndex 提供一個統一的數據存儲管理上下文,# 用于協調向量存儲(vector store)、文檔存儲(docstore)和索引之間的數據流動與持久化操作。# 將文本節點存入文檔存儲(元數據+文本內容)storage_context.docstore.add_documents(nodes)  # 創建索引,將節點向量化并創建可搜索的索引結構index = VectorStoreIndex(nodes,storage_context=storage_context,show_progress=True)# 在創建 VectorStoreIndex 對象時需要傳入該 StorageContext 對象,以確保索引知道如何訪問向量和文檔。# 雙重持久化保障,將存儲上下文和索引對象保存到 Config.PERSIST_DIR 目錄(雙重保證)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 indexdef init_embedding_model():# 初始化Embedding模型embed_model = HuggingFaceEmbedding(model_name=Config.EMBED_MODEL_PATH,# 在一些比較老版本的 llama-index-embeddings-huggingface 中,需要加下面的參數,當前版本(0.5.4)不需要# encode_kwargs = {#     'normalize_embeddings': True,#     'device': 'cuda' if hasattr(Settings, 'device') else 'cpu'# })Settings.embed_model = embed_model# 驗證模型test_embedding = embed_model.get_text_embedding("測試文本")print(f"Embedding維度驗證:{len(test_embedding)}")return embed_modeldef init_llm_model():# 初始化大語言模型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}        # 要讓回答偏向于知識庫,要讓模型減少隨機性,因此把temperature設置低一些,不要高于0.3)Settings.llm = llmreturn llmdef main():embed_model, llm = init_embedding_model(), init_llm_model()# 僅當需要更新數據時執行if not Path(Config.VECTOR_DB_DIR).exists():print("\n初始化數據...")nodes = load_and_create_nodes(Config.DATA_DIR)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.metadataprint(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()

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/86114.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/86114.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/86114.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

覆蓋遷移工具選型、增量同步策略與數據一致性校驗

1 引言 在當今數據驅動的時代&#xff0c;數據遷移已成為系統迭代、數據庫升級、云遷移和架構演進中的關鍵環節。根據Gartner的調研&#xff0c;超過70%的企業級數據遷移項目因工具選擇不當或同步策略缺陷而延期或失敗。數據遷移不僅僅是簡單的數據搬運&#xff0c;而是涉及數…

`docker run -it --rm` 筆記250624

docker run -it --rm 筆記250624 docker run -it --rm 是一個強大且常用的 Docker 命令組合&#xff0c;特別適合交互式開發和調試場景。以下是詳細解析和使用指南&#xff1a; 參數解析 參數作用典型場景-i保持 STDIN 打開&#xff08;交互模式&#xff09;需要輸入命令的交…

解鎖阿里云AnalyticDB:數據倉庫的革新利器

AnalyticDB&#xff1a;云數據倉庫新勢力 在數字化浪潮中&#xff0c;數據已成為企業的核心資產&#xff0c;而云數據倉庫作為數據管理與分析的關鍵基礎設施&#xff0c;正扮演著愈發重要的角色。阿里云 AnalyticDB 作為云數據倉庫領域的佼佼者&#xff0c;以其卓越的性能、創…

【PX30 Qt 5.15 交叉編譯環境搭建完整指南】

PX30 Qt 5.15 交叉編譯環境搭建完整指南 (Ubuntu 20.04 → PX30 aarch64) &#x1f3af; 項目概覽 本指南詳細記錄了在Ubuntu 20.04上搭建針對Rockchip PX30的Qt 5.15.2交叉編譯環境的完整過程&#xff0c;包括實際操作步驟、遇到的問題及解決方案。 目標平臺: Rockchip PX3…

深入理解讀寫鎖 ReadWriteLock

在高性能并發編程中&#xff0c;如何有效地管理共享資源的訪問是核心挑戰之一。傳統的排他鎖&#xff08;如ReentrantLock&#xff09;在讀多寫少的場景下&#xff0c;性能瓶頸尤為突出&#xff0c;因為它不允許并發讀取。Java并發包&#xff08;java.util.concurrent.locks&am…

Unity Addressable使用之檢測更新流程

補充知識 關鍵文件說明 Addressable打包后會生成多種文件&#xff0c;主要包括 .hash、.json 和 .bundle 文件&#xff0c;它們各自有不同的作用。 .hash 文件&#xff08;哈希文件&#xff09; 作用&#xff1a; 用于 版本對比&#xff0c;檢查資源是否有更新。存儲的是 資…

Elasticsearch 中實現推薦搜索(方案設想)

1. 存儲商品數據的數據類型 為了支持推薦搜索&#xff0c;商品數據通常需要包含以下字段&#xff1a; 商品索引結構 PUT /products {"mappings": {"properties": {"product_id": {"type": "keyword" // 商品 ID},"…

Aerotech系列(4)Aerotech.A3200名空間

IconTypeDescriptionAxisMask Represents a selection of axes Controller Represents a controller Allows configuring and c

React Router 是怎么實現靈活導航的?

&#x1f399; 歡迎來到《前端達人 React播客書單》第 21 期。 視頻版&#xff08;播客風格更精彩&#xff09; 今天我們不講 Hook&#xff0c;來拆解前端開發中另一個高頻組件&#xff1a;React Router 的進階導航模式。 你可能用過 <Link> 或 <Route>&#xff0…

Modbus TCP轉Profibus DP網關與JF - 600MT 稱重變送器輕松實現數據互換

Modbus TCP轉Profibus DP網關與JF - 600MT 稱重變送器輕松實現數據互換 在工業自動化領域&#xff0c;不同設備之間的通信與數據交互至關重要。Modbus TCP轉Profibus DP網關作為連接不同協議設備的關鍵橋梁&#xff0c;發揮著不可或缺的作用。本文將以JF - 600MT稱重變送器與3…

聊聊 SQL 注入那些事兒

相信大家對于學校們糟糕的網絡環境和運維手段都早有體會&#xff0c;在此就不多做吐槽了。今天我們來聊一聊SQL注入相關的內容。 何謂SQL注入&#xff1f; SQL注入是一種非常常見的數據庫攻擊手段&#xff0c;SQL注入漏洞也是網絡世界中最普遍的漏洞之一。大家也許都聽過某某學…

多傳感器融合

目錄 多傳感器融合 多傳感器融合的方向 傳感器融合方案介紹 LOAM LIO-SAM LVI-SAM 多線激光雷達性質 什么是運動畸變 兩步優化的幀間里程記 IMU 器件介紹及選型建議 IMU 標定方法簡介 視覺里程計 VS 激光里程計 LVI-SAM 激光視覺融合思路簡介 多傳感器融合工程實踐經驗與技巧 多…

Auto-GPT vs ReAct:兩種智能體思路對決

目錄 Auto-GPT vs ReAct&#xff1a;兩種智能體思路對決 &#x1f9e0; 一、智能體的演化背景 &#x1f9e9; 二、Auto-GPT&#xff1a;自循環的執行體 &#x1f50d; 三、ReAct&#xff1a;推理 行動的交錯協同 ?? 四、對比總結 &#x1f6e0; 五、你該選誰&#xff…

本地部署大模型性能測試,DeepSeek-R1-0528-Qwen-8B 依然是我的不二之選

大家好&#xff0c;我是 ai 學習的老章 介紹一個大模型并發性能測試工具 看一下我高頻使用的&#xff0c;在2*4090顯卡上部署的 DeepSeek-R1-0528-Qwen-8B 性能如何 _我_特別喜歡的三個DeepSeek版本 DeepSeek-R1-0528 蒸餾 Qwen3:8B 大模型&#xff0c;雙 4090 本地部署&am…

華為云Flexus+DeepSeek征文|華為云 Dify 高可用部署教程:CCE 容器集群一鍵構建企業級智能應用

前言 在數字化轉型加速的企業級應用場景中&#xff0c;構建高可用智能平臺已成為業務創新的核心驅動力。本文深度解析基于華為云CCE容器服務的Dify智能應用部署實踐&#xff0c;揭示如何通過云原生架構與AI技術的深度融合&#xff0c;實現企業知識管理、智能客服等場景的敏捷落…

Linux 多進程間通信(IPC)詳解

在 Linux 系統中,多進程通信(Inter-Process Communication, IPC) 是實現多個進程之間數據交換和同步的重要機制。由于每個進程擁有獨立的地址空間,因此需要借助特定的系統機制來實現信息共享。 ?? Linux 下常見的 6 種進程間通信方式 管道(Pipe)命名管道(FIFO)消息隊…

服務器數據恢復——異常斷電導致服務器故障的數據恢復案例

服務器數據恢復環境&#xff1a; 某服務器上有一組由12塊硬盤組建的raid5磁盤陣列。 機房供電不穩定導致機房中該服務器非正常斷電&#xff0c;重啟服務器后管理員發現服務器無法正常使用。 意外斷電可能會導致服務器上的raid模塊損壞。 服務器數據恢復過程&#xff1a; 1、將故…

微信小程序中 rpx與px的區別

在微信小程序中的rpx比px方便的多 <!--pages/welcome/welcome.wxml--> <!--rpx替換px--> <image style"width:200rpx;height: 200rpx"src"/images/avatar/3.png"></image> <text>你好&#xff0c;凍梨</text> <but…

python3實現QQ官方機器人回調驗證

考慮到第三方的機器人現在越來越難維持了&#xff0c;來搗鼓一下官方的機器人。雖然官方藏著掖著不肯開放很多功能&#xff0c;但起碼能用。官方機器人的優點是穩定&#xff0c;只要申請成功&#xff0c;且你自己不亂搞&#xff0c;基本不存在被封的可能&#xff0c;缺點是藤子…

基于Vue3+TS的自定義指令開發與業務場景應用

文章目錄 1. 前言2. 基礎概念與優勢?3. Vue3TS自定義指令的創建與注冊?3.1. 創建自定義指令?3.2. 注冊自定義指令? 4. 實際場景示例?4.1. 權限指令控制?4.2. 圖片懶加載指令? 5. 優化與注意事項? 1. 前言 在 Vue3 的開發生態中&#xff0c;自定義指令是一項極為靈活且…