dhisana 0.0.1.dev85__py3-none-any.whl → 0.0.1.dev236__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. dhisana/schemas/common.py +33 -0
  2. dhisana/schemas/sales.py +224 -23
  3. dhisana/utils/add_mapping.py +72 -63
  4. dhisana/utils/apollo_tools.py +739 -109
  5. dhisana/utils/built_with_api_tools.py +4 -2
  6. dhisana/utils/cache_output_tools.py +23 -23
  7. dhisana/utils/check_email_validity_tools.py +456 -458
  8. dhisana/utils/check_for_intent_signal.py +1 -2
  9. dhisana/utils/check_linkedin_url_validity.py +34 -8
  10. dhisana/utils/clay_tools.py +3 -2
  11. dhisana/utils/clean_properties.py +3 -1
  12. dhisana/utils/compose_salesnav_query.py +0 -1
  13. dhisana/utils/compose_search_query.py +7 -3
  14. dhisana/utils/composite_tools.py +0 -1
  15. dhisana/utils/dataframe_tools.py +2 -2
  16. dhisana/utils/email_body_utils.py +72 -0
  17. dhisana/utils/email_provider.py +375 -0
  18. dhisana/utils/enrich_lead_information.py +585 -85
  19. dhisana/utils/fetch_openai_config.py +129 -0
  20. dhisana/utils/field_validators.py +1 -1
  21. dhisana/utils/g2_tools.py +0 -1
  22. dhisana/utils/generate_content.py +0 -1
  23. dhisana/utils/generate_email.py +69 -16
  24. dhisana/utils/generate_email_response.py +298 -41
  25. dhisana/utils/generate_flow.py +0 -1
  26. dhisana/utils/generate_linkedin_connect_message.py +19 -6
  27. dhisana/utils/generate_linkedin_response_message.py +156 -65
  28. dhisana/utils/generate_structured_output_internal.py +351 -131
  29. dhisana/utils/google_custom_search.py +150 -44
  30. dhisana/utils/google_oauth_tools.py +721 -0
  31. dhisana/utils/google_workspace_tools.py +391 -25
  32. dhisana/utils/hubspot_clearbit.py +3 -1
  33. dhisana/utils/hubspot_crm_tools.py +771 -167
  34. dhisana/utils/instantly_tools.py +3 -1
  35. dhisana/utils/lusha_tools.py +10 -7
  36. dhisana/utils/mailgun_tools.py +150 -0
  37. dhisana/utils/microsoft365_tools.py +447 -0
  38. dhisana/utils/openai_assistant_and_file_utils.py +121 -177
  39. dhisana/utils/openai_helpers.py +19 -16
  40. dhisana/utils/parse_linkedin_messages_txt.py +2 -3
  41. dhisana/utils/profile.py +37 -0
  42. dhisana/utils/proxy_curl_tools.py +507 -206
  43. dhisana/utils/proxycurl_search_leads.py +426 -0
  44. dhisana/utils/research_lead.py +121 -68
  45. dhisana/utils/sales_navigator_crawler.py +1 -6
  46. dhisana/utils/salesforce_crm_tools.py +323 -50
  47. dhisana/utils/search_router.py +131 -0
  48. dhisana/utils/search_router_jobs.py +51 -0
  49. dhisana/utils/sendgrid_tools.py +126 -91
  50. dhisana/utils/serarch_router_local_business.py +75 -0
  51. dhisana/utils/serpapi_additional_tools.py +290 -0
  52. dhisana/utils/serpapi_google_jobs.py +117 -0
  53. dhisana/utils/serpapi_google_search.py +188 -0
  54. dhisana/utils/serpapi_local_business_search.py +129 -0
  55. dhisana/utils/serpapi_search_tools.py +363 -432
  56. dhisana/utils/serperdev_google_jobs.py +125 -0
  57. dhisana/utils/serperdev_local_business.py +154 -0
  58. dhisana/utils/serperdev_search.py +233 -0
  59. dhisana/utils/smtp_email_tools.py +576 -0
  60. dhisana/utils/test_connect.py +1765 -92
  61. dhisana/utils/trasform_json.py +95 -16
  62. dhisana/utils/web_download_parse_tools.py +0 -1
  63. dhisana/utils/zoominfo_tools.py +2 -3
  64. dhisana/workflow/test.py +1 -1
  65. {dhisana-0.0.1.dev85.dist-info → dhisana-0.0.1.dev236.dist-info}/METADATA +5 -2
  66. dhisana-0.0.1.dev236.dist-info/RECORD +100 -0
  67. {dhisana-0.0.1.dev85.dist-info → dhisana-0.0.1.dev236.dist-info}/WHEEL +1 -1
  68. dhisana-0.0.1.dev85.dist-info/RECORD +0 -81
  69. {dhisana-0.0.1.dev85.dist-info → dhisana-0.0.1.dev236.dist-info}/entry_points.txt +0 -0
  70. {dhisana-0.0.1.dev85.dist-info → dhisana-0.0.1.dev236.dist-info}/top_level.txt +0 -0
