gabejavitt commited on
Commit
ae370ed
Β·
verified Β·
1 Parent(s): 7a5d909

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +259 -110
app.py CHANGED
@@ -440,20 +440,28 @@ class ThinkInput(BaseModel):
440
 
441
  @tool(args_schema=ThinkInput)
442
  def think_through_logic(reasoning: str) -> str:
443
- """Think through logic puzzles and riddles"""
444
- start_time = time.time()
445
- try:
446
- print(f"🧠 Thinking: {reasoning[:100]}...")
447
- result = f"""βœ… Logic reasoning recorded.
 
 
 
 
 
 
 
 
 
448
 
449
- Next: calculator (if math) β†’ validate_answer β†’ final_answer_tool
450
 
451
- Remember: MUST call another tool."""
452
- telemetry.record_call("think_through_logic", time.time() - start_time, True)
453
- return result
454
- except Exception as e:
455
- telemetry.record_call("think_through_logic", time.time() - start_time, False)
456
- raise
457
 
458
 
459
  class PlanInput(BaseModel):
@@ -627,84 +635,126 @@ def validate_answer(proposed_answer: str, original_question: str) -> str:
627
  # CORE TOOLS
628
  # =============================================================================
629
  class WikipediaInput(BaseModel):
630
- query: str = Field(description="Topic to search (e.g., 'Mercedes Sosa', 'Python programming')")
631
-
632
- # Replace your wikipedia_search function with:
633
 
634
  @tool(args_schema=WikipediaInput)
635
- @retry_with_backoff(max_retries=2)
636
  def wikipedia_search(query: str) -> str:
637
  """
638
- Search Wikipedia by scraping (API blocked on HuggingFace).
 
 
639
  """
640
- start_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
641
 
642
  try:
643
- print(f"πŸ“š Wikipedia search: {query}")
644
-
645
- # Check cache first
646
- cache_key = f"wiki:{query}"
647
- cached = search_cache.get(cache_key)
648
- if cached:
649
- print(f" (cached)")
650
- telemetry.record_call("wikipedia_search", time.time() - start_time, True)
651
- return cached
652
-
653
- # Build direct Wikipedia URL
654
- wiki_title = query.replace(' ', '_')
655
- wiki_url = f"https://en.wikipedia.org/wiki/{wiki_title}"
656
 
657
- print(f" Trying: {wiki_url}")
658
-
659
- headers = {
660
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
661
- }
662
-
663
- response = requests.get(wiki_url, headers=headers, timeout=10)
664
-
665
- # If 404, try search page
666
- if response.status_code == 404:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  search_url = f"https://en.wikipedia.org/w/index.php?search={query.replace(' ', '+')}"
668
- print(f" 404, trying search: {search_url}")
669
- response = requests.get(search_url, headers=headers, timeout=10)
670
-
671
- response.raise_for_status()
672
-
673
- soup = BeautifulSoup(response.text, 'html.parser')
674
-
675
- # Get title
676
- title_elem = soup.find('h1', {'id': 'firstHeading'})
677
- title = title_elem.get_text() if title_elem else query
678
-
679
- # Get main content
680
- content_div = soup.find('div', {'class': 'mw-parser-output'})
681
-
682
- if not content_div:
683
- raise ValueError("No content found on Wikipedia page")
684
-
685
- # Remove unwanted elements
686
- for tag in content_div(['script', 'style', 'table', 'sup', 'span.reference']):
687
- tag.extract()
688
-
689
- content = content_div.get_text(separator='\n', strip=True)
690
-
691
- print(f" Retrieved {len(content)} chars")
692
-
693
- # Format result
694
- result = f"Wikipedia: {title}\n"
695
- result += f"URL: {response.url}\n\n"
696
- result += content
697
- result = truncate_if_needed(result, max_length=12000)
698
-
699
- # Cache result
700
- search_cache.put(cache_key, result)
701
-
702
- telemetry.record_call("wikipedia_search", time.time() - start_time, True)
703
- return result
704
-
705
  except Exception as e:
706
- telemetry.record_call("wikipedia_search", time.time() - start_time, False)
707
- raise ToolError("wikipedia_search", e, "Try using search_tool instead")
708
 
709
 
710
  class SearchInput(BaseModel):
