cuizhanming commited on
Commit
8a4b1d6
Β·
1 Parent(s): 4eb0e1b
Files changed (6) hide show
  1. agent.py +694 -96
  2. app.py +9 -5
  3. code_interpreter.py +281 -0
  4. explore_metadata.ipynb +332 -0
  5. image_processing.py +26 -0
  6. requirements.txt +2 -2
agent.py CHANGED
@@ -1,15 +1,36 @@
1
- """LangGraph Agent"""
2
  import os
3
  from dotenv import load_dotenv
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  from langgraph.graph import START, StateGraph, MessagesState
5
- from langgraph.prebuilt import tools_condition
6
- from langgraph.prebuilt import ToolNode
7
- from langchain_google_genai import ChatGoogleGenerativeAI
8
- from langchain_groq import ChatGroq
9
- from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
10
  from langchain_community.tools.tavily_search import TavilySearchResults
11
  from langchain_community.document_loaders import WikipediaLoader
12
  from langchain_community.document_loaders import ArxivLoader
 
 
 
 
 
 
 
 
13
  from langchain_community.vectorstores import SupabaseVectorStore
14
  from langchain_core.messages import SystemMessage, HumanMessage
15
  from langchain_core.tools import tool
@@ -18,61 +39,13 @@ from supabase.client import Client, create_client
18
 
19
  load_dotenv()
20
 
21
- @tool
22
- def multiply(a: int, b: int) -> int:
23
- """Multiply two numbers.
24
- Args:
25
- a: first int
26
- b: second int
27
- """
28
- return a * b
29
 
30
- @tool
31
- def add(a: int, b: int) -> int:
32
- """Add two numbers.
33
-
34
- Args:
35
- a: first int
36
- b: second int
37
- """
38
- return a + b
39
-
40
- @tool
41
- def subtract(a: int, b: int) -> int:
42
- """Subtract two numbers.
43
-
44
- Args:
45
- a: first int
46
- b: second int
47
- """
48
- return a - b
49
-
50
- @tool
51
- def divide(a: int, b: int) -> int:
52
- """Divide two numbers.
53
-
54
- Args:
55
- a: first int
56
- b: second int
57
- """
58
- if b == 0:
59
- raise ValueError("Cannot divide by zero.")
60
- return a / b
61
-
62
- @tool
63
- def modulus(a: int, b: int) -> int:
64
- """Get the modulus of two numbers.
65
-
66
- Args:
67
- a: first int
68
- b: second int
69
- """
70
- return a % b
71
 
72
  @tool
73
  def wiki_search(query: str) -> str:
74
  """Search Wikipedia for a query and return maximum 2 results.
75
-
76
  Args:
77
  query: The search query."""
78
  search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
@@ -80,13 +53,15 @@ def wiki_search(query: str) -> str:
80
  [
81
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
82
  for doc in search_docs
83
- ])
 
84
  return {"wiki_results": formatted_search_docs}
85
 
 
86
  @tool
87
  def web_search(query: str) -> str:
88
  """Search Tavily for a query and return maximum 3 results.
89
-
90
  Args:
91
  query: The search query."""
92
  search_docs = TavilySearchResults(max_results=3).invoke(query=query)
@@ -94,13 +69,15 @@ def web_search(query: str) -> str:
94
  [
95
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
96
  for doc in search_docs
97
- ])
 
98
  return {"web_results": formatted_search_docs}
99
 
 
100
  @tool
101
- def arvix_search(query: str) -> str:
102
  """Search Arxiv for a query and return maximum 3 result.
103
-
104
  Args:
105
  query: The search query."""
106
  search_docs = ArxivLoader(query=query, load_max_docs=3).load()
@@ -108,28 +85,620 @@ def arvix_search(query: str) -> str:
108
  [
109
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
110
  for doc in search_docs
111
- ])
112
- return {"arvix_results": formatted_search_docs}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  # load the system prompt from the file
117
  with open("system_prompt.txt", "r", encoding="utf-8") as f:
118
  system_prompt = f.read()
 
119
 
120
  # System message
121
  sys_msg = SystemMessage(content=system_prompt)
122
 
123
  # build a retriever
124
- embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") # dim=768
 
 
125
  supabase: Client = create_client(
126
- os.environ.get("SUPABASE_URL"),
127
- os.environ.get("SUPABASE_SERVICE_KEY"))
128
  vector_store = SupabaseVectorStore(
129
  client=supabase,
130
- embedding= embeddings,
131
- table_name="documents",
132
- query_name="match_documents_langchain",
133
  )
134
  create_retriever_tool = create_retriever_tool(
135
  retriever=vector_store.as_retriever(),
@@ -139,36 +708,52 @@ create_retriever_tool = create_retriever_tool(
139
 
140
 
141
  tools = [
 
 
 
142
  multiply,
143
  add,
144
  subtract,
145
  divide,
146
  modulus,
147
- wiki_search,
148
- web_search,
149
- arvix_search,
 
 
 
 
 
 
 
 
 
 
150
  ]
151
 
 
152
  # Build graph function
153
- def build_graph(provider: str = "google"):
154
  """Build the graph"""
155
  # Load environment variables from .env file
156
- if provider == "google":
157
- # Google Gemini
158
- llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
159
- elif provider == "groq":
160
  # Groq https://console.groq.com/docs/models
161
- llm = ChatGroq(model="qwen-qwq-32b", temperature=0) # optional : qwen-qwq-32b gemma2-9b-it
162
  elif provider == "huggingface":
163
  # TODO: Add huggingface endpoint
164
  llm = ChatHuggingFace(
165
  llm=HuggingFaceEndpoint(
166
- url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
 
 
 
 
167
  temperature=0,
168
  ),
 
169
  )
170
  else:
171
- raise ValueError("Invalid provider. Choose 'google', 'groq' or 'huggingface'.")
172
  # Bind tools to LLM
173
  llm_with_tools = llm.bind_tools(tools)
174
 
@@ -176,28 +761,41 @@ def build_graph(provider: str = "google"):
176
  def assistant(state: MessagesState):
177
  """Assistant node"""
178
  return {"messages": [llm_with_tools.invoke(state["messages"])]}
179
-
180
-
181
- from langchain_core.messages import AIMessage
182
 
183
  def retriever(state: MessagesState):
184
- query = state["messages"][-1].content
185
- similar_doc = vector_store.similarity_search(query, k=1)[0]
186
 
187
- content = similar_doc.page_content
188
- if "Final answer :" in content:
189
- answer = content.split("Final answer :")[-1].strip()
 
 
190
  else:
191
- answer = content.strip()
192
-
193
- return {"messages": [AIMessage(content=answer)]}
194
 
195
  builder = StateGraph(MessagesState)
196
  builder.add_node("retriever", retriever)
197
-
198
- # Retriever ist Start und Endpunkt
199
- builder.set_entry_point("retriever")
200
- builder.set_finish_point("retriever")
 
 
 
 
 
201
 
202
  # Compile graph
203
- return builder.compile()
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  from dotenv import load_dotenv
3
+ from typing import List, Dict, Any, Optional
4
+ import tempfile
5
+ import re
6
+ import json
7
+ import requests
8
+ from urllib.parse import urlparse
9
+ import pytesseract
10
+ from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
11
+ import cmath
12
+ import pandas as pd
13
+ import uuid
14
+ import numpy as np
15
+ from code_interpreter import CodeInterpreter
16
+
17
+ interpreter_instance = CodeInterpreter()
18
+
19
+ from image_processing import *
20
+
21
+ """Langraph"""
22
  from langgraph.graph import START, StateGraph, MessagesState
 
 
 
 
 
23
  from langchain_community.tools.tavily_search import TavilySearchResults
24
  from langchain_community.document_loaders import WikipediaLoader
25
  from langchain_community.document_loaders import ArxivLoader
26
+ from langgraph.prebuilt import ToolNode, tools_condition
27
+ from langchain_google_genai import ChatGoogleGenerativeAI
28
+ from langchain_groq import ChatGroq
29
+ from langchain_huggingface import (
30
+ ChatHuggingFace,
31
+ HuggingFaceEndpoint,
32
+ HuggingFaceEmbeddings,
33
+ )
34
  from langchain_community.vectorstores import SupabaseVectorStore
35
  from langchain_core.messages import SystemMessage, HumanMessage
36
  from langchain_core.tools import tool
 
39
 
40
  load_dotenv()
41
 
42
+ ### =============== BROWSER TOOLS =============== ###
 
 
 
 
 
 
 
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
  @tool
46
  def wiki_search(query: str) -> str:
47
  """Search Wikipedia for a query and return maximum 2 results.
48
+
49
  Args:
50
  query: The search query."""
51
  search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
 
53
  [
54
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
55
  for doc in search_docs
56
+ ]
57
+ )
58
  return {"wiki_results": formatted_search_docs}
59
 
60
+
61
  @tool
62
  def web_search(query: str) -> str:
63
  """Search Tavily for a query and return maximum 3 results.
64
+
65
  Args:
66
  query: The search query."""
67
  search_docs = TavilySearchResults(max_results=3).invoke(query=query)
 
69
  [
70
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
71
  for doc in search_docs
72
+ ]
73
+ )
74
  return {"web_results": formatted_search_docs}
