BaoNhan commited on
Commit
854ceef
·
verified ·
1 Parent(s): 2db91dc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +100 -35
app.py CHANGED
@@ -1,58 +1,119 @@
 
1
  import numpy as np
2
  import traceback
3
  import torch
 
 
4
  from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
5
  from langchain_huggingface import HuggingFaceEmbeddings
6
- from openai import OpenAI
7
- import time
8
- import os
9
 
10
- api_key = os.environ.get("GEMINI_API_KEY")
11
 
12
- if not api_key:
13
- raise ValueError("❌ Lỗi: Không tìm thấy GEMINI_API_KEY. Vui lòng cấu hình trong Settings -> Secrets.")
 
 
 
14
 
15
- client = OpenAI(
16
- api_key=api_key,
17
- base_url="https://generativelanguage.googleapis.com/v1beta/openai/"
 
 
18
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  def md_to_kb_safe(md_text, embedding_model_name="sentence-transformers/all-MiniLM-L6-v2"):
21
  try:
22
- headers_to_split_on = [("#", "Header 1"), ("##", "Header 2"), ("###", "Header 3")]
 
 
 
 
23
  splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
24
  md_chunks = splitter.split_text(md_text)
25
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200, length_function=len)
 
 
 
 
 
26
  final_chunks = text_splitter.split_documents(md_chunks)
 
27
  texts = [doc.page_content for doc in final_chunks]
28
- device = "cuda" if torch.cuda.is_available() and torch.cuda.memory_allocated() < 2_000_000_000 else "cpu"
29
- embedding_model = HuggingFaceEmbeddings(model_name=embedding_model_name, model_kwargs={"device": device})
 
 
 
 
 
30
  vectors = embedding_model.embed_documents(texts)
 
31
  kb = [{"text": texts[i], "vector": vectors[i]} for i in range(len(texts))]
32
- return {"success": True, "num_chunks": len(final_chunks), "kb": kb, "embed_model": embedding_model}
 
 
 
 
 
 
 
33
  except Exception as e:
34
- return {"success": False, "error": str(e), "traceback": traceback.format_exc()}
 
 
 
 
 
35
 
36
  def cosine_similarity(v1, v2):
37
  return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
38
 
 
39
  def semantic_search(query, embed_model, kb, top_k=3):
40
  t0 = time.time()
41
  q_vec = np.array(embed_model.embed_query(query))
42
- scores = [(cosine_similarity(q_vec, item["vector"]), item["text"]) for item in kb]
 
 
 
 
43
  scores.sort(reverse=True, key=lambda x: x[0])
 
44
  return scores[:top_k], time.time() - t0
45
 
 
46
  def build_context(results):
47
  ctx = ""
48
  for i, (score, chunk) in enumerate(results):
49
  ctx += f"=== Context {i+1} ===\n{chunk}\n\n"
50
  return ctx
51
 
 
52
  def rag_answer(query, embed_model, kb):
53
  t0 = time.time()
 
54
  results, t_semantic = semantic_search(query, embed_model, kb, top_k=3)
55
  context = build_context(results)
 
56
  prompt = f"""Use ONLY the information in the following context.
57
 
58
  {context}
@@ -62,19 +123,15 @@ Question: {query}
62
  If the answer is not in the context, respond EXACTLY with:
63
  "I do not have enough information to answer that."
64
  """
65
- response = client.chat.completions.create(
66
- model="gemini-2.5-pro",
67
- temperature=0,
68
- messages=[
69
- {"role": "system", "content": "Answer strictly using the context."},
70
- {"role": "user", "content": prompt}
71
- ]
72
- )
73
- answer = response.choices[0].message.content
74
  return answer, t_semantic, time.time() - t0
75
 
 
76
  def evaluate_ai(response, true_answer):
77
  t0 = time.time()
 
78
  eval_prompt = f"""
79
  AI Response: {response}
80
  Ground Truth: {true_answer}
@@ -83,27 +140,35 @@ Rules:
83
  - 1 = very close to true answer
84
  - 0.5 = partially correct
85
  - 0 = incorrect
 
 
86
  """
87
- response = client.chat.completions.create(
88
- model="gemini-2.5-pro",
89
- temperature=0,
90
- messages=[
91
- {"role": "system", "content": "You are an evaluation system."},
92
- {"role": "user", "content": eval_prompt}
93
- ]
94
- )
95
- return response.choices[0].message.content, time.time() - t0
96
 
97
  def run_rag_pipeline(md_text_input, query, true_answer):
98
  kb_result = md_to_kb_safe(md_text_input)
 
99
  if not kb_result["success"]:
100
  return f"Error creating KB:\n{kb_result['error']}", None, None
 
101
  kb = kb_result["kb"]
102
  embed_model = kb_result["embed_model"]
 
103
  answer, t_semantic, t_rag = rag_answer(query, embed_model, kb)
104
  score, t_eval = evaluate_ai(answer, true_answer)
105
- timings = f"Semantic Search: {t_semantic:.2f}s | LLM Answer: {t_rag:.2f}s | Evaluation: {t_eval:.2f}s"
 
 
 
 
 
 
106
  return answer, score, timings
 
107
  import base64
108
  import os
109
  import re
 
1
+ # --- RAG / Semantic Search imports ---
2
  import numpy as np
3
  import traceback
4
  import torch
5
+ import time
6
+
7
  from langchain_text_splitters import MarkdownHeaderTextSplitter, RecursiveCharacterTextSplitter
8
  from langchain_huggingface import HuggingFaceEmbeddings
 
 
 
9
 
10
+ from transformers import AutoTokenizer, AutoModelForCausalLM
11
 