@@ -1,117 +1,152 @@
1
+ """Mail delivery helpers for SendGrid and compatibility exports for Mailgun.
2
+
3
+ This module now contains:
4
+ - SendGrid: helpers to send e-mail via SendGrid's REST API.
5
+ - Mailgun: re-exports for helpers that were moved to `mailgun_tools.py`.
6
+ """
7
+
1
8
  import logging
2
9
  import os
3
10
  from typing import Optional, List, Dict
11
+ from email.utils import parseaddr
12
+
4
13
  import aiohttp
5
- from dhisana.utils.assistant_tool_tag import assistant_tool
6
14
 
15
+ from dhisana.utils.assistant_tool_tag import assistant_tool
16
+ from dhisana.schemas.common import SendEmailContext
17
+ from dhisana.utils.email_body_utils import body_variants
7
18
 
8
- def get_mailgun_notify_key(tool_config: Optional[List[Dict]] = None) -> str:
9
- """
10
- Retrieves the MAILGUN_NOTIFY_KEY access token from the provided tool configuration.
19
+ # --------------------------------------------------------------------------- #
20
+ # Mailgun (re-exported from dedicated module for backward compatibility)
21
+ # --------------------------------------------------------------------------- #
22
+ from .mailgun_tools import (
23
+ get_mailgun_notify_key,
24
+ get_mailgun_notify_domain,
25
+ send_email_with_mailgun,
26
+ )
11
27
 
12
- Args:
13
- tool_config (list): A list of dictionaries containing the tool configuration.
14
- Each dictionary should have a "name" key and a "configuration" key,
15
- where "configuration" is a list of dictionaries containing "name" and "value" keys.
16
28
 
17
- Returns:
18
- str: The MAILGUN_NOTIFY_KEY access token.
29
+ # --------------------------------------------------------------------------- #
30
+ # SendGrid helpers
31
+ # --------------------------------------------------------------------------- #
19
32
 
20
- Raises:
21
- ValueError: If the access token is not found in the tool configuration or environment variable.
33
+ def get_sendgrid_api_key(tool_config: Optional[List[Dict]] = None) -> str:
22
34
  """
23
- if tool_config:
24
- mailgun_config = next(
25
- (item for item in tool_config if item.get("name") == "mailgun"), None
26
- )
27
- if mailgun_config:
28
- config_map = {
29
- item["name"]: item["value"]
30
- for item in mailgun_config.get("configuration", [])
31
- if item
32
- }
33
- MAILGUN_NOTIFY_KEY = config_map.get("apiKey")
34
- else:
35
- MAILGUN_NOTIFY_KEY = None
36
- else:
37
- MAILGUN_NOTIFY_KEY = None
38
-
39
- MAILGUN_NOTIFY_KEY = MAILGUN_NOTIFY_KEY or os.getenv("MAILGUN_NOTIFY_KEY")
40
- if not MAILGUN_NOTIFY_KEY:
41
- raise ValueError("MAILGUN_NOTIFY_KEY access token not found in tool_config or environment variable")
42
- return MAILGUN_NOTIFY_KEY
43
-
44
- def get_mailgun_notify_domain(tool_config: Optional[List[Dict]] = None) -> str:
45
- """
46
- Retrieves the MAILGUN_NOTIFY_DOMAIN from the provided tool configuration.
47
-
48
- Args:
49
- tool_config (list): A list of dictionaries containing the tool configuration.
50
- Each dictionary should have a "name" key and a "configuration" key,
51
- where "configuration" is a list of dictionaries containing "name" and "value" keys.
52
-
53
- Returns:
54
- str: The MAILGUN_NOTIFY_DOMAIN.
35
+ Retrieve the SendGrid API key from tool_config or environment.
55
36
 
56
- Raises:
57
- ValueError: If the domain is not found in the tool configuration or environment variable.
37
+ Looks for an integration named "sendgrid" and reads configuration item
38
+ with name "apiKey". Falls back to env var SENDGRID_API_KEY.
58
39
  """
