Stefan888 commited on
Commit
ca75a1b
·
1 Parent(s): a9de741

code cleaned and issues fixed

Browse files
Files changed (3) hide show
  1. agent.py +224 -0
  2. app.py +1 -149
  3. requirements.txt +2 -1
agent.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from typing import TypedDict, List, Dict, Any, Optional
3
+ from langgraph.graph import StateGraph, START, END
4
+ from langchain_openai import ChatOpenAI
5
+ from langchain_core.messages import HumanMessage, AIMessage
6
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint
7
+ from langgraph.prebuilt import ToolNode, tools_condition
8
+ from langchain_core.messages import HumanMessage, SystemMessage
9
+ from langchain_core.utils.function_calling import convert_to_openai_tool
10
+ from langchain.tools import Tool
11
+ from serpapi import GoogleSearch
12
+ import requests
13
+ from bs4 import BeautifulSoup
14
+
15
+ SERPAPI_API_KEY = os.environ["SERPAPI_TOKEN"]
16
+
17
+ def serpapi_search(query: str) -> str:
18
+ print(f"Running SerpAPI search for: {query}")
19
+ params = {
20
+ "engine": "google",
21
+ "q": query,
22
+ "api_key": SERPAPI_API_KEY,
23
+ "num": 3,
24
+ }
25
+ search = GoogleSearch(params)
26
+ results = search.get_dict()
27
+ if "organic_results" in results:
28
+ snippets = []
29
+ for item in results["organic_results"]:
30
+ snippet = item.get("snippet", "")
31
+ link = item.get("link", "")
32
+ snippets.append(f"{snippet}\nURL: {link}")
33
+ return "\n\n".join(snippets)
34
+ return "No results found."
35
+
36
+ serpapi_tool = Tool(
37
+ name="serpapi_search",
38
+ func=serpapi_search,
39
+ description="A tool that allows you to search the web using Google via SerpAPI. Input should be a search query."
40
+ )
41
+
42
+ def fetch_website_content(url: str) -> str:
43
+ print(f"Fetching website content from: {url}")
44
+ try:
45
+ response = requests.get(url, timeout=5)
46
+ response.raise_for_status()
47
+ soup = BeautifulSoup(response.text, "html.parser")
48
+ # Get main text content (very basic)
49
+ text = soup.get_text(separator="\n", strip=True)
50
+ return text[:1000] # Return first 1000 chars for brevity
51
+ except Exception as e:
52
+ print(f"Error fetching website: {e}")
53
+ return f"Error fetching website: {e}"
54
+
55
+ fetch_website_tool = Tool(
56
+ name="fetch_website_content",
57
+ func=fetch_website_content,
58
+ description="Fetches and returns the main text content of a given website URL."
59
+ )
60
+
61
+ # Initialize LLM
62
+ model = ChatOpenAI( model="gpt-4o",temperature=0)
63
+ #model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
64
+ #vision_llm = ChatOpenAI(model="gpt-4o")
65
+
66
+ #search_tool = DuckDuckGoSearchRun()
67
+ tools = [serpapi_tool]#, fetch_website_tool]
68
+
69
+ llm_with_tools = model.bind_tools(tools, parallel_tool_calls=False)
70
+
71
+ class AgentState(TypedDict):
72
+ question: Dict[str, Any]
73
+ messages: List[Any]
74
+ answer: Optional[str]
75
+ tool_calls: Optional[list]
76
+ tool_outputs: Optional[list]
77
+
78
+ def assistant(state: AgentState):
79
+ print("\n--- ASSISTANT NODE ---")
80
+ print(f"State received: {state}")
81
+ question = state["question"]
82
+ print(f"Question dict: {question}")
83
+ #textual_description_of_tool = """
84
+ #search_tool: A tool that allows you to search the web using DuckDuckGo. It returns a list of search results based on the query provided.
85
+ #"""
86
+ textual_description_of_tool = """
87
+ serpapi_search: A tool that allows you to search the web using Google via SerpAPI. It returns a list of search results based on the query provided.
88
+ fetch_website_content(url: str) -> str: A tool that fetches and returns the main text content of a given website URL.
89
+ """
90
+ system_prompt = SystemMessage(
91
+ content=f"""
92
+ Your answers are tested. Try to answer the question as accurately as possible. Give only the minimum necessary information to answer the question.
93
+ If you use a tool, answer the question using the tool results provided below.
94
+ Tool results will be provided as context after your question. If you receive a tool output, then use this information and come to the final answer if possible.
95
+ Only call another tool if you cannot answer the question with the information provided.
96
+ If you formulate your final answer, analyze it if it really ONLY answers the question. Don't provide additional information. One word, number or name is enough if it answers the question.
97
+ """
98
+ #You can use the following tools to help you:
99
+ #{textual_description_of_tool}
100
+
101
+ )
102
+
103
+ messages = [system_prompt]
104
+ # Always add the user question
105
+ messages.append(HumanMessage(content=f"Question: {question.get('question', question)}"))
106
+ # If tool_outputs exist, add them as context
107
+ if state.get("tool_outputs"):
108
+ # Format tool results as plain text
109
+ tool_results = state["tool_outputs"]
110
+ if isinstance(tool_results, dict):
111
+ tool_text = ""
112
+ if "search_results" in tool_results and tool_results["search_results"]:
113
+ tool_text += "Search Results:\n"
114
+ tool_text += "\n".join(str(r) for r in tool_results["search_results"])
115
+ if "website_contents" in tool_results and tool_results["website_contents"]:
116
+ tool_text += "\nWebsite Contents:\n"
117
+ for wc in tool_results["website_contents"]:
118
+ tool_text += f"\nURL: {wc['url']}\nContent: {wc['content']}\n"
119
+ else:
120
+ tool_text = str(tool_results)
121
+ messages.append(HumanMessage(content=f"Tool results:\n{tool_text}"))
122
+
123
+ print(f"Messages sent to LLM: {messages}")
124
+ response = llm_with_tools.invoke(messages)
125
+ print(f"Raw LLM response: {response}")
126
+ # If the LLM wants to call a tool, store tool_calls in state
127
+ tool_calls = getattr(response, "tool_calls", None)
128
+ if tool_calls:
129
+ print(f"Tool calls requested: {tool_calls}")
130
+ state["tool_calls"] = tool_calls
131
+ state["answer"] = "" # Not final yet
132
+ state.setdefault("messages", []).append(AIMessage(content="Calling tool: " + str(tool_calls)))
133
+ else:
134
+ state["answer"] = response.content.strip()
135
+ print(f"Model response: {state['answer']}")
136
+ state.setdefault("messages", []).append(AIMessage(content=state["answer"]))
137
+ state["tool_calls"] = None
138
+ return state
139
+
140
+ def tool_node(state: AgentState):
141
+ print("\n--- TOOL NODE ---")
142
+ print(f"State received: {state}")
143
+ search_results = []
144
+ website_contents = []
145
+
146
+ tool_calls = state.get("tool_calls") or []
147
+ for call in tool_calls:
148
+ print(f"Tool call: {call}")
149
+ args = call.get("args", {})
150
+ # Accept both {"query": ...} and {"__arg1": ...}
151
+ query = args.get("query") or args.get("__arg1") or (list(args.values())[0] if args else None)
152
+ print(f"Query to use: {query}")
153
+
154
+ if call["name"] == "serpapi_search":
155
+ print("--- SERPAPI SEARCH ---")
156
+ try:
157
+ result = serpapi_search(query)
158
+ search_results.append(result)
159
+ except Exception as e:
160
+ print(f"Error running SerpAPI search: {e}")
161
+ search_results.append(f"Error: {e}")
162
+
163
+ elif call["name"] == "fetch_website_content":
164
+ print("--- FETCH WEBSITE CONTENT ---")
165
+ try:
166
+ content = fetch_website_content(query)
167
+ website_contents.append({"url": query, "content": content})
168
+ except Exception as e:
169
+ print(f"Error fetching website: {e}")
170
+ website_contents.append({"url": query, "content": f"Error: {e}"})
171
+
172
+ # Store tool outputs in state for the assistant
173
+ state["tool_outputs"] = {
174
+ "search_results": search_results,
175
+ "website_contents": website_contents
176
+ }
177
+ state["tool_calls"] = None # Clear tool calls
178
+
179
+ # Add tool results to conversation history for traceability
180
+ state.setdefault("messages", []).append(
181
+ HumanMessage(content=f"Tool results: {state['tool_outputs']}")
182
+ )
183
+ return state
184
+
185
+ class BasicAgent:
186
+ compiled_graph: StateGraph
187
+ def __init__(self):
188
+ print("BasicAgent initialized.")
189
+ #building the graph
190
+ answering_graph = StateGraph(AgentState)
191
+
192
+ # Add nodes
193
+ answering_graph.add_node("assistant", assistant)
194
+ #answering_graph.add_node("tools", ToolNode(tools))
195
+ answering_graph.add_node("tools", tool_node)
196
+
197
+ # Add edges
198
+ answering_graph.add_edge(START, "assistant")
199
+ answering_graph.add_conditional_edges(
200
+ "assistant",
201
+ lambda state: "tools" if state.get("tool_calls") else END
202
+ )
203
+ answering_graph.add_edge("tools", "assistant")
204
+
205
+ # Compile the graph
206
+ self.compiled_graph = answering_graph.compile()
207
+
208
+
209
+ def __call__(self, question: str) -> str:
210
+ question_text = question.get("question")
211
+ print(f"Agent received question (first 50 chars): {question_text[:50]}...")
212
+
213
+ initial_state = {
214
+ "question": question,
215
+ "messages": [],
216
+ "answer": None,
217
+ "tool_calls": None,
218
+ "tool_outputs": None
219
+ }
220
+
221
+ print(f"Initial state: {initial_state}")
222
+ answer = self.compiled_graph.invoke(initial_state)
223
+ print(f"Agent returning answer: {answer.get('answer')}")
224
+ return answer.get("answer")
app.py CHANGED
@@ -15,6 +15,7 @@ from langchain_core.messages import HumanMessage, SystemMessage
15
  from langchain_core.utils.function_calling import convert_to_openai_tool