12
+ # =========================================================
13
+ # LLM CONFIG (HuggingFace local)
14
+ # =========================================================
15
+ LLM_NAME = "Qwen/Qwen2.5-7B-Instruct" # đổi nếu GPU mạnh hơn
16
+ DTYPE = torch.float16 if torch.cuda.is_available() else torch.float32
17
 
18
+ tokenizer = AutoTokenizer.from_pretrained(LLM_NAME)
19
+ llm_model = AutoModelForCausalLM.from_pretrained(
20
+ LLM_NAME,
21
+ torch_dtype=DTYPE,
22
+ device_map="auto"
23
  )
24
+ llm_model.eval()
25
+
26
+
27
+ def _llm_generate(prompt, max_new_tokens=256):
28
+ inputs = tokenizer(prompt, return_tensors="pt").to(llm_model.device)
29
+ with torch.no_grad():
30
+ outputs = llm_model.generate(
31
+ **inputs,
32
+ max_new_tokens=max_new_tokens,
33
+ do_sample=False,
34
+ temperature=0
35
+ )
36
+ return tokenizer.decode(outputs[0], skip_special_tokens=True)
37
+
38
+
39
+ # =========================================================
40
+ # --- Functions for RAG (GIỮ NGUYÊN TÊN HÀM)
41
+ # =========================================================
42
 
43
  def md_to_kb_safe(md_text, embedding_model_name="sentence-transformers/all-MiniLM-L6-v2"):
44
  try:
45
+ headers_to_split_on = [
46
+ ("#", "Header 1"),
47
+ ("##", "Header 2"),
48
+ ("###", "Header 3"),
49
+ ]
50
  splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
51
  md_chunks = splitter.split_text(md_text)
52
+
53
+ text_splitter = RecursiveCharacterTextSplitter(
54
+ chunk_size=1000,
55
+ chunk_overlap=200,
56
+ length_function=len,
57
+ )
58
  final_chunks = text_splitter.split_documents(md_chunks)
59
+
60
  texts = [doc.page_content for doc in final_chunks]
61
+
62
+ device = "cuda" if torch.cuda.is_available() else "cpu"
63
+ embedding_model = HuggingFaceEmbeddings(
64
+ model_name=embedding_model_name,
65
+ model_kwargs={"device": device},
66
+ )
67
+
68
  vectors = embedding_model.embed_documents(texts)
69
+
70
  kb = [{"text": texts[i], "vector": vectors[i]} for i in range(len(texts))]
71
+
72
+ return {
73
+ "success": True,
74
+ "num_chunks": len(final_chunks),
75
+ "kb": kb,
76
+ "embed_model": embedding_model,
77
+ }
78
+
79
  except Exception as e:
80
+ return {
81
+ "success": False,
82
+ "error": str(e),
83
+ "traceback": traceback.format_exc(),
84
+ }
85
+
86
 
87
  def cosine_similarity(v1, v2):
88
  return np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
89
 
90
+
91
  def semantic_search(query, embed_model, kb, top_k=3):
92
  t0 = time.time()
93
  q_vec = np.array(embed_model.embed_query(query))
94
+
95
+ scores = [
96
+ (cosine_similarity(q_vec, item["vector"]), item["text"])
97
+ for item in kb
98
+ ]
99
  scores.sort(reverse=True, key=lambda x: x[0])
100
+
101
  return scores[:top_k], time.time() - t0
102
 
103
+
104
  def build_context(results):
105
  ctx = ""
106
  for i, (score, chunk) in enumerate(results):
107
  ctx += f"=== Context {i+1} ===\n{chunk}\n\n"
108
  return ctx
109
 
110
+
111
  def rag_answer(query, embed_model, kb):
112
  t0 = time.time()
113
+
114
  results, t_semantic = semantic_search(query, embed_model, kb, top_k=3)
115
  context = build_context(results)
116
+
117
  prompt = f"""Use ONLY the information in the following context.
118
 
119
  {context}
 
123
  If the answer is not in the context, respond EXACTLY with:
124
  "I do not have enough information to answer that."
125
  """
126
+
127
+ answer = _llm_generate(prompt, max_new_tokens=256)
128
+
 
 
 
 
 
 
129
  return answer, t_semantic, time.time() - t0
130
 
131
+
132
  def evaluate_ai(response, true_answer):
133
  t0 = time.time()
134
+
135
  eval_prompt = f"""
136
  AI Response: {response}
137
  Ground Truth: {true_answer}
 
140
  - 1 = very close to true answer
141
  - 0.5 = partially correct
142
  - 0 = incorrect
143
+
144
+ Answer ONLY one number: 0, 0.5, or 1
145
  """
146
+
147
+ score = _llm_generate(eval_prompt, max_new_tokens=8)
148
+
149
+ return score.strip(), time.time() - t0
150
+
 
 
 
 
151
 
152
  def run_rag_pipeline(md_text_input, query, true_answer):
153
  kb_result = md_to_kb_safe(md_text_input)
154
+
155
  if not kb_result["success"]:
156
  return f"Error creating KB:\n{kb_result['error']}", None, None
157
+
158
  kb = kb_result["kb"]
159
  embed_model = kb_result["embed_model"]
160
+
161
  answer, t_semantic, t_rag = rag_answer(query, embed_model, kb)
162
  score, t_eval = evaluate_ai(answer, true_answer)
163
+
164
+ timings = (
165
+ f"Semantic Search: {t_semantic:.2f}s | "
166
+ f"LLM Answer: {t_rag:.2f}s | "
167
+ f"Evaluation: {t_eval:.2f}s"
168
+ )
169
+
170
  return answer, score, timings
171
+
172
  import base64
173
  import os
174
  import re