afaceuser09 commited on
Commit
a2be4fe
·
verified ·
1 Parent(s): c61f0bd

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +1187 -1069
main.py CHANGED
@@ -2,7 +2,6 @@ import os
2
  import asyncio
3
  import logging
4
  import json
5
- import re
6
  import torch
7
  import numpy as np
8
  import httpx
@@ -41,459 +40,1043 @@ logger = logging.getLogger("infinitygrm")
41
 
42
 
43
  # ============================================================
44
- # GLOBAL STATE
45
  # ============================================================
46
 
47
  engine_state = {}
 
 
 
48
  session_store = {}
49
 
50
 
51
  # ============================================================
52
- # REASONING SETTINGS
 
 
 
 
 
 
 
 
 
 
 
53
  # ============================================================
54
 
55
- REASONING_ENABLED = True
56
 
57
- # How many examples are placed into a difficult prompt.
58
- REASONING_EXAMPLES_PER_REQUEST = 3
 
 
 
 
 
 
 
 
 
59
 
60
- # Keep examples reasonably small so prompts do not become huge.
61
- REASONING_MAX_EXAMPLE_CHARS = 2500
62
 
 
 
 
 
 
 
63
 
64
- # ============================================================
65
- # REASONING EXAMPLES
66
- #
67
- # Add more examples to this list later.
68
- # These are in-context examples, NOT actual weight training.
69
- # ============================================================
70
 
71
- REASONING_EXAMPLES = [
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  {
74
- "category": "algorithm",
75
- "question": """
76
- Write a Python function that finds the longest substring without
77
- repeating characters.
 
78
  """,
79
  "reasoning": """
80
- Use a sliding window. Keep a left pointer and scan the string with a
81
- right pointer. Store the most recent index of every character. When a
82
- duplicate occurs inside the current window, move the left pointer past
83
- the previous occurrence. Track the largest window length.
 
 
 
 
 
 
 
 
 
84
  """,
85
  "answer": """
86
- def longest_unique_substring(s):
87
- last_seen = {}
88
- left = 0
89
- best = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- for right, char in enumerate(s):
92
- if char in last_seen and last_seen[char] >= left:
93
- left = last_seen[char] + 1
94
 
95
- last_seen[char] = right
96
- best = max(best, right - left + 1)
 
 
97
 
98
- return best
 
 
 
99
 
100
- Time complexity: O(n).
101
- Space complexity: O(k), where k is the number of distinct characters.
102
  """
103
  },
104
 
105
  {
106
- "category": "algorithm",
107
- "question": """
108
- Given an array of integers, find the length of the longest contiguous
109
- subarray whose sum is exactly k. The array can contain negative numbers.
110
  """,
111
  "reasoning": """
112
- A normal sliding window does not work because negative values make the
113
- sum non-monotonic.
114
 
115
- Use prefix sums. If the current prefix sum is P, we need an earlier
116
- prefix sum equal to P - k. Store the earliest index for each prefix sum.
117
- The earliest index gives the longest possible subarray.
118
  """,
119
  "answer": """
120
- def longest_subarray_sum_k(nums, k):
121
- first_seen = {0: -1}
122
- prefix = 0
123
- best = 0
124
 
125
- for i, value in enumerate(nums):
126
- prefix += value
127
- needed = prefix - k
 
128
 
129
- if needed in first_seen:
130
- best = max(best, i - first_seen[needed])
 
 
 
 
 
 
131
 
132
- if prefix not in first_seen:
133
- first_seen[prefix] = i
 
 
 
 
134
 
135
- return best
 
 
 
136
 
137
- Time: O(n) expected.
138
- Space: O(n).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  """
140
  },
141
 
142
  {
143
  "category": "debugging",
144
- "question": """
145
- Why can modifying a Python list while iterating over it cause elements
146
- to be skipped?
 
 
 
 
 
 
 
147
  """,
148
  "reasoning": """
149
- Removing an element shifts later elements toward the beginning of the
150
- list. The iterator then advances, potentially skipping the element that
151
- was shifted into the current position.
152
 
153
- A safer approach is to create a new list or iterate over a copy when
154
- removing elements.
 
 
155
  """,
156
  "answer": """
157
- The indexes shift when an element is removed, so the iterator can skip
158
- elements. Prefer constructing a new list or iterating over a copy.
159
  """
160
  },
161
 
162
  {
163
- "category": "math",
164
- "question": """
165
- A train travels 120 km at 60 km/h and another 120 km at 80 km/h.
166
- What is its average speed?
 
 
 
 
167
  """,
168
  "reasoning": """
169
- Average speed is total distance divided by total time.
170
 
171
- The first 120 km takes 2 hours.
172
- The second 120 km takes 1.5 hours.
173
 
174
- Total distance is 240 km and total time is 3.5 hours.
175
- Therefore the average speed is 240 / 3.5, approximately 68.57 km/h.
176
  """,
177
  "answer": """
178
- 68.57 km/h approximately.
 
 
 
 
179
  """
180
  },
181
 
182
  {
183
- "category": "logic",
184
- "question": """
185
- Three boxes are labeled APPLES, ORANGES, and MIXED. Every label is
186
- incorrect. You may take one fruit from one box without looking inside.
187
- Which box should you choose?
188
  """,
189
  "reasoning": """
190
- Because every label is wrong, the box labeled MIXED cannot actually
191
- contain a mixture. It must contain only apples or only oranges.
192
 
193
- Taking one fruit from that box reveals its actual contents. The
194
- incorrect labels then allow the remaining boxes to be identified.
195
  """,
196
  "answer": """
197
- Choose the box labeled MIXED.
 
 
 
 
 
 
 
 
 
198
  """
199
  },
200
 
201
  {
202
- "category": "dynamic_programming",
203
- "question": """
204
- Given a list of positive integers, find the maximum sum obtainable
205
- without selecting two adjacent elements.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  """,
207
  "reasoning": """
208
- For every element there are two choices: skip it or take it.
209
 
210
- If we take the current element, we cannot take the previous element.
211
- Therefore:
212
 
213
- current = max(previous_best, two_positions_back + current_value)
 
 
 
 
 
 
 
 
 
214
 
215
- Only the previous two states are required.
 
 
 
 
 
 
 
 
 
 
216
  """,
217
  "answer": """
218
- def max_non_adjacent_sum(values):
219
- two_back = 0
220
- one_back = 0
221
-
222
- for value in values:
223
- current = max(
224
- one_back,
225
- two_back + value
226
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
228
- two_back = one_back
229
- one_back = current
 
 
 
 
 
230
 
231
- return one_back
 
 
 
 
232
 
233
  Time: O(n).
234
- Space: O(1).
235
  """
236
  },
237
 
238
  {
239
- "category": "graph",
240
- "question": """
241
- How can you determine whether an undirected graph contains a cycle?
242
  """,
243
  "reasoning": """
244
- Use DFS or BFS while tracking the parent of each node.
 
245
 
246
- When visiting a node, if an adjacent node has already been visited and
247
- is not the current node's parent, then there is a cycle.
248
 
249
- Every vertex and edge is processed a constant number of times.
250
  """,
251
  "answer": """
252
- Use DFS or BFS with parent tracking.
 
 
253
 
254
- Time complexity: O(V + E).
255
- Space complexity: O(V).
256
  """
257
  },
258
 
259
  {
260
- "category": "binary_search",
261
- "question": """
262
- Implement binary search for a sorted Python list.
 
263
  """,
264
  "reasoning": """
265
- Maintain an interval containing every possible location of the target.
266
- Compare the middle value with the target and discard the half that
267
- cannot contain it.
268
 
269
- The interval is approximately halved on every iteration.
 
270
  """,
271
  "answer": """
272
- def binary_search(values, target):
273
- left = 0
274
- right = len(values) - 1
 
275
 
276
- while left <= right:
277
- mid = left + (right - left) // 2
 
 
 
 
 
 
 
278
 
279
- if values[mid] == target:
280
- return mid
281
 
282
- if values[mid] < target:
283
- left = mid + 1
284
- else:
285
- right = mid - 1
 
286
 
287
- return -1
 
 
288
 
289
- Time: O(log n).
290
- Space: O(1).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  """
292
  },
293
 
294
  {
295
  "category": "complexity",
296
- "question": """
297
- Why does having two nested loops not automatically mean an algorithm is
298
- O(n^2)?
299
  """,
300
  "reasoning": """
301
- Big-O complexity depends on the total number of operations, not merely
302
- the visual nesting of loops.
303
 
304
- If an inner pointer only moves forward across the entire execution, it
305
- may move at most n times overall even though it is inside another loop.
306
- The total work can therefore be O(n).
 
 
 
307
  """,
308
  "answer": """
309
- Nested loops are not automatically O(n^2). Count the total number of
310
- operations performed across the complete execution.
 
 
311
  """
312
  },
313
 
314
  {
315
- "category": "optimization",
316
- "question": """
317
- A Python program performs millions of membership checks against a large
318
- list. How could you optimize it?
319
  """,
320
  "reasoning": """
321
- List membership is O(n) on average because Python may scan many elements.
322
- A set uses hashing and provides expected O(1) membership checks.
 
 
323
 
324
- If ordering and duplicate preservation are not required, a set is
325
- usually a better structure for frequent membership testing.
326
  """,
327
  "answer": """
328
- Replace the list with a set when appropriate:
329
 
330
- identifiers = set(identifier_list)
331
-
332
- if identifier in identifiers:
333
- ...
334
  """
335
  },
336
 
337
  {
338
  "category": "coding",
339
- "question": """
340
- A function works on small inputs but becomes extremely slow on large
341
- inputs. What should you investigate first?
342
  """,
343
  "reasoning": """
344
- First examine algorithmic complexity. An O(n^2) algorithm can appear
345
- fine on small inputs but become impractical as n grows.
346
 
347
- Then inspect repeated work, unnecessary allocations, expensive I/O,
348
- database calls, and memory usage.
349
  """,
350
  "answer": """
351
- Start with time complexity and identify whether an inefficient algorithm
352
- is responsible. Then investigate repeated computation, I/O, allocations,
353
- database operations, and memory usage.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
354
  """
355
  },
356
 
357
  {
358
- "category": "software_architecture",
359
- "question": """
360
- Why might an AI inference service use a queue between its API and GPU
361
- worker?
362
  """,
363
  "reasoning": """
364
- GPU inference is expensive and unlimited concurrent requests can exhaust
365
- GPU memory or reduce stability.
366
 
367
- A queue provides controlled concurrency and backpressure. It also lets
368
- the API layer and inference workers scale independently.
369
  """,
370
  "answer": """
371
- A queue controls inference concurrency, provides backpressure, handles
372
- traffic spikes, protects GPU memory, and separates API scaling from
373
- inference-worker scaling.
374
  """
375
  },
376
 
377
  {
378
- "category": "edge_cases",
379
- "question": """
380
- What edge cases should be considered when writing a function that finds
381
- the second largest distinct number in a list?
382
  """,
383
  "reasoning": """
384
- Consider an empty list, one distinct value, duplicate maximum values,
385
- negative numbers, and lists containing exactly two distinct values.
386
 
387
- The word distinct means repeated copies of the maximum do not count as
388
- a second value.
 
389
  """,
390
  "answer": """
391
- Important cases include empty input, fewer than two distinct values,
392
- duplicates, negative values, and exactly two distinct values.
 
393
  """
394
  },
395
 
396
  {
397
- "category": "probability",
398
- "question": """
399
- A fair six-sided die is rolled twice. What is the probability that the
400
- second roll is greater than the first?
401
  """,
402
  "reasoning": """
403
- There are 36 equally likely ordered outcomes.
404
 
405
- For every pair of different values, exactly one ordering has the second
406
- roll greater than the first. There are 6 choose 2 = 15 such pairs.
 
 
 
 
 
 
 
407
 
408
- Therefore the probability is 15/36 = 5/12.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  """,
410
  "answer": """
411
- 5/12.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  """
413
  },
414
 
415
  {
416
  "category": "systems",
417
- "question": """
418
- How would you design a service that serves a language model to many
419
- users while using only one GPU?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
  """,
421
  "reasoning": """
422
- Load the model once and keep it resident on the GPU.
423
 
424
- Use controlled concurrency so requests do not independently load model
425
- weights. Queue requests when necessary and batch compatible requests to
426
- improve GPU utilization.
427
 
428
- Limit input and output sizes, monitor GPU memory, and isolate user
429
- sessions from one another.
430
  """,
431
  "answer": """
432
- Load one shared model instance, use a request queue and controlled
433
- concurrency, batch requests when useful, enforce token limits, monitor
434
- GPU memory, and maintain separate session state for each user.
435
  """
436
  }
 
437
  ]
