Nihal2000 commited on
Commit
46cdeb1
·
verified ·
1 Parent(s): 7028e71

Update basic_agent.py

Browse files
Files changed (1) hide show
  1. basic_agent.py +180 -263
basic_agent.py CHANGED
@@ -1,272 +1,189 @@
1
- """
2
- A LangGraph-powered agent with at least 14 tools for question answering.
3
- """
4
- import wikipedia
5
- import sympy
6
- import requests
7
- from datetime import datetime
8
- from dateutil import parser as date_parser
9
- from duckduckgo_search import DDGS
10
- import pytz
11
- import openai
12
- import wolframalpha
13
- import pandas as pd
14
- from bs4 import BeautifulSoup
15
- import httpx
16
- from langgraph.graph import StateGraph, END
17
-
18
- # 1. Wikipedia Search Tool
19
- def wikipedia_search(query):
20
- try:
21
- return wikipedia.summary(query, sentences=2)
22
- except Exception as e:
23
- return f"Wikipedia error: {e}"
24
-
25
- # 2. Math Evaluation Tool
26
- def math_eval(expr):
27
- try:
28
- return str(sympy.sympify(expr).evalf())
29
- except Exception as e:
30
- return f"Math error: {e}"
31
-
32
- # 3. DuckDuckGo Web Search Tool
33
- def ddg_search(query):
34
- try:
35
- results = list(DDGS().text(query, max_results=1))
36
- return results[0]['body'] if results else 'No results.'
37
- except Exception as e:
38
- return f"DDG error: {e}"
39
-
40
- # 4. Current Date Tool
41
- def get_current_date():
42
- return datetime.now().strftime('%Y-%m-%d')
43
-
44
- # 5. Current Time Tool
45
- def get_current_time():
46
- return datetime.now().strftime('%H:%M:%S')
47
-
48
- # 6. Timezone Conversion Tool
49
- def convert_timezone(dt_str, from_tz, to_tz):
50
- try:
51
- dt = date_parser.parse(dt_str)
52
- from_zone = pytz.timezone(from_tz)
53
- to_zone = pytz.timezone(to_tz)
54
- dt = from_zone.localize(dt)
55
- return dt.astimezone(to_zone).strftime('%Y-%m-%d %H:%M:%S')
56
- except Exception as e:
57
- return f"Timezone error: {e}"
58
-
59
- # 7. OpenAI GPT-3.5 Completion Tool (requires OPENAI_API_KEY env var)
60
- def openai_completion(prompt):
61
- try:
62
- openai.api_key = os.getenv('OPENAI_API_KEY')
63
- resp = openai.ChatCompletion.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": prompt}])
64
- return resp.choices[0].message.content.strip()
65
- except Exception as e:
66
- return f"OpenAI error: {e}"
67
-
68
- # 8. WolframAlpha Short Answer Tool (requires WOLFRAMALPHA_APPID env var)
69
- def wolfram_query(query):
70
- try:
71
- appid = os.getenv('WOLFRAMALPHA_APPID')
72
- client = wolframalpha.Client(appid)
73
- res = client.query(query)
74
- return next(res.results).text
75
- except Exception as e:
76
- return f"WolframAlpha error: {e}"
77
-
78
- # 9. Weather API Tool (using python-weather-api)
79
- def get_weather(city):
80
- try:
81
- import python_weather
82
- import asyncio
83
- async def getweather():
84
- async with python_weather.Client(unit=python_weather.METRIC) as client:
85
- weather = await client.get(city)
86
- return f"{weather.current.temperature}°C, {weather.current.sky_text}"
87
- return asyncio.run(getweather())
88
- except Exception as e:
89
- return f"Weather error: {e}"
90
-
91
- # 10. Pandas DataFrame Tool
92
- def describe_dataframe(csv_url):
93
- try:
94
- df = pd.read_csv(csv_url)
95
- return str(df.describe())
96
- except Exception as e:
97
- return f"Pandas error: {e}"
98
-
99
- # 11. BeautifulSoup HTML Title Extractor
100
- def extract_title(url):
101
- try:
102
- resp = requests.get(url, timeout=10)
103
- soup = BeautifulSoup(resp.text, 'lxml')
104
- return soup.title.string.strip() if soup.title else 'No title found.'
105
- except Exception as e:
106
- return f"Soup error: {e}"
107
-
108
- # 12. HTTPX GET Tool
109
- def httpx_get(url):
110
- try:
111
- resp = httpx.get(url, timeout=10)
112
- return resp.text[:500]
113
- except Exception as e:
114
- return f"HTTPX error: {e}"
115
-
116
- # 13. Currency Conversion Tool (using exchangerate.host)
117
- def convert_currency(amount, from_cur, to_cur):
118
- try:
119
- url = f"https://api.exchangerate.host/convert?from={from_cur}&to={to_cur}&amount={amount}"
120
- resp = requests.get(url)
121
- data = resp.json()
122
- return f"{amount} {from_cur} = {data['result']} {to_cur}"
123
- except Exception as e:
124
- return f"Currency error: {e}"
125
-
126
- # 14. IP Geolocation Tool
127
- def ip_geolocate(ip):
128
- try:
129
- resp = requests.get(f"https://ipinfo.io/{ip}/json")
130
- data = resp.json()
131
- return f"{data.get('city', '?')}, {data.get('region', '?')}, {data.get('country', '?')}"
132
- except Exception as e:
133
- return f"IP error: {e}"
134
-
135
- # --- LangGraph Agent Setup ---
136
- from langgraph.agent import Tool, Agent
137
-
138
- tools = [
139
- Tool("wikipedia_search", wikipedia_search, description="Search Wikipedia for a summary."),
140
- Tool("math_eval", math_eval, description="Evaluate a math expression."),
141
- Tool("ddg_search", ddg_search, description="DuckDuckGo web search."),
142
- Tool("get_current_date", get_current_date, description="Get the current date."),
143
- Tool("get_current_time", get_current_time, description="Get the current time."),
144
- Tool("convert_timezone", convert_timezone, description="Convert time between timezones."),
145
- Tool("openai_completion", openai_completion, description="Get a completion from OpenAI GPT-3.5."),
146
- Tool("wolfram_query", wolfram_query, description="Query WolframAlpha for a short answer."),
147
- Tool("get_weather", get_weather, description="Get current weather for a city."),
148
- Tool("describe_dataframe", describe_dataframe, description="Describe a CSV file using pandas."),
149
- Tool("extract_title", extract_title, description="Extract the title from a webpage."),
150
- Tool("httpx_get", httpx_get, description="Fetch a webpage using HTTPX."),
151
- Tool("convert_currency", convert_currency, description="Convert currency using exchangerate.host."),
152
- Tool("ip_geolocate", ip_geolocate, description="Get geolocation info for an IP address."),
153
- ]
154
-
155
- class LangGraphAgent:
156
- def __init__(self):
157
- self.agent = Agent(tools=tools)
158
- def __call__(self, question: str) -> str:
159
- """Use LangGraph agent to answer the question."""
160
- try:
161
- return self.agent.run(question)
162
- except Exception as e:
163
- return f"LangGraphAgent error: {e}"
164
-
165
- # Define a simple state for demonstration (can be extended for more complex workflows)
166
- class AgentState(dict):
167
- pass
168
-
169
- def build_langgraph():
170
- # Create a graph
171
- graph = StateGraph(AgentState)
172
 
