Skip to content

Commit 0c7f651

Browse files
committed
feat: enhance logging and error handling across various tools; update DuckDuckGo dependency
1 parent 6b57790 commit 0c7f651

10 files changed

Lines changed: 224 additions & 74 deletions

File tree

application/agents/tools/brave.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import logging
2+
13
import requests
4+
25
from application.agents.tools.base import Tool
36

7+
logger = logging.getLogger(__name__)
8+
49

510
class BraveSearchTool(Tool):
611
"""
@@ -41,7 +46,7 @@ def _web_search(
4146
"""
4247
Performs a web search using the Brave Search API.
4348
"""
44-
print(f"Performing Brave web search for: {query}")
49+
logger.info(f"Brave web search: {query}")
4550

4651
url = f"{self.base_url}/web/search"
4752

@@ -94,7 +99,7 @@ def _image_search(
9499
"""
95100
Performs an image search using the Brave Search API.
96101
"""
97-
print(f"Performing Brave image search for: {query}")
102+
logger.info(f"Brave image search: {query}")
98103

99104
url = f"{self.base_url}/images/search"
100105

application/agents/tools/duckduckgo.py

Lines changed: 141 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
1+
import logging
2+
import time
3+
from typing import Any, Dict, Optional
4+
15
from application.agents.tools.base import Tool
2-
from duckduckgo_search import DDGS
6+
7+
logger = logging.getLogger(__name__)
8+
9+
MAX_RETRIES = 3
10+
RETRY_DELAY = 2.0
11+
DEFAULT_TIMEOUT = 15
312

413

514
class DuckDuckGoSearchTool(Tool):
@@ -10,71 +19,123 @@ class DuckDuckGoSearchTool(Tool):
1019

1120
def __init__(self, config):
1221
self.config = config
22+
self.timeout = config.get("timeout", DEFAULT_TIMEOUT)
23+
24+
def _get_ddgs_client(self):
25+
from ddgs import DDGS
26+
27+
return DDGS(timeout=self.timeout)
28+
29+
def _execute_with_retry(self, operation, operation_name: str) -> Dict[str, Any]:
30+
last_error = None
31+
for attempt in range(1, MAX_RETRIES + 1):
32+
try:
33+
results = operation()
34+
return {
35+
"status_code": 200,
36+
"results": list(results) if results else [],
37+
"message": f"{operation_name} completed successfully.",
38+
}
39+
except Exception as e:
40+
last_error = e
41+
error_str = str(e).lower()
42+
if "ratelimit" in error_str or "429" in error_str:
43+
if attempt < MAX_RETRIES:
44+
delay = RETRY_DELAY * attempt
45+
logger.warning(
46+
f"{operation_name} rate limited, retrying in {delay}s (attempt {attempt}/{MAX_RETRIES})"
47+
)
48+
time.sleep(delay)
49+
continue
50+
logger.error(f"{operation_name} failed: {e}")
51+
break
52+
return {
53+
"status_code": 500,
54+
"results": [],
55+
"message": f"{operation_name} failed: {str(last_error)}",
56+
}
1357

1458
def execute_action(self, action_name, **kwargs):
1559
actions = {
1660
"ddg_web_search": self._web_search,
1761
"ddg_image_search": self._image_search,
62+
"ddg_news_search": self._news_search,
1863
}
19-
20-
if action_name in actions:
21-
return actions[action_name](**kwargs)
22-
else:
64+
if action_name not in actions:
2365
raise ValueError(f"Unknown action: {action_name}")
66+
return actions[action_name](**kwargs)
2467

2568
def _web_search(
2669
self,
27-
query,
28-
max_results=5,
29-
):
30-
print(f"Performing DuckDuckGo web search for: {query}")
70+
query: str,
71+
max_results: int = 5,
72+
region: str = "wt-wt",
73+
safesearch: str = "moderate",
74+
timelimit: Optional[str] = None,
75+
) -> Dict[str, Any]:
76+
logger.info(f"DuckDuckGo web search: {query}")
3177

32-
try:
33-
results = DDGS().text(
78+
def operation():
79+
client = self._get_ddgs_client()
80+
return client.text(
3481
query,
35-
max_results=max_results,
82+
region=region,
83+
safesearch=safesearch,
84+
timelimit=timelimit,
85+
max_results=min(max_results, 20),
3686
)
3787

38-
return {
39-
"status_code": 200,
40-
"results": results,
41-
"message": "Web search completed successfully.",
42-
}
43-
except Exception as e:
44-
return {
45-
"status_code": 500,
46-
"message": f"Web search failed: {str(e)}",
47-
}
88+
return self._execute_with_retry(operation, "Web search")
4889

4990
def _image_search(
5091
self,
51-
query,
52-
max_results=5,
53-
):
54-
print(f"Performing DuckDuckGo image search for: {query}")
55-
56-
try:
57-
results = DDGS().images(
58-
keywords=query,
59-
max_results=max_results,
92+
query: str,
93+
max_results: int = 5,
94+
region: str = "wt-wt",
95+
safesearch: str = "moderate",
96+
timelimit: Optional[str] = None,
97+
) -> Dict[str, Any]:
98+
logger.info(f"DuckDuckGo image search: {query}")
99+
100+
def operation():
101+
client = self._get_ddgs_client()
102+
return client.images(
103+
query,
104+
region=region,
105+
safesearch=safesearch,
106+
timelimit=timelimit,
107+
max_results=min(max_results, 50),
108+
)
109+
110+
return self._execute_with_retry(operation, "Image search")
111+
112+
def _news_search(
113+
self,
114+
query: str,
115+
max_results: int = 5,
116+
region: str = "wt-wt",
117+
safesearch: str = "moderate",
118+
timelimit: Optional[str] = None,
119+
) -> Dict[str, Any]:
120+
logger.info(f"DuckDuckGo news search: {query}")
121+
122+
def operation():
123+
client = self._get_ddgs_client()
124+
return client.news(
125+
query,
126+
region=region,
127+
safesearch=safesearch,
128+
timelimit=timelimit,
129+
max_results=min(max_results, 20),
60130
)
61131

62-
return {
63-
"status_code": 200,
64-
"results": results,
65-
"message": "Image search completed successfully.",
66-
}
67-
except Exception as e:
68-
return {
69-
"status_code": 500,
70-
"message": f"Image search failed: {str(e)}",
71-
}
132+
return self._execute_with_retry(operation, "News search")
72133

73134
def get_actions_metadata(self):
74135
return [
75136
{
76137
"name": "ddg_web_search",
77-
"description": "Perform a web search using DuckDuckGo.",
138+
"description": "Search the web using DuckDuckGo. Returns titles, URLs, and snippets.",
78139
"parameters": {
79140
"type": "object",
80141
"properties": {
@@ -84,25 +145,59 @@ def get_actions_metadata(self):
84145
},
85146
"max_results": {
86147
"type": "integer",
87-
"description": "Number of results to return (default: 5)",
148+
"description": "Number of results (default: 5, max: 20)",
149+
},
150+
"region": {
151+
"type": "string",
152+
"description": "Region code (default: wt-wt for worldwide, us-en for US)",
153+
},
154+
"timelimit": {
155+
"type": "string",
156+
"description": "Time filter: d (day), w (week), m (month), y (year)",
88157
},
89158
},
90159
"required": ["query"],
91160
},
92161
},
93162
{
94163
"name": "ddg_image_search",
95-
"description": "Perform an image search using DuckDuckGo.",
164+
"description": "Search for images using DuckDuckGo. Returns image URLs and metadata.",
96165
"parameters": {
97166
"type": "object",
98167
"properties": {
99168
"query": {
100169
"type": "string",
101-
"description": "Search query",
170+
"description": "Image search query",
102171
},
103172
"max_results": {
104173
"type": "integer",
105-
"description": "Number of results to return (default: 5, max: 50)",
174+
"description": "Number of results (default: 5, max: 50)",
175+
},
176+
"region": {
177+
"type": "string",
178+
"description": "Region code (default: wt-wt for worldwide)",
179+
},
180+
},
181+
"required": ["query"],
182+
},
183+
},
184+
{
185+
"name": "ddg_news_search",
186+
"description": "Search for news articles using DuckDuckGo. Returns recent news.",
187+
"parameters": {
188+
"type": "object",
189+
"properties": {
190+
"query": {
191+
"type": "string",
192+
"description": "News search query",
193+
},
194+
"max_results": {
195+
"type": "integer",
196+
"description": "Number of results (default: 5, max: 20)",
197+
},
198+
"timelimit": {
199+
"type": "string",
200+
"description": "Time filter: d (day), w (week), m (month)",
106201
},
107202
},
108203
"required": ["query"],

application/agents/tools/mcp_tool.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
from pydantic import AnyHttpUrl, ValidationError
2929
from redis import Redis
3030

31+
logger = logging.getLogger(__name__)
32+
3133
mongo = MongoDB.get_client()
3234
db = mongo[settings.MONGO_DB_NAME]
3335

@@ -263,7 +265,7 @@ def run_in_thread():
263265
finally:
264266
loop.close()
265267
except Exception as e:
266-
print(f"Error occurred while running async operation: {e}")
268+
logger.error(f"Error occurred while running async operation: {e}")
267269
raise
268270

269271
def discover_tools(self) -> List[Dict]:

application/agents/tools/postgres.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import logging
2+
13
import psycopg2
4+
25
from application.agents.tools.base import Tool
36

7+
logger = logging.getLogger(__name__)
8+
9+
410
class PostgresTool(Tool):
511
"""
612
PostgreSQL Database Tool
@@ -17,17 +23,12 @@ def execute_action(self, action_name, **kwargs):
1723
"postgres_execute_sql": self._execute_sql,
1824
"postgres_get_schema": self._get_schema,
1925
}
20-
21-
if action_name in actions:
22-
return actions[action_name](**kwargs)
23-
else:
26+
if action_name not in actions:
2427
raise ValueError(f"Unknown action: {action_name}")
28+
return actions[action_name](**kwargs)
2529

