iitmbs24f commited on
Commit
8740e76
·
verified ·
1 Parent(s): 8c62f03

Upload 15 files

Browse files
Files changed (3) hide show
  1. app/agent_fallback.py +153 -0
  2. app/solver.py +12 -7
  3. app/utils.py +38 -0
app/agent_fallback.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LangGraph-based agent fallback for complex quiz solving.
3
+ Used when structured strategies fail or for novel quiz types.
4
+ """
5
+ import os
6
+ import logging
7
+ import time
8
+ from typing import Dict, Any, Optional, List, Annotated
9
+ from typing_extensions import TypedDict
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Try to import LangGraph components (optional dependency)
14
+ try:
15
+ from langgraph.graph import StateGraph, END, START
16
+ from langgraph.prebuilt import ToolNode
17
+ from langchain_core.messages import trim_messages, HumanMessage
18
+ from langchain.chat_models import init_chat_model
19
+ from langgraph.graph.message import add_messages
20
+ from langchain_core.rate_limiters import InMemoryRateLimiter
21
+ LANGGRAPH_AVAILABLE = True
22
+ except ImportError:
23
+ LANGGRAPH_AVAILABLE = False
24
+ logger.warning("LangGraph not available - agent fallback disabled")
25
+
26
+
27
+ class AgentState(TypedDict):
28
+ """State for LangGraph agent."""
29
+ messages: Annotated[List, add_messages]
30
+
31
+
32
+ class AgentFallback:
33
+ """Agent-based fallback solver using LangGraph."""
34
+
35
+ def __init__(self, email: str, secret: str):
36
+ self.email = email
37
+ self.secret = secret
38
+ self.agent = None
39
+ self.tools = []
40
+
41
+ if LANGGRAPH_AVAILABLE:
42
+ self._initialize_agent()
43
+
44
+ def _initialize_agent(self):
45
+ """Initialize the LangGraph agent."""
46
+ try:
47
+ # Define tools (simplified - you'd import from your tools module)
48
+ # For now, we'll create a minimal agent
49
+
50
+ # Initialize LLM with rate limiting
51
+ rate_limiter = InMemoryRateLimiter(
52
+ requests_per_second=4 / 60,
53
+ check_every_n_seconds=1,
54
+ max_bucket_size=4
55
+ )
56
+
57
+ llm = init_chat_model(
58
+ model_provider="google_genai",
59
+ model="gemini-2.5-flash",
60
+ rate_limiter=rate_limiter
61
+ )
62
+
63
+ # Create simple graph (you'd add your tools here)
64
+ graph = StateGraph(AgentState)
65
+ graph.add_node("agent", self._agent_node)
66
+ graph.add_edge(START, "agent")
67
+ graph.add_conditional_edges("agent", self._route, {END: END})
68
+
69
+ self.agent = graph.compile()
70
+ logger.info("Agent fallback initialized")
71
+
72
+ except Exception as e:
73
+ logger.error(f"Error initializing agent: {e}")
74
+ self.agent = None
75
+
76
+ def _agent_node(self, state: AgentState):
77
+ """Agent node that processes messages."""
78
+ # Simplified - would use actual LLM here
79
+ return {"messages": state["messages"]}
80
+
81
+ def _route(self, state: AgentState):
82
+ """Route logic for agent."""
83
+ return END
84
+
85
+ async def solve(self, question: str, page_content: Dict[str, Any],
86
+ remaining_time: float) -> Optional[Any]:
87
+ """
88
+ Attempt to solve using agent-based approach.
89
+
90
+ Args:
91
+ question: Question text
92
+ page_content: Page content
93
+ remaining_time: Time remaining in seconds
94
+
95
+ Returns:
96
+ Answer if solved, None otherwise
97
+ """
98
+ if not LANGGRAPH_AVAILABLE or not self.agent:
99
+ return None
100
+
101
+ # Only use agent if we have enough time
102
+ if remaining_time < 30.0:
103
+ logger.debug("Skipping agent fallback - insufficient time")
104
+ return None
105
+
106
+ try:
107
+ logger.info("Attempting agent-based solution...")
108
+
109
+ system_prompt = f"""
110
+ You are a quiz-solving agent. Solve this question:
111
+
112
+ Question: {question}
113
+
114
+ Page Content: {page_content.get('text', '')[:2000]}
115
+
116
+ Email: {self.email}
117
+ Secret: {self.secret}
118
+
119
+ Provide a clear, concise answer.
120
+ """
121
+
122
+ initial_messages = [
123
+ {"role": "system", "content": system_prompt},
124
+ {"role": "user", "content": question}
125
+ ]
126
+
127
+ # Run agent with timeout
128
+ result = self.agent.invoke(
129
+ {"messages": initial_messages},
130
+ config={"recursion_limit": 100}
131
+ )
132
+
133
+ # Extract answer from result
134
+ if result and "messages" in result:
135
+ last_message = result["messages"][-1]
136
+ if hasattr(last_message, "content"):
137
+ return last_message.content
138
+ elif isinstance(last_message, dict) and "content" in last_message:
139
+ return last_message["content"]
140
+
141
+ return None
142
+
143
+ except Exception as e:
144
+ logger.error(f"Error in agent fallback: {e}")
145
+ return None
146
+
147
+
148
+ def get_agent_fallback(email: str, secret: str) -> Optional[AgentFallback]:
149
+ """Get or create agent fallback instance."""
150
+ if not LANGGRAPH_AVAILABLE:
151
+ return None
152
+ return AgentFallback(email, secret)
153
+
app/solver.py CHANGED
@@ -316,13 +316,18 @@ class QuizSolver:
316
  # Reserve at least 10s for submission
317
  if remaining >= 25.0: # Increased threshold to ensure time for submission
318
  logger.info("Attempting to solve with LLM...")
319
- llm_answer = await solve_with_llm(question, available_data)
320
- if llm_answer:
321
- # Try to parse as JSON if it looks like JSON
322
- json_answer = extract_json_from_text(llm_answer)
323
- if json_answer:
324
- return json_answer
325
- return llm_answer
 
 
 
 
 
326
  else:
327
  logger.warning(f"Skipping LLM call - insufficient time remaining ({remaining:.1f}s, need 25s)")
328
 
 
316
  # Reserve at least 10s for submission
317
  if remaining >= 25.0: # Increased threshold to ensure time for submission
318
  logger.info("Attempting to solve with LLM...")
319
+ try:
320
+ llm_answer = await solve_with_llm(question, available_data)
321
+ if llm_answer:
322
+ # Try to parse as JSON if it looks like JSON
323
+ json_answer = extract_json_from_text(llm_answer)
324
+ if json_answer:
325
+ return json_answer
326
+ return llm_answer
327
+ except Exception as e:
328
+ logger.warning(f"LLM call failed: {e}, trying to extract answer from response")
329
+ # Try to extract any useful information from the error
330
+ pass
331
  else:
332
  logger.warning(f"Skipping LLM call - insufficient time remaining ({remaining:.1f}s, need 25s)")
333
 
app/utils.py CHANGED
@@ -157,9 +157,47 @@ def extract_json_from_text(text: str) -> Optional[Dict[str, Any]]:
157
  except json.JSONDecodeError:
158
  continue
159
 
 
 
 
 
 
 
 
 
 
 
160
  return None
161
 
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  def is_valid_url(url: str) -> bool:
164
  """