@@ -1502,13 +1552,9 @@ defined_tools = [
1502
  reflect_on_progress,
1503
  validate_answer,
1504
 
1505
- # Search & Browse
1506
- wikipedia_search, # NEW: Better for encyclopedic queries
1507
  search_tool,
1508
- iterative_web_browser, # NEW: Multi-turn web navigation
1509
- scrape_and_retrieve,
1510
-
1511
- # Core computation
1512
  calculator,
1513
  code_interpreter,
1514
 
@@ -1516,12 +1562,13 @@ defined_tools = [
1516
  read_file,
1517
  write_file,
1518
  list_directory,
1519
- analyze_data_file, # NEW: Smart CSV/Excel analysis
1520
 
1521
  # Specialized
1522
  audio_transcription_tool,
1523
- analyze_image,
1524
  get_youtube_transcript,
 
 
1525
 
1526
  # Final
1527
  final_answer_tool
@@ -1728,6 +1775,52 @@ OR iterative_web_browser (if needs navigation) β†’ validate β†’ final_answer
1728
  **MATH CALCULATIONS**:
1729
  β†’ calculator β†’ validate β†’ final_answer
1730
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1731
  ═══════════════════════════════════════════════════════════════
1732
  🎯 CRITICAL TOOL USAGE PATTERNS:
1733
  ═══════════════════════════════════════════════════════════════
@@ -1839,14 +1932,46 @@ REMEMBER: One tool per turn. No reasoning without tools. Exact answer format.
1839
  def agent_node(state: AgentState):
1840
  current_turn = state.get('turn', 0) + 1
1841
  print(f"\n{'='*70}")
1842
- print(f"πŸ€– TURN {current_turn}/{config.MAX_TURNS}")
1843
  print('='*70)
1844
 
1845
- if current_turn > config.MAX_TURNS:
1846
  return {
1847
- "messages": [SystemMessage(content="Max turns reached")],
1848
  "turn": current_turn
1849
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1850
 
1851
  consecutive_errors = state.get('consecutive_errors', 0)
1852
  should_reflect = (current_turn > 5 and current_turn % config.REFLECT_EVERY_N_TURNS == 0) or consecutive_errors >= 3
@@ -1873,33 +1998,57 @@ REMEMBER: One tool per turn. No reasoning without tools. Exact answer format.
1873
  # Invoke LLM with retries and fallback
1874
  ai_message = None
1875
 
1876
- for attempt in range(config.MAX_RETRIES):
1877
  try:
1878
  ai_message = self.llm_with_tools.invoke(messages_to_send)
1879
 
1880
  if ai_message.tool_calls:
1881
  break
1882
-
1883
- print(f"⚠️ No tool calls (attempt {attempt+1})")
1884
-
1885
  except Exception as e:
1886
  error_str = str(e)
1887
- print(f"⚠️ {self.current_llm.upper()} error (attempt {attempt+1}): {error_str[:200]}")
1888
 
1889
- # If Groq fails and we have Claude, switch to Claude
1890
- if self.current_llm == "groq" and self.claude_llm and attempt == config.MAX_RETRIES - 1:
1891
- print("πŸ”„ Switching from Groq to Claude for this question...")
1892
- self.llm_with_tools = self.claude_llm
1893
- self.current_llm = "claude"
1894
- try:
1895
- ai_message = self.llm_with_tools.invoke(messages_to_send)
1896
- if ai_message.tool_calls:
1897
- break
1898
- except Exception as e2:
1899
- print(f"⚠️ Claude also failed: {e2}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1900
 
1901
- if attempt == config.MAX_RETRIES - 1:
1902
- print("🚨 Forcing think_through_logic")
 
1903
  ai_message = AIMessage(
1904
  content="",
1905
  tool_calls=[ToolCall(
@@ -1909,7 +2058,7 @@ REMEMBER: One tool per turn. No reasoning without tools. Exact answer format.
1909
  )]
1910
  )
1911
  else:
1912
- time.sleep(config.BASE_RETRY_DELAY * (2 ** attempt))
1913
 
1914
  # Ensure tool calls exist
1915
  if not ai_message.tool_calls:
 
440
 
441
  @tool(args_schema=ThinkInput)
442
  def think_through_logic(reasoning: str) -> str:
443
+ """
444
+ Use ONLY for logic puzzles and riddles (NOT research questions).
445
+
446
+ Use for:
447
+ - Brain teasers, logic puzzles, riddles
448
+
449
+ DON'T use for:
450
+ - Research questions β†’ use search_tool or wikipedia_search
451
+ - Math β†’ use calculator
452
+ - File analysis β†’ use file tools
453
+ """
454
+ print(f"🧠 Thinking: {reasoning[:100]}...")
455
+
456
+ return f"""βœ… Reasoning: {reasoning[:100]}
457
 
458
+ ⚠️ DO NOT CALL think_through_logic AGAIN!
459
 
460
+ For research β†’ use search_tool() or wikipedia_search()
461
+ For math β†’ use calculator()
462
+ If you have answer β†’ use final_answer_tool()
463
+
464
+ TAKE ACTION NOW!"""
 
465
 
466
 
467
  class PlanInput(BaseModel):
 
635
  # CORE TOOLS
636
  # =============================================================================
637
  class WikipediaInput(BaseModel):
638
+ query: str = Field(description="Topic to search (just the subject name)")
 
 
639
 
640
  @tool(args_schema=WikipediaInput)
 
641
  def wikipedia_search(query: str) -> str:
642
  """
643
+ Search Wikipedia directly. Keep query SHORT!
644
+ βœ… GOOD: "Mercedes Sosa"
645
+ ❌ BAD: "Mercedes Sosa discography 2022 Wikipedia version"
646
  """
647
+ # AGGRESSIVE query cleaning
648
+ original = query
649
+ query = query.lower().strip()
650
+
651
+ # Remove these phrases (order matters - longest first!)
652
+ remove_list = [
653
+ "2022 english wikipedia version",
654
+ "english wikipedia version",
655
+ "2022 version",
656
+ "wikipedia version",
657
+ "latest version",
658
+ "wikipedia",
659
+ "wiki",
660
+ "discography",
661
+ "site:",
662
+ " the ",
663
+ " a ",
664
+ " an "
665
+ ]
666
+
667
+ for phrase in remove_list:
668
+ query = query.replace(phrase, "")
669
+
670
+ # Clean whitespace
671
+ query = " ".join(query.split()).strip()
672
+
673
+ # Fallback if query too short
674
+ if len(query) < 2:
675
+ words = original.split()
676
+ query = words[0] if words else original
677
+
678
+ print(f"πŸ“š Wikipedia: '{original}' β†’ '{query}'")
679
+
680
+ # Try direct page
681
+ page_name = query.title().replace(" ", "_")
682
+ page_url = f"https://en.wikipedia.org/wiki/{page_name}"
683
+
684
+ print(f" Trying: {page_url}")
685
 
686
  try:
687
+ headers = {'User-Agent': 'Mozilla/5.0'}
688
+ response = requests.get(page_url, headers=headers, timeout=10)
 
 
 
 
 
 
 
 
 
 
 
689
 
690
+ if response.status_code == 200:
691
+ soup = BeautifulSoup(response.text, 'html.parser')
692
+
693
+ title_tag = soup.find('h1', class_='firstHeading')
694
+ title = title_tag.get_text() if title_tag else page_name
695
+
696
+ content_div = soup.find('div', class_='mw-parser-output')
697
+ preview = ""
698
+ if content_div:
699
+ paragraphs = content_div.find_all('p', limit=3)
700
+ for p in paragraphs:
701
+ text = p.get_text().strip()
702
+ if len(text) > 50:
703
+ preview = text[:300]
704
+ break
705
+
706
+ result = f"""βœ… Found: {title}
707
+ URL: {page_url}
708
+
709
+ Preview: {preview}...
710
+
711
+ NEXT: Use scrape_and_retrieve(url="{page_url}", query="specific info")"""
712
+
713
+ print(f"βœ“ Success: {title}")
714
+ return result
715
+
716
+ else:
717
+ # Try search
718
+ print(f" 404, trying search")
719
  search_url = f"https://en.wikipedia.org/w/index.php?search={query.replace(' ', '+')}"
720
+
721
+ try:
722
+ search_resp = requests.get(search_url, headers=headers, timeout=10)
723
+
724
+ if "wikipedia.org/wiki/" in search_resp.url and search_resp.url != search_url:
725
+ return f"βœ… Redirected to: {search_resp.url}\n\nUse scrape_and_retrieve() for details."
726
+
727
+ soup = BeautifulSoup(search_resp.text, 'html.parser')
728
+ results = soup.find_all('div', class_='mw-search-result-heading', limit=3)
729
+
730
+ if results:
731
+ formatted = []
732
+ for i, result in enumerate(results, 1):
733
+ link = result.find('a')
734
+ if link:
735
+ title = link.get_text()
736
+ href = link.get('href')
737
+ full_url = f"https://en.wikipedia.org{href}"
738
+ formatted.append(f"{i}. {title}\n {full_url}")
739
+
740
+ return "Wikipedia results:\n\n" + "\n\n".join(formatted) + "\n\nUse scrape_and_retrieve() with relevant URL."
741
+
742
+ return f"""No Wikipedia page found for '{query}'.
743
+
744
+ Try:
745
+ 1. search_tool("{query}")
746
+ 2. Different search term
747
+ 3. Check spelling"""
748
+
749
+ except Exception as search_err:
750
+ return f"Wikipedia search failed. Try search_tool('{query}') instead."
751
+
752
+ except requests.Timeout:
753
+ return f"Wikipedia timed out. Try search_tool('{query}') instead."
754
+
 
 
755
  except Exception as e:
756
+ print(f"⚠️ Wikipedia error: {str(e)[:100]}")
757
+ return f"Wikipedia error. Try search_tool('{query}') instead."
758
 
759
 
760
  class SearchInput(BaseModel):
 
1552
  reflect_on_progress,
1553
  validate_answer,
1554
 
1555
+ # Core tools
 
1556
  search_tool,
1557
+ wikipedia_search, # ← ADD THIS NEW TOOL
 
 
 
1558
  calculator,
1559
  code_interpreter,
1560
 
 
1562
  read_file,
1563
  write_file,
1564
  list_directory,
 
1565
 
1566
  # Specialized
1567
  audio_transcription_tool,
1568
+ analyze_image,
1569
  get_youtube_transcript,
1570
+ scrape_and_retrieve,
1571
+ analyze_chess_position,
1572
 
1573
  # Final
1574
  final_answer_tool
 
1775
  **MATH CALCULATIONS**:
1776
  β†’ calculator β†’ validate β†’ final_answer
1777
 
1778
+ ═══════════════════════════════════════════════════════════════
1779
+ πŸ“š WIKIPEDIA QUERIES - CRITICAL:
1780
+ ═══════════════════════════════════════════════════════════════
1781
+
1782
+ If question mentions Wikipedia:
1783
+ 1. Use wikipedia_search() with SHORT query (just the subject)
1784
+ 2. Get Wikipedia URL
1785
+ 3. Use scrape_and_retrieve() for detailed info
1786
+
1787
+ βœ… CORRECT Example:
1788
+ Q: "How many albums by Mercedes Sosa 2000-2009 using Wikipedia?"
1789
+
1790
+ Turn 1: wikipedia_search("Mercedes Sosa")
1791
+ β†’ Returns URL
1792
+
1793
+ Turn 2: scrape_and_retrieve(
1794
+ url="https://en.wikipedia.org/wiki/Mercedes_Sosa",
1795
+ query="studio albums 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009"
1796
+ )
1797
+ β†’ Returns full discography
1798
+
1799
+ Turn 3: code_interpreter("count albums in years 2000-2009")
1800
+ β†’ Returns "3"
1801
+
1802
+ Turn 4: validate_answer("3", question)
1803
+
1804
+ Turn 5: final_answer_tool("3")
1805
+
1806
+ ❌ WRONG Examples:
1807
+ - wikipedia_search("Mercedes Sosa discography 2022 English Wikipedia version")
1808
+ - wikipedia_search("Mercedes Sosa Wikipedia")
1809
+ - wikipedia_search("How many albums Mercedes Sosa")
1810
+
1811
+ REMEMBER: wikipedia_search() wants just the SUBJECT NAME!
1812
+ ═══════════════════════════════════════════════════════════════
1813
+
1814
+ 🚨 ANTI-LOOP RULES:
1815
+ ═══════════════════════════════════════════════════════════════
1816
+
1817
+ 1. NEVER call the same tool 3 times in a row
1818
+ 2. think_through_logic is ONLY for logic puzzles (NOT research)
1819
+ 3. Research questions need search_tool or wikipedia_search
1820
+ 4. If stuck for 3 turns β†’ try DIFFERENT tool
1821
+
1822
+ ═══════════════════════════════════════════════════════════════
1823
+
1824
  ═══════════════════════════════════════════════════════════════
1825
  🎯 CRITICAL TOOL USAGE PATTERNS:
1826
  ═══════════════════════════════════════════════════════════════
 
1932
  def agent_node(state: AgentState):
1933
  current_turn = state.get('turn', 0) + 1
1934
  print(f"\n{'='*70}")
1935
+ print(f"πŸ€– AGENT TURN {current_turn}/{MAX_TURNS}")
1936
  print('='*70)
1937
 
1938
+ if current_turn > MAX_TURNS:
1939
  return {
1940
+ "messages": [SystemMessage(content="Max turns reached.")],
1941
  "turn": current_turn
1942
  }
1943
+ tool_history = state.get('tool_history', [])
1944
+
1945
+ # Check for loops (same tool called 3+ times)
1946
+ if len(tool_history) >= 3:
1947
+ last_3 = tool_history[-3:]
1948
+
1949
+ # If same tool 3 times in a row, FORCE change
1950
+ if len(set(last_3)) == 1:
1951
+ problem_tool = last_3[0]
1952
+ print(f"🚨 LOOP DETECTED: {problem_tool} called 3x - FORCING CHANGE")
1953
+
1954
+ force_msg = SystemMessage(
1955
+ content=f"""⚠️ EMERGENCY: You called {problem_tool}() 3 times in a row!
1956
+
1957
+ THIS IS A LOOP. You MUST use a DIFFERENT tool now.
1958
+
1959
+ BANNED this turn: {problem_tool}
1960
+
1961
+ Pick ANY other tool and call it NOW."""
1962
+ )
1963
+
1964
+ messages_to_send = state["messages"].copy()
1965
+ messages_to_send.append(force_msg)
1966
+ else:
1967
+ messages_to_send = state["messages"].copy()
1968
+ else:
1969
+ messages_to_send = state["messages"].copy()
1970
+ # ===== END LOOP DETECTION =====
1971
+
1972
+ # Check if we should force reflection
1973
+ consecutive_errors = state.get('consecutive_errors', 0)
1974
+ should_reflect = (current_turn > 5 and current_turn % REFLECT_EVERY_N_TURNS == 0) or consecutive_errors >= 3
1975
 
1976
  consecutive_errors = state.get('consecutive_errors', 0)
1977
  should_reflect = (current_turn > 5 and current_turn % config.REFLECT_EVERY_N_TURNS == 0) or consecutive_errors >= 3
 
1998
  # Invoke LLM with retries and fallback
1999
  ai_message = None
2000
 
2001
+ for attempt in range(max_retries):
2002
  try:
2003
  ai_message = self.llm_with_tools.invoke(messages_to_send)
2004
 
2005
  if ai_message.tool_calls:
2006
  break
2007
+
 
 
2008
  except Exception as e:
2009
  error_str = str(e)
2010
+ print(f"⚠️ Groq error (attempt {attempt+1}): {error_str[:200]}")
2011
 
2012
+ # ===== IMPROVED RATE LIMIT HANDLING =====
2013
+ # Check for rate limit FIRST
2014
+ if "429" in error_str or "rate limit" in error_str.lower():
2015
+ print("❌ Groq rate limit hit!")
2016
+
2017
+ if attempt < max_retries - 1:
2018
+ wait = 10 * (2 ** attempt) # 10s, 20s, 40s
2019
+ print(f" Waiting {wait}s before retry...")
2020
+ time.sleep(wait)
2021
+ continue
2022
+
2023
+ # FINAL FALLBACK: Force search_tool as safe default
2024
+ print("πŸ”„ Final attempt failed - using search_tool fallback")
2025
+ ai_message = AIMessage(
2026
+ content="",
2027
+ tool_calls=[ToolCall(
2028
+ name="search_tool",
2029
+ args={"query": "answer to question"},
2030
+ id=str(uuid.uuid4())
2031
+ )]
2032
+ )
2033
+ break
2034
+ # ===== END RATE LIMIT HANDLING =====
2035
+
2036
+ # Tool use failed error
2037
+ if any(kw in error_str for kw in ["tool_use_failed", "tool call validation"]):
2038
+ print("🚨 Tool error - forcing think_through_logic")
2039
+ ai_message = AIMessage(
2040
+ content="",
2041
+ tool_calls=[ToolCall(
2042
+ name="think_through_logic",
2043
+ args={"reasoning": "Processing..."},
2044
+ id=str(uuid.uuid4())
2045
+ )]
2046
+ )
2047
+ break
2048
 
2049
+ # Final retry
2050
+ if attempt == max_retries - 1:
2051
+ print("🚨 All attempts failed - forcing think_through_logic")
2052
  ai_message = AIMessage(
2053
  content="",
2054
  tool_calls=[ToolCall(
 
2058
  )]
2059
  )
2060
  else:
2061
+ time.sleep(2 ** attempt)
2062
 
2063
  # Ensure tool calls exist
2064
  if not ai_message.tool_calls: