youssefreda9 commited on
Commit
e9527e6
·
1 Parent(s): a6aeadf

fix(autocomplete): true context awareness - GPT-2 now adds own candidates + cache key uses 5 words

Browse files
src/app.py CHANGED
@@ -457,7 +457,7 @@ def autocomplete():
457
  t0 = time.time()
458
  suggestions = ac_model.predict(context, n=n)
459
  elapsed = int((time.time() - t0) * 1000)
460
- logger.info(f"[AUTOCOMPLETE] {elapsed}ms | mode={ac_model.get_mode()} | suggestions={suggestions}")
461
 
462
  return jsonify({
463
  'suggestions': suggestions,
 
457
  t0 = time.time()
458
  suggestions = ac_model.predict(context, n=n)
459
  elapsed = int((time.time() - t0) * 1000)
460
+ logger.info(f"[AUTOCOMPLETE] {elapsed}ms | mode={ac_model.get_mode()} | context='{context[:80]}' | suggestions={suggestions}")
461
 
462
  return jsonify({
463
  'suggestions': suggestions,
src/nlp/autocomplete/autocomplete_service.py CHANGED
@@ -47,9 +47,9 @@ def get_autocomplete_model():
47
 
48
  # ─── Cache key helper ─────────────────────────────────────────────────────────
49
  def _context_key(context: str) -> str:
50
- """Reduce context to last 3 words for cache key."""
51
  words = context.strip().split()
52
- return " ".join(words[-3:]) if words else ""
53
 
54
 
55
  # ─── Main Service ─────────────────────────────────────────────────────────────
@@ -216,7 +216,13 @@ class HybridAutoComplete:
216
  return [w for w, _ in preds[:n]]
217
 
218
  def _hybrid_predict(self, context: str, n: int = 5) -> list:
219
- """Hybrid prediction: bigram + GPT-2 scoring."""
 
 
 
 
 
 
220
  from .autocomplete_rules import merge_similar_predictions, filter_suggestions
221
 
222
  tokens = context.strip().split()
@@ -225,7 +231,7 @@ class HybridAutoComplete:
225
 
226
  last_word = tokens[-1]
227
 
228
- # 1. Get bigram candidates
229
  stat_candidates = []
230
  if last_word in self.bigrams:
231
  for w, c in self.bigrams[last_word].items():
@@ -233,27 +239,48 @@ class HybridAutoComplete:
233
  continue
234
  stat_candidates.append((w, c))
235
 
 
 
 
 
 
236
  if not stat_candidates:
237
- # No bigram matches — fall back to bigram-only with unigrams
 
 
 
 
238
  return self._bigram_predict(context, n)
239
 
240
  total = sum(c for _, c in stat_candidates)
241
  if total == 0:
 
 
 
 
242
  return self._bigram_predict(context, n)
243
 
244
  stat_preds = [(w, c / total) for w, c in stat_candidates]
245
  stat_preds.sort(key=lambda x: x[1], reverse=True)
246
  stat_preds = merge_similar_predictions(stat_preds, top_k=20)
247
 
248
- # 2. Get GPT-2 next-token probabilities (ONCE)
249
- gpt2_probs = self._gpt2_next_token_probs(context, top_k=50)
250
-
251
- # 3. Hybrid scoring
252
  results = []
 
253
  for w, stat_p in stat_preds:
254
  neural_p = gpt2_probs.get(w, 1e-8)
255
  score = self.alpha * stat_p + (1 - self.alpha) * neural_p
256
  results.append((w, score))
 
 
 
 
 
 
 
 
 
 
257
 
258
  results.sort(key=lambda x: x[1], reverse=True)
259
  results = filter_suggestions(results)
 
47
 
48
  # ─── Cache key helper ─────────────────────────────────────────────────────────
49
  def _context_key(context: str) -> str:
50
+ """Use last 5 words for cache key — preserves enough context for GPT-2 awareness."""
51
  words = context.strip().split()
52
+ return " ".join(words[-5:]) if words else ""
53
 
54
 
55
  # ─── Main Service ─────────────────────────────────────────────────────────────
 
216
  return [w for w, _ in preds[:n]]
217
 
218
  def _hybrid_predict(self, context: str, n: int = 5) -> list:
219
+ """Hybrid prediction: bigram + GPT-2 scoring.
220
+
221
+ GPT-2 receives the FULL sentence as context for true context awareness.
222
+ Bigram provides frequency-based candidates from the last word.
223
+ GPT-2's own top predictions are ADDED as candidates so contextually
224
+ appropriate words that bigram doesn't know about can still appear.
225
+ """
226
  from .autocomplete_rules import merge_similar_predictions, filter_suggestions
227
 
228
  tokens = context.strip().split()
 
231
 
232
  last_word = tokens[-1]
233
 
234
+ # 1. Get bigram candidates (frequency-based, last word only)
235
  stat_candidates = []
236
  if last_word in self.bigrams:
237
  for w, c in self.bigrams[last_word].items():
 
239
  continue
240
  stat_candidates.append((w, c))
241
 
242
+ # 2. Get GPT-2 next-token probabilities using FULL context
243
+ # GPT-2 sees the entire sentence, not just the last word
244
+ gpt2_probs = self._gpt2_next_token_probs(context, top_k=50)
245
+
246
+ # 3. If no bigram candidates, use GPT-2 predictions directly
247
  if not stat_candidates:
248
+ if gpt2_probs:
249
+ # Use GPT-2's own contextual predictions
250
+ gpt2_preds = sorted(gpt2_probs.items(), key=lambda x: x[1], reverse=True)
251
+ gpt2_preds = filter_suggestions(gpt2_preds)
252
+ return [w for w, _ in gpt2_preds[:n]]
253
  return self._bigram_predict(context, n)
254
 
255
  total = sum(c for _, c in stat_candidates)
256
  if total == 0:
257
+ if gpt2_probs:
258
+ gpt2_preds = sorted(gpt2_probs.items(), key=lambda x: x[1], reverse=True)
259
+ gpt2_preds = filter_suggestions(gpt2_preds)
260
+ return [w for w, _ in gpt2_preds[:n]]
261
  return self._bigram_predict(context, n)
262
 
263
  stat_preds = [(w, c / total) for w, c in stat_candidates]
264
  stat_preds.sort(key=lambda x: x[1], reverse=True)
265
  stat_preds = merge_similar_predictions(stat_preds, top_k=20)
266
 
267
+ # 4. Hybrid scoring: combine bigram frequency with GPT-2 context score
 
 
 
268
  results = []
269
+ seen_words = set()
270
  for w, stat_p in stat_preds:
271
  neural_p = gpt2_probs.get(w, 1e-8)
272
  score = self.alpha * stat_p + (1 - self.alpha) * neural_p
273
  results.append((w, score))
274
+ seen_words.add(w)
275
+
276
+ # 5. ADD GPT-2's own top contextual predictions as bonus candidates
277
+ # These are words GPT-2 thinks fit the context but bigram doesn't know
278
+ for w, neural_p in sorted(gpt2_probs.items(), key=lambda x: x[1], reverse=True)[:10]:
279
+ if w not in seen_words and len(w) >= 2:
280
+ # Give these a lower alpha since they have no bigram backing
281
+ score = 0.3 * neural_p # Lower weight, but still contextual
282
+ results.append((w, score))
283
+ seen_words.add(w)
284
 
285
  results.sort(key=lambda x: x[1], reverse=True)
286
  results = filter_suggestions(results)