husseinelsaadi commited on
Commit
bef6630
·
1 Parent(s): 29cfacc

chatbot updated

Browse files
Files changed (2) hide show
  1. app.py +20 -206
  2. chatbot/chatbot.py +254 -0
app.py CHANGED
@@ -36,215 +36,23 @@ import json
36
  #
37
  # The chatbot uses a local vector database (Chroma) to search the
38
  # ``chatbot/chatbot.txt`` knowledge base. Retrieved passages are fed to
39
- # a lightweight conversational model from Hugging Face (see
40
- # ``init_hf_model`` below). To avoid the expensive model and database
41
- # initialisation on every request, embeddings and the vector collection are
42
- # loaded lazily the first time a chat query is processed. Subsequent
43
- # requests reuse the same global objects. See ``init_chatbot`` and
44
- # ``get_chatbot_response`` for implementation details.
45
 
46
  # Paths for the chatbot knowledge base and persistent vector store. We
47
  # compute these relative to the current file so that the app can be deployed
48
  # anywhere without needing to change configuration. The ``chroma_db``
49
  # directory will be created automatically by the Chroma client if it does not
50
  # exist.
51
- import shutil
52
-
53
- # Remove any old unwritable Chroma DB path from previous versions
54
- shutil.rmtree("/app/chatbot/chroma_db", ignore_errors=True)
55
- CHATBOT_TXT_PATH = os.path.join(current_dir, 'chatbot', 'chatbot.txt')
56
- CHATBOT_DB_DIR = "/tmp/chroma_db"
57
-
58
- # -----------------------------------------------------------------------------
59
- # Hugging Face model configuration
60
- #
61
- # The chatbot uses a small conversational model hosted on Hugging Face. To
62
- # allow easy experimentation, the model name can be overridden via the
63
- # ``HF_CHATBOT_MODEL`` environment variable. If unset, we fall back to
64
- # ``microsoft/DialoGPT-medium`` which provides better conversational quality
65
- # than blenderbot for our use case.
66
- HF_MODEL_NAME = os.getenv("HF_CHATBOT_MODEL", "microsoft/DialoGPT-medium")
67
-
68
- # Global Hugging Face model and tokenizer. These variables remain ``None``
69
- # until ``init_hf_model()`` is called. They are reused across all chatbot
70
- # requests to prevent repeatedly loading the large model into memory.
71
- _hf_model = None
72
- _hf_tokenizer = None
73
-
74
- def init_hf_model():
75
- """
76
- Initialise the Hugging Face conversational model and tokenizer.
77
-
78
- This function loads the specified ``HF_MODEL_NAME`` model and its
79
- corresponding tokenizer. The model is moved to GPU if available,
80
- otherwise it runs on CPU. Subsequent calls return immediately if
81
- the model and tokenizer have already been instantiated.
82
- """
83
- global _hf_model, _hf_tokenizer
84
- if _hf_model is not None and _hf_tokenizer is not None:
85
- return
86
-
87
- from transformers import AutoModelForCausalLM, AutoTokenizer
88
- import torch
89
-
90
- model_name = HF_MODEL_NAME
91
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
92
-
93
- print(f"Loading model {model_name} on device {device}")
94
-
95
- # Load tokenizer and model from Hugging Face
96
- tokenizer = AutoTokenizer.from_pretrained(model_name)
97
- model = AutoModelForCausalLM.from_pretrained(model_name).to(device)
98
-
99
- # Set pad token to eos token if not set
100
- if tokenizer.pad_token is None:
101
- tokenizer.pad_token = tokenizer.eos_token
102
-
103
- _hf_model = model
104
- _hf_tokenizer = tokenizer
105
- print(f"Model loaded successfully on {device}")
106
-
107
- _chatbot_embedder = None
108
- _chatbot_collection = None
109
-
110
- def init_chatbot():
111
- """Initialise the Chroma vector DB with chatbot.txt content."""
112
- global _chatbot_embedder, _chatbot_collection
113
- if _chatbot_embedder is not None and _chatbot_collection is not None:
114
- return
115
-
116
- from langchain.text_splitter import RecursiveCharacterTextSplitter
117
- from sentence_transformers import SentenceTransformer
118
- import chromadb
119
- from chromadb.config import Settings
120
- import os
121
-
122
- os.makedirs(CHATBOT_DB_DIR, exist_ok=True)
123
-
124
- # Read and parse the chatbot knowledge base
125
- try:
126
- with open(CHATBOT_TXT_PATH, encoding="utf-8") as f:
127
- text = f.read()
128
- except FileNotFoundError:
129
- print(f"Warning: {CHATBOT_TXT_PATH} not found, using default content")
130
- text = """
131
- Codingo is an AI-powered recruitment platform designed to streamline job applications,
132
- candidate screening, and hiring. We make hiring smarter, faster, and fairer through
133
- automation and intelligent recommendations.
134
- """
135
-
136
- # Split text into chunks for vector search
137
- splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=100)
138
- docs = [doc.strip() for doc in splitter.split_text(text) if doc.strip()]
139
-
140
- # Initialize embedder
141
- embedder = SentenceTransformer("all-MiniLM-L6-v2")
142
- embeddings = embedder.encode(docs, show_progress_bar=False, batch_size=32)
143
-
144
- # Initialize Chroma client
145
- client = chromadb.Client(Settings(
146
- persist_directory=CHATBOT_DB_DIR,
147
- anonymized_telemetry=False,
148
- is_persistent=True
149
- ))
150
-
151
- # Get or create collection
152
- collection = client.get_or_create_collection("chatbot")
153
-
154
- # Check if collection is empty and populate if needed
155
- try:
156
- existing = collection.get(limit=1)
157
- if not existing.get("documents"):
158
- raise ValueError("Empty Chroma DB")
159
- except Exception:
160
- # Add documents to collection
161
- ids = [f"doc_{i}" for i in range(len(docs))]
162
- collection.add(
163
- documents=docs,
164
- embeddings=embeddings.tolist(),
165
- ids=ids
166
- )
167
- print(f"Added {len(docs)} documents to Chroma DB")
168
-
169
- _chatbot_embedder = embedder
170
- _chatbot_collection = collection
171
-
172
- def get_chatbot_response(query: str) -> str:
173
- """Generate a reply to the user's query using Chroma + Hugging Face model."""
174
- try:
175
- init_chatbot()
176
- init_hf_model()
177
-
178
- # Safety: prevent empty input
179
- if not query or not query.strip():
180
- return "Please type a question about the Codingo platform."
181
-
182
- embedder = _chatbot_embedder
183
- collection = _chatbot_collection
184
- model = _hf_model
185
- tokenizer = _hf_tokenizer
186
- device = model.device
187
-
188
- # Retrieve context from Chroma
189
- query_embedding = embedder.encode([query])[0]
190
- results = collection.query(
191
- query_embeddings=[query_embedding.tolist()],
192
- n_results=3
193
- )
194
- retrieved_docs = results.get("documents", [[]])[0] if results else []
195
- context = "\n".join(retrieved_docs[:3]) # Limit context to top 3 results
196
-
197
- # Build conversational prompt
198
- system_instruction = (
199
- "You are LUNA AI, a helpful assistant for the Codingo recruitment platform. "
200
- "Use the provided context to answer questions about Codingo. "
201
- "If the question is not related to Codingo, politely redirect the conversation. "
202
- "Keep responses concise and friendly."
203
- )
204
-
205
- # Format prompt for DialoGPT
206
- prompt = f"{system_instruction}\n\nContext:\n{context}\n\nUser: {query}\nLUNA AI:"
207
-
208
- # Tokenize with proper truncation
209
- inputs = tokenizer.encode(
210
- prompt,
211
- return_tensors="pt",
212
- truncation=True,
213
- max_length=512,
214
- padding=True
215
- ).to(device)
216
-
217
- # Generate response
218
- with torch.no_grad():
219
- output_ids = model.generate(
220
- inputs,
221
- max_length=inputs.shape[1] + 150,
222
- num_beams=3,
223
- do_sample=True,
224
- temperature=0.7,
225
- pad_token_id=tokenizer.eos_token_id,
226
- eos_token_id=tokenizer.eos_token_id,
227
- early_stopping=True
228
- )
229
-
230
- # Decode response
231
- response = tokenizer.decode(output_ids[0], skip_special_tokens=True)
232
-
233
- # Extract only the bot's response
234
- if "LUNA AI:" in response:
235
- response = response.split("LUNA AI:")[-1].strip()
236
- elif prompt in response:
237
- response = response.replace(prompt, "").strip()
238
-
239
- # Fallback if response is empty
240
- if not response:
241
- response = "I'm here to help you with questions about the Codingo platform. What would you like to know?"
242
-
243
- return response
244
-
245
- except Exception as e:
246
- print(f"Chatbot error: {str(e)}")
247
- return "I'm having trouble processing your request. Please try again or ask about Codingo's features, job matching, or how to use the platform."
248
 