40
+ key: Optional[str] = None
59
41
  if tool_config:
60
- mailgun_config = next(
61
- (item for item in tool_config if item.get("name") == "mailgun"), None
42
+ cfg = next((c for c in tool_config if c.get("name") == "sendgrid"), None)
43
+ if cfg:
44
+ cfg_map = {i.get("name"): i.get("value") for i in cfg.get("configuration", []) if i}
45
+ key = cfg_map.get("apiKey")
46
+ key = key or os.getenv("SENDGRID_API_KEY")
47
+ if not key:
48
+ raise ValueError(
49
+ "SendGrid integration is not configured. Please configure the connection to SendGrid in Integrations."
62
50
  )
63
- if mailgun_config:
64
- config_map = {
65
- item["name"]: item["value"]
66
- for item in mailgun_config.get("configuration", [])
67
- if item
68
- }
69
- MAILGUN_NOTIFY_DOMAIN = config_map.get("notifyDomain")
70
- else:
71
- MAILGUN_NOTIFY_DOMAIN = None
72
- else:
73
- MAILGUN_NOTIFY_DOMAIN = None
51
+ return key
74
52
 
75
- MAILGUN_NOTIFY_DOMAIN = MAILGUN_NOTIFY_DOMAIN or os.getenv("MAILGUN_NOTIFY_DOMAIN")
76
- if not MAILGUN_NOTIFY_DOMAIN:
77
- raise ValueError("MAILGUN_NOTIFY_DOMAIN not found in tool_config or environment variable")
78
- return MAILGUN_NOTIFY_DOMAIN
79
53
 
80
54
  @assistant_tool
81
- async def send_email_with_mailgun(sender: str, recipients: List[str], subject: str, message: str, tool_config: Optional[List[Dict]] = None):
55
+ async def send_email_with_sendgrid(
56
+ sender: str,
57
+ recipients: List[str],
58
+ subject: str,
59
+ message: str,
60
+ tool_config: Optional[List[Dict]] = None,
61
+ body_format: Optional[str] = None,
62
+ ):
82
63
  """
83
- Send an email using the Mailgun API.
64
+ Send an email using SendGrid's v3 Mail Send API.
84
65
 
85
66
  Parameters:
86
- - **sender** (*str*): The email address of the sender.
87
- - **recipients** (*List[str]*): A list of recipient email addresses.
88
- - **subject** (*str*): The subject of the email.
89
- - **message** (*str*): The HTML content of the email.
67
+ - sender: Either "Name <email@example.com>" or a plain e-mail address.
68
+ - recipients: List of recipient e-mail addresses.
69
+ - subject: Subject string.
70
+ - message: HTML body content.
71
+ - tool_config: Optional integration configuration list.
90
72
  """
73
+ api_key = get_sendgrid_api_key(tool_config)
74
+
75
+ name, email_addr = parseaddr(sender)
76
+ from_obj: Dict[str, str] = {"email": email_addr or sender}
77
+ if name:
78
+ from_obj["name"] = name
79
+
80
+ to_list = [{"email": r} for r in recipients if r]
81
+ if not to_list:
82
+ return {"error": "No recipients provided"}
83
+
84
+ plain_body, html_body, _ = body_variants(message, body_format)
85
+ content = [
86
+ {"type": "text/plain", "value": plain_body},
87
+ {"type": "text/html", "value": html_body},
88
+ ]
89
+
90
+ payload = {
91
+ "personalizations": [
92
+ {
93
+ "to": to_list,
94
+ "subject": subject,
95
+ }
96
+ ],
97
+ "from": from_obj,
98
+ "content": content,
99
+ }
100
+
101
+ headers = {
102
+ "Authorization": f"Bearer {api_key}",
103
+ "Content-Type": "application/json",
104
+ }
105
+
91
106
  try:
92
- # Retrieve Mailgun API key and domain from tool configuration or environment variables
93
- api_key = get_mailgun_notify_key(tool_config)
94
- domain = get_mailgun_notify_domain(tool_config)
95
-
96
- # Prepare the data payload for the email
97
- data = {
98
- "from": sender,
99
- "to": recipients,
100
- "subject": subject,
101
- "html": message,
102
- }
103
-
104
- # Create an asynchronous HTTP session
105
107
  async with aiohttp.ClientSession() as session:
106
- # Send a POST request to the Mailgun API to send the email
107
108
  async with session.post(
108
- f"https://api.mailgun.net/v3/{domain}/messages",
109
- auth=aiohttp.BasicAuth("api", api_key),
110
- data=data,
109
+ "https://api.sendgrid.com/v3/mail/send",
110
+ headers=headers,
111
+ json=payload,
111
112
  ) as response:
112
- # Return the response text
113
- return await response.text()
113
+ # SendGrid returns 202 Accepted on success with empty body
114
+ if response.status == 202:
115
+ return {"status": 202, "message": "accepted"}
116
+ # On error, try to parse JSON for helpful message
117
+ try:
118
+ err = await response.json()
119
+ except Exception:
120
+ err = {"text": await response.text()}
121
+ return {"error": err, "status": response.status}
114
122
  except Exception as ex:
115
- # Log any exceptions that occur and return an error message
116
- logging.warning(f"Error sending email invite: {ex}")
117
- return {"error": str(ex)}
123
+ logging.warning(f"Error sending email via SendGrid: {ex}")
124
+ return {"error": str(ex)}
125
+
126
+
127
+ async def send_email_using_sendgrid_async(
128
+ ctx: SendEmailContext,
129
+ tool_config: Optional[List[Dict]] = None,
130
+ ) -> str:
131
+ """
132
+ Provider-style wrapper for SendGrid using SendEmailContext.
133
+ Returns an opaque token since SendGrid does not return a message id.
134
+ """
135
+ plain_body, html_body, _ = body_variants(
136
+ ctx.body,
137
+ getattr(ctx, "body_format", None),
138
+ )
139
+ result = await send_email_with_sendgrid(
140
+ sender=f"{ctx.sender_name} <{ctx.sender_email}>",
141
+ recipients=[ctx.recipient],
142
+ subject=ctx.subject,
143
+ message=ctx.body or "",
144
+ body_format=getattr(ctx, "body_format", None),
145
+ tool_config=tool_config,
146
+ )
147
+ # Normalise output to a string id-like value
148
+ if isinstance(result, dict) and result.get("status") == 202:
149
+ return f"sent:{ctx.sender_email}:{ctx.recipient}:{ctx.subject}"
150
+ if isinstance(result, dict) and "error" in result:
151
+ raise RuntimeError(f"SendGrid send failed: {result['error']}")
152
+ return str(result)
@@ -0,0 +1,75 @@
1
+ import json
2
+ import logging
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from dhisana.utils.assistant_tool_tag import assistant_tool
6
+
7
+ # Your provider-specific helpers (paths may differ in your repo)
8
+ from dhisana.utils.serperdev_local_business import search_local_business_serper
9
+ from dhisana.utils.serpapi_local_business_search import search_local_business_serpai
10
+
11
+ # Re-use your existing provider detector
12
+ from dhisana.utils.search_router import detect_search_provider # or copy the function shown earlier
13
+
14
+ logger = logging.getLogger(__name__)
15
+ logging.basicConfig(level=logging.INFO)
16
+
17
+
18
+ @assistant_tool
19
+ async def search_local_business_with_tools(
20
+ query: str,
21
+ number_of_results: int = 20,
22
+ offset: int = 0,
23
+ tool_config: Optional[List[Dict]] = None,
24
+ location: Optional[str] = None,
25
+ ) -> List[str]:
26
+ """
27
+ Router that returns local-business (Google Maps) results using whichever
28
+ provider is configured in `tool_config`.
29
+
30
+ Priority order:
31
+ 1. Serper.dev – uses `search_local_business_serper`
32
+ 2. SerpApi – uses `search_local_business_serpai`
33
+
34
+ Args:
35
+ query: Search string (e.g. "plumbers near Almaden Valley").
36
+ number_of_results: Desired row count (will paginate if > provider page size).
37
+ offset: Page offset (0-based; converted to provider-specific page or start).
38
+ tool_config: Dhisana tool-configuration blob listing available providers.
39
+ location: Optional city/region hint (Serper + SerpApi both accept it).
40
+
41
+ Returns:
42
+ List[str]: Each element is a JSON-encoded dict with keys:
43
+ full_name, organization_name, phone, organization_website,
44
+ rating, reviews, address, google_maps_url.
45
+ If no provider is configured, returns one item with an "error" key.
46
+ """
47
+ if not query:
48
+ logger.warning("Empty query received by local-business router.")
49
+ return []
50
+
51
+ provider = detect_search_provider(tool_config)
52
+ logger.debug("Local-business router chose provider: %s", provider)
53
+
54
+ if provider == "serperdev":
55
+ logger.info("Routing to Serper.dev local-business helper.")
56
+ return await search_local_business_serper(
57
+ query=query,
58
+ number_of_results=number_of_results,
59
+ offset=offset,
60
+ tool_config=tool_config,
61
+ location=location,
62
+ )
63
+
64
+ if provider == "serpapi":
65
+ logger.info("Routing to SerpApi local-business helper.")
66
+ return await search_local_business_serpai(
67
+ query=query,
68
+ number_of_results=number_of_results,
69
+ offset=offset,
70
+ tool_config=tool_config,
71
+ location=location,
72
+ )
73
+
74
+ logger.error("No supported local-business provider found in tool_config.")
75
+ return [json.dumps({"error": "No supported local-business provider configured."})]
@@ -0,0 +1,290 @@
1
+ import json
2
+ import os
3
+ import re
4
+ from typing import Any, Dict, List, Optional
5
+ from urllib.parse import urlparse
6
+ import aiohttp
7
+ from bs4 import BeautifulSoup
8
+ import urllib
9
+
10
+ from dhisana.utils.assistant_tool_tag import assistant_tool
11
+ from dhisana.utils.cache_output_tools import cache_output, retrieve_output
12
+ from dhisana.utils.web_download_parse_tools import fetch_html_content
13
+ from dhisana.utils.search_router import search_google_with_tools
14
+
15
+ import logging
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def get_serp_api_access_token(tool_config: Optional[List[Dict]] = None) -> str:
21
+ """
22
+ Retrieves the SERPAPI_KEY access token from the provided tool configuration.
23
+
24
+ Args:
25
+ tool_config (list): A list of dictionaries containing the tool configuration.
26
+ Each dictionary should have a "name" key and a "configuration" key,
27
+ where "configuration" is a list of dictionaries containing "name" and "value" keys.
28
+
29
+ Returns:
30
+ str: The SERPAPI_KEY access token.
31
+
32
+ Raises:
33
+ ValueError: If the SerpAPI integration has not been configured.
34
+ """
35
+ logger.info("Entering get_serp_api_access_token")
36
+ SERPAPI_KEY = None
37
+
38
+ if tool_config:
39
+ logger.debug(f"Tool config provided: {tool_config}")
40
+ serpapi_config = next(
41
+ (item for item in tool_config if item.get("name") == "serpapi"), None
42
+ )
43
+ if serpapi_config:
44
+ config_map = {
45
+ item["name"]: item["value"]
46
+ for item in serpapi_config.get("configuration", [])
47
+ if item
48
+ }
49
+ SERPAPI_KEY = config_map.get("apiKey")
50
+ else:
51
+ logger.warning("No 'serpapi' config item found in tool_config.")
52
+ else:
53
+ logger.debug("No tool_config provided or it's None.")
54
+
55
+ SERPAPI_KEY = SERPAPI_KEY or os.getenv("SERPAPI_KEY")
56
+ if not SERPAPI_KEY:
57
+ logger.error("SerpAPI integration is not configured.")
58
+ raise ValueError(
59
+ "SerpAPI integration is not configured. Please configure the connection to SerpAPI in Integrations."
60
+ )
61
+
62
+ logger.info("Retrieved SERPAPI_KEY successfully.")
63
+ return SERPAPI_KEY
64
+
65
+
66
+
67
+
68
+ @assistant_tool
69
+ async def search_google_maps(
70
+ query: str,
71
+ number_of_results: int = 3,
72
+ tool_config: Optional[List[Dict]] = None
73
+ ) -> List[str]:
74
+ """
75
+ Search Google Maps using SERP API and return the results as an array of serialized JSON strings.
76
+
77
+ Parameters:
78
+ - query (str): The search query.
79
+ - number_of_results (int): The number of results to return.
80
+ """
81
+ logger.info("Entering search_google_maps")
82
+ if not query:
83
+ logger.warning("Empty query string provided for search_google_maps.")
84
+ return []
85
+
86
+ SERPAPI_KEY = get_serp_api_access_token(tool_config)
87
+ params = {
88
+ "q": query,
89
+ "num": number_of_results,
90
+ "api_key": SERPAPI_KEY,
91
+ "engine": "google_maps"
92
+ }
93
+ url = "https://serpapi.com/search"
94
+
95
+ logger.debug(f"Searching Google Maps with params: {params}")
96
+ try:
97
+ async with aiohttp.ClientSession() as session:
98
+ async with session.get(url, params=params) as response:
99
+ logger.debug(f"Received status: {response.status}")
100
+ result = await response.json()
101
+ if response.status != 200:
102
+ logger.warning(f"Non-200 response from SERP API: {result}")
103
+ return [json.dumps({"error": result})]
104
+
105
+ serialized_results = [json.dumps(item) for item in result.get('local_results', [])]
106
+ logger.info(f"Returning {len(serialized_results)} map results.")
107
+ return serialized_results
108
+ except Exception as e:
109
+ logger.exception("Exception during search_google_maps request.")
110
+ return [json.dumps({"error": str(e)})]
111
+
112
+
113
+ @assistant_tool
114
+ async def search_google_news(
115
+ query: str,
116
+ number_of_results: int = 3,
117
+ tool_config: Optional[List[Dict]] = None
118
+ ) -> List[str]:
119
+ """
120
+ Search Google News using SERP API and return the results as an array of serialized JSON strings.
121
+
122
+ Parameters:
123
+ - query (str): The search query.
124
+ - number_of_results (int): The number of results to return.
125
+ """
126
+ logger.info("Entering search_google_news")
127
+ if not query:
128
+ logger.warning("Empty query string provided for search_google_news.")
129
+ return []
130
+
131
+ SERPAPI_KEY = get_serp_api_access_token(tool_config)
132
+ params = {
133
+ "q": query,
134
+ "num": number_of_results,
135
+ "api_key": SERPAPI_KEY,
136
+ "engine": "google_news"
137
+ }
138
+ url = "https://serpapi.com/search"
139
+
140
+ logger.debug(f"Searching Google News with params: {params}")
141
+ try:
142
+ async with aiohttp.ClientSession() as session:
143
+ async with session.get(url, params=params) as response:
144
+ logger.debug(f"Received status: {response.status}")
145
+ result = await response.json()
146
+ if response.status != 200:
147
+ logger.warning(f"Non-200 response from SERP API: {result}")
148
+ return [json.dumps({"error": result})]
149
+
150
+ serialized_results = [json.dumps(item) for item in result.get('news_results', [])]
151
+ logger.info(f"Returning {len(serialized_results)} news results.")
152
+ return serialized_results
153
+ except Exception as e:
154
+ logger.exception("Exception during search_google_news request.")
155
+ return [json.dumps({"error": str(e)})]
156
+
157
+
158
+ @assistant_tool
159
+ async def search_job_postings(
160
+ query: str,
161
+ number_of_results: int,
162
+ tool_config: Optional[List[Dict]] = None
163
+ ) -> List[str]:
164
+ """
165
+ Search for job postings using SERP API and return the results as an array of serialized JSON strings.
166
+
167
+ Parameters:
168
+ - query (str): The search query.
169
+ - number_of_results (int): The number of results to return.
170
+ """
171
+ logger.info("Entering search_job_postings")
172
+ if not query:
173
+ logger.warning("Empty query string provided for search_job_postings.")
174
+ return []
175
+
176
+ SERPAPI_KEY = get_serp_api_access_token(tool_config)
177
+ params = {
178
+ "q": query,
179
+ "num": number_of_results,
180
+ "api_key": SERPAPI_KEY,
181
+ "engine": "google_jobs"
182
+ }
183
+ url = "https://serpapi.com/search"
184
+
185
+ logger.debug(f"Searching Google Jobs with params: {params}")
186
+ try:
187
+ async with aiohttp.ClientSession() as session:
188
+ async with session.get(url, params=params) as response:
189
+ logger.debug(f"Received status: {response.status}")
190
+ result = await response.json()
191
+ if response.status != 200:
192
+ logger.warning(f"Non-200 response from SERP API: {result}")
193
+ return [json.dumps({"error": result})]
194
+
195
+ serialized_results = [json.dumps(item) for item in result.get('jobs_results', [])]
196
+ logger.info(f"Returning {len(serialized_results)} job posting results.")
197
+ return serialized_results
198
+ except Exception as e:
199
+ logger.exception("Exception during search_job_postings request.")
200
+ return [json.dumps({"error": str(e)})]
201
+
202
+
203
+ @assistant_tool
204
+ async def search_google_images(
205
+ query: str,
206
+ number_of_results: int,
207
+ tool_config: Optional[List[Dict]] = None
208
+ ) -> List[str]:
209
+ """
210
+ Search Google Images using SERP API and return the results as an array of serialized JSON strings.
211
+
212
+ Parameters:
213
+ - query (str): The search query.
214
+ - number_of_results (int): The number of results to return.
215
+ """
216
+ logger.info("Entering search_google_images")
217
+ if not query:
218
+ logger.warning("Empty query string provided for search_google_images.")
219
+ return []
220
+
221
+ SERPAPI_KEY = get_serp_api_access_token(tool_config)
222
+ params = {
223
+ "q": query,
224
+ "num": number_of_results,
225
+ "api_key": SERPAPI_KEY,
226
+ "engine": "google_images"
227
+ }
228
+ url = "https://serpapi.com/search"
229
+
230
+ logger.debug(f"Searching Google Images with params: {params}")
231
+ try:
232
+ async with aiohttp.ClientSession() as session:
233
+ async with session.get(url, params=params) as response:
234
+ logger.debug(f"Received status: {response.status}")
235
+ result = await response.json()
236
+ if response.status != 200:
237
+ logger.warning(f"Non-200 response from SERP API: {result}")
238
+ return [json.dumps({"error": result})]
239
+
240
+ serialized_results = [json.dumps(item) for item in result.get('images_results', [])]
241
+ logger.info(f"Returning {len(serialized_results)} image results.")
242
+ return serialized_results
243
+ except Exception as e:
244
+ logger.exception("Exception during search_google_images request.")
245
+ return [json.dumps({"error": str(e)})]
246
+
247
+
248
+ @assistant_tool
249
+ async def search_google_videos(
250
+ query: str,
251
+ number_of_results: int,
252
+ tool_config: Optional[List[Dict]] = None
253
+ ) -> List[str]:
254
+ """
255
+ Search Google Videos using SERP API and return the results as an array of serialized JSON strings.
256
+
257
+ Parameters:
258
+ - query (str): The search query.
259
+ - number_of_results (int): The number of results to return.
260
+ """
261
+ logger.info("Entering search_google_videos")
262
+ if not query:
263
+ logger.warning("Empty query string provided for search_google_videos.")
264
+ return []
265
+
266
+ SERPAPI_KEY = get_serp_api_access_token(tool_config)
267
+ params = {
268
+ "q": query,
269
+ "num": number_of_results,
270
+ "api_key": SERPAPI_KEY,
271
+ "engine": "google_videos"
272
+ }
273
+ url = "https://serpapi.com/search"
274
+
275
+ logger.debug(f"Searching Google Videos with params: {params}")
276
+ try:
277
+ async with aiohttp.ClientSession() as session:
278
+ async with session.get(url, params=params) as response:
279
+ logger.debug(f"Received status: {response.status}")
280
+ result = await response.json()
281
+ if response.status != 200:
282
+ logger.warning(f"Non-200 response from SERP API: {result}")
283
+ return [json.dumps({"error": result})]
284
+
285
+ serialized_results = [json.dumps(item) for item in result.get('video_results', [])]
286
+ logger.info(f"Returning {len(serialized_results)} video results.")
287
+ return serialized_results
288
+ except Exception as e:
289
+ logger.exception("Exception during search_google_videos request.")
290
+ return [json.dumps({"error": str(e)})]