beyoru commited on
Commit
56b44c6
·
verified ·
1 Parent(s): 073f3b1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +52 -121
app.py CHANGED
@@ -1,6 +1,5 @@
1
  import duckdb
2
  import pandas as pd
3
- from tabulate import tabulate
4
  import gradio as gr
5
  import torch
6
  from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
@@ -8,146 +7,83 @@ from sentence_transformers import SentenceTransformer, util
8
  import threading
9
 
10
  # --------------------------
11
- # Setup: Load data and models
12
  # --------------------------
13
 
14
- # Load Excel data into a pandas DataFrame.
15
  df = pd.read_excel("mau_bao_cao.xlsx")
16
 
17
- # (Optional) Create a DuckDB table if needed.
18
  conn = duckdb.connect('mau_bao_cao.db')
19
  conn.execute("""\
20
  CREATE TABLE IF NOT EXISTS production_data AS
21
  SELECT * FROM read_xlsx('mau_bao_cao.xlsx');
22
  """)
23
 
24
- # Load embedding model for computing embeddings.
 
 
 
25
  embedding_model = SentenceTransformer("intfloat/multilingual-e5-large-instruct")
26
  column_names = df.columns.tolist()
27
  column_embeddings = embedding_model.encode(column_names, convert_to_tensor=True)
28
  row_texts = df.apply(lambda row: " | ".join(row.astype(str)), axis=1)
29
  row_embeddings = embedding_model.encode(row_texts.tolist(), convert_to_tensor=True)
30
 
31
- # Load Qwen model and tokenizer for conversational generation.
32
- fc_model = AutoModelForCausalLM.from_pretrained(
33
- "Qwen/Qwen2.5-3B-Instruct",
34
- torch_dtype=torch.float16, # Sử dụng float16 để tiết kiệm VRAM
35
- device_map="cuda", # Ép mô hình chạy trên GPU
36
- trust_remote_code=True # Cho phép tải mã nguồn từ repo
37
- ).cuda() # Chắc chắn mô hình được chuyển lên GPU
38
-
39
  fc_tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen2.5-3B-Instruct')
40
 
41
- def extract_table(user_query: str):
42
- """
43
- Dựa trên câu truy vấn của người dùng:
44
- - Tính embedding cho câu truy vấn.
45
- - Lấy top k cột và top m dòng phù hợp.
46
- - Trả về DataFrame đã lọc, danh sách tên cột và bảng dạng text.
47
- """
48
- # Embed câu truy vấn
49
- question_embedding = embedding_model.encode(user_query, convert_to_tensor=True)
50
-
51
- # Lấy top 3 cột phù hợp
52
- k = 3
53
- column_similarities = util.cos_sim(question_embedding, column_embeddings)[0]
54
- best_column_indices = torch.topk(column_similarities, k).indices.tolist()
55
- best_column_names = [column_names[i] for i in best_column_indices]
56
-
57
- # Lấy top 10 dòng phù hợp
58
- row_similarities = util.cos_sim(question_embedding, row_embeddings).squeeze(0)
59
- m = 10
60
- best_row_indices = torch.topk(row_similarities, m).indices.tolist()
61
- filtered_df = df.iloc[best_row_indices][best_column_names]
62
-
63
- # Tạo bảng text (dùng cho prompt cho mô hình)
64
- table_text = tabulate(filtered_df, headers=best_column_names, tablefmt="grid")
65
-
66
- return filtered_df, best_column_names, table_text
67
-
68
  # --------------------------
69
- # Define the streaming generation function
70
  # --------------------------
71
 
72
  def generate_response(user_query: str):
73
  """
74
- A generator function that:
75
- 1. Embeds the query.
76
- 2. Selects top matching columns and rows from the data.
77
- 3. Prepares a system prompt with the extracted table.
78
- 4. Uses TextIteratorStreamer to stream the generated response.
79
- Yields the partial generated text as it is updated.
80
  """
81
- # 1. Embed the user query.
82
  question_embedding = embedding_model.encode(user_query, convert_to_tensor=True)
83
 
84
- # 2. Find best matching columns (top 10).
85
- k = 10
86
  column_similarities = util.cos_sim(question_embedding, column_embeddings)[0]
