|
from smolagents import DuckDuckGoSearchTool, GoogleSearchTool |
|
from youtube_transcript_api import YouTubeTranscriptApi |
|
from supadata import Supadata, SupadataError |
|
import wikipedia |
|
from wikipedia_tables_parser import fetch_wikipedia_tables |
|
import pandas as pd |
|
from typing import Any |
|
import os |
|
from dotenv import load_dotenv |
|
|
|
load_dotenv() |
|
import importlib.util |
|
import sys |
|
import io |
|
import contextlib |
|
from llama_index.llms.openrouter import OpenRouter |
|
from llama_index.core.types import ChatMessage |
|
|
|
|
|
llm = OpenRouter( |
|
api_key=os.getenv("OPENROUTER_API_KEY"), |
|
model="google/gemini-2.5-flash-preview", |
|
temperature=0.7, |
|
) |
|
supadata = Supadata(api_key=os.getenv("SUPADATA_API_KEY")) |
|
|
|
|
|
def reverse_text(text: str, **kwargs) -> str: |
|
""" |
|
Returns the reversed version of the text. |
|
If you receive some unknown text, that can't be recognized and analyzed, then you need to use this tool to make it clear. |
|
|
|
Args: |
|
text: text to be reversed |
|
|
|
Return: |
|
The reversed text. |
|
""" |
|
try: |
|
print(text[::-1]) |
|
return text[::-1] |
|
except Exception as e: |
|
raise ValueError(f"Can't reverse text: {e}") |
|
|
|
|
|
def fetch_historical_event_data(event_name: str, year: str, **kwargs) -> str: |
|
""" |
|
Fetches data about historical event that occured in certain year. |
|
Some examples of events: Olympics games, Footbal games, NBA etc. |
|
|
|
Args: |
|
event_name: String name of the event |
|
year: String year of the event |
|
|
|
Return: |
|
String with data about the event |
|
""" |
|
result = wikipedia.page(f"{event_name} in {year}") |
|
|
|
url = result.url |
|
content = result.content |
|
try: |
|
tables = pd.read_html(url) |
|
except Exception as e: |
|
tables = fetch_wikipedia_tables(url) |
|
|
|
result = f"Content: {content}\nTables: {tables}" |
|
|
|
return result |
|
|
|
|
|
def classify_fruit_vegitable(item: str, **kwargs) -> str: |
|
""" |
|
Classifies items to fruits and vegitables |
|
|
|
Args: |
|
item: Item to classify |
|
|
|
Returns: |
|
Text with explanation whether it is a fruit or vegetable. |
|
""" |
|
response = llm.chat( |
|
messages=[ |
|
ChatMessage( |
|
content=f"Classify whether it is fruit or vegetable: {item}. Return only `fruit` or `vegetable` without explanations" |
|
) |
|
] |
|
) |
|
return response.message.content |
|
|
|
|
|
def web_search(query: str, **kwargs) -> str: |
|
""" |
|
Returns web search results for the provided query. |
|
Don't use it for Wikipedia queries. For Wikipedia queries use wikipedia_search tool. |
|
Important, query is human-language string input, not the URL or key. |
|
|
|
Args: |
|
query: query to search in WEB |
|
|
|
Return: |
|
String with web search results. |
|
""" |
|
result = DuckDuckGoSearchTool().forward(query) |
|
|
|
print(result) |
|
return result |
|
|
|
|
|
def wikipedia_search(query: str, **kwargs) -> Any: |
|
""" |
|
Returns wikipedia search results for the provided query. |
|
|
|
Args: |
|
query: query to search in WIKIPEDIA |
|
|
|
Return: |
|
Wikipedia search results. |
|
""" |
|
result = wikipedia.page(query) |
|
|
|
url = result.url |
|
content = result.content |
|
try: |
|
tables = pd.read_html(url) |
|
except: |
|
tables = fetch_wikipedia_tables(url) |
|
|
|
result = f"Content: {content}\nTables: {tables}" |
|
|
|
return result |
|
|
|
|
|
def multiply(a: float, b: float, **kwargs) -> float: |
|
""" |
|
Multiply two numbers. |
|
|
|
Args: |
|
a: First number |
|
b: Second number |
|
|
|
Return: |
|
The product of the two numbers. |
|
""" |
|
return a * b |
|
|
|
|
|
def compute_sum(values: list[int | float], **kwargs) -> float: |
|
""" |
|
Computes sum of provided values |
|
|
|
Args: |
|
values: list of integer or float values |
|
Return: |
|
Sum of the values |
|
""" |
|
return sum(values) |
|
|
|
|
|
def length(iterable: Any, **kwargs) -> int: |
|
""" |
|
Return the length of an iterable. |
|
|
|
Args: |
|
iterable: Any iterable |
|
|
|
Return: |
|
The length of the iterable. |
|
""" |
|
return len(iterable) |
|
|
|
|
|
def execute_python_file(file_path: str) -> Any: |
|
""" |
|
Executes a Python file and returns its result. |
|
|
|
This function takes a path to a Python file, executes it by importing it as a module, |
|
and returns the result. The file should contain a function call that produces |
|
the result to be returned. This version also executes code under the |
|
'if __name__ == "__main__":' block. |
|
|
|
Args: |
|
file_path (str): Path to the Python file to execute. |
|
|
|
Returns: |
|
Any: The result of executing the Python file. If the file sets a variable |
|
named 'result', that value will be returned. |
|
|
|
Raises: |
|
FileNotFoundError: If the specified file does not exist. |
|
ImportError: If there was an error importing the Python file. |
|
|
|
Example: |
|
>>> # If example.py contains: result = 2 + 3 |
|
>>> execute_python_file('example.py') |
|
5 |
|
""" |
|
|
|
if not os.path.isfile(file_path): |
|
raise FileNotFoundError(f"File not found: {file_path}") |
|
|
|
|
|
file_dir = os.path.dirname(os.path.abspath(file_path)) |
|
file_name = os.path.basename(file_path) |
|
module_name = file_name.replace(".py", "") |
|
|
|
|
|
original_sys_path = sys.path.copy() |
|
sys.path.insert(0, file_dir) |
|
|
|
|
|
stdout_capture = io.StringIO() |
|
stderr_capture = io.StringIO() |
|
|
|
|
|
try: |
|
spec = importlib.util.spec_from_file_location(module_name, file_path) |
|
if spec is None or spec.loader is None: |
|
raise ImportError(f"Could not load module spec from {file_path}") |
|
|
|
module = importlib.util.module_from_spec(spec) |
|
sys.modules[module_name] = module |
|
|
|
|
|
with contextlib.redirect_stdout(stdout_capture), contextlib.redirect_stderr( |
|
stderr_capture |
|
): |
|
spec.loader.exec_module(module) |
|
|
|
|
|
|
|
with open(file_path, "r") as f: |
|
file_content = f.read() |
|
|
|
namespace = {**module.__dict__} |
|
namespace["__name__"] = "__main__" |
|
exec(file_content, namespace) |
|
|
|
if hasattr(module, "result"): |
|
return module.result |
|
else: |
|
output = stdout_capture.getvalue().strip() |
|
print(f"RESULT PYTHON: {output}") |
|
return output |
|
|
|
except Exception as e: |
|
error_output = stderr_capture.getvalue() |
|
if error_output: |
|
raise type(e)(f"{str(e)}\nProgram output: {error_output}") from None |
|
else: |
|
raise |
|
finally: |
|
sys.path = original_sys_path |
|
|
|
if module_name in sys.modules: |
|
del sys.modules[module_name] |
|
|
|
|
|
def trascript_youtube(video_id: str, **kwargs) -> str: |
|
""" |
|
Returns transcript of YouTube video. |
|
|
|
Args: |
|
video_id: ID of youtube video (Pass in the video ID, NOT the video URL. For a video with the URL https://www.youtube.com/watch?v=12345 the ID is 12345.) |
|
|
|
Return: |
|
Transcript of YouTube video. |
|
""" |
|
transcript = supadata.youtube.transcript(video_id=video_id, lang="en") |
|
|
|
return transcript.content |
|
|
|
|
|
def read_excel(path: str, **kwargs) -> pd.DataFrame: |
|
""" |
|
Reads xlsx file |
|
|
|
Args: |
|
path: path to xlsx file |
|
|
|
Return: |
|
Pandas dataframe |
|
""" |
|
return pd.read_excel(path) |
|
|
|
|
|
def pandas_column_sum( |
|
pandas_column_values: list[int | float], column_name: str, **kwargs |
|
) -> float: |
|
""" |
|
Computes sum on pandas dataframe column |
|
|
|
Args: |
|
pandas_column_values: List with float or integer pandas values |
|
column_name: Name of the column |
|
|
|
Return: |
|
Sum of the column |
|
""" |
|
return sum(pandas_column_values) |
|
|
|
|
|
def final_answer(answer: str, **kwargs) -> str: |
|
""" |
|
Prepare the final answer for the user. It should be always used as a last step. |
|
|
|
Args: |
|
answer: The answer to format and return to the user |
|
Return: |
|
The final answer. |
|
""" |
|
resp = llm.chat( |
|
messages=[ |
|
ChatMessage( |
|
content=f""" |
|
Final answer from agent: {answer} |
|
Make final answer as short as possible. |
|
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 above rules depending of whether the element to be put in the list is a number or a string. |
|
There might be requested exact number, then you need to compress the output so that it was only number without any comments or explanations (float or integer). |
|
And on the other hand, the question might request some exact string value. Don't explain it, just return this value (For example, insted of `In response to the question, desired person is X` return only `X`) |
|
Again, you don't need to modify or solve answer, you just need to format it properly. |
|
""" |
|
) |
|
] |
|
) |
|
return resp.message.content |
|
|
|
|
|
|
|
|
|
|