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

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +810 -1201
main.py CHANGED
@@ -1,15 +1,14 @@
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,11 +18,71 @@ from fastapi.middleware.cors import CORSMiddleware
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,7 +92,7 @@ from transformers import (
33
 
34
  logging.basicConfig(
35
  level=logging.INFO,
36
- format="%(asctime)s - %(levelname)s - %(message)s"
37
  )
38
 
39
  logger = logging.getLogger("infinitygrm")
@@ -45,45 +104,44 @@ 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
 
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.
@@ -92,234 +150,275 @@ 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?
@@ -327,21 +426,24 @@ number?
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.
@@ -356,18 +458,22 @@ Find the bug.
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
 
@@ -377,32 +483,36 @@ for x in values:
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()
@@ -415,11 +525,15 @@ for x in values:
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.
@@ -429,120 +543,125 @@ 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?
@@ -550,90 +669,108 @@ locally cheapest option?
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
  """,
@@ -643,139 +780,172 @@ Sort intervals by starting point.
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
  """,
@@ -783,41 +953,49 @@ What is an invariant in an algorithm?
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?
@@ -828,34 +1006,43 @@ Correctness and efficiency are separate properties.
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?
@@ -863,59 +1050,46 @@ prove A is true?
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?
@@ -923,32 +1097,162 @@ implementation accepts negative weights. Is the proof still valid?
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:
@@ -960,37 +1264,28 @@ def build_reasoning_embeddings():
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
 
@@ -1003,50 +1298,120 @@ def retrieve_reasoning_examples(
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:
@@ -1060,14 +1425,16 @@ def format_reasoning_examples(
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
  )
@@ -1076,433 +1443,90 @@ EXPECTED APPROACH:
1076
 
1077
 
1078
  # ============================================================
1079
- # MODEL INITIALIZATION
1080
  # ============================================================
1081
 
1082
- @asynccontextmanager
1083
- async def lifespan(app: FastAPI):
1084
-
1085
- logger.info(
1086
- "Initializing neural weights and engine dependencies..."
1087
- )
1088
-
1089
- device = (
1090
- "cuda"
1091
- if torch.cuda.is_available()
1092
- else "cpu"
1093
- )
1094
-
1095
- dtype = (
1096
- torch.float16
1097
- if device == "cuda"
1098
- else torch.float32
1099
- )
1100
-
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
1115
- )
1116
-
1117
- config.pad_token_id = tokenizer.eos_token_id
1118
-
1119
- model = AutoModelForCausalLM.from_pretrained(
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(
1127
- "sentence-transformers/all-MiniLM-L6-v2",
1128
- device=device
1129
- )
1130
-
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()
1150
 
1151
- logger.info(
1152
- "Application context fully flushed."
 
 
1153
  )
1154
 
 
1155
 
1156
- # ============================================================
1157
- # FASTAPI
1158
- # ============================================================
1159
-
1160
- app = FastAPI(
1161
- title="Infinity GRM Engine",
1162
- lifespan=lifespan
1163
- )
1164
-
1165
-
1166
- # ============================================================
1167
- # CORS
1168
- # ============================================================
1169
-
1170
- app.add_middleware(
1171
- CORSMiddleware,
1172
- allow_origins=["*"],
1173
- allow_credentials=True,
1174
- allow_methods=["*"],
1175
- allow_headers=["*"],
1176
- )
1177
 
 
 
 
 
 
1178
 
1179
- # ============================================================
1180
- # REQUEST MODEL
1181
- # ============================================================
1182
-
1183
- class ChatRequest(BaseModel):
1184
- message: str
1185
- session_id: str
1186
-
1187
-
1188
- # ============================================================
1189
- # SESSION MANAGEMENT
1190
- # ============================================================
1191
-
1192
- def fetch_or_create_session(
1193
- session_id: str
1194
- ) -> dict:
1195
-
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": "",
1203
- "last_response": "",
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
-
1224
-
1225
- def update_history_buffer(
1226
- session: dict,
1227
- role: str,
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
- # ============================================================
1243
- # SEMANTIC SIMILARITY
1244
- # ============================================================
1245
 
1246
- def calculate_semantic_similarity(
1247
- a: str,
1248
- b: str
1249
- ) -> float:
1250
 
1251
- embedder = engine_state["embed_model"]
1252
 
1253
- embeddings = embedder.encode(
1254
- [a, b],
1255
- normalize_embeddings=True
1256
- )
1257
 
1258
- return float(
1259
- np.dot(
1260
- embeddings[0],
1261
- embeddings[1]
1262
  )
1263
- )
1264
-
1265
-
1266
- # ============================================================
1267
- # FOLLOW-UP DETECTION
1268
- # ============================================================
1269
-
1270
- def checks_pronoun_reference(
1271
- query: str
1272
- ) -> bool:
1273
-
1274
- pronoun_references = {
1275
- "he",
1276
- "him",
1277
- "his",
1278
- "she",
1279
- "her",
1280
- "hers",
1281
- "they",
1282
- "them",
1283
- "their",
1284
- "it",
1285
- "its",
1286
- "that",
1287
- "those",
1288
- "this",
1289
- "these",
1290
- "then",
1291
- "next",
1292
- "after"
1293
- }
1294
-
1295
- return any(
1296
- word in pronoun_references
1297
- for word in query.lower().split()
1298
- )
1299
-
1300
 
1301
- def is_followup(
1302
- query: str,
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(
1333
- scores,
1334
- default=0
1335
- )
1336
-
1337
- return (
1338
- best_match > 0.45
1339
- or len(query.split()) <= 3
1340
- or checks_pronoun_reference(query)
1341
- )
1342
 
 
 
1343
 
1344
- # ============================================================
1345
- # LLM GENERATION
1346
- # ============================================================
1347
-
1348
- def execute_llm_generation(
1349
- prompt: str,
1350
- max_new_tokens: int = 2048
1351
- ) -> str:
1352
-
1353
- tokenizer = engine_state["tokenizer"]
1354
- model = engine_state["model"]
1355
-
1356
- inputs = tokenizer(
1357
- prompt,
1358
- return_tensors="pt"
1359
- )
1360
 
1361
- inputs = {
1362
- key: value.to(model.device)
1363
- for key, value in inputs.items()
1364
- }
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
1392
- # ============================================================
1393
 
1394
- def run_topic_extraction(
1395
- query: str
 
1396
  ) -> str:
1397
 
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,
1409
- max_new_tokens=64
1410
- ).split("\n")[0].strip()
1411
-
1412
-
1413
- # ============================================================
1414
- # FOLLOW-UP QUERY REWRITING
1415
- # ============================================================
1416
-
1417
- def rewrite_followup_query(
1418
- query: str,
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(
1455
- q: str,
1456
- session: dict
1457
- ) -> bool:
1458
-
1459
- q = q.lower()
1460
-
1461
- search_triggers = [
1462
- "who",
1463
- "what",
1464
- "when",
1465
- "where",
1466
- "why",
1467
- "how",
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:
@@ -1511,45 +1535,44 @@ def isolated_blocking_ddg(
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
  # ============================================================
@@ -1558,36 +1581,45 @@ async def search_web_async(
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:
@@ -1595,454 +1627,31 @@ async def scrape_target_url(
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
- # ============================================================
1712
- # MAIN ORCHESTRATION
1713
- # ============================================================
1714
-
1715
- async def process_ask_orchestration(
1716
- q: str,
1717
- session: dict
1718
- ) -> tuple:
1719
-
1720
- use_web = should_trigger_web_search(
1721
- q,
1722
- session
1723
- )
1724
-
1725
- search_query = q
1726
-
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(
1761
- search_query
1762
- )
1763
- )
1764
-
1765
- if len(context.strip()) < 150:
1766
-
1767
- context, sources = (
1768
- await build_context_pipeline(
1769
- search_query + " wikipedia"
1770
- )
1771
- )
1772
-
1773
- session[
1774
- "last_web_context"
1775
- ].append(
1776
- {
1777
- "user": q,
1778
- "search": search_query
1779
- }
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(
1899
- {
1900
- "role": "user",
1901
- "content": q
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
1930
-
1931
- update_history_buffer(
1932
- session,
1933
- "user",
1934
- q
1935
- )
1936
-
1937
- update_history_buffer(
1938
- session,
1939
- "assistant",
1940
- response_string
1941
- )
1942
-
1943
- return (
1944
- response_string,
1945
- sources
1946
- )
1947
-
1948
-
1949
- # ============================================================
1950
- # API ENDPOINTS
1951
- # ============================================================
1952
-
1953
- @app.post("/query")
1954
- async def chat_endpoint(
1955
- request: ChatRequest
1956
- ):
1957
-
1958
- try:
1959
-
1960
- session = fetch_or_create_session(
1961
- request.session_id
1962
- )
1963
-
1964
- answer, sources = (
1965
- await process_ask_orchestration(
1966
- request.message,
1967
- session
1968
- )
1969
- )
1970
-
1971
- save_session_to_redis(
1972
- request.session_id,
1973
- session
1974
- )
1975
-
1976
- return {
1977
- "response": answer,
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(
1988
- status_code=500,
1989
- detail=str(e)
1990
- )
1991
-
1992
-
1993
- @app.get("/health")
1994
- async def validation_heartbeat():
1995
-
1996
- return {
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
-
2023
- return {
2024
- "message":
2025
- "Infinity GRM Engine Core is active.",
2026
- "reasoning_examples":
2027
- len(REASONING_EXAMPLES)
2028
- }
2029
-
2030
-
2031
- # ============================================================
2032
- # HUGGING FACE PORT
2033
- # ============================================================
2034
-
2035
- if __name__ == "__main__":
2036
-
2037
- port = int(
2038
- os.getenv(
2039
- "PORT",
2040
- 7860
2041
- )
2042
- )
2043
-
2044
- uvicorn.run(
2045
- "main:app",
2046
- host="0.0.0.0",
2047
- port=port
2048
- )
 
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
  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
 
93
  logging.basicConfig(
94
  level=logging.INFO,
95
+ format="%(asctime)s - %(levelname)s - %(message)s",
96
  )
97
 
98
  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.
 
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?
 
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.
 
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
 
 
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()
 
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.
 
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?
 
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
  """,
 
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
  """,
 
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?
 
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?
 
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?
 
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
  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
  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
  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
 
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
 
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
 
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
  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
+ )