datdevsteve commited on
Commit
1aaf9ad
·
verified ·
1 Parent(s): ac26227

now using smolagents instead of langchain

Browse files
Files changed (1) hide show
  1. gaia_agent.py +146 -168
gaia_agent.py CHANGED
@@ -1,185 +1,164 @@
1
  import os
2
  import requests
3
- from langchain.agents import create_react_agent, AgentExecutor
4
- from langchain.tools import tool
5
- from langchain_core.prompts import PromptTemplate
6
  from dotenv import load_dotenv
7
- from langchain_community.document_loaders import ArxivLoader, WikipediaLoader
8
- from duckduckgo_search import DDGS
9
  from bs4 import BeautifulSoup
10
 
11
  # Load environment variables
12
  # load_dotenv()
13
 
14
  # --- Agent Setup ---
15
- openrouter_key = os.getenv("OPENROUTER_API_KEY")
16
- if not openrouter_key:
17
- raise RuntimeError("Set OPENROUTER_API_KEY in your .env (OpenRouter API key)")
18
-
19
- from langchain_openai import ChatOpenAI
20
-
21
- model = ChatOpenAI(
22
- api_key=openrouter_key,
23
- base_url="https://openrouter.ai/api/v1",
24
- model="openai/gpt-4o-mini",
25
- max_tokens=10000,
26
- temperature=0
27
- )
28
-
29
- # --- Tools Definition ---
30
- @tool
31
- def multiply(a: float, b: float) -> float:
32
- """Multiply two numbers.
33
- Args:
34
- a: first number
35
- b: second number
36
- """
37
- return a * b
38
-
39
- @tool
40
- def add(a: float, b: float) -> float:
41
- """Add two numbers.
42
- Args:
43
- a: first number
44
- b: second number
45
- """
46
- return a + b
47
-
48
- @tool
49
- def subtract(a: float, b: float) -> float:
50
- """Subtract two numbers.
51
- Args:
52
- a: first number
53
- b: second number
54
- """
55
- return a - b
56
-
57
- @tool
58
- def divide(a: float, b: float) -> float:
59
- """Divide two numbers.
60
- Args:
61
- a: first number
62
- b: second number
63
- """
64
- if b == 0:
65
- raise ValueError("Cannot divide by zero.")
66
- return a / b
67
-
68
- @tool
69
- def modulus(a: float, b: float) -> float:
70
- """Get the modulus of two numbers.
71
- Args:
72
- a: first number
73
- b: second number
74
- """
75
- return a % b
76
-
77
- @tool
78
- def wiki_search(query: str) -> str:
79
- """Search Wikipedia for a query and return maximum 2 results."""
80
- try:
81
- search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
82
- formatted_search_docs = "\n\n---\n\n".join(
83
- [
84
- f'\n{doc.page_content}\n'
85
- for doc in search_docs
86
- ]
87
- )
88
- return formatted_search_docs
89
- except Exception as e:
90
- return f"Error searching Wikipedia: {str(e)}"
91
-
92
- @tool
93
- def web_search(query: str) -> str:
94
- """Search the web for a query and return maximum 3 results."""
95
- try:
96
- search_docs = DDGS().text(query, max_results=3)
97
- formatted_search_docs = "\n\n---\n\n".join(
98
- [
99
- f'Title:{doc["title"]}\nContent:{doc["body"]}\n--\n'
100
- for doc in search_docs
101
- ]
102
- )
103
- return formatted_search_docs
104
- except Exception as e:
105
- return f"Error searching web: {str(e)}"
106
-
107
- @tool
108
- def arxiv_search(query: str) -> str:
109
- """Search arXiv for a query and return maximum 3 results."""
110
- try:
111
- search_docs = ArxivLoader(query=query, load_max_docs=3).load()
112
- formatted_search_docs = "\n\n---\n\n".join(
113
- [
114
- f'\n{doc.page_content[:1000]}\n'
115
- for doc in search_docs
116
- ]
117
- )
118
- return formatted_search_docs
119
- except Exception as e:
120
- return f"Error searching arXiv: {str(e)}"
121
-
122
- @tool
123
- def fetch_url_content(url: str) -> str:
124
- """Fetch and return the text content from a webpage URL."""
125
- try:
126
- response = requests.get(url, timeout=10)
127
- response.raise_for_status()
128
- soup = BeautifulSoup(response.text, 'html.parser')
129
- for script in soup(["script", "style"]):
130
- script.decompose()
131
- text = soup.get_text(separator='\n', strip=True)
132
- return text[:3000] + ("..." if len(text) > 3000 else "")
133
- except Exception as e:
134
- return f"Error fetching URL: {str(e)}"
135
-
136
- # Tools list
137
- tools = [
138
- multiply, add, subtract, divide, modulus,
139
- wiki_search, web_search, arxiv_search,
140
- fetch_url_content,
141
- ]
142
-
143
- # React prompt template
144
- react_prompt = PromptTemplate.from_template("""You are a helpful assistant that answers questions accurately and concisely.
145
-
146
- Answer the following questions as best you can. You have access to the following tools:
147
-
148
- {tools}
149
 
150
- Use the following format:
 
 
 
 
151
 
152
- Question: the input question you must answer
153
- Thought: you should always think about what to do
154
- Action: the action to take, should be one of [{tool_names}]
155
- Action Input: the input to the action
156
- Observation: the result of the action
157
- ... (this Thought/Action/Action Input/Observation can repeat N times)
158
- Thought: I now know the final answer
159
- Final Answer: the final answer to the original input question
 
 
 
 
 
 
 
 
160
 
161
- IMPORTANT: Your Final Answer must be:
162
- - Short and direct (just the answer, no extra explanation)
163
- - A single value or short phrase
164
- - No formatting, no bullet points, no extra text
165
- - Just the factual answer
166
 
167
- Begin!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
- Question: {input}
170
- Thought:{agent_scratchpad}""")
 
 
 
 
 
 
 
 
 
