beyoru commited on
Commit
f0d82b5
·
verified ·
1 Parent(s): f316e3b

Update app.py

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