75
 
76
+
77
  @tool
78
+ def arxiv_search(query: str) -> str:
79
  """Search Arxiv for a query and return maximum 3 result.
80
+
81
  Args:
82
  query: The search query."""
83
  search_docs = ArxivLoader(query=query, load_max_docs=3).load()
 
85
  [
86
  f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
87
  for doc in search_docs
88
+ ]
89
+ )
90
+ return {"arxiv_results": formatted_search_docs}
91
+
92
+
93
+ ### =============== CODE INTERPRETER TOOLS =============== ###
94
+
95
+
96
+ @tool
97
+ def execute_code_multilang(code: str, language: str = "python") -> str:
98
+ """Execute code in multiple languages (Python, Bash, SQL, C, Java) and return results.
99
+
100
+ Args:
101
+ code (str): The source code to execute.
102
+ language (str): The language of the code. Supported: "python", "bash", "sql", "c", "java".
103
+
104
+ Returns:
105
+ A string summarizing the execution results (stdout, stderr, errors, plots, dataframes if any).
106
+ """
107
+ supported_languages = ["python", "bash", "sql", "c", "java"]
108
+ language = language.lower()
109
+
110
+ if language not in supported_languages:
111
+ return f"❌ Unsupported language: {language}. Supported languages are: {', '.join(supported_languages)}"
112
+
113
+ result = interpreter_instance.execute_code(code, language=language)
114
+
115
+ response = []
116
+
117
+ if result["status"] == "success":
118
+ response.append(f"βœ… Code executed successfully in **{language.upper()}**")
119
+
120
+ if result.get("stdout"):
121
+ response.append(
122
+ "\n**Standard Output:**\n```\n" + result["stdout"].strip() + "\n```"
123
+ )
124
+
125
+ if result.get("stderr"):
126
+ response.append(
127
+ "\n**Standard Error (if any):**\n```\n"
128
+ + result["stderr"].strip()
129
+ + "\n```"
130
+ )
131
+
132
+ if result.get("result") is not None:
133
+ response.append(
134
+ "\n**Execution Result:**\n```\n"
135
+ + str(result["result"]).strip()
136
+ + "\n```"
137
+ )
138
+
139
+ if result.get("dataframes"):
140
+ for df_info in result["dataframes"]:
141
+ response.append(
142
+ f"\n**DataFrame `{df_info['name']}` (Shape: {df_info['shape']})**"
143
+ )
144
+ df_preview = pd.DataFrame(df_info["head"])
145
+ response.append("First 5 rows:\n```\n" + str(df_preview) + "\n```")
146
+
147
+ if result.get("plots"):
148
+ response.append(
149
+ f"\n**Generated {len(result['plots'])} plot(s)** (Image data returned separately)"
150
+ )
151
+
152
+ else:
153
+ response.append(f"❌ Code execution failed in **{language.upper()}**")
154
+ if result.get("stderr"):
155
+ response.append(
156
+ "\n**Error Log:**\n```\n" + result["stderr"].strip() + "\n```"
157
+ )
158
+
159
+ return "\n".join(response)
160
+
161
+
162
+ ### =============== MATHEMATICAL TOOLS =============== ###
163
+
164
+
165
+ @tool
166
+ def multiply(a: float, b: float) -> float:
167
+ """
168
+ Multiplies two numbers.
169
+
170
+ Args:
171
+ a (float): the first number
172
+ b (float): the second number
173
+ """
174
+ return a * b
175
+
176
+
177
+ @tool
178
+ def add(a: float, b: float) -> float:
179
+ """
180
+ Adds two numbers.
181
+
182
+ Args:
183
+ a (float): the first number
184
+ b (float): the second number
185
+ """
186
+ return a + b
187
+
188
+
189
+ @tool
190
+ def subtract(a: float, b: float) -> int:
191
+ """
192
+ Subtracts two numbers.
193
+
194
+ Args:
195
+ a (float): the first number
196
+ b (float): the second number
197
+ """
198
+ return a - b
199
+
200
+
201
+ @tool
202
+ def divide(a: float, b: float) -> float:
203
+ """
204
+ Divides two numbers.
205
+
206
+ Args:
207
+ a (float): the first float number
208
+ b (float): the second float number
209
+ """
210
+ if b == 0:
211
+ raise ValueError("Cannot divided by zero.")
212
+ return a / b
213
+
214
+
215
+ @tool
216
+ def modulus(a: int, b: int) -> int:
217
+ """
218
+ Get the modulus of two numbers.
219
+
220
+ Args:
221
+ a (int): the first number
222
+ b (int): the second number
223
+ """
224
+ return a % b
225
+
226
+
227
+ @tool
228
+ def power(a: float, b: float) -> float:
229
+ """
230
+ Get the power of two numbers.
231
+
232
+ Args:
233
+ a (float): the first number
234
+ b (float): the second number
235
+ """
236
+ return a**b
237
+
238
+
239
+ @tool
240
+ def square_root(a: float) -> float | complex:
241
+ """
242
+ Get the square root of a number.
243
+
244
+ Args:
245
+ a (float): the number to get the square root of
246
+ """
247
+ if a >= 0:
248
+ return a**0.5
249
+ return cmath.sqrt(a)
250
+
251
+
252
+ ### =============== DOCUMENT PROCESSING TOOLS =============== ###
253
+
254
+
255
+ @tool
256
+ def save_and_read_file(content: str, filename: Optional[str] = None) -> str:
257
+ """
258
+ Save content to a file and return the path.
259
+
260
+ Args:
261
+ content (str): the content to save to the file
262
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
263
+ """
264
+ temp_dir = tempfile.gettempdir()
265
+ if filename is None:
266
+ temp_file = tempfile.NamedTemporaryFile(delete=False, dir=temp_dir)
267
+ filepath = temp_file.name
268
+ else:
269
+ filepath = os.path.join(temp_dir, filename)
270
+
271
+ with open(filepath, "w") as f:
272
+ f.write(content)
273
+
274
+ return f"File saved to {filepath}. You can read this file to process its contents."
275
+
276
+
277
+ @tool
278
+ def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
279
+ """
280
+ Download a file from a URL and save it to a temporary location.
281
+
282
+ Args:
283
+ url (str): the URL of the file to download.
284
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
285
+ """
286
+ try:
287
+ # Parse URL to get filename if not provided
288
+ if not filename:
289
+ path = urlparse(url).path
290
+ filename = os.path.basename(path)
291
+ if not filename:
292
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}"
293
+
294
+ # Create temporary file
295
+ temp_dir = tempfile.gettempdir()
296
+ filepath = os.path.join(temp_dir, filename)
297
+
298
+ # Download the file
299
+ response = requests.get(url, stream=True)
300
+ response.raise_for_status()
301
+
302
+ # Save the file
303
+ with open(filepath, "wb") as f:
304
+ for chunk in response.iter_content(chunk_size=8192):
305
+ f.write(chunk)
306
+
307
+ return f"File downloaded to {filepath}. You can read this file to process its contents."
308
+ except Exception as e:
309
+ return f"Error downloading file: {str(e)}"
310
+
311
+
312
+ @tool
313
+ def extract_text_from_image(image_path: str) -> str:
314
+ """
315
+ Extract text from an image using OCR library pytesseract (if available).
316
+
317
+ Args:
318
+ image_path (str): the path to the image file.
319
+ """
320
+ try:
321
+ # Open the image
322
+ image = Image.open(image_path)
323
+
324
+ # Extract text from the image
325
+ text = pytesseract.image_to_string(image)
326
+
327
+ return f"Extracted text from image:\n\n{text}"
328
+ except Exception as e:
329
+ return f"Error extracting text from image: {str(e)}"
330
+
331
+
332
+ @tool
333
+ def analyze_csv_file(file_path: str, query: str) -> str:
334
+ """
335
+ Analyze a CSV file using pandas and answer a question about it.
336
+
337
+ Args:
338
+ file_path (str): the path to the CSV file.
339
+ query (str): Question about the data
340
+ """
341
+ try:
342
+ # Read the CSV file
343
+ df = pd.read_csv(file_path)
344
+
345
+ # Run various analyses based on the query
346
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
347
+ result += f"Columns: {', '.join(df.columns)}\n\n"
348
+
349
+ # Add summary statistics
350
+ result += "Summary statistics:\n"
351
+ result += str(df.describe())
352
+
353
+ return result
354
+
355
+ except Exception as e:
356
+ return f"Error analyzing CSV file: {str(e)}"
357
+
358
+
359
+ @tool
360
+ def analyze_excel_file(file_path: str, query: str) -> str:
361
+ """
362
+ Analyze an Excel file using pandas and answer a question about it.
363
+
364
+ Args:
365
+ file_path (str): the path to the Excel file.
366
+ query (str): Question about the data
367
+ """
368
+ try:
369
+ # Read the Excel file
370
+ df = pd.read_excel(file_path)
371
+
372
+ # Run various analyses based on the query
373
+ result = (
374
+ f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
375
+ )
376
+ result += f"Columns: {', '.join(df.columns)}\n\n"
377
+
378
+ # Add summary statistics
379
+ result += "Summary statistics:\n"
380
+ result += str(df.describe())
381
+
382
+ return result
383
+
384
+ except Exception as e:
385
+ return f"Error analyzing Excel file: {str(e)}"
386
+
387
+
388
+ ### ============== IMAGE PROCESSING AND GENERATION TOOLS =============== ###
389
+
390
+
391
+ @tool
392
+ def analyze_image(image_base64: str) -> Dict[str, Any]:
393
+ """
394
+ Analyze basic properties of an image (size, mode, color analysis, thumbnail preview).
395
+
396
+ Args:
397
+ image_base64 (str): Base64 encoded image string
398
+
399
+ Returns:
400
+ Dictionary with analysis result
401
+ """
402
+ try:
403
+ img = decode_image(image_base64)
404
+ width, height = img.size
405
+ mode = img.mode
406
+
407
+ if mode in ("RGB", "RGBA"):
408
+ arr = np.array(img)
409
+ avg_colors = arr.mean(axis=(0, 1))
410
+ dominant = ["Red", "Green", "Blue"][np.argmax(avg_colors[:3])]
411
+ brightness = avg_colors.mean()
412
+ color_analysis = {
413
+ "average_rgb": avg_colors.tolist(),
414
+ "brightness": brightness,
415
+ "dominant_color": dominant,
416
+ }
417
+ else:
418
+ color_analysis = {"note": f"No color analysis for mode {mode}"}
419
+
420
+ thumbnail = img.copy()
421
+ thumbnail.thumbnail((100, 100))
422
+ thumb_path = save_image(thumbnail, "thumbnails")
423
+ thumbnail_base64 = encode_image(thumb_path)
424
+
425
+ return {
426
+ "dimensions": (width, height),
427
+ "mode": mode,
428
+ "color_analysis": color_analysis,
429
+ "thumbnail": thumbnail_base64,
430
+ }
431
+ except Exception as e:
432
+ return {"error": str(e)}
433
 
