dhisana 0.0.1.dev116__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.
- dhisana/schemas/common.py +10 -1
- dhisana/schemas/sales.py +203 -22
- dhisana/utils/add_mapping.py +0 -2
- dhisana/utils/apollo_tools.py +739 -119
- dhisana/utils/built_with_api_tools.py +4 -2
- dhisana/utils/check_email_validity_tools.py +35 -18
- dhisana/utils/check_for_intent_signal.py +1 -2
- dhisana/utils/check_linkedin_url_validity.py +34 -8
- dhisana/utils/clay_tools.py +3 -2
- dhisana/utils/clean_properties.py +1 -4
- dhisana/utils/compose_salesnav_query.py +0 -1
- dhisana/utils/compose_search_query.py +7 -3
- dhisana/utils/composite_tools.py +0 -1
- dhisana/utils/dataframe_tools.py +2 -2
- dhisana/utils/email_body_utils.py +72 -0
- dhisana/utils/email_provider.py +174 -35
- dhisana/utils/enrich_lead_information.py +183 -53
- dhisana/utils/fetch_openai_config.py +129 -0
- dhisana/utils/field_validators.py +1 -1
- dhisana/utils/g2_tools.py +0 -1
- dhisana/utils/generate_content.py +0 -1
- dhisana/utils/generate_email.py +68 -23
- dhisana/utils/generate_email_response.py +294 -46
- dhisana/utils/generate_flow.py +0 -1
- dhisana/utils/generate_linkedin_connect_message.py +9 -2
- dhisana/utils/generate_linkedin_response_message.py +137 -66
- dhisana/utils/generate_structured_output_internal.py +317 -164
- dhisana/utils/google_custom_search.py +150 -44
- dhisana/utils/google_oauth_tools.py +721 -0
- dhisana/utils/google_workspace_tools.py +278 -54
- dhisana/utils/hubspot_clearbit.py +3 -1
- dhisana/utils/hubspot_crm_tools.py +718 -272
- dhisana/utils/instantly_tools.py +3 -1
- dhisana/utils/lusha_tools.py +10 -7
- dhisana/utils/mailgun_tools.py +150 -0
- dhisana/utils/microsoft365_tools.py +447 -0
- dhisana/utils/openai_assistant_and_file_utils.py +121 -177
- dhisana/utils/openai_helpers.py +8 -6
- dhisana/utils/parse_linkedin_messages_txt.py +1 -3
- dhisana/utils/profile.py +37 -0
- dhisana/utils/proxy_curl_tools.py +377 -76
- dhisana/utils/proxycurl_search_leads.py +426 -0
- dhisana/utils/research_lead.py +3 -3
- dhisana/utils/sales_navigator_crawler.py +1 -6
- dhisana/utils/salesforce_crm_tools.py +323 -50
- dhisana/utils/search_router.py +131 -0
- dhisana/utils/search_router_jobs.py +51 -0
- dhisana/utils/sendgrid_tools.py +126 -91
- dhisana/utils/serarch_router_local_business.py +75 -0
- dhisana/utils/serpapi_additional_tools.py +290 -0
- dhisana/utils/serpapi_google_jobs.py +117 -0
- dhisana/utils/serpapi_google_search.py +188 -0
- dhisana/utils/serpapi_local_business_search.py +129 -0
- dhisana/utils/serpapi_search_tools.py +360 -432
- dhisana/utils/serperdev_google_jobs.py +125 -0
- dhisana/utils/serperdev_local_business.py +154 -0
- dhisana/utils/serperdev_search.py +233 -0
- dhisana/utils/smtp_email_tools.py +178 -18
- dhisana/utils/test_connect.py +1603 -130
- dhisana/utils/trasform_json.py +3 -3
- dhisana/utils/web_download_parse_tools.py +0 -1
- dhisana/utils/zoominfo_tools.py +2 -3
- dhisana/workflow/test.py +1 -1
- {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/METADATA +1 -1
- dhisana-0.0.1.dev236.dist-info/RECORD +100 -0
- {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/WHEEL +1 -1
- dhisana-0.0.1.dev116.dist-info/RECORD +0 -83
- {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/entry_points.txt +0 -0
- {dhisana-0.0.1.dev116.dist-info → dhisana-0.0.1.dev236.dist-info}/top_level.txt +0 -0
dhisana/utils/sendgrid_tools.py
CHANGED
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
18
|
-
|
|
29
|
+
# --------------------------------------------------------------------------- #
|
|
30
|
+
# SendGrid helpers
|
|
31
|
+
# --------------------------------------------------------------------------- #
|
|
19
32
|
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
64
|
+
Send an email using SendGrid's v3 Mail Send API.
|
|
84
65
|
|
|
85
66
|
Parameters:
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
"https://api.sendgrid.com/v3/mail/send",
|
|
110
|
+
headers=headers,
|
|
111
|
+
json=payload,
|
|
111
112
|
) as response:
|
|
112
|
-
#
|
|
113
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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)})]
|