File size: 12,890 Bytes
a7c3281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7a36d3c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a7c3281
 
 
 
 
 
 
 
 
 
 
 
 
7a36d3c
a7c3281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
import os
from dotenv import load_dotenv
from langgraph.graph import START, StateGraph, MessagesState
from langgraph.prebuilt import tools_condition, ToolNode
from langchain_google_genai import ChatGoogleGenerativeAI, GoogleGenerativeAIEmbeddings
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_community.document_loaders import WikipediaLoader, JSONLoader
from langchain_community.vectorstores import Chroma
from langchain_core.messages import SystemMessage, HumanMessage
from langchain_core.tools import tool
from langchain.tools.retriever import create_retriever_tool
from PIL import Image
import pandas as pd
import numpy as np
import google.generativeai as genai
import subprocess


load_dotenv()

genai.configure(api_key=os.environ["GOOGLE_API_KEY"])


@tool
def wiki_search(query: str) -> str:
    """
    Search Wikipedia for the content of a specific article. Use this tool to get the current information, facts, and data from a Wikipedia page.
    This tool is NOT for finding out about Wikipedia's edit history or discussions; use web_search for that.

    Args:
        query: The search query, ideally the exact title of the Wikipedia page.
    """
    search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
    formatted_search_docs = "\n\n---\n\n".join(
        [
            f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
            for doc in search_docs
        ])
    return formatted_search_docs


tavily_search = TavilySearchResults(max_results=3)


@tool
def web_search(query: str) -> str:
    """
    Search the general web for information, news, or to answer questions that require up-to-date information or knowledge about a page's history (like edit history).
    Use this tool when you need to find information that is not the content of a specific page, for example, 'who nominated a specific article' or 'when was a page created'.

    Args:
        query: The search query.
    """
    search_docs = tavily_search.invoke(query)
    formatted_search_docs = "\n\n---\n\n".join(
        [
            f'<Document source="{doc["url"]}">{doc["content"]}</Document>'
            for doc in search_docs
        ]
    )
    return formatted_search_docs


@tool
def process_image(image_path: str, question: str) -> str:
    """
    Process an image file and answer questions about it. Use this when a question refers to an image.
    The `image_path` is the name of the file (e.g., 'image.png') provided in the question's 'file_name'.

    Args:
        image_path: Path to the image file.
        question: Question about the image.
    """
    try:
        model = genai.GenerativeModel('gemini-2.5-flash')
        image = Image.open(image_path)
        response = model.generate_content([question, image])
        return response.text
    except Exception as e:
        return f"Error processing image: {str(e)}"


@tool
def process_audio(audio_path: str, question: str) -> str:
    """
    Process an audio file and extract information. Use this when a question refers to an audio file (e.g., .mp3, .wav).
    The `audio_path` is the name of the file (e.g., 'audio.mp3') provided in the question's 'file_name'.

    Args:
        audio_path: Path to the audio file.
        question: Question about the audio content.
    """
    try:
        model = genai.GenerativeModel('gemini-2.5-flash')
        audio_file = genai.upload_file(path=audio_path)
        response = model.generate_content([question, audio_file])
        return response.text
    except Exception as e:
        return f"Error processing audio: {str(e)}"


@tool
def process_excel_file(file_path: str, question: str) -> str:
    """
    Process an Excel file by loading it into a pandas DataFrame and using code to answer a question about it.
    Use this for complex queries about data in an Excel file (.xlsx).
    The `file_path` is the name of the file provided in the question's 'file_name'.

    Args:
        file_path: Path to the Excel file.
        question: The question to answer about the Excel data.
    """
    try:
        df = pd.read_excel(file_path)
        code_gen_llm = ChatGoogleGenerativeAI(
            model="gemini-2.5-flash", temperature=0)

        prompt = f"""
You are an expert in pandas. You are given a pandas DataFrame named `df`.
The user has the following question about the data:
"{question}"

The DataFrame has the following columns: {list(df.columns)}
And here is the head of the DataFrame:
{df.head().to_string()}

Write a short Python script that uses the `df` DataFrame to answer the question.
The script must calculate the answer and print it.
Your code must not contain any explanation or markdown formatting.
Assume `df` is already loaded. For example: `print(df['Sales'].sum())`
"""

        code_response = code_gen_llm.invoke(prompt)
        generated_code = code_response.content.strip().replace(
            "```python", "").replace("```", "")

        from io import StringIO
        import sys

        old_stdout = sys.stdout
        redirected_output = sys.stdout = StringIO()

        local_scope = {'df': df, 'pd': pd, 'np': np}
        exec(generated_code, globals(), local_scope)

        sys.stdout = old_stdout

        result = redirected_output.getvalue().strip()
        if not result:
            return "The code executed but produced no output."
        return f"The answer to '{question}' is: {result}"

    except Exception as e:
        return f"Error processing Excel file '{file_path}': {str(e)}"


@tool
def execute_python_code(code_path: str) -> str:
    """
    Execute a Python file and return the output. Use this when a question refers to a Python code file (.py).
    The `code_path` is the name of the file provided in the question's 'file_name'.

    Args:
        code_path: Path to the Python file.
    """
    try:
        result = subprocess.run(['python', code_path],
                                capture_output=True, text=True, check=True)
        return f"Output: {result.stdout}\nErrors: {result.stderr}"
    except Exception as e:
        return f"Error executing Python code: {str(e)}"


@tool
def reverse_text(text: str) -> str:
    """
    Reverse the given text. Use this for questions that require reversing a string.

    Args:
        text: Text to reverse.
    """
    return text[::-1]