165
  Validate if a string is a valid URL.
 
157
  except json.JSONDecodeError:
158
  continue
159
 
160
+ # Try to fix common JSON issues
161
+ try:
162
+ # Remove markdown code blocks
163
+ text = re.sub(r'```json\s*', '', text)
164
+ text = re.sub(r'```\s*', '', text)
165
+ # Try parsing the cleaned text
166
+ return json.loads(text.strip())
167
+ except json.JSONDecodeError:
168
+ pass
169
+
170
  return None
171
 
172
 
173
+ def safe_extract_json(text: str, max_retries: int = 1) -> Optional[Dict[str, Any]]:
174
+ """
175
+ Safely extract JSON with better error handling.
176
+
177
+ Args:
178
+ text: Text that may contain JSON
179
+ max_retries: Maximum retry attempts
180
+
181
+ Returns:
182
+ Parsed JSON dict or None
183
+ """
184
+ result = extract_json_from_text(text)
185
+ if result:
186
+ return result
187
+
188
+ # Try to fix common issues
189
+ fixed_text = text
190
+ # Remove leading/trailing whitespace and newlines
191
+ fixed_text = fixed_text.strip()
192
+ # Remove markdown formatting
193
+ fixed_text = re.sub(r'^```(?:json)?\s*', '', fixed_text, flags=re.MULTILINE)
194
+ fixed_text = re.sub(r'\s*```$', '', fixed_text, flags=re.MULTILINE)
195
+ # Try again with fixed text
196
+ result = extract_json_from_text(fixed_text)
197
+
198
+ return result
199
+
200
+
201
  def is_valid_url(url: str) -> bool:
202
  """
203
  Validate if a string is a valid URL.