APRG commited on
Commit
d5dec6f
·
verified ·
1 Parent(s): ac691ef

Update GeneralAgent.py

Browse files
Files changed (1) hide show
  1. GeneralAgent.py +222 -222
GeneralAgent.py CHANGED
@@ -1,223 +1,223 @@
1
- import os
2
- import requests
3
- import pandas as pd
4
-
5
- from typing import Annotated
6
- from typing_extensions import TypedDict
7
- from langchain_google_genai import ChatGoogleGenerativeAI
8
- from langgraph.graph import StateGraph, START, END
9
- from langgraph.graph.message import add_messages
10
- from langgraph.prebuilt import ToolNode
11
- from langchain_core.tools import tool
12
- from langchain_core.messages import AIMessage, ToolMessage, HumanMessage, SystemMessage
13
-
14
- from smolagents import DuckDuckGoSearchTool
15
- import requests
16
- from bs4 import BeautifulSoup
17
- import wikipedia
18
- import pandas as pd
19
-
20
- # (Keep Constants as is)
21
- # --- Constants ---
22
- DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
23
-
24
- # --- Basic Agent Definition ---
25
- # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
26
- class OrderState(TypedDict):
27
- """State representing the customer's order conversation."""
28
- messages: Annotated[list, add_messages]
29
- order: list[str]
30
- finished: bool
31
-
32
- # System instruction for the Agent
33
- SYSINT = (
34
- "system",
35
- "You are a general AI assistant. I will ask you a question."
36
- "The question requires a tool to solve. You must attempt to use at least one of the available tools before returning an answer."
37
- "Report your thoughts, and finish your answer with the following template: "
38
- "FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings."
39
- "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."
40
- "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."
41
- "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."
42
- "If a tool required for task completion is not functioning, return 0."
43
- )
44
-
45
- WELCOME_MSG = "Welcome to my general-purpose AI agent. Type `q` to quit. How shall I fail to serve you today?"
46
-
47
- @tool
48
- def wikipedia_search_tool(title: str) -> str:
49
- """Provides an excerpt from a Wikipedia article with the given title."""
50
- page = wikipedia.page(title, auto_suggest=False)
51
- return page.content[:3000]
52
-
53
- @tool
54
- def media_tool(file_path: str) -> str:
55
- """Used for deciphering video and audio files."""
56
- return "This tool hasn't been implemented yet. Please return 0 if the task cannot be solved without knowing the contents of this file."
57
-
58
- @tool
59
- def internet_search_tool(search_query: str) -> str:
60
- """Does a google search with using the input as the search query. Returns a long batch of textual information related to the query."""
61
- search_tool = DuckDuckGoSearchTool()
62
- result = search_tool(search_query)
63
- return result
64
-
65
- @tool
66
- def webscraper_tool(url: str) -> str:
67
- """Returns the page's html content from the input url."""
68
- response = requests.get(url, stream=True)
69
- if response.status_code == 200:
70
- soup = BeautifulSoup(response.content, 'html.parser')
71
- html_text = soup.get_text()
72
- return html_text
73
- else:
74
- raise Exception(f"Failed to retrieve the webpage. Status code: {response.status_code}")
75
-
76
- @tool
77
- def read_excel_tool(file_path: str) -> str:
78
- """Returns the contents of an Excel file as a Pandas dataframe."""
79
- df = pd.read_excel(file_path, engine = "openpyxl")
80
- return df.to_string(index=False)
81
-
82
- class AgenticAI:
83
- def __init__(self):
84
- # initialize LLM
85
- self.llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash")
86
- # prepare tool list
87
- self.tools = [
88
- wikipedia_search_tool,
89
- media_tool,
90
- internet_search_tool,
91
- webscraper_tool,
92
- read_excel_tool,
93
- ]
94
- # bind tools
95
- self.llm_with_tools = self.llm.bind_tools(self.tools)
96
- # standalone ToolNode for any non-interactive tools (none here)
97
- self.tool_node = ToolNode([])
98
- # build state graph
99
- self.graph = StateGraph(OrderState)
100
- self.graph.add_node("agent", self._agent_node)
101
- self.graph.add_node("interactive_tools", self._interactive_tools_node)
102
- self.graph.add_node("human", self._human_node)
103
- # routing
104
- self.graph.add_conditional_edges("agent", self._maybe_route_to_tools)
105
- self.graph.add_conditional_edges("human", self._maybe_exit_human_node)
106
- self.graph.add_edge("interactive_tools", "agent")
107
- self.graph.add_edge(START, "human")
108
- self.chat_graph = self.graph.compile()
109
-
110
- def ask(self, human_input: str) -> str:
111
- """
112
- Take a single human input, run through the full agent+tool graph,
113
- return the AI's reply, and discard any stored human/chat history.
114
- """
115
- # build initial messages
116
- init_msgs = [
117
- SystemMessage(content=SYSINT),
118
- HumanMessage(content=human_input)
119
- ]
120
- state = {"messages": init_msgs, "order": [], "finished": False}
121
- try:
122
- final_state = self.chat_graph.invoke(state, {"recursion_limit": 10})
123
- # last message should be from the AI
124
- ai_msg = final_state["messages"][-1]
125
- return ai_msg.content
126
- except Exception as e:
127
- return f"Error during processing: {e}"
128
-
129
- # --- internal node functions (mirror your original code) ---
130
- def _agent_node(self, state: OrderState) -> OrderState:
131
- print(f"Messagelist sent to agent node: {[msg.content for msg in state.get('messages', [])]}")
132
- defaults = {"order": [], "finished": False}
133
- msgs = state.get("messages", [])
134
- if not msgs:
135
- # no prior messages: seed with system + empty AI message
136
- return {**defaults, "messages": [SystemMessage(SYSINT), AIMessage(content="")]}
137
-
138
- try:
139
- # always ensure system prompt is first
140
- msgs = [SystemMessage(SYSINT)] + msgs
141
- new_output = self.llm_with_tools.invoke(msgs)
142
- return {**defaults, "messages": [new_output]}
143
- except Exception as e:
144
- return {**defaults, "messages": [AIMessage(content=f"I'm having trouble: {e}")]}
145
-
146
- def _interactive_tools_node(self, state: OrderState) -> OrderState:
147
- tool_msg = state["messages"][-1]
148
- outbound_msgs = []
149
- for tool_call in tool_msg.tool_calls:
150
- tool_name = tool_call["name"]
151
- tool_args = tool_call["args"]
152
-
153
- if tool_name == "wikipedia_search_tool":
154
- print(f"called wikipedia with {str(tool_args)}")
155
- page = wikipedia.page(tool_args.get("title"), auto_suggest=False)
156
- response = page.content[:3000]
157
- elif tool_name == "media_tool":
158
- print(f"called media with {str(tool_args)}")
159
- response = "This tool hasn't been implemented yet. Please return 0 if the task cannot be solved without knowing the contents of this file."
160
- elif tool_name == "internet_search_tool":
161
- print(f"called internet with {str(tool_args)}")
162
- question = tool_args.get("search_query")
163
- search_tool = DuckDuckGoSearchTool()
164
- response = search_tool(question)[:3000]
165
- elif tool_name == "webscraper_tool":
166
- print(f"called webscraper with {str(tool_args)}")
167
- url = tool_args.get("url")
168
- response = requests.get(url, stream=True)
169
- if response.status_code == 200:
170
- soup = BeautifulSoup(response.content, 'html.parser')
171
- html_text = soup.get_text()
172
- response = html_text
173
- else:
174
- response = Exception(f"Failed to retrieve the webpage. Status code: {response.status_code}")
175
- elif tool_name == "read_excel_tool":
176
- print(f"called excel with {str(tool_args)}")
177
- path = tool_args.get("file_path")
178
- df = pd.read_excel(path, engine = "openpyxl")
179
- response = df
180
-
181
- else:
182
- raise NotImplementedError(f'Unknown tool call: {tool_name}')
183
-
184
- outbound_msgs.append(
185
- ToolMessage(
186
- content=response,
187
- name=tool_name,
188
- tool_call_id=tool_call["id"],
189
- )
190
- )
191
-
192
- return {"messages": outbound_msgs, "order": state.get("order", []), "finished": False}
193
-
194
- def _human_node(self, state: OrderState) -> OrderState:
195
- print(f"Messagelist sent to human node: {[msg.content for msg in state.get('messages', [])]}")
196
- last = state["messages"][-1]
197
- if isinstance(last, HumanMessage) and last.content.strip().lower() in {"q", "quit", "exit", "goodbye"}:
198
- state["finished"] = True
199
- return state
200
-
201
- def _maybe_route_to_tools(self, state: OrderState) -> str:
202
- msgs = state.get("messages", [])
203
- if state.get("finished"):
204
- print("from agent GOTO End node")
205
- return END
206
-
207
- last = msgs[-1]
208
- if hasattr(last, "tool_calls") and last.tool_calls:
209
- print("from agent GOTO tools node")
210
- # go run interactive tools
211
- return "interactive_tools"
212
- # else, end conversation
213
- print("tool call failed, quitting")
214
- return END
215
-
216
- def _maybe_exit_human_node(self, state: OrderState) -> str:
217
- if state.get("finished"):
218
- print("from human GOTO End node")
219
- return END
220
- last = state["messages"][-1]
221
- # if AIMessage then end after one turn
222
- print("from human GOTO agent node or quit")
223
  return END if isinstance(last, AIMessage) else "agent"
 
