Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -23,25 +23,18 @@ except OSError:
|
|
| 23 |
nlp = spacy.load("en_core_web_sm")
|
| 24 |
|
| 25 |
|
| 26 |
-
# (Keep Constants as is)
|
| 27 |
-
# --- Constants ---
|
| 28 |
-
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 29 |
-
|
| 30 |
-
# --- Basic Agent Definition ---
|
| 31 |
-
# ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
|
| 32 |
-
# class BasicAgent:
|
| 33 |
-
# def __init__(self):
|
| 34 |
-
# print("BasicAgent initialized.")
|
| 35 |
-
# def __call__(self, question: str) -> str:
|
| 36 |
-
# print(f"Agent received question (first 50 chars): {question[:50]}...")
|
| 37 |
-
# fixed_answer = "This is a default answer."
|
| 38 |
-
# print(f"Agent returning fixed answer: {fixed_answer}")
|
| 39 |
-
# return fixed_answer
|
| 40 |
-
|
| 41 |
-
|
| 42 |
# --- Constants ---
|
| 43 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
class SuperSmartAgent:
|
| 47 |
def __init__(self):
|
|
@@ -50,99 +43,161 @@ class SuperSmartAgent:
|
|
| 50 |
extract_format=wikipediaapi.ExtractFormat.WIKI,
|
| 51 |
user_agent='SelimResearchAgent/1.0'
|
| 52 |
)
|
|
|
|
| 53 |
self.graph = self._build_graph() # Build graph after initializing wiki_wiki
|
| 54 |
|
| 55 |
def _build_graph(self):
|
| 56 |
-
#
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
else:
|
| 119 |
-
state["response"] = "
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
]
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
def get_relevant_context(self, question, search_results):
|
| 148 |
"""
|
|
@@ -218,7 +273,7 @@ class SuperSmartAgent:
|
|
| 218 |
context = re.sub(r'\[\d+\]', '', context) # Remove [1], [2], etc.
|
| 219 |
context = re.sub(r'<ref[^>]*>.*?<\/ref>', '', context, flags=re.DOTALL | re.IGNORECASE) # Remove <ref> tags
|
| 220 |
context = re.sub(r'\{\{.*?\}\}', '', context, flags=re.DOTALL) # Remove {{templates}}
|
| 221 |
-
context = re.sub(r'{\|.*?\|\}', '', context, flags=re.DOTALL) # Remove wiki tables (if extract_tables_from_wikipedia doesn't catch all)
|
| 222 |
context = re.sub(r'==\s*See also\s*==.*?$', '', context, flags=re.DOTALL | re.IGNORECASE) # Remove "See also" section and anything after
|
| 223 |
context = re.sub(r'==\s*References\s*==.*?$', '', context, flags=re.DOTALL | re.IGNORECASE) # Remove "References" section and anything after
|
| 224 |
context = re.sub(r'\s+', ' ', context).strip() # Normalize whitespace
|
|
@@ -249,6 +304,9 @@ class SuperSmartAgent:
|
|
| 249 |
return state
|
| 250 |
|
| 251 |
context = self.get_relevant_context(question, search_results)
|
|
|
|
|
|
|
|
|
|
| 252 |
if not context:
|
| 253 |
state["response"] = "Sorry, I couldn't find detailed relevant information."
|
| 254 |
return state
|
|
@@ -327,7 +385,7 @@ class SuperSmartAgent:
|
|
| 327 |
if not re.search(r'\d+', answer):
|
| 328 |
# If question asks for a number but answer has no number, it's likely wrong
|
| 329 |
return False
|
| 330 |
-
|
| 331 |
# Check for year/date answers for "when" questions
|
| 332 |
if "when" in question_lower or "year" in question_lower:
|
| 333 |
if not re.search(r'\b\d{4}\b', answer) and not re.search(r'\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w*\s+\d{1,2}(?:st|nd|rd|th)?,\s+\d{4}\b', answer):
|
|
@@ -452,7 +510,7 @@ class SuperSmartAgent:
|
|
| 452 |
relevant_date = self.find_relevant_date_spacy(question_lower, numbers_dates, entities)
|
| 453 |
if relevant_date:
|
| 454 |
return f"The answer is {relevant_date}."
|
| 455 |
-
|
| 456 |
elif question_type == "location":
|
| 457 |
relevant_location = self.find_relevant_location_spacy(question_lower, entities)
|
| 458 |
if relevant_location:
|
|
@@ -715,376 +773,107 @@ class SuperSmartAgent:
|
|
| 715 |
headers = [self._clean_cell_content(cell).lower() for cell in table[0]] if table else []
|
| 716 |
data_rows = table[1:] if len(table) > 1 else []
|
| 717 |
|
| 718 |
-
# Determine column types
|
| 719 |
column_types = self.detect_column_types(table)
|
| 720 |
|
| 721 |
-
#
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
if
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
if row_score > best_match_score:
|
| 742 |
-
for col_idx in numeric_columns_indices:
|
| 743 |
-
if col_idx < len(row):
|
| 744 |
-
cell_content = self._clean_cell_content(row[col_idx])
|
| 745 |
-
numbers = re.findall(r'(\d[\d,]*\d*)', cell_content)
|
| 746 |
-
if numbers:
|
| 747 |
-
# Take the first number found in the cell
|
| 748 |
-
clean_num = numbers[0].replace(',', '')
|
| 749 |
-
if clean_num.isdigit():
|
| 750 |
-
best_match_score = row_score
|
| 751 |
-
best_numeric_answer = clean_num
|
| 752 |
-
break # Found a number, move to next row if not the best
|
| 753 |
-
|
| 754 |
-
if best_numeric_answer:
|
| 755 |
-
return f"The answer is {best_numeric_answer}."
|
| 756 |
-
|
| 757 |
-
elif "who" in question_lower or "which person" in question_lower or "player" in question_lower:
|
| 758 |
-
name_columns_indices = [i for i, col_type in enumerate(column_types) if col_type == 'name']
|
| 759 |
-
|
| 760 |
-
if name_columns_indices and data_rows:
|
| 761 |
-
best_match_score = -1
|
| 762 |
-
best_name_answer = None
|
| 763 |
-
|
| 764 |
-
for row in data_rows:
|
| 765 |
-
row_text_lower = ' '.join([self._clean_cell_content(c).lower() for c in row])
|
| 766 |
-
row_score = sum(1 for kw in question_keywords if kw.lower() in row_text_lower)
|
| 767 |
-
|
| 768 |
-
if row_score > best_match_score:
|
| 769 |
-
for col_idx in name_columns_indices:
|
| 770 |
-
if col_idx < len(row):
|
| 771 |
-
cell_content = self._clean_cell_content(row[col_idx])
|
| 772 |
-
# Check if the cell content looks like a name using spaCy
|
| 773 |
-
doc_cell = nlp(cell_content)
|
| 774 |
-
if any(ent.label_ == "PERSON" for ent in doc_cell.ents):
|
| 775 |
-
best_match_score = row_score
|
| 776 |
-
best_name_answer = cell_content.strip()
|
| 777 |
-
break
|
| 778 |
-
if best_name_answer:
|
| 779 |
-
return f"The answer is {best_name_answer}."
|
| 780 |
-
|
| 781 |
-
elif "when" in question_lower or "year" in question_lower or "date" in question_lower:
|
| 782 |
-
date_columns_indices = [i for i, col_type in enumerate(column_types) if col_type == 'date']
|
| 783 |
-
|
| 784 |
-
if date_columns_indices and data_rows:
|
| 785 |
-
best_match_score = -1
|
| 786 |
-
best_date_answer = None
|
| 787 |
-
|
| 788 |
-
for row in data_rows:
|
| 789 |
-
row_text_lower = ' '.join([self._clean_cell_content(c).lower() for c in row])
|
| 790 |
-
row_score = sum(1 for kw in question_keywords if kw.lower() in row_text_lower)
|
| 791 |
-
|
| 792 |
-
if row_score > best_match_score:
|
| 793 |
-
for col_idx in date_columns_indices:
|
| 794 |
-
if col_idx < len(row):
|
| 795 |
-
cell_content = self._clean_cell_content(row[col_idx])
|
| 796 |
-
# Use more robust date detection
|
| 797 |
-
if re.search(r'\b(19|20)\d{2}\b', cell_content) or \
|
| 798 |
-
re.search(r'\b\d{1,2}\s+(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w*\s*\d{4}\b', cell_content, re.IGNORECASE):
|
| 799 |
-
best_match_score = row_score
|
| 800 |
-
best_date_answer = cell_content.strip()
|
| 801 |
-
break
|
| 802 |
-
if best_date_answer:
|
| 803 |
-
return f"The answer is {best_date_answer}."
|
| 804 |
-
|
| 805 |
return None
|
| 806 |
|
| 807 |
def detect_column_types(self, table):
|
| 808 |
"""
|
| 809 |
-
Detects the type of data in each column (e.g., 'number', '
|
| 810 |
-
|
| 811 |
"""
|
| 812 |
-
if not table:
|
| 813 |
return []
|
|
|
|
|
|
|
|
|
|
| 814 |
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
# Sample a few rows to determine type
|
| 819 |
-
sample_rows = table[1:min(len(table), 5)]
|
| 820 |
-
|
| 821 |
-
for col_idx in range(num_columns):
|
| 822 |
-
col_values = [self._clean_cell_content(row[col_idx]) for row in sample_rows if col_idx < len(row)]
|
| 823 |
-
|
| 824 |
num_count = 0
|
| 825 |
-
name_count = 0
|
| 826 |
date_count = 0
|
| 827 |
-
|
| 828 |
-
|
| 829 |
-
|
| 830 |
-
|
| 831 |
-
|
| 832 |
-
|
| 833 |
-
|
| 834 |
-
|
| 835 |
-
# Check for dates
|
| 836 |
-
if any(ent.label_ == "DATE" for ent in value_doc.ents):
|
| 837 |
-
date_count += 1
|
| 838 |
-
elif re.search(r'\b\d{4}\b|\b\d{1,2}/\d{1,2}/\d{2,4}\b', value):
|
| 839 |
-
date_count += 1
|
| 840 |
-
|
| 841 |
-
# Check for names (PERSON entity)
|
| 842 |
-
if any(ent.label_ == "PERSON" for ent in value_doc.ents):
|
| 843 |
-
name_count += 1
|
| 844 |
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
elif date_count / len(col_values) > 0.7: # More than 70% dates
|
| 850 |
-
column_types[col_idx] = 'date'
|
| 851 |
-
elif name_count / len(col_values) > 0.5 and num_count == 0: # More than 50% names and no numbers
|
| 852 |
-
column_types[col_idx] = 'name'
|
| 853 |
-
# Default remains 'text'
|
| 854 |
-
|
| 855 |
return column_types
|
| 856 |
|
| 857 |
-
|
| 858 |
-
"""Checks if a sample of values from a column primarily contains names using spaCy."""
|
| 859 |
-
if not sample_values:
|
| 860 |
-
return False
|
| 861 |
-
|
| 862 |
-
name_like_count = 0
|
| 863 |
-
for value in sample_values:
|
| 864 |
-
doc = nlp(value)
|
| 865 |
-
# A value looks like a name if spaCy identifies a PERSON entity
|
| 866 |
-
if any(ent.label_ == "PERSON" for ent in doc.ents):
|
| 867 |
-
name_like_count += 1
|
| 868 |
-
|
| 869 |
-
return name_like_count / len(sample_values) > 0.6 # Majority are name-like
|
| 870 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 871 |
|
| 872 |
-
|
| 873 |
-
question
|
| 874 |
-
|
| 875 |
-
is_python: bool
|
| 876 |
-
is_riddle: bool
|
| 877 |
-
is_wiki: bool
|
| 878 |
-
needs_reasoning: bool
|
| 879 |
-
response: str
|
| 880 |
-
use_tool: str
|
| 881 |
-
# Add current_context to state for find_relevant_person_spacy etc.
|
| 882 |
-
current_context: str # Stores the context retrieved from Wikipedia
|
| 883 |
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
# into the SuperSmartAgent class as methods.
|
| 892 |
-
# The graph nodes will then call self.method_name.
|
| 893 |
-
|
| 894 |
-
# Ensure the graph nodes correctly reference the class methods
|
| 895 |
-
# For the graph to work, these need to be callable methods of the class.
|
| 896 |
-
# So we adapt the node definitions:
|
| 897 |
-
|
| 898 |
-
builder = StateGraph(self.AgentState)
|
| 899 |
-
|
| 900 |
-
builder.add_node("check_reversed", self.check_reversed_node)
|
| 901 |
-
builder.add_node("fix_question", self.fix_question_node)
|
| 902 |
-
builder.add_node("check_riddle_or_trick", self.check_riddle_or_trick_node)
|
| 903 |
-
builder.add_node("solve_riddle", self.solve_riddle_node)
|
| 904 |
-
builder.add_node("check_wikipedia_suitability", self.check_wikipedia_suitability_node)
|
| 905 |
-
builder.add_node("check_reasoning_needed", self.check_reasoning_needed_node)
|
| 906 |
-
builder.add_node("general_reasoning_qa", self.general_reasoning_qa_node)
|
| 907 |
-
builder.add_node("check_python_suitability", self.check_python_suitability_node)
|
| 908 |
-
builder.add_node("generate_code", self.generate_code_node)
|
| 909 |
-
builder.add_node("fallback", self.fallback_node)
|
| 910 |
-
|
| 911 |
-
|
| 912 |
-
# Bind the functions as methods of the class for the graph to call them
|
| 913 |
-
# This is a common pattern when using StateGraph with class methods
|
| 914 |
-
# The methods need to be defined outside _build_graph as instance methods
|
| 915 |
-
# I've defined them above as regular methods, so this part simplifies.
|
| 916 |
-
|
| 917 |
-
# Rename the nested functions to be class methods or use wrappers
|
| 918 |
-
# For simplicity, I'm just renaming the graph nodes to call self.method
|
| 919 |
-
# Make sure the actual function implementations are now class methods.
|
| 920 |
-
|
| 921 |
-
# Define wrapper methods to fit the graph signature if needed, or
|
| 922 |
-
# directly call the class methods from the graph nodes.
|
| 923 |
-
# Here, I'm directly renaming the graph calls to assume the original
|
| 924 |
-
# functions are now methods.
|
| 925 |
-
|
| 926 |
-
# Set entry point and define edges
|
| 927 |
-
builder.set_entry_point("check_reversed")
|
| 928 |
-
builder.add_edge("check_reversed", "fix_question")
|
| 929 |
-
builder.add_edge("fix_question", "check_riddle_or_trick")
|
| 930 |
-
builder.add_conditional_edges(
|
| 931 |
-
"check_riddle_or_trick",
|
| 932 |
-
lambda s: "solve_riddle" if s.get("is_riddle") else "check_wikipedia_suitability"
|
| 933 |
-
)
|
| 934 |
-
builder.add_conditional_edges(
|
| 935 |
-
"check_wikipedia_suitability",
|
| 936 |
-
lambda s: "general_reasoning_qa" if s.get("is_wiki") else "check_reasoning_needed" # Go directly to general_reasoning_qa for wiki
|
| 937 |
-
)
|
| 938 |
-
builder.add_conditional_edges(
|
| 939 |
-
"check_reasoning_needed",
|
| 940 |
-
lambda s: "general_reasoning_qa" if s.get("needs_reasoning") else "check_python_suitability"
|
| 941 |
-
)
|
| 942 |
-
builder.add_conditional_edges(
|
| 943 |
-
"check_python_suitability",
|
| 944 |
-
lambda s: "generate_code" if s.get("is_python") else "fallback"
|
| 945 |
)
|
|
|
|
| 946 |
|
| 947 |
-
|
| 948 |
-
|
| 949 |
-
|
| 950 |
-
builder.add_edge("fallback", END)
|
| 951 |
-
|
| 952 |
-
return builder.compile()
|
| 953 |
-
|
| 954 |
-
# --- Wrapper methods for the graph nodes ---
|
| 955 |
-
# These call the actual logic methods. This is a common pattern
|
| 956 |
-
# when your graph functions are class methods and need `self`.
|
| 957 |
-
def check_reversed_node(self, state):
|
| 958 |
-
return self._check_reversed(state)
|
| 959 |
-
|
| 960 |
-
def fix_question_node(self, state):
|
| 961 |
-
return self._fix_question(state)
|
| 962 |
-
|
| 963 |
-
def check_riddle_or_trick_node(self, state):
|
| 964 |
-
return self._check_riddle_or_trick(state)
|
| 965 |
-
|
| 966 |
-
def solve_riddle_node(self, state):
|
| 967 |
-
return self._solve_riddle(state)
|
| 968 |
-
|
| 969 |
-
def check_wikipedia_suitability_node(self, state):
|
| 970 |
-
return self._check_wikipedia_suitability(state)
|
| 971 |
-
|
| 972 |
-
def check_reasoning_needed_node(self, state):
|
| 973 |
-
return self._check_reasoning_needed(state)
|
| 974 |
-
|
| 975 |
-
def general_reasoning_qa_node(self, state):
|
| 976 |
-
# Before calling general_reasoning_qa, ensure current_context is set up
|
| 977 |
-
# This part of the logic might need to be shifted depending on graph flow.
|
| 978 |
-
# For now, general_reasoning_qa itself will fetch context.
|
| 979 |
-
response_state = self.general_reasoning_qa(state)
|
| 980 |
-
# Update current_context in the state if it was retrieved, for consistency
|
| 981 |
-
# although general_reasoning_qa itself uses it internally.
|
| 982 |
-
# This is a bit tricky with StateGraph if context isn't explicitly passed around
|
| 983 |
-
# or stored in the state by the `general_reasoning_qa` function itself.
|
| 984 |
-
# The `find_relevant_person_spacy` and similar methods now assume `self.current_context`
|
| 985 |
-
# is available. The `general_reasoning_qa` method *should* set it.
|
| 986 |
-
return response_state
|
| 987 |
-
|
| 988 |
-
def check_python_suitability_node(self, state):
|
| 989 |
-
return self._check_python_suitability(state)
|
| 990 |
-
|
| 991 |
-
def generate_code_node(self, state):
|
| 992 |
-
return self._generate_code(state)
|
| 993 |
-
|
| 994 |
-
def fallback_node(self, state):
|
| 995 |
-
return self._fallback(state)
|
| 996 |
-
|
| 997 |
-
# --- Renamed original helper functions to be internal methods ---
|
| 998 |
-
# These are the actual implementations, now as instance methods.
|
| 999 |
-
def _check_reversed(self, state):
|
| 1000 |
-
question = state["question"]
|
| 1001 |
-
reversed_candidate = question[::-1]
|
| 1002 |
-
original_score = self._score_text(question)
|
| 1003 |
-
reversed_score = self._score_text(reversed_candidate)
|
| 1004 |
-
if reversed_score > original_score:
|
| 1005 |
-
state["is_reversed"] = True
|
| 1006 |
-
else:
|
| 1007 |
-
state["is_reversed"] = False
|
| 1008 |
-
return state
|
| 1009 |
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1014 |
|
| 1015 |
-
|
| 1016 |
-
|
| 1017 |
-
|
| 1018 |
-
|
| 1019 |
-
|
| 1020 |
|
| 1021 |
-
def _solve_riddle(self, state):
|
| 1022 |
-
q = state["question"].lower()
|
| 1023 |
-
if "opposite of the word" in q:
|
| 1024 |
-
if "left" in q:
|
| 1025 |
-
state["response"] = "right"
|
| 1026 |
-
elif "up" in q:
|
| 1027 |
-
state["response"] = "down"
|
| 1028 |
-
elif "hot" in q:
|
| 1029 |
-
state["response"] = "cold"
|
| 1030 |
-
else:
|
| 1031 |
-
state["response"] = "Unknown opposite."
|
| 1032 |
-
else:
|
| 1033 |
-
state["response"] = "Could not solve riddle."
|
| 1034 |
-
return state
|
| 1035 |
-
|
| 1036 |
-
def _check_python_suitability(self, state):
|
| 1037 |
-
question = state["question"].lower()
|
| 1038 |
-
patterns = ["sum", "average", "count", "sort", "generate", "regex", "convert"]
|
| 1039 |
-
state["is_python"] = any(word in question for word in patterns)
|
| 1040 |
-
return state
|
| 1041 |
-
|
| 1042 |
-
def _generate_code(self, state):
|
| 1043 |
-
q = state["question"].lower()
|
| 1044 |
-
if "sum" in q:
|
| 1045 |
-
state["response"] = "numbers = [1, 2, 3]\nprint(sum(numbers))"
|
| 1046 |
-
elif "average" in q:
|
| 1047 |
-
state["response"] = "numbers = [1, 2, 3]\nprint(sum(numbers) / len(numbers))"
|
| 1048 |
-
elif "sort" in q:
|
| 1049 |
-
state["response"] = "data = [3, 1, 2]\ndata.sort()\nprint(data)"
|
| 1050 |
-
else:
|
| 1051 |
-
state["response"] = "# Code generation not implemented for this case."
|
| 1052 |
-
return state
|
| 1053 |
-
|
| 1054 |
-
def _fallback(self, state):
|
| 1055 |
-
state["response"] = "This question doesn't require Python or is unclear."
|
| 1056 |
-
return state
|
| 1057 |
-
|
| 1058 |
-
def _check_reasoning_needed(self, state):
|
| 1059 |
-
q = state["question"].lower()
|
| 1060 |
-
needs_reasoning = any(word in q for word in ["whose", "only", "first", "after", "before", "no longer", "not", "but", "except"])
|
| 1061 |
-
state["needs_reasoning"] = needs_reasoning
|
| 1062 |
-
return state
|
| 1063 |
-
|
| 1064 |
-
def _check_wikipedia_suitability(self, state):
|
| 1065 |
-
q = state["question"].lower()
|
| 1066 |
-
triggers = [
|
| 1067 |
-
"wikipedia", "who is", "what is", "when did", "where is",
|
| 1068 |
-
"tell me about", "how many", "how much", "what was the",
|
| 1069 |
-
"describe", "explain", "information about", "details about",
|
| 1070 |
-
"history of", "facts about", "define", "give me data on"
|
| 1071 |
-
]
|
| 1072 |
-
state["is_wiki"] = any(trigger in q for trigger in triggers)
|
| 1073 |
-
return state
|
| 1074 |
-
|
| 1075 |
-
def _score_text(self, text):
|
| 1076 |
-
alnum_count = sum(c.isalnum() for c in text)
|
| 1077 |
-
space_count = text.count(' ')
|
| 1078 |
-
punctuation_count = sum(c in string.punctuation for c in text)
|
| 1079 |
-
ends_properly = text[-1] in '.!?'
|
| 1080 |
-
score = alnum_count + space_count
|
| 1081 |
-
if ends_properly:
|
| 1082 |
-
score += 5
|
| 1083 |
-
return score
|
| 1084 |
|
| 1085 |
|
| 1086 |
|
| 1087 |
-
########################################
|
| 1088 |
def run_and_submit_all( profile: gr.OAuthProfile | None):
|
| 1089 |
"""
|
| 1090 |
Fetches all questions, runs the BasicAgent on them, submits all answers,
|
|
|
|
| 23 |
nlp = spacy.load("en_core_web_sm")
|
| 24 |
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
# --- Constants ---
|
| 27 |
DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
| 28 |
|
| 29 |
+
# Define the State for the graph
|
| 30 |
+
class AgentState(TypedDict):
|
| 31 |
+
question: str
|
| 32 |
+
response: str
|
| 33 |
+
is_reversed: bool
|
| 34 |
+
is_riddle: bool
|
| 35 |
+
is_python: bool
|
| 36 |
+
needs_reasoning: bool
|
| 37 |
+
is_wiki: bool
|
| 38 |
|
| 39 |
class SuperSmartAgent:
|
| 40 |
def __init__(self):
|
|
|
|
| 43 |
extract_format=wikipediaapi.ExtractFormat.WIKI,
|
| 44 |
user_agent='SelimResearchAgent/1.0'
|
| 45 |
)
|
| 46 |
+
self.current_context = "" # Initialize current_context for use in methods that need it
|
| 47 |
self.graph = self._build_graph() # Build graph after initializing wiki_wiki
|
| 48 |
|
| 49 |
def _build_graph(self):
|
| 50 |
+
# Define the graph
|
| 51 |
+
workflow = StateGraph(AgentState)
|
| 52 |
+
|
| 53 |
+
# Add nodes (these will now be methods of the class)
|
| 54 |
+
workflow.add_node("check_reversed", self.check_reversed)
|
| 55 |
+
workflow.add_node("fix_question", self.fix_question)
|
| 56 |
+
workflow.add_node("check_riddle_or_trick", self.check_riddle_or_trick)
|
| 57 |
+
workflow.add_node("solve_riddle", self.solve_riddle)
|
| 58 |
+
workflow.add_node("check_python_suitability", self.check_python_suitability)
|
| 59 |
+
workflow.add_node("generate_code", self.generate_code)
|
| 60 |
+
workflow.add_node("check_reasoning_needed", self.check_reasoning_needed)
|
| 61 |
+
workflow.add_node("check_wikipedia_suitability", self.check_wikipedia_suitability)
|
| 62 |
+
workflow.add_node("general_reasoning_qa", self.general_reasoning_qa)
|
| 63 |
+
workflow.add_node("fallback", self.fallback) # A fallback node for questions that don't fit categories
|
| 64 |
+
|
| 65 |
+
# Set entry point
|
| 66 |
+
workflow.set_entry_point("check_reversed")
|
| 67 |
+
|
| 68 |
+
# Add edges
|
| 69 |
+
workflow.add_conditional_edges(
|
| 70 |
+
"check_reversed",
|
| 71 |
+
lambda state: "fix_question" if state["is_reversed"] else "check_riddle_or_trick",
|
| 72 |
+
)
|
| 73 |
+
workflow.add_edge("fix_question", "check_riddle_or_trick")
|
| 74 |
+
|
| 75 |
+
workflow.add_conditional_edges(
|
| 76 |
+
"check_riddle_or_trick",
|
| 77 |
+
lambda state: "solve_riddle" if state["is_riddle"] else "check_python_suitability",
|
| 78 |
+
)
|
| 79 |
+
workflow.add_edge("solve_riddle", END) # End if riddle solved
|
| 80 |
+
|
| 81 |
+
workflow.add_conditional_edges(
|
| 82 |
+
"check_python_suitability",
|
| 83 |
+
lambda state: "generate_code" if state["is_python"] else "check_wikipedia_suitability",
|
| 84 |
+
)
|
| 85 |
+
workflow.add_edge("generate_code", END) # End if code generated
|
| 86 |
+
|
| 87 |
+
workflow.add_conditional_edges(
|
| 88 |
+
"check_wikipedia_suitability",
|
| 89 |
+
lambda state: "general_reasoning_qa" if state["is_wiki"] else "fallback",
|
| 90 |
+
)
|
| 91 |
+
workflow.add_edge("general_reasoning_qa", END)
|
| 92 |
+
workflow.add_edge("fallback", END)
|
| 93 |
+
|
| 94 |
+
return workflow.compile()
|
| 95 |
+
|
| 96 |
+
def __call__(self, question: str) -> str:
|
| 97 |
+
"""
|
| 98 |
+
Runs the agent's graph with the given question.
|
| 99 |
+
"""
|
| 100 |
+
# Initialize state for each new question
|
| 101 |
+
initial_state = AgentState(
|
| 102 |
+
question=question,
|
| 103 |
+
response="",
|
| 104 |
+
is_reversed=False,
|
| 105 |
+
is_riddle=False,
|
| 106 |
+
is_python=False,
|
| 107 |
+
needs_reasoning=False,
|
| 108 |
+
is_wiki=False
|
| 109 |
+
)
|
| 110 |
+
final_state = self.graph.invoke(initial_state)
|
| 111 |
+
return final_state["response"]
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
# --- HELPER METHODS (NOW PART OF THE CLASS) ---
|
| 115 |
+
|
| 116 |
+
def score_text(self, text):
|
| 117 |
+
alnum_count = sum(c.isalnum() for c in text)
|
| 118 |
+
space_count = text.count(' ')
|
| 119 |
+
punctuation_count = sum(c in string.punctuation for c in text)
|
| 120 |
+
ends_properly = text[-1] in '.!?'
|
| 121 |
+
score = alnum_count + space_count
|
| 122 |
+
if ends_properly:
|
| 123 |
+
score += 5
|
| 124 |
+
return score
|
| 125 |
+
|
| 126 |
+
def check_reversed(self, state):
|
| 127 |
+
question = state["question"]
|
| 128 |
+
reversed_candidate = question[::-1]
|
| 129 |
+
original_score = self.score_text(question)
|
| 130 |
+
reversed_score = self.score_text(reversed_candidate)
|
| 131 |
+
if reversed_score > original_score:
|
| 132 |
+
state["is_reversed"] = True
|
| 133 |
+
else:
|
| 134 |
+
state["is_reversed"] = False
|
| 135 |
+
return state
|
| 136 |
+
|
| 137 |
+
def fix_question(self, state):
|
| 138 |
+
if state.get("is_reversed", False):
|
| 139 |
+
state["question"] = state["question"][::-1]
|
| 140 |
+
return state
|
| 141 |
+
|
| 142 |
+
def check_riddle_or_trick(self, state):
|
| 143 |
+
q = state["question"].lower()
|
| 144 |
+
keywords = ["opposite of", "if you understand", "riddle", "trick question", "what comes next", "i speak without"]
|
| 145 |
+
state["is_riddle"] = any(kw in q for kw in keywords)
|
| 146 |
+
return state
|
| 147 |
+
|
| 148 |
+
def solve_riddle(self, state):
|
| 149 |
+
q = state["question"].lower()
|
| 150 |
+
if "opposite of the word" in q:
|
| 151 |
+
if "left" in q:
|
| 152 |
+
state["response"] = "right"
|
| 153 |
+
elif "up" in q:
|
| 154 |
+
state["response"] = "down"
|
| 155 |
+
elif "hot" in q:
|
| 156 |
+
state["response"] = "cold"
|
| 157 |
else:
|
| 158 |
+
state["response"] = "Unknown opposite."
|
| 159 |
+
else:
|
| 160 |
+
state["response"] = "Could not solve riddle."
|
| 161 |
+
return state
|
| 162 |
+
|
| 163 |
+
def check_python_suitability(self, state):
|
| 164 |
+
question = state["question"].lower()
|
| 165 |
+
patterns = ["sum", "average", "count", "sort", "generate", "regex", "convert"]
|
| 166 |
+
state["is_python"] = any(word in question for word in patterns)
|
| 167 |
+
return state
|
| 168 |
+
|
| 169 |
+
def generate_code(self, state):
|
| 170 |
+
q = state["question"].lower()
|
| 171 |
+
if "sum" in q:
|
| 172 |
+
state["response"] = "numbers = [1, 2, 3]\nprint(sum(numbers))"
|
| 173 |
+
elif "average" in q:
|
| 174 |
+
state["response"] = "numbers = [1, 2, 3]\nprint(sum(numbers) / len(numbers))"
|
| 175 |
+
elif "sort" in q:
|
| 176 |
+
state["response"] = "data = [3, 1, 2]\ndata.sort()\nprint(data)"
|
| 177 |
+
else:
|
| 178 |
+
state["response"] = "# Code generation not implemented for this case."
|
| 179 |
+
return state
|
| 180 |
+
|
| 181 |
+
def fallback(self, state):
|
| 182 |
+
state["response"] = "This question doesn't require Python or is unclear."
|
| 183 |
+
return state
|
| 184 |
+
|
| 185 |
+
def check_reasoning_needed(self, state):
|
| 186 |
+
q = state["question"].lower()
|
| 187 |
+
needs_reasoning = any(word in q for word in ["whose", "only", "first", "after", "before", "no longer", "not", "but", "except"])
|
| 188 |
+
state["needs_reasoning"] = needs_reasoning
|
| 189 |
+
return state
|
| 190 |
+
|
| 191 |
+
def check_wikipedia_suitability(self, state):
|
| 192 |
+
q = state["question"].lower()
|
| 193 |
+
triggers = [
|
| 194 |
+
"wikipedia", "who is", "what is", "when did", "where is",
|
| 195 |
+
"tell me about", "how many", "how much", "what was the",
|
| 196 |
+
"describe", "explain", "information about", "details about",
|
| 197 |
+
"history of", "facts about", "define", "give me data on"
|
| 198 |
+
]
|
| 199 |
+
state["is_wiki"] = any(trigger in q for trigger in triggers)
|
| 200 |
+
return state
|
| 201 |
|
| 202 |
def get_relevant_context(self, question, search_results):
|
| 203 |
"""
|
|
|
|
| 273 |
context = re.sub(r'\[\d+\]', '', context) # Remove [1], [2], etc.
|
| 274 |
context = re.sub(r'<ref[^>]*>.*?<\/ref>', '', context, flags=re.DOTALL | re.IGNORECASE) # Remove <ref> tags
|
| 275 |
context = re.sub(r'\{\{.*?\}\}', '', context, flags=re.DOTALL) # Remove {{templates}}
|
| 276 |
+
context = re.sub(r'\{\|.*?\|\}\}', '', context, flags=re.DOTALL) # Remove wiki tables (if extract_tables_from_wikipedia doesn't catch all)
|
| 277 |
context = re.sub(r'==\s*See also\s*==.*?$', '', context, flags=re.DOTALL | re.IGNORECASE) # Remove "See also" section and anything after
|
| 278 |
context = re.sub(r'==\s*References\s*==.*?$', '', context, flags=re.DOTALL | re.IGNORECASE) # Remove "References" section and anything after
|
| 279 |
context = re.sub(r'\s+', ' ', context).strip() # Normalize whitespace
|
|
|
|
| 304 |
return state
|
| 305 |
|
| 306 |
context = self.get_relevant_context(question, search_results)
|
| 307 |
+
# Store the context in an instance variable so other methods can access it
|
| 308 |
+
self.current_context = context
|
| 309 |
+
|
| 310 |
if not context:
|
| 311 |
state["response"] = "Sorry, I couldn't find detailed relevant information."
|
| 312 |
return state
|
|
|
|
| 385 |
if not re.search(r'\d+', answer):
|
| 386 |
# If question asks for a number but answer has no number, it's likely wrong
|
| 387 |
return False
|
| 388 |
+
|
| 389 |
# Check for year/date answers for "when" questions
|
| 390 |
if "when" in question_lower or "year" in question_lower:
|
| 391 |
if not re.search(r'\b\d{4}\b', answer) and not re.search(r'\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\w*\s+\d{1,2}(?:st|nd|rd|th)?,\s+\d{4}\b', answer):
|
|
|
|
| 510 |
relevant_date = self.find_relevant_date_spacy(question_lower, numbers_dates, entities)
|
| 511 |
if relevant_date:
|
| 512 |
return f"The answer is {relevant_date}."
|
| 513 |
+
|
| 514 |
elif question_type == "location":
|
| 515 |
relevant_location = self.find_relevant_location_spacy(question_lower, entities)
|
| 516 |
if relevant_location:
|
|
|
|
| 773 |
headers = [self._clean_cell_content(cell).lower() for cell in table[0]] if table else []
|
| 774 |
data_rows = table[1:] if len(table) > 1 else []
|
| 775 |
|
| 776 |
+
# Determine column types (placeholder for now, needs implementation)
|
| 777 |
column_types = self.detect_column_types(table)
|
| 778 |
|
| 779 |
+
# ... (Rest of your table search logic would go here)
|
| 780 |
+
# For now, just return a dummy answer if a table is found to avoid the error.
|
| 781 |
+
# You'll need to implement the actual table search and answer extraction logic.
|
| 782 |
+
# Example:
|
| 783 |
+
if "president" in question_lower and "usa" in question_lower and headers and "name" in headers:
|
| 784 |
+
name_col_idx = headers.index("name")
|
| 785 |
+
# More complex logic needed to find relevant row
|
| 786 |
+
return "Placeholder: Found answer in table for president question."
|
| 787 |
+
|
| 788 |
+
# Simple keyword match within table cells as a basic example
|
| 789 |
+
for row_idx, row in enumerate(table):
|
| 790 |
+
for cell_idx, cell_content in enumerate(row):
|
| 791 |
+
cell_lower = cell_content.lower()
|
| 792 |
+
if any(keyword in cell_lower for keyword in question_keywords):
|
| 793 |
+
# This is very basic, a proper implementation would consider column headers
|
| 794 |
+
# and question type to return a specific cell or a combination.
|
| 795 |
+
if headers and cell_idx < len(headers):
|
| 796 |
+
return f"Found '{cell_content}' under '{headers[cell_idx]}' in a table."
|
| 797 |
+
else:
|
| 798 |
+
return f"Found '{cell_content}' in a table."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 799 |
return None
|
| 800 |
|
| 801 |
def detect_column_types(self, table):
|
| 802 |
"""
|
| 803 |
+
Detects the type of data in each column (e.g., 'number', 'text', 'date').
|
| 804 |
+
This is a placeholder and would need a robust implementation.
|
| 805 |
"""
|
| 806 |
+
if not table or not table[0]:
|
| 807 |
return []
|
| 808 |
+
|
| 809 |
+
num_cols = len(table[0])
|
| 810 |
+
column_types = ["text"] * num_cols # Default to text
|
| 811 |
|
| 812 |
+
# Example: rudimentary detection for the first few rows
|
| 813 |
+
for col_idx in range(num_cols):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 814 |
num_count = 0
|
|
|
|
| 815 |
date_count = 0
|
| 816 |
+
for row_idx in range(1, min(len(table), 5)): # Check first 5 data rows
|
| 817 |
+
if col_idx < len(table[row_idx]):
|
| 818 |
+
cell_content = self._clean_cell_content(table[row_idx][col_idx])
|
| 819 |
+
if re.match(r'^\d+(\.\d+)?$', cell_content):
|
| 820 |
+
num_count += 1
|
| 821 |
+
if re.match(r'\b\d{4}\b', cell_content) or re.match(r'\b(Jan|Feb|Mar)\w*\s+\d{1,2}(?:st|nd|rd|th)?(?:,\s+\d{4})?\b', cell_content, re.IGNORECASE):
|
| 822 |
+
date_count += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 823 |
|
| 824 |
+
if num_count > 2: # Heuristic: if more than 2 numbers
|
| 825 |
+
column_types[col_idx] = "number"
|
| 826 |
+
elif date_count > 1: # Heuristic: if more than 1 date
|
| 827 |
+
column_types[col_idx] = "date"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 828 |
return column_types
|
| 829 |
|
| 830 |
+
# --- Gradio Interface (as provided, assuming it will call agent correctly) ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 831 |
|
| 832 |
+
# Define the Gradio interface if needed for testing outside the main app.
|
| 833 |
+
# If this is part of a larger application, this might be handled differently.
|
| 834 |
+
if __name__ == '__main__':
|
| 835 |
+
agent = SuperSmartAgent() # Instantiate the agent
|
| 836 |
|
| 837 |
+
def chat_interface(question):
|
| 838 |
+
response = agent(question) # The __call__ method handles the graph invocation
|
| 839 |
+
return response
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
|
| 841 |
+
if os.getenv("RUN_GRADIO_APP", "false").lower() == "true":
|
| 842 |
+
demo = gr.Interface(
|
| 843 |
+
fn=chat_interface,
|
| 844 |
+
inputs=gr.Textbox(lines=2, placeholder="Enter your question here..."),
|
| 845 |
+
outputs="text",
|
| 846 |
+
title="Super Smart Agent",
|
| 847 |
+
description="An agent capable of answering questions, solving riddles, and generating Python code."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 848 |
)
|
| 849 |
+
demo.launch()
|
| 850 |
|
| 851 |
+
# Example usage for testing without Gradio:
|
| 852 |
+
print("Testing SuperSmartAgent without Gradio:")
|
| 853 |
+
my_agent = SuperSmartAgent()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 854 |
|
| 855 |
+
test_questions = [
|
| 856 |
+
"What is the opposite of the word 'cold'?",
|
| 857 |
+
"How many planets are in our solar system?",
|
| 858 |
+
"Sort the list [5, 2, 8, 1]",
|
| 859 |
+
"What is the capital of France?",
|
| 860 |
+
"When was Albert Einstein born?",
|
| 861 |
+
"If I have 3 apples and you take 1, how many do I have?", # Riddle
|
| 862 |
+
"What is the population of Tokyo?",
|
| 863 |
+
"What is the sum of 10 and 20?",
|
| 864 |
+
"gnitset a si sihT", # Reversed question
|
| 865 |
+
"Who invented the telephone?"
|
| 866 |
+
]
|
| 867 |
|
| 868 |
+
for q in test_questions:
|
| 869 |
+
print(f"\nQuestion: {q}")
|
| 870 |
+
answer = my_agent(q)
|
| 871 |
+
print(f"Answer: {answer}")
|
| 872 |
+
|
| 873 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 874 |
|
| 875 |
|
| 876 |
|
|
|
|
| 877 |
def run_and_submit_all( profile: gr.OAuthProfile | None):
|
| 878 |
"""
|
| 879 |
Fetches all questions, runs the BasicAgent on them, submits all answers,
|