2630
def _execute_sql(self, sql_query):
27-
"""
28-
Executes an SQL query against the PostgreSQL database using a connection string.
29-
"""
30-
conn = None # Initialize conn to None for error handling
31+
conn = None
3132
try:
3233
conn = psycopg2.connect(self.connection_string)
3334
cur = conn.cursor()
@@ -54,14 +55,14 @@ def _execute_sql(self, sql_query):
5455

5556
except psycopg2.Error as e:
5657
error_message = f"Database error: {e}"
57-
print(f"Database error: {e}")
58+
logger.error(error_message)
5859
return {
5960
"status_code": 500,
6061
"message": "Failed to execute SQL query.",
6162
"error": error_message,
6263
}
6364
finally:
64-
if conn: # Ensure connection is closed even if errors occur
65+
if conn:
6566
conn.close()
6667

6768
def _get_schema(self, db_name):
@@ -110,14 +111,14 @@ def _get_schema(self, db_name):
110111

111112
except psycopg2.Error as e:
112113
error_message = f"Database error: {e}"
113-
print(f"Database error: {e}")
114+
logger.error(error_message)
114115
return {
115116
"status_code": 500,
116117
"message": "Failed to retrieve database schema.",
117118
"error": error_message,
118119
}
119120
finally:
120-
if conn: # Ensure connection is closed even if errors occur
121+
if conn:
121122
conn.close()
122123

