afaceuser09 commited on
Commit
0630430
·
verified ·
1 Parent(s): 43f7918

Update main.py

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