171
 
172
  class GAIAAgent:
173
  def __init__(self):
174
- # create internal agent with React agent
175
  try:
176
- agent = create_react_agent(model, tools, react_prompt)
177
- self.agent_executor = AgentExecutor(
178
- agent=agent,
179
  tools=tools,
180
- verbose=True,
181
- handle_parsing_errors=True,
182
- max_iterations=15
183
  )
184
  except Exception as e:
185
  print(f"Error creating agent: {e}")
@@ -187,13 +166,12 @@ class GAIAAgent:
187
 
188
  def __call__(self, question: str) -> str:
189
  try:
190
- result = self.agent_executor.invoke({"input": question})
191
- answer = result.get("output", "")
192
 
193
- # Clean up the answer - remove any extra formatting
194
- answer = answer.strip()
195
 
196
- # Remove common prefixes that might be added
197
  prefixes_to_remove = [
198
  "The answer is:",
199
  "The final answer is:",
 
1
  import os
2
  import requests
3
+ from smolagents import CodeAgent, Tool, HfApiModel
 
 
4
  from dotenv import load_dotenv
 
 
5
  from bs4 import BeautifulSoup
6
 
7
  # Load environment variables
8
  # load_dotenv()
9
 
10
  # --- Agent Setup ---
11
+ hf_token = os.getenv("HF_TOKEN")
12
+ if not hf_token:
13
+ raise RuntimeError("Set HF_TOKEN in your Space secrets")
14
+
15
+ # Initialize the model
16
+ model = HfApiModel(token=hf_token, model_id="Qwen/Qwen2.5-72B-Instruct")
17
+
18
+ # --- Custom Tools Definition ---
19
+ class MultiplyTool(Tool):
20
+ name = "multiply"
21
+ description = "Multiplies two numbers together. Takes two arguments: a (first number) and b (second number)."
22
+ inputs = {"a": {"type": "number", "description": "First number"},
23
+ "b": {"type": "number", "description": "Second number"}}
24
+ output_type = "number"
25
+
26
+ def forward(self, a: float, b: float) -> float:
27
+ return a * b
28
+
29
+ class AddTool(Tool):
30
+ name = "add"
31
+ description = "Adds two numbers together. Takes two arguments: a (first number) and b (second number)."
32
+ inputs = {"a": {"type": "number", "description": "First number"},
33
+ "b": {"type": "number", "description": "Second number"}}
34
+ output_type = "number"
35
+
36
+ def forward(self, a: float, b: float) -> float:
37
+ return a + b
38
+
39
+ class SubtractTool(Tool):
40
+ name = "subtract"
41
+ description = "Subtracts b from a. Takes two arguments: a (first number) and b (second number)."
42
+ inputs = {"a": {"type": "number", "description": "First number"},
43
+ "b": {"type": "number", "description": "Second number"}}
44
+ output_type = "number"
45
+
46
+ def forward(self, a: float, b: float) -> float:
47
+ return a - b
48
+
49
+ class DivideTool(Tool):
50
+ name = "divide"
51
+ description = "Divides a by b. Takes two arguments: a (first number) and b (second number)."
52
+ inputs = {"a": {"type": "number", "description": "First number"},
53
+ "b": {"type": "number", "description": "Second number"}}
54
+ output_type = "number"
55
+
56
+ def forward(self, a: float, b: float) -> float:
57
+ if b == 0:
58
+ raise ValueError("Cannot divide by zero.")
59
+ return a / b
60
+
61
+ class ModulusTool(Tool):
62
+ name = "modulus"
63
+ description = "Calculates the modulus (remainder) of a divided by b. Takes two arguments: a (first number) and b (second number)."
64
+ inputs = {"a": {"type": "number", "description": "First number"},
65
+ "b": {"type": "number", "description": "Second number"}}
66
+ output_type = "number"
67
+
68
+ def forward(self, a: float, b: float) -> float:
69
+ return a % b
70
+
71
+ class WikipediaSearchTool(Tool):
72
+ name = "wikipedia_search"
73
+ description = "Searches Wikipedia for a given query and returns relevant information."
74
+ inputs = {"query": {"type": "string", "description": "The search query"}}
75
+ output_type = "string"
76
+
77
+ def forward(self, query: str) -> str:
78
+ try:
79
+ import wikipedia
80
+ wikipedia.set_lang("en")
81
+ results = wikipedia.search(query, results=2)
82
+ if not results:
83
+ return "No results found."
84
+
85
+ summaries = []
86
+ for result in results[:2]:
87
+ try:
88
+ page = wikipedia.page(result, auto_suggest=False)
89
+ summaries.append(f"Title: {page.title}\n{page.summary[:500]}")
90
+ except:
91
+ continue
92
+
93
+ return "\n\n---\n\n".join(summaries) if summaries else "No content found."
94
+ except Exception as e:
95
+ return f"Error searching Wikipedia: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
+ class WebSearchTool(Tool):
98
+ name = "web_search"
99
+ description = "Searches the web using DuckDuckGo and returns relevant results."
100
+ inputs = {"query": {"type": "string", "description": "The search query"}}
101
+ output_type = "string"
102
 
103
+ def forward(self, query: str) -> str:
104
+ try:
105
+ from duckduckgo_search import DDGS
106
+ results = DDGS().text(query, max_results=3)
107
+
108
+ formatted_results = []
109
+ for result in results:
110
+ formatted_results.append(
111
+ f"Title: {result.get('title', 'N/A')}\n"
112
+ f"Content: {result.get('body', 'N/A')}\n"
113
+ f"URL: {result.get('href', 'N/A')}"
114
+ )
115
+
116
+ return "\n\n---\n\n".join(formatted_results) if formatted_results else "No results found."
117
+ except Exception as e:
118
+ return f"Error searching web: {str(e)}"
119
 
120
+ class FetchURLTool(Tool):
121
+ name = "fetch_url_content"
122
+ description = "Fetches and returns the text content from a given webpage URL."
123
+ inputs = {"url": {"type": "string", "description": "The URL to fetch content from"}}
124
+ output_type = "string"
125
 
126
+ def forward(self, url: str) -> str:
127
+ try:
128
+ response = requests.get(url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
129
+ response.raise_for_status()
130
+ soup = BeautifulSoup(response.text, 'html.parser')
131
+
132
+ # Remove script and style elements
133
+ for script in soup(["script", "style", "meta", "link"]):
134
+ script.decompose()
135
+
136
+ text = soup.get_text(separator='\n', strip=True)
137
+ # Limit to 3000 characters
138
+ return text[:3000] + ("..." if len(text) > 3000 else "")
139
+ except Exception as e:
140
+ return f"Error fetching URL: {str(e)}"
141
 
142
+ # Initialize tools
143
+ tools = [
144
+ MultiplyTool(),
145
+ AddTool(),
146
+ SubtractTool(),
147
+ DivideTool(),
148
+ ModulusTool(),
149
+ WikipediaSearchTool(),
150
+ WebSearchTool(),
151
+ FetchURLTool(),
152
+ ]
153
 
154
  class GAIAAgent:
155
  def __init__(self):
 
156
  try:
157
+ self.agent = CodeAgent(
 
 
158
  tools=tools,
159
+ model=model,
160
+ max_steps=10,
161
+ verbosity_level=2
162
  )
163
  except Exception as e:
164
  print(f"Error creating agent: {e}")
 
166
 
167
  def __call__(self, question: str) -> str:
168
  try:
169
+ result = self.agent.run(question)
 
170
 
171
+ # Clean up the answer
172
+ answer = str(result).strip()
173
 
174
+ # Remove common prefixes
175
  prefixes_to_remove = [
176
  "The answer is:",
177
  "The final answer is:",