Spaces:
Running
on
T4
Running
on
T4
update
Browse files- app.py +53 -233
- data_processor.py +86 -0
- llm_handler.py → rag_pipeline.py +0 -0
- retrieval_handler.py → retriever.py +0 -0
app.py
CHANGED
@@ -1,238 +1,58 @@
|
|
1 |
-
# app.py
|
2 |
-
# Phiên bản triển khai cuối cùng, tải các mô hình từ Hub và các tài sản dữ liệu đã xử lý trước.
|
3 |
-
|
4 |
-
import os
|
5 |
-
import sys
|
6 |
-
import json
|
7 |
-
import re
|
8 |
-
import pickle
|
9 |
-
from collections import defaultdict
|
10 |
-
|
11 |
-
# Core ML/DL và Unsloth
|
12 |
-
import torch
|
13 |
-
from unsloth import FastLanguageModel
|
14 |
-
from transformers import TextStreamer
|
15 |
-
|
16 |
-
# RAG - Retrieval
|
17 |
-
import faiss
|
18 |
-
from sentence_transformers import SentenceTransformer
|
19 |
-
from rank_bm25 import BM25Okapi
|
20 |
-
import numpy as np
|
21 |
-
|
22 |
-
# Deployment
|
23 |
import gradio as gr
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
points = clause.get("points_in_clause", [])
|
56 |
-
if points:
|
57 |
-
for point in points:
|
58 |
-
text = point.get("point_text_original")
|
59 |
-
if text:
|
60 |
-
flat_list.append({"text": text, "metadata": {"article": article_data.get("article"), "clause": clause.get("clause_number"), "point": point.get("point_id")}})
|
61 |
-
else:
|
62 |
-
text = clause.get("clause_text_original")
|
63 |
-
if text:
|
64 |
-
flat_list.append({"text": text, "metadata": {"article": article_data.get("article"), "clause": clause.get("clause_number")}})
|
65 |
-
return flat_list
|
66 |
-
|
67 |
-
def tokenize_vi_simple(text):
|
68 |
-
"""Tokenize tiếng Việt đơn giản."""
|
69 |
-
text = text.lower()
|
70 |
-
text = re.sub(r'[^\w\s]', '', text)
|
71 |
-
return text.split()
|
72 |
-
|
73 |
-
# --- PHẦN 3: LOGIC CỐT LÕI CỦA ỨNG DỤNG ---
|
74 |
-
|
75 |
-
def load_app_resources():
|
76 |
-
"""Tải hoặc tạo tất cả các tài nguyên cần thiết cho ứng dụng."""
|
77 |
-
print("--- Bắt đầu quá trình tải tài nguyên ---")
|
78 |
-
|
79 |
-
# 1. Tải các mô hình AI từ Hub
|
80 |
-
print("1. Đang tải LLM và Embedding Model từ Hugging Face Hub...")
|
81 |
-
device = "cuda" if torch.cuda.is_available() else "cpu"
|
82 |
-
llm_model, tokenizer = FastLanguageModel.from_pretrained(
|
83 |
-
model_name=LLM_MODEL_NAME, max_seq_length=2048, dtype=None, load_in_4bit=True
|
84 |
)
|
85 |
-
FastLanguageModel.for_inference(llm_model)
|
86 |
-
embedding_model = SentenceTransformer(EMBEDDING_MODEL_NAME, device=device)
|
87 |
-
APP_RESOURCES['llm_model'] = llm_model
|
88 |
-
APP_RESOURCES['tokenizer'] = tokenizer
|
89 |
-
APP_RESOURCES['embedding_model'] = embedding_model
|
90 |
-
print("✅ Tải mô hình AI thành công.")
|
91 |
-
|
92 |
-
# 2. Tải và xử lý dữ liệu luật từ file JSON bạn cung cấp
|
93 |
-
print(f"2. Đang tải và xử lý dữ liệu từ '{RAW_LAW_DATA_FILE}'...")
|
94 |
-
if not os.path.exists(RAW_LAW_DATA_FILE):
|
95 |
-
raise FileNotFoundError(f"Không tìm thấy file dữ liệu luật: '{RAW_LAW_DATA_FILE}'. Vui lòng tạo thư mục 'data' và upload file này lên Space.")
|
96 |
-
with open(RAW_LAW_DATA_FILE, 'r', encoding='utf-8') as f:
|
97 |
-
raw_data = json.load(f)
|
98 |
-
chunks_data = process_law_data_to_chunks(raw_data)
|
99 |
-
APP_RESOURCES['chunks_data'] = chunks_data
|
100 |
-
print(f"✅ Đã xử lý thành {len(chunks_data)} chunks dữ liệu.")
|
101 |
-
|
102 |
-
# 3. Tải FAISS Index đã được tạo sẵn
|
103 |
-
print(f"3. Đang tải FAISS index từ '{FAISS_INDEX_FILE}'...")
|
104 |
-
if not os.path.exists(FAISS_INDEX_FILE):
|
105 |
-
raise FileNotFoundError(f"Không tìm thấy file FAISS index: '{FAISS_INDEX_FILE}'. Vui lòng upload file này.")
|
106 |
-
faiss_index = faiss.read_index(FAISS_INDEX_FILE)
|
107 |
-
APP_RESOURCES['faiss_index'] = faiss_index
|
108 |
-
print(f"✅ Đã tải FAISS Index với {faiss_index.ntotal} vectors.")
|
109 |
-
|
110 |
-
# 4. Tải hoặc tạo BM25 Model (bước này nhanh nên có thể tạo on-the-fly)
|
111 |
-
if os.path.exists(BM25_MODEL_FILE):
|
112 |
-
print(f"4. Đang tải BM25 model từ '{BM25_MODEL_FILE}'...")
|
113 |
-
with open(BM25_MODEL_FILE, 'rb') as f:
|
114 |
-
bm25_model = pickle.load(f)
|
115 |
-
else:
|
116 |
-
print("4. Không tìm thấy BM25 model. Đang tạo mới...")
|
117 |
-
tokenized_corpus = [tokenize_vi_simple(c['text']) for c in chunks_data]
|
118 |
-
bm25_model = BM25Okapi(tokenized_corpus)
|
119 |
-
with open(BM25_MODEL_FILE, 'wb') as f:
|
120 |
-
pickle.dump(bm25_model, f)
|
121 |
-
print(f"✅ Đã tạo và lưu BM25 model vào '{BM25_MODEL_FILE}'.")
|
122 |
-
APP_RESOURCES['bm25_model'] = bm25_model
|
123 |
-
|
124 |
-
print("\n--- Tải tài nguyên hoàn tất! Ứng dụng đã sẵn sàng. ---")
|
125 |
-
|
126 |
-
def search_relevant_laws(query_text, k=5):
|
127 |
-
"""Thực hiện Hybrid Search."""
|
128 |
-
# (Hàm này giữ nguyên như trước, không cần thay đổi)
|
129 |
-
rrf_k_constant = 60
|
130 |
-
embedding_model = APP_RESOURCES['embedding_model']
|
131 |
-
faiss_index = APP_RESOURCES['faiss_index']
|
132 |
-
chunks_data = APP_RESOURCES['chunks_data']
|
133 |
-
bm25_model = APP_RESOURCES['bm25_model']
|
134 |
-
|
135 |
-
query_embedding = embedding_model.encode([query_text], convert_to_tensor=True)
|
136 |
-
query_embedding_np = query_embedding.cpu().numpy().astype('float32')
|
137 |
-
faiss.normalize_L2(query_embedding_np)
|
138 |
-
num_candidates = min(k * 10, faiss_index.ntotal)
|
139 |
-
_, semantic_indices = faiss_index.search(query_embedding_np, num_candidates)
|
140 |
-
|
141 |
-
tokenized_query = tokenize_vi_simple(query_text)
|
142 |
-
bm25_scores = bm25_model.get_scores(tokenized_query)
|
143 |
-
bm25_results = sorted(enumerate(bm25_scores), key=lambda x: x[1], reverse=True)[:num_candidates]
|
144 |
-
|
145 |
-
rrf_scores = defaultdict(float)
|
146 |
-
if semantic_indices.size > 0:
|
147 |
-
for rank, doc_idx in enumerate(semantic_indices[0]):
|
148 |
-
if doc_idx != -1: rrf_scores[doc_idx] += 1.0 / (rrf_k_constant + rank)
|
149 |
-
for rank, (doc_idx, score) in enumerate(bm25_results):
|
150 |
-
if score > 0: rrf_scores[doc_idx] += 1.0 / (rrf_k_constant + rank)
|
151 |
-
|
152 |
-
fused_results = sorted(rrf_scores.items(), key=lambda x: x[1], reverse=True)
|
153 |
-
|
154 |
-
final_results = []
|
155 |
-
for doc_idx, score in fused_results[:k]:
|
156 |
-
result = chunks_data[doc_idx].copy()
|
157 |
-
result['retrieval_score'] = score
|
158 |
-
final_results.append(result)
|
159 |
-
|
160 |
-
return final_results
|
161 |
-
|
162 |
-
def generate_llm_response(query, context):
|
163 |
-
"""Sinh câu trả lời từ LLM."""
|
164 |
-
# (Hàm này giữ nguyên như trước, không cần thay đổi)
|
165 |
-
llm_model = APP_RESOURCES['llm_model']
|
166 |
-
tokenizer = APP_RESOURCES['tokenizer']
|
167 |
-
prompt = f"""Bạn là một trợ lý AI chuyên tư vấn về luật giao thông đường bộ Việt Nam. Dựa vào các thông tin luật được cung cấp dưới đây để trả lời câu hỏi của người dùng một cách chính xác và chi tiết.
|
168 |
-
|
169 |
-
### Thông tin luật được trích dẫn:
|
170 |
-
{context}
|
171 |
-
|
172 |
-
### Câu hỏi của người dùng:
|
173 |
-
{query}
|
174 |
-
|
175 |
-
### Trả lời của bạn:"""
|
176 |
-
inputs = tokenizer(prompt, return_tensors="pt").to(llm_model.device)
|
177 |
-
output_ids = llm_model.generate(**inputs, max_new_tokens=512, temperature=0.3, do_sample=True, pad_token_id=tokenizer.eos_token_id)
|
178 |
-
response_text = tokenizer.decode(output_ids[0][inputs.input_ids.shape[1]:], skip_special_tokens=True).strip()
|
179 |
-
return response_text
|
180 |
-
|
181 |
-
# --- PHẦN 4: CÁC HÀM GIAO TIẾP VỚI GRADIO ---
|
182 |
-
|
183 |
-
def retriever_interface(query):
|
184 |
-
"""Giao tiếp với Tab 1: Chỉ tìm kiếm."""
|
185 |
-
retrieved_results = search_relevant_laws(query)
|
186 |
-
if not retrieved_results: return "Không tìm thấy điều luật nào liên quan."
|
187 |
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
progress(0.7, desc="Đang sinh câu trả lời...")
|
209 |
-
final_answer = generate_llm_response(query, context_for_llm)
|
210 |
-
progress(1, desc="Hoàn tất!")
|
211 |
-
return final_answer, context_for_display
|
212 |
-
|
213 |
-
# --- PHẦN 5: KHỞI CHẠY ỨNG DỤNG ---
|
214 |
-
|
215 |
-
# Tải tài nguyên một lần duy nhất
|
216 |
-
load_app_resources()
|
217 |
-
|
218 |
-
# Xây dựng giao diện
|
219 |
-
with gr.Blocks(theme=gr.themes.Soft(), title="Chatbot Luật GTĐB") as demo:
|
220 |
-
gr.Markdown("# ⚖️ Chatbot Luật Giao thông Đường bộ Việt Nam (RAG)")
|
221 |
-
with gr.Tabs():
|
222 |
-
with gr.TabItem("Tìm kiếm Điều luật (Retriever)"):
|
223 |
-
retriever_query = gr.Textbox(label="Nhập nội dung tìm kiếm", placeholder="Vd: Vượt đèn đỏ")
|
224 |
-
retriever_button = gr.Button("Tìm kiếm", variant="secondary")
|
225 |
-
retriever_output = gr.Markdown(label="Các điều luật liên quan")
|
226 |
-
|
227 |
-
with gr.TabItem("Hỏi-Đáp (RAG)"):
|
228 |
-
rag_query = gr.Textbox(label="Nhập câu hỏi", placeholder="Vd: Vượt đèn đỏ phạt bao nhiêu tiền?")
|
229 |
-
rag_button = gr.Button("Gửi", variant="primary")
|
230 |
-
rag_answer = gr.Textbox(label="Câu trả lời", interactive=False, lines=7)
|
231 |
-
with gr.Accordion("Xem ngữ cảnh đã sử dụng", open=False):
|
232 |
-
rag_context = gr.Markdown()
|
233 |
-
|
234 |
-
retriever_button.click(fn=retriever_interface, inputs=retriever_query, outputs=retriever_output)
|
235 |
-
rag_button.click(fn=rag_interface, inputs=rag_query, outputs=[rag_answer, rag_context])
|
236 |
|
237 |
if __name__ == "__main__":
|
238 |
-
demo.launch()
|
|
|
1 |
+
# file: app.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import gradio as gr
|
3 |
+
import time
|
4 |
+
|
5 |
+
from rag_pipeline import initialize_components, generate_response
|
6 |
+
|
7 |
+
# --- KHỞI TẠO CÁC THÀNH PHẦN (CHỈ CHẠY 1 LẦN) ---
|
8 |
+
start_time = time.time()
|
9 |
+
print("Bắt đầu khởi tạo ứng dụng Chatbot Luật Giao thông...")
|
10 |
+
DATA_PATH = "data/luat_chi_tiet_output_openai_sdk_final_cleaned.json"
|
11 |
+
COMPONENTS = initialize_components(DATA_PATH)
|
12 |
+
end_time = time.time()
|
13 |
+
print(f"✅ Ứng dụng đã sẵn sàng! Thời gian khởi tạo: {end_time - start_time:.2f} giây.")
|
14 |
+
# ----------------------------------------------------
|
15 |
+
|
16 |
+
|
17 |
+
def chat_interface(query, history):
|
18 |
+
"""
|
19 |
+
Hàm xử lý logic cho giao diện chat của Gradio.
|
20 |
+
"""
|
21 |
+
print(f"Nhận được câu hỏi từ người dùng: '{query}'")
|
22 |
+
# Gọi hàm generate_response với query và các thành phần đã được khởi tạo
|
23 |
+
response = generate_response(query, COMPONENTS)
|
24 |
+
return response
|
25 |
+
|
26 |
+
# --- GIAO DIỆN GRADIO ---
|
27 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="Chatbot Luật Giao thông Việt Nam") as demo:
|
28 |
+
gr.Markdown(
|
29 |
+
"""
|
30 |
+
# ⚖️ Chatbot Luật Giao thông Việt Nam
|
31 |
+
Hỏi đáp về các quy định, mức phạt trong luật giao thông đường bộ dựa trên cơ sở dữ liệu được cung cấp.
|
32 |
+
*Lưu ý: Đây là một sản phẩm demo. Thông tin chỉ mang tính chất tham khảo.*
|
33 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
|
36 |
+
chatbot = gr.Chatbot(label="Chatbot", height=500)
|
37 |
+
msg = gr.Textbox(label="Nhập câu hỏi của bạn", placeholder="Ví dụ: Vượt đèn đỏ bị phạt bao nhiêu tiền?")
|
38 |
+
clear = gr.ClearButton([msg, chatbot])
|
39 |
+
|
40 |
+
def respond(message, chat_history):
|
41 |
+
bot_message = chat_interface(message, chat_history)
|
42 |
+
chat_history.append((message, bot_message))
|
43 |
+
return "", chat_history
|
44 |
+
|
45 |
+
msg.submit(respond, [msg, chatbot], [msg, chatbot])
|
46 |
+
|
47 |
+
gr.Examples(
|
48 |
+
examples=[
|
49 |
+
"Phương tiện giao thông đường bộ gồm những loại nào?",
|
50 |
+
"Vượt đèn đỏ phạt bao nhiêu tiền đối với xe máy?",
|
51 |
+
"Nồng độ cồn cho phép khi lái xe ô tô là bao nhiêu?",
|
52 |
+
"Đi sai làn đường bị trừ mấy điểm bằng lái?",
|
53 |
+
],
|
54 |
+
inputs=msg
|
55 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
if __name__ == "__main__":
|
58 |
+
demo.launch()
|
data_processor.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# file: data_processor.py
|
2 |
+
import json
|
3 |
+
|
4 |
+
def process_law_data_to_chunks(structured_data_input):
|
5 |
+
"""
|
6 |
+
Xử lý dữ liệu luật từ cấu trúc JSON lồng nhau thành một danh sách phẳng các chunks.
|
7 |
+
Mỗi chunk chứa text và metadata tương ứng.
|
8 |
+
"""
|
9 |
+
flat_list = []
|
10 |
+
|
11 |
+
# Đảm bảo đầu vào là một danh sách các điều luật (articles)
|
12 |
+
if isinstance(structured_data_input, dict) and "article" in structured_data_input:
|
13 |
+
articles_list = [structured_data_input]
|
14 |
+
elif isinstance(structured_data_input, list):
|
15 |
+
articles_list = structured_data_input
|
16 |
+
else:
|
17 |
+
print("Lỗi: Dữ liệu đầu vào không phải là danh sách các Điều luật hoặc một đối tượng Điều luật.")
|
18 |
+
return flat_list
|
19 |
+
|
20 |
+
for article_data in articles_list:
|
21 |
+
if not isinstance(article_data, dict):
|
22 |
+
print(f"Cảnh báo: Bỏ qua một mục trong danh sách điều luật vì không phải là dictionary: {article_data}")
|
23 |
+
continue
|
24 |
+
|
25 |
+
article_metadata_base = {
|
26 |
+
"source_document": article_data.get("source_document"),
|
27 |
+
"article": article_data.get("article"),
|
28 |
+
"article_title": article_data.get("article_title")
|
29 |
+
}
|
30 |
+
|
31 |
+
clauses = article_data.get("clauses", [])
|
32 |
+
if not isinstance(clauses, list):
|
33 |
+
print(f"Cảnh báo: 'clauses' trong điều {article_metadata_base.get('article')} không phải là danh sách. Bỏ qua.")
|
34 |
+
continue
|
35 |
+
|
36 |
+
for clause_data in clauses:
|
37 |
+
if not isinstance(clause_data, dict):
|
38 |
+
print(f"Cảnh báo: Bỏ qua một mục trong 'clauses' vì không phải là dictionary: {clause_data}")
|
39 |
+
continue
|
40 |
+
|
41 |
+
clause_metadata_base = article_metadata_base.copy()
|
42 |
+
clause_metadata_base.update({
|
43 |
+
"clause_number": clause_data.get("clause_number"),
|
44 |
+
"clause_metadata_summary": clause_data.get("clause_metadata_summary")
|
45 |
+
})
|
46 |
+
|
47 |
+
points_in_clause = clause_data.get("points_in_clause", [])
|
48 |
+
if not isinstance(points_in_clause, list):
|
49 |
+
print(f"Cảnh báo: 'points_in_clause' trong khoản {clause_metadata_base.get('clause_number')} của điều {article_metadata_base.get('article')} không phải là danh sách. Bỏ qua.")
|
50 |
+
continue
|
51 |
+
|
52 |
+
if points_in_clause:
|
53 |
+
for point_data in points_in_clause:
|
54 |
+
if not isinstance(point_data, dict):
|
55 |
+
print(f"Cảnh báo: Bỏ qua một mục trong 'points_in_clause' vì không phải là dictionary: {point_data}")
|
56 |
+
continue
|
57 |
+
|
58 |
+
chunk_text = point_data.get("point_text_original") or point_data.get("violation_description_summary")
|
59 |
+
if not chunk_text:
|
60 |
+
continue
|
61 |
+
|
62 |
+
current_point_metadata = clause_metadata_base.copy()
|
63 |
+
point_specific_metadata = point_data.copy()
|
64 |
+
if "point_text_original" in point_specific_metadata:
|
65 |
+
del point_specific_metadata["point_text_original"]
|
66 |
+
|
67 |
+
current_point_metadata.update(point_specific_metadata)
|
68 |
+
final_metadata_cleaned = {k: v for k, v in current_point_metadata.items() if v is not None}
|
69 |
+
|
70 |
+
flat_list.append({"text": chunk_text, "metadata": final_metadata_cleaned})
|
71 |
+
else:
|
72 |
+
chunk_text = clause_data.get("clause_text_original")
|
73 |
+
if chunk_text:
|
74 |
+
current_clause_metadata = clause_metadata_base.copy()
|
75 |
+
additional_clause_info = {}
|
76 |
+
for key, value in clause_data.items():
|
77 |
+
if key not in ["clause_text_original", "points_in_clause", "clause_number", "clause_metadata_summary"]:
|
78 |
+
additional_clause_info[key] = value
|
79 |
+
|
80 |
+
if additional_clause_info:
|
81 |
+
current_clause_metadata.update(additional_clause_info)
|
82 |
+
|
83 |
+
final_metadata_cleaned = {k: v for k, v in current_clause_metadata.items() if v is not None}
|
84 |
+
flat_list.append({"text": chunk_text, "metadata": final_metadata_cleaned})
|
85 |
+
|
86 |
+
return flat_list
|
llm_handler.py → rag_pipeline.py
RENAMED
File without changes
|
retrieval_handler.py → retriever.py
RENAMED
File without changes
|