87
  best_column_indices = torch.topk(column_similarities, k).indices.tolist()
88
  best_column_names = [column_names[i] for i in best_column_indices]
89
 
90
- # 3. Select top matching rows (top 10).
91
  row_similarities = util.cos_sim(question_embedding, row_embeddings).squeeze(0)
92
  m = 10
93
  best_row_indices = torch.topk(row_similarities, m).indices.tolist()
94
  filtered_df = df.iloc[best_row_indices][best_column_names]
95
 
96
- # 4. Format the filtered data as a table.
97
- table_output = tabulate(filtered_df, headers=best_column_names, tablefmt="grid")
 
98
 
99
- # 5. Build the system prompt.
100
- system_prompt = f"""\
101
- Bạn là một trợ lý báo cáo sản xuất thông minh, chuyên phân tích tổng hợp dữ liệu một cách rõ ràng, dễ hiểu.
102
- **_Chỉ báo cáo nếu người dùng yêu cầu nếu không thì cứ giao tiếp bình thường với họ._**
103
-
104
- Dưới đây là dữ liệu bạn cần phân tích:
105
- 🔹 Các cột dữ liệu liên quan: {', '.join(best_column_names)}
106
- 🔹 Bảng dữ liệu:
107
- {table_output}
108
-
109
- 📌 Nhiệm vụ của bạn:
110
-
111
- Tóm tắt số liệu quan trọng, tránh liệt kê máy móc.
112
-
113
- Nhận xét về xu hướng và điểm bất thường.
114
-
115
- Nếu có thể, đề xuất giải pháp hoặc hành động tiếp theo.
116
-
117
- 📊 Cách trả lời:
118
- ✔️ Tự nhiên, dễ hiểu, không quá cứng nhắc.
119
- ✔️ Không cần nhắc lại bảng dữ liệu, hãy diễn giải nó.
120
- ✔️ Trả lời đúng trọng tâm, không dư thừa.
121
- ✔️ Nếu người dùng không hỏi về bảng dữ liệu, hãy chỉ giao tiếp bình thường.
122
- ✔️ Mô hình hóa dữ câu trả lời nếu cần thiết, giúp người dùng dễ hiểu hơn về câu trả lời.
123
-
124
- Ví dụ:
125
-
126
- 🔹 "Hôm nay, sản lượng đạt 95%, cao hơn 5% so với tuần trước."
127
-
128
- ⚠️ "Dây chuyền A đang giảm hiệu suất, cần theo dõi thêm."
129
-
130
- 🚀 "Nếu duy trì tốc độ này, sản lượng tháng có thể vượt kế hoạch 10%."
131
-
132
- 🚀 "Không có gì nếu bạn cần thêm thông tin chi tiết hãy nói cho tôi biết nhé ;))"
133
-
134
- Bạn đã sẵn sàng phân tích và đưa ra báo cáo!
135
- """
136
 
137
- # 6. Create the conversation messages.
138
  messages = [
139
  {'role': 'system', 'content': system_prompt},
140
  {'role': 'user', 'content': user_query}
141
  ]
142
 
143
- # 7. Prepare the prompt for the model.
144
  response_template = fc_tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
145
  response_inputs = fc_tokenizer(response_template, return_tensors="pt").to(fc_model.device)
146
 
147
- # 8. Use TextIteratorStreamer to yield tokens as they are generated.
148
  streamer = TextIteratorStreamer(fc_tokenizer, skip_prompt=True, skip_special_tokens=True)
149
 