16
  from langchain.tools import Tool
17
  from serpapi import GoogleSearch
 
18
 
19
  # (Keep Constants as is)
20
  # --- Constants ---
@@ -25,155 +26,6 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
25
 
26
  SERPAPI_API_KEY = os.environ["SERPAPI_TOKEN"]
27
 
28
- def serpapi_search(query: str) -> str:
29
- print(f"Running SerpAPI search for: {query}")
30
- params = {
31
- "engine": "google",
32
- "q": query,
33
- "api_key": SERPAPI_API_KEY,
34
- "num": 3,
35
- }
36
- search = GoogleSearch(params)
37
- results = search.get_dict()
38
- if "organic_results" in results:
39
- snippets = [item.get("snippet", "") for item in results["organic_results"]]
40
- return "\n".join(snippets)
41
- return "No results found."
42
-
43
- serpapi_tool = Tool(
44
- name="serpapi_search",
45
- func=serpapi_search,
46
- description="A tool that allows you to search the web using Google via SerpAPI. Input should be a search query."
47
- )
48
-
49
- # Initialize LLM
50
- model = ChatOpenAI( model="gpt-4o",temperature=0)
51
- vision_llm = ChatOpenAI(model="gpt-4o")
52
-
53
- #search_tool = DuckDuckGoSearchRun()
54
- tools = [serpapi_tool]
55
-
56
- llm_with_tools = model.bind_tools(tools, parallel_tool_calls=False)
57
-
58
- class AgentState(TypedDict):
59
- question: Dict[str, Any]
60
- messages: List[Any]
61
- answer: Optional[str]
62
- tool_calls: Optional[list]
63
- tool_outputs: Optional[list]
64
-
65
- def assistant(state: AgentState):
66
- print("\n--- ASSISTANT NODE ---")
67
- print(f"State received: {state}")
68
- question = state["question"]
69
- print(f"Question dict: {question}")
70
- #textual_description_of_tool = """
71
- #search_tool: A tool that allows you to search the web using DuckDuckGo. It returns a list of search results based on the query provided.
72
- #"""
73
- textual_description_of_tool = """
74
- serpapi_search: A tool that allows you to search the web using Google via SerpAPI. It returns a list of search results based on the query provided.
75
- """
76
- system_prompt = SystemMessage(
77
- content=f"""
78
- You are an expert assistant. Try to answer the question as accurately as possible. Give only the minimum necessary information to answer the question.
79
- E.g. if the question is to only give the first name of a person then only give the fist name. No additional information or context is needed.
80
- Or if you are asked to give a number, then only give the number.
81
- If you don't know the answer, you can use the tools available to you.
82
- But try to answer the question first and only use the tools if you are not sure.
83
- If you get a response from a tool, try to come to the final answer.
84
- You can use the following tools to help you:
85
- {textual_description_of_tool}
86
- """
87
- )
88
- # Always include conversation history
89
- messages = [system_prompt] + state.get("messages", [])
90
- # Add the user question only if not already present
91
- if not any(isinstance(m, HumanMessage) and m.content.startswith("Question:") for m in messages):
92
- user_prompt = HumanMessage(content=f"Question: {question.get('question', question)}")
93
- messages.append(user_prompt)
94
- # If tool_outputs exist, add them as context
95
- if state.get("tool_outputs"):
96
- tool_msg = HumanMessage(content=f"Tool results: {state['tool_outputs']}")
97
- messages.append(tool_msg)
98
- state.setdefault("messages", []).append(tool_msg)
99
- print(f"Messages sent to LLM: {messages}")
100
- response = llm_with_tools.invoke(messages)
101
- print(f"Raw LLM response: {response}")
102
- # If the LLM wants to call a tool, store tool_calls in state
103
- tool_calls = getattr(response, "tool_calls", None)
104
- if tool_calls:
105
- print(f"Tool calls requested: {tool_calls}")
106
- state["tool_calls"] = tool_calls
107
- state["answer"] = "" # Not final yet
108
- state.setdefault("messages", []).append(AIMessage(content="Calling tool: " + str(tool_calls)))
109
- else:
110
- state["answer"] = response.content.strip()
111
- print(f"Model response: {state['answer']}")
112
- state.setdefault("messages", []).append(AIMessage(content=state["answer"]))
113
- return state
114
-
115
- def tool_node(state: AgentState):
116
- print("\n--- TOOL NODE ---")
117
- print(f"State received: {state}")
118
- outputs = []
119
- for call in state.get("tool_calls", []):
120
- print(f"Tool call: {call}")
121
- args = call.get("args", {})
122
- # Try to get 'query' or fallback to the first value
123
- query = args.get("query")
124
- if query is None and len(args) > 0:
125
- query = list(args.values())[0]
126
- print(f"Query to use: {query}")
127
- if call["name"] == "serpapi_search":
128
- try:
129
- result = serpapi_search(query)
130
- except Exception as e:
131
- print(f"Error running SerpAPI search: {e}")
132
- result = f"Error: {e}"
133
- outputs.append(result)
134
- state["tool_outputs"] = outputs
135
- state["tool_calls"] = None # Clear tool calls
136
- # Append tool output to conversation history
137
- state.setdefault("messages", []).append(HumanMessage(content=f"Tool results: {outputs}"))
138
- return state
139
-
140
- #building the graph
141
- answering_graph = StateGraph(AgentState)
142
-
143
- # Add nodes
144
- answering_graph.add_node("assistant", assistant)
145
- #answering_graph.add_node("tools", ToolNode(tools))
146
- answering_graph.add_node("tools", tool_node)
147
-
148
- # Add edges
149
- answering_graph.add_edge(START, "assistant")
150
- answering_graph.add_conditional_edges(
151
- "assistant",
152
- lambda state: "tools" if state.get("tool_calls") else END
153
- )
154
- answering_graph.add_edge("tools", "assistant")
155
-
156
- # Compile the graph
157
- compiled_graph = answering_graph.compile()
158
-
159
- class BasicAgent:
160
- def __init__(self):
161
- print("BasicAgent initialized.")
162
- def __call__(self, question: str) -> str:
163
- question_text = question.get("question")
164
- print(f"Agent received question (first 50 chars): {question_text[:50]}...")
165
-
166
- initial_state = {
167
- "question": question,
168
- "messages": [],
169
- "answer": None
170
- }
171
-
172
- print(f"Initial state: {initial_state}")
173
- answer = compiled_graph.invoke(initial_state)
174
- print(f"Agent returning answer: {answer.get('answer')}")
175
- return answer.get("answer")
176
-
177
  def run_and_submit_all( profile: gr.OAuthProfile | None):
178
  """
179
  Fetches all questions, runs the BasicAgent on them, submits all answers,
 
15
  from langchain_core.utils.function_calling import convert_to_openai_tool
16
  from langchain.tools import Tool
17
  from serpapi import GoogleSearch
18
+ from agent import BasicAgent
19
 
20
  # (Keep Constants as is)
21
  # --- Constants ---
 
26
 
27
  SERPAPI_API_KEY = os.environ["SERPAPI_TOKEN"]
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  def run_and_submit_all( profile: gr.OAuthProfile | None):
30
  """
31
  Fetches all questions, runs the BasicAgent on them, submits all answers,
requirements.txt CHANGED
@@ -9,4 +9,5 @@ serpapi
9
  gradio
10
  requests
11
  pandas
12
- ipython
 
 
9
  gradio
10
  requests
11
  pandas
12
+ ipython
13
+ beautifulsoup4