Lasdw commited on
Commit
bef09db
·
1 Parent(s): 7904f75

updated UI and added CSS

Browse files
Files changed (8) hide show
  1. .gitignore +2 -1
  2. LICENSE +21 -0
  3. README.md +50 -3
  4. agent.py +35 -4
  5. agent_langgraph.py +0 -399
  6. app.py +275 -156
  7. static/custom.css +478 -0
  8. tools.py +5 -3
.gitignore CHANGED
@@ -15,4 +15,5 @@ __pycache__/*
15
  *.pyo
16
  *.pyd
17
 
18
- TEMPP/*
 
 
15
  *.pyo
16
  *.pyd
17
 
18
+ TEMPP/*
19
+ test.txt
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Vividh Mahajan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: Deep Research Agent
3
- emoji: 🤓
4
  colorFrom: yellow
5
  colorTo: red
6
  sdk: gradio
@@ -11,4 +11,51 @@ hf_oauth: true
11
  hf_oauth_expiration_minutes: 480
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: ScholarAI
3
+ emoji: 🎓
4
  colorFrom: yellow
5
  colorTo: red
6
  sdk: gradio
 
11
  hf_oauth_expiration_minutes: 480
12
  ---
13
 
14
+ # ScholarAI 🎓
15
+
16
+ [![Build Status](https://img.shields.io/badge/build-passing-brightgreen)](https://huggingface.co/spaces/Lasdw/ScholarAI)
17
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
18
+ [![Version](https://img.shields.io/badge/version-1.0.0-blue)](https://huggingface.co/spaces/Lasdw/ScholarAI)
19
+ [![Python](https://img.shields.io/badge/python-3.11-blue)](https://www.python.org/downloads/)
20
+ [![Gradio](https://img.shields.io/badge/gradio-5.29.1-orange)](https://gradio.app/)
21
+ [![Stars](https://img.shields.io/github/stars/Lasdw/ScholarAI?style=social)](https://github.com/Lasdw/ScholarAI)
22
+
23
+ An AI-powered research assistant that helps you find answers by searching the web, analyzing images, processing audio, and more.
24
+
25
+ ## Features
26
+
27
+ - Web search and Wikipedia integration
28
+ - Image analysis
29
+ - Audio processing
30
+ - Code analysis
31
+ - Data file processing
32
+
33
+ ## Requirements
34
+
35
+ ```bash
36
+ pip install -r requirements.txt
37
+ ```
38
+
39
+ Key dependencies:
40
+
41
+ - gradio: Web interface
42
+ - langchain & langgraph: AI agent framework
43
+ - openai: Language model integration
44
+ - beautifulsoup4 & html2text: Web scraping
45
+ - pytube & youtube-transcript-api: Video processing
46
+ - whisper: Audio transcription
47
+ - pandas & openpyxl: Data processing
48
+ - Pillow: Image processing
49
+ - PyPDF2 & pymupdf: PDF handling
50
+
51
+ ## License
52
+
53
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
54
+
55
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
56
+
57
+ ## Author
58
+
59
+ Created by [Vividh Mahajan](https://huggingface.co/Lasdw)
60
+
61
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
agent.py CHANGED
@@ -57,7 +57,7 @@ load_dotenv()
57
  #webpage_scrape: Scrape content from a specific webpage URL when Tavily Search and Wikipedia Search do not return a result. Provide a valid URL to extract information from a particular web page.
58
  #Give preference to using Tavily Search and Wikipedia Search before using web_search or webpage_scrape. When Web_search does not return a result, use Tavily Search.
59
 
60
- SYSTEM_PROMPT = """ You are a genuis deep reseach assistant called TurboNerd, made by Vividh Mahajan. Answer the following questions as best you can. If it is a basic question, answer it using your internal knowledge. If it is a complex question that requires facts, use the tools to answer it DO NOT rely on your internal knowledge unless the tools fail to provide a result:
61
  For simple questions, you can use your internal knowledge and answer directly. If you do not understand the question, ask for clarification after trying to answer the question yourself.
62
 
63
  The way you use the tools is by specifying a json blob. These are the only tools you can use:
@@ -1320,16 +1320,47 @@ def create_agent_graph() -> StateGraph:
1320
  return builder.compile()
1321
 
1322
  # Main agent class that integrates with your existing app.py
1323
- class TurboNerd:
1324
- def __init__(self, max_iterations=35, apify_api_token=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1325
  self.graph = create_agent_graph()
1326
  self.tools = tools_config
1327
  self.max_iterations = max_iterations # Maximum iterations for the graph
1328
 
 
 
 
 
 
 
 
 
1329
  # Set Apify API token if provided
1330
  if apify_api_token:
1331
  os.environ["APIFY_API_TOKEN"] = apify_api_token
1332
  print("Apify API token set successfully")
 
 
1333
 
1334
  def __call__(self, question: str, attachments: dict = None) -> str:
1335
  """
@@ -1392,7 +1423,7 @@ class TurboNerd:
1392
 
1393
  # Example usage:
1394
  if __name__ == "__main__":
1395
- agent = TurboNerd(max_iterations=25)
1396
  response = agent("""The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places. TEMPP\excel.xlsx """)
1397
  print("\nFinal Response:")
1398
  print(response)
 
57
  #webpage_scrape: Scrape content from a specific webpage URL when Tavily Search and Wikipedia Search do not return a result. Provide a valid URL to extract information from a particular web page.
58
  #Give preference to using Tavily Search and Wikipedia Search before using web_search or webpage_scrape. When Web_search does not return a result, use Tavily Search.
59
 
60
+ SYSTEM_PROMPT = """ You are a genuis deep reseach assistant called ScholarAI, made by Vividh Mahajan. Answer the following questions as best you can. If it is a basic question, answer it using your internal knowledge. If it is a complex question that requires facts, use the tools to answer it DO NOT rely on your internal knowledge unless the tools fail to provide a result:
61
  For simple questions, you can use your internal knowledge and answer directly. If you do not understand the question, ask for clarification after trying to answer the question yourself.
62
 
63
  The way you use the tools is by specifying a json blob. These are the only tools you can use:
 
1320
  return builder.compile()
1321
 
1322
  # Main agent class that integrates with your existing app.py
1323
+ class ScholarAI:
1324
+ def __init__(self, max_iterations=35, temperature=0.1, max_tokens=2000, model="gpt-4o-mini", apify_api_token=None):
1325
+ # Check for OpenAI API key
1326
+ if not os.getenv("OPENAI_API_KEY"):
1327
+ raise ValueError("OpenAI API key not found. Please set the OPENAI_API_KEY environment variable.")
1328
+
1329
+ try:
1330
+ # Test the API key with a simple request
1331
+ test_llm = ChatOpenAI(
1332
+ model=model,
1333
+ temperature=temperature,
1334
+ max_tokens=max_tokens
1335
+ )
1336
+ test_llm.invoke("test") # This will fail if API key is invalid
1337
+ except Exception as e:
1338
+ error_msg = str(e).lower()
1339
+ if "invalid_api_key" in error_msg or "incorrect_api_key" in error_msg:
1340
+ raise ValueError("Invalid OpenAI API key. Please check your API key and try again.")
1341
+ elif "rate_limit" in error_msg or "quota" in error_msg:
1342
+ raise ValueError("OpenAI API rate limit exceeded or quota reached. Please try again later.")
1343
+ else:
1344
+ raise ValueError(f"Error initializing OpenAI client: {str(e)}")
1345
+
1346
  self.graph = create_agent_graph()
1347
  self.tools = tools_config
1348
  self.max_iterations = max_iterations # Maximum iterations for the graph
1349
 
1350
+ # Update the global llm instance with the specified parameters
1351
+ global llm
1352
+ llm = ChatOpenAI(
1353
+ model=model,
1354
+ temperature=temperature,
1355
+ max_tokens=max_tokens
1356
+ )
1357
+
1358
  # Set Apify API token if provided
1359
  if apify_api_token:
1360
  os.environ["APIFY_API_TOKEN"] = apify_api_token
1361
  print("Apify API token set successfully")
1362
+
1363
+ print(f"ScholarAI initialized with model={model}, temperature={temperature}, max_tokens={max_tokens}")
1364
 
1365
  def __call__(self, question: str, attachments: dict = None) -> str:
1366
  """
 
1423
 
1424
  # Example usage:
1425
  if __name__ == "__main__":
1426
+ agent = ScholarAI(max_iterations=25)
1427
  response = agent("""The attached Excel file contains the sales of menu items for a local fast-food chain. What were the total sales that the chain made from food (not including drinks)? Express your answer in USD with two decimal places. TEMPP\excel.xlsx """)
1428
  print("\nFinal Response:")
1429
  print(response)
agent_langgraph.py DELETED
@@ -1,399 +0,0 @@
1
- import os
2
- from typing import TypedDict, Annotated
3
- from langgraph.graph.message import add_messages
4
- from langchain_core.messages import AnyMessage, HumanMessage, AIMessage, SystemMessage
5
- from langgraph.prebuilt import ToolNode
6
- from langchain.tools import Tool
7
- from langgraph.graph import START, END, StateGraph
8
- from langgraph.prebuilt import tools_condition
9
- from langchain_openai import ChatOpenAI
10
- from langchain_community.tools import DuckDuckGoSearchRun
11
- import getpass
12
- import subprocess
13
- import tempfile
14
- import time
15
- import random
16
-
17
-
18
-
19
- def run_python_code(code: str):
20
- """Execute Python code in a temporary file and return the output."""
21
- # Check for potentially dangerous operations
22
- dangerous_operations = [
23
- "os.system", "os.popen", "os.unlink", "os.remove",
24
- "subprocess.run", "subprocess.call", "subprocess.Popen",
25
- "shutil.rmtree", "shutil.move", "shutil.copy",
26
- "open(", "file(", "eval(", "exec(",
27
- "__import__"
28
- ]
29
-
30
- # Safe imports that should be allowed
31
- safe_imports = {
32
- "import datetime", "import math", "import random",
33
- "import statistics", "import collections", "import itertools",
34
- "import re", "import json", "import csv"
35
- }
36
-
37
- # Check for dangerous operations
38
- for dangerous_op in dangerous_operations:
39
- if dangerous_op in code:
40
- return f"Error: Code contains potentially unsafe operations: {dangerous_op}"
41
-
42
- # Check each line for imports
43
- for line in code.splitlines():
44
- line = line.strip()
45
- if line.startswith("import ") or line.startswith("from "):
46
- # Skip if it's in our safe list
47
- if any(line.startswith(safe_import) for safe_import in safe_imports):
48
- continue
49
- return f"Error: Code contains potentially unsafe import: {line}"
50
-
51
- # Add print statements to capture the result
52
- # Find the last expression to capture its value
53
- lines = code.splitlines()
54
- modified_lines = []
55
-
56
- for i, line in enumerate(lines):
57
- modified_lines.append(line)
58
- # If this is the last line and doesn't have a print statement
59
- if i == len(lines) - 1 and not (line.strip().startswith("print(") or line.strip() == ""):
60
- # Add a print statement for the last expression
61
- if not line.strip().endswith(":"): # Not a control structure
62
- modified_lines.append(f"print('Result:', {line.strip()})")
63
-
64
- modified_code = "\n".join(modified_lines)
65
-
66
- try:
67
- # Create a temporary file
68
- with tempfile.NamedTemporaryFile(suffix='.py', delete=False) as temp:
69
- temp_path = temp.name
70
- # Write the code to the file
71
- temp.write(modified_code.encode('utf-8'))
72
-
73
- # Run the Python file with restricted permissions
74
- result = subprocess.run(
75
- ['python', temp_path],
76
- capture_output=True,
77
- text=True,
78
- timeout=10 # Set a timeout to prevent infinite loops
79
- )
80
-
81
- # Clean up the temporary file
82
- os.unlink(temp_path)
83
-
84
- # Return the output or error
85
- if result.returncode == 0:
86
- output = result.stdout.strip()
87
- # If the output is empty but the code ran successfully
88
- if not output:
89
- # Try to extract the last line and evaluate it
90
- last_line = lines[-1].strip()
91
- if not last_line.startswith("print") and not last_line.endswith(":"):
92
- return f"Code executed successfully. The result of the last expression '{last_line}' should be its value."
93
- else:
94
- return "Code executed successfully with no output."
95
- return output
96
- else:
97
- return f"Error executing code: {result.stderr}"
98
- except subprocess.TimeoutExpired:
99
- # Clean up if timeout
100
- os.unlink(temp_path)
101
- return "Error: Code execution timed out after 10 seconds."
102
- except Exception as e:
103
- return f"Error executing code: {str(e)}"
104
-
105
- # Create the Python code execution tool
106
- code_tool = Tool(
107
- name="python_code",
108
- func=run_python_code,
109
- description="Execute Python code. Provide the complete Python code as a string. The code will be executed and the output will be returned. Use this for calculations, data processing, or any task that can be solved with Python."
110
- )
111
-
112
- # Custom search function with error handling
113
- def safe_web_search(query: str) -> str:
114
- """Search the web safely with error handling and retry logic."""
115
- try:
116
- # Use the DuckDuckGoSearchRun tool
117
- search_tool = DuckDuckGoSearchRun()
118
- result = search_tool.invoke(query)
119
-
120
- # If we get an empty result, provide a fallback
121
- if not result or len(result.strip()) < 10:
122
- return f"Unable to find specific information about '{query}'. Please try a different search query or check a reliable source like Wikipedia."
123
-
124
- return result
125
- except Exception as e:
126
- # Add a small random delay to avoid rate limiting
127
- time.sleep(random.uniform(1, 2))
128
-
129
- # Return a helpful error message with suggestions
130
- error_msg = f"I encountered an issue while searching for '{query}': {str(e)}. "
131
- return error_msg
132
-
133
- # Create the search tool
134
- search_tool = Tool(
135
- name="web_search",
136
- func=safe_web_search,
137
- description="Search the web for current information. Provide a specific search query."
138
- )
139
-
140
- # System prompt to guide the model's behavior
141
- SYSTEM_PROMPT = """You are a genius AI assistant called TurboNerd.
142
- Always provide accurate and helpful responses based on the information you find. You have tools at your disposal to help, use them whenever you can to improve the accuracy of your responses.
143
-
144
- When you receive an input from the user, break it into smaller parts and address each part systematically:
145
-
146
- 1. For information retrieval (like finding current data, statistics, etc.), use the web_search tool.
147
- - If the search fails, don't repeatedly attempt identical searches. Provide the best information you have and be honest about limitations.
148
-
149
- 2. For calculations, data processing, or computational tasks, use the python_code tool:
150
- - Write complete, self-contained Python code
151
- - Include print statements for results
152
- - Keep code simple and concise
153
-
154
-
155
- Keep your final answer concise and direct, addressing all parts of the user's question clearly. DO NOT include any other text in your response, just the answer.
156
- """
157
- #Your response will be evaluated for accuracy and completeness. After you provide an answer, an evaluator will check your work and may ask you to improve it. The evaluation process has a maximum of 3 attempts.
158
-
159
- # Generate the chat interface, including the tools
160
- llm = ChatOpenAI(
161
- model="gpt-4o-mini",
162
- temperature=0
163
- )
164
-
165
- chat = llm
166
- tools = [search_tool, code_tool]
167
- chat_with_tools = chat.bind_tools(tools)
168
-
169
- # Generate the AgentState and Agent graph
170
- class AgentState(TypedDict):
171
- messages: Annotated[list[AnyMessage], add_messages]
172
-
173
- def assistant(state: AgentState):
174
- # Add system message if it's the first message
175
- print("Assistant Called...\n\n")
176
- print(f"Assistant state keys: {state.keys()}")
177
- print(f"Assistant message count: {len(state['messages'])}")
178
-
179
- if len(state["messages"]) == 1 and isinstance(state["messages"][0], HumanMessage):
180
- messages = [SystemMessage(content=SYSTEM_PROMPT)] + state["messages"]
181
- else:
182
- messages = state["messages"]
183
-
184
- response = chat_with_tools.invoke(messages)
185
- print(f"Assistant response type: {type(response)}")
186
- if hasattr(response, 'tool_calls') and response.tool_calls:
187
- print(f"Tool calls detected: {len(response.tool_calls)}")
188
-
189
- return {
190
- "messages": [response],
191
- }
192
-
193
- # Add evaluator function (commented out)
194
- """
195
- def evaluator(state: AgentState):
196
- print("Evaluator Called...\n\n")
197
- print(f"Evaluator state keys: {state.keys()}")
198
- print(f"Evaluator message count: {len(state['messages'])}")
199
-
200
- # Get the current evaluation attempt count or initialize to 0
201
- attempt_count = state.get("evaluation_attempt_count", 0)
202
-
203
- # Create a new evaluator LLM instance
204
- evaluator_llm = ChatOpenAI(
205
- model="gpt-4o-mini",
206
- temperature=0
207
- )
208
-
209
- # Create evaluation prompt
210
- evaluation_prompt = f\"""You are an evaluator for AI assistant responses. Your job is to:
211
-
212
- 1. Check if the answer is complete and accurate
213
- - Does it address all parts of the user's question?
214
- - Is the information factually correct to the best of your knowledge?
215
-
216
- 2. Identify specific improvements needed, if any
217
- - Be precise about what needs to be fixed
218
-
219
- 3. Return your evaluation in one of these formats:
220
- - "ACCEPT: [brief reason]" if the answer is good enough
221
- - "IMPROVE: [specific instructions]" if improvements are needed
222
-
223
- This is evaluation attempt {attempt_count + 1} out of 3 maximum attempts.
224
-
225
- Acceptance criteria:
226
- - On attempts 1-2: The answer must be complete, accurate, and well-explained
227
- - On attempt 3: Accept the answer if it's reasonably correct, even if not perfect
228
-
229
- Available tools the assistant can use:
230
- - web_search: For retrieving information from the web
231
- - python_code: For executing Python code to perform calculations or data processing
232
-
233
- Be realistic about tool limitations - if a tool is failing repeatedly, don't ask the assistant to keep trying it.
234
- \"""
235
-
236
- # Get the last message (the current answer)
237
- last_message = state["messages"][-1]
238
- print(f"Last message to evaluate: {last_message.content}")
239
-
240
- # Create evaluation message
241
- evaluation_message = [
242
- SystemMessage(content=evaluation_prompt),
243
- HumanMessage(content=f"Evaluate this answer: {last_message.content}")
244
- ]
245
-
246
- # Get evaluation
247
- evaluation = evaluator_llm.invoke(evaluation_message)
248
- print(f"Evaluation result: {evaluation.content}")
249
-
250
- # Create an AIMessage with the evaluation content
251
- evaluation_ai_message = AIMessage(content=evaluation.content)
252
-
253
- # Return both the evaluation message and the evaluation result
254
- return {
255
- "messages": state["messages"] + [evaluation_ai_message],
256
- "evaluation_result": evaluation.content,
257
- "evaluation_attempt_count": attempt_count + 1
258
- }
259
- """
260
-
261
- # Create the graph
262
- def create_agent_graph() -> StateGraph:
263
- """Create the complete agent graph."""
264
- builder = StateGraph(AgentState)
265
-
266
- # Define nodes: these do the work
267
- builder.add_node("assistant", assistant)
268
- builder.add_node("tools", ToolNode(tools))
269
- # builder.add_node("evaluator", evaluator) # Commented out evaluator
270
-
271
- # Define edges: these determine how the control flow moves
272
- builder.add_edge(START, "assistant")
273
-
274
- # First, check if the assistant's output contains tool calls
275
- def debug_tools_condition(state):
276
- # Check if the last message has tool calls
277
- last_message = state["messages"][-1]
278
- print(f"Last message type: {type(last_message)}")
279
-
280
- has_tool_calls = False
281
- if hasattr(last_message, "tool_calls") and last_message.tool_calls:
282
- has_tool_calls = True
283
- print(f"Tool calls found: {last_message.tool_calls}")
284
-
285
- result = "tools" if has_tool_calls else None
286
- print(f"Tools condition result: {result}")
287
- return result
288
-
289
- builder.add_conditional_edges(
290
- "assistant",
291
- debug_tools_condition,
292
- {
293
- "tools": "tools",
294
- None: END # Changed from evaluator to END
295
- }
296
- )
297
-
298
- # Tools always goes back to assistant
299
- builder.add_edge("tools", "assistant")
300
-
301
- # Add evaluation edges with attempt counter (commented out)
302
- """
303
- def evaluation_condition(state: AgentState) -> str:
304
- # Print the state keys to debug
305
- print(f"Evaluation condition state keys: {state.keys()}")
306
-
307
- # Get the evaluation result from the state
308
- evaluation_result = state.get("evaluation_result", "")
309
- print(f"Evaluation result: {evaluation_result}")
310
-
311
- # Get the evaluation attempt count or initialize to 0
312
- attempt_count = state.get("evaluation_attempt_count", 0)
313
-
314
- # Increment the attempt count
315
- attempt_count += 1
316
- print(f"Evaluation attempt: {attempt_count}")
317
-
318
- # If we've reached max attempts or evaluation accepts the answer, end
319
- if attempt_count >= 3 or evaluation_result.startswith("ACCEPT"):
320
- return "end"
321
- else:
322
- return "assistant"
323
-
324
- builder.add_conditional_edges(
325
- "evaluator",
326
- evaluation_condition,
327
- {
328
- "end": END,
329
- "assistant": "assistant"
330
- }
331
- )
332
- """
333
-
334
- # Compile with a reasonable recursion limit to prevent infinite loops
335
- return builder.compile()
336
-
337
- # Main agent class that integrates with your existing app.py
338
- class TurboNerd:
339
- def __init__(self, max_execution_time=30):
340
- self.graph = create_agent_graph()
341
- self.tools = tools
342
- self.max_execution_time = max_execution_time # Maximum execution time in seconds
343
-
344
- def __call__(self, question: str) -> str:
345
- """Process a question and return an answer."""
346
- # Initialize the state with the question
347
- initial_state = {
348
- "messages": [HumanMessage(content=question)],
349
- }
350
-
351
- # Run the graph with timeout
352
- print(f"Starting graph execution with question: {question}")
353
- start_time = time.time()
354
-
355
- try:
356
- # Set a reasonable recursion limit
357
- result = self.graph.invoke(initial_state, config={"recursion_limit": 10})
358
-
359
- # Print the final state for debugging
360
- print(f"Final state keys: {result.keys()}")
361
- print(f"Final message count: {len(result['messages'])}")
362
-
363
- # Extract the final message
364
- final_message = result["messages"][-1]
365
- return final_message.content
366
-
367
- except Exception as e:
368
- elapsed_time = time.time() - start_time
369
- print(f"Error after {elapsed_time:.2f} seconds: {str(e)}")
370
-
371
- # If we've been running too long, return a timeout message
372
- if elapsed_time > self.max_execution_time:
373
- return f"""I wasn't able to complete the full analysis within the time limit, but here's what I found:
374
-
375
- The population of New York City is approximately 8.8 million (as of the 2020 Census).
376
-
377
- For a population doubling at 2% annual growth rate, it would take about 35 years. This can be calculated using the Rule of 70, which states that dividing 70 by the growth rate gives the approximate doubling time:
378
-
379
- 70 ÷ 2 = 35 years
380
-
381
- You can verify this with a Python calculation:
382
- ```python
383
- years = 0
384
- population = 1
385
- while population < 2:
386
- population *= 1.02 # 2% growth
387
- years += 1
388
- print(years) # Result: 35
389
- ```"""
390
-
391
- # Otherwise return the error
392
- return f"I encountered an error while processing your question: {str(e)}"
393
-
394
- # Example usage:
395
- if __name__ == "__main__":
396
- agent = TurboNerd(max_execution_time=30)
397
- response = agent("What is the population of New York City? Then write a Python program to calculate how many years it would take for the population to double at a 2% annual growth rate.")
398
- print("\nFinal Response:")
399
- print(response)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -4,13 +4,25 @@ import requests
4
  import inspect
5
  import pandas as pd
6
  import base64
7
- from agent import TurboNerd
8
  from rate_limiter import QueryRateLimiter
9
  from flask import request
 
 
 
 
 
 
 
 
 
 
10
 
11
  # --- Constants ---
12
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
13
  ALLOWED_FILE_EXTENSIONS = [".mp3", ".xlsx", ".py", ".png", ".jpg", ".jpeg", ".gif", ".txt", ".md", ".json", ".csv", ".yml", ".yaml", ".html", ".css", ".js"]
 
 
14
 
15
  # Initialize rate limiter (5 queries per hour)
16
  query_limiter = QueryRateLimiter(max_queries_per_hour=5)
@@ -18,17 +30,10 @@ query_limiter = QueryRateLimiter(max_queries_per_hour=5)
18
  # Dictionary to store session-specific conversation histories
19
  session_histories = {}
20
 
21
- # --- Basic Agent Definition ---
22
- class BasicAgent:
23
- def __init__(self):
24
- print("BasicAgent initialized.")
25
- self.agent = TurboNerd()
26
-
27
- def __call__(self, question: str) -> str:
28
- print(f"Agent received question (first 50 chars): {question[:50]}...")
29
- answer = self.agent(question)
30
- print(f"Agent returning answer: {answer[:50]}...")
31
- return answer
32
 
33
  # --- Chat Interface Functions ---
34
  def format_history_for_agent(history: list) -> str:
@@ -47,11 +52,90 @@ def format_history_for_agent(history: list) -> str:
47
 
48
  return "\n".join(formatted_history)
49
 
50
- def chat_with_agent(question: str, file_uploads, history: list) -> tuple:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  """
52
- Handle chat interaction with TurboNerd agent, now with file upload support.
53
  """
54
- if not question.strip() and not file_uploads:
 
 
 
55
  return history, ""
56
 
57
  try:
@@ -62,7 +146,6 @@ def chat_with_agent(question: str, file_uploads, history: list) -> tuple:
62
  # Initialize or get session history
63
  if session_id not in session_histories:
64
  session_histories[session_id] = []
65
- # If we have existing history, add it to the session history
66
  if history:
67
  session_histories[session_id].extend(history)
68
 
@@ -70,8 +153,30 @@ def chat_with_agent(question: str, file_uploads, history: list) -> tuple:
70
  history.append({"role": "user", "content": question})
71
  session_histories[session_id].append({"role": "user", "content": question})
72
 
73
- # Initialize agent
74
- agent = TurboNerd()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
  # Process uploaded files if any
77
  attachments = {}
@@ -82,16 +187,26 @@ def chat_with_agent(question: str, file_uploads, history: list) -> tuple:
82
  if file is not None:
83
  file_path = file.name
84
  file_name = os.path.basename(file_path)
85
- file_ext = os.path.splitext(file_name)[1].lower()
86
 
87
- # Check if file extension is allowed
88
- if file_ext in ALLOWED_FILE_EXTENSIONS:
89
- # Read file content and encode as base64
 
 
 
 
 
 
 
 
 
 
 
90
  with open(file_path, "rb") as f:
91
  file_content = f.read()
92
- file_content_b64 = base64.b64encode(file_content).decode("utf-8")
93
  attachments[file_name] = file_content_b64
94
- file_info += f"\nUploaded file: {file_path}"
95
 
96
  if file_info:
97
  if question.strip():
@@ -101,7 +216,6 @@ def chat_with_agent(question: str, file_uploads, history: list) -> tuple:
101
 
102
  # Format the session-specific conversation history
103
  conversation_history = format_history_for_agent(session_histories[session_id])
104
- print(f"Current conversation history:\n{conversation_history}") # Debug print
105
 
106
  # Prepare the full context for the agent
107
  full_context = f"Question: {question}\n\nConversation History:\n{conversation_history}"
@@ -115,25 +229,23 @@ def chat_with_agent(question: str, file_uploads, history: list) -> tuple:
115
  # Format the response to show thought process
116
  formatted_response = ""
117
  if "Thought:" in response:
118
- # Split the response into sections
119
  sections = response.split("\n\n")
120
  for section in sections:
121
  if section.startswith("Thought:"):
122
- formatted_response += f"🤔 {section[7:].strip()}\n\n"
123
  elif section.startswith("Action:"):
124
- # Extract the tool being used
125
  if "action" in section and "action_input" in section:
126
  try:
127
  import json
128
  action_json = json.loads(section.split("```json")[1].split("```")[0].strip())
129
  tool_name = action_json.get("action", "").replace("_", " ").title()
130
- formatted_response += f"🛠️ Using {tool_name}...\n\n"
131
  except:
132
- formatted_response += f"🛠️ {section[7:].strip()}\n\n"
133
  elif section.startswith("Observation:"):
134
- formatted_response += f"📝 {section[11:].strip()}\n\n"
135
  elif section.startswith("Final Answer:"):
136
- formatted_response += f"{section[12:].strip()}\n\n"
137
  else:
138
  formatted_response += f"{section}\n\n"
139
  else:
@@ -144,23 +256,15 @@ def chat_with_agent(question: str, file_uploads, history: list) -> tuple:
144
  session_histories[session_id].append({"role": "assistant", "content": formatted_response})
145
 
146
  return history, ""
147
- except RecursionError as e:
148
- error_message = (
149
- "I apologize, but I've reached my thinking limit while trying to answer your question. "
150
- "This usually happens when the question requires too many steps to solve and therefore too much money"
151
- "Could you please try breaking down your question into smaller, more specific parts? "
152
- "For example, instead of asking about multiple things at once, try asking about one aspect at a time."
153
- )
154
- history.append({"role": "assistant", "content": error_message})
155
- if session_id in session_histories:
156
- session_histories[session_id].append({"role": "assistant", "content": error_message})
157
- return history, ""
158
  except Exception as e:
159
  error_str = str(e).lower()
160
- if "credit" in error_str or "quota" in error_str or "limit" in error_str or "exceeded" in error_str or "OPENAI_API_KEY" in error_str or "TAVILY_API_KEY" in error_str:
161
- error_message = (
162
- "It seems I've run out of API credits. "
163
- "Please try again later or tomorrow when the credits reset. ")
 
 
164
  else:
165
  error_message = f"Error: {str(e)}"
166
 
@@ -176,7 +280,7 @@ def clear_chat():
176
  # --- Evaluation Functions ---
177
  def run_and_submit_all(profile: gr.OAuthProfile | None):
178
  """
179
- Fetches all questions, runs the BasicAgent on them, submits all answers,
180
  and displays the results.
181
  """
182
  # --- Determine HF Space Runtime URL and Repo URL ---
@@ -193,9 +297,14 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
193
  questions_url = f"{api_url}/questions"
194
  submit_url = f"{api_url}/submit"
195
 
196
- # 1. Instantiate Agent ( modify this part to create your agent)
197
  try:
198
- agent = BasicAgent()
 
 
 
 
 
199
  except Exception as e:
200
  print(f"Error instantiating agent: {e}")
201
  return f"Error initializing agent: {e}", None
@@ -312,133 +421,143 @@ def run_and_submit_all(profile: gr.OAuthProfile | None):
312
  return status_message, results_df
313
 
314
  # --- Build Gradio Interface using Blocks with Tabs ---
315
- with gr.Blocks(title="TurboNerd Agent🤓") as demo:
316
- gr.Markdown("# TurboNerd 🤓- The Deep Research Agent \n ### Made by Vividh Mahajan - @Lasdw on HuggingFace")
317
-
318
- with gr.Tabs():
319
- # Tab 1: Chat Interface
320
- with gr.TabItem("🤓", id="chat"):
321
  gr.Markdown("""
322
- ## Chat with TurboNerd 🤓
323
- Ask any question and get an answer from TurboNerd. The agent can search the web, Wikipedia, analyze images, process audio, and more!
324
-
325
- Note: For best results, ask specific questions that have factual answers andd have the question be properly formatted as given below. Some websites, like reddit, may be inaccessible due to web scraping restrictions.
326
- ### Example Questions:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
- **Research & Analysis:**
329
- - "Find the first name of the only Malko Competition recipient from the 20th Century (after 1977) whose nationality on record is a country that no longer exists. Cross-reference this information with their Wikipedia page and any recent news articles."
330
- - "Analyze this image of a mathematical equation, and find an academic papers that use this equation."
331
 
332
- **Multi-Modal Analysis:**
333
- - "I have an interview recording and a transcript. Compare the audio transcription with the provided transcript, identify any discrepancies."
334
- - "This image shows a historical document. Find me the historical events from that era."
335
 
336
- **Code & Data Processing:**
337
- - "I have a Python script and an Excel file with data. Analyze the code's functionality and suggest improvements based on the data patterns."
338
- - "This code contains a bug. Debug it ."
339
 
340
- The agent can handle multiple file uploads and combine information from various sources to provide comprehensive answers. Try asking complex questions that require multiple tools working together!
341
- """)
342
-
343
- gr.Markdown("""
344
- ### Disclaimer
345
- This tool is designed for educational and research purposes only. It is not intended for malicious use.
346
- """)
347
-
 
 
348
  with gr.Row():
349
- with gr.Column(scale=4):
350
- chatbot = gr.Chatbot(
351
- height=300,
352
- type="messages" # Use the new messages format
353
- )
354
- # remaining_queries = gr.Textbox(
355
- # label="Remaining Queries",
356
- # value="Remaining queries this hour: 5/5",
357
- # interactive=False
358
- # )
359
  with gr.Row():
360
- question_input = gr.Textbox(
361
- label="Ask a question",
362
- placeholder="Type your question here...",
363
- lines=9,
364
- max_lines=9,
365
- container=True,
366
- scale=3
367
- )
368
- file_upload = gr.File(
369
- label="Upload Files",
370
- file_types=ALLOWED_FILE_EXTENSIONS,
371
- file_count="multiple",
372
- scale=1
373
- )
374
- with gr.Row():
375
- submit_btn = gr.Button("Send", variant="primary")
376
-
377
- # Chat interface event handlers
378
- submit_btn.click(
379
- fn=chat_with_agent,
380
- inputs=[question_input, file_upload, chatbot],
381
- outputs=[chatbot, question_input]
382
- )
383
-
384
- question_input.submit(
385
- fn=chat_with_agent,
386
- inputs=[question_input, file_upload, chatbot],
387
- outputs=[chatbot, question_input]
388
- )
389
 
390
- # Tab 2: Evaluation Interface
391
- with gr.TabItem(" ", id="evaluation"):
392
- gr.Markdown("""
393
- # You found a secret page 🤫
394
- ## Agent Evaluation Runner for the AI Agents course on HF :P
395
- ## See my ranking (@Lasdw) on the course [here](https://huggingface.co/spaces/agents-course/Students_leaderboard)
396
-
397
- ## Below is the original README.md for the space
398
-
399
- **Instructions:**
400
-
401
- 1. Log in to your Hugging Face account using the button below.
402
- 2. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
403
-
404
- ---
405
- **Disclaimers:**
406
- Once clicking on the "submit" button, it can take quite some time (this is the time for the agent to go through all the questions).
407
- This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution.
408
- """)
409
-
410
- gr.LoginButton()
411
-
412
- run_button = gr.Button("Run Evaluation & Submit All Answers", variant="primary")
413
-
414
- status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
415
- results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
416
-
417
- run_button.click(
418
- fn=run_and_submit_all,
419
- outputs=[status_output, results_table]
420
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
 
422
  if __name__ == "__main__":
423
  print("\n" + "-"*30 + " App Starting " + "-"*30)
424
- # Check for SPACE_HOST and SPACE_ID at startup for information
425
  space_host_startup = os.getenv("SPACE_HOST")
426
- space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
427
 
428
  if space_host_startup:
429
- print(f"SPACE_HOST found: {space_host_startup}")
430
  print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
431
  else:
432
- print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
433
 
434
- if space_id_startup: # Print repo URLs if SPACE_ID is found
435
- print(f"SPACE_ID found: {space_id_startup}")
436
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
437
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
438
  else:
439
- print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
440
 
441
  print("-"*(60 + len(" App Starting ")) + "\n")
442
 
443
- print("Launching Gradio Interface for TurboNerd Agent...")
444
  demo.launch(debug=True, share=False, show_api=False, favicon_path="static/favicon.ico", enable_monitoring=True)
 
4
  import inspect
5
  import pandas as pd
6
  import base64
7
+ from agent import ScholarAI
8
  from rate_limiter import QueryRateLimiter
9
  from flask import request
10
+ import PyPDF2
11
+ import fitz # PyMuPDF
12
+ import time
13
+ from typing import List, Tuple, Optional
14
+ from langchain_openai import ChatOpenAI
15
+ from langchain_core.messages import HumanMessage, AIMessage
16
+
17
+ # Load custom CSS
18
+ with open("static/custom.css", "r", encoding="utf-8") as f:
19
+ custom_css = f.read()
20
 
21
  # --- Constants ---
22
  DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
23
  ALLOWED_FILE_EXTENSIONS = [".mp3", ".xlsx", ".py", ".png", ".jpg", ".jpeg", ".gif", ".txt", ".md", ".json", ".csv", ".yml", ".yaml", ".html", ".css", ".js"]
24
+ MAX_FILE_SIZE_MB = 10
25
+ CHUNK_SIZE = 1000 # characters per chunk for text processing
26
 
27
  # Initialize rate limiter (5 queries per hour)
28
  query_limiter = QueryRateLimiter(max_queries_per_hour=5)
 
30
  # Dictionary to store session-specific conversation histories
31
  session_histories = {}
32
 
33
+ # --- Model Settings ---
34
+ DEFAULT_TEMPERATURE = 0.1
35
+ DEFAULT_MAX_TOKENS = 2000
36
+ DEFAULT_MODEL = "gpt-4o-mini"
 
 
 
 
 
 
 
37
 
38
  # --- Chat Interface Functions ---
39
  def format_history_for_agent(history: list) -> str:
 
52
 
53
  return "\n".join(formatted_history)
54
 
55
+ def validate_inputs(question: str, file_uploads: List[gr.File]) -> Tuple[bool, str]:
56
+ """Validate user inputs before processing."""
57
+ if not question.strip() and (not file_uploads or len(file_uploads) == 0):
58
+ return False, "Please enter a question or upload a file."
59
+
60
+ if len(question) > 2000:
61
+ return False, "Question is too long. Please keep it under 2000 characters."
62
+
63
+ if file_uploads:
64
+ for file in file_uploads:
65
+ if file is None:
66
+ continue
67
+
68
+ file_path = file.name
69
+ if not os.path.exists(file_path):
70
+ return False, f"File {os.path.basename(file_path)} not found."
71
+
72
+ file_size = os.path.getsize(file_path) / (1024 * 1024) # Convert to MB
73
+
74
+ if file_size > MAX_FILE_SIZE_MB:
75
+ return False, f"File {os.path.basename(file_path)} is too large. Maximum size is {MAX_FILE_SIZE_MB}MB."
76
+
77
+ file_ext = os.path.splitext(file_path)[1].lower()
78
+ if file_ext not in ALLOWED_FILE_EXTENSIONS:
79
+ return False, f"File {os.path.basename(file_path)} has an unsupported format. Allowed formats: {', '.join(ALLOWED_FILE_EXTENSIONS)}"
80
+
81
+ return True, ""
82
+
83
+ def process_document(file_path: str, progress=gr.Progress()) -> List[str]:
84
+ """Process document and return chunks with progress bar."""
85
+ file_ext = os.path.splitext(file_path)[1].lower()
86
+ chunks = []
87
+
88
+ try:
89
+ if file_ext == '.pdf':
90
+ # Process PDF
91
+ doc = fitz.open(file_path)
92
+ total_pages = len(doc)
93
+
94
+ for page_num in progress.tqdm(range(total_pages), desc="Processing PDF pages"):
95
+ page = doc[page_num]
96
+ text = page.get_text()
97
+ # Split text into chunks
98
+ for i in range(0, len(text), CHUNK_SIZE):
99
+ chunk = text[i:i + CHUNK_SIZE]
100
+ if chunk.strip():
101
+ chunks.append(f"[Page {page_num + 1}] {chunk}")
102
+ time.sleep(0.1) # Small delay to show progress
103
+
104
+ elif file_ext in ['.txt', '.md', '.json', '.csv', '.yml', '.yaml', '.html', '.css', '.js', '.py']:
105
+ # Process text files
106
+ with open(file_path, 'r', encoding='utf-8') as f:
107
+ text = f.read()
108
+ total_chunks = len(text) // CHUNK_SIZE + (1 if len(text) % CHUNK_SIZE else 0)
109
+
110
+ for i in progress.tqdm(range(0, len(text), CHUNK_SIZE), desc="Processing text chunks"):
111
+ chunk = text[i:i + CHUNK_SIZE]
112
+ if chunk.strip():
113
+ chunks.append(chunk)
114
+ time.sleep(0.1) # Small delay to show progress
115
+
116
+ elif file_ext in ['.xlsx']:
117
+ # Process Excel files
118
+ df = pd.read_excel(file_path)
119
+ total_rows = len(df)
120
+
121
+ for i in progress.tqdm(range(0, total_rows, CHUNK_SIZE), desc="Processing Excel rows"):
122
+ chunk_df = df.iloc[i:i + CHUNK_SIZE]
123
+ chunks.append(chunk_df.to_string())
124
+ time.sleep(0.1) # Small delay to show progress
125
+
126
+ return chunks
127
+
128
+ except Exception as e:
129
+ return [f"Error processing file: {str(e)}"]
130
+
131
+ def chat_with_agent(question: str, file_uploads, history: list, temperature: float, max_tokens: int, model: str, progress=gr.Progress()) -> tuple:
132
  """
133
+ Handle chat interaction with ScholarAI agent, now with file upload support and input validation.
134
  """
135
+ # Validate inputs
136
+ is_valid, error_message = validate_inputs(question, file_uploads)
137
+ if not is_valid:
138
+ history.append({"role": "assistant", "content": f"❌ {error_message}"})
139
  return history, ""
140
 
141
  try:
 
146
  # Initialize or get session history
147
  if session_id not in session_histories:
148
  session_histories[session_id] = []
 
149
  if history:
150
  session_histories[session_id].extend(history)
151
 
 
153
  history.append({"role": "user", "content": question})
154
  session_histories[session_id].append({"role": "user", "content": question})
155
 
156
+ try:
157
+ # Initialize agent with current settings
158
+ agent = ScholarAI(
159
+ max_iterations=35,
160
+ temperature=temperature,
161
+ max_tokens=max_tokens,
162
+ model=model
163
+ )
164
+ print("Agent initialized successfully with Temperature: ", temperature, "Max Tokens: ", max_tokens, "Model: ", model)
165
+ except ValueError as e:
166
+ error_message = str(e)
167
+ if "API key not found" in error_message:
168
+ error_message = "OpenAI API key not found. Please set the OPENAI_API_KEY environment variable."
169
+ elif "Invalid OpenAI API key" in error_message:
170
+ error_message = "Invalid OpenAI API key. Please check your API key and try again."
171
+ elif "rate limit" in error_message.lower() or "quota" in error_message.lower():
172
+ error_message = "OpenAI API rate limit exceeded or quota reached. Please try again later."
173
+ else:
174
+ error_message = f"Error initializing AI agent: {error_message}"
175
+
176
+ history.append({"role": "assistant", "content": error_message})
177
+ if session_id in session_histories:
178
+ session_histories[session_id].append({"role": "assistant", "content": error_message})
179
+ return history, ""
180
 
181
  # Process uploaded files if any
182
  attachments = {}
 
187
  if file is not None:
188
  file_path = file.name
189
  file_name = os.path.basename(file_path)
 
190
 
191
+ # Process document and get chunks
192
+ chunks = process_document(file_path, progress)
193
+
194
+ if len(chunks) > 1:
195
+ file_info += f"\nProcessing {file_name} in {len(chunks)} chunks..."
196
+
197
+ # Process each chunk
198
+ for i, chunk in enumerate(chunks, 1):
199
+ chunk_name = f"{file_name}_chunk_{i}"
200
+ chunk_content = base64.b64encode(chunk.encode('utf-8')).decode('utf-8')
201
+ attachments[chunk_name] = chunk_content
202
+ file_info += f"\nProcessed chunk {i}/{len(chunks)}"
203
+ else:
204
+ # Single chunk or error
205
  with open(file_path, "rb") as f:
206
  file_content = f.read()
207
+ file_content_b64 = base64.b64encode(file_content).decode('utf-8')
208
  attachments[file_name] = file_content_b64
209
+ file_info += f"\nUploaded file: {file_name}"
210
 
211
  if file_info:
212
  if question.strip():
 
216
 
217
  # Format the session-specific conversation history
218
  conversation_history = format_history_for_agent(session_histories[session_id])
 
219
 
220
  # Prepare the full context for the agent
221
  full_context = f"Question: {question}\n\nConversation History:\n{conversation_history}"
 
229
  # Format the response to show thought process
230
  formatted_response = ""
231
  if "Thought:" in response:
 
232
  sections = response.split("\n\n")
233
  for section in sections:
234
  if section.startswith("Thought:"):
235
+ formatted_response += f"{section[7:].strip()}\n\n"
236
  elif section.startswith("Action:"):
 
237
  if "action" in section and "action_input" in section:
238
  try:
239
  import json
240
  action_json = json.loads(section.split("```json")[1].split("```")[0].strip())
241
  tool_name = action_json.get("action", "").replace("_", " ").title()
242
+ formatted_response += f"Using {tool_name}...\n\n"
243
  except:
244
+ formatted_response += f"{section[7:].strip()}\n\n"
245
  elif section.startswith("Observation:"):
246
+ formatted_response += f"{section[11:].strip()}\n\n"
247
  elif section.startswith("Final Answer:"):
248
+ formatted_response += f"{section[12:].strip()}\n\n"
249
  else:
250
  formatted_response += f"{section}\n\n"
251
  else:
 
256
  session_histories[session_id].append({"role": "assistant", "content": formatted_response})
257
 
258
  return history, ""
259
+
 
 
 
 
 
 
 
 
 
 
260
  except Exception as e:
261
  error_str = str(e).lower()
262
+ if "credit" in error_str or "quota" in error_str or "limit" in error_str or "exceeded" in error_str:
263
+ error_message = "It seems I've run out of API credits. Please try again later or tomorrow when the credits reset."
264
+ elif "invalid_api_key" in error_str or "incorrect_api_key" in error_str:
265
+ error_message = "Invalid OpenAI API key. Please check your API key and try again."
266
+ elif "api_key" in error_str:
267
+ error_message = "OpenAI API key not found. Please set the OPENAI_API_KEY environment variable."
268
  else:
269
  error_message = f"Error: {str(e)}"
270
 
 
280
  # --- Evaluation Functions ---
281
  def run_and_submit_all(profile: gr.OAuthProfile | None):
282
  """
283
+ Fetches all questions, runs the ScholarAI on them, submits all answers,
284
  and displays the results.
285
  """
286
  # --- Determine HF Space Runtime URL and Repo URL ---
 
297
  questions_url = f"{api_url}/questions"
298
  submit_url = f"{api_url}/submit"
299
 
300
+ # 1. Instantiate Agent
301
  try:
302
+ agent = ScholarAI(
303
+ max_iterations=35,
304
+ temperature=DEFAULT_TEMPERATURE,
305
+ max_tokens=DEFAULT_MAX_TOKENS,
306
+ model=DEFAULT_MODEL
307
+ )
308
  except Exception as e:
309
  print(f"Error instantiating agent: {e}")
310
  return f"Error initializing agent: {e}", None
 
421
  return status_message, results_df
422
 
423
  # --- Build Gradio Interface using Blocks with Tabs ---
424
+ with gr.Blocks(title="ScholarAI Agent", css=custom_css) as demo:
425
+ with gr.Row(elem_classes="header-bar"):
426
+ with gr.Column(scale=3):
427
+ gr.Markdown("# <span style='font-size: 1.8em'>ScholarAI</span>", elem_classes="title")
 
 
428
  gr.Markdown("""
429
+ <div class="badges">
430
+ <img src="https://img.shields.io/badge/build-passing-brightgreen" alt="Build Status">
431
+ <img src="https://img.shields.io/badge/License-MIT-yellow" alt="License">
432
+ <img src="https://img.shields.io/badge/version-1.0.0-blue" alt="Version">
433
+ <img src="https://img.shields.io/badge/python-3.11-blue" alt="Python">
434
+ <img src="https://img.shields.io/badge/gradio-5.29.1-orange" alt="Gradio">
435
+ </div>
436
+ """, elem_classes="badges-container")
437
+ with gr.Column(scale=1):
438
+ gr.Markdown("<span style='font-size: 0.9em'>by [Vividh Mahajan](https://huggingface.co/Lasdw)</span>", elem_classes="author")
439
+
440
+ gr.Markdown("""
441
+ ## ScholarAI helps you find answers by searching the web, analyzing images, processing audio, and more.
442
+
443
+ ### Tip: Ask specific, factual questions for best results. Some websites may be restricted.
444
+ """)
445
+
446
+ with gr.Accordion("Example Questions", open=False, elem_classes="example-questions"):
447
+ gr.Markdown("""
448
+ ### Example Questions:
449
 
450
+ **Research & Analysis:**
451
+ - "Find the first name of the only Malko Competition recipient from the 20th Century (after 1977) whose nationality on record is a country that no longer exists. Tell me thier current age and where they are from."
452
+ - "Analyze this image of a mathematical equation, and find an academic papers that use this equation."
453
 
454
+ **Multi-Modal Analysis:**
455
+ - "I have an interview recording and a transcript. Compare the audio transcription with the provided transcript, identify any discrepancies."
456
+ - "This image shows a historical document. Find me the historical events from that era."
457
 
458
+ **Code & Data Processing:**
459
+ - "I have a Python script and an Excel file with data. Analyze the code's functionality and suggest improvements based on the data patterns."
460
+ - "This code contains a bug. Debug it."
461
 
462
+ The agent can handle multiple file uploads and combine information from various sources to provide comprehensive answers. Try asking complex questions that require multiple tools working together!
463
+ """)
464
+
465
+ with gr.Row():
466
+ # Left panel - Chat interface
467
+ with gr.Column(scale=2):
468
+ chatbot = gr.Chatbot(
469
+ height=250,
470
+ type="messages"
471
+ )
472
  with gr.Row():
473
+ question_input = gr.Textbox(
474
+ label="Ask a question",
475
+ placeholder="e.g. Analyze this interview transcript and find discrepancies",
476
+ lines=5,
477
+ max_lines=5,
478
+ container=True,
479
+ scale=2,
480
+ min_width=500
481
+ )
482
+ with gr.Column(scale=1):
483
  with gr.Row():
484
+ with gr.Column(scale=2):
485
+ file_upload = gr.File(
486
+ label="Upload Files (.png, .txt, .mp3, .xlsx, .py)",
487
+ file_types=ALLOWED_FILE_EXTENSIONS,
488
+ file_count="multiple",
489
+ height=175,
490
+ min_width=200
491
+ )
492
+ with gr.Row():
493
+ submit_btn = gr.Button("Start Research", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
 
495
+ # Right panel - Controls
496
+ with gr.Column(scale=1):
497
+ gr.Markdown("# Model Settings")
498
+ with gr.Group():
499
+ temperature = gr.Slider(
500
+ minimum=0.0,
501
+ maximum=1.0,
502
+ value=DEFAULT_TEMPERATURE,
503
+ step=0.1,
504
+ label="Temperature",
505
+ info="Higher values make the output more random, lower values make it more deterministic"
506
+ )
507
+ max_tokens = gr.Slider(
508
+ minimum=100,
509
+ maximum=4000,
510
+ value=DEFAULT_MAX_TOKENS,
511
+ step=100,
512
+ label="Max Tokens",
513
+ info="Maximum length of the response"
514
+ )
515
+ model = gr.Dropdown(
516
+ choices=["gpt-4o-mini", "gpt-3.5-turbo"],
517
+ value=DEFAULT_MODEL,
518
+ label="Model",
519
+ info="The language model to use"
520
+ )
521
+
522
+ # Footer with disclaimer
523
+ gr.Markdown("""
524
+ <div class="footer">
525
+ This tool is designed for educational and research purposes only. It is not intended for malicious use.
526
+ </div>
527
+ """)
528
+
529
+ # Chat interface event handlers
530
+ submit_btn.click(
531
+ fn=chat_with_agent,
532
+ inputs=[question_input, file_upload, chatbot, temperature, max_tokens, model],
533
+ outputs=[chatbot, question_input]
534
+ )
535
+
536
+ question_input.submit(
537
+ fn=chat_with_agent,
538
+ inputs=[question_input, file_upload, chatbot, temperature, max_tokens, model],
539
+ outputs=[chatbot, question_input]
540
+ )
541
 
542
  if __name__ == "__main__":
543
  print("\n" + "-"*30 + " App Starting " + "-"*30)
 
544
  space_host_startup = os.getenv("SPACE_HOST")
545
+ space_id_startup = os.getenv("SPACE_ID")
546
 
547
  if space_host_startup:
548
+ print(f"SPACE_HOST found: {space_host_startup}")
549
  print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
550
  else:
551
+ print("SPACE_HOST environment variable not found (running locally?).")
552
 
553
+ if space_id_startup:
554
+ print(f"SPACE_ID found: {space_id_startup}")
555
  print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
556
  print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
557
  else:
558
+ print("SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
559
 
560
  print("-"*(60 + len(" App Starting ")) + "\n")
561
 
562
+ print("Launching Gradio Interface for ScholarAI Agent...")
563
  demo.launch(debug=True, share=False, show_api=False, favicon_path="static/favicon.ico", enable_monitoring=True)
static/custom.css ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary-purple: #8a3db8;
3
+ --dark-purple: #6a2c9e;
4
+ --light-purple: #b366ff;
5
+ --black: #1a1a1a;
6
+ --dark-gray: #2d2d2d;
7
+ --light-gray: #404040;
8
+ --white: #ffffff;
9
+ }
10
+
11
+ /* Header styling */
12
+ h1 {
13
+ font-size: 2.5em !important;
14
+ margin-bottom: 0.5em !important;
15
+ }
16
+
17
+ h1 span {
18
+ color: var(--light-purple) !important;
19
+ font-weight: bold !important;
20
+ }
21
+
22
+ /* Global styles */
23
+ .gradio-container {
24
+ background-color: var(--black) !important;
25
+ color: var(--white) !important;
26
+ padding-bottom: 60px !important;
27
+ }
28
+
29
+ /* Chat interface */
30
+ .chatbot {
31
+ background-color: var(--dark-gray) !important;
32
+ border-radius: 8px !important;
33
+ max-width: 520px !important; /* Limit width of the whole chat area */
34
+ margin-left: auto !important;
35
+ margin-right: auto !important;
36
+ }
37
+
38
+ /* Message bubbles */
39
+ .message, .user-message, .assistant-message {
40
+ border: none !important;
41
+ border-radius: 10px !important;
42
+ box-shadow: none !important;
43
+ background: var(--light-purple) !important;
44
+ color: var(--white) !important;
45
+ margin: 8px 0 !important;
46
+ padding: 12px 18px !important;
47
+ max-width: 85% !important; /* Limit width of each message bubble */
48
+ word-break: break-word !important;
49
+ }
50
+
51
+ .user-message {
52
+ background-color: var(--light-purple) !important;
53
+ color: var(--white) !important;
54
+ }
55
+
56
+ .assistant-message {
57
+ background-color: var(--light-gray) !important;
58
+ color: var(--white) !important;
59
+ }
60
+
61
+ /* Input area */
62
+ .textbox {
63
+ background-color: var(--dark-gray) !important;
64
+ color: var(--white) !important;
65
+ }
66
+
67
+ .textbox:focus {
68
+ border-color: var(--light-purple) !important;
69
+ box-shadow: 0 0 5px var(--light-purple) !important;
70
+ }
71
+
72
+ /* Buttons */
73
+ button {
74
+ background-color: var(--light-purple) !important;
75
+ color: var(--white) !important;
76
+ border: none !important;
77
+ border-radius: 4px !important;
78
+ transition: background-color 0.3s ease !important;
79
+ }
80
+
81
+ button:hover {
82
+ background-color: var(--primary-purple) !important;
83
+ }
84
+
85
+ /* Slider styling */
86
+ input[type="range"],
87
+ .gr-form,
88
+ .gr-box,
89
+ .gr-input {
90
+ -webkit-appearance: none !important;
91
+ background: var(--light-purple) !important;
92
+ height: 4px !important;
93
+ border-radius: 2px !important;
94
+ }
95
+
96
+ input[type="range"]::-webkit-slider-thumb,
97
+ .gr-form::-webkit-slider-thumb,
98
+ .gr-box::-webkit-slider-thumb,
99
+ .gr-input::-webkit-slider-thumb {
100
+ -webkit-appearance: none !important;
101
+ width: 16px !important;
102
+ height: 16px !important;
103
+ background: var(--light-purple) !important;
104
+ border: 2px solid var(--white) !important;
105
+ border-radius: 50% !important;
106
+ cursor: pointer !important;
107
+ }
108
+
109
+ input[type="range"]::-moz-range-thumb,
110
+ .gr-form::-moz-range-thumb,
111
+ .gr-box::-moz-range-thumb,
112
+ .gr-input::-moz-range-thumb {
113
+ width: 16px !important;
114
+ height: 16px !important;
115
+ background: var(--light-purple) !important;
116
+ border: 2px solid var(--white) !important;
117
+ border-radius: 50% !important;
118
+ cursor: pointer !important;
119
+ }
120
+
121
+ input[type="range"]::-webkit-slider-runnable-track,
122
+ .gr-form::-webkit-slider-runnable-track,
123
+ .gr-box::-webkit-slider-runnable-track,
124
+ .gr-input::-webkit-slider-runnable-track {
125
+ background: var(--light-purple) !important;
126
+ height: 4px !important;
127
+ border-radius: 2px !important;
128
+ }
129
+
130
+ input[type="range"]::-moz-range-track,
131
+ .gr-form::-moz-range-track,
132
+ .gr-box::-moz-range-track,
133
+ .gr-input::-moz-range-track {
134
+ background: var(--light-purple) !important;
135
+ height: 4px !important;
136
+ border-radius: 2px !important;
137
+ }
138
+
139
+ /* Dropdown styling */
140
+ select, .dropdown {
141
+ background-color: var(--dark-gray) !important;
142
+ color: var(--white) !important;
143
+ border: 1px solid var(--light-purple) !important;
144
+ border-radius: 4px !important;
145
+ }
146
+
147
+ select:hover, .dropdown:hover {
148
+ border-color: var(--light-purple) !important;
149
+ }
150
+
151
+ select:focus, .dropdown:focus {
152
+ border-color: var(--light-purple) !important;
153
+ box-shadow: 0 0 5px var(--light-purple) !important;
154
+ }
155
+
156
+ /* Markdown content */
157
+ .markdown {
158
+ color: var(--white) !important;
159
+ }
160
+
161
+ .markdown h1, .markdown h2, .markdown h3 {
162
+ color: var(--light-purple) !important;
163
+ }
164
+
165
+ /* File upload area */
166
+ .upload-area {
167
+ background-color: var(--dark-gray) !important;
168
+ border: 2px dashed var(--light-purple) !important;
169
+ border-radius: 8px !important;
170
+ }
171
+
172
+ .upload-area:hover {
173
+ border-color: var(--light-purple) !important;
174
+ }
175
+
176
+ /* Scrollbars */
177
+ ::-webkit-scrollbar {
178
+ width: 8px;
179
+ height: 8px;
180
+ }
181
+
182
+ ::-webkit-scrollbar-track {
183
+ background: var(--dark-gray);
184
+ }
185
+
186
+ ::-webkit-scrollbar-thumb {
187
+ background: var(--light-purple);
188
+ border-radius: 4px;
189
+ }
190
+
191
+ ::-webkit-scrollbar-thumb:hover {
192
+ background: var(--primary-purple);
193
+ }
194
+
195
+ /* Footer styling */
196
+ .footer {
197
+ position: fixed;
198
+ bottom: 0;
199
+ left: 0;
200
+ right: 0;
201
+ background-color: var(--black) !important;
202
+ color: var(--white) !important;
203
+ padding: 10px 20px !important;
204
+ text-align: center !important;
205
+ border-top: 1px solid var(--light-purple) !important;
206
+ font-size: 1em !important;
207
+ z-index: 1000 !important;
208
+ }
209
+
210
+ /* Accordion styling */
211
+ .accordion {
212
+ background-color: var(--dark-gray) !important;
213
+ border: 1px solid var(--light-purple) !important;
214
+ border-radius: 8px !important;
215
+ margin: 10px 0 !important;
216
+ }
217
+
218
+ .accordion-header {
219
+ background-color: var(--dark-gray) !important;
220
+ color: var(--white) !important;
221
+ padding: 12px !important;
222
+ cursor: pointer !important;
223
+ transition: background-color 0.3s ease !important;
224
+ }
225
+
226
+ .accordion-header:hover {
227
+ background-color: var(--light-gray) !important;
228
+ }
229
+
230
+ .accordion-content {
231
+ background-color: var(--dark-gray) !important;
232
+ color: var(--white) !important;
233
+ padding: 15px !important;
234
+ border-top: 1px solid var(--light-purple) !important;
235
+ }
236
+
237
+ /* Accordion icon */
238
+ .accordion-header::after {
239
+ color: var(--light-purple) !important;
240
+ }
241
+
242
+ /* Model Settings styling */
243
+ .slider,
244
+ .gr-slider,
245
+ .gr-form {
246
+ background-color: var(--black) !important;
247
+ }
248
+
249
+ .slider .thumb,
250
+ .gr-slider .thumb,
251
+ .gr-form .thumb {
252
+ background-color: var(--light-purple) !important;
253
+ border: 2px solid var(--white) !important;
254
+ }
255
+
256
+ .slider .track,
257
+ .gr-slider .track,
258
+ .gr-form .track {
259
+ background-color: var(--dark-purple) !important;
260
+ }
261
+
262
+ .slider .track-fill,
263
+ .gr-slider .track-fill,
264
+ .gr-form .track-fill {
265
+ background-color: var(--light-purple) !important;
266
+ }
267
+
268
+ .slider .label,
269
+ .gr-slider .label,
270
+ .gr-form .label {
271
+ color: var(--white) !important;
272
+ }
273
+
274
+ .slider .info,
275
+ .gr-slider .info,
276
+ .gr-form .info,
277
+ .gr-box .info,
278
+ .gr-input .info {
279
+ color: var(--black) !important;
280
+ opacity: 1 !important;
281
+ }
282
+
283
+ /* Additional Gradio-specific overrides */
284
+ .gr-box[data-testid="slider"] .track {
285
+ background-color: var(--dark-purple) !important;
286
+ }
287
+
288
+ .gr-box[data-testid="slider"] .info {
289
+ color: var(--black) !important;
290
+ opacity: 1 !important;
291
+ }
292
+
293
+ .gr-box[data-testid="slider"] .track-fill {
294
+ background-color: var(--light-purple) !important;
295
+ }
296
+
297
+ /* Box below model settings */
298
+ .gr-box,
299
+ .gr-box[data-testid="box"],
300
+ .gr-box[data-testid="group"] {
301
+ background-color: var(--light-purple) !important;
302
+ border-radius: 8px !important;
303
+ padding: 15px !important;
304
+ margin-top: 10px !important;
305
+ }
306
+
307
+ .gr-box *,
308
+ .gr-box[data-testid="box"] *,
309
+ .gr-box[data-testid="group"] * {
310
+ color: var(--white) !important;
311
+ }
312
+
313
+ .gr-box .markdown,
314
+ .gr-box[data-testid="box"] .markdown,
315
+ .gr-box[data-testid="group"] .markdown {
316
+ color: var(--white) !important;
317
+ background-color: transparent !important;
318
+ }
319
+
320
+ .gr-box .markdown p,
321
+ .gr-box[data-testid="box"] .markdown p,
322
+ .gr-box[data-testid="group"] .markdown p {
323
+ color: var(--white) !important;
324
+ margin: 0 !important;
325
+ background-color: transparent !important;
326
+ }
327
+
328
+ /* Override any conflicting styles */
329
+ .gr-box[data-testid="box"] .column,
330
+ .gr-box[data-testid="group"] .column {
331
+ background-color: transparent !important;
332
+ }
333
+
334
+ .gr-box[data-testid="box"] .column .markdown,
335
+ .gr-box[data-testid="group"] .column .markdown {
336
+ background-color: transparent !important;
337
+ }
338
+
339
+ /* Info text styling */
340
+ .info {
341
+ color: var(--light-purple) !important;
342
+ font-size: 0.9em !important;
343
+ font-style: italic !important;
344
+ }
345
+
346
+ /* Side panel styling */
347
+ .column {
348
+ background-color: var(--black) !important;
349
+ }
350
+
351
+ .column .markdown {
352
+ background-color: var(--light-purple) !important;
353
+ border: none !important;
354
+ }
355
+
356
+ .column .markdown p {
357
+ background-color: var(--light-purple) !important;
358
+ border: none !important;
359
+ }
360
+
361
+ .header-bar {
362
+ padding: 5px 10px !important;
363
+ margin-bottom: 10px !important;
364
+ background: transparent !important;
365
+ }
366
+
367
+ .title {
368
+ text-align: left !important;
369
+ margin: 0 !important;
370
+ padding: 0 !important;
371
+ }
372
+
373
+ .author {
374
+ text-align: right !important;
375
+ margin: 0 !important;
376
+ padding: 0 !important;
377
+ line-height: 2.5em !important;
378
+ }
379
+
380
+ /* Example Questions styling */
381
+ .example-questions {
382
+ margin: 20px 0 !important;
383
+ border: 2px solid var(--light-purple) !important;
384
+ border-radius: 8px !important;
385
+ background: linear-gradient(45deg, var(--primary-purple), var(--light-purple)) !important;
386
+ transition: all 0.3s ease !important;
387
+ }
388
+
389
+ .example-questions:hover {
390
+ transform: translateY(-2px) !important;
391
+ box-shadow: 0 4px 15px rgba(156, 77, 204, 0.3) !important;
392
+ }
393
+
394
+ .example-questions .accordion-header {
395
+ background: transparent !important;
396
+ color: var(--white) !important;
397
+ font-size: 1.2em !important;
398
+ font-weight: bold !important;
399
+ padding: 15px 20px !important;
400
+ cursor: pointer !important;
401
+ display: flex !important;
402
+ align-items: center !important;
403
+ gap: 10px !important;
404
+ }
405
+
406
+ .example-questions .accordion-header::after {
407
+ content: "Click to expand" !important;
408
+ font-size: 0.8em !important;
409
+ opacity: 0.8 !important;
410
+ margin-left: auto !important;
411
+ }
412
+
413
+ .example-questions .accordion-content {
414
+ background-color: var(--dark-gray) !important;
415
+ padding: 20px !important;
416
+ border-top: 1px solid var(--light-purple) !important;
417
+ }
418
+
419
+ .example-questions .markdown {
420
+ color: var(--white) !important;
421
+ }
422
+
423
+ .example-questions .markdown h3 {
424
+ color: var(--light-purple) !important;
425
+ margin-bottom: 15px !important;
426
+ }
427
+
428
+ .example-questions .markdown strong {
429
+ color: var(--light-purple) !important;
430
+ }
431
+
432
+ /* Badges styling */
433
+ .badges-container {
434
+ margin-top: 5px !important;
435
+ }
436
+
437
+ .badges {
438
+ display: flex !important;
439
+ gap: 8px !important;
440
+ flex-wrap: wrap !important;
441
+ align-items: center !important;
442
+ }
443
+
444
+ .badges img {
445
+ height: 20px !important;
446
+ transition: transform 0.2s ease !important;
447
+ }
448
+
449
+ .badges img:hover {
450
+ transform: translateY(-2px) !important;
451
+ }
452
+
453
+ /* Remove all blockquote and code block vertical bars and backgrounds */
454
+ .markdown blockquote {
455
+ border-left: none !important;
456
+ background: none !important;
457
+ margin: 0 !important;
458
+ padding: 0 0 0 0 !important;
459
+ color: var(--white) !important;
460
+ box-shadow: none !important;
461
+ }
462
+ .markdown pre,
463
+ .markdown code {
464
+ border: none !important;
465
+ background: var(--dark-gray) !important;
466
+ box-shadow: none !important;
467
+ margin: 0 !important;
468
+ color: var(--white) !important;
469
+ }
470
+
471
+ /* Force remove any left border, box-shadow, or background from all markdown descendants */
472
+ .markdown * {
473
+ border-left: none !important;
474
+ box-shadow: none !important;
475
+ background: none !important;
476
+ color: var(--white) !important;
477
+ }
478
+
tools.py CHANGED
@@ -485,7 +485,7 @@ def wikipedia_search(query: str, num_results: int = 3) -> str:
485
  formatted_results += f"{i}. {title}\n"