173
- # Add each tool as a node
174
- for tool in tools:
175
- def make_tool_node(tool_func):
176
- def node(state: AgentState):
177
- question = state.get('question', '')
178
- try:
179
- result = tool_func(question)
180
- except Exception as e:
181
- result = f"Tool error: {e}"
182
- state['result'] = result
183
- return state
184
- return node
185
- graph.add_node(tool.name, make_tool_node(tool.func))
186
 
187
- # For demonstration, connect all nodes to END (in practice, you may want to chain or branch)
188
- for tool in tools:
189
- graph.add_edge(tool.name, END)
 
 
190
 
191
- # Set entry point (first tool)
192
- graph.set_entry_point(tools[0].name)
 
193
 
194
- return graph
 
 
 
 
195
 
196
- # --- GAIA-style Multi-step Agent ---
197
- import re
 
198
 
199
- class GAIAAgent:
 
 
200
  """
201
- A simple GAIA-style agent that can plan and chain multiple tools for multi-step reasoning.
202
- This is a minimal demonstration and can be extended for more advanced planning.
203
- """
204
- def __init__(self):
205
- self.tool_map = {t.name: t.func for t in tools}
206
-
207
- def plan(self, question: str):
208
- """
209
- Very simple planner: looks for keywords to select tools and chain them.
210
- Returns a list of (tool_name, tool_input) tuples.
211
- """
212
- steps = []
213
- q = question.lower()
214
- # Example: If question asks for weather in a city and then convert time
215
- if 'weather' in q and 'time' in q and 'convert' in q:
216
- # e.g., "What is the weather in Paris and convert the time to Tokyo timezone?"
217
- city = re.findall(r'weather in ([a-zA-Z ]+)', q)
218
- city = city[0].strip() if city else 'London'
219
- steps.append(('get_weather', city))
220
- # Assume user wants to convert current time from city to Tokyo
221
- steps.append(('convert_timezone', [datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'Europe/Paris', 'Asia/Tokyo']))
222
- elif 'weather' in q:
223
- city = re.findall(r'weather in ([a-zA-Z ]+)', q)
224
- city = city[0].strip() if city else 'London'
225
- steps.append(('get_weather', city))
226
- elif 'currency' in q or 'convert' in q and 'usd' in q and 'eur' in q:
227
- amount = re.findall(r'(\d+)', q)
228
- amount = amount[0] if amount else '1'
229
- steps.append(('convert_currency', [amount, 'USD', 'EUR']))
230
- elif 'wikipedia' in q or 'who is' in q or 'what is' in q:
231
- topic = re.findall(r'(?:wikipedia|who is|what is) ([a-zA-Z0-9 ]+)', q)
232
- topic = topic[0].strip() if topic else question
233
- steps.append(('wikipedia_search', topic))
234
- elif 'math' in q or any(op in q for op in ['+', '-', '*', '/']):
235
- expr = re.findall(r'([\d\s\+\-\*/\.]+)', q)
236
- expr = expr[0] if expr else question
237
- steps.append(('math_eval', expr))
238
- else:
239
- # Default: try DuckDuckGo search
240
- steps.append(('ddg_search', question))
241
- return steps
242
 