438
 
439
 
440
  # ============================================================
441
- # KEYWORDS
442
  # ============================================================
443
 
444
- REASONING_KEYWORDS = {
445
- "algorithm",
446
- "algorithms",
447
- "complexity",
448
- "optimize",
449
- "optimization",
450
- "debug",
451
- "bug",
452
- "error",
453
- "implement",
454
- "implementation",
455
- "code",
456
- "coding",
457
- "python",
458
- "javascript",
459
- "typescript",
460
- "function",
461
- "calculate",
462
- "prove",
463
- "proof",
464
- "derive",
465
- "analyze",
466
- "analysis",
467
- "compare",
468
- "design",
469
- "architecture",
470
- "data structure",
471
- "recursion",
472
- "dynamic programming",
473
- "graph",
474
- "tree",
475
- "array",
476
- "database",
477
- "sql",
478
- "regex",
479
- "performance",
480
- "efficient",
481
- "edge case",
482
- "logic",
483
- "probability",
484
- "equation",
485
- "mathematical",
486
- "reason",
487
- "reasoning",
488
- "solve",
489
- "solution",
490
- "constraint",
491
- "complex"
492
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
 
494
 
495
  # ============================================================
496
- # LIFESPAN
497
  # ============================================================
498
 
499
  @asynccontextmanager
@@ -518,15 +1101,14 @@ async def lifespan(app: FastAPI):
518
  model_name = "Qwen/Qwen2.5-3B-Instruct"
519
 
520
  logger.info(
521
- f"Loading {model_name} on {device}"
522
  )
523
 
524
  tokenizer = AutoTokenizer.from_pretrained(
525
  model_name
526
  )
527
 
528
- if tokenizer.pad_token is None:
529
- tokenizer.pad_token = tokenizer.eos_token
530
 
531
  config = AutoConfig.from_pretrained(
532
  model_name
@@ -538,11 +1120,7 @@ async def lifespan(app: FastAPI):
538
  model_name,
539
  config=config,
540
  torch_dtype=dtype,
541
- device_map=(
542
- "auto"
543
- if device == "cuda"
544
- else None
545
- )
546
  ).eval()
547
 
548
  embed_model = SentenceTransformer(
@@ -553,18 +1131,19 @@ async def lifespan(app: FastAPI):
553
  engine_state["tokenizer"] = tokenizer
554
  engine_state["model"] = model
555
  engine_state["embed_model"] = embed_model
 
556
  engine_state["max_history"] = 15
557
- engine_state["device"] = device
558
 
559
- logger.info(
560
- f"Model successfully loaded on {device}"
561
- )
562
 
563
  logger.info(
564
- f"Reasoning examples available: "
565
- f"{len(REASONING_EXAMPLES)}"
566
  )
567
 
 
 
 
568
  yield
569
 
570
  engine_state.clear()
@@ -575,7 +1154,7 @@ async def lifespan(app: FastAPI):
575
 
576
 
577
  # ============================================================
578
- # APP
579
  # ============================================================
580
 
581
  app = FastAPI(
@@ -607,7 +1186,7 @@ class ChatRequest(BaseModel):
607
 
608
 
609
  # ============================================================
610
- # SESSION
611
  # ============================================================
612
 
613
  def fetch_or_create_session(
@@ -617,7 +1196,7 @@ def fetch_or_create_session(
617
  if session_id in session_store:
618
  return session_store[session_id]
619
 
620
- session = {
621
  "history": [],
622
  "last_entity": "",
623
  "last_web_query": "",
@@ -625,15 +1204,20 @@ def fetch_or_create_session(
625
  "last_web_context": []
626
  }
627
 
628
- session_store[session_id] = session
629
 
630
- return session
631
 
632
 
633
  def save_session_to_redis(
634
  session_id: str,
635
  session: dict
636
  ):
 
 
 
 
 
637
 
638
  session_store[session_id] = session
639
 
@@ -644,21 +1228,15 @@ def update_history_buffer(
644
  content: str
645
  ):
646
 
647
- session["history"].append({
648
- "role": role,
649
- "content": content
650
- })
651
-
652
- max_history = engine_state.get(
653
- "max_history",
654
- 15
655
  )
656
 
657
- if len(session["history"]) > max_history:
658
-
659
- session["history"] = session[
660
- "history"
661
- ][-max_history:]
662
 
663
 
664
  # ============================================================
@@ -714,14 +1292,9 @@ def checks_pronoun_reference(
714
  "after"
715
  }
716
 
717
- words = set(
718
- query.lower().split()
719
- )
720
-
721
- return bool(
722
- words.intersection(
723
- pronoun_references
724
- )
725
  )
726
 
727
 
@@ -730,47 +1303,30 @@ def is_followup(
730
  session: dict
731
  ) -> bool:
732
 
733
- if not session.get(
734
- "last_entity"
735
- ):
736
-
737
  return False
738
 
739
  scores = []
740
 
741
  try:
742
-
743
  scores.append(
744
  calculate_semantic_similarity(
745
  query,
746
- session[
747
- "last_entity"
748
- ]
749
  )
750
  )
751
-
752
  except Exception:
753
-
754
  pass
755
 
756
  try:
757
-
758
- last_response = session.get(
759
- "last_response",
760
- ""
761
- )
762
-
763
- if last_response:
764
-
765
  scores.append(
766
  calculate_semantic_similarity(
767
  query,
768
- last_response[:500]
769
  )
770
  )
771
-
772
  except Exception:
773
-
774
  pass
775
 
776
  best_match = max(
@@ -799,9 +1355,7 @@ def execute_llm_generation(
799
 
800
  inputs = tokenizer(
801
  prompt,
802
- return_tensors="pt",
803
- truncation=True,
804
- max_length=8192
805
  )
806
 
807
  inputs = {
@@ -811,30 +1365,27 @@ def execute_llm_generation(
811
 
812
  with torch.no_grad():
813
 
814
- output = model.generate(
815
  **inputs,
816
  max_new_tokens=max_new_tokens,
817
- temperature=0.20,
818
- top_p=0.90,
819
  repetition_penalty=1.08,
820
  do_sample=True,
821
- pad_token_id=tokenizer.pad_token_id,
822
- eos_token_id=tokenizer.eos_token_id
823
  )
824
 
825
- generated = output[
826
  0
827
  ][
828
  inputs["input_ids"].shape[1]:
829
  ]
830
 
831
- result = tokenizer.decode(
832
- generated,
833
  skip_special_tokens=True
834
  ).strip()
835
 
836
- return result
837
-
838
 
839
  # ============================================================
840
  # TOPIC EXTRACTION
@@ -847,11 +1398,11 @@ def run_topic_extraction(
847
  prompt = f"""
848
  Extract the primary subject of this question.
849
 
850
- Question:
851
  {query}
852
 
853
  Return only the subject.
854
- """
855
 
856
  return execute_llm_generation(
857
  prompt,
@@ -860,7 +1411,7 @@ Return only the subject.
860
 
861
 
862
  # ============================================================
863
- # FOLLOW-UP QUERY REWRITE
864
  # ============================================================
865
 
866
  def rewrite_followup_query(
@@ -868,42 +1419,36 @@ def rewrite_followup_query(
868
  session: dict
869
  ) -> str:
870
 
871
- context = "\n".join(
872
  [
873
- (
874
- f"User: {item.get('user', '')}\n"
875
- f"Search: {item.get('search', '')}"
876
- )
877
- for item in session.get(
878
- "last_web_context",
879
- []
880
- )[-5:]
881
  ]
882
  )
883
 
884
  prompt = f"""
885
- Rewrite the user's follow-up as a standalone web search query.
 
886
 
887
- Previous subject:
888
- {session.get('last_entity', '')}
889
 
890
- Recent searches:
891
- {context}
892
-
893
- Follow-up:
894
  {query}
895
 
896
- Return ONLY the standalone search query.
897
- """
 
 
898
 
899
  return execute_llm_generation(
900
  prompt,
901
- max_new_tokens=96
902
  ).split("\n")[0].strip()
903
 
904
 
905
  # ============================================================
906
- # WEB SEARCH DECISION
907
  # ============================================================
908
 
909
  def should_trigger_web_search(
@@ -911,7 +1456,7 @@ def should_trigger_web_search(
911
  session: dict
912
  ) -> bool:
913
 
914
- q_lower = q.lower()
915
 
916
  search_triggers = [
917
  "who",
@@ -923,596 +1468,244 @@ def should_trigger_web_search(
923
  "founder",
924
  "ceo",
925
  "company",
926
- "price",
927
- "stock",
928
- "news",
929
- "latest",
930
- "current",
931
- "today",
932
- "code",
933
- "python",
934
- "html"
935
- ]
936
-
937
- if any(
938
- trigger in q_lower
939
- for trigger in search_triggers
940
- ):
941
-
942
- return True
943
-
944
- return is_followup(
945
- q,
946
- session
947
- )
948
-
949
-
950
- # ============================================================
951
- # DUCKDUCKGO
952
- # ============================================================
953
-
954
- def isolated_blocking_ddg(
955
- query_string: str
956
- ) -> list:
957
-
958
- try:
959
-
960
- with DDGS() as ddgs:
961
-
962
- results = ddgs.text(
963
- query_string,
964
- max_results=5
965
- )
966
-
967
- if results:
968
-
969
- return [
970
- result["href"]
971
- for result in results
972
- if "href" in result
973
- ]
974
-
975
- except Exception as e:
976
-
977
- logger.error(
978
- f"DuckDuckGo search failed: {e}"
979
- )
980
-
981
- return []
982
-
983
-
984
- async def search_web_async(
985
- query: str
986
- ) -> list:
987
-
988
- urls = await asyncio.to_thread(
989
- isolated_blocking_ddg,
990
- query
991
- )
992
-
993
- if not urls:
994
-
995
- urls.append(
996
- "https://en.wikipedia.org/wiki/"
997
- + query.replace(
998
- " ",
999
- "_"
1000
- )
1001
- )
1002
-
1003
- return list(
1004
- dict.fromkeys(urls)
1005
- )[:5]
1006
-
1007
-
1008
- # ============================================================
1009
- # SCRAPING
1010
- # ============================================================
1011
-
1012
- async def scrape_target_url(
1013
- client,
1014
- url: str
1015
- ):
1016
-
1017
- try:
1018
-
1019
- response = await client.get(
1020
- url,
1021
- timeout=10.0
1022
- )
1023
-
1024
- if response.status_code != 200:
1025
-
1026
- logger.warning(
1027
- f"HTTP {response.status_code}: {url}"
1028
- )
1029
-
1030
- return None
1031
-
1032
- text = None
1033
-
1034
- try:
1035
-
1036
- text = extract(
1037
- response.text
1038
- )
1039
-
1040
- except Exception:
1041
-
1042
- pass
1043
-
1044
- if not text:
1045
-
1046
- try:
1047
-
1048
- soup = BeautifulSoup(
1049
- response.text,
1050
- "html.parser"
1051
- )
1052
-
1053
- text = soup.get_text(
1054
- separator=" ",
1055
- strip=True
1056
- )
1057
-
1058
- except Exception:
1059
-
1060
- return None
1061
-
1062
- if not text:
1063
-
1064
- return None
1065
-
1066
- if len(
1067
- text.split()
1068
- ) < 20:
1069
-
1070
- return None
1071
-
1072
- return {
1073
- "url": url,
1074
- "domain": urlparse(url).netloc,
1075
- "text": text[:2000]
1076
- }
1077
-
1078
- except Exception as e:
1079
-
1080
- logger.warning(
1081
- f"Scraping error for {url}: {e}"
1082
- )
1083
-
1084
- return None
1085
-
1086
-
1087
- async def build_context_pipeline(
1088
- query: str
1089
- ) -> tuple:
1090
-
1091
- context_blocks = []
1092
- sources = []
1093
-
1094
- target_urls = await search_web_async(
1095
- query
1096
- )
1097
-
1098
- headers = {
1099
- "User-Agent":
1100
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
1101
- "AppleWebKit/537.36 "
1102
- "(KHTML, like Gecko) "
1103
- "Chrome/122.0.0.0 Safari/537.36",
1104
- "Accept":
1105
- "text/html,application/xhtml+xml,"
1106
- "application/xml;q=0.9,*/*;q=0.8",
1107
- "Accept-Language":
1108
- "en-US,en;q=0.9"
1109
- }
1110
-
1111
- from curl_cffi.requests import AsyncSession
1112
-
1113
- async with AsyncSession(
1114
- impersonate="chrome",
1115
- headers=headers
1116
- ) as client:
1117
-
1118
- tasks = [
1119
- scrape_target_url(
1120
- client,
1121
- url
1122
- )
1123
- for url in target_urls
1124
- ]
1125
-
1126
- results = await asyncio.gather(
1127
- *tasks
1128
- )
1129
-
1130
- for result in results:
1131
-
1132
- if result:
1133
-
1134
- context_blocks.append(
1135
- f"[{result['domain']}] "
1136
- f"{result['text']}"
1137
- )
1138
-
1139
- sources.append(
1140
- result["url"]
1141
- )
1142
-
1143
- return (
1144
- "\n\n".join(context_blocks),
1145
- list(set(sources))
1146
- )
1147
-
1148
-
1149
- # ============================================================
1150
- # REASONING COMPLEXITY
1151
- # ============================================================
1152
-
1153
- def calculate_reasoning_complexity(
1154
- query: str
1155
- ) -> float:
1156
-
1157
- q = query.lower()
1158
-
1159
- score = 0.0
1160
-
1161
- word_count = len(
1162
- q.split()
1163
- )
1164
-
1165
- if word_count > 20:
1166
- score += 0.15
1167
-
1168
- if word_count > 50:
1169
- score += 0.15
1170
-
1171
- matches = sum(
1172
- 1
1173
- for keyword in REASONING_KEYWORDS
1174
- if keyword in q
1175
- )
1176
-
1177
- score += min(
1178
- matches * 0.08,
1179
- 0.55
1180
- )
1181
-
1182
- strong_phrases = [
1183
- "time complexity",
1184
- "space complexity",
1185
- "big o",
1186
- "edge case",
1187
- "step by step",
1188
- "prove that",
1189
- "find the bug",
1190
- "optimize this",
1191
- "implement",
1192
- "derive",
1193
- "analyze"
1194
- ]
1195
-
1196
- if any(
1197
- phrase in q
1198
- for phrase in strong_phrases
1199
- ):
1200
-
1201
- score += 0.25
1202
-
1203
- return min(
1204
- score,
1205
- 1.0
1206
- )
1207
-
1208
-
1209
- # ============================================================
1210
- # CODING DETECTION
1211
- # ============================================================
1212
-
1213
- def detect_coding_request(
1214
- query: str
1215
- ) -> bool:
1216
-
1217
- q = query.lower()
1218
-
1219
- coding_terms = [
1220
- "python",
1221
- "javascript",
1222
- "typescript",
1223
- "java",
1224
- "c++",
1225
- "rust",
1226
- "golang",
1227
  "code",
1228
- "coding",
1229
- "function",
1230
- "class",
1231
- "api",
1232
- "debug",
1233
- "bug",
1234
- "algorithm",
1235
- "sql",
1236
- "regex",
1237
  "html",
1238
- "css",
1239
- "json",
1240
- "docker",
1241
- "fastapi",
1242
- "flask",
1243
- "script",
1244
- "program"
1245
  ]
1246
 
1247
- return any(
1248
- term in q
1249
- for term in coding_terms
 
 
 
 
 
 
1250
  )
1251
 
1252
 
1253
  # ============================================================
1254
- # SELECT REASONING EXAMPLES
1255
  # ============================================================
1256
 
1257
- def select_reasoning_examples(
1258
- query: str,
1259
- count: int = 3
1260
  ) -> list:
1261
 
1262
- if not REASONING_EXAMPLES:
1263
-
1264
- return []
1265
 
1266
- query_lower = query.lower()
1267
 
1268
- query_words = set(
1269
- re.findall(
1270
- r"[a-zA-Z]{3,}",
1271
- query_lower
1272
- )
1273
- )
1274
 
1275
- scored = []
1276
 
1277
- for example in REASONING_EXAMPLES:
 
 
 
 
1278
 
1279
- score = 0.0
1280
 
1281
- category = example[
1282
- "category"
1283
- ].lower().replace(
1284
- "_",
1285
- " "
1286
  )
1287
 
1288
- question = example[
1289
- "question"
1290
- ].lower()
1291
-
1292
- category_words = set(
1293
- category.split()
1294
- )
1295
 
1296
- question_words = set(
1297
- re.findall(
1298
- r"[a-zA-Z]{3,}",
1299
- question
1300
- )
1301
- )
1302
 
1303
- score += len(
1304
- category_words &
1305
- query_words
1306
- ) * 1.5
1307
-
1308
- score += min(
1309
- len(
1310
- question_words &
1311
- query_words
1312
- ) * 0.12,
1313
- 1.5
1314
- )
1315
 
1316
- if (
1317
- detect_coding_request(query)
1318
- and example["category"]
1319
- in {
1320
- "algorithm",
1321
- "debugging",
1322
- "coding",
1323
- "optimization",
1324
- "complexity",
1325
- "dynamic_programming",
1326
- "binary_search"
1327
- }
1328
- ):
1329
 
1330
- score += 1.5
1331
 
1332
- scored.append(
1333
- (
1334
- score,
1335
- example
1336
- )
1337
  )
1338
 
1339
- scored.sort(
1340
- key=lambda item: item[0],
1341
- reverse=True
1342
- )
1343
-
1344
- return [
1345
- item[1]
1346
- for item in scored[:count]
1347
- ]
1348
 
1349
 
1350
  # ============================================================
1351
- # BUILD REASONING PROMPT
1352
  # ============================================================
1353
 
1354
- def build_reasoning_instruction(
1355
- query: str
1356
- ) -> str:
 
1357
 
1358
- if not REASONING_ENABLED:
1359
 
1360
- return ""
 
 
 
1361
 
1362
- complexity = calculate_reasoning_complexity(
1363
- query
1364
- )
1365
 
1366
- if complexity < 0.20:
 
 
1367
 
1368
- return ""
1369
 
1370
- examples = select_reasoning_examples(
1371
- query,
1372
- REASONING_EXAMPLES_PER_REQUEST
1373
- )
1374
 
1375
- demonstrations = []
1376
 
1377
- for index, example in enumerate(
1378
- examples,
1379
- 1
1380
- ):
1381
 
1382
- question = example[
1383
- "question"
1384
- ].strip()
1385
 
1386
- reasoning = example[
1387
- "reasoning"
1388
- ].strip()
1389
 
1390
- answer = example[
1391
- "answer"
1392
- ].strip()
1393
 
1394
- question = question[
1395
- :REASONING_MAX_EXAMPLE_CHARS
1396
- ]
1397
 
1398
- reasoning = reasoning[
1399
- :REASONING_MAX_EXAMPLE_CHARS
1400
- ]
 
1401
 
1402
- answer = answer[
1403
- :REASONING_MAX_EXAMPLE_CHARS
1404
- ]
 
1405
 
1406
- demonstrations.append(
1407
- f"""
1408
- EXAMPLE {index}
1409
 
1410
- Problem:
1411
- {question}
 
1412
 
1413
- Reasoning:
1414
- {reasoning}
1415
 
1416
- Answer:
1417
- {answer}
1418
- """
1419
- )
1420
 
1421
- coding_rules = ""
 
 
 
 
1422
 
1423
- if detect_coding_request(
1424
- query
1425
- ):
 
 
 
 
1426
 
1427
- coding_rules = """
1428
- For programming questions:
1429
- - Understand the requirements before coding.
1430
- - Produce valid code.
1431
- - Consider edge cases.
1432
- - Explain important design decisions.
1433
- - Give accurate time complexity.
1434
- - Give accurate space complexity when relevant.
1435
- - If debugging, identify the cause before proposing the fix.
1436
- """
1437
 
1438
- return f"""
1439
- REASONING GUIDANCE
 
1440
 
1441
- Use the following examples only as demonstrations of useful
1442
- problem-solving patterns.
 
1443
 
1444
- IMPORTANT OUTPUT RULES:
1445
- - Solve ONLY the current user question.
1446
- - Do NOT repeat the examples.
1447
- - Do NOT repeat the user's question.
1448
- - Do NOT generate "Human:" messages.
1449
- - Do NOT generate "Assistant:" messages.
1450
- - Do NOT continue an example conversation.
1451
- - Do NOT produce multiple simulated conversations.
1452
- - Give one answer to the current question.
1453
- - Stop after answering the current question.
1454
 
1455
- For difficult problems:
1456
- 1. Identify the objective.
1457
- 2. Identify important constraints.
1458
- 3. Break the problem into useful steps.
1459
- 4. Select an appropriate method.
1460
- 5. Check the result.
1461
- 6. Give the final answer clearly.
1462
 
1463
- {coding_rules}
 
 
 
 
 
1464
 
1465
- {''.join(demonstrations)}
 
 
 
1466
 
1467
- CURRENT QUESTION:
1468
- {query}
1469
- """
1470
 
 
 
1471
 
1472
- # ============================================================
1473
- # CLEAN MODEL OUTPUT
1474
- # ============================================================
1475
 
1476
- def clean_model_response(
1477
- response: str
1478
- ) -> str:
1479
 
1480
- if not response:
1481
 
1482
- return ""
 
 
 
1483
 
1484
- response = response.strip()
 
 
 
 
 
 
1485
 
1486
- # Remove accidental leading assistant labels.
1487
- response = re.sub(
1488
- r"^\s*assistant\s*:\s*",
1489
- "",
1490
- response,
1491
- flags=re.IGNORECASE
1492
- )
1493
 
1494
- # If the model starts generating another conversation,
1495
- # stop before it.
1496
- markers = [
1497
- "\nHuman:",
1498
- "\nUser:",
1499
- "\nHuman :",
1500
- "\nUser :"
1501
- ]
1502
 
1503
- positions = [
1504
- response.find(marker)
1505
- for marker in markers
1506
- if response.find(marker) != -1
1507
- ]
1508
 
1509
- if positions:
 
 
 
1510
 
1511
- response = response[
1512
- :min(positions)
1513
- ].strip()
1514
 
1515
- return response
 
 
 
1516
 
1517
 
1518
  # ============================================================
@@ -1534,43 +1727,34 @@ async def process_ask_orchestration(
1534
  if (
1535
  use_web
1536
  and is_followup(q, session)
1537
- and session.get("last_entity")
1538
  ):
1539
 
1540
- try:
1541
-
1542
- search_query = (
1543
- rewrite_followup_query(
1544
- q,
1545
- session
1546
- )
1547
- )
1548
-
1549
- except Exception:
1550
-
1551
- search_query = q
1552
 
1553
  context = ""
1554
  sources = []
1555
 
 
 
 
 
1556
  if use_web:
1557
 
1558
- if not is_followup(
1559
- q,
1560
- session
1561
- ):
1562
 
1563
  try:
1564
 
1565
- session[
1566
- "last_entity"
1567
- ] = run_topic_extraction(q)
1568
 
1569
  except Exception:
1570
 
1571
- session[
1572
- "last_entity"
1573
- ] = q
1574
 
1575
  context, sources = (
1576
  await build_context_pipeline(
@@ -1578,9 +1762,7 @@ async def process_ask_orchestration(
1578
  )
1579
  )
1580
 
1581
- if len(
1582
- context.strip()
1583
- ) < 150:
1584
 
1585
  context, sources = (
1586
  await build_context_pipeline(
@@ -1598,103 +1780,119 @@ async def process_ask_orchestration(
1598
  )
1599
 
1600
  if len(
1601
- session[
1602
- "last_web_context"
1603
- ]
1604
  ) > 5:
1605
 
1606
  session[
1607
  "last_web_context"
1608
- ] = session[
1609
- "last_web_context"
1610
- ][-5:]
1611
 
1612
  session[
1613
  "last_web_query"
1614
  ] = search_query
1615
 
1616
- system_message = """
1617
- You are Infinity GRM, a helpful and accurate AI assistant.
 
1618
 
1619
- Answer ONLY the current user's question.
 
 
 
 
 
 
1620
 
1621
- Do not repeat previous conversations.
1622
- Do not generate fake Human/Assistant turns.
1623
- Do not continue examples.
1624
 
1625
- When web context is provided, use it as evidence.
1626
- Do not invent facts that are not supported by the context.
1627
 
1628
- For coding and reasoning problems, solve the problem carefully and
1629
- provide a clear final answer.
1630
 
1631
- You are a detailed research assistant.
1632
- Provide a structured comprehensive answer without bullet points.
1633
- Always use provided web context in your answer.
1634
- The answer must be long and detailed.
1635
- """
1636
 
1637
- user_message = (
1638
- f"WEB CONTEXT:\n"
1639
- f"{context}\n\n"
1640
- f"CURRENT QUESTION:\n"
1641
- f"{q}"
1642
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1643
 
1644
- messages = [
 
 
 
 
 
 
1645
  {
1646
  "role": "system",
1647
- "content": system_message
 
 
1648
  }
1649
- ]
 
 
 
 
 
 
1650
 
1651
- # Only include recent actual conversation turns.
1652
  messages.extend(
1653
- session[
1654
- "history"
1655
- ][-6:]
1656
  )
1657
 
1658
  messages.append(
1659
  {
1660
  "role": "user",
1661
- "content": user_message
 
 
 
 
1662
  }
1663
  )
1664
 
1665
  else:
1666
 
1667
- messages = [
1668
- {
1669
- "role": "system",
1670
- "content": """
1671
- You are Infinity GRM, a highly capable helpful AI assistant.
1672
-
1673
- Answer ONLY the current user's question.
1674
-
1675
- Do not repeat the question.
1676
- Do not generate Human: or Assistant: labels.
1677
- Do not repeat previous examples.
1678
- Do not continue previous conversations.
1679
- Do not output simulated conversations.
1680
-
1681
- For difficult reasoning questions, reason carefully before giving the
1682
- answer.
1683
-
1684
- For programming questions:
1685
- - Understand the problem.
1686
- - Write correct code.
1687
- - Consider edge cases.
1688
- - Explain important reasoning.
1689
- - Give accurate complexity analysis.
1690
- """
1691
- }
1692
- ]
1693
-
1694
  messages.extend(
1695
- session[
1696
- "history"
1697
- ][-6:]
1698
  )
1699
 
1700
  messages.append(
@@ -1704,82 +1902,28 @@ For programming questions:
1704
  }
1705
  )
1706
 
1707
- # ========================================================
1708
- # REASONING EXAMPLES
1709
- #
1710
- # Put the reasoning instruction INTO the current user
1711
- # message instead of creating another system-message layer.
1712
- # This avoids malformed chat formatting.
1713
- # ========================================================
1714
-
1715
- try:
1716
-
1717
- reasoning_instruction = (
1718
- build_reasoning_instruction(q)
1719
- )
1720
-
1721
- if reasoning_instruction:
1722
-
1723
- current_content = messages[-1][
1724
- "content"
1725
- ]
1726
-
1727
- messages[-1][
1728
- "content"
1729
- ] = (
1730
- reasoning_instruction
1731
- + "\n\n"
1732
- + current_content
1733
- )
1734
-
1735
- except Exception as e:
1736
-
1737
- logger.warning(
1738
- f"Reasoning prompt failed safely: {e}"
1739
- )
1740
-
1741
- # ========================================================
1742
  # CHAT TEMPLATE
1743
- # ========================================================
1744
-
1745
- tokenizer = engine_state[
1746
- "tokenizer"
1747
- ]
1748
 
1749
  compiled_prompt = (
1750
- tokenizer.apply_chat_template(
 
1751
  messages,
1752
  tokenize=False,
1753
  add_generation_prompt=True
1754
  )
1755
  )
1756
 
1757
- # ========================================================
1758
  # GENERATE
1759
- # ========================================================
1760
-
1761
- response_string = (
1762
- execute_llm_generation(
1763
- compiled_prompt,
1764
- max_new_tokens=2048
1765
- )
1766
- )
1767
 
1768
- response_string = clean_model_response(
1769
- response_string
 
1770
  )
1771
 
1772
- if not response_string:
1773
-
1774
- response_string = (
1775
- "I couldn't generate a response. "
1776
- "Please try the question again."
1777
- )
1778
-
1779
- # ========================================================
1780
- # SAVE HISTORY
1781
- # ========================================================
1782
-
1783
  session[
1784
  "last_response"
1785
  ] = response_string
@@ -1803,7 +1947,7 @@ For programming questions:
1803
 
1804
 
1805
  # ============================================================
1806
- # API
1807
  # ============================================================
1808
 
1809
  @app.post("/query")
@@ -1813,17 +1957,8 @@ async def chat_endpoint(
1813
 
1814
  try:
1815
 
1816
- if not request.message.strip():
1817
-
1818
- raise HTTPException(
1819
- status_code=400,
1820
- detail="Message cannot be empty."
1821
- )
1822
-
1823
- session = (
1824
- fetch_or_create_session(
1825
- request.session_id
1826
- )
1827
  )
1828
 
1829
  answer, sources = (
@@ -1843,14 +1978,10 @@ async def chat_endpoint(
1843
  "sources": sources
1844
  }
1845
 
1846
- except HTTPException:
1847
-
1848
- raise
1849
-
1850
  except Exception as e:
1851
 
1852
  logger.exception(
1853
- "Critical exception inside chat runtime."
1854
  )
1855
 
1856
  raise HTTPException(
@@ -1859,10 +1990,6 @@ async def chat_endpoint(
1859
  )
1860
 
1861
 
1862
- # ============================================================
1863
- # HEALTH
1864
- # ============================================================
1865
-
1866
  @app.get("/health")
1867
  async def validation_heartbeat():
1868
 
@@ -1870,35 +1997,26 @@ async def validation_heartbeat():
1870
  "status": "healthy",
1871
  "gpu_acceleration_active":
1872
  torch.cuda.is_available(),
1873
- "reasoning_enabled":
1874
- REASONING_ENABLED,
1875
  "reasoning_examples":
1876
  len(REASONING_EXAMPLES)
1877
  }
1878
 
1879
 
1880
  # ============================================================
1881
- # STATIC
1882
  # ============================================================
1883
 
1884
  if not os.path.exists("static"):
1885
-
1886
  os.makedirs("static")
1887
 
1888
 
1889
  app.mount(
1890
  "/static",
1891
- StaticFiles(
1892
- directory="static"
1893
- ),
1894
  name="static"
1895
  )
1896
 
1897
 
1898
- # ============================================================
1899
- # ROOT
1900
- # ============================================================
1901
-
1902
  @app.get("/")
1903
  async def home_route_processor():
1904
 
 
2
  import asyncio
3
  import logging
4
  import json
 
5
  import torch
6
  import numpy as np
7
  import httpx
 
40
 
41
 
42
  # ============================================================
43
+ # GLOBAL ENGINE STATE
44
  # ============================================================
45
 
46
  engine_state = {}
47
+
48
+ # Temporary in-memory session storage
49
+ # Replace with Redis later if needed.
50
  session_store = {}
51
 
52
 
53
  # ============================================================
54
+ # REASONING EXAMPLES
55
+ #
56
+ # These are intentionally concise.
57
+ #
58
+ # The model should learn:
59
+ # - how to identify the problem type
60
+ # - how to reject tempting wrong approaches
61
+ # - how to identify useful state/invariants
62
+ # - how to verify an answer
63
+ # - how to reason about complexity
64
+ #
65
+ # You can expand this list to 200-500+ examples.
66
  # ============================================================
67
 
68
+ REASONING_EXAMPLES = [
69
 
70
+ {
71
+ "category": "algorithms",
72
+ "problem": """
73
+ Given an array of integers that may contain negative numbers,
74
+ find the longest contiguous subarray whose sum equals k.
75
+ """,
76
+ "reasoning": """
77
+ Key observation:
78
+ Negative numbers make a normal sliding window unreliable because
79
+ adding an element can decrease the sum and removing an element can
80
+ increase it.
81
 
82
+ Use prefix sums.
 
83
 
84
+ If the current prefix sum is S, we need an earlier prefix sum S-k.
85
+ Store the earliest index of every prefix sum because the earliest
86
+ index gives the longest possible subarray.
87
+ """,
88
+ "answer": """
89
+ Use a hashmap of prefix sums.
90
 
91
+ prefix = {0: -1}
92
+ running_sum = 0
93
+ best = 0
 
 
 
94
 
95
+ For each index i:
96
+ running_sum += nums[i]
97
+
98
+ if running_sum - k exists:
99
+ best = max(best, i - prefix[running_sum - k])
100
+
101
+ store running_sum only if it has not been seen before.
102
+
103
+ Time: O(n) average.
104
+ Space: O(n).
105
+ """
106
+ },
107
 
108
  {
109
+ "category": "graphs",
110
+ "problem": """
111
+ A directed graph has positive edge costs. You may use a discount
112
+ exactly once, causing one chosen edge to cost floor(cost / 2).
113
+ Find the minimum cost from s to t.
114
  """,
115
  "reasoning": """
116
+ The cost of traversing an edge depends on whether the discount has
117
+ already been used.
118
+
119
+ Therefore one distance per node is insufficient.
120
+
121
+ Use two states:
122
+ (node, unused)
123
+ (node, used)
124
+
125
+ From an unused state, an edge can either be taken normally or with
126
+ the discount. From a used state, it must be taken normally.
127
+
128
+ Run Dijkstra on this expanded state graph.
129
  """,
130
  "answer": """
131
+ Dijkstra over 2*n states.
132
+
133
+ unused -> unused with cost c
134
+ unused -> used with cost c // 2
135
+ used -> used with cost c
136
+
137
+ Answer is the distance to t in the appropriate final state.
138
+
139
+ Time: O((n+m) log n).
140
+ Space: O(n+m).
141
+ """
142
+ },
143
+
144
+ {
145
+ "category": "algorithms",
146
+ "problem": """
147
+ Determine whether a directed graph contains a cycle in O(V+E).
148
+ """,
149
+ "reasoning": """
150
+ A visited set alone is insufficient for directed-cycle detection.
151
 
152
+ A node can be completely processed without forming a cycle, while
153
+ another edge can point back to a node currently being explored.
 
154
 
155
+ Use three states:
156
+ 0 = unvisited
157
+ 1 = currently exploring
158
+ 2 = completely processed
159
 
160
+ An edge to a state-1 node is a back edge and proves a cycle.
161
+ """,
162
+ "answer": """
163
+ Use DFS with three colors/states.
164
 
165
+ Time: O(V+E).
166
+ Space: O(V).
167
  """
168
  },
169
 
170
  {
171
+ "category": "data_structures",
172
+ "problem": """
173
+ Find the first non-repeating character in a string.
 
174
  """,
175
  "reasoning": """
176
+ The answer depends on both frequency and original order.
 
177
 
178
+ First count every character.
179
+ Then scan the string again and return the first character whose
180
+ frequency is one.
181
  """,
182
  "answer": """
183
+ Use a frequency hashmap followed by a second scan.
 
 
 
184
 
185
+ Time: O(n).
186
+ Space: O(k), where k is the number of distinct characters.
187
+ """
188
+ },
189
 
190
+ {
191
+ "category": "algorithms",
192
+ "problem": """
193
+ Given a sorted array, determine whether two numbers sum to target.
194
+ """,
195
+ "reasoning": """
196
+ Because the array is sorted, two pointers can eliminate many
197
+ possibilities at once.
198
 
199
+ If left + right is too small, increase left.
200
+ If it is too large, decrease right.
201
+ If equal, the target exists.
202
+ """,
203
+ "answer": """
204
+ Use two pointers.
205
 
206
+ Time: O(n).
207
+ Space: O(1).
208
+ """
209
+ },
210
 
211
+ {
212
+ "category": "algorithms",
213
+ "problem": """
214
+ Find the shortest path in an unweighted graph.
215
+ """,
216
+ "reasoning": """
217
+ Every edge has equal cost.
218
+
219
+ Therefore BFS explores nodes in increasing number of edges from
220
+ the source. The first time a node is reached is its shortest
221
+ distance from the source.
222
+ """,
223
+ "answer": """
224
+ Use BFS with a queue.
225
+
226
+ Time: O(V+E).
227
+ Space: O(V).
228
+ """
229
+ },
230
+
231
+ {
232
+ "category": "algorithms",
233
+ "problem": """
234
+ Find the shortest path in a graph with positive weighted edges.
235
+ """,
236
+ "reasoning": """
237
+ BFS is not sufficient because edges have different costs.
238
+
239
+ Since all edge weights are positive, Dijkstra's greedy selection
240
+ of the currently cheapest unsettled node is valid.
241
+ """,
242
+ "answer": """
243
+ Use Dijkstra with a min-heap.
244
+
245
+ Typical complexity:
246
+ O((V+E) log V).
247
+ """
248
+ },
249
+
250
+ {
251
+ "category": "algorithms",
252
+ "problem": """
253
+ A graph has only edge weights 0 and 1. Find shortest paths.
254
+ """,
255
+ "reasoning": """
256
+ Ordinary BFS treats every edge as having equal cost, which is wrong.
257
+
258
+ Dijkstra works, but a deque can exploit the special weights.
259
+
260
+ When traversing a zero-cost edge, add the node to the front.
261
+ When traversing a one-cost edge, add it to the back.
262
+ """,
263
+ "answer": """
264
+ Use 0-1 BFS.
265
+
266
+ Time: O(V+E).
267
+ Space: O(V).
268
+ """
269
+ },
270
+
271
+ {
272
+ "category": "dynamic_programming",
273
+ "problem": """
274
+ Given positive integers, maximize the sum without selecting adjacent
275
+ elements.
276
+ """,
277
+ "reasoning": """
278
+ At every position there are two choices:
279
+
280
+ Skip the current element.
281
+ Take the current element, which means the previous element cannot
282
+ be taken.
283
+
284
+ This gives:
285
+
286
+ dp[i] = max(dp[i-1], dp[i-2] + nums[i])
287
+
288
+ Only the previous two values are required.
289
+ """,
290
+ "answer": """
291
+ Use dynamic programming with O(1) extra space.
292
+
293
+ Time: O(n).
294
+ Space: O(1).
295
+ """
296
+ },
297
+
298
+ {
299
+ "category": "binary_search",
300
+ "problem": """
301
+ Find the minimum integer x for which a feasibility condition becomes
302
+ true.
303
+ """,
304
+ "reasoning": """
305
+ Binary search is appropriate only if feasibility is monotonic.
306
+
307
+ If x is feasible, and every larger x is also feasible, then the
308
+ search space has a false -> true boundary.
309
+
310
+ Search for the first true value.
311
+ """,
312
+ "answer": """
313
+ Use binary search on the answer.
314
+
315
+ At every midpoint, evaluate feasibility.
316
+
317
+ Time is O(log C) feasibility checks, where C is the search range.
318
+ """
319
+ },
320
+
321
+ {
322
+ "category": "algorithm_selection",
323
+ "problem": """
324
+ Should binary search automatically be used whenever an answer is a
325
+ number?
326
+ """,
327
+ "reasoning": """
328
+ No.
329
+
330
+ Binary search requires an ordered or monotonic search condition.
331
+ The fact that the answer is numeric is not enough.
332
+
333
+ First establish that a feasibility predicate has a monotonic
334
+ true/false boundary.
335
+ """,
336
+ "answer": """
337
+ Do not automatically use binary search.
338
+
339
+ Check monotonicity first.
340
  """
341
  },
342
 
343
  {
344
  "category": "debugging",
345
+ "problem": """
346
+ A function returns the first unique character but returns None too
347
+ early.
348
+
349
+ for c in s:
350
+ if counts[c] == 1:
351
+ return c
352
+ return None
353
+
354
+ Find the bug.
355
  """,
356
  "reasoning": """
357
+ The return None statement is inside the loop.
 
 
358
 
359
+ Therefore the function stops after checking only the first character
360
+ that is not unique.
361
+
362
+ The failure is control flow, not the counting logic.
363
  """,
364
  "answer": """
365
+ Move return None outside the loop.
 
366
  """
367
  },
368
 
369
  {
370
+ "category": "complexity",
371
+ "problem": """
372
+ What is the complexity of:
373
+
374
+ result = []
375
+ for x in values:
376
+ if x not in result:
377
+ result.append(x)
378
  """,
379
  "reasoning": """
380
+ Membership testing in a Python list is O(n).
381
 
382
+ That operation occurs for each input element.
 
383
 
384
+ Therefore the worst-case complexity is O(n²).
 
385
  """,
386
  "answer": """
387
+ Worst case:
388
+ Time: O(n²)
389
+ Space: O(n)
390
+
391
+ A set can reduce average membership checking to O(1).
392
  """
393
  },
394
 
395
  {
396
+ "category": "optimization",
397
+ "problem": """
398
+ Remove duplicates from a list while preserving original order.
 
 
399
  """,
400
  "reasoning": """
401
+ We need fast membership testing and must preserve the first
402
+ occurrence.
403
 
404
+ A set provides fast membership testing while the result list
405
+ preserves order.
406
  """,
407
  "answer": """
408
+ seen = set()
409
+ result = []
410
+
411
+ for x in values:
412
+ if x not in seen:
413
+ seen.add(x)
414
+ result.append(x)
415
+
416
+ Average time: O(n).
417
+ Space: O(n).
418
  """
419
  },
420
 
421
  {
422
+ "category": "stacks",
423
+ "problem": """
424
+ Determine whether parentheses, brackets, and braces are correctly
425
+ balanced and nested.
426
+ """,
427
+ "reasoning": """
428
+ The most recently opened bracket must be the first bracket closed.
429
+
430
+ That is last-in-first-out behavior, so a stack is appropriate.
431
+
432
+ When opening a bracket, push it.
433
+ When closing one, compare it with the stack top.
434
+ """,
435
+ "answer": """
436
+ Use a stack.
437
+
438
+ Time: O(n).
439
+ Space: O(n).
440
+ """
441
+ },
442
+
443
+ {
444
+ "category": "sliding_window",
445
+ "problem": """
446
+ Find the longest substring without repeating characters.
447
  """,
448
  "reasoning": """
449
+ Maintain a window containing unique characters.
450
 
451
+ When a repeated character appears, move the left boundary beyond
452
+ its previous occurrence.
453
 
454
+ The right pointer only moves forward, so each character is processed
455
+ a constant number of times.
456
+ """,
457
+ "answer": """
458
+ Use a sliding window and hashmap.
459
+
460
+ Time: O(n).
461
+ Space: O(k).
462
+ """
463
+ },
464
 
465
+ {
466
+ "category": "sorting",
467
+ "problem": """
468
+ Find the kth largest element without fully sorting the array.
469
+ """,
470
+ "reasoning": """
471
+ Full sorting costs O(n log n), but the problem only needs one order
472
+ statistic.
473
+
474
+ Quickselect partitions around a pivot and only recursively processes
475
+ the side containing the desired position.
476
  """,
477
  "answer": """
478
+ Quickselect has average O(n) time and O(1) auxiliary partition
479
+ space, depending on implementation.
480
+ """
481
+ },
482
+
483
+ {
484
+ "category": "graphs",
485
+ "problem": """
486
+ Determine whether an undirected graph is connected.
487
+ """,
488
+ "reasoning": """
489
+ Starting from any vertex, perform DFS or BFS.
490
+
491
+ If every vertex is reached, the graph is connected.
492
+ Otherwise, at least one vertex belongs to another component.
493
+ """,
494
+ "answer": """
495
+ DFS or BFS.
496
+
497
+ Time: O(V+E).
498
+ Space: O(V).
499
+ """
500
+ },
501
 
502
+ {
503
+ "category": "trees",
504
+ "problem": """
505
+ Find the maximum depth of a binary tree.
506
+ """,
507
+ "reasoning": """
508
+ The depth of a node is one plus the maximum depth of its children.
509
 
510
+ This naturally forms a recursive recurrence:
511
+ depth(node) = 1 + max(depth(left), depth(right)).
512
+ """,
513
+ "answer": """
514
+ Recursive DFS.
515
 
516
  Time: O(n).
517
+ Space: O(h), where h is tree height.
518
  """
519
  },
520
 
521
  {
522
+ "category": "trees",
523
+ "problem": """
524
+ Determine whether a binary tree is height-balanced.
525
  """,
526
  "reasoning": """
527
+ Checking the height independently for every node can repeatedly
528
+ traverse the same subtrees.
529
 
530
+ Instead, calculate height bottom-up.
 
531
 
532
+ Return failure immediately when a subtree is unbalanced.
533
  """,
534
  "answer": """
535
+ Use postorder DFS.
536
+
537
+ Return height or a sentinel indicating imbalance.
538
 
539
+ Time: O(n).
540
+ Space: O(h).
541
  """
542
  },
543
 
544
  {
545
+ "category": "greedy",
546
+ "problem": """
547
+ Can you always solve an optimization problem greedily by taking the
548
+ locally cheapest option?
549
  """,
550
  "reasoning": """
551
+ No.
552
+
553
+ A local choice may prevent a better global solution.
554
 
555
+ A greedy algorithm needs a proof that the local choice is safe,
556
+ usually through an exchange argument or another greedy property.
557
  """,
558
  "answer": """
559
+ Do not assume greedy works.
560
+ Find a counterexample or prove the greedy choice is always safe.
561
+ """
562
+ },
563
 
564
+ {
565
+ "category": "algorithm_selection",
566
+ "problem": """
567
+ You have a problem where a state can be reached in multiple ways,
568
+ and future options depend on how that state was reached. Should you
569
+ store only one distance per node?
570
+ """,
571
+ "reasoning": """
572
+ Not necessarily.
573
 
574
+ If the future behavior depends on additional information, that
575
+ information is part of the state.
576
 
577
+ For example, whether a discount has been used creates two distinct
578
+ states even when the physical node is identical.
579
+ """,
580
+ "answer": """
581
+ Expand the state representation.
582
 
583
+ Use (node, extra_state) rather than only node.
584
+ """
585
+ },
586
 
587
+ {
588
+ "category": "verification",
589
+ "problem": """
590
+ How should a newly written algorithm be tested?
591
+ """,
592
+ "reasoning": """
593
+ Do not test only the normal case.
594
+
595
+ Use:
596
+ - smallest valid input
597
+ - empty input when allowed
598
+ - duplicate values
599
+ - negative values
600
+ - boundary values
601
+ - already sorted/reversed data
602
+ - cases where the answer is impossible
603
+ - cases with multiple optimal answers
604
+ - large inputs for complexity
605
+ """,
606
+ "answer": """
607
+ Test normal cases, edge cases, adversarial cases, and large cases.
608
  """
609
  },
610
 
611
  {
612
  "category": "complexity",
613
+ "problem": """
614
+ An algorithm performs a binary search and runs Dijkstra for every
615
+ binary-search step. What happens to the complexity?
616
  """,
617
  "reasoning": """
618
+ The costs multiply.
 
619
 
620
+ If Dijkstra costs O(E log V) and binary search performs O(log C)
621
+ iterations, the total is:
622
+
623
+ O(E log V log C)
624
+
625
+ It cannot simply be described as O(E log V).
626
  """,
627
  "answer": """
628
+ Multiply nested algorithm costs.
629
+
630
+ Total:
631
+ O(E log V log C).
632
  """
633
  },
634
 
635
  {
636
+ "category": "coding",
637
+ "problem": """
638
+ Write a Python function that merges overlapping intervals.
 
639
  """,
640
  "reasoning": """
641
+ Sort intervals by starting point.
642
+
643
+ After sorting, the next interval either overlaps the current interval
644
+ or starts after it ends.
645
 
646
+ If it overlaps, extend the current end.
647
+ Otherwise, finalize the current interval and start a new one.
648
  """,
649
  "answer": """
650
+ Sort first, then scan.
651
 
652
+ Time: O(n log n).
653
+ Space: O(n) for the output.
 
 
654
  """
655
  },
656
 
657
  {
658
  "category": "coding",
659
+ "problem": """
660
+ Given a string, determine whether it is a palindrome after removing
661
+ non-alphanumeric characters and ignoring case.
662
  """,
663
  "reasoning": """
664
+ Normalization can be performed while comparing characters from both
665
+ ends.
666
 
667
+ Skip characters that are not alphanumeric.
668
+ Compare normalized characters.
669
  """,
670
  "answer": """
671
+ Two pointers.
672
+
673
+ Time: O(n).
674
+ Space: O(1) if normalization is performed in-place during scanning.
675
+ """
676
+ },
677
+
678
+ {
679
+ "category": "debugging",
680
+ "problem": """
681
+ A recursive function works on small inputs but crashes on very large
682
+ inputs.
683
+ """,
684
+ "reasoning": """
685
+ The algorithm may be correct, but Python recursion has a recursion
686
+ depth limitation.
687
+
688
+ First determine whether recursion depth is proportional to input
689
+ size. If so, consider an iterative stack or another formulation.
690
+ """,
691
+ "answer": """
692
+ Check recursion depth.
693
+ Convert to iteration when necessary.
694
  """
695
  },
696
 
697
  {
698
+ "category": "graphs",
699
+ "problem": """
700
+ Why can't ordinary Dijkstra handle negative edge weights?
 
701
  """,
702
  "reasoning": """
703
+ Dijkstra relies on the fact that once the smallest tentative
704
+ distance is selected, no later path can improve it.
705
 
706
+ A negative edge can produce a cheaper path later, violating that
707
+ assumption.
708
  """,
709
  "answer": """
710
+ Negative edges invalidate Dijkstra's greedy correctness condition.
711
+ Consider Bellman-Ford or another algorithm appropriate to the graph.
 
712
  """
713
  },
714
 
715
  {
716
+ "category": "math",
717
+ "problem": """
718
+ How can you determine whether an integer is a power of two?
 
719
  """,
720
  "reasoning": """
721
+ A positive power of two has exactly one bit set in binary.
 
722
 
723
+ For positive n:
724
+ n & (n - 1) == 0
725
+ exactly when n is a power of two.
726
  """,
727
  "answer": """
728
+ Check:
729
+
730
+ n > 0 and (n & (n - 1)) == 0
731
  """
732
  },
733
 
734
  {
735
+ "category": "algorithm_selection",
736
+ "problem": """
737
+ You are given a problem with n up to 200,000. A proposed solution
738
+ uses O(n²). What should you investigate?
739
  """,
740
  "reasoning": """
741
+ At n = 200,000, quadratic work is generally too large.
742
 
743
+ Look for a structural improvement:
744
+ hashing, sorting, two pointers, prefix sums, binary search,
745
+ divide-and-conquer, a heap, graph algorithms, or dynamic programming.
746
+ """,
747
+ "answer": """
748
+ Inspect the O(n²) bottleneck and search for an O(n log n) or O(n)
749
+ formulation.
750
+ """
751
+ },
752
 
753
+ {
754
+ "category": "problem_solving",
755
+ "problem": """
756
+ A solution works on the examples but fails hidden tests. What
757
+ should you investigate first?
758
+ """,
759
+ "reasoning": """
760
+ Check assumptions.
761
+
762
+ Typical causes:
763
+ incorrect boundary handling,
764
+ empty inputs,
765
+ duplicate values,
766
+ integer overflow,
767
+ incorrect state transitions,
768
+ wrong complexity,
769
+ or an algorithm that only works under unstated assumptions.
770
  """,
771
  "answer": """
772
+ Use adversarial and edge-case testing and verify the algorithm's
773
+ invariants.
774
+ """
775
+ },
776
+
777
+ {
778
+ "category": "invariants",
779
+ "problem": """
780
+ What is an invariant in an algorithm?
781
+ """,
782
+ "reasoning": """
783
+ An invariant is a property that remains true throughout the relevant
784
+ part of the algorithm.
785
+
786
+ Identifying one helps prove correctness and detect implementation
787
+ errors.
788
+ """,
789
+ "answer": """
790
+ State the property that must remain true after every iteration or
791
+ state transition.
792
+ """
793
+ },
794
+
795
+ {
796
+ "category": "dp",
797
+ "problem": """
798
+ How do you design a dynamic programming solution to an unfamiliar
799
+ problem?
800
+ """,
801
+ "reasoning": """
802
+ Start by identifying the smallest information needed to describe a
803
+ subproblem.
804
+
805
+ Then define:
806
+ 1. state
807
+ 2. transition
808
+ 3. base cases
809
+ 4. order of computation
810
+ 5. final state
811
+
812
+ Avoid creating a state containing unnecessary information.
813
+ """,
814
+ "answer": """
815
+ Define the state and transition before writing the implementation.
816
+ """
817
+ },
818
+
819
+ {
820
+ "category": "debugging",
821
+ "problem": """
822
+ A program produces the right answer but is too slow. What should
823
+ you do?
824
+ """,
825
+ "reasoning": """
826
+ Correctness and efficiency are separate properties.
827
+
828
+ First identify the dominant operation using complexity analysis or
829
+ profiling.
830
+
831
+ Then replace the bottleneck rather than randomly optimizing code.
832
+ """,
833
+ "answer": """
834
+ Find the dominant bottleneck, determine its complexity, and improve
835
+ the algorithm or data structure responsible.
836
+ """
837
+ },
838
+
839
+ {
840
+ "category": "coding",
841
+ "problem": """
842
+ Why should a dictionary store the earliest index of a prefix sum when
843
+ finding the longest subarray with a target sum?
844
+ """,
845
+ "reasoning": """
846
+ For a fixed ending index i, a smaller earlier index creates a longer
847
+ subarray.
848
+
849
+ Therefore replacing an existing prefix-sum index with a later index
850
+ can only make future answers shorter.
851
+ """,
852
+ "answer": """
853
+ Keep the earliest occurrence of each prefix sum.
854
+ """
855
+ },
856
+
857
+ {
858
+ "category": "logic",
859
+ "problem": """
860
+ A statement says: If A is true, then B is true. Does B being true
861
+ prove A is true?
862
+ """,
863
+ "reasoning": """
864
+ No.
865
+
866
+ The implication only establishes A -> B.
867
+
868
+ B may be true for other reasons. Inferring A from B is affirming the
869
+ consequent.
870
+ """,
871
+ "answer": """
872
+ No. A -> B does not imply B -> A.
873
+ """
874
+ },
875
+
876
+ {
877
+ "category": "coding",
878
+ "problem": """
879
+ A Python function needs to process a stream containing millions of
880
+ items, but all items cannot fit comfortably in memory. What should
881
+ you consider?
882
+ """,
883
+ "reasoning": """
884
+ Avoid loading the entire stream at once.
885
+
886
+ Process incrementally, keeping only the state needed to produce the
887
+ answer.
888
+
889
+ This is a streaming algorithm.
890
+ """,
891
+ "answer": """
892
+ Process items one at a time or in bounded batches.
893
  """
894
  },
895
 
896
  {
897
  "category": "systems",
898
+ "problem": """
899
+ Twenty users may request inference from the same GPU-hosted language
900
+ model simultaneously. How can the server avoid exhausting GPU
901
+ memory?
902
+ """,
903
+ "reasoning": """
904
+ Do not allow unlimited concurrent generation.
905
+
906
+ Use a request queue, concurrency limits, and preferably batching
907
+ where supported.
908
+
909
+ The model weights can be shared, while generation memory grows with
910
+ active requests and sequence lengths.
911
+ """,
912
+ "answer": """
913
+ Use bounded concurrency, request queuing, and controlled batching.
914
+ """
915
+ },
916
+
917
+ {
918
+ "category": "reasoning",
919
+ "problem": """
920
+ An algorithm's proof assumes all edge weights are positive, but the
921
+ implementation accepts negative weights. Is the proof still valid?
922
  """,
923
  "reasoning": """
924
+ No.
925
 
926
+ The implementation's input domain is broader than the proof's
927
+ assumptions.
 
928
 
929
+ Correctness guarantees only apply when the stated assumptions hold.
 
930
  """,
931
  "answer": """
932
+ No. Either enforce the positive-weight constraint or use an algorithm
933
+ whose correctness supports negative weights.
 
934
  """
935
  }
936
+
937
  ]
938
 
939
 
940
  # ============================================================
941
+ # EMBEDDING CACHE
942
  # ============================================================
943
 
944
+ reasoning_embeddings = None
945
+
946
+
947
+ def build_reasoning_embeddings():
948
+ """
949
+ Build embeddings once during startup so we don't repeatedly
950
+ encode the entire example library for every user request.
951
+ """
952
+ global reasoning_embeddings
953
+
954
+ if not REASONING_EXAMPLES:
955
+ reasoning_embeddings = None
956
+ return
957
+
958
+ embed_model = engine_state.get("embed_model")
959
+
960
+ if embed_model is None:
961
+ return
962
+
963
+ texts = []
964
+
965
+ for example in REASONING_EXAMPLES:
966
+ combined = (
967
+ example["category"]
968
+ + "\n"
969
+ + example["problem"]
970
+ + "\n"
971
+ + example["reasoning"]
972
+ )
973
+ texts.append(combined)
974
+
975
+ reasoning_embeddings = embed_model.encode(
976
+ texts,
977
+ normalize_embeddings=True,
978
+ convert_to_numpy=True
979
+ )
980
+
981
+ logger.info(
982
+ f"Loaded {len(REASONING_EXAMPLES)} reasoning examples."
983
+ )
984
+
985
+
986
+ def retrieve_reasoning_examples(
987
+ query: str,
988
+ top_k: int = 4
989
+ ) -> list:
990
+ """
991
+ Retrieve only the most relevant examples instead of inserting
992
+ the entire example library into every prompt.
993
+ """
994
+
995
+ global reasoning_embeddings
996
+
997
+ if not REASONING_EXAMPLES:
998
+ return []
999
+
1000
+ embed_model = engine_state.get("embed_model")
1001
+
1002
+ if embed_model is None:
1003
+ return REASONING_EXAMPLES[:top_k]
1004
+
1005
+ try:
1006
+ query_embedding = embed_model.encode(
1007
+ [query],
1008
+ normalize_embeddings=True,
1009
+ convert_to_numpy=True
1010
+ )[0]
1011
+
1012
+ if reasoning_embeddings is None:
1013
+ build_reasoning_embeddings()
1014
+
1015
+ scores = np.dot(
1016
+ reasoning_embeddings,
1017
+ query_embedding
1018
+ )
1019
+
1020
+ indices = np.argsort(scores)[::-1][:top_k]
1021
+
1022
+ selected = []
1023
+
1024
+ for index in indices:
1025
+ example = REASONING_EXAMPLES[int(index)].copy()
1026
+ example["_similarity"] = float(scores[int(index)])
1027
+ selected.append(example)
1028
+
1029
+ return selected
1030
+
1031
+ except Exception as e:
1032
+ logger.warning(
1033
+ f"Reasoning example retrieval failed: {e}"
1034
+ )
1035
+
1036
+ return REASONING_EXAMPLES[:top_k]
1037
+
1038
+
1039
+ def format_reasoning_examples(
1040
+ query: str,
1041
+ top_k: int = 4
1042
+ ) -> str:
1043
+ """
1044
+ Convert retrieved examples into compact prompt context.
1045
+ """
1046
+
1047
+ examples = retrieve_reasoning_examples(
1048
+ query,
1049
+ top_k=top_k
1050
+ )
1051
+
1052
+ if not examples:
1053
+ return ""
1054
+
1055
+ blocks = []
1056
+
1057
+ for i, example in enumerate(examples, 1):
1058
+
1059
+ blocks.append(
1060
+ f"""
1061
+ EXAMPLE {i}
1062
+ CATEGORY: {example["category"]}
1063
+
1064
+ PROBLEM:
1065
+ {example["problem"].strip()}
1066
+
1067
+ REASONING PATTERN:
1068
+ {example["reasoning"].strip()}
1069
+
1070
+ EXPECTED APPROACH:
1071
+ {example["answer"].strip()}
1072
+ """.strip()
1073
+ )
1074
+
1075
+ return "\n\n---\n\n".join(blocks)
1076
 
1077
 
1078
  # ============================================================
1079
+ # MODEL INITIALIZATION
1080
  # ============================================================
1081
 
1082
  @asynccontextmanager
 
1101
  model_name = "Qwen/Qwen2.5-3B-Instruct"
1102
 
1103
  logger.info(
1104
+ f"Loading model: {model_name}"
1105
  )
1106
 
1107
  tokenizer = AutoTokenizer.from_pretrained(
1108
  model_name
1109
  )
1110
 
1111
+ tokenizer.pad_token = tokenizer.eos_token
 
1112
 
1113
  config = AutoConfig.from_pretrained(
1114
  model_name
 
1120
  model_name,
1121
  config=config,
1122
  torch_dtype=dtype,
1123
+ device_map="auto" if device == "cuda" else None
 
 
 
 
1124
  ).eval()
1125
 
1126
  embed_model = SentenceTransformer(
 
1131
  engine_state["tokenizer"] = tokenizer
1132
  engine_state["model"] = model
1133
  engine_state["embed_model"] = embed_model
1134
+
1135
  engine_state["max_history"] = 15
 
1136
 
1137
+ # Number of relevant examples inserted into each prompt.
1138
+ engine_state["reasoning_examples"] = 4
 
1139
 
1140
  logger.info(
1141
+ f"Engine mapped to: {device}"
 
1142
  )
1143
 
1144
+ # Build example embeddings once.
1145
+ build_reasoning_embeddings()
1146
+
1147
  yield
1148
 
1149
  engine_state.clear()
 
1154
 
1155
 
1156
  # ============================================================
1157
+ # FASTAPI
1158
  # ============================================================
1159
 
1160
  app = FastAPI(
 
1186
 
1187
 
1188
  # ============================================================
1189
+ # SESSION MANAGEMENT
1190
  # ============================================================
1191
 
1192
  def fetch_or_create_session(
 
1196
  if session_id in session_store:
1197
  return session_store[session_id]
1198
 
1199
+ default_session = {
1200
  "history": [],
1201
  "last_entity": "",
1202
  "last_web_query": "",
 
1204
  "last_web_context": []
1205
  }
1206
 
1207
+ session_store[session_id] = default_session
1208
 
1209
+ return default_session
1210
 
1211
 
1212
  def save_session_to_redis(
1213
  session_id: str,
1214
  session: dict
1215
  ):
1216
+ """
1217
+ Temporary in-memory storage.
1218
+
1219
+ Replace with Redis later.
1220
+ """
1221
 
1222
  session_store[session_id] = session
1223
 
 
1228
  content: str
1229
  ):
1230
 
1231
+ session["history"].append(
1232
+ {
1233
+ "role": role,
1234
+ "content": content
1235
+ }
 
 
 
1236
  )
1237
 
1238
+ if len(session["history"]) > engine_state["max_history"]:
1239
+ session["history"].pop(0)
 
 
 
1240
 
1241
 
1242
  # ============================================================
 
1292
  "after"
1293
  }
1294
 
1295
+ return any(
1296
+ word in pronoun_references
1297
+ for word in query.lower().split()
 
 
 
 
 
1298
  )
1299
 
1300
 
 
1303
  session: dict
1304
  ) -> bool:
1305
 
1306
+ if not session["last_entity"]:
 
 
 
1307
  return False
1308
 
1309
  scores = []
1310
 
1311
  try:
 
1312
  scores.append(
1313
  calculate_semantic_similarity(
1314
  query,
1315
+ session["last_entity"]
 
 
1316
  )
1317
  )
 
1318
  except Exception:
 
1319
  pass
1320
 
1321
  try:
1322
+ if session["last_response"]:
 
 
 
 
 
 
 
1323
  scores.append(
1324
  calculate_semantic_similarity(
1325
  query,
1326
+ session["last_response"][:500]
1327
  )
1328
  )
 
1329
  except Exception:
 
1330
  pass
1331
 
1332
  best_match = max(
 
1355
 
1356
  inputs = tokenizer(
1357
  prompt,
1358
+ return_tensors="pt"
 
 
1359
  )
1360
 
1361
  inputs = {
 
1365
 
1366
  with torch.no_grad():
1367
 
1368
+ out = model.generate(
1369
  **inputs,
1370
  max_new_tokens=max_new_tokens,
1371
+ temperature=0.15,
1372
+ top_p=0.9,
1373
  repetition_penalty=1.08,
1374
  do_sample=True,
1375
+ pad_token_id=tokenizer.eos_token_id
 
1376
  )
1377
 
1378
+ generated_tokens = out[
1379
  0
1380
  ][
1381
  inputs["input_ids"].shape[1]:
1382
  ]
1383
 
1384
+ return tokenizer.decode(
1385
+ generated_tokens,
1386
  skip_special_tokens=True
1387
  ).strip()
1388
 
 
 
1389
 
1390
  # ============================================================
1391
  # TOPIC EXTRACTION
 
1398
  prompt = f"""
1399
  Extract the primary subject of this question.
1400
 
1401
+ Query:
1402
  {query}
1403
 
1404
  Return only the subject.
1405
+ """.strip()
1406
 
1407
  return execute_llm_generation(
1408
  prompt,
 
1411
 
1412
 
1413
  # ============================================================
1414
+ # FOLLOW-UP QUERY REWRITING
1415
  # ============================================================
1416
 
1417
  def rewrite_followup_query(
 
1419
  session: dict
1420
  ) -> str:
1421
 
1422
+ history = "\n".join(
1423
  [
1424
+ f"User: {x['user']}\nSearch: {x['search']}"
1425
+ for x in session["last_web_context"][-5:]
 
 
 
 
 
 
1426
  ]
1427
  )
1428
 
1429
  prompt = f"""
1430
+ Entity:
1431
+ {session["last_entity"]}
1432
 
1433
+ Recent conversation:
1434
+ {history}
1435
 
1436
+ User follow-up:
 
 
 
1437
  {query}
1438
 
1439
+ Rewrite the follow-up as a standalone web search query.
1440
+
1441
+ Return only the search query.
1442
+ """.strip()
1443
 
1444
  return execute_llm_generation(
1445
  prompt,
1446
+ max_new_tokens=128
1447
  ).split("\n")[0].strip()
1448
 
1449
 
1450
  # ============================================================
1451
+ # WEB SEARCH DETECTION
1452
  # ============================================================
1453
 
1454
  def should_trigger_web_search(
 
1456
  session: dict
1457
  ) -> bool:
1458
 
1459
+ q = q.lower()
1460
 
1461
  search_triggers = [
1462
  "who",
 
1468
  "founder",
1469
  "ceo",
1470
  "company",
1471
+ "price",
1472
+ "stock",
1473
+ "news",
1474
+ "latest",
1475
+ "today",
1476
+ "current",
1477
+ "recent",
1478
+ "update",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1479
  "code",
1480
+ "python",
 
 
 
 
 
 
 
 
1481
  "html",
1482
+ "javascript",
1483
+ "documentation",
1484
+ "release",
1485
+ "version"
 
 
 
1486
  ]
1487
 
1488
+ if any(
1489
+ trigger in q
1490
+ for trigger in search_triggers
1491
+ ):
1492
+ return True
1493
+
1494
+ return is_followup(
1495
+ q,
1496
+ session
1497
  )
1498
 
1499
 
1500
  # ============================================================
1501
+ # DUCKDUCKGO
1502
  # ============================================================
1503
 
1504
+ def isolated_blocking_ddg(
1505
+ query_string: str
 
1506
  ) -> list:
1507
 
1508
+ try:
 
 
1509
 
1510
+ with DDGS() as ddgs:
1511
 
1512
+ results = ddgs.text(
1513
+ query_string,
1514
+ max_results=5
1515
+ )
 
 
1516
 
1517
+ if results:
1518
 
1519
+ return [
1520
+ r["href"]
1521
+ for r in results
1522
+ if "href" in r
1523
+ ]
1524
 
1525
+ except Exception as e:
1526
 
1527
+ logger.error(
1528
+ f"Official DuckDuckGo Search failed: {e}"
 
 
 
1529
  )
1530
 
1531
+ return []
 
 
 
 
 
 
1532
 
 
 
 
 
 
 
1533
 
1534
+ async def search_web_async(
1535
+ query: str
1536
+ ) -> list:
 
 
 
 
 
 
 
 
 
1537
 
1538
+ urls = await asyncio.to_thread(
1539
+ isolated_blocking_ddg,
1540
+ query
1541
+ )
 
 
 
 
 
 
 
 
 
1542
 
1543
+ if not urls:
1544
 
1545
+ urls.append(
1546
+ "https://en.wikipedia.org/wiki/"
1547
+ + query.replace(" ", "_")
 
 
1548
  )
1549
 
1550
+ return list(
1551
+ dict.fromkeys(urls)
1552
+ )[:5]
 
 
 
 
 
 
1553
 
1554
 
1555
  # ============================================================
1556
+ # WEB SCRAPING
1557
  # ============================================================
1558
 
1559
+ async def scrape_target_url(
1560
+ client,
1561
+ url: str
1562
+ ):
1563
 
1564
+ try:
1565
 
1566
+ r = await client.get(
1567
+ url,
1568
+ timeout=10.0
1569
+ )
1570
 
1571
+ if r.status_code != 200:
 
 
1572
 
1573
+ logger.warning(
1574
+ f"HTTP {r.status_code}: {url}"
1575
+ )
1576
 
1577
+ return None
1578
 
1579
+ text = None
 
 
 
1580
 
1581
+ try:
1582
 
1583
+ text = extract(
1584
+ r.text
1585
+ )
 
1586
 
1587
+ except Exception as e:
 
 
1588
 
1589
+ logger.warning(
1590
+ f"Trafilatura failed for {url}: {e}"
1591
+ )
1592
 
1593
+ if not text:
 
 
1594
 
1595
+ try:
 
 
1596
 
1597
+ soup = BeautifulSoup(
1598
+ r.text,
1599
+ "html.parser"
1600
+ )
1601
 
1602
+ text = soup.get_text(
1603
+ separator=" ",
1604
+ strip=True
1605
+ )
1606
 
1607
+ except Exception as e:
 
 
1608
 
1609
+ logger.error(
1610
+ f"BeautifulSoup failed for {url}: {e}"
1611
+ )
1612
 
1613
+ return None
 
1614
 
1615
+ if not text or len(text.split()) < 20:
1616
+ return None
 
 
1617
 
1618
+ return {
1619
+ "url": url,
1620
+ "domain": urlparse(url).netloc,
1621
+ "text": text[:2500]
1622
+ }
1623
 
1624
+ except Exception as e:
1625
+
1626
+ logger.exception(
1627
+ f"Scraping error for {url}: {e}"
1628
+ )
1629
+
1630
+ return None
1631
 
 
 
 
 
 
 
 
 
 
 
1632
 
1633
+ # ============================================================
1634
+ # WEB CONTEXT PIPELINE
1635
+ # ============================================================
1636
 
1637
+ async def build_context_pipeline(
1638
+ query: str
1639
+ ) -> tuple:
1640
 
1641
+ context_blocks = []
1642
+ extraction_sources = []
 
 
 
 
 
 
 
 
1643
 
1644
+ target_urls = await search_web_async(
1645
+ query
1646
+ )
 
 
 
 
1647
 
1648
+ headers = {
1649
+ "User-Agent":
1650
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
1651
+ "AppleWebKit/537.36 "
1652
+ "(KHTML, like Gecko) "
1653
+ "Chrome/122.0.0.0 Safari/537.36",
1654
 
1655
+ "Accept":
1656
+ "text/html,application/xhtml+xml,"
1657
+ "application/xml;q=0.9,image/avif,"
1658
+ "image/webp,*/*;q=0.8",
1659
 
1660
+ "Accept-Language":
1661
+ "en-US,en;q=0.9",
 
1662
 
1663
+ "Accept-Encoding":
1664
+ "gzip, deflate, br",
1665
 
1666
+ "Connection":
1667
+ "keep-alive",
 
1668
 
1669
+ "Upgrade-Insecure-Requests":
1670
+ "1"
1671
+ }
1672
 
1673
+ from curl_cffi.requests import AsyncSession
1674
 
1675
+ async with AsyncSession(
1676
+ impersonate="chrome",
1677
+ headers=headers
1678
+ ) as client:
1679
 
1680
+ tasks = [
1681
+ scrape_target_url(
1682
+ client,
1683
+ u
1684
+ )
1685
+ for u in target_urls
1686
+ ]
1687
 
1688
+ scraped_results = await asyncio.gather(
1689
+ *tasks
1690
+ )
 
 
 
 
1691
 
1692
+ for result in scraped_results:
 
 
 
 
 
 
 
1693
 
1694
+ if result:
 
 
 
 
1695
 
1696
+ context_blocks.append(
1697
+ f"[{result['domain']}] "
1698
+ f"{result['text']}"
1699
+ )
1700
 
1701
+ extraction_sources.append(
1702
+ result["url"]
1703
+ )
1704
 
1705
+ return (
1706
+ "\n\n".join(context_blocks),
1707
+ list(set(extraction_sources))
1708
+ )
1709
 
1710
 
1711
  # ============================================================
 
1727
  if (
1728
  use_web
1729
  and is_followup(q, session)
1730
+ and session["last_entity"]
1731
  ):
1732
 
1733
+ search_query = rewrite_followup_query(
1734
+ q,
1735
+ session
1736
+ )
 
 
 
 
 
 
 
 
1737
 
1738
  context = ""
1739
  sources = []
1740
 
1741
+ # --------------------------------------------------------
1742
+ # WEB SEARCH
1743
+ # --------------------------------------------------------
1744
+
1745
  if use_web:
1746
 
1747
+ if not is_followup(q, session):
 
 
 
1748
 
1749
  try:
1750
 
1751
+ session["last_entity"] = (
1752
+ run_topic_extraction(q)
1753
+ )
1754
 
1755
  except Exception:
1756
 
1757
+ session["last_entity"] = q
 
 
1758
 
1759
  context, sources = (
1760
  await build_context_pipeline(
 
1762
  )
1763
  )
1764
 
1765
+ if len(context.strip()) < 150:
 
 
1766
 
1767
  context, sources = (
1768
  await build_context_pipeline(
 
1780
  )
1781
 
1782
  if len(
1783
+ session["last_web_context"]
 
 
1784
  ) > 5:
1785
 
1786
  session[
1787
  "last_web_context"
1788
+ ].pop(0)
 
 
1789
 
1790
  session[
1791
  "last_web_query"
1792
  ] = search_query
1793
 
1794
+ # --------------------------------------------------------
1795
+ # RETRIEVE REASONING EXAMPLES
1796
+ # --------------------------------------------------------
1797
 
1798
+ reasoning_context = format_reasoning_examples(
1799
+ q,
1800
+ top_k=engine_state.get(
1801
+ "reasoning_examples",
1802
+ 4
1803
+ )
1804
+ )
1805
 
1806
+ # --------------------------------------------------------
1807
+ # SYSTEM PROMPT
1808
+ # --------------------------------------------------------
1809
 
1810
+ system_prompt = """
1811
+ You are Infinity GRM, a capable reasoning and coding assistant.
1812
 
1813
+ Solve problems carefully rather than jumping to the first familiar
1814
+ algorithm.
1815
 
1816
+ For difficult problems:
 
 
 
 
1817
 
1818
+ 1. Identify exactly what the problem requires.
1819
+ 2. Identify important constraints and assumptions.
1820
+ 3. Determine the key property or structure.
1821
+ 4. Consider tempting approaches when useful.
1822
+ 5. Reject an approach if its assumptions do not hold.
1823
+ 6. Choose the appropriate algorithm or data structure.
1824
+ 7. Check important edge cases.
1825
+ 8. Verify the result.
1826
+ 9. Give the final answer clearly.
1827
+
1828
+ For programming problems:
1829
+ - Prefer correct and efficient algorithms.
1830
+ - Respect the stated constraints.
1831
+ - Explain important implementation decisions.
1832
+ - State time and space complexity.
1833
+ - Do not claim an algorithm is O(n) if it contains a hidden
1834
+ O(n log n), O(n²), or repeated expensive operation.
1835
+
1836
+ Use the provided reasoning examples as patterns, not as facts that
1837
+ must be copied.
1838
+
1839
+ If web context is provided, use it when relevant.
1840
+ Do not invent information that contradicts the provided context.
1841
+
1842
+ Do not blindly follow an example if the current problem is different.
1843
+ """.strip()
1844
+
1845
+ # --------------------------------------------------------
1846
+ # BUILD MESSAGES
1847
+ # --------------------------------------------------------
1848
+
1849
+ messages = [
1850
+ {
1851
+ "role": "system",
1852
+ "content": system_prompt
1853
+ }
1854
+ ]
1855
 
1856
+ # --------------------------------------------------------
1857
+ # REASONING EXAMPLES
1858
+ # --------------------------------------------------------
1859
+
1860
+ if reasoning_context:
1861
+
1862
+ messages.append(
1863
  {
1864
  "role": "system",
1865
+ "content":
1866
+ "RELEVANT REASONING EXAMPLES:\n\n"
1867
+ + reasoning_context
1868
  }
1869
+ )
1870
+
1871
+ # --------------------------------------------------------
1872
+ # WEB CONTEXT
1873
+ # --------------------------------------------------------
1874
+
1875
+ if use_web:
1876
 
 
1877
  messages.extend(
1878
+ session["history"][-10:]
 
 
1879
  )
1880
 
1881
  messages.append(
1882
  {
1883
  "role": "user",
1884
+ "content":
1885
+ f"WEB CONTEXT:\n"
1886
+ f"{context}\n\n"
1887
+ f"QUESTION:\n"
1888
+ f"{q}"
1889
  }
1890
  )
1891
 
1892
  else:
1893
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1894
  messages.extend(
1895
+ session["history"][-8:]
 
 
1896
  )
1897
 
1898
  messages.append(
 
1902
  }
1903
  )
1904
 
1905
+ # --------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1906
  # CHAT TEMPLATE
1907
+ # --------------------------------------------------------
 
 
 
 
1908
 
1909
  compiled_prompt = (
1910
+ engine_state["tokenizer"]
1911
+ .apply_chat_template(
1912
  messages,
1913
  tokenize=False,
1914
  add_generation_prompt=True
1915
  )
1916
  )
1917
 
1918
+ # --------------------------------------------------------
1919
  # GENERATE
1920
+ # --------------------------------------------------------
 
 
 
 
 
 
 
1921
 
1922
+ response_string = execute_llm_generation(
1923
+ compiled_prompt,
1924
+ max_new_tokens=2048
1925
  )
1926
 
 
 
 
 
 
 
 
 
 
 
 
1927
  session[
1928
  "last_response"
1929
  ] = response_string
 
1947
 
1948
 
1949
  # ============================================================
1950
+ # API ENDPOINTS
1951
  # ============================================================
1952
 
1953
  @app.post("/query")
 
1957
 
1958
  try:
1959
 
1960
+ session = fetch_or_create_session(
1961
+ request.session_id
 
 
 
 
 
 
 
 
 
1962
  )
1963
 
1964
  answer, sources = (
 
1978
  "sources": sources
1979
  }
1980
 
 
 
 
 
1981
  except Exception as e:
1982
 
1983
  logger.exception(
1984
+ "Critical exception inside chat runtime pipeline"
1985
  )
1986
 
1987
  raise HTTPException(
 
1990
  )
1991
 
1992
 
 
 
 
 
1993
  @app.get("/health")
1994
  async def validation_heartbeat():
1995
 
 
1997
  "status": "healthy",
1998
  "gpu_acceleration_active":
1999
  torch.cuda.is_available(),
 
 
2000
  "reasoning_examples":
2001
  len(REASONING_EXAMPLES)
2002
  }
2003
 
2004
 
2005
  # ============================================================
2006
+ # STATIC FILES
2007
  # ============================================================
2008
 
2009
  if not os.path.exists("static"):
 
2010
  os.makedirs("static")
2011
 
2012
 
2013
  app.mount(
2014
  "/static",
2015
+ StaticFiles(directory="static"),
 
 
2016
  name="static"
2017
  )
2018
 
2019
 
 
 
 
 
2020
  @app.get("/")
2021
  async def home_route_processor():
2022