Facelook commited on
Commit
607fb80
·
1 Parent(s): 7b9b915

Added duckduckgo search.

Browse files
Files changed (2) hide show
  1. app.py +147 -31
  2. 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 (first 50 chars): {question[:50]}...")
24
 
25
  try:
26
- # Generate response
27
- print("Using Inference API for generation...")
28
- completion = self.client.chat.completions.create(
29
- extra_headers={
30
- "HTTP-Referer": "<YOUR_SITE_URL>", # Optional. Site URL for rankings on openrouter.ai.
31
- "X-Title": "<YOUR_SITE_NAME>", # Optional. Site title for rankings on openrouter.ai.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  },
33
- extra_body={},
34
- # model="meta-llama/llama-4-scout:free",
35
- model="meta-llama/llama-4-maverick:free",
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
- # "type": "image_url",
50
- # "image_url": {
51
- # "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"
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