249
  # Initialize Flask app
250
  app = Flask(
@@ -540,11 +348,17 @@ if __name__ == '__main__':
540
 
541
  with app.app_context():
542
  db.create_all()
543
- # Pre-initialize chatbot on startup for faster first response
 
 
 
 
544
  print("Initializing chatbot...")
545
  try:
546
- init_chatbot()
547
- init_hf_model()
 
 
548
  print("Chatbot initialized successfully")
549
  except Exception as e:
550
  print(f"Chatbot initialization warning: {e}")
 
36
  #
37
  # The chatbot uses a local vector database (Chroma) to search the
38
  # ``chatbot/chatbot.txt`` knowledge base. Retrieved passages are fed to
39
+ # a lightweight conversational model from Hugging Face. To avoid the
40
+ # expensive model and database initialisation on every request, embeddings
41
+ # and the vector collection are loaded lazily the first time a chat query
42
+ # is processed. Subsequent requests reuse the same global objects. All
43
+ # chatbot logic resides in ``chatbot/chatbot.py``.
 
44
 
45
  # Paths for the chatbot knowledge base and persistent vector store. We
46
  # compute these relative to the current file so that the app can be deployed
47
  # anywhere without needing to change configuration. The ``chroma_db``
48
  # directory will be created automatically by the Chroma client if it does not
49
  # exist.
50
+ # The internal chatbot logic has been extracted to ``chatbot/chatbot.py``. See
51
+ # that module for details. We import the ``get_chatbot_response`` function
52
+ # here so that the Flask route can delegate queries directly to it. This
53
+ # prevents ``app.py`` from depending on the heavy ML libraries and keeps
54
+ # the application entry point lean.
55
+ from chatbot.chatbot import get_chatbot_response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
  # Initialize Flask app
58
  app = Flask(
 
348
 
349
  with app.app_context():
350
  db.create_all()
351
+ # Pre-initialize the chatbot on startup for faster first response. We
352
+ # deliberately trigger a dummy query here to force loading of the
353
+ # sentence encoder, vector store and conversational model. Any
354
+ # exceptions during warm‑up are logged but do not stop the app from
355
+ # starting.
356
  print("Initializing chatbot...")
357
  try:
358
+ # Import inside the block to ensure the module has been
359
+ # properly loaded with the current environment settings.
360
+ from chatbot.chatbot import get_chatbot_response
361
+ _ = get_chatbot_response("Hello!")
362
  print("Chatbot initialized successfully")
363
  except Exception as e:
364
  print(f"Chatbot initialization warning: {e}")
chatbot/chatbot.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Chatbot module for Codingo
3
+ ==========================
4
+
5
+ This module encapsulates all functionality required to serve answers to
6
+ questions about the Codingo platform. It loads a small conversational
7
+ model from Hugging Face and a lightweight vector database populated from
8
+ ``chatbot.txt``. When a user asks a question, the module retrieves
9
+ relevant snippets from the knowledge base and feeds them into the
10
+ language model to generate a friendly response.
11
+
12
+ Key features:
13
+
14
+ * Completely self‑contained: there are no OpenAI or external API
15
+ dependencies. Only free, locally hosted Hugging Face models are used.
16
+ * Lazy initialisation: the model and vector store are loaded on the
17
+ first call to ``get_chatbot_response``. Subsequent calls reuse
18
+ existing objects, avoiding expensive reloads.
19
+ * GPU support: if a CUDA device is available, the model is automatically
20
+ moved onto the GPU for faster inference.
21
+
22
+ This file lives inside ``codingo/chatbot`` alongside ``chatbot.txt``.
23
+ ``chatbot.txt`` should contain a plain‑text knowledge base of
24
+ Codingo‑specific information and FAQs. Feel free to update the
25
+ contents of that file without touching any code here.
26
+
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import os
32
+ import shutil
33
+ from typing import List
34
+
35
+ # -----------------------------------------------------------------------------
36
+ # Environment configuration
37
+ #
38
+ # We set a few environment variables to force Hugging Face to store model
39
+ # weights and tokeniser files inside ``/tmp``. Hugging Face Spaces
40
+ # provisions a read‑only file system outside of ``/tmp``, so without these
41
+ # settings the transformers library might attempt to write into
42
+ # unwritable locations. These variables have no effect if the same
43
+ # variables are already set by the hosting environment.
44
+
45
+ os.environ.setdefault("HF_HOME", "/tmp/huggingface")
46
+ os.environ.setdefault("TRANSFORMERS_CACHE", "/tmp/huggingface/transformers")
47
+ os.environ.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp/huggingface/hub")
48
+
49
+ # -----------------------------------------------------------------------------
50
+ # Module‑level state
51
+ _hf_model = None # type: ignore[assignment]
52
+ _hf_tokenizer = None # type: ignore[assignment]
53
+ _chatbot_embedder = None # type: ignore[assignment]
54
+ _chatbot_collection = None # type: ignore[assignment]
55
+
56
+ # Paths
57
+ _current_dir = os.path.dirname(os.path.abspath(__file__))
58
+ _knowledge_base_path = os.path.join(_current_dir, "chatbot.txt")
59
+ _chroma_db_dir = "/tmp/chroma_db"
60
+
61
+ # Default Hugging Face model for FAQ‑style Q&A. You can override this
62
+ # behaviour at deployment time by setting the ``HF_CHATBOT_MODEL``
63
+ # environment variable. DialoGPT is a lightweight conversational model
64
+ # suitable for generating coherent short answers. If you need more
65
+ # open‑domain capability, consider ``facebook/blenderbot-400M-distill``.
66
+ DEFAULT_MODEL_NAME = "microsoft/DialoGPT-medium"
67
+
68
+
69
+ def _init_hf_model() -> None:
70
+ """Load the Hugging Face model and tokenizer if not already loaded."""
71
+ global _hf_model, _hf_tokenizer
72
+ if _hf_model is not None and _hf_tokenizer is not None:
73
+ return
74
+
75
+ from transformers import AutoModelForCausalLM, AutoTokenizer # slow import
76
+ import torch
77
+
78
+ model_name = os.getenv("HF_CHATBOT_MODEL", DEFAULT_MODEL_NAME)
79
+ # Choose GPU if available; otherwise CPU
80
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
81
+
82
+ # Download and load tokenizer and model. They will be cached under
83
+ # the directories specified above. If running for the first time on
84
+ # Hugging Face Spaces, model download may take a while.
85
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
86
+ model = AutoModelForCausalLM.from_pretrained(model_name).to(device)
87
+
88
+ # Ensure the pad token is defined. Many casual conversation models
89
+ # reuse the end‑of‑sentence token for padding.
90
+ if tokenizer.pad_token is None:
91
+ tokenizer.pad_token = tokenizer.eos_token
92
+
93
+ _hf_model = model
94
+ _hf_tokenizer = tokenizer
95
+
96
+
97
+ def _init_vector_store() -> None:
98
+ """Initialise the Chroma vector store from ``chatbot.txt`` if needed."""
99
+ global _chatbot_embedder, _chatbot_collection
100
+ if _chatbot_embedder is not None and _chatbot_collection is not None:
101
+ return
102
+
103
+ # Import heavy dependencies lazily to reduce module import time
104
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
105
+ from sentence_transformers import SentenceTransformer
106
+ import chromadb
107
+ from chromadb.config import Settings
108
+
109
+ # Clear out any legacy database path that might be unwritable. Previous
110
+ # versions of this project wrote under ``/app/chatbot/chroma_db`` which
111
+ # fails on Hugging Face Spaces. The ``ignore_errors=True`` flag
112
+ # suppresses FileNotFoundError.
113
+ shutil.rmtree("/app/chatbot/chroma_db", ignore_errors=True)
114
+
115
+ os.makedirs(_chroma_db_dir, exist_ok=True)
116
+
117
+ # Read the knowledge base file. If the file is missing, fall back to a
118
+ # minimal description of Codingo so the chatbot can still respond.
119
+ try:
120
+ with open(_knowledge_base_path, encoding="utf-8") as f:
121
+ raw_text = f.read()
122
+ except FileNotFoundError:
123
+ raw_text = (
124
+ "Codingo is an AI-powered recruitment platform designed to "
125
+ "streamline job applications, candidate screening, and hiring. "
126
+ "We make hiring smarter, faster, and fairer through automation "
127
+ "and intelligent recommendations."
128
+ )
129
+
130
+ # Split the knowledge base into overlapping chunks for semantic search.
131
+ splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=100)
132
+ docs: List[str] = [doc.strip() for doc in splitter.split_text(raw_text) if doc.strip()]
133
+
134
+ # Embed the chunks using a small sentence transformer. This model is
135
+ # lightweight (~80 MB) and works well for semantic similarity tasks.
136
+ embedder = SentenceTransformer("all-MiniLM-L6-v2")
137
+ embeddings = embedder.encode(docs, show_progress_bar=False, batch_size=32)
138
+
139
+ # Initialise a persistent Chroma client. We disable anonymous telemetry
140
+ # because the environment has no outbound internet access.
141
+ client = chromadb.Client(Settings(
142
+ persist_directory=_chroma_db_dir,
143
+ anonymized_telemetry=False,
144
+ is_persistent=True,
145
+ ))
146
+
147
+ # Create or retrieve the "chatbot" collection within the database.
148
+ collection = client.get_or_create_collection("chatbot")
149
+
150
+ # If no documents are present, populate the collection with our chunks.
151
+ try:
152
+ existing = collection.get(limit=1)
153
+ if not existing.get("documents"):
154
+ raise ValueError("Empty Chroma DB")
155
+ except Exception:
156
+ ids = [f"doc_{i}" for i in range(len(docs))]
157
+ collection.add(documents=docs, embeddings=embeddings.tolist(), ids=ids)
158
+
159
+ _chatbot_embedder = embedder
160
+ _chatbot_collection = collection
161
+
162
+
163
+ def get_chatbot_response(query: str) -> str:
164
+ """
165
+ Generate a chatbot reply to the given user query.
166
+
167
+ The response is generated by retrieving up to three relevant snippets
168
+ from the knowledge base using the MiniLM embeddings and then feeding
169
+ those snippets together with the user question into the conversational
170
+ model. If no relevant information is found or the model generates
171
+ an empty response, a helpful fallback message is returned.
172
+
173
+ Parameters
174
+ ----------
175
+ query : str
176
+ The user's message. Should be non‑empty and related to the
177
+ Codingo platform.
178
+
179
+ Returns
180
+ -------
181
+ str
182
+ The chatbot's reply, always a string.
183
+ """
184
+ # Basic validation of the query string
185
+ if not query or not query.strip():
186
+ return "Please type a question about the Codingo platform."
187
+
188
+ # Lazy initialisation of the vector store and Hugging Face model
189
+ _init_vector_store()
190
+ _init_hf_model()
191
+
192
+ # Unpack state
193
+ embedder = _chatbot_embedder # type: ignore[assignment]
194
+ collection = _chatbot_collection # type: ignore[assignment]
195
+ model = _hf_model # type: ignore[assignment]
196
+ tokenizer = _hf_tokenizer # type: ignore[assignment]
197
+
198
+ import torch
199
+
200
+ # Embed the incoming query using the same sentence transformer
201
+ query_embedding = embedder.encode([query])[0] # type: ignore[operator]
202
+ # Retrieve the three most similar documents from the vector store
203
+ results = collection.query(query_embeddings=[query_embedding.tolist()], n_results=3)
204
+ retrieved_docs = results.get("documents", [[]])[0] if results else []
205
+
206
+ # Build a context string from the retrieved documents
207
+ context = "\n".join(retrieved_docs[:3])
208
+
209
+ # Compose the system instruction. The model is prompted as a
210
+ # persona called LUNA AI. Keep responses concise and friendly, and
211
+ # redirect politely on irrelevant questions.
212
+ system_instruction = (
213
+ "You are LUNA AI, a helpful assistant for the Codingo recruitment "
214
+ "platform. Use the provided context to answer questions about "
215
+ "Codingo. If the question is not related to Codingo, politely "
216
+ "redirect the conversation. Keep responses concise and friendly."
217
+ )
218
+
219
+ prompt = (
220
+ f"{system_instruction}\n\nContext:\n{context}\n\n"
221
+ f"User: {query}\nLUNA AI:"
222
+ )
223
+
224
+ # Tokenise the prompt and truncate to the maximum input length supported
225
+ inputs = tokenizer.encode(prompt, return_tensors="pt", truncation=True, max_length=512, padding=True)
226
+ inputs = inputs.to(model.device)
227
+
228
+ # Generate a continuation from the model
229
+ with torch.no_grad():
230
+ output_ids = model.generate(
231
+ inputs,
232
+ max_length=inputs.shape[1] + 150,
233
+ num_beams=3,
234
+ do_sample=True,
235
+ temperature=0.7,
236
+ pad_token_id=tokenizer.eos_token_id,
237
+ eos_token_id=tokenizer.eos_token_id,
238
+ early_stopping=True,
239
+ )
240
+
241
+ # Decode the output and strip the prompt from the beginning
242
+ response = tokenizer.decode(output_ids[0], skip_special_tokens=True)
243
+ if "LUNA AI:" in response:
244
+ response = response.split("LUNA AI:")[-1].strip()
245
+ elif prompt in response:
246
+ response = response.replace(prompt, "").strip()
247
+
248
+ # Fallback if the model didn't return anything useful
249
+ if not response:
250
+ return (
251
+ "I'm here to help you with questions about the Codingo platform. "
252
+ "What would you like to know?"
253
+ )
254
+ return response