@tool
def analyze_text_pattern(text: str) -> str:
    """
    Analyze text patterns and solve text puzzles. Use this for complex text manipulation that is not simple reversal.

    Args:
        text: Text to analyze.
    """
    words = text.split()
    reversed_words = [word[::-1] for word in words]
    reversed_sentence = ' '.join(reversed_words[::-1])

    analysis = f"Original: {text}\n"
    analysis += f"Reversed sentence: {reversed_sentence}\n"
    analysis += f"Word-by-word reverse: {' '.join(reversed_words)}\n"

    return analysis


@tool
def youtube_video_info(video_url: str, question: str) -> str:
    """
    Get information about a YouTube video, like its transcript or a summary, by searching the web.
    Use this tool when the question involves watching or analyzing a YouTube video.

    Args:
        video_url: YouTube video URL.
        question: Question about the video.
    """
    try:
        search_query = f"transcript of youtube video {video_url} {question}"
        search_results = tavily_search.invoke(search_query)
        if search_results:
            formatted_search_docs = "\n\n---\n\n".join(
                [
                    f'<Document source="{doc["url"]}">{doc["content"]}</Document>'
                    for doc in search_results
                ]
            )
            return f"Found information about the video {video_url}:\n\n{formatted_search_docs}"
        return f"Could not find a transcript or information for the video: {video_url}"
    except Exception as e:
        return f"Error processing video URL {video_url}: {str(e)}"


@tool
def mathematical_analysis(expression: str) -> str:
    """
    Evaluates a mathematical expression and returns the result.
    Can perform basic arithmetic. For more complex problems like analyzing tables,
    the model should break down the problem into smaller calculations.

    Args:
        expression: A string containing a mathematical expression to be evaluated.
    """
    try:
        result = eval(expression)
        return f"The result of the expression '{expression}' is: {result}"
    except Exception as e:
        return f"Could not evaluate the mathematical expression. Error: {str(e)}. Please provide a standard Python mathematical expression."


# load the system prompt from the file
with open("system_prompt.txt", "r", encoding="utf-8") as f:
    system_prompt = f.read()

# System message
sys_msg = SystemMessage(content=system_prompt)

# # build a retriever
# embeddings = GoogleGenerativeAIEmbeddings(
#     model="models/embedding-001")

# # Load documents from metadata.jsonl
# loader = JSONLoader(
#     file_path='./metadata.jsonl',
#     jq_schema='.',
#     json_lines=True,
#     text_content=False)
# documents = loader.load()

# # Create or load the Chroma vector store
# persist_directory = "chroma_db_persistent"

# if os.path.exists(persist_directory) and os.listdir(persist_directory):
#     vector_store = Chroma(
#         persist_directory=persist_directory,
#         embedding_function=embeddings
#     )
# else:
#     vector_store = Chroma.from_documents(
#         documents=documents,
#         embedding=embeddings,
#         persist_directory=persist_directory
#     )

# retriever_tool = create_retriever_tool(
#     retriever=vector_store.as_retriever(),
#     name="question_search",
#     description="A tool to retrieve similar questions from a vector store to use as reference.",
# )


tools = [
    wiki_search,
    web_search,
    process_image,
    process_audio,
    process_excel_file,
    execute_python_code,
    reverse_text,
    analyze_text_pattern,
    youtube_video_info,
    mathematical_analysis,
    # retriever_tool,
]


# Build graph function
def build_graph():
    """Build the graph"""
    llm = ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0)
    llm_with_tools = llm.bind_tools(tools)

    def assistant(state: MessagesState):
        """Assistant node"""
        return {"messages": [llm_with_tools.invoke(state["messages"])]}

    builder = StateGraph(MessagesState)
    builder.add_node("assistant", assistant)
    builder.add_node("tools", ToolNode(tools))
    builder.add_edge(START, "assistant")
    builder.add_conditional_edges(
        "assistant",
        tools_condition,
    )
    builder.add_edge("tools", "assistant")

    return builder.compile()


# # Wrapper class to comply with the Hugging Face evaluation format
# class Agent:
#     """
#     Wrapper for the LangGraph agent to be compatible with the evaluation script.
#     The evaluation script expects an object named 'agent' with a 'run' method.
#     """

#     def __init__(self):
#         self.graph = build_graph()

#     def run(self, question: str, file_name: str = None) -> str:
#         """
#         Invokes the graph and returns the final answer as a string.
#         """
#         try:
#             # Append file information to the question if a file_name is provided
#             if file_name:
#                 question += f"\n\n[Additional context: The question refers to the file named '{file_name}']"

#             messages = [sys_msg, HumanMessage(content=question)]
#             result = self.graph.invoke({"messages": messages})
#             final_answer = result["messages"][-1].content
#             return final_answer
#         except Exception as e:
#             print(f"Error during agent execution: {e}")
#             return "This is a default answer due to an error."


# # Instantiate the agent for the evaluation script to import
# agent = Agent()


# # test
# if __name__ == "__main__":
#     # Example question about an image
#     # image_question = "How many at bats did the Yankee with the most walks in the 1977 regular season have that same season?"
#     # image_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."
#     image_question = "Who nominated the only Featured Article on English Wikipedia about a dinosaur that was promoted in November 2016?"
#     # image_file = "cca530fc-4052-43b2-b130-b30968d8aa44.png"

#     print("--- Running Test ---")
#     # The evaluation script would call agent.run(question, file_name)
#     # We simulate that here.
#     answer = agent.run(question=image_question)
#     print(f"Question: {image_question}")
#     # print(f"File: {image_file}")
#     print(f"Answer: {answer}")
#     print("--- Test Complete ---")