243
- def __call__(self, question: str) -> str:
244
- """
245
- Execute the planned steps, chaining outputs if needed.
246
- """
247
- steps = self.plan(question)
248
- last_output = None
249
- for tool_name, tool_input in steps:
250
- func = self.tool_map.get(tool_name)
251
- if not func:
252
- last_output = f"Tool {tool_name} not found."
253
- break
254
- # If previous output is needed as input, use it
255
- if isinstance(tool_input, list):
256
- # Replace any placeholder with last_output
257
- tool_input = [last_output if x == '__PREV__' else x for x in tool_input]
258
- try:
259
- last_output = func(*tool_input)
260
- except Exception as e:
261
- last_output = f"Error in {tool_name}: {e}"
262
- else:
263
- try:
264
- last_output = func(tool_input)
265
- except Exception as e:
266
- last_output = f"Error in {tool_name}: {e}"
267
- return last_output
268
 
269
- # Example usage:
270
- # graph = build_langgraph()
271
- # result = graph.run({'question': 'What is the capital of France?'})
272
- # print(result['result'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """LangGraph Agent"""
2
+ import os
3
+ from dotenv import load_dotenv
4
+ from langgraph.graph import START, StateGraph, MessagesState, END
5
+ from langgraph.prebuilt import tools_condition
6
+ from langgraph.prebuilt import ToolNode
7
+ from langchain_google_genai import ChatGoogleGenerativeAI
8
+ from langchain_groq import ChatGroq
9
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
10
+ from langchain_community.tools.tavily_search import TavilySearchResults
11
+ from langchain_community.document_loaders import WikipediaLoader
12
+ from langchain_community.document_loaders import ArxivLoader
13
+ from langchain_community.vectorstores import SupabaseVectorStore
14
+ from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
15
+ from langchain_core.tools import tool
16
+ from langchain.tools.retriever import create_retriever_tool
17
+ from supabase.client import Client, create_client
18
+ from typing import List, Dict, Any
19
+
20
+ load_dotenv()
21
+
22
+ # --- Tools ---
23
+ @tool
24
+ def multiply(a: int, b: int) -> int:
25
+ """Multiply two numbers.
26
+ Args:
27
+ a: first int
28
+ b: second int
29
+ """
30
+ return a * b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
+ @tool
33
+ def add(a: int, b: int) -> int:
34
+ """Add two numbers.
 
 
 
 
 
 
 
 
 
 
35
 
36
+ Args:
37
+ a: first int
38
+ b: second int
39
+ """
40
+ return a + b
41
 
42
+ @tool
43
+ def subtract(a: int, b: int) -> int:
44
+ """Subtract two numbers.
45
 
46
+ Args:
47
+ a: first int
48
+ b: second int
49
+ """
50
+ return a - b
51
 
52
+ @tool
53
+ def divide(a: int, b: int) -> int:
54
+ """Divide two numbers.
55
 
56
+ Args:
57
+ a: first int
58
+ b: second int
59
  """
