Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
"""
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
|
| 449 |
-
|
| 450 |
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 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 (
|
| 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
|
|
|
|
|
|
|
| 639 |
"""
|
| 640 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 641 |
|
| 642 |
try:
|
| 643 |
-
|
| 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 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
search_url = f"https://en.wikipedia.org/w/index.php?search={query.replace(' ', '+')}"
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
return result
|
| 704 |
-
|
| 705 |
except Exception as e:
|
| 706 |
-
|
| 707 |
-
|
| 708 |
|
| 709 |
|
| 710 |
class SearchInput(BaseModel):
|
|
@@ -1502,13 +1552,9 @@ defined_tools = [
|
|
| 1502 |
reflect_on_progress,
|
| 1503 |
validate_answer,
|
| 1504 |
|
| 1505 |
-
#
|
| 1506 |
-
wikipedia_search, # NEW: Better for encyclopedic queries
|
| 1507 |
search_tool,
|
| 1508 |
-
|
| 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}/{
|
| 1843 |
print('='*70)
|
| 1844 |
|
| 1845 |
-
if current_turn >
|
| 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(
|
| 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"β οΈ
|
| 1888 |
|
| 1889 |
-
#
|
| 1890 |
-
|
| 1891 |
-
|
| 1892 |
-
|
| 1893 |
-
|
| 1894 |
-
|
| 1895 |
-
|
| 1896 |
-
|
| 1897 |
-
|
| 1898 |
-
|
| 1899 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1900 |
|
| 1901 |
-
|
| 1902 |
-
|
|
|
|
| 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(
|
| 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:
|