Spaces:
Paused
Paused
Commit
·
25c6eb9
1
Parent(s):
ab83281
updated
Browse files- chatbot/chatbot.py +135 -214
chatbot/chatbot.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
# codingo/chatbot/chatbot.py
|
2 |
-
"""Chatbot module for Codingo
|
3 |
|
4 |
import os
|
5 |
import shutil
|
@@ -9,7 +9,7 @@ import traceback
|
|
9 |
os.environ.setdefault("HF_HOME", "/tmp/huggingface")
|
10 |
os.environ.setdefault("TRANSFORMERS_CACHE", "/tmp/huggingface/transformers")
|
11 |
os.environ.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp/huggingface/hub")
|
12 |
-
os.environ["CUDA_LAUNCH_BLOCKING"] = "1"
|
13 |
|
14 |
_hf_model = None
|
15 |
_hf_tokenizer = None
|
@@ -20,15 +20,10 @@ _current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
20 |
_knowledge_base_path = os.path.join(_current_dir, "chatbot.txt")
|
21 |
_chroma_db_dir = "/tmp/chroma_db"
|
22 |
|
23 |
-
# Try a smaller, more reliable model for debugging
|
24 |
DEFAULT_MODEL_NAME = "microsoft/DialoGPT-small"
|
25 |
|
26 |
def _init_hf_model() -> None:
|
27 |
-
from transformers import
|
28 |
-
AutoModelForCausalLM,
|
29 |
-
AutoModelForSeq2SeqLM,
|
30 |
-
AutoTokenizer,
|
31 |
-
)
|
32 |
import torch
|
33 |
|
34 |
global _hf_model, _hf_tokenizer
|
@@ -42,51 +37,22 @@ def _init_hf_model() -> None:
|
|
42 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
43 |
print(f"Using device: {device}")
|
44 |
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
model = AutoModelForSeq2SeqLM.from_pretrained(
|
62 |
-
model_name,
|
63 |
-
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
|
64 |
-
low_cpu_mem_usage=True
|
65 |
-
)
|
66 |
-
model_type = "seq2seq"
|
67 |
-
print("Loaded as seq2seq model")
|
68 |
-
|
69 |
-
# Move model to device
|
70 |
-
model = model.to(device)
|
71 |
-
model.eval()
|
72 |
-
print("Model moved to device and set to eval mode")
|
73 |
-
|
74 |
-
# Configure padding token
|
75 |
-
if tokenizer.pad_token is None:
|
76 |
-
tokenizer.pad_token = tokenizer.eos_token
|
77 |
-
print(f"Set pad_token to: {tokenizer.pad_token}")
|
78 |
-
|
79 |
-
# Store model type
|
80 |
-
model.model_type = model_type
|
81 |
-
|
82 |
-
_hf_model = model
|
83 |
-
_hf_tokenizer = tokenizer
|
84 |
-
print("Model initialization complete")
|
85 |
-
|
86 |
-
except Exception as e:
|
87 |
-
print(f"Error during model initialization: {e}")
|
88 |
-
traceback.print_exc()
|
89 |
-
raise
|
90 |
|
91 |
def _init_vector_store() -> None:
|
92 |
global _chatbot_embedder, _chatbot_collection
|
@@ -95,70 +61,49 @@ def _init_vector_store() -> None:
|
|
95 |
|
96 |
print("Initializing vector store...")
|
97 |
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
from chromadb.config import Settings
|
103 |
-
|
104 |
-
# Clean up old database
|
105 |
-
shutil.rmtree(_chroma_db_dir, ignore_errors=True)
|
106 |
-
os.makedirs(_chroma_db_dir, exist_ok=True)
|
107 |
-
|
108 |
-
# Load knowledge base
|
109 |
-
try:
|
110 |
-
with open(_knowledge_base_path, encoding="utf-8") as f:
|
111 |
-
raw_text = f.read()
|
112 |
-
print(f"Loaded knowledge base with {len(raw_text)} characters")
|
113 |
-
except FileNotFoundError:
|
114 |
-
print("Knowledge base file not found, using default text")
|
115 |
-
raw_text = (
|
116 |
-
"Codingo is an AI-powered recruitment platform designed to "
|
117 |
-
"streamline job applications, candidate screening, and hiring. "
|
118 |
-
"We make hiring smarter, faster, and fairer through automation "
|
119 |
-
"and intelligent recommendations."
|
120 |
-
)
|
121 |
-
|
122 |
-
# Split text
|
123 |
-
splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=100)
|
124 |
-
docs = [doc.strip() for doc in splitter.split_text(raw_text) if doc.strip()]
|
125 |
-
print(f"Split into {len(docs)} documents")
|
126 |
-
|
127 |
-
# Initialize embedder
|
128 |
-
print("Loading sentence transformer...")
|
129 |
-
embedder = SentenceTransformer("all-MiniLM-L6-v2")
|
130 |
-
print("Encoding documents...")
|
131 |
-
embeddings = embedder.encode(docs, show_progress_bar=False, batch_size=32)
|
132 |
-
print(f"Created {len(embeddings)} embeddings")
|
133 |
-
|
134 |
-
# Initialize ChromaDB (use in-memory for HF Spaces)
|
135 |
-
print("Initializing ChromaDB...")
|
136 |
-
client = chromadb.Client(Settings(
|
137 |
-
anonymized_telemetry=False,
|
138 |
-
is_persistent=False, # Changed to False for HF Spaces
|
139 |
-
))
|
140 |
-
|
141 |
-
# Create collection
|
142 |
-
try:
|
143 |
-
client.delete_collection("chatbot")
|
144 |
-
except:
|
145 |
-
pass
|
146 |
-
|
147 |
-
collection = client.create_collection("chatbot")
|
148 |
-
|
149 |
-
# Add documents
|
150 |
-
ids = [f"doc_{i}" for i in range(len(docs))]
|
151 |
-
collection.add(documents=docs, embeddings=embeddings.tolist(), ids=ids)
|
152 |
-
print(f"Added {len(docs)} documents to collection")
|
153 |
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
162 |
|
163 |
def get_chatbot_response(query: str) -> str:
|
164 |
try:
|
@@ -167,24 +112,12 @@ def get_chatbot_response(query: str) -> str:
|
|
167 |
if not query or not query.strip():
|
168 |
return "Please type a question about the Codingo platform."
|
169 |
|
170 |
-
# Clear GPU cache
|
171 |
import torch
|
172 |
if torch.cuda.is_available():
|
173 |
torch.cuda.empty_cache()
|
174 |
-
print("Cleared GPU cache")
|
175 |
|
176 |
-
|
177 |
-
|
178 |
-
_init_vector_store()
|
179 |
-
except Exception as e:
|
180 |
-
print(f"Vector store initialization failed: {e}")
|
181 |
-
return "I'm having trouble accessing my knowledge base. Please try again later."
|
182 |
-
|
183 |
-
try:
|
184 |
-
_init_hf_model()
|
185 |
-
except Exception as e:
|
186 |
-
print(f"Model initialization failed: {e}")
|
187 |
-
return "I'm having trouble loading my language model. Please try again later."
|
188 |
|
189 |
embedder = _chatbot_embedder
|
190 |
collection = _chatbot_collection
|
@@ -192,102 +125,90 @@ def get_chatbot_response(query: str) -> str:
|
|
192 |
tokenizer = _hf_tokenizer
|
193 |
|
194 |
# Get relevant documents
|
195 |
-
print("Creating query embedding...")
|
196 |
query_embedding = embedder.encode([query])[0]
|
197 |
-
|
198 |
-
print("Searching for relevant documents...")
|
199 |
results = collection.query(query_embeddings=[query_embedding.tolist()], n_results=3)
|
200 |
retrieved_docs = results.get("documents", [[]])[0] if results else []
|
201 |
-
context = "\n".join(retrieved_docs[:3]) if retrieved_docs else ""
|
202 |
-
print(f"Retrieved {len(retrieved_docs)} documents")
|
203 |
|
204 |
-
|
205 |
-
if hasattr(model, 'model_type') and model.model_type == "seq2seq":
|
206 |
-
prompt = f"Context: {context}\n\nUser: {query}\nAssistant:"
|
207 |
-
else:
|
208 |
-
# For DialoGPT or other causal models
|
209 |
-
prompt = f"Context: {context}\n\nUser: {query}\nLUNA AI:"
|
210 |
|
211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
|
213 |
# Tokenize
|
214 |
-
|
215 |
-
|
216 |
-
inputs = tokenizer(
|
217 |
-
prompt,
|
218 |
-
return_tensors="pt",
|
219 |
-
truncation=True,
|
220 |
-
max_length=400, # Reduced for safety
|
221 |
-
padding=True,
|
222 |
-
return_attention_mask=True
|
223 |
-
)
|
224 |
-
print(f"Input shape: {inputs['input_ids'].shape}")
|
225 |
-
except Exception as e:
|
226 |
-
print(f"Tokenization error: {e}")
|
227 |
-
traceback.print_exc()
|
228 |
-
return "I had trouble processing your input. Please try a shorter question."
|
229 |
|
230 |
-
#
|
231 |
-
inputs = {k: v.to(model.device) for k, v in inputs.items()}
|
232 |
-
|
233 |
-
# Generate response
|
234 |
-
print("Generating response...")
|
235 |
with torch.no_grad():
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
eos_token_id=tokenizer.eos_token_id,
|
247 |
-
early_stopping=True,
|
248 |
-
)
|
249 |
-
print(f"Output shape: {output_ids.shape}")
|
250 |
-
except Exception as e:
|
251 |
-
print(f"Generation error: {e}")
|
252 |
-
traceback.print_exc()
|
253 |
-
|
254 |
-
# Try a simpler generation
|
255 |
-
try:
|
256 |
-
print("Trying simpler generation...")
|
257 |
-
output_ids = model.generate(
|
258 |
-
input_ids=inputs['input_ids'],
|
259 |
-
max_new_tokens=50,
|
260 |
-
pad_token_id=tokenizer.pad_token_id,
|
261 |
-
)
|
262 |
-
except Exception as e2:
|
263 |
-
print(f"Simple generation also failed: {e2}")
|
264 |
-
return "I'm having trouble generating a response. Please try again."
|
265 |
-
|
266 |
-
# Decode response
|
267 |
-
print("Decoding response...")
|
268 |
-
response = tokenizer.decode(output_ids[0], skip_special_tokens=True)
|
269 |
-
print(f"Raw response: {response[:100]}...")
|
270 |
-
|
271 |
-
# Clean up response
|
272 |
-
if "LUNA AI:" in response:
|
273 |
-
response = response.split("LUNA AI:")[-1].strip()
|
274 |
-
elif "Assistant:" in response:
|
275 |
-
response = response.split("Assistant:")[-1].strip()
|
276 |
-
|
277 |
-
# Remove the input if it's in the response
|
278 |
-
if query in response:
|
279 |
-
response = response.replace(query, "").strip()
|
280 |
-
|
281 |
-
# Final cleanup
|
282 |
-
response = response.strip()
|
283 |
|
284 |
-
|
285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
|
287 |
-
print(f"Final response: {response}")
|
288 |
return response
|
289 |
|
290 |
except Exception as e:
|
291 |
-
print(f"
|
292 |
traceback.print_exc()
|
293 |
-
return "I apologize
|
|
|
1 |
# codingo/chatbot/chatbot.py
|
2 |
+
"""Chatbot module for Codingo - Optimized for conversational AI"""
|
3 |
|
4 |
import os
|
5 |
import shutil
|
|
|
9 |
os.environ.setdefault("HF_HOME", "/tmp/huggingface")
|
10 |
os.environ.setdefault("TRANSFORMERS_CACHE", "/tmp/huggingface/transformers")
|
11 |
os.environ.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp/huggingface/hub")
|
12 |
+
os.environ["CUDA_LAUNCH_BLOCKING"] = "1"
|
13 |
|
14 |
_hf_model = None
|
15 |
_hf_tokenizer = None
|
|
|
20 |
_knowledge_base_path = os.path.join(_current_dir, "chatbot.txt")
|
21 |
_chroma_db_dir = "/tmp/chroma_db"
|
22 |
|
|
|
23 |
DEFAULT_MODEL_NAME = "microsoft/DialoGPT-small"
|
24 |
|
25 |
def _init_hf_model() -> None:
|
26 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
|
|
|
|
|
|
|
|
27 |
import torch
|
28 |
|
29 |
global _hf_model, _hf_tokenizer
|
|
|
37 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
38 |
print(f"Using device: {device}")
|
39 |
|
40 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
41 |
+
model = AutoModelForCausalLM.from_pretrained(
|
42 |
+
model_name,
|
43 |
+
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
|
44 |
+
low_cpu_mem_usage=True
|
45 |
+
)
|
46 |
+
|
47 |
+
model = model.to(device)
|
48 |
+
model.eval()
|
49 |
+
|
50 |
+
if tokenizer.pad_token is None:
|
51 |
+
tokenizer.pad_token = tokenizer.eos_token
|
52 |
+
|
53 |
+
_hf_model = model
|
54 |
+
_hf_tokenizer = tokenizer
|
55 |
+
print("Model initialization complete")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
def _init_vector_store() -> None:
|
58 |
global _chatbot_embedder, _chatbot_collection
|
|
|
61 |
|
62 |
print("Initializing vector store...")
|
63 |
|
64 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
65 |
+
from sentence_transformers import SentenceTransformer
|
66 |
+
import chromadb
|
67 |
+
from chromadb.config import Settings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
|
69 |
+
shutil.rmtree(_chroma_db_dir, ignore_errors=True)
|
70 |
+
os.makedirs(_chroma_db_dir, exist_ok=True)
|
71 |
+
|
72 |
+
try:
|
73 |
+
with open(_knowledge_base_path, encoding="utf-8") as f:
|
74 |
+
raw_text = f.read()
|
75 |
+
print(f"Loaded knowledge base with {len(raw_text)} characters")
|
76 |
+
except FileNotFoundError:
|
77 |
+
print("Knowledge base file not found, using default text")
|
78 |
+
raw_text = (
|
79 |
+
"Codingo is an AI-powered recruitment platform designed to "
|
80 |
+
"streamline job applications, candidate screening, and hiring."
|
81 |
+
)
|
82 |
+
|
83 |
+
splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=100)
|
84 |
+
docs = [doc.strip() for doc in splitter.split_text(raw_text) if doc.strip()]
|
85 |
+
print(f"Split into {len(docs)} documents")
|
86 |
+
|
87 |
+
embedder = SentenceTransformer("all-MiniLM-L6-v2")
|
88 |
+
embeddings = embedder.encode(docs, show_progress_bar=False, batch_size=32)
|
89 |
+
|
90 |
+
client = chromadb.Client(Settings(
|
91 |
+
anonymized_telemetry=False,
|
92 |
+
is_persistent=False, # In-memory for HF Spaces
|
93 |
+
))
|
94 |
+
|
95 |
+
try:
|
96 |
+
client.delete_collection("chatbot")
|
97 |
+
except:
|
98 |
+
pass
|
99 |
+
|
100 |
+
collection = client.create_collection("chatbot")
|
101 |
+
ids = [f"doc_{i}" for i in range(len(docs))]
|
102 |
+
collection.add(documents=docs, embeddings=embeddings.tolist(), ids=ids)
|
103 |
+
|
104 |
+
_chatbot_embedder = embedder
|
105 |
+
_chatbot_collection = collection
|
106 |
+
print("Vector store initialization complete")
|
107 |
|
108 |
def get_chatbot_response(query: str) -> str:
|
109 |
try:
|
|
|
112 |
if not query or not query.strip():
|
113 |
return "Please type a question about the Codingo platform."
|
114 |
|
|
|
115 |
import torch
|
116 |
if torch.cuda.is_available():
|
117 |
torch.cuda.empty_cache()
|
|
|
118 |
|
119 |
+
_init_vector_store()
|
120 |
+
_init_hf_model()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
|
122 |
embedder = _chatbot_embedder
|
123 |
collection = _chatbot_collection
|
|
|
125 |
tokenizer = _hf_tokenizer
|
126 |
|
127 |
# Get relevant documents
|
|
|
128 |
query_embedding = embedder.encode([query])[0]
|
|
|
|
|
129 |
results = collection.query(query_embeddings=[query_embedding.tolist()], n_results=3)
|
130 |
retrieved_docs = results.get("documents", [[]])[0] if results else []
|
|
|
|
|
131 |
|
132 |
+
print(f"Retrieved {len(retrieved_docs)} documents")
|
|
|
|
|
|
|
|
|
|
|
133 |
|
134 |
+
# First, try to find direct answers in the retrieved documents
|
135 |
+
query_lower = query.lower()
|
136 |
+
|
137 |
+
# Check for FAQ matches
|
138 |
+
for doc in retrieved_docs:
|
139 |
+
if "Q:" in doc and "A:" in doc:
|
140 |
+
lines = doc.split('\n')
|
141 |
+
for i, line in enumerate(lines):
|
142 |
+
if line.strip().startswith('Q:'):
|
143 |
+
question = line[2:].strip().lower()
|
144 |
+
# Check for keyword overlap
|
145 |
+
query_words = set(query_lower.split())
|
146 |
+
question_words = set(question.split())
|
147 |
+
overlap = len(query_words & question_words)
|
148 |
+
if overlap >= 2 or any(word in question for word in query_words if len(word) > 4):
|
149 |
+
# Found matching question
|
150 |
+
for j in range(i+1, len(lines)):
|
151 |
+
if lines[j].strip().startswith('A:'):
|
152 |
+
answer = lines[j][2:].strip()
|
153 |
+
print(f"Found FAQ match: {answer}")
|
154 |
+
return answer
|
155 |
+
elif lines[j].strip().startswith('Q:'):
|
156 |
+
break
|
157 |
+
|
158 |
+
# If no FAQ match, create a conversational response using the context
|
159 |
+
context_summary = ". ".join([doc[:150] for doc in retrieved_docs[:2]])
|
160 |
+
|
161 |
+
# Build conversation for DialoGPT
|
162 |
+
conversation = f"Tell me about Codingo. {context_summary} User asks: {query}"
|
163 |
|
164 |
# Tokenize
|
165 |
+
inputs = tokenizer.encode(conversation, return_tensors="pt", truncation=True, max_length=200)
|
166 |
+
inputs = inputs.to(model.device)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
167 |
|
168 |
+
# Generate
|
|
|
|
|
|
|
|
|
169 |
with torch.no_grad():
|
170 |
+
output_ids = model.generate(
|
171 |
+
inputs,
|
172 |
+
max_length=inputs.shape[1] + 100,
|
173 |
+
num_beams=3,
|
174 |
+
temperature=0.8,
|
175 |
+
pad_token_id=tokenizer.eos_token_id,
|
176 |
+
eos_token_id=tokenizer.eos_token_id,
|
177 |
+
do_sample=True,
|
178 |
+
top_p=0.9,
|
179 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
|
181 |
+
# Decode
|
182 |
+
full_response = tokenizer.decode(output_ids[0], skip_special_tokens=True)
|
183 |
+
|
184 |
+
# Extract only the generated part
|
185 |
+
response = full_response[len(conversation):].strip()
|
186 |
+
|
187 |
+
# Clean up
|
188 |
+
if not response or len(response) < 10:
|
189 |
+
# Fallback: create response from context
|
190 |
+
if "how" in query_lower:
|
191 |
+
if "work" in query_lower:
|
192 |
+
return "Codingo works by using AI to match candidates with suitable job postings. Candidates create profiles, upload resumes, and our AI analyzes their skills to recommend the best job matches."
|
193 |
+
elif "improve" in query_lower:
|
194 |
+
return "To improve your match score on Codingo, update your profile with accurate skills, add relevant keywords from job descriptions, and include links to your portfolio projects."
|
195 |
+
elif "what" in query_lower:
|
196 |
+
if "codingo" in query_lower:
|
197 |
+
return "Codingo is an AI-powered recruitment platform that streamlines job applications and hiring. We help candidates find suitable jobs and employers find the right talent through intelligent matching."
|
198 |
+
elif "special" in query_lower or "different" in query_lower:
|
199 |
+
return "What makes Codingo special is our AI that understands both technical skills and language, real-time CV feedback, bias-aware algorithms, and specialized focus on tech professionals."
|
200 |
+
elif "can" in query_lower or "does" in query_lower:
|
201 |
+
if "chatbot" in query_lower:
|
202 |
+
return "I can help you with questions about the Codingo platform, including how to use it, improve your profile, understand our features, and get tips for job applications."
|
203 |
+
elif "free" in query_lower or "cost" in query_lower:
|
204 |
+
return "Profile creation and job applications are free on Codingo. Premium features may be offered for advanced analytics and additional services."
|
205 |
+
|
206 |
+
# Generic but relevant response
|
207 |
+
return "I'd be happy to help you with Codingo! You can ask me about creating profiles, job matching, CV tips, supported job types, or any other features of our recruitment platform."
|
208 |
|
|
|
209 |
return response
|
210 |
|
211 |
except Exception as e:
|
212 |
+
print(f"Error in get_chatbot_response: {e}")
|
213 |
traceback.print_exc()
|
214 |
+
return "I apologize for the technical issue. Please try asking about Codingo's features, job matching, or how to improve your profile."
|