ogx786 commited on
Commit
3a3782d
·
verified ·
1 Parent(s): 9c6889b

Update chatbot.py

Browse files
Files changed (1) hide show
  1. chatbot.py +41 -60
chatbot.py CHANGED
@@ -5,11 +5,10 @@ import pickle
5
  import argparse
6
  import traceback
7
 
8
- os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
9
  import torch
10
  import faiss
11
  from sentence_transformers import SentenceTransformer
12
- from transformers import AutoModelForCausalLM, AutoTokenizer
13
 
14
  # ---------------------------------------------------------------------------
15
  # Defaults — edit these if you don't want to pass CLI flags every time
@@ -17,7 +16,7 @@ from transformers import AutoModelForCausalLM, AutoTokenizer
17
  DEFAULT_FAISS_INDEX_PATH = "./hbl_site_index_COMPLETE.faiss"
18
  DEFAULT_CHUNKS_METADATA_PATH = "./hbl_site_metadata_COMPLETE.pkl"
19
  DEFAULT_EMBED_MODEL_PATH = "./bge-m3"
20
- DEFAULT_LLM_MODEL_PATH = "./qwen2.5-3b-instruct"
21
 
22
  RETRIEVAL_TOP_K = 4
23
  RETRIEVAL_MIN_SCORE = 0.55