123124
def get_actions_metadata(self):

application/agents/tools/telegram.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import logging
2+
13
import requests
4+
25
from application.agents.tools.base import Tool
36

7+
logger = logging.getLogger(__name__)
8+
49

510
class TelegramTool(Tool):
611
"""
@@ -18,21 +23,19 @@ def execute_action(self, action_name, **kwargs):
1823
"telegram_send_message": self._send_message,
1924
"telegram_send_image": self._send_image,
2025
}
21-
22-
if action_name in actions:
23-
return actions[action_name](**kwargs)
24-
else:
26+
if action_name not in actions:
2527
raise ValueError(f"Unknown action: {action_name}")
28+
return actions[action_name](**kwargs)
2629

2730
def _send_message(self, text, chat_id):
28-
print(f"Sending message: {text}")
31+
logger.info(f"Telegram: sending message to {chat_id}")
2932
url = f"https://api.telegram.org/bot{self.token}/sendMessage"
3033
payload = {"chat_id": chat_id, "text": text}
3134
response = requests.post(url, data=payload)
3235
return {"status_code": response.status_code, "message": "Message sent"}
3336

3437
def _send_image(self, image_url, chat_id):
35-
print(f"Sending image: {image_url}")
38+
logger.info(f"Telegram: sending image to {chat_id}")
3639
url = f"https://api.telegram.org/bot{self.token}/sendPhoto"
3740
payload = {"chat_id": chat_id, "photo": image_url}
3841
response = requests.post(url, data=payload)

0 commit comments

Comments
 (0)