Added duckduckgo search.
Browse files- app.py +147 -31
- requirements.txt +2 -1
app.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1 |
import os
|
2 |
import gradio as gr
|
3 |
import requests
|
|
|
4 |
import pandas as pd
|
5 |
from openai import OpenAI
|
|
|
6 |
|
7 |
|
8 |
# (Keep Constants as is)
|
@@ -16,48 +18,162 @@ DEFAULT_API_URL = "https://agents-course-unit4-scoring.hf.space"
|
|
16 |
class BasicAgent:
|
17 |
def __init__(self):
|
18 |
print("BasicAgent initialized.")
|
19 |
-
|
20 |
self.client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OR_TOKEN"))
|
21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
def __call__(self, question: str) -> str:
|
23 |
-
print(f"Agent received question
|
24 |
|
25 |
try:
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
},
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
messages=[
|
37 |
-
{
|
38 |
-
"role": "system",
|
39 |
-
"content": "You are a general AI assistant. I will ask you a question. Do not report your thoughts, only give 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. 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. 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. 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.",
|
40 |
-
},
|
41 |
-
{
|
42 |
-
"role": "user",
|
43 |
-
"content": [
|
44 |
{
|
45 |
"type": "text",
|
46 |
"text": question
|
47 |
},
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
}
|
56 |
-
|
57 |
-
|
58 |
-
answer = completion.choices[0].message.content
|
59 |
-
print(f"Agent generated response (first 50 chars): {answer[:50]}...")
|
60 |
-
return answer
|
61 |
except Exception as e:
|
62 |
print(f"Error generating response: {e}")
|
63 |
fallback_answer = "I apologize, but I encountered an error when trying to answer your question."
|
|
|
1 |
import os
|
2 |
import gradio as gr
|
3 |
import requests
|
4 |
+
import json
|
5 |
import pandas as pd
|
6 |
from openai import OpenAI
|
7 |
+
from bs4 import BeautifulSoup
|
8 |
|
9 |
|
10 |
# (Keep Constants as is)
|
|
|
18 |
class BasicAgent:
|
19 |
def __init__(self):
|
20 |
print("BasicAgent initialized.")
|
|
|
21 |
self.client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=os.getenv("OR_TOKEN"))
|
22 |
|
23 |
+
def duckduckgo_search(self, query: str, num_results: int = 3) -> list:
|
24 |
+
"""
|
25 |
+
Perform a search using DuckDuckGo and return the results.
|
26 |
+
|
27 |
+
Args:
|
28 |
+
query: The search query string
|
29 |
+
num_results: Maximum number of results to return (default: 5)
|
30 |
+
|
31 |
+
Returns:
|
32 |
+
List of dictionaries containing search results with title, url, and snippet
|
33 |
+
"""
|
34 |
+
print(f"Performing DuckDuckGo search for: {query}")
|
35 |
+
|
36 |
+
try:
|
37 |
+
headers = {
|
38 |
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
39 |
+
}
|
40 |
+
|
41 |
+
# Format the query for the URL
|
42 |
+
formatted_query = query.replace(' ', '+')
|
43 |
+
url = f"https://html.duckduckgo.com/html/?q={formatted_query}"
|
44 |
+
|
45 |
+
# Send the request
|
46 |
+
response = requests.get(url, headers=headers, timeout=10)
|
47 |
+
response.raise_for_status()
|
48 |
+
|
49 |
+
# Parse the HTML response
|
50 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
51 |
+
|
52 |
+
# Extract search results
|
53 |
+
results = []
|
54 |
+
for result in soup.select('.result'):
|
55 |
+
title_elem = result.select_one('.result__title')
|
56 |
+
link_elem = result.select_one('.result__url')
|
57 |
+
snippet_elem = result.select_one('.result__snippet')
|
58 |
+
|
59 |
+
if title_elem and link_elem:
|
60 |
+
title = title_elem.get_text(strip=True)
|
61 |
+
url = link_elem.get('href') if link_elem.get('href') else link_elem.get_text(strip=True)
|
62 |
+
snippet = snippet_elem.get_text(strip=True) if snippet_elem else ""
|
63 |
+
|
64 |
+
results.append({
|
65 |
+
"title": title,
|
66 |
+
"url": url,
|
67 |
+
"snippet": snippet
|
68 |
+
})
|
69 |
+
|
70 |
+
if len(results) >= num_results:
|
71 |
+
break
|
72 |
+
|
73 |
+
print(f"Found {len(results)} results for query: {query}")
|
74 |
+
return results
|
75 |
+
except Exception as e:
|
76 |
+
print(f"Error during DuckDuckGo search: {e}")
|
77 |
+
return []
|
78 |
+
|
79 |
def __call__(self, question: str) -> str:
|
80 |
+
print(f"Agent received question: {question}...")
|
81 |
|
82 |
try:
|
83 |
+
tools = [
|
84 |
+
{
|
85 |
+
"type": "function",
|
86 |
+
"function": {
|
87 |
+
"name": "duckduckgo_search",
|
88 |
+
"description": "Search the web using DuckDuckGo and return relevant results",
|
89 |
+
"parameters": {
|
90 |
+
"type": "object",
|
91 |
+
"properties": {
|
92 |
+
"query": {
|
93 |
+
"type": "string",
|
94 |
+
"description": "The search query string"
|
95 |
+
},
|
96 |
+
"num_results": {
|
97 |
+
"type": "integer",
|
98 |
+
"description": "Maximum number of results to return",
|
99 |
+
"default": 5
|
100 |
+
}
|
101 |
+
},
|
102 |
+
"required": ["query"]
|
103 |
+
}
|
104 |
+
}
|
105 |
+
}
|
106 |
+
]
|
107 |
+
|
108 |
+
tools_mapping = {
|
109 |
+
"duckduckgo_search": self.duckduckgo_search
|
110 |
+
}
|
111 |
+
|
112 |
+
messages = [
|
113 |
+
{
|
114 |
+
"role": "system",
|
115 |
+
"content": "You are a general AI assistant. I will ask you a question. Read the question carefully. Break down the question into multiple questions and use the tools available to you to answer the question. Do not report your thoughts, only give 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. 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. 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. 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.",
|
116 |
},
|
117 |
+
{
|
118 |
+
"role": "user",
|
119 |
+
"content": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
{
|
121 |
"type": "text",
|
122 |
"text": question
|
123 |
},
|
124 |
+
# {
|
125 |
+
# "type": "image_url",
|
126 |
+
# "image_url": {
|
127 |
+
# "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
|
128 |
+
# }
|
129 |
+
# }
|
130 |
+
]
|
131 |
+
}
|
132 |
+
]
|
133 |
+
|
134 |
+
# Execute once
|
135 |
+
for _ in range(3):
|
136 |
+
# Generate response
|
137 |
+
print("Using Inference API for generation...")
|
138 |
+
completion = self.client.chat.completions.create(
|
139 |
+
extra_headers={
|
140 |
+
"HTTP-Referer": "<YOUR_SITE_URL>", # Optional. Site URL for rankings on openrouter.ai.
|
141 |
+
"X-Title": "<YOUR_SITE_NAME>", # Optional. Site title for rankings on openrouter.ai.
|
142 |
+
},
|
143 |
+
extra_body={},
|
144 |
+
# model="meta-llama/llama-4-scout:free",
|
145 |
+
model="meta-llama/llama-4-maverick:free",
|
146 |
+
# model="google/gemini-2.0-flash-exp:free",
|
147 |
+
# model="mistralai/mistral-small-3.1-24b-instruct:free",
|
148 |
+
#tools=tools,
|
149 |
+
messages=messages
|
150 |
+
)
|
151 |
+
|
152 |
+
messages.append(completion.choices[0].message)
|
153 |
+
print(f"Message after completion: {completion.choices[0].message}")
|
154 |
+
|
155 |
+
if completion.choices[0].message.tool_calls is None:
|
156 |
+
answer = completion.choices[0].message.content
|
157 |
+
print(f"Agent generated response: {answer}")
|
158 |
+
return answer
|
159 |
+
|
160 |
+
for tool_call in completion.choices[0].message.tool_calls:
|
161 |
+
"""
|
162 |
+
In this case we only provided one tool, so we know what function to call.
|
163 |
+
When providing multiple tools, you can inspect `tool_call.function.name`
|
164 |
+
to figure out what function you need to call locally.
|
165 |
+
"""
|
166 |
+
tool_name = tool_call.function.name
|
167 |
+
tool_args = json.loads(tool_call.function.arguments)
|
168 |
+
tool_response = tools_mapping[tool_name](**tool_args)
|
169 |
+
message = {
|
170 |
+
"role": "tool",
|
171 |
+
"tool_call_id": tool_call.id,
|
172 |
+
"name": tool_name,
|
173 |
+
"content": json.dumps(tool_response),
|
174 |
}
|
175 |
+
messages.append(message)
|
176 |
+
print(f"Message after tools call: {message}")
|
|
|
|
|
|
|
177 |
except Exception as e:
|
178 |
print(f"Error generating response: {e}")
|
179 |
fallback_answer = "I apologize, but I encountered an error when trying to answer your question."
|
requirements.txt
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
gradio
|
2 |
requests
|
3 |
huggingface_hub
|
4 |
-
openai
|
|
|
|
1 |
gradio
|
2 |
requests
|
3 |
huggingface_hub
|
4 |
+
openai
|
5 |
+
bs4
|