@@ -34,18 +33,18 @@ CONTEXT:
34
  {context}"""
35
 
36
  CODE_PATTERNS = [
37
- r"```", # any fenced code block
38
- r"\bdef\s+\w+\s*\(", # python function def
39
- r"\bimport\s+\w+", # import statements
40
- r"\bfunction\s+\w+\s*\(", # js function
41
  r"\bconsole\.log\(",
42
  r"\bprint\(",
43
  r"\breturn\s+\w+",
44
  ]
45
 
46
  MATH_PATTERNS = [
47
- r"^\s*-?\d+(\.\d+)?\s*[\+\-\*/x×]\s*-?\d+(\.\d+)?", # "6+7", "12 * 3", "6 x 7"
48
- r"\bwhat\s+is\s+\d+.{0,15}[\+\-\*/].{0,15}\d+", # "what is 6 plus 7" style
49
  r"\bcalculate\s+\d+.{0,15}\d+",
50
  r"\bsolve\s+(this|the)?\s*(equation|expression|problem)\b",
51
  ]
@@ -69,9 +68,7 @@ def is_writing_task(message):
69
  def strip_or_block(answer):
70
  if not contains_code(answer):
71
  return answer
72
- # Remove fenced code blocks
73
  cleaned = re.sub(r"```.*?```", "\x00CODE_REMOVED\x00", answer, flags=re.DOTALL)
74
- # Split into sentences/lines and drop ones that reference the removed code
75
  lines = cleaned.split("\n")
76
  result_lines = []
77
  reference_phrases = [
@@ -83,9 +80,9 @@ def strip_or_block(answer):
83
  for line in lines:
84
  low = line.lower()
85
  if "\x00CODE_REMOVED\x00" in line:
86
- continue # drop the marker line entirely
87
  if any(p in low for p in reference_phrases):
88
- continue # drop sentences describing the removed code
89
  result_lines.append(line)
90
  cleaned = "\n".join(result_lines).strip()
91
  cleaned += ("\n\n*(Note: I can explain loan interest calculations in plain language "
@@ -105,11 +102,16 @@ def format_chunks_display(retrieved):
105
  # Main
106
  # ---------------------------------------------------------------------------
107
  def parse_args():
108
- p = argparse.ArgumentParser(description="HBL Internal Assistant (RAG chatbot) — terminal version")
109
  p.add_argument("--faiss-index", default=os.environ.get("FAISS_INDEX_PATH", DEFAULT_FAISS_INDEX_PATH))
110
  p.add_argument("--chunks-metadata", default=os.environ.get("CHUNKS_METADATA_PATH", DEFAULT_CHUNKS_METADATA_PATH))
111
  p.add_argument("--embed-model", default=os.environ.get("EMBED_MODEL_PATH", DEFAULT_EMBED_MODEL_PATH))
112
- p.add_argument("--llm-model", default=os.environ.get("LLM_MODEL_PATH", DEFAULT_LLM_MODEL_PATH))
 
 
 
 
 
113
  p.add_argument("--show-chunks", action="store_true", help="Print retrieved chunks before each answer")
114
  return p.parse_args()
115
 
@@ -119,14 +121,7 @@ def main():
119
  assert os.path.exists(args.faiss_index), f"FAISS index not found: {args.faiss_index}"
120
  assert os.path.exists(args.chunks_metadata), f"Chunks metadata not found: {args.chunks_metadata}"
121
  assert os.path.exists(args.embed_model), f"Embedding model folder not found: {args.embed_model}"
122
- assert os.path.exists(args.llm_model), f"LLM model folder not found: {args.llm_model}"
123
-
124
- device = "cuda" if torch.cuda.is_available() else "cpu"
125
- print(f"Using device: {device}")
126
-
127
- if device == "cpu":
128
- torch.set_num_threads(4)
129
- torch.set_num_interop_threads(4)
130
 
131
  print("Loading FAISS index...")
132
  index = faiss.read_index(args.faiss_index)
@@ -138,32 +133,18 @@ def main():
138
  print(f"Loaded {index.ntotal} vectors, {len(chunks)} chunks.")
139
 
140
  print(f"Loading embedding model from {args.embed_model}...")
141
- embed_model = SentenceTransformer(args.embed_model, device=device)
142
  print("Retrieval assets ready.")
143
 
144
- print(f"Loading LLM from {args.llm_model}...")
145
- tokenizer = AutoTokenizer.from_pretrained(args.llm_model)
146
-
147
- if device == "cuda":
148
- llm_model = AutoModelForCausalLM.from_pretrained(
149
- args.llm_model,
150
- torch_dtype=torch.bfloat16,
151
- low_cpu_mem_usage=True,
152
- local_files_only=True,
153
- use_safetensors=True,
154
- device_map="auto",
155
- )
156
- else:
157
- llm_model = AutoModelForCausalLM.from_pretrained(
158
- args.llm_model,
159
- torch_dtype=torch.bfloat16,
160
- low_cpu_mem_usage=True,
161
- local_files_only=True,
162
- use_safetensors=True,
163
- )
164
- llm_model = llm_model.to("cpu")
165
-
166
- print("LLM ready.\n")
167
 
168
  def retrieve(query, k=RETRIEVAL_TOP_K, min_score=RETRIEVAL_MIN_SCORE):
169
  t0 = time.time()
@@ -178,22 +159,22 @@ def main():
178
  print(f"[timing] retrieve() {time.time() - t0:.2f}s, {len(results)} chunks")
179
  return results
180
 
181
- def call_llm_with_history(system_prompt, history, current_message, max_new_tokens=400):
 
182
  messages = [{"role": "system", "content": system_prompt}]
183
  messages += history
184
  messages.append({"role": "user", "content": current_message})
185
- prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
186
- inputs = tokenizer(prompt, return_tensors="pt").to(device)
187
  t0 = time.time()
188
- outputs = llm_model.generate(
189
- **inputs,
190
- max_new_tokens=max_new_tokens,
191
- temperature=0.3,
192
- do_sample=True,
193
- pad_token_id=tokenizer.eos_token_id,
194
  )
195
  print(f"[timing] generate_response() {time.time() - t0:.2f}s")
196
- return tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True).strip()
197
 
198
  def chatbot_respond(message, history):
199
  try:
@@ -215,11 +196,11 @@ def main():
215
  return f"⚠️ Internal error: {e}", "*Error occurred*"
216
 
217
  # ---------------------------------------------------------------------
218
- # Terminal chat loop (replaces Gradio ChatInterface)
219
  # ---------------------------------------------------------------------
220
- history = [] # list of {"role": "user"/"assistant", "content": str}
221
  print("=" * 60)
222
- print("HBL Internal Assistant — terminal mode")
223
  print("Type your question and press Enter.")
224
  print("Commands: 'exit' or 'quit' to stop, 'reset' to clear history.")
225
  print("=" * 60 + "\n")
 
5
  import argparse
6
  import traceback
7
 
 
8
  import torch
9
  import faiss
10
  from sentence_transformers import SentenceTransformer
11
+ from llama_cpp import Llama
12
 
13
  # ---------------------------------------------------------------------------
14
  # Defaults — edit these if you don't want to pass CLI flags every time
 
16
  DEFAULT_FAISS_INDEX_PATH = "./hbl_site_index_COMPLETE.faiss"
17
  DEFAULT_CHUNKS_METADATA_PATH = "./hbl_site_metadata_COMPLETE.pkl"
18
  DEFAULT_EMBED_MODEL_PATH = "./bge-m3"
19
+ DEFAULT_LLM_GGUF_PATH = "./qwen2.5-0.5b-instruct-q5_k_m.gguf"
20
 
21
  RETRIEVAL_TOP_K = 4
22
  RETRIEVAL_MIN_SCORE = 0.55
 
33
  {context}"""
34
 
35
  CODE_PATTERNS = [
36
+ r"```",
37
+ r"\bdef\s+\w+\s*\(",
38
+ r"\bimport\s+\w+",
39
+ r"\bfunction\s+\w+\s*\(",
40
  r"\bconsole\.log\(",
41
  r"\bprint\(",
42
  r"\breturn\s+\w+",
43
  ]
44
 
45
  MATH_PATTERNS = [
46
+ r"^\s*-?\d+(\.\d+)?\s*[\+\-\*/x×]\s*-?\d+(\.\d+)?",
47
+ r"\bwhat\s+is\s+\d+.{0,15}[\+\-\*/].{0,15}\d+",
48
  r"\bcalculate\s+\d+.{0,15}\d+",
49
  r"\bsolve\s+(this|the)?\s*(equation|expression|problem)\b",
50
  ]
 
68
  def strip_or_block(answer):
69
  if not contains_code(answer):
70
  return answer
 
