Commit
·
b6c03a5
1
Parent(s):
c11970b
enhanced tools + Croq
Browse files- agents/__pycache__/agent.cpython-312.pyc +0 -0
- agents/agent.py +41 -15
- system_prompt.txt +5 -56
- tools/__pycache__/codetools.cpython-312.pyc +0 -0
- tools/__pycache__/documenttools.cpython-312.pyc +0 -0
- tools/__pycache__/imagetools.cpython-312.pyc +0 -0
- tools/__pycache__/mathtools.cpython-312.pyc +0 -0
- tools/__pycache__/searchtools.cpython-312.pyc +0 -0
- tools/codetools.py +348 -0
- tools/documenttools.py +137 -0
- tools/imagetools.py +311 -0
- tools/mathtools.py +53 -26
- tools/searchtools.py +25 -18
agents/__pycache__/agent.cpython-312.pyc
CHANGED
Binary files a/agents/__pycache__/agent.cpython-312.pyc and b/agents/__pycache__/agent.cpython-312.pyc differ
|
|
agents/agent.py
CHANGED
@@ -6,11 +6,14 @@ load_dotenv()
|
|
6 |
from langgraph.graph import START, StateGraph, MessagesState
|
7 |
from langgraph.prebuilt import tools_condition
|
8 |
from langgraph.prebuilt import ToolNode
|
9 |
-
from langchain_google_genai import ChatGoogleGenerativeAI
|
10 |
from langchain_core.messages import SystemMessage, HumanMessage
|
11 |
-
from tools.searchtools import wiki_search, web_search,
|
12 |
-
from tools.mathtools import multiply, add, subtract, divide, modulus
|
13 |
-
|
|
|
|
|
|
|
|
|
14 |
|
15 |
# load the system prompt from the file
|
16 |
with open("system_prompt.txt", "r", encoding="utf-8") as f:
|
@@ -19,21 +22,37 @@ with open("system_prompt.txt", "r", encoding="utf-8") as f:
|
|
19 |
# System message
|
20 |
sys_msg = SystemMessage(content=system_prompt)
|
21 |
|
|
|
22 |
tools = [
|
|
|
|
|
|
|
23 |
multiply,
|
24 |
add,
|
25 |
subtract,
|
26 |
divide,
|
27 |
modulus,
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
]
|
32 |
|
|
|
33 |
# Build graph function
|
34 |
def build_graph():
|
35 |
"""Build the graph"""
|
36 |
-
|
|
|
37 |
|
38 |
# Bind tools to LLM
|
39 |
llm_with_tools = llm.bind_tools(tools)
|
@@ -42,14 +61,19 @@ def build_graph():
|
|
42 |
def assistant(state: MessagesState):
|
43 |
"""Assistant node"""
|
44 |
return {"messages": [llm_with_tools.invoke(state["messages"])]}
|
45 |
-
|
46 |
def retriever(state: MessagesState):
|
47 |
"""Retriever node"""
|
48 |
similar_question = vector_store.similarity_search(state["messages"][0].content)
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
builder = StateGraph(MessagesState)
|
55 |
builder.add_node("retriever", retriever)
|
@@ -57,8 +81,10 @@ def build_graph():
|
|
57 |
builder.add_node("tools", ToolNode(tools))
|
58 |
builder.add_edge(START, "retriever")
|
59 |
builder.add_edge("retriever", "assistant")
|
60 |
-
builder.add_conditional_edges(
|
61 |
-
|
|
|
|
|
62 |
builder.add_edge("tools", "assistant")
|
63 |
|
64 |
# Compile graph
|
|
|
6 |
from langgraph.graph import START, StateGraph, MessagesState
|
7 |
from langgraph.prebuilt import tools_condition
|
8 |
from langgraph.prebuilt import ToolNode
|
|
|
9 |
from langchain_core.messages import SystemMessage, HumanMessage
|
10 |
+
from tools.searchtools import wiki_search, web_search, arxiv_search, vector_store
|
11 |
+
from tools.mathtools import multiply, add, subtract, divide, modulus,power,square_root
|
12 |
+
from tools.codetools import execute_code_multilang
|
13 |
+
from tools.documenttools import save_and_read_file,download_file_from_url, extract_text_from_image, analyze_csv_file, analyze_excel_file
|
14 |
+
from tools.imagetools import analyze_image, transform_image, draw_on_image, generate_simple_image, combine_images
|
15 |
+
from langchain_groq import ChatGroq
|
16 |
+
from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
|
17 |
|
18 |
# load the system prompt from the file
|
19 |
with open("system_prompt.txt", "r", encoding="utf-8") as f:
|
|
|
22 |
# System message
|
23 |
sys_msg = SystemMessage(content=system_prompt)
|
24 |
|
25 |
+
|
26 |
tools = [
|
27 |
+
web_search,
|
28 |
+
wiki_search,
|
29 |
+
arxiv_search,
|
30 |
multiply,
|
31 |
add,
|
32 |
subtract,
|
33 |
divide,
|
34 |
modulus,
|
35 |
+
power,
|
36 |
+
square_root,
|
37 |
+
save_and_read_file,
|
38 |
+
download_file_from_url,
|
39 |
+
extract_text_from_image,
|
40 |
+
analyze_csv_file,
|
41 |
+
analyze_excel_file,
|
42 |
+
execute_code_multilang,
|
43 |
+
analyze_image,
|
44 |
+
transform_image,
|
45 |
+
draw_on_image,
|
46 |
+
generate_simple_image,
|
47 |
+
combine_images,
|
48 |
]
|
49 |
|
50 |
+
|
51 |
# Build graph function
|
52 |
def build_graph():
|
53 |
"""Build the graph"""
|
54 |
+
# Load environment variables from .env file
|
55 |
+
llm = ChatGroq(model="qwen-qwq-32b", temperature=0)
|
56 |
|
57 |
# Bind tools to LLM
|
58 |
llm_with_tools = llm.bind_tools(tools)
|
|
|
61 |
def assistant(state: MessagesState):
|
62 |
"""Assistant node"""
|
63 |
return {"messages": [llm_with_tools.invoke(state["messages"])]}
|
64 |
+
|
65 |
def retriever(state: MessagesState):
|
66 |
"""Retriever node"""
|
67 |
similar_question = vector_store.similarity_search(state["messages"][0].content)
|
68 |
+
|
69 |
+
if similar_question: # Check if the list is not empty
|
70 |
+
example_msg = HumanMessage(
|
71 |
+
content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
|
72 |
+
)
|
73 |
+
return {"messages": [sys_msg] + state["messages"] + [example_msg]}
|
74 |
+
else:
|
75 |
+
# Handle the case when no similar questions are found
|
76 |
+
return {"messages": [sys_msg] + state["messages"]}
|
77 |
|
78 |
builder = StateGraph(MessagesState)
|
79 |
builder.add_node("retriever", retriever)
|
|
|
81 |
builder.add_node("tools", ToolNode(tools))
|
82 |
builder.add_edge(START, "retriever")
|
83 |
builder.add_edge("retriever", "assistant")
|
84 |
+
builder.add_conditional_edges(
|
85 |
+
"assistant",
|
86 |
+
tools_condition,
|
87 |
+
)
|
88 |
builder.add_edge("tools", "assistant")
|
89 |
|
90 |
# Compile graph
|
system_prompt.txt
CHANGED
@@ -1,56 +1,5 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
Here is a few examples showing you how to answer the question step by step.
|
7 |
-
|
8 |
-
Question 1: Of the cities within the United States where U.S. presidents were born, which two are the farthest apart from the westernmost to the easternmost going east, giving the city names only? Give them to me in alphabetical order, in a comma-separated list
|
9 |
-
Steps:
|
10 |
-
1. Searched "cities where us presidents are born" on Google.
|
11 |
-
2. Opened "List of presidents of the United States by home state" on Wikipedia.
|
12 |
-
3. Searched the eastern cities to find the easternmost one (Braintree, MA).
|
13 |
-
4. Checked the westernmost city (Honolulu, HI).
|
14 |
-
Tools:
|
15 |
-
1. Search engine
|
16 |
-
2. Web browser
|
17 |
-
Final Answer: Braintree, Honolulu
|
18 |
-
|
19 |
-
Question 2: An office held a Secret Santa gift exchange where each of its twelve employees was assigned one other employee in the group to present with a gift. Each employee filled out a profile including three likes or hobbies. On the day of the gift exchange, only eleven gifts were given, each one specific to one of the recipient's interests. Based on the information in the document, who did not give a gift?
|
20 |
-
Steps:
|
21 |
-
1. Open the document.
|
22 |
-
2. Look at gifts and recipient interests.
|
23 |
-
3. Match Galileo Galilei biography (could apply to astronomy or books -> Miguel or Micah)
|
24 |
-
4. Match fishing reel (only applies to fishing -> Harry)
|
25 |
-
5. Match Raku programming guide (Perl language, but could also apply to JavaScript enthusiast - > Fred or Jun)
|
26 |
-
6. Match chisel set (could apply to camping or woodworking, but Harry is already fulfilled -> Jun, so Raku guide is for Fred)
|
27 |
-
7. Match custom dice (could apply to board games or tabletop RPGs -> Lucy or Sara)
|
28 |
-
8. Match “War and Peace” American film copy (could apply to old movies or Audrey Hepburn -> Perry or Alex)
|
29 |
-
9. Match yarn (only applies to knitting -> Micah, so the Galileo biography is for Miguel)
|
30 |
-
10. Match "One Piece" graphic novel (could apply to books or manga, but Micah already has yarn -> Alex, so the "War and Peace" film is for Perry)
|
31 |
-
11. Match "War and Peace" novel (could apply to books or historical fiction novels, but Micah has yarn -> Tyson)
|
32 |
-
12. Match Starbucks gift card (only applies to coffee -> Lucy, so the dice are for Sara)
|
33 |
-
13. Match foam exercise mat (only applies to yoga -> Georgette)
|
34 |
-
14. Note which recipients have gifts (Miguel, Harry, Fred, Jun, Sara, Perry, Micah, Alex, Tyson, Lucy, Georgette) and which does not (Rebecca).
|
35 |
-
15. Find who was supposed to give Rebecca a gift (Fred).
|
36 |
-
Tools:
|
37 |
-
1. Word document access
|
38 |
-
Final Answer: Fred
|
39 |
-
|
40 |
-
Question 3: Eva Draconis has a personal website which can be accessed on her YouTube page. What is the meaning of the only symbol seen in the top banner that has a curved line that isn't a circle or a portion of a circle? Answer without punctuation.
|
41 |
-
Steps:
|
42 |
-
1. By googling Eva Draconis youtube, you can find her channel.
|
43 |
-
2. In her about section, she has written her website URL, orionmindproject.com.
|
44 |
-
3. Entering this website, you can see a series of symbols at the top, and the text "> see what the symbols mean here" below it.
|
45 |
-
4. Reading through the entries, you can see a short description of some of the symbols.
|
46 |
-
5. The only symbol with a curved line that isn't a circle or a portion of a circle is the last one.
|
47 |
-
6. Note that the symbol supposedly means "War is not here, this is a land of peace."
|
48 |
-
Tools:
|
49 |
-
1. A web browser.
|
50 |
-
2. A search engine.
|
51 |
-
3. Access to YouTube
|
52 |
-
4. Image recognition tools
|
53 |
-
Final Answer: War is not here this is a land of peace
|
54 |
-
|
55 |
-
==========================
|
56 |
-
Now, please answer the following question step by step.
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tools/__pycache__/codetools.cpython-312.pyc
ADDED
Binary file (14.4 kB). View file
|
|
tools/__pycache__/documenttools.cpython-312.pyc
ADDED
Binary file (6.01 kB). View file
|
|
tools/__pycache__/imagetools.cpython-312.pyc
ADDED
Binary file (14.5 kB). View file
|
|
tools/__pycache__/mathtools.cpython-312.pyc
CHANGED
Binary files a/tools/__pycache__/mathtools.cpython-312.pyc and b/tools/__pycache__/mathtools.cpython-312.pyc differ
|
|
tools/__pycache__/searchtools.cpython-312.pyc
CHANGED
Binary files a/tools/__pycache__/searchtools.cpython-312.pyc and b/tools/__pycache__/searchtools.cpython-312.pyc differ
|
|
tools/codetools.py
ADDED
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import tool
|
2 |
+
import os
|
3 |
+
import io
|
4 |
+
import sys
|
5 |
+
import uuid
|
6 |
+
import base64
|
7 |
+
import traceback
|
8 |
+
import contextlib
|
9 |
+
import tempfile
|
10 |
+
import subprocess
|
11 |
+
import sqlite3
|
12 |
+
from typing import Dict, List, Any, Optional, Union
|
13 |
+
import numpy as np
|
14 |
+
import pandas as pd
|
15 |
+
import matplotlib.pyplot as plt
|
16 |
+
from PIL import Image
|
17 |
+
|
18 |
+
class CodeInterpreter:
|
19 |
+
def __init__(self, allowed_modules=None, max_execution_time=30, working_directory=None):
|
20 |
+
"""Initialize the code interpreter with safety measures."""
|
21 |
+
self.allowed_modules = allowed_modules or [
|
22 |
+
"numpy", "pandas", "matplotlib", "scipy", "sklearn",
|
23 |
+
"math", "random", "statistics", "datetime", "collections",
|
24 |
+
"itertools", "functools", "operator", "re", "json",
|
25 |
+
"sympy", "networkx", "nltk", "PIL", "pytesseract",
|
26 |
+
"cmath", "uuid", "tempfile", "requests", "urllib"
|
27 |
+
]
|
28 |
+
self.max_execution_time = max_execution_time
|
29 |
+
self.working_directory = working_directory or os.path.join(os.getcwd())
|
30 |
+
if not os.path.exists(self.working_directory):
|
31 |
+
os.makedirs(self.working_directory)
|
32 |
+
|
33 |
+
self.globals = {
|
34 |
+
"__builtins__": __builtins__,
|
35 |
+
"np": np,
|
36 |
+
"pd": pd,
|
37 |
+
"plt": plt,
|
38 |
+
"Image": Image,
|
39 |
+
}
|
40 |
+
self.temp_sqlite_db = os.path.join(tempfile.gettempdir(), "code_exec.db")
|
41 |
+
|
42 |
+
def execute_code(self, code: str, language: str = "python") -> Dict[str, Any]:
|
43 |
+
"""Execute the provided code in the selected programming language."""
|
44 |
+
language = language.lower()
|
45 |
+
execution_id = str(uuid.uuid4())
|
46 |
+
|
47 |
+
result = {
|
48 |
+
"execution_id": execution_id,
|
49 |
+
"status": "error",
|
50 |
+
"stdout": "",
|
51 |
+
"stderr": "",
|
52 |
+
"result": None,
|
53 |
+
"plots": [],
|
54 |
+
"dataframes": []
|
55 |
+
}
|
56 |
+
|
57 |
+
try:
|
58 |
+
if language == "python":
|
59 |
+
return self._execute_python(code, execution_id)
|
60 |
+
elif language == "bash":
|
61 |
+
return self._execute_bash(code, execution_id)
|
62 |
+
elif language == "sql":
|
63 |
+
return self._execute_sql(code, execution_id)
|
64 |
+
elif language == "c":
|
65 |
+
return self._execute_c(code, execution_id)
|
66 |
+
elif language == "java":
|
67 |
+
return self._execute_java(code, execution_id)
|
68 |
+
else:
|
69 |
+
result["stderr"] = f"Unsupported language: {language}"
|
70 |
+
except Exception as e:
|
71 |
+
result["stderr"] = str(e)
|
72 |
+
|
73 |
+
return result
|
74 |
+
|
75 |
+
def _execute_python(self, code: str, execution_id: str) -> dict:
|
76 |
+
output_buffer = io.StringIO()
|
77 |
+
error_buffer = io.StringIO()
|
78 |
+
result = {
|
79 |
+
"execution_id": execution_id,
|
80 |
+
"status": "error",
|
81 |
+
"stdout": "",
|
82 |
+
"stderr": "",
|
83 |
+
"result": None,
|
84 |
+
"plots": [],
|
85 |
+
"dataframes": []
|
86 |
+
}
|
87 |
+
|
88 |
+
try:
|
89 |
+
exec_dir = os.path.join(self.working_directory, execution_id)
|
90 |
+
os.makedirs(exec_dir, exist_ok=True)
|
91 |
+
plt.switch_backend('Agg')
|
92 |
+
|
93 |
+
with contextlib.redirect_stdout(output_buffer), contextlib.redirect_stderr(error_buffer):
|
94 |
+
exec_result = exec(code, self.globals)
|
95 |
+
|
96 |
+
if plt.get_fignums():
|
97 |
+
for i, fig_num in enumerate(plt.get_fignums()):
|
98 |
+
fig = plt.figure(fig_num)
|
99 |
+
img_path = os.path.join(exec_dir, f"plot_{i}.png")
|
100 |
+
fig.savefig(img_path)
|
101 |
+
with open(img_path, "rb") as img_file:
|
102 |
+
img_data = base64.b64encode(img_file.read()).decode('utf-8')
|
103 |
+
result["plots"].append({
|
104 |
+
"figure_number": fig_num,
|
105 |
+
"data": img_data
|
106 |
+
})
|
107 |
+
|
108 |
+
for var_name, var_value in self.globals.items():
|
109 |
+
if isinstance(var_value, pd.DataFrame) and len(var_value) > 0:
|
110 |
+
result["dataframes"].append({
|
111 |
+
"name": var_name,
|
112 |
+
"head": var_value.head().to_dict(),
|
113 |
+
"shape": var_value.shape,
|
114 |
+
"dtypes": str(var_value.dtypes)
|
115 |
+
})
|
116 |
+
|
117 |
+
result["status"] = "success"
|
118 |
+
result["stdout"] = output_buffer.getvalue()
|
119 |
+
result["result"] = exec_result
|
120 |
+
|
121 |
+
except Exception as e:
|
122 |
+
result["status"] = "error"
|
123 |
+
result["stderr"] = f"{error_buffer.getvalue()}\n{traceback.format_exc()}"
|
124 |
+
|
125 |
+
return result
|
126 |
+
|
127 |
+
def _execute_bash(self, code: str, execution_id: str) -> dict:
|
128 |
+
try:
|
129 |
+
completed = subprocess.run(
|
130 |
+
code, shell=True, capture_output=True, text=True, timeout=self.max_execution_time
|
131 |
+
)
|
132 |
+
return {
|
133 |
+
"execution_id": execution_id,
|
134 |
+
"status": "success" if completed.returncode == 0 else "error",
|
135 |
+
"stdout": completed.stdout,
|
136 |
+
"stderr": completed.stderr,
|
137 |
+
"result": None,
|
138 |
+
"plots": [],
|
139 |
+
"dataframes": []
|
140 |
+
}
|
141 |
+
except subprocess.TimeoutExpired:
|
142 |
+
return {
|
143 |
+
"execution_id": execution_id,
|
144 |
+
"status": "error",
|
145 |
+
"stdout": "",
|
146 |
+
"stderr": "Execution timed out.",
|
147 |
+
"result": None,
|
148 |
+
"plots": [],
|
149 |
+
"dataframes": []
|
150 |
+
}
|
151 |
+
|
152 |
+
def _execute_sql(self, code: str, execution_id: str) -> dict:
|
153 |
+
result = {
|
154 |
+
"execution_id": execution_id,
|
155 |
+
"status": "error",
|
156 |
+
"stdout": "",
|
157 |
+
"stderr": "",
|
158 |
+
"result": None,
|
159 |
+
"plots": [],
|
160 |
+
"dataframes": []
|
161 |
+
}
|
162 |
+
try:
|
163 |
+
conn = sqlite3.connect(self.temp_sqlite_db)
|
164 |
+
cur = conn.cursor()
|
165 |
+
cur.execute(code)
|
166 |
+
if code.strip().lower().startswith("select"):
|
167 |
+
columns = [description[0] for description in cur.description]
|
168 |
+
rows = cur.fetchall()
|
169 |
+
df = pd.DataFrame(rows, columns=columns)
|
170 |
+
result["dataframes"].append({
|
171 |
+
"name": "query_result",
|
172 |
+
"head": df.head().to_dict(),
|
173 |
+
"shape": df.shape,
|
174 |
+
"dtypes": str(df.dtypes)
|
175 |
+
})
|
176 |
+
else:
|
177 |
+
conn.commit()
|
178 |
+
|
179 |
+
result["status"] = "success"
|
180 |
+
result["stdout"] = "Query executed successfully."
|
181 |
+
|
182 |
+
except Exception as e:
|
183 |
+
result["stderr"] = str(e)
|
184 |
+
finally:
|
185 |
+
conn.close()
|
186 |
+
|
187 |
+
return result
|
188 |
+
|
189 |
+
def _execute_c(self, code: str, execution_id: str) -> dict:
|
190 |
+
temp_dir = tempfile.mkdtemp()
|
191 |
+
source_path = os.path.join(temp_dir, "program.c")
|
192 |
+
binary_path = os.path.join(temp_dir, "program")
|
193 |
+
|
194 |
+
try:
|
195 |
+
with open(source_path, "w") as f:
|
196 |
+
f.write(code)
|
197 |
+
|
198 |
+
compile_proc = subprocess.run(
|
199 |
+
["gcc", source_path, "-o", binary_path],
|
200 |
+
capture_output=True, text=True, timeout=self.max_execution_time
|
201 |
+
)
|
202 |
+
if compile_proc.returncode != 0:
|
203 |
+
return {
|
204 |
+
"execution_id": execution_id,
|
205 |
+
"status": "error",
|
206 |
+
"stdout": compile_proc.stdout,
|
207 |
+
"stderr": compile_proc.stderr,
|
208 |
+
"result": None,
|
209 |
+
"plots": [],
|
210 |
+
"dataframes": []
|
211 |
+
}
|
212 |
+
|
213 |
+
run_proc = subprocess.run(
|
214 |
+
[binary_path],
|
215 |
+
capture_output=True, text=True, timeout=self.max_execution_time
|
216 |
+
)
|
217 |
+
return {
|
218 |
+
"execution_id": execution_id,
|
219 |
+
"status": "success" if run_proc.returncode == 0 else "error",
|
220 |
+
"stdout": run_proc.stdout,
|
221 |
+
"stderr": run_proc.stderr,
|
222 |
+
"result": None,
|
223 |
+
"plots": [],
|
224 |
+
"dataframes": []
|
225 |
+
}
|
226 |
+
except Exception as e:
|
227 |
+
return {
|
228 |
+
"execution_id": execution_id,
|
229 |
+
"status": "error",
|
230 |
+
"stdout": "",
|
231 |
+
"stderr": str(e),
|
232 |
+
"result": None,
|
233 |
+
"plots": [],
|
234 |
+
"dataframes": []
|
235 |
+
}
|
236 |
+
|
237 |
+
def _execute_java(self, code: str, execution_id: str) -> dict:
|
238 |
+
temp_dir = tempfile.mkdtemp()
|
239 |
+
source_path = os.path.join(temp_dir, "Main.java")
|
240 |
+
|
241 |
+
try:
|
242 |
+
with open(source_path, "w") as f:
|
243 |
+
f.write(code)
|
244 |
+
|
245 |
+
compile_proc = subprocess.run(
|
246 |
+
["javac", source_path],
|
247 |
+
capture_output=True, text=True, timeout=self.max_execution_time
|
248 |
+
)
|
249 |
+
if compile_proc.returncode != 0:
|
250 |
+
return {
|
251 |
+
"execution_id": execution_id,
|
252 |
+
"status": "error",
|
253 |
+
"stdout": compile_proc.stdout,
|
254 |
+
"stderr": compile_proc.stderr,
|
255 |
+
"result": None,
|
256 |
+
"plots": [],
|
257 |
+
"dataframes": []
|
258 |
+
}
|
259 |
+
|
260 |
+
run_proc = subprocess.run(
|
261 |
+
["java", "-cp", temp_dir, "Main"],
|
262 |
+
capture_output=True, text=True, timeout=self.max_execution_time
|
263 |
+
)
|
264 |
+
return {
|
265 |
+
"execution_id": execution_id,
|
266 |
+
"status": "success" if run_proc.returncode == 0 else "error",
|
267 |
+
"stdout": run_proc.stdout,
|
268 |
+
"stderr": run_proc.stderr,
|
269 |
+
"result": None,
|
270 |
+
"plots": [],
|
271 |
+
"dataframes": []
|
272 |
+
}
|
273 |
+
except Exception as e:
|
274 |
+
return {
|
275 |
+
"execution_id": execution_id,
|
276 |
+
"status": "error",
|
277 |
+
"stdout": "",
|
278 |
+
"stderr": str(e),
|
279 |
+
"result": None,
|
280 |
+
"plots": [],
|
281 |
+
"dataframes": []
|
282 |
+
}
|
283 |
+
|
284 |
+
|
285 |
+
interpreter_instance = CodeInterpreter()
|
286 |
+
|
287 |
+
@tool
|
288 |
+
def execute_code_multilang(code: str, language: str = "python") -> str:
|
289 |
+
"""Execute code in multiple languages (Python, Bash, SQL, C, Java) and return results.
|
290 |
+
Args:
|
291 |
+
code (str): The source code to execute.
|
292 |
+
language (str): The language of the code. Supported: "python", "bash", "sql", "c", "java".
|
293 |
+
Returns:
|
294 |
+
A string summarizing the execution results (stdout, stderr, errors, plots, dataframes if any).
|
295 |
+
"""
|
296 |
+
supported_languages = ["python", "bash", "sql", "c", "java"]
|
297 |
+
language = language.lower()
|
298 |
+
|
299 |
+
if language not in supported_languages:
|
300 |
+
return f"❌ Unsupported language: {language}. Supported languages are: {', '.join(supported_languages)}"
|
301 |
+
|
302 |
+
result = interpreter_instance.execute_code(code, language=language)
|
303 |
+
|
304 |
+
response = []
|
305 |
+
|
306 |
+
if result["status"] == "success":
|
307 |
+
response.append(f"✅ Code executed successfully in **{language.upper()}**")
|
308 |
+
|
309 |
+
if result.get("stdout"):
|
310 |
+
response.append(
|
311 |
+
"\n**Standard Output:**\n```\n" + result["stdout"].strip() + "\n```"
|
312 |
+
)
|
313 |
+
|
314 |
+
if result.get("stderr"):
|
315 |
+
response.append(
|
316 |
+
"\n**Standard Error (if any):**\n```\n"
|
317 |
+
+ result["stderr"].strip()
|
318 |
+
+ "\n```"
|
319 |
+
)
|
320 |
+
|
321 |
+
if result.get("result") is not None:
|
322 |
+
response.append(
|
323 |
+
"\n**Execution Result:**\n```\n"
|
324 |
+
+ str(result["result"]).strip()
|
325 |
+
+ "\n```"
|
326 |
+
)
|
327 |
+
|
328 |
+
if result.get("dataframes"):
|
329 |
+
for df_info in result["dataframes"]:
|
330 |
+
response.append(
|
331 |
+
f"\n**DataFrame `{df_info['name']}` (Shape: {df_info['shape']})**"
|
332 |
+
)
|
333 |
+
df_preview = pd.DataFrame(df_info["head"])
|
334 |
+
response.append("First 5 rows:\n```\n" + str(df_preview) + "\n```")
|
335 |
+
|
336 |
+
if result.get("plots"):
|
337 |
+
response.append(
|
338 |
+
f"\n**Generated {len(result['plots'])} plot(s)** (Image data returned separately)"
|
339 |
+
)
|
340 |
+
|
341 |
+
else:
|
342 |
+
response.append(f"❌ Code execution failed in **{language.upper()}**")
|
343 |
+
if result.get("stderr"):
|
344 |
+
response.append(
|
345 |
+
"\n**Error Log:**\n```\n" + result["stderr"].strip() + "\n```"
|
346 |
+
)
|
347 |
+
|
348 |
+
return "\n".join(response)
|
tools/documenttools.py
ADDED
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import tool
|
2 |
+
from typing import List, Dict, Any, Optional
|
3 |
+
import tempfile
|
4 |
+
from urllib.parse import urlparse
|
5 |
+
import os
|
6 |
+
import uuid
|
7 |
+
import requests
|
8 |
+
from PIL import Image
|
9 |
+
import pytesseract
|
10 |
+
import pandas as pd
|
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 |
+
|
33 |
+
@tool
|
34 |
+
def download_file_from_url(url: str, filename: Optional[str] = None) -> str:
|
35 |
+
"""
|
36 |
+
Download a file from a URL and save it to a temporary location.
|
37 |
+
Args:
|
38 |
+
url (str): the URL of the file to download.
|
39 |
+
filename (str, optional): the name of the file. If not provided, a random name file will be created.
|
40 |
+
"""
|
41 |
+
try:
|
42 |
+
# Parse URL to get filename if not provided
|
43 |
+
if not filename:
|
44 |
+
path = urlparse(url).path
|
45 |
+
filename = os.path.basename(path)
|
46 |
+
if not filename:
|
47 |
+
filename = f"downloaded_{uuid.uuid4().hex[:8]}"
|
48 |
+
|
49 |
+
# Create temporary file
|
50 |
+
temp_dir = tempfile.gettempdir()
|
51 |
+
filepath = os.path.join(temp_dir, filename)
|
52 |
+
|
53 |
+
# Download the file
|
54 |
+
response = requests.get(url, stream=True)
|
55 |
+
response.raise_for_status()
|
56 |
+
|
57 |
+
# Save the file
|
58 |
+
with open(filepath, "wb") as f:
|
59 |
+
for chunk in response.iter_content(chunk_size=8192):
|
60 |
+
f.write(chunk)
|
61 |
+
|
62 |
+
return f"File downloaded to {filepath}. You can read this file to process its contents."
|
63 |
+
except Exception as e:
|
64 |
+
return f"Error downloading file: {str(e)}"
|
65 |
+
|
66 |
+
|
67 |
+
@tool
|
68 |
+
def extract_text_from_image(image_path: str) -> str:
|
69 |
+
"""
|
70 |
+
Extract text from an image using OCR library pytesseract (if available).
|
71 |
+
Args:
|
72 |
+
image_path (str): the path to the image file.
|
73 |
+
"""
|
74 |
+
try:
|
75 |
+
# Open the image
|
76 |
+
image = Image.open(image_path)
|
77 |
+
|
78 |
+
# Extract text from the image
|
79 |
+
text = pytesseract.image_to_string(image)
|
80 |
+
|
81 |
+
return f"Extracted text from image:\n\n{text}"
|
82 |
+
except Exception as e:
|
83 |
+
return f"Error extracting text from image: {str(e)}"
|
84 |
+
|
85 |
+
|
86 |
+
@tool
|
87 |
+
def analyze_csv_file(file_path: str, query: str) -> str:
|
88 |
+
"""
|
89 |
+
Analyze a CSV file using pandas and answer a question about it.
|
90 |
+
Args:
|
91 |
+
file_path (str): the path to the CSV file.
|
92 |
+
query (str): Question about the data
|
93 |
+
"""
|
94 |
+
try:
|
95 |
+
# Read the CSV file
|
96 |
+
df = pd.read_csv(file_path)
|
97 |
+
|
98 |
+
# Run various analyses based on the query
|
99 |
+
result = f"CSV file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
|
100 |
+
result += f"Columns: {', '.join(df.columns)}\n\n"
|
101 |
+
|
102 |
+
# Add summary statistics
|
103 |
+
result += "Summary statistics:\n"
|
104 |
+
result += str(df.describe())
|
105 |
+
|
106 |
+
return result
|
107 |
+
|
108 |
+
except Exception as e:
|
109 |
+
return f"Error analyzing CSV file: {str(e)}"
|
110 |
+
|
111 |
+
|
112 |
+
@tool
|
113 |
+
def analyze_excel_file(file_path: str, query: str) -> str:
|
114 |
+
"""
|
115 |
+
Analyze an Excel file using pandas and answer a question about it.
|
116 |
+
Args:
|
117 |
+
file_path (str): the path to the Excel file.
|
118 |
+
query (str): Question about the data
|
119 |
+
"""
|
120 |
+
try:
|
121 |
+
# Read the Excel file
|
122 |
+
df = pd.read_excel(file_path)
|
123 |
+
|
124 |
+
# Run various analyses based on the query
|
125 |
+
result = (
|
126 |
+
f"Excel file loaded with {len(df)} rows and {len(df.columns)} columns.\n"
|
127 |
+
)
|
128 |
+
result += f"Columns: {', '.join(df.columns)}\n\n"
|
129 |
+
|
130 |
+
# Add summary statistics
|
131 |
+
result += "Summary statistics:\n"
|
132 |
+
result += str(df.describe())
|
133 |
+
|
134 |
+
return result
|
135 |
+
|
136 |
+
except Exception as e:
|
137 |
+
return f"Error analyzing Excel file: {str(e)}"
|
tools/imagetools.py
ADDED
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from langchain_core.tools import tool
|
2 |
+
import os
|
3 |
+
import io
|
4 |
+
import base64
|
5 |
+
import uuid
|
6 |
+
from PIL import Image
|
7 |
+
from typing import List, Dict, Any, Optional
|
8 |
+
import numpy as np
|
9 |
+
from PIL import Image, ImageDraw, ImageFont, ImageEnhance, ImageFilter
|
10 |
+
|
11 |
+
# Helper functions for image processing
|
12 |
+
def encode_image(image_path: str) -> str:
|
13 |
+
"""Convert an image file to base64 string."""
|
14 |
+
with open(image_path, "rb") as image_file:
|
15 |
+
return base64.b64encode(image_file.read()).decode("utf-8")
|
16 |
+
|
17 |
+
|
18 |
+
def decode_image(base64_string: str) -> Image.Image:
|
19 |
+
"""Convert a base64 string to a PIL Image."""
|
20 |
+
image_data = base64.b64decode(base64_string)
|
21 |
+
return Image.open(io.BytesIO(image_data))
|
22 |
+
|
23 |
+
|
24 |
+
def save_image(image: Image.Image, directory: str = "image_outputs") -> str:
|
25 |
+
"""Save a PIL Image to disk and return the path."""
|
26 |
+
os.makedirs(directory, exist_ok=True)
|
27 |
+
image_id = str(uuid.uuid4())
|
28 |
+
image_path = os.path.join(directory, f"{image_id}.png")
|
29 |
+
image.save(image_path)
|
30 |
+
return image_path
|
31 |
+
|
32 |
+
|
33 |
+
@tool
|
34 |
+
def analyze_image(image_base64: str) -> Dict[str, Any]:
|
35 |
+
"""
|
36 |
+
Analyze basic properties of an image (size, mode, color analysis, thumbnail preview).
|
37 |
+
Args:
|
38 |
+
image_base64 (str): Base64 encoded image string
|
39 |
+
Returns:
|
40 |
+
Dictionary with analysis result
|
41 |
+
"""
|
42 |
+
try:
|
43 |
+
img = decode_image(image_base64)
|
44 |
+
width, height = img.size
|
45 |
+
mode = img.mode
|
46 |
+
|
47 |
+
if mode in ("RGB", "RGBA"):
|
48 |
+
arr = np.array(img)
|
49 |
+
avg_colors = arr.mean(axis=(0, 1))
|
50 |
+
dominant = ["Red", "Green", "Blue"][np.argmax(avg_colors[:3])]
|
51 |
+
brightness = avg_colors.mean()
|
52 |
+
color_analysis = {
|
53 |
+
"average_rgb": avg_colors.tolist(),
|
54 |
+
"brightness": brightness,
|
55 |
+
"dominant_color": dominant,
|
56 |
+
}
|
57 |
+
else:
|
58 |
+
color_analysis = {"note": f"No color analysis for mode {mode}"}
|
59 |
+
|
60 |
+
thumbnail = img.copy()
|
61 |
+
thumbnail.thumbnail((100, 100))
|
62 |
+
thumb_path = save_image(thumbnail, "thumbnails")
|
63 |
+
thumbnail_base64 = encode_image(thumb_path)
|
64 |
+
|
65 |
+
return {
|
66 |
+
"dimensions": (width, height),
|
67 |
+
"mode": mode,
|
68 |
+
"color_analysis": color_analysis,
|
69 |
+
"thumbnail": thumbnail_base64,
|
70 |
+
}
|
71 |
+
except Exception as e:
|
72 |
+
return {"error": str(e)}
|
73 |
+
|
74 |
+
|
75 |
+
@tool
|
76 |
+
def transform_image(
|
77 |
+
image_base64: str, operation: str, params: Optional[Dict[str, Any]] = None
|
78 |
+
) -> Dict[str, Any]:
|
79 |
+
"""
|
80 |
+
Apply transformations: resize, rotate, crop, flip, brightness, contrast, blur, sharpen, grayscale.
|
81 |
+
Args:
|
82 |
+
image_base64 (str): Base64 encoded input image
|
83 |
+
operation (str): Transformation operation
|
84 |
+
params (Dict[str, Any], optional): Parameters for the operation
|
85 |
+
Returns:
|
86 |
+
Dictionary with transformed image (base64)
|
87 |
+
"""
|
88 |
+
try:
|
89 |
+
img = decode_image(image_base64)
|
90 |
+
params = params or {}
|
91 |
+
|
92 |
+
if operation == "resize":
|
93 |
+
img = img.resize(
|
94 |
+
(
|
95 |
+
params.get("width", img.width // 2),
|
96 |
+
params.get("height", img.height // 2),
|
97 |
+
)
|
98 |
+
)
|
99 |
+
elif operation == "rotate":
|
100 |
+
img = img.rotate(params.get("angle", 90), expand=True)
|
101 |
+
elif operation == "crop":
|
102 |
+
img = img.crop(
|
103 |
+
(
|
104 |
+
params.get("left", 0),
|
105 |
+
params.get("top", 0),
|
106 |
+
params.get("right", img.width),
|
107 |
+
params.get("bottom", img.height),
|
108 |
+
)
|
109 |
+
)
|
110 |
+
elif operation == "flip":
|
111 |
+
if params.get("direction", "horizontal") == "horizontal":
|
112 |
+
img = img.transpose(Image.FLIP_LEFT_RIGHT)
|
113 |
+
else:
|
114 |
+
img = img.transpose(Image.FLIP_TOP_BOTTOM)
|
115 |
+
elif operation == "adjust_brightness":
|
116 |
+
img = ImageEnhance.Brightness(img).enhance(params.get("factor", 1.5))
|
117 |
+
elif operation == "adjust_contrast":
|
118 |
+
img = ImageEnhance.Contrast(img).enhance(params.get("factor", 1.5))
|
119 |
+
elif operation == "blur":
|
120 |
+
img = img.filter(ImageFilter.GaussianBlur(params.get("radius", 2)))
|
121 |
+
elif operation == "sharpen":
|
122 |
+
img = img.filter(ImageFilter.SHARPEN)
|
123 |
+
elif operation == "grayscale":
|
124 |
+
img = img.convert("L")
|
125 |
+
else:
|
126 |
+
return {"error": f"Unknown operation: {operation}"}
|
127 |
+
|
128 |
+
result_path = save_image(img)
|
129 |
+
result_base64 = encode_image(result_path)
|
130 |
+
return {"transformed_image": result_base64}
|
131 |
+
|
132 |
+
except Exception as e:
|
133 |
+
return {"error": str(e)}
|
134 |
+
|
135 |
+
|
136 |
+
@tool
|
137 |
+
def draw_on_image(
|
138 |
+
image_base64: str, drawing_type: str, params: Dict[str, Any]
|
139 |
+
) -> Dict[str, Any]:
|
140 |
+
"""
|
141 |
+
Draw shapes (rectangle, circle, line) or text onto an image.
|
142 |
+
Args:
|
143 |
+
image_base64 (str): Base64 encoded input image
|
144 |
+
drawing_type (str): Drawing type
|
145 |
+
params (Dict[str, Any]): Drawing parameters
|
146 |
+
Returns:
|
147 |
+
Dictionary with result image (base64)
|
148 |
+
"""
|
149 |
+
try:
|
150 |
+
img = decode_image(image_base64)
|
151 |
+
draw = ImageDraw.Draw(img)
|
152 |
+
color = params.get("color", "red")
|
153 |
+
|
154 |
+
if drawing_type == "rectangle":
|
155 |
+
draw.rectangle(
|
156 |
+
[params["left"], params["top"], params["right"], params["bottom"]],
|
157 |
+
outline=color,
|
158 |
+
width=params.get("width", 2),
|
159 |
+
)
|
160 |
+
elif drawing_type == "circle":
|
161 |
+
x, y, r = params["x"], params["y"], params["radius"]
|
162 |
+
draw.ellipse(
|
163 |
+
(x - r, y - r, x + r, y + r),
|
164 |
+
outline=color,
|
165 |
+
width=params.get("width", 2),
|
166 |
+
)
|
167 |
+
elif drawing_type == "line":
|
168 |
+
draw.line(
|
169 |
+
(
|
170 |
+
params["start_x"],
|
171 |
+
params["start_y"],
|
172 |
+
params["end_x"],
|
173 |
+
params["end_y"],
|
174 |
+
),
|
175 |
+
fill=color,
|
176 |
+
width=params.get("width", 2),
|
177 |
+
)
|
178 |
+
elif drawing_type == "text":
|
179 |
+
font_size = params.get("font_size", 20)
|
180 |
+
try:
|
181 |
+
font = ImageFont.truetype("arial.ttf", font_size)
|
182 |
+
except IOError:
|
183 |
+
font = ImageFont.load_default()
|
184 |
+
draw.text(
|
185 |
+
(params["x"], params["y"]),
|
186 |
+
params.get("text", "Text"),
|
187 |
+
fill=color,
|
188 |
+
font=font,
|
189 |
+
)
|
190 |
+
else:
|
191 |
+
return {"error": f"Unknown drawing type: {drawing_type}"}
|
192 |
+
|
193 |
+
result_path = save_image(img)
|
194 |
+
result_base64 = encode_image(result_path)
|
195 |
+
return {"result_image": result_base64}
|
196 |
+
|
197 |
+
except Exception as e:
|
198 |
+
return {"error": str(e)}
|
199 |
+
|
200 |
+
|
201 |
+
@tool
|
202 |
+
def generate_simple_image(
|
203 |
+
image_type: str,
|
204 |
+
width: int = 500,
|
205 |
+
height: int = 500,
|
206 |
+
params: Optional[Dict[str, Any]] = None,
|
207 |
+
) -> Dict[str, Any]:
|
208 |
+
"""
|
209 |
+
Generate a simple image (gradient, noise, pattern, chart).
|
210 |
+
Args:
|
211 |
+
image_type (str): Type of image
|
212 |
+
width (int), height (int)
|
213 |
+
params (Dict[str, Any], optional): Specific parameters
|
214 |
+
Returns:
|
215 |
+
Dictionary with generated image (base64)
|
216 |
+
"""
|
217 |
+
try:
|
218 |
+
params = params or {}
|
219 |
+
|
220 |
+
if image_type == "gradient":
|
221 |
+
direction = params.get("direction", "horizontal")
|
222 |
+
start_color = params.get("start_color", (255, 0, 0))
|
223 |
+
end_color = params.get("end_color", (0, 0, 255))
|
224 |
+
|
225 |
+
img = Image.new("RGB", (width, height))
|
226 |
+
draw = ImageDraw.Draw(img)
|
227 |
+
|
228 |
+
if direction == "horizontal":
|
229 |
+
for x in range(width):
|
230 |
+
r = int(
|
231 |
+
start_color[0] + (end_color[0] - start_color[0]) * x / width
|
232 |
+
)
|
233 |
+
g = int(
|
234 |
+
start_color[1] + (end_color[1] - start_color[1]) * x / width
|
235 |
+
)
|
236 |
+
b = int(
|
237 |
+
start_color[2] + (end_color[2] - start_color[2]) * x / width
|
238 |
+
)
|
239 |
+
draw.line([(x, 0), (x, height)], fill=(r, g, b))
|
240 |
+
else:
|
241 |
+
for y in range(height):
|
242 |
+
r = int(
|
243 |
+
start_color[0] + (end_color[0] - start_color[0]) * y / height
|
244 |
+
)
|
245 |
+
g = int(
|
246 |
+
start_color[1] + (end_color[1] - start_color[1]) * y / height
|
247 |
+
)
|
248 |
+
b = int(
|
249 |
+
start_color[2] + (end_color[2] - start_color[2]) * y / height
|
250 |
+
)
|
251 |
+
draw.line([(0, y), (width, y)], fill=(r, g, b))
|
252 |
+
|
253 |
+
elif image_type == "noise":
|
254 |
+
noise_array = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8)
|
255 |
+
img = Image.fromarray(noise_array, "RGB")
|
256 |
+
|
257 |
+
else:
|
258 |
+
return {"error": f"Unsupported image_type {image_type}"}
|
259 |
+
|
260 |
+
result_path = save_image(img)
|
261 |
+
result_base64 = encode_image(result_path)
|
262 |
+
return {"generated_image": result_base64}
|
263 |
+
|
264 |
+
except Exception as e:
|
265 |
+
return {"error": str(e)}
|
266 |
+
|
267 |
+
|
268 |
+
@tool
|
269 |
+
def combine_images(
|
270 |
+
images_base64: List[str], operation: str, params: Optional[Dict[str, Any]] = None
|
271 |
+
) -> Dict[str, Any]:
|
272 |
+
"""
|
273 |
+
Combine multiple images (collage, stack, blend).
|
274 |
+
Args:
|
275 |
+
images_base64 (List[str]): List of base64 images
|
276 |
+
operation (str): Combination type
|
277 |
+
params (Dict[str, Any], optional)
|
278 |
+
Returns:
|
279 |
+
Dictionary with combined image (base64)
|
280 |
+
"""
|
281 |
+
try:
|
282 |
+
images = [decode_image(b64) for b64 in images_base64]
|
283 |
+
params = params or {}
|
284 |
+
|
285 |
+
if operation == "stack":
|
286 |
+
direction = params.get("direction", "horizontal")
|
287 |
+
if direction == "horizontal":
|
288 |
+
total_width = sum(img.width for img in images)
|
289 |
+
max_height = max(img.height for img in images)
|
290 |
+
new_img = Image.new("RGB", (total_width, max_height))
|
291 |
+
x = 0
|
292 |
+
for img in images:
|
293 |
+
new_img.paste(img, (x, 0))
|
294 |
+
x += img.width
|
295 |
+
else:
|
296 |
+
max_width = max(img.width for img in images)
|
297 |
+
total_height = sum(img.height for img in images)
|
298 |
+
new_img = Image.new("RGB", (max_width, total_height))
|
299 |
+
y = 0
|
300 |
+
for img in images:
|
301 |
+
new_img.paste(img, (0, y))
|
302 |
+
y += img.height
|
303 |
+
else:
|
304 |
+
return {"error": f"Unsupported combination operation {operation}"}
|
305 |
+
|
306 |
+
result_path = save_image(new_img)
|
307 |
+
result_base64 = encode_image(result_path)
|
308 |
+
return {"combined_image": result_base64}
|
309 |
+
|
310 |
+
except Exception as e:
|
311 |
+
return {"error": str(e)}
|
tools/mathtools.py
CHANGED
@@ -2,53 +2,80 @@ from langchain_core.tools import tool
|
|
2 |
|
3 |
|
4 |
@tool
|
5 |
-
def multiply(a:
|
6 |
-
"""
|
7 |
-
|
8 |
Args:
|
9 |
-
a: first
|
10 |
-
b: second
|
11 |
"""
|
12 |
return a * b
|
13 |
|
|
|
14 |
@tool
|
15 |
-
def add(a:
|
16 |
-
"""
|
17 |
-
|
18 |
Args:
|
19 |
-
a: first
|
20 |
-
b: second
|
21 |
"""
|
22 |
return a + b
|
23 |
|
|
|
24 |
@tool
|
25 |
-
def subtract(a:
|
26 |
-
"""
|
27 |
-
|
28 |
Args:
|
29 |
-
a: first
|
30 |
-
b: second
|
31 |
"""
|
32 |
return a - b
|
33 |
|
|
|
34 |
@tool
|
35 |
-
def divide(a:
|
36 |
-
"""
|
37 |
-
|
38 |
Args:
|
39 |
-
a: first
|
40 |
-
b: second
|
41 |
"""
|
42 |
if b == 0:
|
43 |
-
raise ValueError("Cannot
|
44 |
return a / b
|
45 |
|
|
|
46 |
@tool
|
47 |
def modulus(a: int, b: int) -> int:
|
48 |
-
"""
|
49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
50 |
Args:
|
51 |
-
a:
|
52 |
-
b: second int
|
53 |
"""
|
54 |
-
|
|
|
|
|
|
2 |
|
3 |
|
4 |
@tool
|
5 |
+
def multiply(a: float, b: float) -> float:
|
6 |
+
"""
|
7 |
+
Multiplies two numbers.
|
8 |
Args:
|
9 |
+
a (float): the first number
|
10 |
+
b (float): the second number
|
11 |
"""
|
12 |
return a * b
|
13 |
|
14 |
+
|
15 |
@tool
|
16 |
+
def add(a: float, b: float) -> float:
|
17 |
+
"""
|
18 |
+
Adds two numbers.
|
19 |
Args:
|
20 |
+
a (float): the first number
|
21 |
+
b (float): the second number
|
22 |
"""
|
23 |
return a + b
|
24 |
|
25 |
+
|
26 |
@tool
|
27 |
+
def subtract(a: float, b: float) -> int:
|
28 |
+
"""
|
29 |
+
Subtracts two numbers.
|
30 |
Args:
|
31 |
+
a (float): the first number
|
32 |
+
b (float): the second number
|
33 |
"""
|
34 |
return a - b
|
35 |
|
36 |
+
|
37 |
@tool
|
38 |
+
def divide(a: float, b: float) -> float:
|
39 |
+
"""
|
40 |
+
Divides two numbers.
|
41 |
Args:
|
42 |
+
a (float): the first float number
|
43 |
+
b (float): the second float number
|
44 |
"""
|
45 |
if b == 0:
|
46 |
+
raise ValueError("Cannot divided by zero.")
|
47 |
return a / b
|
48 |
|
49 |
+
|
50 |
@tool
|
51 |
def modulus(a: int, b: int) -> int:
|
52 |
+
"""
|
53 |
+
Get the modulus of two numbers.
|
54 |
+
Args:
|
55 |
+
a (int): the first number
|
56 |
+
b (int): the second number
|
57 |
+
"""
|
58 |
+
return a % b
|
59 |
+
|
60 |
+
|
61 |
+
@tool
|
62 |
+
def power(a: float, b: float) -> float:
|
63 |
+
"""
|
64 |
+
Get the power of two numbers.
|
65 |
+
Args:
|
66 |
+
a (float): the first number
|
67 |
+
b (float): the second number
|
68 |
+
"""
|
69 |
+
return a**b
|
70 |
+
|
71 |
+
|
72 |
+
@tool
|
73 |
+
def square_root(a: float) -> float | complex:
|
74 |
+
"""
|
75 |
+
Get the square root of a number.
|
76 |
Args:
|
77 |
+
a (float): the number to get the square root of
|
|
|
78 |
"""
|
79 |
+
if a >= 0:
|
80 |
+
return a**0.5
|
81 |
+
return cmath.sqrt(a)
|
tools/searchtools.py
CHANGED
@@ -27,44 +27,51 @@ question_retrieve_tool = create_retriever_tool(
|
|
27 |
)
|
28 |
|
29 |
|
|
|
30 |
@tool
|
31 |
-
def wiki_search(
|
32 |
"""Search Wikipedia for a query and return maximum 2 results.
|
33 |
-
|
34 |
Args:
|
35 |
-
|
36 |
-
search_docs = WikipediaLoader(query=
|
37 |
formatted_search_docs = "\n\n---\n\n".join(
|
38 |
[
|
39 |
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
|
40 |
for doc in search_docs
|
41 |
-
]
|
|
|
42 |
return {"wiki_results": formatted_search_docs}
|
43 |
|
|
|
44 |
@tool
|
45 |
-
def web_search(
|
46 |
"""Search Tavily for a query and return maximum 3 results.
|
47 |
-
|
48 |
Args:
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
@tool
|
56 |
-
def
|
57 |
"""Search Arxiv for a query and return maximum 3 result.
|
58 |
-
|
59 |
Args:
|
60 |
-
|
61 |
-
search_docs = ArxivLoader(query=
|
62 |
formatted_search_docs = "\n\n---\n\n".join(
|
63 |
[
|
64 |
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
|
65 |
for doc in search_docs
|
66 |
-
]
|
67 |
-
|
|
|
|
|
68 |
|
69 |
@tool
|
70 |
def similar_question_search(question: str) -> str:
|
|
|
27 |
)
|
28 |
|
29 |
|
30 |
+
|
31 |
@tool
|
32 |
+
def wiki_search(query: str) -> str:
|
33 |
"""Search Wikipedia for a query and return maximum 2 results.
|
|
|
34 |
Args:
|
35 |
+
query: The search query."""
|
36 |
+
search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
|
37 |
formatted_search_docs = "\n\n---\n\n".join(
|
38 |
[
|
39 |
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
|
40 |
for doc in search_docs
|
41 |
+
]
|
42 |
+
)
|
43 |
return {"wiki_results": formatted_search_docs}
|
44 |
|
45 |
+
|
46 |
@tool
|
47 |
+
def web_search(query: str) -> str:
|
48 |
"""Search Tavily for a query and return maximum 3 results.
|
|
|
49 |
Args:
|
50 |
+
query: The search query."""
|
51 |
+
search_docs = TavilySearchResults(max_results=3).invoke(query=query)
|
52 |
+
formatted_search_docs = "\n\n---\n\n".join(
|
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 {"web_results": formatted_search_docs}
|
59 |
+
|
60 |
|
61 |
@tool
|
62 |
+
def arxiv_search(query: str) -> str:
|
63 |
"""Search Arxiv for a query and return maximum 3 result.
|
|
|
64 |
Args:
|
65 |
+
query: The search query."""
|
66 |
+
search_docs = ArxivLoader(query=query, load_max_docs=3).load()
|
67 |
formatted_search_docs = "\n\n---\n\n".join(
|
68 |
[
|
69 |
f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
|
70 |
for doc in search_docs
|
71 |
+
]
|
72 |
+
)
|
73 |
+
return {"arxiv_results": formatted_search_docs}
|
74 |
+
|
75 |
|
76 |
@tool
|
77 |
def similar_question_search(question: str) -> str:
|