434
 
435
+ @tool
436
+ def transform_image(
437
+ image_base64: str, operation: str, params: Optional[Dict[str, Any]] = None
438
+ ) -> Dict[str, Any]:
439
+ """
440
+ Apply transformations: resize, rotate, crop, flip, brightness, contrast, blur, sharpen, grayscale.
441
+
442
+ Args:
443
+ image_base64 (str): Base64 encoded input image
444
+ operation (str): Transformation operation
445
+ params (Dict[str, Any], optional): Parameters for the operation
446
+
447
+ Returns:
448
+ Dictionary with transformed image (base64)
449
+ """
450
+ try:
451
+ img = decode_image(image_base64)
452
+ params = params or {}
453
+
454
+ if operation == "resize":
455
+ img = img.resize(
456
+ (
457
+ params.get("width", img.width // 2),
458
+ params.get("height", img.height // 2),
459
+ )
460
+ )
461
+ elif operation == "rotate":
462
+ img = img.rotate(params.get("angle", 90), expand=True)
463
+ elif operation == "crop":
464
+ img = img.crop(
465
+ (
466
+ params.get("left", 0),
467
+ params.get("top", 0),
468
+ params.get("right", img.width),
469
+ params.get("bottom", img.height),
470
+ )
471
+ )
472
+ elif operation == "flip":
473
+ if params.get("direction", "horizontal") == "horizontal":
474
+ img = img.transpose(Image.FLIP_LEFT_RIGHT)
475
+ else:
476
+ img = img.transpose(Image.FLIP_TOP_BOTTOM)
477
+ elif operation == "adjust_brightness":
478
+ img = ImageEnhance.Brightness(img).enhance(params.get("factor", 1.5))
479
+ elif operation == "adjust_contrast":
480
+ img = ImageEnhance.Contrast(img).enhance(params.get("factor", 1.5))
481
+ elif operation == "blur":
482
+ img = img.filter(ImageFilter.GaussianBlur(params.get("radius", 2)))
483
+ elif operation == "sharpen":
484
+ img = img.filter(ImageFilter.SHARPEN)
485
+ elif operation == "grayscale":
486
+ img = img.convert("L")
487
+ else:
488
+ return {"error": f"Unknown operation: {operation}"}
489
+
490
+ result_path = save_image(img)
491
+ result_base64 = encode_image(result_path)
492
+ return {"transformed_image": result_base64}
493
+
494
+ except Exception as e:
495
+ return {"error": str(e)}
496
+
497
+
498
+ @tool
499
+ def draw_on_image(
500
+ image_base64: str, drawing_type: str, params: Dict[str, Any]
501
+ ) -> Dict[str, Any]:
502
+ """
503
+ Draw shapes (rectangle, circle, line) or text onto an image.
504
+
505
+ Args:
506
+ image_base64 (str): Base64 encoded input image
507
+ drawing_type (str): Drawing type
508
+ params (Dict[str, Any]): Drawing parameters
509
+
510
+ Returns:
511
+ Dictionary with result image (base64)
512
+ """
513
+ try:
514
+ img = decode_image(image_base64)
515
+ draw = ImageDraw.Draw(img)
516
+ color = params.get("color", "red")
517
+
518
+ if drawing_type == "rectangle":
519
+ draw.rectangle(
520
+ [params["left"], params["top"], params["right"], params["bottom"]],
521
+ outline=color,
522
+ width=params.get("width", 2),
523
+ )
524
+ elif drawing_type == "circle":
525
+ x, y, r = params["x"], params["y"], params["radius"]
526
+ draw.ellipse(
527
+ (x - r, y - r, x + r, y + r),
528
+ outline=color,
529
+ width=params.get("width", 2),
530
+ )
531
+ elif drawing_type == "line":
532
+ draw.line(
533
+ (
534
+ params["start_x"],
535
+ params["start_y"],
536
+ params["end_x"],
537
+ params["end_y"],
538
+ ),
539
+ fill=color,
540
+ width=params.get("width", 2),
541
+ )
542
+ elif drawing_type == "text":
543
+ font_size = params.get("font_size", 20)
544
+ try:
545
+ font = ImageFont.truetype("arial.ttf", font_size)
546
+ except IOError:
547
+ font = ImageFont.load_default()
548
+ draw.text(
549
+ (params["x"], params["y"]),
550
+ params.get("text", "Text"),
551
+ fill=color,
552
+ font=font,
553
+ )
554
+ else:
555
+ return {"error": f"Unknown drawing type: {drawing_type}"}
556
+
557
+ result_path = save_image(img)
558
+ result_base64 = encode_image(result_path)
559
+ return {"result_image": result_base64}
560
+
561
+ except Exception as e:
562
+ return {"error": str(e)}
563
+
564
+
565
+ @tool
566
+ def generate_simple_image(
567
+ image_type: str,
568
+ width: int = 500,
569
+ height: int = 500,
570
+ params: Optional[Dict[str, Any]] = None,
571
+ ) -> Dict[str, Any]:
572
+ """
573
+ Generate a simple image (gradient, noise, pattern, chart).
574
+
575
+ Args:
576
+ image_type (str): Type of image
577
+ width (int), height (int)
578
+ params (Dict[str, Any], optional): Specific parameters
579
+
580
+ Returns:
581
+ Dictionary with generated image (base64)
582
+ """
583
+ try:
584
+ params = params or {}
585
+
586
+ if image_type == "gradient":
587
+ direction = params.get("direction", "horizontal")
588
+ start_color = params.get("start_color", (255, 0, 0))
589
+ end_color = params.get("end_color", (0, 0, 255))
590
+
591
+ img = Image.new("RGB", (width, height))
592
+ draw = ImageDraw.Draw(img)
593
+
594
+ if direction == "horizontal":
595
+ for x in range(width):
596
+ r = int(
597
+ start_color[0] + (end_color[0] - start_color[0]) * x / width
598
+ )
599
+ g = int(
600
+ start_color[1] + (end_color[1] - start_color[1]) * x / width
601
+ )
602
+ b = int(
603
+ start_color[2] + (end_color[2] - start_color[2]) * x / width
604
+ )
605
+ draw.line([(x, 0), (x, height)], fill=(r, g, b))
606
+ else:
607
+ for y in range(height):
608
+ r = int(
609
+ start_color[0] + (end_color[0] - start_color[0]) * y / height
610
+ )
611
+ g = int(
612
+ start_color[1] + (end_color[1] - start_color[1]) * y / height
613
+ )
614
+ b = int(
615
+ start_color[2] + (end_color[2] - start_color[2]) * y / height
616
+ )
617
+ draw.line([(0, y), (width, y)], fill=(r, g, b))
618
+
619
+ elif image_type == "noise":
620
+ noise_array = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8)
621
+ img = Image.fromarray(noise_array, "RGB")
622
+
623
+ else:
624
+ return {"error": f"Unsupported image_type {image_type}"}
625
+
626
+ result_path = save_image(img)
627
+ result_base64 = encode_image(result_path)
628
+ return {"generated_image": result_base64}
629
+
630
+ except Exception as e:
631
+ return {"error": str(e)}
632
+
633
+
634
+ @tool
635
+ def combine_images(
636
+ images_base64: List[str], operation: str, params: Optional[Dict[str, Any]] = None
637
+ ) -> Dict[str, Any]:
638
+ """
639
+ Combine multiple images (collage, stack, blend).
640
+
641
+ Args:
642
+ images_base64 (List[str]): List of base64 images
643
+ operation (str): Combination type
644
+ params (Dict[str, Any], optional)
645
+
646
+ Returns:
647
+ Dictionary with combined image (base64)
648
+ """
649
+ try:
650
+ images = [decode_image(b64) for b64 in images_base64]
651
+ params = params or {}
652
+
653
+ if operation == "stack":
654
+ direction = params.get("direction", "horizontal")
655
+ if direction == "horizontal":
656
+ total_width = sum(img.width for img in images)
657
+ max_height = max(img.height for img in images)
658
+ new_img = Image.new("RGB", (total_width, max_height))
659
+ x = 0
660
+ for img in images:
661
+ new_img.paste(img, (x, 0))
662
+ x += img.width
663
+ else:
664
+ max_width = max(img.width for img in images)
665
+ total_height = sum(img.height for img in images)
666
+ new_img = Image.new("RGB", (max_width, total_height))
667
+ y = 0
668
+ for img in images:
669
+ new_img.paste(img, (0, y))
670
+ y += img.height
671
+ else:
672
+ return {"error": f"Unsupported combination operation {operation}"}
673
+
674
+ result_path = save_image(new_img)
675
+ result_base64 = encode_image(result_path)
676
+ return {"combined_image": result_base64}
677
+
678
+ except Exception as e:
679
+ return {"error": str(e)}
680
+
681
 
682
  # load the system prompt from the file
683
  with open("system_prompt.txt", "r", encoding="utf-8") as f:
684
  system_prompt = f.read()
685
+ print(system_prompt)
686
 
687
  # System message
688
  sys_msg = SystemMessage(content=system_prompt)
689
 
690
  # build a retriever
691
+ embeddings = HuggingFaceEmbeddings(
692
+ model_name="sentence-transformers/all-mpnet-base-v2"
693
+ ) # dim=768
694
  supabase: Client = create_client(
695
+ os.environ.get("SUPABASE_URL"), os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
696
+ )
697
  vector_store = SupabaseVectorStore(
698
  client=supabase,
699
+ embedding=embeddings,
700
+ table_name="documents2",
701
+ query_name="match_documents_2",
702
  )
703
  create_retriever_tool = create_retriever_tool(
704
  retriever=vector_store.as_retriever(),
 
708
 
709
 
710
  tools = [
711
+ web_search,
712
+ wiki_search,
713
+ arxiv_search,
714
  multiply,
715
  add,
716
  subtract,
717
  divide,
718
  modulus,
719
+ power,
720
+ square_root,
721
+ save_and_read_file,
722
+ download_file_from_url,
723
+ extract_text_from_image,
724
+ analyze_csv_file,
725
+ analyze_excel_file,
726
+ execute_code_multilang,
727
+ analyze_image,
728
+ transform_image,
729
+ draw_on_image,
730
+ generate_simple_image,
731
+ combine_images,
732
  ]
733
 
734
+
735
  # Build graph function
736
+ def build_graph(provider: str = "groq"):
737
  """Build the graph"""
738
  # Load environment variables from .env file
739
+ if provider == "groq":
 
 
 
740
  # Groq https://console.groq.com/docs/models
741
+ llm = ChatGroq(model="qwen-qwq-32b", temperature=0)
742
  elif provider == "huggingface":
743
  # TODO: Add huggingface endpoint
744
  llm = ChatHuggingFace(
745
  llm=HuggingFaceEndpoint(
746
+ repo_id="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
747
+ task="text-generation", # for chat‐style use β€œtext-generation”
748
+ max_new_tokens=1024,
749
+ do_sample=False,
750
+ repetition_penalty=1.03,
751
  temperature=0,
752
  ),
753
+ verbose=True,
754
  )
755
  else:
756
+ raise ValueError("Invalid provider. Choose 'groq' or 'huggingface'.")
757
  # Bind tools to LLM
758
  llm_with_tools = llm.bind_tools(tools)
759
 
 
761
  def assistant(state: MessagesState):
762
  """Assistant node"""
763
  return {"messages": [llm_with_tools.invoke(state["messages"])]}
 
 
 
764
 
765
  def retriever(state: MessagesState):
766
+ """Retriever node"""
767
+ similar_question = vector_store.similarity_search(state["messages"][0].content)
768
 
769
+ if similar_question: # Check if the list is not empty
770
+ example_msg = HumanMessage(
771
+ content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
772
+ )
773
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
774
  else:
775
+ # Handle the case when no similar questions are found
776
+ return {"messages": [sys_msg] + state["messages"]}
 
777
 
778
  builder = StateGraph(MessagesState)
779
  builder.add_node("retriever", retriever)
780
+ builder.add_node("assistant", assistant)
781
+ builder.add_node("tools", ToolNode(tools))
782
+ builder.add_edge(START, "retriever")
783
+ builder.add_edge("retriever", "assistant")
784
+ builder.add_conditional_edges(
785
+ "assistant",
786
+ tools_condition,
787
+ )
788
+ builder.add_edge("tools", "assistant")
789
 
790
  # Compile graph
791
+ return builder.compile()
792
+
793
+
794
+ # test
795
+ if __name__ == "__main__":
796
+ question = "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect?"
797
+ graph = build_graph(provider="groq")
798
+ messages = [HumanMessage(content=question)]
799
+ messages = graph.invoke({"messages": messages})
800
+ for m in messages["messages"]:
801
+ m.pretty_print()
app.py CHANGED
@@ -4,6 +4,7 @@ import inspect
4
  import gradio as gr
5
  import requests
6
  import pandas as pd
 
7
  from langchain_core.messages import HumanMessage
8
  from agent import build_graph
9
 
@@ -21,15 +22,15 @@ class BasicAgent:
21
  """A langgraph agent."""
22
  def __init__(self):
23
  print("BasicAgent initialized.")
24
- self.graph = build_graph(provider="huggingface")
25
 
26
  def __call__(self, question: str) -> str:
27
  print(f"Agent received question (first 50 chars): {question[:50]}...")
 
28
  messages = [HumanMessage(content=question)]
29
- result = self.graph.invoke({"messages": messages})
30
- answer = result['messages'][-1].content
31
- return answer # kein [14:] mehr nΓΆtig!
32
-
33
 
34
 
35
  def run_and_submit_all( profile: gr.OAuthProfile | None):
@@ -92,6 +93,9 @@ def run_and_submit_all( profile: gr.OAuthProfile | None):
92
  if not task_id or question_text is None:
93
  print(f"Skipping item with missing task_id or question: {item}")
94
  continue
 
 
 
95
  try:
96
  submitted_answer = agent(question_text)
97
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
 
4
  import gradio as gr
5
  import requests
6
  import pandas as pd
7
+ import time
8
  from langchain_core.messages import HumanMessage
9
  from agent import build_graph
10
 
 
22
  """A langgraph agent."""
23
  def __init__(self):
24
  print("BasicAgent initialized.")
25
+ self.graph = build_graph()
26
 
27
  def __call__(self, question: str) -> str:
28
  print(f"Agent received question (first 50 chars): {question[:50]}...")
29
+ # Wrap the question in a HumanMessage from langchain_core
30
  messages = [HumanMessage(content=question)]
31
+ messages = self.graph.invoke({"messages": messages})
32
+ answer = messages['messages'][-1].content
33
+ return answer[14:]
 
34
 
35
 
36
  def run_and_submit_all( profile: gr.OAuthProfile | None):
 
93
  if not task_id or question_text is None:
94
  print(f"Skipping item with missing task_id or question: {item}")
95
  continue
96
+
97
+ # time.sleep(10)
98
+
99
  try:
100
  submitted_answer = agent(question_text)
101
  answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
code_interpreter.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import sys
4
+ import uuid
5
+ import base64
6
+ import traceback
7
+ import contextlib
8
+ import tempfile
9
+ import subprocess
10
+ import sqlite3
11
+ from typing import Dict, List, Any, Optional, Union
12
+ import numpy as np
13
+ import pandas as pd
14
+ import matplotlib.pyplot as plt
15
+ from PIL import Image
16
+
17
+ class CodeInterpreter:
18
+ def __init__(self, allowed_modules=None, max_execution_time=30, working_directory=None):
19
+ """Initialize the code interpreter with safety measures."""
20
+ self.allowed_modules = allowed_modules or [
21
+ "numpy", "pandas", "matplotlib", "scipy", "sklearn",
22
+ "math", "random", "statistics", "datetime", "collections",
23
+ "itertools", "functools", "operator", "re", "json",
24
+ "sympy", "networkx", "nltk", "PIL", "pytesseract",
25
+ "cmath", "uuid", "tempfile", "requests", "urllib"
26
+ ]
27
+ self.max_execution_time = max_execution_time
28
+ self.working_directory = working_directory or os.path.join(os.getcwd())
29
+ if not os.path.exists(self.working_directory):
30
+ os.makedirs(self.working_directory)
31
+
32
+ self.globals = {
33
+ "__builtins__": __builtins__,
34
+ "np": np,
35
+ "pd": pd,
36
+ "plt": plt,
37
+ "Image": Image,
38
+ }
39
+ self.temp_sqlite_db = os.path.join(tempfile.gettempdir(), "code_exec.db")
40
+
41
+ def execute_code(self, code: str, language: str = "python") -> Dict[str, Any]:
42
+ """Execute the provided code in the selected programming language."""
43
+ language = language.lower()
44
+ execution_id = str(uuid.uuid4())
45
+
46
+ result = {
47
+ "execution_id": execution_id,
48
+ "status": "error",
49
+ "stdout": "",
50
+ "stderr": "",
51
+ "result": None,
52
+ "plots": [],
53
+ "dataframes": []
54
+ }
55
+
56
+ try:
57
+ if language == "python":
58
+ return self._execute_python(code, execution_id)
59
+ elif language == "bash":
60
+ return self._execute_bash(code, execution_id)
61
+ elif language == "sql":
62
+ return self._execute_sql(code, execution_id)
63
+ elif language == "c":
64
+ return self._execute_c(code, execution_id)
65
+ elif language == "java":
66
+ return self._execute_java(code, execution_id)
67
+ else:
68
+ result["stderr"] = f"Unsupported language: {language}"
69
+ except Exception as e:
70
+ result["stderr"] = str(e)
71
+
72
+ return result
73
+
74
+ def _execute_python(self, code: str, execution_id: str) -> dict:
75
+ output_buffer = io.StringIO()
76
+ error_buffer = io.StringIO()
77
+ result = {
78
+ "execution_id": execution_id,
79
+ "status": "error",
80
+ "stdout": "",
81
+ "stderr": "",
82
+ "result": None,
83
+ "plots": [],
84
+ "dataframes": []
85
+ }
86
+
87
+ try:
88
+ exec_dir = os.path.join(self.working_directory, execution_id)
89
+ os.makedirs(exec_dir, exist_ok=True)
90
+ plt.switch_backend('Agg')
91
+
92
+ with contextlib.redirect_stdout(output_buffer), contextlib.redirect_stderr(error_buffer):
93
+ exec_result = exec(code, self.globals)
94
+
95
+ if plt.get_fignums():
96
+ for i, fig_num in enumerate(plt.get_fignums()):
97
+ fig = plt.figure(fig_num)
98
+ img_path = os.path.join(exec_dir, f"plot_{i}.png")
99
+ fig.savefig(img_path)
100
+ with open(img_path, "rb") as img_file:
101
+ img_data = base64.b64encode(img_file.read()).decode('utf-8')
102
+ result["plots"].append({
103
+ "figure_number": fig_num,
104
+ "data": img_data
105
+ })
106
+
107
+ for var_name, var_value in self.globals.items():
108
+ if isinstance(var_value, pd.DataFrame) and len(var_value) > 0:
109
+ result["dataframes"].append({
110
+ "name": var_name,
111
+ "head": var_value.head().to_dict(),
112
+ "shape": var_value.shape,
113
+ "dtypes": str(var_value.dtypes)
114
+ })
115
+
116
+ result["status"] = "success"
117
+ result["stdout"] = output_buffer.getvalue()
118
+ result["result"] = exec_result
119
+
120
+ except Exception as e:
121
+ result["status"] = "error"
122
+ result["stderr"] = f"{error_buffer.getvalue()}\n{traceback.format_exc()}"
123
+
124
+ return result
125
+
126
+ def _execute_bash(self, code: str, execution_id: str) -> dict:
127
+ try:
128
+ completed = subprocess.run(
129
+ code, shell=True, capture_output=True, text=True, timeout=self.max_execution_time
130
+ )
131
+ return {
132
+ "execution_id": execution_id,
133
+ "status": "success" if completed.returncode == 0 else "error",
134
+ "stdout": completed.stdout,
135
+ "stderr": completed.stderr,
136
+ "result": None,
137
+ "plots": [],
138
+ "dataframes": []
139
+ }
140
+ except subprocess.TimeoutExpired:
141
+ return {
142
+ "execution_id": execution_id,
143
+ "status": "error",
144
+ "stdout": "",
145
+ "stderr": "Execution timed out.",
146
+ "result": None,
147
+ "plots": [],
148
+ "dataframes": []
149
+ }
150
+
151
+ def _execute_sql(self, code: str, execution_id: str) -> dict:
152
+ result = {
153
+ "execution_id": execution_id,
154
+ "status": "error",
155
+ "stdout": "",
156
+ "stderr": "",
157
+ "result": None,
158
+ "plots": [],
159
+ "dataframes": []
160
+ }
161
+ try:
162
+ conn = sqlite3.connect(self.temp_sqlite_db)
163
+ cur = conn.cursor()
164
+ cur.execute(code)
165
+ if code.strip().lower().startswith("select"):
166
+ columns = [description[0] for description in cur.description]
167
+ rows = cur.fetchall()
168
+ df = pd.DataFrame(rows, columns=columns)
169
+ result["dataframes"].append({
170
+ "name": "query_result",
171
+ "head": df.head().to_dict(),
172
+ "shape": df.shape,
173
+ "dtypes": str(df.dtypes)
174
+ })
175
+ else:
176
+ conn.commit()
177
+
178
+ result["status"] = "success"
179
+ result["stdout"] = "Query executed successfully."
180
+
181
+ except Exception as e:
182
+ result["stderr"] = str(e)
183
+ finally:
184
+ conn.close()
185
+
186
+ return result
187
+
188
+ def _execute_c(self, code: str, execution_id: str) -> dict:
189
+ temp_dir = tempfile.mkdtemp()
190
+ source_path = os.path.join(temp_dir, "program.c")
191
+ binary_path = os.path.join(temp_dir, "program")
192
+
193
+ try:
194
+ with open(source_path, "w") as f:
195
+ f.write(code)
196
+
197
+ compile_proc = subprocess.run(
198
+ ["gcc", source_path, "-o", binary_path],
199
+ capture_output=True, text=True, timeout=self.max_execution_time
200
+ )
201
+ if compile_proc.returncode != 0:
202
+ return {
203
+ "execution_id": execution_id,
204
+ "status": "error",
205
+ "stdout": compile_proc.stdout,
206
+ "stderr": compile_proc.stderr,
207
+ "result": None,
208
+ "plots": [],
209
+ "dataframes": []
210
+ }
211
+
212
+ run_proc = subprocess.run(
213
+ [binary_path],
214
+ capture_output=True, text=True, timeout=self.max_execution_time
215
+ )
216
+ return {
217
+ "execution_id": execution_id,
218
+ "status": "success" if run_proc.returncode == 0 else "error",
219
+ "stdout": run_proc.stdout,
220
+ "stderr": run_proc.stderr,
221
+ "result": None,
222
+ "plots": [],
223
+ "dataframes": []
224
+ }
225
+ except Exception as e:
226
+ return {
227
+ "execution_id": execution_id,
228
+ "status": "error",
229
+ "stdout": "",
230
+ "stderr": str(e),
231
+ "result": None,
232
+ "plots": [],
233
+ "dataframes": []
234
+ }
235
+
236
+ def _execute_java(self, code: str, execution_id: str) -> dict:
237
+ temp_dir = tempfile.mkdtemp()
238
+ source_path = os.path.join(temp_dir, "Main.java")
239
+
240
+ try:
241
+ with open(source_path, "w") as f:
242
+ f.write(code)
243
+
244
+ compile_proc = subprocess.run(
245
+ ["javac", source_path],
246
+ capture_output=True, text=True, timeout=self.max_execution_time
247
+ )
248
+ if compile_proc.returncode != 0:
249
+ return {
250
+ "execution_id": execution_id,
251
+ "status": "error",
252
+ "stdout": compile_proc.stdout,
253
+ "stderr": compile_proc.stderr,
254
+ "result": None,
255
+ "plots": [],
256
+ "dataframes": []
257
+ }
258
+
259
+ run_proc = subprocess.run(
260
+ ["java", "-cp", temp_dir, "Main"],
261
+ capture_output=True, text=True, timeout=self.max_execution_time
262
+ )
263
+ return {
264
+ "execution_id": execution_id,
265
+ "status": "success" if run_proc.returncode == 0 else "error",
266
+ "stdout": run_proc.stdout,
267
+ "stderr": run_proc.stderr,
268
+ "result": None,
269
+ "plots": [],
270
+ "dataframes": []
271
+ }
272
+ except Exception as e:
273
+ return {
274
+ "execution_id": execution_id,
275
+ "status": "error",
276
+ "stdout": "",
277
+ "stderr": str(e),
278
+ "result": None,
279
+ "plots": [],
280
+ "dataframes": []
281
+ }
explore_metadata.ipynb ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 9,
6
+ "id": "a600d7fc",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import json \n",
11
+ "with open('metadata.jsonl', 'r') as f: \n",
12
+ " json_list = list(f)\n",
13
+ "\n",
14
+ "json_QA = []\n",
15
+ "for json_str in json_list: \n",
16
+ " json_data = json.loads(json_str)\n",
17
+ " json_QA.append(json_data)"
18
+ ]
19
+ },
20
+ {
21
+ "cell_type": "code",
22
+ "execution_count": 10,
23
+ "id": "fa5d8eb8",
24
+ "metadata": {},
25
+ "outputs": [
26
+ {
27
+ "name": "stdout",
28
+ "output_type": "stream",
29
+ "text": [
30
+ "==================================================\n",
31
+ "Task ID: d1af70ea-a9a4-421a-b9cc-94b5e02f1788\n",
32
+ "Question: As of the 2020 census, what was the population difference between the largest county seat and smallest county seat, by land area of the county seat, in Washington state? For population figures, please use the official data from data.census.gov. Please report the integer difference.\n",
33
+ "Level: 2\n",
34
+ "Final Answer: 736455\n",
35
+ "Annotator Metadata: \n",
36
+ " β”œβ”€β”€ Steps: \n",
37
+ " β”‚ β”œβ”€β”€ Step 1: Using a web browser, access a search engine and conduct a search, \"Washington cities by area\"\n",
38
+ " β”‚ β”œβ”€β”€ Step 2: Navigate to the second search result, https://en.wikipedia.org/wiki/List_of_municipalities_in_Washington\n",
39
+ " β”‚ β”œβ”€β”€ Step 3: Evaluate the page contents, finding the largest and smallest county seats by land area, Seattle and Cathlamet\n",
40
+ " β”‚ β”œβ”€β”€ Step 4: Using a web browser, navigate to https://data.census.gov/\n",
41
+ " β”‚ β”œβ”€β”€ Step 5: Using the website's search area, conduct a search, Seattle, Washington\n",
42
+ " β”‚ β”œβ”€β”€ Step 6: Record the reported 2020 Decennial Census population of Seattle, Washington, 737,015\n",
43
+ " β”‚ β”œβ”€β”€ Step 7: Using the website's search area, conduct a search, Cathlamet, Washington\n",
44
+ " β”‚ β”œβ”€β”€ Step 8: Record the reported 2020 Decennial Census population of Cathlamet, Washington, 560\n",
45
+ " β”‚ β”œβ”€β”€ Step 9: Using a calculator, find the difference in populations,\n",
46
+ " β”‚ β”œβ”€β”€ \n",
47
+ " β”‚ β”œβ”€β”€ 737,015 - 560\n",
48
+ " β”‚ β”œβ”€β”€ 736,455\n",
49
+ " β”‚ β”œβ”€β”€ Step 10: Report the correct answer to my user in the requested format, \"736,455\"\n",
50
+ " β”œβ”€β”€ Number of steps: 10\n",
51
+ " β”œβ”€β”€ How long did this take?: 5 minutes\n",
52
+ " β”œβ”€β”€ Tools:\n",
53
+ " β”‚ β”œβ”€β”€ 1. A web browser\n",
54
+ " β”‚ β”œβ”€β”€ 2. A search engine\n",
55
+ " β”‚ β”œβ”€β”€ 3. A calculator\n",
56
+ " └── Number of tools: 3\n",
57
+ "==================================================\n"
58
+ ]
59
+ }
60
+ ],
61
+ "source": [
62
+ "import random\n",
63
+ "random_samples = random.sample(json_QA, 1)\n",
64
+ "for sample in random_samples:\n",
65
+ " print(\"=\" * 50)\n",
66
+ " print(f\"Task ID: {sample['task_id']}\")\n",
67
+ " print(f\"Question: {sample['Question']}\")\n",
68
+ " print(f\"Level: {sample['Level']}\")\n",
69
+ " print(f\"Final Answer: {sample['Final answer']}\")\n",
70
+ " print(f\"Annotator Metadata: \")\n",
71
+ " print(f\" β”œβ”€β”€ Steps: \")\n",
72
+ " for step in sample['Annotator Metadata']['Steps'].split('\\n'):\n",
73
+ " print(f\" β”‚ β”œβ”€β”€ {step}\")\n",
74
+ " print(f\" β”œβ”€β”€ Number of steps: {sample['Annotator Metadata']['Number of steps']}\")\n",
75
+ " print(f\" β”œβ”€β”€ How long did this take?: {sample['Annotator Metadata']['How long did this take?']}\")\n",
76
+ " print(f\" β”œβ”€β”€ Tools:\")\n",
77
+ " for tool in sample['Annotator Metadata']['Tools'].split('\\n'):\n",
78
+ " print(f\" β”‚ β”œβ”€β”€ {tool}\")\n",
79
+ " print(f\" └── Number of tools: {sample['Annotator Metadata']['Number of tools']}\")\n",
80
+ "print(\"=\" * 50)"
81
+ ]
82
+ },
83
+ {
84
+ "cell_type": "code",
85
+ "execution_count": 11,
86
+ "id": "05076516",
87
+ "metadata": {},
88
+ "outputs": [],
89
+ "source": [
90
+ "import os\n",
91
+ "from dotenv import load_dotenv\n",
92
+ "from langchain_huggingface import HuggingFaceEmbeddings\n",
93
+ "from langchain_community.vectorstores import SupabaseVectorStore\n",
94
+ "from supabase.client import Client, create_client\n",
95
+ "\n",
96
+ "\n",
97
+ "load_dotenv()\n",
98
+ "embeddings = HuggingFaceEmbeddings(model_name=\"sentence-transformers/all-mpnet-base-v2\") # dim=768\n",
99
+ "\n",
100
+ "supabase_url = os.environ.get(\"SUPABASE_URL\")\n",
101
+ "supabase_key = os.environ.get(\"SUPABASE_SERVICE_ROLE_KEY\")\n",
102
+ "supabase: Client = create_client(supabase_url, supabase_key)"
103
+ ]
104
+ },
105
+ {
106
+ "cell_type": "code",
107
+ "execution_count": 20,
108
+ "id": "aa1402e3",
109
+ "metadata": {},
110
+ "outputs": [],
111
+ "source": [
112
+ "from langchain.schema import Document\n",
113
+ "docs = []\n",
114
+ "cnt = 0 \n",
115
+ "for sample in json_QA:\n",
116
+ " content = f\"Question : {sample['Question']}\\n\\nFinal answer : {sample['Final answer']}\"\n",
117
+ " doc = {\n",
118
+ " \"id\" : cnt,\n",
119
+ " \"content\" : content,\n",
120
+ " \"metadata\" : {\n",
121
+ " \"source\" : sample['task_id']\n",
122
+ " },\n",
123
+ " \"embedding\" : embeddings.embed_query(content),\n",
124
+ " }\n",
125
+ " docs.append(doc)\n",
126
+ " cnt += 1\n",
127
+ "\n",
128
+ "# upload the documents to the vector database\n",
129
+ "try:\n",
130
+ " response = (\n",
131
+ " supabase.table(\"documents2\")\n",
132
+ " .insert(docs)\n",
133
+ " .execute()\n",
134
+ " )\n",
135
+ "except Exception as exception:\n",
136
+ " print(\"Error inserting data into Supabase:\", exception)\n",
137
+ "\n",
138
+ "# # Save the documents (a list of dict) into a csv file, and manually upload it to Supabase\n",
139
+ "# import pandas as pd\n",
140
+ "# df = pd.DataFrame(docs)\n",
141
+ "# df.to_csv('supabase_docs.csv',index=False)"
142
+ ]
143
+ },
144
+ {
145
+ "cell_type": "code",
146
+ "execution_count": 41,
147
+ "id": "9aa7eb5e",
148
+ "metadata": {},
149
+ "outputs": [],
150
+ "source": [
151
+ "# add items to vector database\n",
152
+ "vector_store = SupabaseVectorStore(\n",
153
+ " client=supabase,\n",
154
+ " embedding= embeddings,\n",
155
+ " table_name=\"documents2\",\n",
156
+ " query_name=\"match_documents_2\",\n",
157
+ ")\n",
158
+ "retriever = vector_store.as_retriever()"
159
+ ]
160
+ },
161
+ {
162
+ "cell_type": "code",
163
+ "execution_count": 42,
164
+ "id": "9eecafd1",
165
+ "metadata": {},
166
+ "outputs": [],
167
+ "source": [
168
+ "query = \"On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?\"\n",
169
+ "# matched_docs = vector_store.similarity_search(query, k=2)\n",
170
+ "docs = retriever.invoke(query)"
171
+ ]
172
+ },
173
+ {
174
+ "cell_type": "code",
175
+ "execution_count": 43,
176
+ "id": "ff917840",
177
+ "metadata": {},
178
+ "outputs": [
179
+ {
180
+ "data": {
181
+ "text/plain": [
182
+ "Document(metadata={'source': '840bfca7-4f7b-481a-8794-c560c340185d'}, page_content='Question : On June 6, 2023, an article by Carolyn Collins Petersen was published in Universe Today. This article mentions a team that produced a paper about their observations, linked at the bottom of the article. Find this paper. Under what NASA award number was the work performed by R. G. Arendt supported by?\\n\\nFinal answer : 80GSFC21M0002')"
183
+ ]
184
+ },
185
+ "execution_count": 43,
186
+ "metadata": {},
187
+ "output_type": "execute_result"
188
+ }
189
+ ],
190
+ "source": [
191
+ "docs[0]"
192
+ ]
193
+ },
194
+ {
195
+ "cell_type": "code",
196
+ "execution_count": 44,
197
+ "id": "01c8f337",
198
+ "metadata": {},
199
+ "outputs": [
200
+ {
201
+ "name": "stdout",
202
+ "output_type": "stream",
203
+ "text": [
204
+ "List of tools used in all samples:\n",
205
+ "Total number of tools used: 83\n",
206
+ " β”œβ”€β”€ web browser: 107\n",
207
+ " β”œβ”€β”€ image recognition tools (to identify and parse a figure with three axes): 1\n",
208
+ " β”œβ”€β”€ search engine: 101\n",
209
+ " β”œβ”€β”€ calculator: 34\n",
210
+ " β”œβ”€β”€ unlambda compiler (optional): 1\n",
211
+ " β”œβ”€β”€ a web browser.: 2\n",
212
+ " β”œβ”€β”€ a search engine.: 2\n",
213
+ " β”œβ”€β”€ a calculator.: 1\n",
214
+ " β”œβ”€β”€ microsoft excel: 5\n",
215
+ " β”œβ”€β”€ google search: 1\n",
216
+ " β”œβ”€β”€ ne: 9\n",
217
+ " β”œβ”€β”€ pdf access: 7\n",
218
+ " β”œβ”€β”€ file handling: 2\n",
219
+ " β”œβ”€β”€ python: 3\n",
220
+ " β”œβ”€β”€ image recognition tools: 12\n",
221
+ " β”œβ”€β”€ jsonld file access: 1\n",
222
+ " β”œβ”€β”€ video parsing: 1\n",
223
+ " β”œβ”€β”€ python compiler: 1\n",
224
+ " β”œβ”€β”€ video recognition tools: 3\n",
225
+ " β”œβ”€β”€ pdf viewer: 7\n",
226
+ " β”œβ”€β”€ microsoft excel / google sheets: 3\n",
227
+ " β”œβ”€β”€ word document access: 1\n",
228
+ " β”œβ”€β”€ tool to extract text from images: 1\n",
229
+ " β”œβ”€β”€ a word reversal tool / script: 1\n",
230
+ " β”œβ”€β”€ counter: 1\n",
231
+ " β”œβ”€β”€ excel: 3\n",
232
+ " β”œβ”€β”€ image recognition: 5\n",
233
+ " β”œβ”€β”€ color recognition: 3\n",
234
+ " β”œβ”€β”€ excel file access: 3\n",
235
+ " β”œβ”€β”€ xml file access: 1\n",
236
+ " β”œβ”€β”€ access to the internet archive, web.archive.org: 1\n",
237
+ " β”œβ”€β”€ text processing/diff tool: 1\n",
238
+ " β”œβ”€β”€ gif parsing tools: 1\n",
239
+ " β”œβ”€β”€ a web browser: 7\n",
240
+ " β”œβ”€β”€ a search engine: 7\n",
241
+ " β”œβ”€β”€ a speech-to-text tool: 2\n",
242
+ " β”œβ”€β”€ code/data analysis tools: 1\n",
243
+ " β”œβ”€β”€ audio capability: 2\n",
244
+ " β”œβ”€β”€ pdf reader: 1\n",
245
+ " β”œβ”€β”€ markdown: 1\n",
246
+ " β”œβ”€β”€ a calculator: 5\n",
247
+ " β”œβ”€β”€ access to wikipedia: 3\n",
248
+ " β”œβ”€β”€ image recognition/ocr: 3\n",
249
+ " β”œβ”€β”€ google translate access: 1\n",
250
+ " β”œβ”€β”€ ocr: 4\n",
251
+ " β”œβ”€β”€ bass note data: 1\n",
252
+ " β”œβ”€β”€ text editor: 1\n",
253
+ " β”œβ”€β”€ xlsx file access: 1\n",
254
+ " β”œβ”€β”€ powerpoint viewer: 1\n",
255
+ " β”œβ”€β”€ csv file access: 1\n",
256
+ " β”œβ”€β”€ calculator (or use excel): 1\n",
257
+ " β”œβ”€β”€ computer algebra system: 1\n",
258
+ " β”œβ”€β”€ video processing software: 1\n",
259
+ " β”œβ”€β”€ audio processing software: 1\n",
260
+ " β”œβ”€β”€ computer vision: 1\n",
261
+ " β”œβ”€β”€ google maps: 1\n",
262
+ " β”œβ”€β”€ access to excel files: 1\n",
263
+ " β”œβ”€β”€ calculator (or ability to count): 1\n",
264
+ " β”œβ”€β”€ a file interface: 3\n",
265
+ " β”œβ”€β”€ a python ide: 1\n",
266
+ " β”œβ”€β”€ spreadsheet editor: 1\n",
267
+ " β”œβ”€β”€ tools required: 1\n",
268
+ " β”œβ”€β”€ b browser: 1\n",
269
+ " β”œβ”€β”€ image recognition and processing tools: 1\n",
270
+ " β”œβ”€β”€ computer vision or ocr: 1\n",
271
+ " β”œβ”€β”€ c++ compiler: 1\n",
272
+ " β”œβ”€β”€ access to google maps: 1\n",
273
+ " β”œβ”€β”€ youtube player: 1\n",
274
+ " β”œβ”€β”€ natural language processor: 1\n",
275
+ " β”œβ”€β”€ graph interaction tools: 1\n",
276
+ " β”œβ”€β”€ bablyonian cuniform -> arabic legend: 1\n",
277
+ " β”œβ”€β”€ access to youtube: 1\n",
278
+ " β”œβ”€β”€ image search tools: 1\n",
279
+ " β”œβ”€β”€ calculator or counting function: 1\n",
280
+ " β”œβ”€β”€ a speech-to-text audio processing tool: 1\n",
281
+ " β”œβ”€β”€ access to academic journal websites: 1\n",
282
+ " β”œβ”€β”€ pdf reader/extracter: 1\n",
283
+ " β”œβ”€β”€ rubik's cube model: 1\n",
284
+ " β”œβ”€β”€ wikipedia: 1\n",
285
+ " β”œβ”€β”€ video capability: 1\n",
286
+ " β”œβ”€β”€ image processing tools: 1\n",
287
+ " β”œβ”€β”€ age recognition software: 1\n",
288
+ " β”œβ”€β”€ youtube: 1\n"
289
+ ]
290
+ }
291
+ ],
292
+ "source": [
293
+ "# list of the tools used in all the samples\n",
294
+ "from collections import Counter, OrderedDict\n",
295
+ "\n",
296
+ "tools = []\n",
297
+ "for sample in json_QA:\n",
298
+ " for tool in sample['Annotator Metadata']['Tools'].split('\\n'):\n",
299
+ " tool = tool[2:].strip().lower()\n",
300
+ " if tool.startswith(\"(\"):\n",
301
+ " tool = tool[11:].strip()\n",
302
+ " tools.append(tool)\n",
303
+ "tools_counter = OrderedDict(Counter(tools))\n",
304
+ "print(\"List of tools used in all samples:\")\n",
305
+ "print(\"Total number of tools used:\", len(tools_counter))\n",
306
+ "for tool, count in tools_counter.items():\n",
307
+ " print(f\" β”œβ”€β”€ {tool}: {count}\")"
308
+ ]
309
+ }
310
+ ],
311
+ "metadata": {
312
+ "kernelspec": {
313
+ "display_name": "env",
314
+ "language": "python",
315
+ "name": "python3"
316
+ },
317
+ "language_info": {
318
+ "codemirror_mode": {
319
+ "name": "ipython",
320
+ "version": 3
321
+ },
322
+ "file_extension": ".py",
323
+ "mimetype": "text/x-python",
324
+ "name": "python",
325
+ "nbconvert_exporter": "python",
326
+ "pygments_lexer": "ipython3",
327
+ "version": "3.11.9"
328
+ }
329
+ },
330
+ "nbformat": 4,
331
+ "nbformat_minor": 5
332
+ }
image_processing.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ import uuid
5
+ from PIL import Image
6
+
7
+ # Helper functions for image processing
8
+ def encode_image(image_path: str) -> str:
9
+ """Convert an image file to base64 string."""
10
+ with open(image_path, "rb") as image_file:
11
+ return base64.b64encode(image_file.read()).decode("utf-8")
12
+
13
+
14
+ def decode_image(base64_string: str) -> Image.Image:
15
+ """Convert a base64 string to a PIL Image."""
16
+ image_data = base64.b64decode(base64_string)
17
+ return Image.open(io.BytesIO(image_data))
18
+
19
+
20
+ def save_image(image: Image.Image, directory: str = "image_outputs") -> str:
21
+ """Save a PIL Image to disk and return the path."""
22
+ os.makedirs(directory, exist_ok=True)
23
+ image_id = str(uuid.uuid4())
24
+ image_path = os.path.join(directory, f"{image_id}.png")
25
+ image.save(image_path)
26
+ return image_path
requirements.txt CHANGED
@@ -16,5 +16,5 @@ pymupdf
16
  wikipedia
17
  pgvector
18
  python-dotenv
19
- jupyter
20
- ipython
 
16
  wikipedia
17
  pgvector
18
  python-dotenv
19
+ pytesseract
20
+ matplotlib