71
  cleaned = re.sub(r"```.*?```", "\x00CODE_REMOVED\x00", answer, flags=re.DOTALL)
 
72
  lines = cleaned.split("\n")
73
  result_lines = []
74
  reference_phrases = [
 
80
  for line in lines:
81
  low = line.lower()
82
  if "\x00CODE_REMOVED\x00" in line:
83
+ continue
84
  if any(p in low for p in reference_phrases):
85
+ continue
86
  result_lines.append(line)
87
  cleaned = "\n".join(result_lines).strip()
88
  cleaned += ("\n\n*(Note: I can explain loan interest calculations in plain language "
 
102
  # Main
103
  # ---------------------------------------------------------------------------
104
  def parse_args():
105
+ p = argparse.ArgumentParser(description="HBL Internal Assistant (RAG chatbot) — llama.cpp terminal version")
106
  p.add_argument("--faiss-index", default=os.environ.get("FAISS_INDEX_PATH", DEFAULT_FAISS_INDEX_PATH))
107
  p.add_argument("--chunks-metadata", default=os.environ.get("CHUNKS_METADATA_PATH", DEFAULT_CHUNKS_METADATA_PATH))
108
  p.add_argument("--embed-model", default=os.environ.get("EMBED_MODEL_PATH", DEFAULT_EMBED_MODEL_PATH))
109
+ p.add_argument("--llm-gguf", default=os.environ.get("LLM_GGUF_PATH", DEFAULT_LLM_GGUF_PATH),
110
+ help="Path to the .gguf model file")
111
+ p.add_argument("--n-threads", type=int, default=os.cpu_count(),
112
+ help="CPU threads for llama.cpp to use (default: all logical cores)")
113
+ p.add_argument("--n-ctx", type=int, default=4096, help="Context window size")
114
+ p.add_argument("--max-tokens", type=int, default=300, help="Max tokens to generate per response")
115
  p.add_argument("--show-chunks", action="store_true", help="Print retrieved chunks before each answer")
116
  return p.parse_args()
117
 
 
121
  assert os.path.exists(args.faiss_index), f"FAISS index not found: {args.faiss_index}"
122
  assert os.path.exists(args.chunks_metadata), f"Chunks metadata not found: {args.chunks_metadata}"
123
  assert os.path.exists(args.embed_model), f"Embedding model folder not found: {args.embed_model}"
124
+ assert os.path.exists(args.llm_gguf), f"GGUF model file not found: {args.llm_gguf}"
 
 
 
 
 
 
 
125
 
126
  print("Loading FAISS index...")
127
  index = faiss.read_index(args.faiss_index)
 
133
  print(f"Loaded {index.ntotal} vectors, {len(chunks)} chunks.")
134
 
135
  print(f"Loading embedding model from {args.embed_model}...")
136
+ embed_model = SentenceTransformer(args.embed_model, device="cpu")
137
  print("Retrieval assets ready.")
138
 
139
+ print(f"Loading LLM (GGUF) from {args.llm_gguf} with {args.n_threads} threads...")
140
+ t0 = time.time()
141
+ llm = Llama(
142
+ model_path=args.llm_gguf,
143
+ n_ctx=args.n_ctx,
144
+ n_threads=args.n_threads,
145
+ verbose=False,
146
+ )
147
+ print(f"LLM ready in {time.time() - t0:.2f}s.\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
  def retrieve(query, k=RETRIEVAL_TOP_K, min_score=RETRIEVAL_MIN_SCORE):
150
  t0 = time.time()
 
159
  print(f"[timing] retrieve() {time.time() - t0:.2f}s, {len(results)} chunks")
160
  return results
161
 
162
+ def call_llm_with_history(system_prompt, history, current_message, max_new_tokens=None):
163
+ max_new_tokens = max_new_tokens or args.max_tokens
164
  messages = [{"role": "system", "content": system_prompt}]
165
  messages += history
166
  messages.append({"role": "user", "content": current_message})
167
+
 
168
  t0 = time.time()
169
+ # llama-cpp-python's create_chat_completion applies the model's chat
170
+ # template internally, same role as tokenizer.apply_chat_template before.
171
+ result = llm.create_chat_completion(
172
+ messages=messages,
173
+ max_tokens=max_new_tokens,
174
+ temperature=0.0, # greedy-ish; set >0 if you want variation back
175
  )
176
  print(f"[timing] generate_response() {time.time() - t0:.2f}s")
177
+ return result["choices"][0]["message"]["content"].strip()
178
 
179
  def chatbot_respond(message, history):
180
  try:
 
196
  return f"⚠️ Internal error: {e}", "*Error occurred*"
197
 
198
  # ---------------------------------------------------------------------
199
+ # Terminal chat loop
200
  # ---------------------------------------------------------------------
201
+ history = []
202
  print("=" * 60)
203
+ print("HBL Internal Assistant — terminal mode (llama.cpp)")
204
  print("Type your question and press Enter.")
205
  print("Commands: 'exit' or 'quit' to stop, 'reset' to clear history.")
206
  print("=" * 60 + "\n")