150
- # Start generation in a separate thread so we can yield tokens as they arrive.
151
  thread = threading.Thread(
152
  target=lambda: fc_model.generate(
153
  **response_inputs,
@@ -159,48 +95,43 @@ Bạn đã sẵn sàng phân tích và đưa ra báo cáo!
159
  )
160
  thread.start()
161
 
162
- # 9. Yield tokens incrementally.
163
  collected_text = ""
164
  for new_text in streamer:
165
  collected_text += new_text
166
  yield collected_text
167
 
168
  # --------------------------
169
- # Build the Gradio conversation interface
170
  # --------------------------
171
 
172
  def chat_interface(user_message, history):
173
  """
174
- A generator function for Gradio that:
175
- - Updates the conversation history with the user message.
176
- - Streams the model's response token-by-token in real time.
177
- The history is maintained as a list of pairs [user_message, bot_response].
178
  """
179
- # Create a new conversation entry with user message and an empty bot response.
180
  history.append([user_message, ""])
181
- # Yield the initial state.
182
  yield "", history
183
-
184
- # Stream tokens from the generate_response generator.
185
  for partial_response in generate_response(user_message):
186
- # Update the latest conversation entry with the partial bot response.
187
  history[-1][1] = partial_response
188
  yield "", history
189
 
190
- with gr.Blocks() as demo:
191
- gr.Markdown("## Giao diện Chat với Streaming Hiển thị Bảng Dữ liệu")
192
- chatbot = gr.Chatbot()
193
- state = gr.State([]) # duy trì lịch sử chat dưới dạng danh sách các cặp
194
- table_display = gr.Dataframe(label="Bảng dữ liệu liên quan") # hiển thị bảng dữ liệu cho người dùng
195
-
196
- with gr.Row():
197
- txt = gr.Textbox(show_label=False, placeholder="Nhập câu hỏi c��a bạn...", container=False)
198
- send_btn = gr.Button("Gửi")
199
-
200
- # Cả submit của Textbox và click của nút gửi đều kích hoạt hàm chat_interface,
201
- # trả về 3 outputs: textbox, chatbot và table_display.
202
- txt.submit(chat_interface, inputs=[txt, state], outputs=[txt, chatbot, table_display], queue=True)
203
- send_btn.click(chat_interface, inputs=[txt, state], outputs=[txt, chatbot, table_display], queue=True)
204
-
205
 
206
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import duckdb
2
  import pandas as pd
 
3
  import gradio as gr
4
  import torch
5
  from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
 
7
  import threading
8
 
9
  # --------------------------
10
+ # Setup: Load dữ liệu và mô hình
11
  # --------------------------
12
 
13
+ # Đọc dữ liệu từ file Excel vào DataFrame
14
  df = pd.read_excel("mau_bao_cao.xlsx")
15
 
16
+ # Tạo bảng production_data trong DuckDB (nếu cần)
17
  conn = duckdb.connect('mau_bao_cao.db')
18
  conn.execute("""\
19
  CREATE TABLE IF NOT EXISTS production_data AS
20
  SELECT * FROM read_xlsx('mau_bao_cao.xlsx');
21
  """)
22
 
23
+ # Lấy mẫu bảng production_data để hiển thị (ở đây dùng 10 dòng đầu)
24
+ production_data_df = df.head(10)
25
+
26
+ # Load mô hình embedding để tính embedding cho cột và dòng dữ liệu
27
  embedding_model = SentenceTransformer("intfloat/multilingual-e5-large-instruct")
28
  column_names = df.columns.tolist()
29
  column_embeddings = embedding_model.encode(column_names, convert_to_tensor=True)
30
  row_texts = df.apply(lambda row: " | ".join(row.astype(str)), axis=1)
31
  row_embeddings = embedding_model.encode(row_texts.tolist(), convert_to_tensor=True)
32
 
33
+ # Load mô hình Qwen tokenizer cho việc tạo phản hồi
34
+ fc_model = AutoModelForCausalLM.from_pretrained('Qwen/Qwen2.5-3B-Instruct', torch_dtype=torch.float16, device_map="auto")
 
 
 
 
 
 
35
  fc_tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen2.5-3B-Instruct')
36
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  # --------------------------
38
+ # Hàm tạo phản hồi streaming theo thời gian thực
39
  # --------------------------
40
 
41
  def generate_response(user_query: str):
42
  """
43
+ Hàm này sẽ:
44
+ - Tính embedding cho câu truy vấn của người dùng.
45
+ - Chọn ra top 3 cột top 10 dòng phù hợp từ dữ liệu.
46
+ - Tạo system prompt bao gồm bảng dữ liệu đã được format bằng tabulate.
47
+ - Sử dụng TextIteratorStreamer để stream phản hồi từ mô hình theo thời gian thực.
 
48
  """
49
+ # Tính embedding cho câu truy vấn
50
  question_embedding = embedding_model.encode(user_query, convert_to_tensor=True)
51
 
52
+ # Chọn top 3 cột phù hợp
53
+ k = 3
54
  column_similarities = util.cos_sim(question_embedding, column_embeddings)[0]
55
  best_column_indices = torch.topk(column_similarities, k).indices.tolist()
56
  best_column_names = [column_names[i] for i in best_column_indices]
57
 
58
+ # Chọn top 10 dòng phù hợp
59
  row_similarities = util.cos_sim(question_embedding, row_embeddings).squeeze(0)
60
  m = 10
61
  best_row_indices = torch.topk(row_similarities, m).indices.tolist()
62
  filtered_df = df.iloc[best_row_indices][best_column_names]
63
 
64
+ # Format bảng dữ liệu sử dụng tabulate
65
+ from tabulate import tabulate
66
+ table_text = tabulate(filtered_df, headers=best_column_names, tablefmt="grid")
67
 
68
+ # Tạo system prompt chứa thông tin bảng dữ liệu
69
+ system_prompt = (
70
+ f"Bạn là một trợ lý AI thông minh, luôn trả lời bằng tiếng Việt.\n"
71
+ f"Dưới đây dữ liệu liên quan đến câu hỏi của bạn:\n\n"
72
+ f"🔹 **Các cột phù hợp**: {', '.join(best_column_names)}\n"
73
+ f"🔹 **Dữ liệu:**\n{table_text}"
74
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
 
76
  messages = [
77
  {'role': 'system', 'content': system_prompt},
78
  {'role': 'user', 'content': user_query}
79
  ]
80
 
 
81
  response_template = fc_tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
82
  response_inputs = fc_tokenizer(response_template, return_tensors="pt").to(fc_model.device)
83
 
84
+ # Dùng TextIteratorStreamer để stream phản hồi
85
  streamer = TextIteratorStreamer(fc_tokenizer, skip_prompt=True, skip_special_tokens=True)
86
 
 
87
  thread = threading.Thread(
88
  target=lambda: fc_model.generate(
89
  **response_inputs,
 
95
  )
96
  thread.start()
97
 
 
98
  collected_text = ""
99
  for new_text in streamer:
100
  collected_text += new_text
101
  yield collected_text
102
 
103
  # --------------------------
104
+ # Hàm giao diện chat
105
  # --------------------------
106
 
107
  def chat_interface(user_message, history):
108
  """
109
+ Hàm này sẽ:
110
+ - Thêm tin nhắn của người dùng vào lịch sử chat (dưới dạng cặp [tin nhắn người dùng, phản hồi AI]).
111
+ - Stream phản hồi từ hình theo thời gian thực và cập nhật lịch sử.
 
112
  """
 
113
  history.append([user_message, ""])
 
114
  yield "", history
 
 
115
  for partial_response in generate_response(user_message):
 
116
  history[-1][1] = partial_response
117
  yield "", history
118
 
119
+ # --------------------------
120
+ # Xây dựng giao diện Gradio với 2 tab: Chat Production Data Sample
121
+ # --------------------------
 
 
 
 
 
 
 
 
 
 
 
 
122
 
123
+ with gr.Blocks() as demo:
124
+ gr.Markdown("## Giao diện Chat và Hiển thị Bảng production_data Mẫu")
125
+ with gr.Tabs():
126
+ with gr.TabItem("Chat"):
127
+ chatbot = gr.Chatbot()
128
+ state = gr.State([])
129
+ with gr.Row():
130
+ txt = gr.Textbox(show_label=False, placeholder="Nhập câu hỏi của bạn...", container=False)
131
+ send_btn = gr.Button("Gửi")
132
+ txt.submit(chat_interface, inputs=[txt, state], outputs=[txt, chatbot], queue=True)
133
+ send_btn.click(chat_interface, inputs=[txt, state], outputs=[txt, chatbot], queue=True)
134
+ with gr.TabItem("Production Data Sample"):
135
+ gr.Markdown("Dưới đây là bảng **production_data** mẫu:")
136
+ production_table = gr.Dataframe(value=production_data_df, label="Production Data Sample")
137
+ demo.launch()