anup220799 commited on
Commit
79c593b
·
1 Parent(s): a2a6242

Update app.py

Browse files
agent.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+ from tools.python_interpreter import CodeInterpreter
4
+
5
+ interpreter_instance = CodeInterpreter()
6
+
7
+
8
+ from tools.image import *
9
+
10
+ """Langraph"""
11
+ from langgraph.graph import START, StateGraph, MessagesState
12
+ from langgraph.prebuilt import ToolNode, tools_condition
13
+ from langchain_groq import ChatGroq
14
+ from langchain_huggingface import (
15
+ ChatHuggingFace,
16
+ HuggingFaceEndpoint,
17
+ HuggingFaceEmbeddings,
18
+ )
19
+ from langchain_community.vectorstores import SupabaseVectorStore
20
+ from langchain_core.messages import SystemMessage, HumanMessage
21
+ from langchain.tools.retriever import create_retriever_tool
22
+ from supabase.client import Client, create_client
23
+ # ------- Tools
24
+ from tools.browse import web_search, wiki_search, arxiv_search
25
+ from tools.document_process import save_and_read_file, analyze_csv_file, analyze_excel_file, extract_text_from_image, download_file_from_url
26
+ from tools.image_tools import analyze_image, generate_simple_image , transform_image, draw_on_image, combine_images
27
+ from tools.simple_math import multiply, add, subtract, divide, modulus, power, square_root
28
+ from tools.python_interpreter import execute_code_lang
29
+
30
+ load_dotenv()
31
+
32
+ with open("system_prompt.txt", "r", encoding="utf-8") as f:
33
+ system_prompt = f.read()
34
+ print(system_prompt)
35
+
36
+ # System message
37
+ sys_msg = SystemMessage(content=system_prompt)
38
+
39
+ # build a retriever
40
+ embeddings = HuggingFaceEmbeddings(
41
+ model_name="sentence-transformers/all-mpnet-base-v2",
42
+ ) # dim=768
43
+ supabase: Client = create_client(
44
+ os.environ.get("SUPABASE_URL_HUGGING_FACE"), os.environ.get("SUPABASE_SERVICE_ROLE_HUGGING_FACE")
45
+ )
46
+ vector_store = SupabaseVectorStore(
47
+ client=supabase,
48
+ embedding=embeddings,
49
+ table_name="documents",
50
+ query_name="match_documents_langchain",
51
+ )
52
+ create_retriever_tool = create_retriever_tool(
53
+ retriever=vector_store.as_retriever(),
54
+ name="Question Search",
55
+ description="A tool to retrieve similar questions from a vector store.",
56
+ )
57
+
58
+
59
+ tools = [
60
+ web_search,
61
+ wiki_search,
62
+ arxiv_search,
63
+ multiply,
64
+ add,
65
+ subtract,
66
+ divide,
67
+ modulus,
68
+ power,
69
+ square_root,
70
+ save_and_read_file,
71
+ download_file_from_url,
72
+ extract_text_from_image,
73
+ analyze_csv_file,
74
+ analyze_excel_file,
75
+ execute_code_lang,
76
+ analyze_image,
77
+ transform_image,
78
+ draw_on_image,
79
+ generate_simple_image,
80
+ combine_images,
81
+ ]
82
+
83
+ def build_graph(provider: str = "groq"):
84
+ if provider == "groq":
85
+ # Groq https://console.groq.com/docs/models
86
+ llm = ChatGroq(model="qwen-qwq-32b", temperature=0)
87
+ # llm = ChatGroq(model="deepseek-r1-distill-llama-70b", temperature=0)
88
+ elif provider == "huggingface":
89
+ llm = ChatHuggingFace(
90
+ llm=HuggingFaceEndpoint(
91
+ repo_id="TinyLlama/TinyLlama-1.1B-Chat-v1.0",
92
+ task="text-generation", # for chat‐style use “text-generation”
93
+ max_new_tokens=1024,
94
+ do_sample=False,
95
+ repetition_penalty=1.03,
96
+ temperature=0,
97
+ ),
98
+ verbose=True,
99
+ )
100
+ else:
101
+ raise ValueError("Invalid provider. Choose 'groq' or 'huggingface'.")
102
+
103
+ llm_with_tools = llm.bind_tools(tools)
104
+
105
+ def assistant(state: MessagesState):
106
+ """Assistant Node"""
107
+ return {"messages": [llm_with_tools.invoke(state['messages'])]}
108
+
109
+ def retriever(state: MessagesState):
110
+ """Retriever Node"""
111
+ # Extract the latest message content
112
+ query = state['messages'][-1].content
113
+ similar_question = vector_store.similarity_search(query, k = 2)
114
+ if similar_question:
115
+ example_msg = HumanMessage(
116
+ content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
117
+ )
118
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
119
+ else:
120
+ return {"messages": [sys_msg] + state["messages"]}
121
+
122
+
123
+ builder = StateGraph(MessagesState)
124
+ builder.add_node("retriever", retriever)
125
+ builder.add_node("assistant", assistant)
126
+ builder.add_node("tools", ToolNode(tools))
127
+ builder.add_edge(START, "retriever")
128
+ builder.add_edge("retriever", "assistant")
129
+ builder.add_conditional_edges("assistant", tools_condition)
130
+ builder.add_edge("tools", "assistant")
131
+ return builder.compile()
132
+
133
+ if __name__ == "__main__":
134
+ question = "How many studio albums were published by Mercedes Sosa between 2000 and 2009 (included)? You can use the latest 2022 version of english wikipedia."
135
+ # question = """Q is Examine the video at https://www.youtube.com/watch?v=1htKBjuUWec. What does Teal'c say in response to the question "Isn't that hot?"""
136
+ graph = build_graph(provider="groq")
137
+ messages = [HumanMessage(content=question)]
138
+ messages = graph.invoke({"messages": messages})
139
+ for m in messages["messages"]:
140
+ m.pretty_print()
app.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import gradio as gr
4
+ import requests
5
+ import inspect
6
+ import pandas as pd
7
+ from langchain_core.messages import HumanMessage
8
+ from agent import build_graph
9
+
10
+ # (Keep Constants as is)
11
+ # --- Constants ---
12
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
13
+
14
+ # --- Basic Agent Definition ---
15
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
16
+ class BasicAgent:
17
+ "An Agent Based on LangGraph"
18
+ def __init__(self):
19
+ print("BasicAgent initialized.")
20
+ self.graph = build_graph()
21
+ def __call__(self, question: str) -> str:
22
+ print(f"Agent received question (first 50 chars): {question[:50]}...")
23
+ messages = [HumanMessage(content=question)]
24
+ messages = self.graph.invoke({"messages": messages})
25
+ answer = messages["messages"][-1].content
26
+ return answer[14:]
27
+
28
+ def run_and_submit_all( profile: gr.OAuthProfile | None):
29
+ """
30
+ Fetches all questions, runs the BasicAgent on them, submits all answers,
31
+ and displays the results.
32
+ """
33
+ # --- Determine HF Space Runtime URL and Repo URL ---
34
+ space_id = os.getenv("SPACE_ID") # Get the SPACE_ID for sending link to the code
35
+
36
+ if profile:
37
+ username= f"{profile.username}"
38
+ print(f"User logged in: {username}")
39
+ else:
40
+ print("User not logged in.")
41
+ return "Please Login to Hugging Face with the button.", None
42
+
43
+ api_url = DEFAULT_API_URL
44
+ questions_url = f"{api_url}/questions"
45
+ submit_url = f"{api_url}/submit"
46
+
47
+ # 1. Instantiate Agent ( modify this part to create your agent)
48
+ try:
49
+ agent = BasicAgent()
50
+ except Exception as e:
51
+ print(f"Error instantiating agent: {e}")
52
+ return f"Error initializing agent: {e}", None
53
+ # In the case of an app running as a hugging Face space, this link points toward your codebase ( usefull for others so please keep it public)
54
+ agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main"
55
+ print(agent_code)
56
+
57
+ # 2. Fetch Questions
58
+ print(f"Fetching questions from: {questions_url}")
59
+ try:
60
+ response = requests.get(questions_url, timeout=15)
61
+ response.raise_for_status()
62
+ questions_data = response.json()
63
+ if not questions_data:
64
+ print("Fetched questions list is empty.")
65
+ return "Fetched questions list is empty or invalid format.", None
66
+ print(f"Fetched {len(questions_data)} questions.")
67
+ except requests.exceptions.RequestException as e:
68
+ print(f"Error fetching questions: {e}")
69
+ return f"Error fetching questions: {e}", None
70
+ except requests.exceptions.JSONDecodeError as e:
71
+ print(f"Error decoding JSON response from questions endpoint: {e}")
72
+ print(f"Response text: {response.text[:500]}")
73
+ return f"Error decoding server response for questions: {e}", None
74
+ except Exception as e:
75
+ print(f"An unexpected error occurred fetching questions: {e}")
76
+ return f"An unexpected error occurred fetching questions: {e}", None
77
+
78
+ # 3. Run your Agent
79
+ results_log = []
80
+ answers_payload = []
81
+ print(f"Running agent on {len(questions_data)} questions...")
82
+ for item in questions_data:
83
+ task_id = item.get("task_id")
84
+ question_text = item.get("question")
85
+ if not task_id or question_text is None:
86
+ print(f"Skipping item with missing task_id or question: {item}")
87
+ continue
88
+ # time.sleep(10)
89
+ try:
90
+ submitted_answer = agent(question_text)
91
+ answers_payload.append({"task_id": task_id, "submitted_answer": submitted_answer})
92
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": submitted_answer})
93
+ except Exception as e:
94
+ print(f"Error running agent on task {task_id}: {e}")
95
+ results_log.append({"Task ID": task_id, "Question": question_text, "Submitted Answer": f"AGENT ERROR: {e}"})
96
+
97
+ if not answers_payload:
98
+ print("Agent did not produce any answers to submit.")
99
+ return "Agent did not produce any answers to submit.", pd.DataFrame(results_log)
100
+
101
+ # 4. Prepare Submission
102
+ submission_data = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
103
+ status_update = f"Agent finished. Submitting {len(answers_payload)} answers for user '{username}'..."
104
+ print(status_update)
105
+
106
+ # 5. Submit
107
+ print(f"Submitting {len(answers_payload)} answers to: {submit_url}")
108
+ try:
109
+ response = requests.post(submit_url, json=submission_data, timeout=60)
110
+ response.raise_for_status()
111
+ result_data = response.json()
112
+ final_status = (
113
+ f"Submission Successful!\n"
114
+ f"User: {result_data.get('username')}\n"
115
+ f"Overall Score: {result_data.get('score', 'N/A')}% "
116
+ f"({result_data.get('correct_count', '?')}/{result_data.get('total_attempted', '?')} correct)\n"
117
+ f"Message: {result_data.get('message', 'No message received.')}"
118
+ )
119
+ print("Submission successful.")
120
+ results_df = pd.DataFrame(results_log)
121
+ return final_status, results_df
122
+ except requests.exceptions.HTTPError as e:
123
+ error_detail = f"Server responded with status {e.response.status_code}."
124
+ try:
125
+ error_json = e.response.json()
126
+ error_detail += f" Detail: {error_json.get('detail', e.response.text)}"
127
+ except requests.exceptions.JSONDecodeError:
128
+ error_detail += f" Response: {e.response.text[:500]}"
129
+ status_message = f"Submission Failed: {error_detail}"
130
+ print(status_message)
131
+ results_df = pd.DataFrame(results_log)
132
+ return status_message, results_df
133
+ except requests.exceptions.Timeout:
134
+ status_message = "Submission Failed: The request timed out."
135
+ print(status_message)
136
+ results_df = pd.DataFrame(results_log)
137
+ return status_message, results_df
138
+ except requests.exceptions.RequestException as e:
139
+ status_message = f"Submission Failed: Network error - {e}"
140
+ print(status_message)
141
+ results_df = pd.DataFrame(results_log)
142
+ return status_message, results_df
143
+ except Exception as e:
144
+ status_message = f"An unexpected error occurred during submission: {e}"
145
+ print(status_message)
146
+ results_df = pd.DataFrame(results_log)
147
+ return status_message, results_df
148
+
149
+
150
+ # --- Build Gradio Interface using Blocks ---
151
+ with gr.Blocks() as demo:
152
+ gr.Markdown("# Basic Agent Evaluation Runner")
153
+ gr.Markdown(
154
+ """
155
+ **Instructions:**
156
+ 1. Please clone this space, then modify the code to define your agent's logic, the tools, the necessary packages, etc ...
157
+ 2. Log in to your Hugging Face account using the button below. This uses your HF username for submission.
158
+ 3. Click 'Run Evaluation & Submit All Answers' to fetch questions, run your agent, submit answers, and see the score.
159
+ ---
160
+ **Disclaimers:**
161
+ 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).
162
+ This space provides a basic setup and is intentionally sub-optimal to encourage you to develop your own, more robust solution. For instance for the delay process of the submit button, a solution could be to cache the answers and submit in a seperate action or even to answer the questions in async.
163
+ """
164
+ )
165
+
166
+ gr.LoginButton()
167
+
168
+ run_button = gr.Button("Run Evaluation & Submit All Answers")
169
+
170
+ status_output = gr.Textbox(label="Run Status / Submission Result", lines=5, interactive=False)
171
+ # Removed max_rows=10 from DataFrame constructor
172
+ results_table = gr.DataFrame(label="Questions and Agent Answers", wrap=True)
173
+
174
+ run_button.click(
175
+ fn=run_and_submit_all,
176
+ outputs=[status_output, results_table]
177
+ )
178
+
179
+ if __name__ == "__main__":
180
+ print("\n" + "-"*30 + " App Starting " + "-"*30)
181
+ # Check for SPACE_HOST and SPACE_ID at startup for information
182
+ space_host_startup = os.getenv("SPACE_HOST")
183
+ space_id_startup = os.getenv("SPACE_ID") # Get SPACE_ID at startup
184
+
185
+ if space_host_startup:
186
+ print(f"✅ SPACE_HOST found: {space_host_startup}")
187
+ print(f" Runtime URL should be: https://{space_host_startup}.hf.space")
188
+ else:
189
+ print("ℹ️ SPACE_HOST environment variable not found (running locally?).")
190
+
191
+ if space_id_startup: # Print repo URLs if SPACE_ID is found
192
+ print(f"✅ SPACE_ID found: {space_id_startup}")
193
+ print(f" Repo URL: https://huggingface.co/spaces/{space_id_startup}")
194
+ print(f" Repo Tree URL: https://huggingface.co/spaces/{space_id_startup}/tree/main")
195
+ else:
196
+ print("ℹ️ SPACE_ID environment variable not found (running locally?). Repo URL cannot be determined.")
197
+
198
+ print("-"*(60 + len(" App Starting ")) + "\n")
199
+
200
+ print("Launching Gradio Interface for Basic Agent Evaluation...")
201
+ demo.launch(debug=True, share=False)
metadata.jsonl ADDED
File without changes
requirements.txt ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ requests
3
+ gradio
4
+ requests
5
+ langchain
6
+ langchain-community
7
+ langchain-core
8
+ langchain-google-genai
9
+ langchain-huggingface
10
+ langchain-groq
11
+ langchain-tavily
12
+ langchain-chroma
13
+ langgraph
14
+ huggingface_hub
15
+ supabase
16
+ arxiv
17
+ pymupdf
18
+ wikipedia
19
+ pgvector
20
+ python-dotenv
21
+ pytesseract
22
+ matplotlib
system_prompt.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ You are a helpful assistant tasked with answering questions using a set of tools.
2
+ Now, I will ask you a question. Report your thoughts, and finish your answer with the following template:
3
+ FINAL ANSWER: [YOUR FINAL ANSWER].
4
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings. If you are asked for a number, don't use comma to write your number neither use units such as $ or percent sign unless specified otherwise. If you are asked for a string, don't use articles, neither abbreviations (e.g. for cities), and write the digits in plain text unless specified otherwise. If you are asked for a comma separated list, Apply the rules above for each element (number or string), ensure there is exactly one space after each comma.
5
+ Your answer should only start with "FINAL ANSWER: ", then follows with the answer.
test.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
tools/browse.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import copy
2
+ from langchain_community.tools.tavily_search import TavilySearchResults
3
+ from langchain_community.document_loaders import WikipediaLoader
4
+ from langchain_community.document_loaders import ArxivLoader
5
+ from langchain_core.tools import tool
6
+ def format_search_docs(search_docs):
7
+ """Format search documents into a consistent string format.
8
+
9
+ Args:
10
+ search_docs: List of document objects with metadata and page_content.
11
+
12
+ Returns:
13
+ Formatted string with document sources and content.
14
+ """
15
+ return "\n\n---\n\n".join(
16
+ [
17
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
18
+ for doc in search_docs
19
+ ]
20
+ )
21
+
22
+ @tool
23
+ def wiki_search(query: str) -> str:
24
+ """Search Wikipedia for a query and return maximum 2 results.
25
+ Args:
26
+ query: The search query."""
27
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
28
+ formatted_search_docs = format_search_docs(search_docs)
29
+ return {"wiki_results": formatted_search_docs}
30
+
31
+ @tool
32
+ def web_search(query: str) -> str:
33
+ """Search Tavily for a query and return maximum 3 results.
34
+ Args:
35
+ query: The search query."""
36
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
37
+ formatted_search_docs = format_search_docs(search_docs)
38
+ return {"web_results": formatted_search_docs}
39
+
40
+ @tool
41
+ def arxiv_search(query: str) -> str:
42
+ """Search Arxiv for a query and return maximum 3 result.
43
+ Args:
44
+ query: The search query."""
45
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
46
+ truncated_docs = []
47
+ for doc in search_docs:
48
+ doc_copy = copy.copy(doc)
49
+ doc_copy.page_content = doc.page_content[:1000]
50
+ truncated_docs.append(doc_copy)
51
+
52
+ formatted_search_docs = format_search_docs(truncated_docs)
53
+ return {"arxiv_results": formatted_search_docs}
tools/document_process.py ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ import os
3
+ from typing import Optional
4
+ import tempfile
5
+ import requests
6
+ from urllib.parse import urlparse
7
+ import pytesseract
8
+ from PIL import Image
9
+ import pandas as pd
10
+ import uuid
11
+
12
+ @tool
13
+ def save_and_read_file(content: str, filename: Optional[str] = None) -> str:
14
+ """
15
+ Save content to a file and return the path.
16
+ Args:
17
+ content (str): the content to save to the file
18
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
19
+ """
20
+ temp_dir = tempfile.gettempdir()
21
+ if filename is None:
22
+ temp_file = tempfile.NamedTemporaryFile(delete=False, dir=temp_dir)
23
+ filepath = temp_file.name
24
+ else:
25
+ filepath = os.path.join(temp_dir, filename)
26
+
27
+ with open(filepath, "w") as f:
28
+ f.write(content)
29
+
30
+ return f"File saved to {filepath}. You can read this file to process its contents."
31
+
32
+ @tool
33
+ def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
34
+ """
35
+ Download a file from a URL and save it to a temporary location.
36
+ Args:
37
+ url (str): the URL of the file to download.
38
+ filename (str, optional): the name of the file. If not provided, a random name file will be created.
39
+ """
40
+ try:
41
+ if not filename:
42
+ path = urlparse(url).path
43
+ filename = os.path.basename(path)
44
+ if not filename:
45
+ filename = f"downloaded_{uuid.uuid4().hex[:8]}"
46
+
47
+ temp_dir = tempfile.gettempdir()
48
+ filepath = os.path.join(temp_dir, filename)
49
+
50
+ response = requests.get(url, stream=True)
51
+ response.raise_for_status()
52
+
53
+ # Save the file
54
+ with open(filepath, "wb") as f:
55
+ for chunk in response.iter_content(chunk_size=8192):
56
+ f.write(chunk)
57
+
58
+ return f"File downloaded to {filepath}. You can read this file to process its contents."
59
+ except Exception as e:
60
+ return f"Error downloading file: {str(e)}"
61
+
62
+ @tool
63
+ def extract_text_from_image(image_path: str) -> str:
64
+ """
65
+ Extract text from an image using OCR library pytesseract (if available).
66
+ Args:
67
+ image_path (str): the path to the image file.
68
+ """
69
+ try:
70
+ image = Image.open(image_path)
71
+
72
+ # Extract text from the image
73
+ text = pytesseract.image_to_string(image)
74
+
75
+ return f"Extracted text from image:\n\n{text}"
76
+ except Exception as e:
77
+ return f"Error extracting text from image: {str(e)}"
78
+
79
+ @tool
80
+ def analyze_csv_file(file_path: str, query: str) -> str:
81
+ """
82
+ Analyze a CSV file using pandas and answer a question about it.
83
+ Args:
84
+ file_path (str): the path to the CSV file.
85
+ query (str): Question about the data
86
+ """
87
+ try:
88
+ df = pd.read_csv(file_path)
89
+
90
+ result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
91
+ result += f"Columns: {', '.join(df.columns)}\n\n"
92
+
93
+ result += "Summary statistics:\n"
94
+ result += str(df.describe())
95
+
96
+ return result
97
+
98
+ except Exception as e:
99
+ return f"Error analyzing CSV file: {str(e)}"
100
+
101
+ @tool
102
+ def analyze_excel_file(file_path: str, query: str) -> str:
103
+ """
104
+ Analyze an Excel file using pandas and answer a question about it.
105
+ Args:
106
+ file_path (str): the path to the Excel file.
107
+ query (str): Question about the data
108
+ """
109
+ try:
110
+ df = pd.read_excel(file_path)
111
+
112
+ result = (
113
+ f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
114
+ )
115
+ result += f"Columns: {', '.join(df.columns)}\n\n"
116
+
117
+ result += "Summary statistics:\n"
118
+ result += str(df.describe())
119
+
120
+ return result
121
+
122
+ except Exception as e:
123
+ return f"Error analyzing Excel file: {str(e)}"
tools/image.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import base64
4
+ import uuid
5
+ from PIL import Image
6
+
7
+
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(image_path: str) -> Image.Image:
15
+ """Convert a base64 string to a PIL Image."""
16
+ image_data = base64.b64decode(image_path)
17
+ return Image.open(io.BytesIO(image_data))
18
+
19
+ def save_image(image: Image.Image, directory:str = "images") -> str:
20
+ """Save a PIL Image to disk and return the path."""
21
+ os.makedirs(directory, exist_ok = True)
22
+ image_id = str(uuid.uuid4())
23
+ image_path = os.path.join(directory, f"{image_id}.png")
24
+ image.save(image_path)
25
+ return image_path
tools/image_tools.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+ from tools.image import decode_image, encode_image, save_image
3
+ from typing import Dict, Any, List, Optional
4
+ import numpy as np
5
+ from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
6
+
7
+ @tool
8
+ def analyze_image(image_base64: str) -> Dict[str, Any]:
9
+ """
10
+ Analyze basic properties of an image (size, mode, color analysis, thumbnail preview).
11
+ Args:
12
+ image_base64 (str): Base64 encoded image string
13
+ Returns:
14
+ Dictionary with analysis result
15
+ """
16
+ try:
17
+ img = decode_image(image_base64)
18
+ width, height = img.size
19
+ mode = img.mode
20
+
21
+ if mode in ("RGB", "RGBA"):
22
+ arr = np.array(img)
23
+ avg_colors = arr.mean(axis=(0, 1))
24
+ dominant = ["Red", "Green", "Blue"][np.argmax(avg_colors[:3])]
25
+ brightness = avg_colors.mean()
26
+ color_analysis = {
27
+ "average_rgb": avg_colors.tolist(),
28
+ "brightness": brightness,
29
+ "dominant_color": dominant,
30
+ }
31
+ else:
32
+ color_analysis = {"note": f"No color analysis for mode {mode}"}
33
+
34
+ thumbnail = img.copy()
35
+ thumbnail.thumbnail((100, 100))
36
+ thumb_path = save_image(thumbnail, "thumbnails")
37
+ thumbnail_base64 = encode_image(thumb_path)
38
+
39
+ return {
40
+ "dimensions": (width, height),
41
+ "mode": mode,
42
+ "color_analysis": color_analysis,
43
+ "thumbnail": thumbnail_base64,
44
+ }
45
+ except Exception as e:
46
+ return {"error": str(e)}
47
+
48
+ @tool
49
+ def transform_image(
50
+ image_base64: str, operation: str, params: Optional[Dict[str, Any]] = None
51
+ ) -> Dict[str, Any]:
52
+ """
53
+ Apply transformations: resize, rotate, crop, flip, brightness, contrast, blur, sharpen, grayscale.
54
+ Args:
55
+ image_base64 (str): Base64 encoded input image
56
+ operation (str): Transformation operation
57
+ params (Dict[str, Any], optional): Parameters for the operation
58
+ Returns:
59
+ Dictionary with transformed image (base64)
60
+ """
61
+ try:
62
+ img = decode_image(image_base64)
63
+ params = params or {}
64
+
65
+ if operation == "resize":
66
+ img = img.resize(
67
+ (
68
+ params.get("width", img.width // 2),
69
+ params.get("height", img.height // 2),
70
+ )
71
+ )
72
+ elif operation == "rotate":
73
+ img = img.rotate(params.get("angle", 90), expand=True)
74
+ elif operation == "crop":
75
+ img = img.crop(
76
+ (
77
+ params.get("left", 0),
78
+ params.get("top", 0),
79
+ params.get("right", img.width),
80
+ params.get("bottom", img.height),
81
+ )
82
+ )
83
+ elif operation == "flip":
84
+ if params.get("direction", "horizontal") == "horizontal":
85
+ img = img.transpose(Image.FLIP_LEFT_RIGHT)
86
+ else:
87
+ img = img.transpose(Image.FLIP_TOP_BOTTOM)
88
+ elif operation == "adjust_brightness":
89
+ img = ImageEnhance.Brightness(img).enhance(params.get("factor", 1.5))
90
+ elif operation == "adjust_contrast":
91
+ img = ImageEnhance.Contrast(img).enhance(params.get("factor", 1.5))
92
+ elif operation == "blur":
93
+ img = img.filter(ImageFilter.GaussianBlur(params.get("radius", 2)))
94
+ elif operation == "sharpen":
95
+ img = img.filter(ImageFilter.SHARPEN)
96
+ elif operation == "grayscale":
97
+ img = img.convert("L")
98
+ else:
99
+ return {"error": f"Unknown operation: {operation}"}
100
+
101
+ result_path = save_image(img)
102
+ result_base64 = encode_image(result_path)
103
+ return {"transformed_image": result_base64}
104
+
105
+ except Exception as e:
106
+ return {"error": str(e)}
107
+
108
+
109
+ @tool
110
+ def draw_on_image(
111
+ image_base64: str, drawing_type: str, params: Dict[str, Any]
112
+ ) -> Dict[str, Any]:
113
+ """
114
+ Draw shapes (rectangle, circle, line) or text onto an image.
115
+ Args:
116
+ image_base64 (str): Base64 encoded input image
117
+ drawing_type (str): Drawing type
118
+ params (Dict[str, Any]): Drawing parameters
119
+ Returns:
120
+ Dictionary with result image (base64)
121
+ """
122
+ try:
123
+ img = decode_image(image_base64)
124
+ draw = ImageDraw.Draw(img)
125
+ color = params.get("color", "red")
126
+
127
+ if drawing_type == "rectangle":
128
+ draw.rectangle(
129
+ [params["left"], params["top"], params["right"], params["bottom"]],
130
+ outline=color,
131
+ width=params.get("width", 2),
132
+ )
133
+ elif drawing_type == "circle":
134
+ x, y, r = params["x"], params["y"], params["radius"]
135
+ draw.ellipse(
136
+ (x - r, y - r, x + r, y + r),
137
+ outline=color,
138
+ width=params.get("width", 2),
139
+ )
140
+ elif drawing_type == "line":
141
+ draw.line(
142
+ (
143
+ params["start_x"],
144
+ params["start_y"],
145
+ params["end_x"],
146
+ params["end_y"],
147
+ ),
148
+ fill=color,
149
+ width=params.get("width", 2),
150
+ )
151
+ elif drawing_type == "text":
152
+ font_size = params.get("font_size", 20)
153
+ try:
154
+ font = ImageFont.truetype("arial.ttf", font_size)
155
+ except IOError:
156
+ font = ImageFont.load_default()
157
+ draw.text(
158
+ (params["x"], params["y"]),
159
+ params.get("text", "Text"),
160
+ fill=color,
161
+ font=font,
162
+ )
163
+ else:
164
+ return {"error": f"Unknown drawing type: {drawing_type}"}
165
+
166
+ result_path = save_image(img)
167
+ result_base64 = encode_image(result_path)
168
+ return {"result_image": result_base64}
169
+
170
+ except Exception as e:
171
+ return {"error": str(e)}
172
+
173
+
174
+ @tool
175
+ def generate_simple_image(
176
+ image_type: str,
177
+ width: int = 500,
178
+ height: int = 500,
179
+ params: Optional[Dict[str, Any]] = None,
180
+ ) -> Dict[str, Any]:
181
+ """
182
+ Generate a simple image (gradient, noise, pattern, chart).
183
+ Args:
184
+ image_type (str): Type of image
185
+ width (int), height (int)
186
+ params (Dict[str, Any], optional): Specific parameters
187
+ Returns:
188
+ Dictionary with generated image (base64)
189
+ """
190
+ try:
191
+ params = params or {}
192
+
193
+ if image_type == "gradient":
194
+ direction = params.get("direction", "horizontal")
195
+ start_color = params.get("start_color", (255, 0, 0))
196
+ end_color = params.get("end_color", (0, 0, 255))
197
+
198
+ img = Image.new("RGB", (width, height))
199
+ draw = ImageDraw.Draw(img)
200
+
201
+ if direction == "horizontal":
202
+ for x in range(width):
203
+ r = int(
204
+ start_color[0] + (end_color[0] - start_color[0]) * x / width
205
+ )
206
+ g = int(
207
+ start_color[1] + (end_color[1] - start_color[1]) * x / width
208
+ )
209
+ b = int(
210
+ start_color[2] + (end_color[2] - start_color[2]) * x / width
211
+ )
212
+ draw.line([(x, 0), (x, height)], fill=(r, g, b))
213
+ else:
214
+ for y in range(height):
215
+ r = int(
216
+ start_color[0] + (end_color[0] - start_color[0]) * y / height
217
+ )
218
+ g = int(
219
+ start_color[1] + (end_color[1] - start_color[1]) * y / height
220
+ )
221
+ b = int(
222
+ start_color[2] + (end_color[2] - start_color[2]) * y / height
223
+ )
224
+ draw.line([(0, y), (width, y)], fill=(r, g, b))
225
+
226
+ elif image_type == "noise":
227
+ noise_array = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8)
228
+ img = Image.fromarray(noise_array, "RGB")
229
+
230
+ else:
231
+ return {"error": f"Unsupported image_type {image_type}"}
232
+
233
+ result_path = save_image(img)
234
+ result_base64 = encode_image(result_path)
235
+ return {"generated_image": result_base64}
236
+
237
+ except Exception as e:
238
+ return {"error": str(e)}
239
+
240
+
241
+ @tool
242
+ def combine_images(
243
+ images_base64: List[str], operation: str, params: Optional[Dict[str, Any]] = None
244
+ ) -> Dict[str, Any]:
245
+ """
246
+ Combine multiple images (collage, stack, blend).
247
+ Args:
248
+ images_base64 (List[str]): List of base64 images
249
+ operation (str): Combination type
250
+ params (Dict[str, Any], optional)
251
+ Returns:
252
+ Dictionary with combined image (base64)
253
+ """
254
+ try:
255
+ images = [decode_image(b64) for b64 in images_base64]
256
+ params = params or {}
257
+
258
+ if operation == "stack":
259
+ direction = params.get("direction", "horizontal")
260
+ if direction == "horizontal":
261
+ total_width = sum(img.width for img in images)
262
+ max_height = max(img.height for img in images)
263
+ new_img = Image.new("RGB", (total_width, max_height))
264
+ x = 0
265
+ for img in images:
266
+ new_img.paste(img, (x, 0))
267
+ x += img.width
268
+ else:
269
+ max_width = max(img.width for img in images)
270
+ total_height = sum(img.height for img in images)
271
+ new_img = Image.new("RGB", (max_width, total_height))
272
+ y = 0
273
+ for img in images:
274
+ new_img.paste(img, (0, y))
275
+ y += img.height
276
+ else:
277
+ return {"error": f"Unsupported combination operation {operation}"}
278
+
279
+ result_path = save_image(new_img)
280
+ result_base64 = encode_image(result_path)
281
+ return {"combined_image": result_base64}
282
+
283
+ except Exception as e:
284
+ return {"error": str(e)}
tools/python_interpreter.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import uuid
4
+ import base64
5
+ import traceback
6
+ import contextlib
7
+ from typing import Dict, Any
8
+ import numpy as np
9
+ import pandas as pd
10
+ import matplotlib.pyplot as plt
11
+ from PIL import Image
12
+ from langchain_core.tools import tool
13
+
14
+ class CodeInterpreter:
15
+
16
+ def __init__(self, allowed_modules = None, max_execution_time = 30, working_directory = None):
17
+ """Initialize the code interpreter with safety measures."""
18
+ self.allowed_modules = allowed_modules or [
19
+ "numpy", "pandas", "matplotlib", "scipy", "sklearn",
20
+ "math", "random", "statistics", "datetime", "collections",
21
+ "itertools", "functools", "operator", "re", "json",
22
+ "sympy", "networkx", "nltk", "PIL", "pytesseract",
23
+ "cmath", "uuid", "tempfile", "requests", "urllib"
24
+ ]
25
+ self.max_execution_time = max_execution_time
26
+ self.working_directory = working_directory or os.path.join(os.getcwd())
27
+ if not os.path.exists(self.working_directory):
28
+ os.makedirs(self.working_directory)
29
+
30
+ self.globals = {
31
+ "__builtins__": __builtins__,
32
+ "np": np,
33
+ "pd": pd,
34
+ "plt": plt,
35
+ "Image": Image,
36
+ }
37
+
38
+ def execute_code(self, code: str, language: str = "python") -> Dict[str, Any]:
39
+ """Execute the provided code in the selected programming language."""
40
+ language = language.lower()
41
+ execution_id = str(uuid.uuid4())
42
+
43
+ result = {
44
+ "execution_id": execution_id,
45
+ "status": "error",
46
+ "stdout": "",
47
+ "stderr": "",
48
+ "result": None,
49
+ "plots": [],
50
+ "dataframes": []
51
+ }
52
+
53
+ try:
54
+ return self._execute_python(code, execution_id)
55
+ except Exception as e:
56
+ result["stderr"] = f"Unsupported Language: {str(e)}"
57
+
58
+ return result
59
+
60
+ def _execute_python(self, code: str, execution_id: str) -> dict:
61
+ output_buffer = io.StringIO()
62
+ error_buffer = io.StringIO()
63
+ result = {
64
+ "execution_id": execution_id,
65
+ "status": "error",
66
+ "stdout": "",
67
+ "stderr": "",
68
+ "result": None,
69
+ "plots": [],
70
+ "dataframes": []
71
+ }
72
+
73
+ try:
74
+ exec_dir = os.path.join(self.working_directory, execution_id)
75
+ os.makedirs(exec_dir, exist_ok=True)
76
+ plt.switch_backend('Agg')
77
+
78
+ with contextlib.redirect_stdout(output_buffer), contextlib.redirect_stderr(error_buffer):
79
+ exec_result = exec(code, self.globals)
80
+
81
+ if plt.get_fignums():
82
+ for i, fig_num in enumerate(plt.get_fignums()):
83
+ fig = plt.figure(fig_num)
84
+ img_path = os.path.join(exec_dir, f"plot_{i}.png")
85
+ fig.savefig(img_path)
86
+ with open(img_path, "rb") as img_file:
87
+ img_data = base64.b64encode(img_file.read()).decode('utf-8')
88
+ result["plots"].append({
89
+ "figure_number": fig_num,
90
+ "data": img_data
91
+ })
92
+
93
+ for var_name, var_value in self.globals.items():
94
+ if isinstance(var_value, pd.DataFrame) and len(var_value) > 0:
95
+ result["dataframes"].append({
96
+ "name": var_name,
97
+ "head": var_value.head().to_dict(),
98
+ "shape": var_value.shape,
99
+ "dtypes": str(var_value.dtypes)
100
+ })
101
+
102
+ result["status"] = "success"
103
+ result["stdout"] = output_buffer.getvalue()
104
+ result["result"] = exec_result
105
+
106
+ except Exception as e:
107
+ result["status"] = "error"
108
+ result["stderr"] = f"{error_buffer.getvalue()}\n{traceback.format_exc()}"
109
+
110
+ return result
111
+
112
+
113
+ interpreter_instance = CodeInterpreter()
114
+ @tool
115
+ def execute_code_lang(code: str, language: str = "python") -> str:
116
+ """Execute code in python
117
+ Args:
118
+ code (str): The source code to execute.
119
+ language (str): The language of the code. Supported: "python".
120
+ Returns:
121
+ A string summarizing the execution results (stdout, stderr, errors, plots, dataframes if any).
122
+ """
123
+ supported_language = "python"
124
+ language = language.lower()
125
+
126
+ if language != supported_language:
127
+ return f"❌ Unsupported language: {language}."
128
+
129
+ result = interpreter_instance.execute_code(code, language=language)
130
+
131
+ response = []
132
+
133
+ if result["status"] == "success":
134
+ response.append(f"✅ Code executed successfully in **{language.upper()}**")
135
+
136
+ if result.get("stdout"):
137
+ response.append(
138
+ "\n**Standard Output:**\n```\n" + result["stdout"].strip() + "\n```"
139
+ )
140
+
141
+ if result.get("stderr"):
142
+ response.append(
143
+ "\n**Standard Error (if any):**\n```\n"
144
+ + result["stderr"].strip()
145
+ + "\n```"
146
+ )
147
+
148
+ if result.get("result") is not None:
149
+ response.append(
150
+ "\n**Execution Result:**\n```\n"
151
+ + str(result["result"]).strip()
152
+ + "\n```"
153
+ )
154
+
155
+ if result.get("dataframes"):
156
+ for df_info in result["dataframes"]:
157
+ response.append(
158
+ f"\n**DataFrame `{df_info['name']}` (Shape: {df_info['shape']})**"
159
+ )
160
+ df_preview = pd.DataFrame(df_info["head"])
161
+ response.append("First 5 rows:\n```\n" + str(df_preview) + "\n```")
162
+
163
+ if result.get("plots"):
164
+ response.append(
165
+ f"\n**Generated {len(result['plots'])} plot(s)** (Image data returned separately)"
166
+ )
167
+
168
+ else:
169
+ response.append(f"❌ Code execution failed in **{language.upper()}**")
170
+ if result.get("stderr"):
171
+ response.append(
172
+ "\n**Error Log:**\n```\n" + result["stderr"].strip() + "\n```"
173
+ )
174
+
175
+ return "\n".join(response)
tools/simple_math.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_core.tools import tool
2
+
3
+ @tool
4
+ def multiply(a: float, b: float) -> float:
5
+ """
6
+ Multiplies two numbers.
7
+ Args:
8
+ a (float): the first number
9
+ b (float): the second number
10
+ """
11
+ return a * b
12
+
13
+
14
+ @tool
15
+ def add(a: float, b: float) -> float:
16
+ """
17
+ Adds two numbers.
18
+ Args:
19
+ a (float): the first number
20
+ b (float): the second number
21
+ """
22
+ return a + b
23
+
24
+
25
+ @tool
26
+ def subtract(a: float, b: float) -> int:
27
+ """
28
+ Subtracts two numbers.
29
+ Args:
30
+ a (float): the first number
31
+ b (float): the second number
32
+ """
33
+ return a - b
34
+
35
+
36
+ @tool
37
+ def divide(a: float, b: float) -> float:
38
+ """
39
+ Divides two numbers.
40
+ Args:
41
+ a (float): the first float number
42
+ b (float): the second float number
43
+ """
44
+ if b == 0:
45
+ raise ValueError("Cannot divided by zero.")
46
+ return a / b
47
+
48
+
49
+ @tool
50
+ def modulus(a: int, b: int) -> int:
51
+ """
52
+ Get the modulus of two numbers.
53
+ Args:
54
+ a (int): the first number
55
+ b (int): the second number
56
+ """
57
+ return a % b
58
+
59
+
60
+ @tool
61
+ def power(a: float, b: float) -> float:
62
+ """
63
+ Get the power of two numbers.
64
+ Args:
65
+ a (float): the first number
66
+ b (float): the second number
67
+ """
68
+ return a**b
69
+
70
+
71
+ @tool
72
+ def square_root(a: float) -> float | complex:
73
+ """
74
+ Get the square root of a number.
75
+ Args:
76
+ a (float): the number to get the square root of
77
+ """
78
+ if a >= 0:
79
+ return a**0.5
80
+ return cmath.sqrt(a)