60
+ if b == 0:
61
+ raise ValueError("Cannot divide by zero.")
62
+ return a / b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ @tool
65
+ def modulus(a: int, b: int) -> int:
66
+ """Get the modulus of two numbers.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
+ Args:
69
+ a: first int
70
+ b: second int
71
+ """
72
+ return a % b
73
+
74
+ @tool
75
+ def wiki_search(query: str) -> str:
76
+ """Search Wikipedia for a query and return maximum 2 results.
77
+
78
+ Args:
79
+ query: The search query."""
80
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
81
+ formatted_search_docs = "\n\n---\n\n".join(
82
+ [
83
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
84
+ for doc in search_docs
85
+ ])
86
+ return {"wiki_results": formatted_search_docs}
87
+
88
+ @tool
89
+ def web_search(query: str) -> str:
90
+ """Search Tavily for a query and return maximum 3 results.
91
+
92
+ Args:
93
+ query: The search query."""
94
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
95
+ formatted_search_docs = "\n\n---\n\n".join(
96
+ [
97
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
98
+ for doc in search_docs
99
+ ])
100
+ return {"web_results": formatted_search_docs}
101
+
102
+ @tool
103
+ def arxiv_search(query: str) -> str:
104
+ """Search Arxiv for a query and return maximum 3 result.
105
+
106
+ Args:
107
+ query: The search query."""
108
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
109
+ formatted_search_docs = "\n\n---\n\n".join(
110
+ [
111
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
112
+ for doc in search_docs
113
+ ])
114
+ return {"arxiv_results": formatted_search_docs}
115
+
116
+
117
+ # --- Prompt & Retriever Setup ---
118
+ with open("system_prompt.txt", "r", encoding="utf-8") as f:
119
+ system_prompt = f.read()
120
+ sys_msg = SystemMessage(content=system_prompt)
121
+
122
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
123
+ supabase: Client = create_client(
124
+ os.environ.get("SUPABASE_URL"),
125
+ os.environ.get("SUPABASE_SERVICE_KEY"))
126
+ vector_store = SupabaseVectorStore(
127
+ client=supabase,
128
+ embedding=embeddings,
129
+ table_name="documents",
130
+ query_name="match_documents_langchain",
131
+ )
132
+ retriever = vector_store.as_retriever() # Access the retriever directly
133
+
134
+ # --- Graph Definition ---
135
+ def build_graph(provider: str = "google"):
136
+ """Build the graph"""
137
+ if provider == "google":
138
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
139
+ elif provider == "groq":
140
+ llm = ChatGroq(model="qwen-qwq-32b", temperature=0)
141
+ elif provider == "huggingface":
142
+ llm = ChatHuggingFace(
143
+ llm=HuggingFaceEndpoint(
144
+ url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
145
+ temperature=0,
146
+ ),
147
+ )
148
+ else:
149
+ raise ValueError("Invalid provider. Choose 'google', 'groq' or 'huggingface'.")
150
+
151
+ # Bind tools to LLM
152
+ llm_with_tools = llm.bind_tools(tools)
153
+
154
+ # Define nodes
155
+ def retrieval_node(state: MessagesState):
156
+ """Retrieves relevant documents."""
157
+ query = state["messages"][-1].content # Get latest message
158
+ docs = retriever.get_relevant_documents(query)
159
+ context = "\n\n".join([d.page_content for d in docs]) # Concatenate document content
160
+ #Append context to the messages so that it can be send to LLM
161
+ return {"messages": [HumanMessage(content=f"Context:\n{context}\nOriginal question: {query}")]}
162
+
163
+ def llm_node(state: MessagesState):
164
+ """Invokes the LLM to answer the question."""
165
+ return {"messages": [llm_with_tools.invoke(state)]}
166
+
167
+ # Add tools node
168
+ tool_node = ToolNode(tools)
169
+ # Define graph
170
+
171
+ builder = StateGraph(MessagesState)
172
+ builder.add_node("retrieval", retrieval_node)
173
+ builder.add_node("llm", llm_node)
174
+ builder.add_node("tools", tool_node)
175
+
176
+ # Graph structure
177
+ builder.set_entry_point("retrieval")
178
+ builder.add_edge("retrieval", "llm")
179
+
180
+ #Conditional Edges
181
+ builder.add_conditional_edges(
182
+ "llm",
183
+ tools_condition,
184
+ )
185
+ builder.add_edge("tools", "llm")
186
+ builder.add_edge("llm", END)
187
+
188
+ # Compile
189
+ return builder.compile()