ShawnIL/GAIA_Agent_Evaluation
0
1import os2from dotenv import load_dotenv3from typing import List, Dict, Any, Optional4import tempfile5import re6import json7import requests8from urllib.parse import urlparse9import pytesseract10from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter11import cmath12import pandas as pd13import uuid14import numpy as np15from code_interpreter import CodeInterpreter16 17interpreter_instance = CodeInterpreter()18 19from image_processing import *20 21"""Langraph"""22from langgraph.graph import START, StateGraph, MessagesState23from langchain_community.tools.tavily_search import TavilySearchResults24from langchain_openai import ChatOpenAI25from langchain_community.document_loaders import WikipediaLoader26from langchain_community.document_loaders import ArxivLoader27from langgraph.prebuilt import ToolNode, tools_condition28from langchain_google_genai import ChatGoogleGenerativeAI29from langchain_groq import ChatGroq30from langchain_huggingface import (31 ChatHuggingFace,32 HuggingFaceEndpoint,33 HuggingFaceEmbeddings,34)35from langchain_community.vectorstores import SupabaseVectorStore36from langchain_core.messages import SystemMessage, HumanMessage37from langchain_core.tools import tool, create_retriever_tool38from supabase.client import Client, create_client39 40"""Langfuse (optional)"""41try:42 from langfuse import Langfuse43 from langfuse.callback import CallbackHandler as LangfuseCallbackHandler44except Exception:45 Langfuse = None46 LangfuseCallbackHandler = None47 48load_dotenv()49 50 51# Optional Langfuse callback handler52def get_langfuse_handler():53 """Return a Langfuse callback handler if env vars are available; else None.54 Expected envs:55 - LANGFUSE_HOST56 - LANGFUSE_PUBLIC_KEY57 - LANGFUSE_SECRET_KEY58 """59 if Langfuse is None or LangfuseCallbackHandler is None:60 return None61 host = os.getenv("LANGFUSE_HOST")62 public = os.getenv("LANGFUSE_PUBLIC_KEY")63 secret = os.getenv("LANGFUSE_SECRET_KEY")64 try:65 # Langfuse SDK can read envs; pass explicitly for clarity66 lf = Langfuse(host=host, public_key=public, secret_key=secret)67 return LangfuseCallbackHandler(lf)68 except Exception:69 return None70 71 72### =============== BROWSER TOOLS =============== ###73 74 75@tool76def wiki_search(query: str) -> str:77 """Search Wikipedia for a query and return maximum 2 results.78 Args:79 query: The search query."""80 search_docs = WikipediaLoader(query=query, load_max_docs=2).load()81 formatted_search_docs = "\n\n---\n\n".join(82 [83 f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'84 for doc in search_docs85 ]86 )87 return {"wiki_results": formatted_search_docs}88 89 90@tool91def web_search(query: str) -> str:92 """Search Tavily for a query and return maximum 3 results.93 Args:94 query: The search query."""95 search_docs = TavilySearchResults(max_results=3).invoke(query)96 formatted_search_docs = "\n\n---\n\n".join(97 [98 f'<Document source="{doc.get("url", "")}" title="{doc.get("title", "")}"/>\n{doc.get("content", "")}\n</Document>'99 for doc in search_docs100 ]101 )102 return {"web_results": formatted_search_docs}103 104 105@tool106def arxiv_search(query: str) -> str:107 """Search Arxiv for a query and return maximum 3 result.108 Args:109 query: The search query."""110 search_docs = ArxivLoader(query=query, load_max_docs=3).load()111 formatted_search_docs = "\n\n---\n\n".join(112 [113 f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'114 for doc in search_docs115 ]116 )117 return {"arxiv_results": formatted_search_docs}118 119 120### =============== CODE INTERPRETER TOOLS =============== ###121 122 123@tool124def execute_code_multilang(code: str, language: str = "python") -> str:125 """Execute code in multiple languages (Python, Bash, SQL, C, Java) and return results.126 Args:127 code (str): The source code to execute.128 language (str): The language of the code. Supported: "python", "bash", "sql", "c", "java".129 Returns:130 A string summarizing the execution results (stdout, stderr, errors, plots, dataframes if any).131 """132 supported_languages = ["python", "bash", "sql", "c", "java"]133 language = language.lower()134 135 if language not in supported_languages:136 return f"❌ Unsupported language: {language}. Supported languages are: {', '.join(supported_languages)}"137 138 result = interpreter_instance.execute_code(code, language=language)139 140 response = []141 142 if result["status"] == "success":143 response.append(f"✅ Code executed successfully in **{language.upper()}**")144 145 if result.get("stdout"):146 response.append(147 "\n**Standard Output:**\n```\n" + result["stdout"].strip() + "\n```"148 )149 150 if result.get("stderr"):151 response.append(152 "\n**Standard Error (if any):**\n```\n"153 + result["stderr"].strip()154 + "\n```"155 )156 157 if result.get("result") is not None:158 response.append(159 "\n**Execution Result:**\n```\n"160 + str(result["result"]).strip()161 + "\n```"162 )163 164 if result.get("dataframes"):165 for df_info in result["dataframes"]:166 response.append(167 f"\n**DataFrame `{df_info['name']}` (Shape: {df_info['shape']})**"168 )169 df_preview = pd.DataFrame(df_info["head"])170 response.append("First 5 rows:\n```\n" + str(df_preview) + "\n```")171 172 if result.get("plots"):173 response.append(174 f"\n**Generated {len(result['plots'])} plot(s)** (Image data returned separately)"175 )176 177 else:178 response.append(f"❌ Code execution failed in **{language.upper()}**")179 if result.get("stderr"):180 response.append(181 "\n**Error Log:**\n```\n" + result["stderr"].strip() + "\n```"182 )183 184 return "\n".join(response)185 186 187### =============== MATHEMATICAL TOOLS =============== ###188 189 190@tool191def multiply(a: float, b: float) -> float:192 """193 Multiplies two numbers.194 Args:195 a (float): the first number196 b (float): the second number197 """198 return a * b199 200 201@tool202def add(a: float, b: float) -> float:203 """204 Adds two numbers.205 Args:206 a (float): the first number207 b (float): the second number208 """209 return a + b210 211 212@tool213def subtract(a: float, b: float) -> int:214 """215 Subtracts two numbers.216 Args:217 a (float): the first number218 b (float): the second number219 """220 return a - b221 222 223@tool224def divide(a: float, b: float) -> float:225 """226 Divides two numbers.227 Args:228 a (float): the first float number229 b (float): the second float number230 """231 if b == 0:232 raise ValueError("Cannot divided by zero.")233 return a / b234 235 236@tool237def modulus(a: int, b: int) -> int:238 """239 Get the modulus of two numbers.240 Args:241 a (int): the first number242 b (int): the second number243 """244 return a % b245 246 247@tool248def power(a: float, b: float) -> float:249 """250 Get the power of two numbers.251 Args:252 a (float): the first number253 b (float): the second number254 """255 return a**b256 257 258@tool259def square_root(a: float) -> float | complex:260 """261 Get the square root of a number.262 Args:263 a (float): the number to get the square root of264 """265 if a >= 0:266 return a**0.5267 return cmath.sqrt(a)268 269 270### =============== DOCUMENT PROCESSING TOOLS =============== ###271 272 273@tool274def save_and_read_file(content: str, filename: Optional[str] = None) -> str:275 """276 Save content to a file and return the path.277 Args:278 content (str): the content to save to the file279 filename (str, optional): the name of the file. If not provided, a random name file will be created.280 """281 temp_dir = tempfile.gettempdir()282 if filename is None:283 temp_file = tempfile.NamedTemporaryFile(delete=False, dir=temp_dir)284 filepath = temp_file.name285 else:286 filepath = os.path.join(temp_dir, filename)287 288 with open(filepath, "w") as f:289 f.write(content)290 291 return f"File saved to {filepath}. You can read this file to process its contents."292 293 294@tool295def download_file_from_url(url: str, filename: Optional[str] = None) -> str:296 """297 Download a file from a URL and save it to a temporary location.298 Args:299 url (str): the URL of the file to download.300 filename (str, optional): the name of the file. If not provided, a random name file will be created.301 """302 try:303 # Parse URL to get filename if not provided304 if not filename:305 path = urlparse(url).path306 filename = os.path.basename(path)307 if not filename:308 filename = f"downloaded_{uuid.uuid4().hex[:8]}"309 310 # Create temporary file311 temp_dir = tempfile.gettempdir()312 filepath = os.path.join(temp_dir, filename)313 314 # Download the file315 response = requests.get(url, stream=True)316 response.raise_for_status()317 318 # Save the file319 with open(filepath, "wb") as f:320 for chunk in response.iter_content(chunk_size=8192):321 f.write(chunk)322 323 return f"File downloaded to {filepath}. You can read this file to process its contents."324 except Exception as e:325 return f"Error downloading file: {str(e)}"326 327 328@tool329def extract_text_from_image(image_path: str) -> str:330 """331 Extract text from an image using OCR library pytesseract (if available).332 Args:333 image_path (str): the path to the image file.334 """335 try:336 # Open the image337 image = Image.open(image_path)338 339 # Extract text from the image340 text = pytesseract.image_to_string(image)341 342 return f"Extracted text from image:\n\n{text}"343 except Exception as e:344 return f"Error extracting text from image: {str(e)}"345 346 347@tool348def analyze_csv_file(file_path: str, query: str) -> str:349 """350 Analyze a CSV file using pandas and answer a question about it.351 Args:352 file_path (str): the path to the CSV file.353 query (str): Question about the data354 """355 try:356 # Read the CSV file357 df = pd.read_csv(file_path)358 359 # Run various analyses based on the query360 result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"361 result += f"Columns: {', '.join(df.columns)}\n\n"362 363 # Add summary statistics364 result += "Summary statistics:\n"365 result += str(df.describe())366 367 return result368 369 except Exception as e:370 return f"Error analyzing CSV file: {str(e)}"371 372 373@tool374def analyze_excel_file(file_path: str, query: str) -> str:375 """376 Analyze an Excel file using pandas and answer a question about it.377 Args:378 file_path (str): the path to the Excel file.379 query (str): Question about the data380 """381 try:382 # Read the Excel file383 df = pd.read_excel(file_path)384 385 # Run various analyses based on the query386 result = (387 f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"388 )389 result += f"Columns: {', '.join(df.columns)}\n\n"390 391 # Add summary statistics392 result += "Summary statistics:\n"393 result += str(df.describe())394 395 return result396 397 except Exception as e:398 return f"Error analyzing Excel file: {str(e)}"399 400 401### ============== IMAGE PROCESSING AND GENERATION TOOLS =============== ###402 403 404@tool405def analyze_image(image_base64: str) -> Dict[str, Any]:406 """407 Analyze basic properties of an image (size, mode, color analysis, thumbnail preview).408 Args:409 image_base64 (str): Base64 encoded image string410 Returns:411 Dictionary with analysis result412 """413 try:414 img = decode_image(image_base64)415 width, height = img.size416 mode = img.mode417 418 if mode in ("RGB", "RGBA"):419 arr = np.array(img)420 avg_colors = arr.mean(axis=(0, 1))421 dominant = ["Red", "Green", "Blue"][np.argmax(avg_colors[:3])]422 brightness = avg_colors.mean()423 color_analysis = {424 "average_rgb": avg_colors.tolist(),425 "brightness": brightness,426 "dominant_color": dominant,427 }428 else:429 color_analysis = {"note": f"No color analysis for mode {mode}"}430 431 thumbnail = img.copy()432 thumbnail.thumbnail((100, 100))433 thumb_path = save_image(thumbnail, "thumbnails")434 thumbnail_base64 = encode_image(thumb_path)435 436 return {437 "dimensions": (width, height),438 "mode": mode,439 "color_analysis": color_analysis,440 "thumbnail": thumbnail_base64,441 }442 except Exception as e:443 return {"error": str(e)}444 445 446@tool447def transform_image(448 image_base64: str, operation: str, params: Optional[Dict[str, Any]] = None449) -> Dict[str, Any]:450 """451 Apply transformations: resize, rotate, crop, flip, brightness, contrast, blur, sharpen, grayscale.452 Args:453 image_base64 (str): Base64 encoded input image454 operation (str): Transformation operation455 params (Dict[str, Any], optional): Parameters for the operation456 Returns:457 Dictionary with transformed image (base64)458 """459 try:460 img = decode_image(image_base64)461 params = params or {}462 463 if operation == "resize":464 img = img.resize(465 (466 params.get("width", img.width // 2),467 params.get("height", img.height // 2),468 )469 )470 elif operation == "rotate":471 img = img.rotate(params.get("angle", 90), expand=True)472 elif operation == "crop":473 img = img.crop(474 (475 params.get("left", 0),476 params.get("top", 0),477 params.get("right", img.width),478 params.get("bottom", img.height),479 )480 )481 elif operation == "flip":482 if params.get("direction", "horizontal") == "horizontal":483 img = img.transpose(Image.FLIP_LEFT_RIGHT)484 else:485 img = img.transpose(Image.FLIP_TOP_BOTTOM)486 elif operation == "adjust_brightness":487 img = ImageEnhance.Brightness(img).enhance(params.get("factor", 1.5))488 elif operation == "adjust_contrast":489 img = ImageEnhance.Contrast(img).enhance(params.get("factor", 1.5))490 elif operation == "blur":491 img = img.filter(ImageFilter.GaussianBlur(params.get("radius", 2)))492 elif operation == "sharpen":493 img = img.filter(ImageFilter.SHARPEN)494 elif operation == "grayscale":495 img = img.convert("L")496 else:497 return {"error": f"Unknown operation: {operation}"}498 499 result_path = save_image(img)500 result_base64 = encode_image(result_path)501 return {"transformed_image": result_base64}502 503 except Exception as e:504 return {"error": str(e)}505 506 507@tool508def draw_on_image(509 image_base64: str, drawing_type: str, params: Dict[str, Any]510) -> Dict[str, Any]:511 """512 Draw shapes (rectangle, circle, line) or text onto an image.513 Args:514 image_base64 (str): Base64 encoded input image515 drawing_type (str): Drawing type516 params (Dict[str, Any]): Drawing parameters517 Returns:518 Dictionary with result image (base64)519 """520 try:521 img = decode_image(image_base64)522 draw = ImageDraw.Draw(img)523 color = params.get("color", "red")524 525 if drawing_type == "rectangle":526 draw.rectangle(527 [params["left"], params["top"], params["right"], params["bottom"]],528 outline=color,529 width=params.get("width", 2),530 )531 elif drawing_type == "circle":532 x, y, r = params["x"], params["y"], params["radius"]533 draw.ellipse(534 (x - r, y - r, x + r, y + r),535 outline=color,536 width=params.get("width", 2),537 )538 elif drawing_type == "line":539 draw.line(540 (541 params["start_x"],542 params["start_y"],543 params["end_x"],544 params["end_y"],545 ),546 fill=color,547 width=params.get("width", 2),548 )549 elif drawing_type == "text":550 font_size = params.get("font_size", 20)551 try:552 font = ImageFont.truetype("arial.ttf", font_size)553 except IOError:554 font = ImageFont.load_default()555 draw.text(556 (params["x"], params["y"]),557 params.get("text", "Text"),558 fill=color,559 font=font,560 )561 else:562 return {"error": f"Unknown drawing type: {drawing_type}"}563 564 result_path = save_image(img)565 result_base64 = encode_image(result_path)566 return {"result_image": result_base64}567 568 except Exception as e:569 return {"error": str(e)}570 571 572@tool573def generate_simple_image(574 image_type: str,575 width: int = 500,576 height: int = 500,577 params: Optional[Dict[str, Any]] = None,578) -> Dict[str, Any]:579 """580 Generate a simple image (gradient, noise, pattern, chart).581 Args:582 image_type (str): Type of image583 width (int), height (int)584 params (Dict[str, Any], optional): Specific parameters585 Returns:586 Dictionary with generated image (base64)587 """588 try:589 params = params or {}590 591 if image_type == "gradient":592 direction = params.get("direction", "horizontal")593 start_color = params.get("start_color", (255, 0, 0))594 end_color = params.get("end_color", (0, 0, 255))595 596 img = Image.new("RGB", (width, height))597 draw = ImageDraw.Draw(img)598 599 if direction == "horizontal":600 for x in range(width):601 r = int(602 start_color[0] + (end_color[0] - start_color[0]) * x / width603 )604 g = int(605 start_color[1] + (end_color[1] - start_color[1]) * x / width606 )607 b = int(608 start_color[2] + (end_color[2] - start_color[2]) * x / width609 )610 draw.line([(x, 0), (x, height)], fill=(r, g, b))611 else:612 for y in range(height):613 r = int(614 start_color[0] + (end_color[0] - start_color[0]) * y / height615 )616 g = int(617 start_color[1] + (end_color[1] - start_color[1]) * y / height618 )619 b = int(620 start_color[2] + (end_color[2] - start_color[2]) * y / height621 )622 draw.line([(0, y), (width, y)], fill=(r, g, b))623 624 elif image_type == "noise":625 noise_array = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8)626 img = Image.fromarray(noise_array, "RGB")627 628 else:629 return {"error": f"Unsupported image_type {image_type}"}630 631 result_path = save_image(img)632 result_base64 = encode_image(result_path)633 return {"generated_image": result_base64}634 635 except Exception as e:636 return {"error": str(e)}637 638 639@tool640def combine_images(641 images_base64: List[str], operation: str, params: Optional[Dict[str, Any]] = None642) -> Dict[str, Any]:643 """644 Combine multiple images (collage, stack, blend).645 Args:646 images_base64 (List[str]): List of base64 images647 operation (str): Combination type648 params (Dict[str, Any], optional)649 Returns:650 Dictionary with combined image (base64)651 """652 try:653 images = [decode_image(b64) for b64 in images_base64]654 params = params or {}655 656 if operation == "stack":657 direction = params.get("direction", "horizontal")658 if direction == "horizontal":659 total_width = sum(img.width for img in images)660 max_height = max(img.height for img in images)661 new_img = Image.new("RGB", (total_width, max_height))662 x = 0663 for img in images:664 new_img.paste(img, (x, 0))665 x += img.width666 else:667 max_width = max(img.width for img in images)668 total_height = sum(img.height for img in images)669 new_img = Image.new("RGB", (max_width, total_height))670 y = 0671 for img in images:672 new_img.paste(img, (0, y))673 y += img.height674 else:675 return {"error": f"Unsupported combination operation {operation}"}676 677 result_path = save_image(new_img)678 result_base64 = encode_image(result_path)679 return {"combined_image": result_base64}680 681 except Exception as e:682 return {"error": str(e)}683 684 685# load the system prompt from the file686with open("system_prompt.txt", "r", encoding="utf-8") as f:687 system_prompt = f.read()688print(system_prompt)689 690# System message691sys_msg = SystemMessage(content=system_prompt)692 693# build a retriever694embeddings = HuggingFaceEmbeddings(695 model_name="sentence-transformers/all-mpnet-base-v2"696) # dim=768697supabase: Client = create_client(698 os.environ.get("SUPABASE_URL"), os.environ.get("SUPABASE_SERVICE_ROLE_KEY")699)700vector_store = SupabaseVectorStore(701 client=supabase,702 embedding=embeddings,703 table_name="documents2",704 query_name="match_documents_2",705)706create_retriever_tool = create_retriever_tool(707 retriever=vector_store.as_retriever(),708 name="Question Search",709 description="A tool to retrieve similar questions from a vector store.",710)711 712 713tools = [714 web_search,715 wiki_search,716 arxiv_search,717 multiply,718 add,719 subtract,720 divide,721 modulus,722 power,723 square_root,724 save_and_read_file,725 download_file_from_url,726 extract_text_from_image,727 analyze_csv_file,728 analyze_excel_file,729 execute_code_multilang,730 analyze_image,731 transform_image,732 draw_on_image,733 generate_simple_image,734 combine_images,735]736 737 738# Supabase RPC-based similarity search (compatible with newer supabase-py)739def supabase_similarity_search(query: str, k: int = 3) -> List[Dict[str, Any]]:740 try:741 query_embedding = embeddings.embed_query(query)742 # Call the SQL function via RPC to retrieve similar documents743 resp = supabase.rpc(744 "match_documents_2",745 {"query_embedding": query_embedding, "match_count": k},746 ).execute()747 data = getattr(resp, "data", None)748 if not data:749 return []750 # Ensure list of dicts with at least 'content'751 return data752 except Exception:753 return []754 755 756# Build graph function757def build_graph(provider: str = "groq"):758 """Build the graph"""759 # Optional observability via Langfuse760 lf_handler = get_langfuse_handler()761 if provider == "groq":762 # Groq https://console.groq.com/docs/models763 llm = ChatGroq(764 model="qwen/qwen3-32b",765 temperature=0,766 callbacks=[lf_handler] if lf_handler else None,767 )768 elif provider == "openai":769 # OpenAI GPT models770 llm = ChatOpenAI(771 model="gpt-3.5-turbo",772 temperature=0,773 callbacks=[lf_handler] if lf_handler else None,774 )775 else:776 raise ValueError("Invalid provider. Choose 'groq' or 'openai'.")777 # Bind tools to LLM778 llm_with_tools = llm.bind_tools(tools)779 780 # Node781 def assistant(state: MessagesState):782 """Assistant node"""783 return {"messages": [llm_with_tools.invoke(state["messages"])]}784 785 def retriever(state: MessagesState):786 """Retriever node"""787 # Use Supabase RPC to avoid client/version incompatibilities788 results = supabase_similarity_search(state["messages"][0].content, k=3)789 790 if results: # Check if the list is not empty791 top = results[0]792 content = top.get("content") or top.get("page_content") or ""793 example_msg = HumanMessage(794 content=f"Here I provide a similar question and answer for reference: \n\n{content}",795 )796 return {"messages": [sys_msg] + state["messages"] + [example_msg]}797 else:798 # Handle the case when no similar questions are found799 return {"messages": [sys_msg] + state["messages"]}800 801 builder = StateGraph(MessagesState)802 builder.add_node("retriever", retriever)803 builder.add_node("assistant", assistant)804 builder.add_node("tools", ToolNode(tools))805 builder.add_edge(START, "retriever")806 builder.add_edge("retriever", "assistant")807 builder.add_conditional_edges(808 "assistant",809 tools_condition,810 )811 builder.add_edge("tools", "assistant")812 813 # Compile graph814 return builder.compile()815 816 817### =============== GRAPH VISUALIZATION =============== ###818 819 820def visualize_graph(821 graph, output_format: str = "mermaid", save_path: Optional[str] = None822) -> str:823 """824 Visualize the LangGraph agent graph.825 826 Args:827 graph: The compiled LangGraph graph828 output_format: "mermaid" for text diagram, "png" for image, "ascii" for terminal829 save_path: Optional path to save the visualization830 831 Returns:832 The visualization content (Mermaid code or file path)833 """834 try:835 if output_format == "mermaid":836 # Generate Mermaid diagram code837 mermaid_code = graph.get_graph().draw_mermaid()838 839 if save_path:840 with open(save_path, "w", encoding="utf-8") as f:841 f.write("```mermaid\n")842 f.write(mermaid_code)843 f.write("\n```")844 print(f"✅ Mermaid diagram saved to: {save_path}")845 846 return mermaid_code847 848 elif output_format == "png":849 # Generate PNG image850 try:851 png_data = graph.get_graph().draw_mermaid_png()852 853 if save_path is None:854 save_path = "langgraph_diagram.png"855 856 with open(save_path, "wb") as f:857 f.write(png_data)858 print(f"✅ PNG diagram saved to: {save_path}")859 return save_path860 861 except Exception as e:862 print(f"⚠️ PNG generation failed: {e}")863 print("Falling back to Mermaid text format...")864 return visualize_graph(865 graph,866 "mermaid",867 save_path.replace(".png", ".md") if save_path else None,868 )869 870 elif output_format == "ascii":871 # Print ASCII diagram to console872 graph.get_graph().print_ascii()873 return "ASCII diagram printed to console"874 875 else:876 raise ValueError(877 f"Unknown format: {output_format}. Use 'mermaid', 'png', or 'ascii'"878 )879 880 except Exception as e:881 print(f"❌ Visualization error: {e}")882 return f"Error: {e}"883 884 885def get_graph_info(graph) -> dict:886 """887 Get detailed information about the graph structure.888 889 Args:890 graph: The compiled LangGraph graph891 892 Returns:893 Dictionary with graph information894 """895 graph_obj = graph.get_graph()896 897 info = {898 "nodes": list(graph_obj.nodes.keys()),899 "edges": [],900 "entry_point": None,901 "tool_count": len(tools),902 "tools": [t.name for t in tools],903 }904 905 # Get edges906 for edge in graph_obj.edges:907 info["edges"].append({"from": edge.source, "to": edge.target})908 909 return info910 911 912def print_graph_summary():913 """Print a summary of the agent graph structure."""914 print("\n" + "=" * 60)915 print("🤖 GAIA Agent - LangGraph Structure Summary")916 print("=" * 60)917 918 print("\n📊 NODES:")919 print(" ├── retriever : RAG node - searches similar Q&A from Supabase")920 print(" ├── assistant : LLM node - processes messages with bound tools")921 print(" └── tools : ToolNode - executes tool calls")922 923 print("\n🔗 EDGES:")924 print(" START → retriever → assistant")925 print(" assistant → [tools_condition] → tools OR END")926 print(" tools → assistant")927 928 print("\n🛠️ AVAILABLE TOOLS (21):")929 print(" ├── 🔍 Search: web_search, wiki_search, arxiv_search")930 print(" ├── 🔢 Math: add, subtract, multiply, divide, modulus, power, square_root")931 print(932 " ├── 📁 File: save_and_read_file, download_file_from_url, extract_text_from_image"933 )934 print(" ├── 📊 Data: analyze_csv_file, analyze_excel_file")935 print(" ├── 💻 Code: execute_code_multilang (Python, Bash, SQL, C, Java)")936 print(937 " └── 🖼️ Image: analyze_image, transform_image, draw_on_image, generate_simple_image, combine_images"938 )939 940 print("\n🔑 ENVIRONMENT VARIABLES:")941 print(" ├── GROQ_API_KEY : For Groq LLM (qwen3-32b)")942 print(" ├── TAVILY_API_KEY : For web search tool")943 print(" ├── SUPABASE_URL : For vector database")944 print(" └── SUPABASE_SERVICE_ROLE_KEY : For Supabase authentication")945 946 print("\n" + "=" * 60 + "\n")947 948 949# test950if __name__ == "__main__":951 print("\n" + "=" * 60)952 print("🚀 GAIA Agent - Graph Visualization & Test")953 print("=" * 60 + "\n")954 955 # Build the graph956 print("🔧 Building agent graph...")957 graph = build_graph(provider="groq")958 print("✅ Graph built successfully!\n")959 960 # Print graph summary961 print_graph_summary()962 963 # Generate Mermaid diagram964 print("📊 Generating Mermaid diagram...")965 mermaid_code = visualize_graph(graph, "mermaid", "graph_diagram.md")966 print("\n--- Mermaid Diagram Code ---")967 print(mermaid_code)968 print("--- End of Mermaid Code ---\n")969 970 # Try to generate PNG (may fail without required dependencies)971 print("🖼️ Attempting to generate PNG diagram...")972 visualize_graph(graph, "png", "langgraph_diagram.png")973 974 # Get graph info975 print("\n📋 Graph Information:")976 info = get_graph_info(graph)977 print(f" Nodes: {info['nodes']}")978 print(f" Edges: {info['edges']}")979 print(f" Tool count: {info['tool_count']}")980 print(f" Tools: {info['tools'][:5]}... (and {len(info['tools'])-5} more)")981 982 # Test with a sample question983 print("\n" + "=" * 60)984 print("🧪 Testing with sample question...")985 print("=" * 60 + "\n")986 987 question = "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect?"988 print(f"❓ Question: {question}\n")989 990 messages = [HumanMessage(content=question)]991 result = graph.invoke({"messages": messages})992 993 print("📨 Response messages:")994 for m in result["messages"]:995 m.pretty_print()996 997 print("\n" + "=" * 60)998 print("✅ Test completed!")999 print("=" * 60 + "\n")1000 