File size: 6,563 Bytes
0d1f3af
 
 
 
 
 
 
 
 
 
 
 
 
18988d2
0d1f3af
 
 
ced0039
0d1f3af
 
defa779
ced0039
0d1f3af
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18988d2
0d1f3af
 
18988d2
0d1f3af
 
 
 
 
 
 
 
 
 
 
 
 
 
18988d2
0d1f3af
defa779
ced0039
0d1f3af
884c186
 
 
 
 
 
0d4e942
0d1f3af
 
 
884c186
0d1f3af
 
 
884c186
0d1f3af
 
 
defa779
0d1f3af
 
 
 
defa779
0d1f3af
 
 
 
defa779
0d1f3af
 
 
 
defa779
 
884c186
 
defa779
0d1f3af
 
 
f8f7918
6956e32
18988d2
0d1f3af
c8e6024
0d1f3af
 
ced0039
 
18988d2
ced0039
 
a74e137
18988d2
 
 
 
34dada1
 
 
 
 
 
ced0039
 
a74e137
d06fea0
ced0039
a74e137
 
18988d2
34dada1
18988d2
 
 
 
 
 
 
 
 
 
 
34dada1
 
 
 
 
0d4e942
ced0039
 
 
29f54ab
0d1f3af
ff55f75
ced0039
ff55f75
0d1f3af
ced0039
0d1f3af
 
 
 
 
ff55f75
0d1f3af
 
ff55f75
ced0039
 
29f54ab
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
from typing import List, TypedDict, Annotated, Optional
from langchain_core.messages import AnyMessage, SystemMessage, HumanMessage
from langgraph.graph.message import add_messages
from langgraph.graph import START, StateGraph
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_groq import ChatGroq
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.tools import tool

from langchain_community.tools import DuckDuckGoSearchRun
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper


import requests, io, pandas as pd, PyPDF2, ast, pytesseract
from PIL import Image


class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]


@tool
def add(a: float, b: float) -> float:
    """
    Adds two numbers.

    Args:
        a (float): First number.
        b (float): Second number.

    Returns:
        float: Sum of a and b.
    """
    return a + b

@tool
def subtract(a: float, b: float) -> float:
    """
    Subtracts one number from another.

    Args:
        a (float): Minuend.
        b (float): Subtrahend.

    Returns:
        float: Result of a - b.
    """
    return a - b

@tool
def multiply(a: float, b: float) -> float:
    """
    Multiplies two numbers.

    Args:
        a (float): First number.
        b (float): Second number.

    Returns:
        float: Product of a and b.
    """
    return a * b

@tool
def divide(a: float, b: float) -> float:
    """
    Divides one number by another.

    Args:
        a (float): Dividend.
        b (float): Divisor.

    Raises:
        ValueError: If b is zero.

    Returns:
        float: Result of a / b.
    """
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

@tool
def web_search(query: str) -> str:
    """
    Searches the web for the given query.

    Args:
        query (str): The search query.

    Returns:
        str: The search results
    """
    search = DuckDuckGoSearchRun()
    print("search")
    return search.invoke(query)

@tool
def wikisearch(query: str) -> str:
    """
    Searches wikipedia for the given query.

    Args:
        query (str): The search query.

    Returns:
        str: The wikipedia results.
    """
    wikipedia = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())
    print("wiki")
    return wikipedia.run(query)


@tool
def gaia_retriever_tool(task_id: str) -> str:
    """
    Downloads a file from a Gaia task using the provided task_id and returns its raw text content.
    Does NOT analyze or interpret the content.
    Supports PDFs, Excel/CSV spreadsheets, Python scripts, and images.
    """
    print("retriever")
    url = f'https://agents-course-unit4-scoring.hf.space/files/{task_id}'
    resp = requests.get(url)
    if resp.status_code != 200:
        return f"Failed to download file {task_id}"

    file_bytes = io.BytesIO(resp.content)
    text = ""

    try:
        df = pd.read_excel(file_bytes)
        text = df.to_csv(index=False)
    except Exception:
        try:
            file_bytes.seek(0)
            df = pd.read_csv(file_bytes)
            text = df.to_csv(index=False)
        except Exception:
            try:
                file_bytes.seek(0)
                reader = PyPDF2.PdfReader(file_bytes)
                text = "\n".join([p.extract_text() or "" for p in reader.pages])
            except Exception:
                try:
                    file_bytes.seek(0)
                    img = Image.open(file_bytes)
                    text = pytesseract.image_to_string(img)
                except Exception:
                    file_bytes.seek(0)
                    text = file_bytes.read().decode("utf-8", errors="ignore")
    return text

tools = [add, subtract, multiply, divide, web_search, wikisearch, gaia_retriever_tool]

llm = ChatGoogleGenerativeAI(
    model="gemini-2.5-flash",
    api_key="...",
    temperature=0.3,
)

llm_with_tools = llm.bind_tools(tools)

system_prompt = SystemMessage(
    content="""
You are a concise AI assistant. Use tools only when strictly necessary.

**Math:** use add(a,b), subtract(a,b), multiply(a,b), divide(a,b).  
**Search:** 
- Always try wikisearch(query) first.
- If wiki fails or is insufficient, use web_search(query).
- Only fallback to general knowledge if both fail.

**Files:** use gaia_retriever_tool(task_id) **whenever the question mentions a file, attachment, picture, Excel, CSV, PDF, or Python file.**  
- Variations like "attached Excel file", "following CSV file", or "image attached" count as explicit mentions.  
- **The agent should pass the task_id from the question to gaia_retriever_tool.**  
- **The tool itself handles downloading and extracting content.**  
- **Do NOT attempt to download or read the file outside the tool.**  
- **Do NOT call gaia_retriever_tool if no file is mentioned.**

**Answer rules:**
1. Give **exact answer only**, no explanations.  
2. Numbers → output only the number.
3. Lists → comma-separated values, no words.  
4. Text → 1–5 words.  
5. Conceptual questions (opposites, synonyms, meanings) → answer correctly in 1 word.  
6. Never include extra context, tool names, or sentences.
7. Your answer must strictly be short

**Example 1:**  
Question: Who is the president of France?  
Action: wikisearch("president of France") → returns "Emmanuel Macron"  
Answer: Emmanuel Macron

**Example 2:**  
Question: Some obscure topic, task_id: 123  
Action: wikisearch("Some obscure topic") → returns ""  
Since wiki returned nothing, call web_search("Some obscure topic")  
Answer: [result from web_search]  
**Do NOT call gaia_retriever_tool because the question does not mention a file.**

**Example 3:**  
Question: The attached Excel file contains sales data, task_id: 7bd855d8-463d-4ed5-93ca-5fe35145f733  
Action: gaia_retriever_tool("7bd855d8-463d-4ed5-93ca-5fe35145f733") → returns file content  
Answer: 30420.00
"""
)


def assistant(state: AgentState):
    return {
        "messages": [llm_with_tools.invoke([system_prompt] + state["messages"])],
    }


builder = StateGraph(AgentState)
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")
graph = builder.compile()

def run(prompt, task_id='1'):
    state: AgentState = {"messages": [HumanMessage(prompt)]}
    output = graph.invoke(state)
    return output["messages"][-1].content