1
+ import os
2
+ import requests
3
+ import pandas as pd
4
+
5
+ from typing import Annotated
6
+ from typing_extensions import TypedDict
7
+ from langchain_google_genai import ChatGoogleGenerativeAI
8
+ from langgraph.graph import StateGraph, START, END
9
+ from langgraph.graph.message import add_messages
10
+ from langgraph.prebuilt import ToolNode
11
+ from langchain_core.tools import tool
12
+ from langchain_core.messages import AIMessage, ToolMessage, HumanMessage, SystemMessage
13
+
14
+ from smolagents import DuckDuckGoSearchTool
15
+ import requests
16
+ from bs4 import BeautifulSoup
17
+ import wikipedia
18
+ import pandas as pd
19
+
20
+ # (Keep Constants as is)
21
+ # --- Constants ---
22
+ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
23
+
24
+ # --- Basic Agent Definition ---
25
+ # ----- THIS IS WERE YOU CAN BUILD WHAT YOU WANT ------
26
+ class OrderState(TypedDict):
27
+ """State representing the customer's order conversation."""
28
+ messages: Annotated[list, add_messages]
29
+ order: list[str]
30
+ finished: bool
31
+
32
+ # System instruction for the Agent
33
+ SYSINT = (
34
+ "system",
35
+ "You are a general AI assistant. I will ask you a question."
36
+ "The question requires a tool to solve. You must attempt to use at least one of the available tools before returning an answer."
37
+ "Report your thoughts, and finish your answer with the following template: "
38
+ "FINAL ANSWER: [YOUR FINAL ANSWER]. YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma separated list of numbers and/or strings."
39
+ "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."
40
+ "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."
41
+ "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."
42
+ "If a tool required for task completion is not functioning, return 0."
43
+ )
44
+
45
+ WELCOME_MSG = "Welcome to my general-purpose AI agent. Type `q` to quit. How shall I fail to serve you today?"
46
+
47
+ @tool
48
+ def wikipedia_search_tool(title: str) -> str:
49
+ """Provides an excerpt from a Wikipedia article with the given title."""
50
+ page = wikipedia.page(title, auto_suggest=False)
51
+ return page.content[:3000]
52
+
53
+ @tool
54
+ def media_tool(file_path: str) -> str:
55
+ """Used for deciphering video and audio files."""
56
+ return "This tool hasn't been implemented yet. Please return 0 if the task cannot be solved without knowing the contents of this file."
57
+
58
+ @tool
59
+ def internet_search_tool(search_query: str) -> str:
60
+ """Does a google search with using the input as the search query. Returns a long batch of textual information related to the query."""
61
+ search_tool = DuckDuckGoSearchTool()
62
+ result = search_tool(search_query)
63
+ return result
64
+
65
+ @tool
66
+ def webscraper_tool(url: str) -> str:
67
+ """Returns the page's html content from the input url."""
68
+ response = requests.get(url, stream=True)
69
+ if response.status_code == 200:
70
+ soup = BeautifulSoup(response.content, 'html.parser')
71
+ html_text = soup.get_text()
72
+ return html_text
73
+ else:
74
+ raise Exception(f"Failed to retrieve the webpage. Status code: {response.status_code}")
75
+
76
+ @tool
77
+ def read_excel_tool(file_path: str) -> str:
78
+ """Returns the contents of an Excel file as a Pandas dataframe."""
79
+ df = pd.read_excel(file_path, engine = "openpyxl")
80
+ return df.to_string(index=False)
81
+
82
+ class AgenticAI:
83
+ def __init__(self):
84
+ # initialize LLM
85
+ self.llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash")
86
+ # prepare tool list
87
+ self.tools = [
88
+ wikipedia_search_tool,
89
+ media_tool,
90
+ internet_search_tool,
91
+ webscraper_tool,
92
+ read_excel_tool,
93
+ ]
94
+ # bind tools
95
+ self.llm_with_tools = self.llm.bind_tools(self.tools)
96
+ # standalone ToolNode for any non-interactive tools (none here)
97
+ self.tool_node = ToolNode([])
98
+ # build state graph
99
+ self.graph = StateGraph(OrderState)
100
+ self.graph.add_node("agent", self._agent_node)
101
+ self.graph.add_node("interactive_tools", self._interactive_tools_node)
102
+ self.graph.add_node("human", self._human_node)
103
+ # routing
104
+ self.graph.add_conditional_edges("agent", self._maybe_route_to_tools)
105
+ self.graph.add_conditional_edges("human", self._maybe_exit_human_node)
106
+ self.graph.add_edge("interactive_tools", "agent")
107
+ self.graph.add_edge(START, "human")
108
+ self.chat_graph = self.graph.compile()
109
+
110
+ def ask(self, human_input: str) -> str:
111
+ """
112
+ Take a single human input, run through the full agent+tool graph,
113
+ return the AI's reply, and discard any stored human/chat history.
114
+ """
115
+ # build initial messages
116
+ init_msgs = [
117
+ SystemMessage(content=SYSINT),
118
+ HumanMessage(content=human_input)
119
+ ]
120
+ state = {"messages": init_msgs, "order": [], "finished": False}
121
+ try:
122
+ final_state = self.chat_graph.invoke(state, {"recursion_limit": 15})
123
+ # last message should be from the AI
124
+ ai_msg = final_state["messages"][-1]
125
+ return ai_msg.content
126
+ except Exception as e:
127
+ return f"Error during processing: {e}"
128
+
129
+ # --- internal node functions (mirror your original code) ---
130
+ def _agent_node(self, state: OrderState) -> OrderState:
131
+ print(f"Messagelist sent to agent node: {[msg.content for msg in state.get('messages', [])]}")
132
+ defaults = {"order": [], "finished": False}
133
+ msgs = state.get("messages", [])
134
+ if not msgs:
135
+ # no prior messages: seed with system + empty AI message
136
+ return {**defaults, "messages": [SystemMessage(SYSINT), AIMessage(content="")]}
137
+
138
+ try:
139
+ # always ensure system prompt is first
140
+ msgs = [SystemMessage(SYSINT)] + msgs
141
+ new_output = self.llm_with_tools.invoke(msgs)
142
+ return {**defaults, "messages": [new_output]}
143
+ except Exception as e:
144
+ return {**defaults, "messages": [AIMessage(content=f"I'm having trouble: {e}")]}
145
+
146
+ def _interactive_tools_node(self, state: OrderState) -> OrderState:
147
+ tool_msg = state["messages"][-1]
148
+ outbound_msgs = []
149
+ for tool_call in tool_msg.tool_calls:
150
+ tool_name = tool_call["name"]
151
+ tool_args = tool_call["args"]
152
+
153
+ if tool_name == "wikipedia_search_tool":
154
+ print(f"called wikipedia with {str(tool_args)}")
155
+ page = wikipedia.page(tool_args.get("title"), auto_suggest=False)
156
+ response = page.content[:3000]
157
+ elif tool_name == "media_tool":
158
+ print(f"called media with {str(tool_args)}")
159
+ response = "This tool hasn't been implemented yet. Please return 0 if the task cannot be solved without knowing the contents of this file."
160
+ elif tool_name == "internet_search_tool":
161
+ print(f"called internet with {str(tool_args)}")
162
+ question = tool_args.get("search_query")
163
+ search_tool = DuckDuckGoSearchTool()
164
+ response = search_tool(question)[:3000]
165
+ elif tool_name == "webscraper_tool":
166
+ print(f"called webscraper with {str(tool_args)}")
167
+ url = tool_args.get("url")
168
+ response = requests.get(url, stream=True)
169
+ if response.status_code == 200:
170
+ soup = BeautifulSoup(response.content, 'html.parser')
171
+ html_text = soup.get_text()
172
+ response = html_text
173
+ else:
174
+ response = Exception(f"Failed to retrieve the webpage. Status code: {response.status_code}")
175
+ elif tool_name == "read_excel_tool":
176
+ print(f"called excel with {str(tool_args)}")
177
+ path = tool_args.get("file_path")
178
+ df = pd.read_excel(path, engine = "openpyxl")
179
+ response = df
180
+
181
+ else:
182
+ raise NotImplementedError(f'Unknown tool call: {tool_name}')
183
+
184
+ outbound_msgs.append(
185
+ ToolMessage(
186
+ content=response,
187
+ name=tool_name,
188
+ tool_call_id=tool_call["id"],
189
+ )
190
+ )
191
+
192
+ return {"messages": outbound_msgs, "order": state.get("order", []), "finished": False}
193
+
194
+ def _human_node(self, state: OrderState) -> OrderState:
195
+ print(f"Messagelist sent to human node: {[msg.content for msg in state.get('messages', [])]}")
196
+ last = state["messages"][-1]
197
+ if isinstance(last, HumanMessage) and last.content.strip().lower() in {"q", "quit", "exit", "goodbye"}:
198
+ state["finished"] = True
199
+ return state
200
+
201
+ def _maybe_route_to_tools(self, state: OrderState) -> str:
202
+ msgs = state.get("messages", [])
203
+ if state.get("finished"):
204
+ print("from agent GOTO End node")
205
+ return END
206
+
207
+ last = msgs[-1]
208
+ if hasattr(last, "tool_calls") and last.tool_calls:
209
+ print("from agent GOTO tools node")
210
+ # go run interactive tools
211
+ return "interactive_tools"
212
+ # else, end conversation
213
+ print("tool call failed, quitting")
214
+ return END
215
+
216
+ def _maybe_exit_human_node(self, state: OrderState) -> str:
217
+ if state.get("finished"):
218
+ print("from human GOTO End node")
219
+ return END
220
+ last = state["messages"][-1]
221
+ # if AIMessage then end after one turn
222
+ print("from human GOTO agent node or quit")
223
  return END if isinstance(last, AIMessage) else "agent"