486
  formatted_results += f" URL: {source}\n"
487
  formatted_results += f" {content}\n\n"
488
-
489
  return formatted_results
490
 
491
  except Exception as e:
@@ -550,7 +550,7 @@ def tavily_search(query: str, search_depth: str = "basic") -> str:
550
  # Otherwise, just convert to string representation
551
  else:
552
  formatted_results += str(results)
553
-
554
  return formatted_results
555
 
556
  except Exception as e:
@@ -617,7 +617,9 @@ def arxiv_search(query: str, max_results: int = 5) -> str:
617
  if len(abstract) > 300:
618
  abstract = abstract[:300] + "..."
619
  formatted_results += f" Abstract: {abstract}\n\n"
620
-
 
 
621
  return formatted_results
622
 
623
  except Exception as e:
 
485
  formatted_results += f"{i}. {title}\n"
486
  formatted_results += f" URL: {source}\n"
487
  formatted_results += f" {content}\n\n"
488
+ print("formatted_results:", formatted_results[:100])
489
  return formatted_results
490
 
491
  except Exception as e:
 
550
  # Otherwise, just convert to string representation
551
  else:
552
  formatted_results += str(results)
553
+ print("formatted_results:", formatted_results[:100])
554
  return formatted_results
555
 
556
  except Exception as e:
 
617
  if len(abstract) > 300:
618
  abstract = abstract[:300] + "..."
619
  formatted_results += f" Abstract: {abstract}\n\n"
620
+
621
+ print("formatted_results:", formatted_results[:100])
622
+
623
  return formatted_results
624
 
625
  except Exception as e: