holmesgpt 0.11.5__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.
Potentially problematic release.
This version of holmesgpt might be problematic. Click here for more details.
- holmes/.git_archival.json +7 -0
- holmes/__init__.py +76 -0
- holmes/__init__.py.bak +76 -0
- holmes/clients/robusta_client.py +24 -0
- holmes/common/env_vars.py +47 -0
- holmes/config.py +526 -0
- holmes/core/__init__.py +0 -0
- holmes/core/conversations.py +578 -0
- holmes/core/investigation.py +152 -0
- holmes/core/investigation_structured_output.py +264 -0
- holmes/core/issue.py +54 -0
- holmes/core/llm.py +250 -0
- holmes/core/models.py +157 -0
- holmes/core/openai_formatting.py +51 -0
- holmes/core/performance_timing.py +72 -0
- holmes/core/prompt.py +42 -0
- holmes/core/resource_instruction.py +17 -0
- holmes/core/runbooks.py +26 -0
- holmes/core/safeguards.py +120 -0
- holmes/core/supabase_dal.py +540 -0
- holmes/core/tool_calling_llm.py +798 -0
- holmes/core/tools.py +566 -0
- holmes/core/tools_utils/__init__.py +0 -0
- holmes/core/tools_utils/tool_executor.py +65 -0
- holmes/core/tools_utils/toolset_utils.py +52 -0
- holmes/core/toolset_manager.py +418 -0
- holmes/interactive.py +229 -0
- holmes/main.py +1041 -0
- holmes/plugins/__init__.py +0 -0
- holmes/plugins/destinations/__init__.py +6 -0
- holmes/plugins/destinations/slack/__init__.py +2 -0
- holmes/plugins/destinations/slack/plugin.py +163 -0
- holmes/plugins/interfaces.py +32 -0
- holmes/plugins/prompts/__init__.py +48 -0
- holmes/plugins/prompts/_current_date_time.jinja2 +1 -0
- holmes/plugins/prompts/_default_log_prompt.jinja2 +11 -0
- holmes/plugins/prompts/_fetch_logs.jinja2 +36 -0
- holmes/plugins/prompts/_general_instructions.jinja2 +86 -0
- holmes/plugins/prompts/_global_instructions.jinja2 +12 -0
- holmes/plugins/prompts/_runbook_instructions.jinja2 +13 -0
- holmes/plugins/prompts/_toolsets_instructions.jinja2 +56 -0
- holmes/plugins/prompts/generic_ask.jinja2 +36 -0
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +32 -0
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +50 -0
- holmes/plugins/prompts/generic_investigation.jinja2 +42 -0
- holmes/plugins/prompts/generic_post_processing.jinja2 +13 -0
- holmes/plugins/prompts/generic_ticket.jinja2 +12 -0
- holmes/plugins/prompts/investigation_output_format.jinja2 +32 -0
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +84 -0
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +39 -0
- holmes/plugins/runbooks/README.md +22 -0
- holmes/plugins/runbooks/__init__.py +100 -0
- holmes/plugins/runbooks/catalog.json +14 -0
- holmes/plugins/runbooks/jira.yaml +12 -0
- holmes/plugins/runbooks/kube-prometheus-stack.yaml +10 -0
- holmes/plugins/runbooks/networking/dns_troubleshooting_instructions.md +66 -0
- holmes/plugins/runbooks/upgrade/upgrade_troubleshooting_instructions.md +44 -0
- holmes/plugins/sources/github/__init__.py +77 -0
- holmes/plugins/sources/jira/__init__.py +123 -0
- holmes/plugins/sources/opsgenie/__init__.py +93 -0
- holmes/plugins/sources/pagerduty/__init__.py +147 -0
- holmes/plugins/sources/prometheus/__init__.py +0 -0
- holmes/plugins/sources/prometheus/models.py +104 -0
- holmes/plugins/sources/prometheus/plugin.py +154 -0
- holmes/plugins/toolsets/__init__.py +171 -0
- holmes/plugins/toolsets/aks-node-health.yaml +65 -0
- holmes/plugins/toolsets/aks.yaml +86 -0
- holmes/plugins/toolsets/argocd.yaml +70 -0
- holmes/plugins/toolsets/atlas_mongodb/instructions.jinja2 +8 -0
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +307 -0
- holmes/plugins/toolsets/aws.yaml +76 -0
- holmes/plugins/toolsets/azure_sql/__init__.py +0 -0
- holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +600 -0
- holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +309 -0
- holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +445 -0
- holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +251 -0
- holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +317 -0
- holmes/plugins/toolsets/azure_sql/azure_base_toolset.py +55 -0
- holmes/plugins/toolsets/azure_sql/azure_sql_instructions.jinja2 +137 -0
- holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +183 -0
- holmes/plugins/toolsets/azure_sql/install.md +66 -0
- holmes/plugins/toolsets/azure_sql/tools/__init__.py +1 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +324 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +243 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +205 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +249 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +373 -0
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +237 -0
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +172 -0
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +170 -0
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +188 -0
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +180 -0
- holmes/plugins/toolsets/azure_sql/utils.py +83 -0
- holmes/plugins/toolsets/bash/__init__.py +0 -0
- holmes/plugins/toolsets/bash/bash_instructions.jinja2 +14 -0
- holmes/plugins/toolsets/bash/bash_toolset.py +208 -0
- holmes/plugins/toolsets/bash/common/bash.py +52 -0
- holmes/plugins/toolsets/bash/common/config.py +14 -0
- holmes/plugins/toolsets/bash/common/stringify.py +25 -0
- holmes/plugins/toolsets/bash/common/validators.py +24 -0
- holmes/plugins/toolsets/bash/grep/__init__.py +52 -0
- holmes/plugins/toolsets/bash/kubectl/__init__.py +100 -0
- holmes/plugins/toolsets/bash/kubectl/constants.py +96 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_describe.py +66 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_events.py +88 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +108 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_logs.py +20 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_run.py +46 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_top.py +81 -0
- holmes/plugins/toolsets/bash/parse_command.py +103 -0
- holmes/plugins/toolsets/confluence.yaml +19 -0
- holmes/plugins/toolsets/consts.py +5 -0
- holmes/plugins/toolsets/coralogix/api.py +158 -0
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +103 -0
- holmes/plugins/toolsets/coralogix/utils.py +181 -0
- holmes/plugins/toolsets/datadog.py +153 -0
- holmes/plugins/toolsets/docker.yaml +46 -0
- holmes/plugins/toolsets/git.py +756 -0
- holmes/plugins/toolsets/grafana/__init__.py +0 -0
- holmes/plugins/toolsets/grafana/base_grafana_toolset.py +54 -0
- holmes/plugins/toolsets/grafana/common.py +68 -0
- holmes/plugins/toolsets/grafana/grafana_api.py +31 -0
- holmes/plugins/toolsets/grafana/loki_api.py +89 -0
- holmes/plugins/toolsets/grafana/tempo_api.py +124 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +102 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +102 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +10 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +299 -0
- holmes/plugins/toolsets/grafana/trace_parser.py +195 -0
- holmes/plugins/toolsets/helm.yaml +42 -0
- holmes/plugins/toolsets/internet/internet.py +275 -0
- holmes/plugins/toolsets/internet/notion.py +137 -0
- holmes/plugins/toolsets/kafka.py +638 -0
- holmes/plugins/toolsets/kubernetes.yaml +255 -0
- holmes/plugins/toolsets/kubernetes_logs.py +426 -0
- holmes/plugins/toolsets/kubernetes_logs.yaml +42 -0
- holmes/plugins/toolsets/logging_utils/__init__.py +0 -0
- holmes/plugins/toolsets/logging_utils/logging_api.py +217 -0
- holmes/plugins/toolsets/logging_utils/types.py +0 -0
- holmes/plugins/toolsets/mcp/toolset_mcp.py +135 -0
- holmes/plugins/toolsets/newrelic.py +222 -0
- holmes/plugins/toolsets/opensearch/__init__.py +0 -0
- holmes/plugins/toolsets/opensearch/opensearch.py +245 -0
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +151 -0
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +211 -0
- holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +12 -0
- holmes/plugins/toolsets/opensearch/opensearch_utils.py +166 -0
- holmes/plugins/toolsets/prometheus/prometheus.py +818 -0
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +38 -0
- holmes/plugins/toolsets/rabbitmq/api.py +398 -0
- holmes/plugins/toolsets/rabbitmq/rabbitmq_instructions.jinja2 +37 -0
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +222 -0
- holmes/plugins/toolsets/robusta/__init__.py +0 -0
- holmes/plugins/toolsets/robusta/robusta.py +235 -0
- holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +24 -0
- holmes/plugins/toolsets/runbook/__init__.py +0 -0
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +78 -0
- holmes/plugins/toolsets/service_discovery.py +92 -0
- holmes/plugins/toolsets/servicenow/install.md +37 -0
- holmes/plugins/toolsets/servicenow/instructions.jinja2 +3 -0
- holmes/plugins/toolsets/servicenow/servicenow.py +198 -0
- holmes/plugins/toolsets/slab.yaml +20 -0
- holmes/plugins/toolsets/utils.py +137 -0
- holmes/plugins/utils.py +14 -0
- holmes/utils/__init__.py +0 -0
- holmes/utils/cache.py +84 -0
- holmes/utils/cert_utils.py +40 -0
- holmes/utils/default_toolset_installation_guide.jinja2 +44 -0
- holmes/utils/definitions.py +13 -0
- holmes/utils/env.py +53 -0
- holmes/utils/file_utils.py +56 -0
- holmes/utils/global_instructions.py +20 -0
- holmes/utils/holmes_status.py +22 -0
- holmes/utils/holmes_sync_toolsets.py +80 -0
- holmes/utils/markdown_utils.py +55 -0
- holmes/utils/pydantic_utils.py +54 -0
- holmes/utils/robusta.py +10 -0
- holmes/utils/tags.py +97 -0
- holmesgpt-0.11.5.dist-info/LICENSE.txt +21 -0
- holmesgpt-0.11.5.dist-info/METADATA +400 -0
- holmesgpt-0.11.5.dist-info/RECORD +183 -0
- holmesgpt-0.11.5.dist-info/WHEEL +4 -0
- holmesgpt-0.11.5.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any, Optional, Tuple, Dict, List
|
|
5
|
+
|
|
6
|
+
from requests import RequestException, Timeout # type: ignore
|
|
7
|
+
from holmes.core.tools import (
|
|
8
|
+
Tool,
|
|
9
|
+
ToolParameter,
|
|
10
|
+
Toolset,
|
|
11
|
+
ToolsetTag,
|
|
12
|
+
CallablePrerequisite,
|
|
13
|
+
)
|
|
14
|
+
from markdownify import markdownify
|
|
15
|
+
from bs4 import BeautifulSoup
|
|
16
|
+
|
|
17
|
+
import requests # type: ignore
|
|
18
|
+
from holmes.core.tools import StructuredToolResult, ToolResultStatus
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# TODO: change and make it holmes
|
|
22
|
+
INTERNET_TOOLSET_USER_AGENT = os.environ.get(
|
|
23
|
+
"INTERNET_TOOLSET_USER_AGENT",
|
|
24
|
+
"Mozilla/5.0 (X11; Linux x86_64; rv:128.0; holmesgpt;) Gecko/20100101 Firefox/128.0",
|
|
25
|
+
)
|
|
26
|
+
INTERNET_TOOLSET_TIMEOUT_SECONDS = int(
|
|
27
|
+
os.environ.get("INTERNET_TOOLSET_TIMEOUT_SECONDS", "60")
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
SELECTORS_TO_REMOVE = [
|
|
31
|
+
"script",
|
|
32
|
+
"style",
|
|
33
|
+
"link",
|
|
34
|
+
"noscript",
|
|
35
|
+
"header",
|
|
36
|
+
"footer",
|
|
37
|
+
"nav",
|
|
38
|
+
"iframe",
|
|
39
|
+
"svg",
|
|
40
|
+
"img",
|
|
41
|
+
"button",
|
|
42
|
+
"menu",
|
|
43
|
+
"sidebar",
|
|
44
|
+
"aside",
|
|
45
|
+
".header",
|
|
46
|
+
".footer",
|
|
47
|
+
".navigation",
|
|
48
|
+
".nav",
|
|
49
|
+
".menu",
|
|
50
|
+
".sidebar",
|
|
51
|
+
".ad",
|
|
52
|
+
".advertisement",
|
|
53
|
+
".social",
|
|
54
|
+
".popup",
|
|
55
|
+
".modal",
|
|
56
|
+
".banner",
|
|
57
|
+
".cookie-notice",
|
|
58
|
+
".social-share",
|
|
59
|
+
".related-articles",
|
|
60
|
+
".recommended",
|
|
61
|
+
"#header",
|
|
62
|
+
"#footer",
|
|
63
|
+
"#navigation",
|
|
64
|
+
"#nav",
|
|
65
|
+
"#menu",
|
|
66
|
+
"#sidebar",
|
|
67
|
+
"#ad",
|
|
68
|
+
"#advertisement",
|
|
69
|
+
"#social",
|
|
70
|
+
"#popup",
|
|
71
|
+
"#modal",
|
|
72
|
+
"#banner",
|
|
73
|
+
"#cookie-notice",
|
|
74
|
+
"#social-share",
|
|
75
|
+
"#related-articles",
|
|
76
|
+
"#recommended",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def scrape(url: str, headers: Dict[str, str]) -> Tuple[Optional[str], Optional[str]]:
|
|
81
|
+
response = None
|
|
82
|
+
content = None
|
|
83
|
+
mime_type = None
|
|
84
|
+
if not headers:
|
|
85
|
+
headers = {}
|
|
86
|
+
headers["User-Agent"] = INTERNET_TOOLSET_USER_AGENT
|
|
87
|
+
try:
|
|
88
|
+
response = requests.get(
|
|
89
|
+
url,
|
|
90
|
+
headers=headers,
|
|
91
|
+
timeout=INTERNET_TOOLSET_TIMEOUT_SECONDS,
|
|
92
|
+
)
|
|
93
|
+
response.raise_for_status()
|
|
94
|
+
except Timeout:
|
|
95
|
+
error_message = f"Failed to load {url}. Timeout after {INTERNET_TOOLSET_TIMEOUT_SECONDS} seconds"
|
|
96
|
+
logging.error(
|
|
97
|
+
error_message,
|
|
98
|
+
exc_info=True,
|
|
99
|
+
)
|
|
100
|
+
return error_message, None
|
|
101
|
+
except RequestException as e:
|
|
102
|
+
error_message = f"Failed to load {url}: {str(e)}"
|
|
103
|
+
logging.warning(error_message, exc_info=True)
|
|
104
|
+
return error_message, None
|
|
105
|
+
|
|
106
|
+
if response:
|
|
107
|
+
content = response.text
|
|
108
|
+
try:
|
|
109
|
+
content_type = response.headers["content-type"]
|
|
110
|
+
if content_type:
|
|
111
|
+
mime_type = content_type.split(";")[0]
|
|
112
|
+
except Exception:
|
|
113
|
+
logging.info(
|
|
114
|
+
f"Failed to parse content type from headers {response.headers}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return (content, mime_type)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def cleanup(soup: BeautifulSoup):
|
|
121
|
+
"""Remove all elements that are irrelevant to the textual representation of a web page.
|
|
122
|
+
This includes images, extra data, even links as there is no intention to navigate from that page.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
for selector in SELECTORS_TO_REMOVE:
|
|
126
|
+
for element in soup.select(selector):
|
|
127
|
+
element.decompose()
|
|
128
|
+
|
|
129
|
+
for tag in soup.find_all(True):
|
|
130
|
+
for attr in list(tag.attrs): # type: ignore
|
|
131
|
+
if attr != "href":
|
|
132
|
+
tag.attrs.pop(attr, None) # type: ignore
|
|
133
|
+
|
|
134
|
+
return soup
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def html_to_markdown(page_source: str):
|
|
138
|
+
soup = BeautifulSoup(page_source, "html.parser")
|
|
139
|
+
soup = cleanup(soup)
|
|
140
|
+
page_source = str(soup)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
md = markdownify(page_source)
|
|
144
|
+
except OSError as e:
|
|
145
|
+
logging.error(
|
|
146
|
+
f"There was an error in converting the HTML to markdown. Falling back to returning the raw HTML. Error: {str(e)}"
|
|
147
|
+
)
|
|
148
|
+
return page_source
|
|
149
|
+
|
|
150
|
+
md = re.sub(r"</div>", " ", md)
|
|
151
|
+
md = re.sub(r"<div>", " ", md)
|
|
152
|
+
|
|
153
|
+
md = re.sub(r"\n\s*\n", "\n\n", md)
|
|
154
|
+
|
|
155
|
+
return md
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def looks_like_html(content):
|
|
159
|
+
"""
|
|
160
|
+
Check if the content looks like HTML.
|
|
161
|
+
"""
|
|
162
|
+
if isinstance(content, str):
|
|
163
|
+
# Check for common HTML tags
|
|
164
|
+
html_patterns = [r"<!DOCTYPE\s+html", r"<html", r"<head", r"<body"]
|
|
165
|
+
return any(
|
|
166
|
+
re.search(pattern, content, re.IGNORECASE) for pattern in html_patterns
|
|
167
|
+
)
|
|
168
|
+
return False
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class FetchWebpage(Tool):
|
|
172
|
+
toolset: "InternetToolset"
|
|
173
|
+
|
|
174
|
+
def __init__(self, toolset: "InternetToolset"):
|
|
175
|
+
super().__init__(
|
|
176
|
+
name="fetch_webpage",
|
|
177
|
+
description="Fetch a webpage. Use this to fetch runbooks if they are present before starting your investigation (if no other tool like confluence is more appropriate)",
|
|
178
|
+
parameters={
|
|
179
|
+
"url": ToolParameter(
|
|
180
|
+
description="The URL to fetch",
|
|
181
|
+
type="string",
|
|
182
|
+
required=True,
|
|
183
|
+
),
|
|
184
|
+
},
|
|
185
|
+
toolset=toolset, # type: ignore
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _invoke(self, params: Any) -> StructuredToolResult:
|
|
189
|
+
url: str = params["url"]
|
|
190
|
+
|
|
191
|
+
additional_headers = (
|
|
192
|
+
self.toolset.additional_headers if self.toolset.additional_headers else {}
|
|
193
|
+
)
|
|
194
|
+
content, mime_type = scrape(url, additional_headers)
|
|
195
|
+
|
|
196
|
+
if not content:
|
|
197
|
+
logging.error(f"Failed to retrieve content from {url}")
|
|
198
|
+
return StructuredToolResult(
|
|
199
|
+
status=ToolResultStatus.ERROR,
|
|
200
|
+
error=f"Failed to retrieve content from {url}",
|
|
201
|
+
params=params,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Check if the content is HTML based on MIME type or content
|
|
205
|
+
if (mime_type and mime_type.startswith("text/html")) or (
|
|
206
|
+
mime_type is None and looks_like_html(content)
|
|
207
|
+
):
|
|
208
|
+
content = html_to_markdown(content)
|
|
209
|
+
|
|
210
|
+
return StructuredToolResult(
|
|
211
|
+
status=ToolResultStatus.SUCCESS,
|
|
212
|
+
data=content,
|
|
213
|
+
params=params,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def get_parameterized_one_liner(self, params) -> str:
|
|
217
|
+
url: str = params["url"]
|
|
218
|
+
return f"fetched webpage {url}"
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class InternetBaseToolset(Toolset):
|
|
222
|
+
additional_headers: Dict[str, str] = {}
|
|
223
|
+
|
|
224
|
+
def __init__(
|
|
225
|
+
self,
|
|
226
|
+
name: str,
|
|
227
|
+
description: str,
|
|
228
|
+
icon_url: str,
|
|
229
|
+
tools: list[Tool],
|
|
230
|
+
is_default: bool,
|
|
231
|
+
tags: List[ToolsetTag],
|
|
232
|
+
docs_url: Optional[str] = None,
|
|
233
|
+
):
|
|
234
|
+
super().__init__(
|
|
235
|
+
name=name,
|
|
236
|
+
description=description,
|
|
237
|
+
icon_url=icon_url,
|
|
238
|
+
prerequisites=[
|
|
239
|
+
CallablePrerequisite(callable=self.prerequisites_callable),
|
|
240
|
+
],
|
|
241
|
+
tools=tools,
|
|
242
|
+
tags=tags,
|
|
243
|
+
is_default=is_default,
|
|
244
|
+
docs_url=docs_url,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
def prerequisites_callable(self, config: Dict[str, Any]) -> Tuple[bool, str]:
|
|
248
|
+
if not config:
|
|
249
|
+
return True, ""
|
|
250
|
+
self.additional_headers = config.get("additional_headers", {})
|
|
251
|
+
return True, ""
|
|
252
|
+
|
|
253
|
+
def get_example_config(self) -> Dict[str, Any]:
|
|
254
|
+
return {
|
|
255
|
+
"additional_headers": {"Authorization": "Basic <base_64_encoded_string>"}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class InternetToolset(InternetBaseToolset):
|
|
260
|
+
additional_headers: Dict[str, str] = {}
|
|
261
|
+
|
|
262
|
+
def __init__(self):
|
|
263
|
+
super().__init__(
|
|
264
|
+
name="internet",
|
|
265
|
+
description="Fetch webpages",
|
|
266
|
+
icon_url="https://platform.robusta.dev/demos/internet-access.svg",
|
|
267
|
+
tools=[
|
|
268
|
+
FetchWebpage(self),
|
|
269
|
+
],
|
|
270
|
+
docs_url="https://docs.robusta.dev/master/configuration/holmesgpt/toolsets/internet.html",
|
|
271
|
+
tags=[
|
|
272
|
+
ToolsetTag.CORE,
|
|
273
|
+
],
|
|
274
|
+
is_default=True,
|
|
275
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import logging
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any, Dict, Tuple
|
|
5
|
+
from holmes.core.tools import (
|
|
6
|
+
Tool,
|
|
7
|
+
ToolParameter,
|
|
8
|
+
ToolsetTag,
|
|
9
|
+
)
|
|
10
|
+
from holmes.plugins.toolsets.internet.internet import (
|
|
11
|
+
InternetBaseToolset,
|
|
12
|
+
scrape,
|
|
13
|
+
)
|
|
14
|
+
from holmes.core.tools import (
|
|
15
|
+
StructuredToolResult,
|
|
16
|
+
ToolResultStatus,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FetchNotion(Tool):
|
|
21
|
+
toolset: "InternetBaseToolset"
|
|
22
|
+
|
|
23
|
+
def __init__(self, toolset: "InternetBaseToolset"):
|
|
24
|
+
super().__init__(
|
|
25
|
+
name="fetch_notion_webpage",
|
|
26
|
+
description="Fetch a Notion webpage with HTTP requests and authentication.",
|
|
27
|
+
parameters={
|
|
28
|
+
"url": ToolParameter(
|
|
29
|
+
description="The URL to fetch",
|
|
30
|
+
type="string",
|
|
31
|
+
required=True,
|
|
32
|
+
),
|
|
33
|
+
},
|
|
34
|
+
toolset=toolset, # type: ignore
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def convert_notion_url(self, url):
|
|
38
|
+
if "api.notion.com" in url:
|
|
39
|
+
return url
|
|
40
|
+
match = re.search(r"-(\w{32})$", url)
|
|
41
|
+
if match:
|
|
42
|
+
notion_id = match.group(1)
|
|
43
|
+
return f"https://api.notion.com/v1/blocks/{notion_id}/children"
|
|
44
|
+
return url # Return original URL if no match is found
|
|
45
|
+
|
|
46
|
+
def _invoke(self, params: Any) -> StructuredToolResult:
|
|
47
|
+
url: str = params["url"]
|
|
48
|
+
|
|
49
|
+
# Get headers from the toolset configuration
|
|
50
|
+
additional_headers = (
|
|
51
|
+
self.toolset.additional_headers if self.toolset.additional_headers else {}
|
|
52
|
+
)
|
|
53
|
+
url = self.convert_notion_url(url)
|
|
54
|
+
content, _ = scrape(url, additional_headers)
|
|
55
|
+
|
|
56
|
+
if not content:
|
|
57
|
+
logging.error(f"Failed to retrieve content from {url}")
|
|
58
|
+
return StructuredToolResult(
|
|
59
|
+
status=ToolResultStatus.ERROR,
|
|
60
|
+
error=f"Failed to retrieve content from {url}",
|
|
61
|
+
params=params,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return StructuredToolResult(
|
|
65
|
+
status=ToolResultStatus.SUCCESS,
|
|
66
|
+
data=self.parse_notion_content(content),
|
|
67
|
+
params=params,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def parse_notion_content(self, content: Any) -> str:
|
|
71
|
+
data = json.loads(content)
|
|
72
|
+
texts = []
|
|
73
|
+
|
|
74
|
+
for result in data.get("results", []):
|
|
75
|
+
# Handle paragraph blocks
|
|
76
|
+
if result.get("type") == "paragraph":
|
|
77
|
+
rich_texts = result["paragraph"].get("rich_text", [])
|
|
78
|
+
formatted_text = self.format_rich_text(rich_texts)
|
|
79
|
+
if formatted_text:
|
|
80
|
+
texts.append(formatted_text)
|
|
81
|
+
|
|
82
|
+
# Handle bulleted list items
|
|
83
|
+
elif result.get("type") == "bulleted_list_item":
|
|
84
|
+
rich_texts = result["bulleted_list_item"].get("rich_text", [])
|
|
85
|
+
formatted_text = self.format_rich_text(rich_texts)
|
|
86
|
+
if formatted_text:
|
|
87
|
+
texts.append(f"- {formatted_text}")
|
|
88
|
+
|
|
89
|
+
# Join and return the formatted text
|
|
90
|
+
return "\n\n".join(texts)
|
|
91
|
+
|
|
92
|
+
def format_rich_text(self, rich_texts: list) -> str:
|
|
93
|
+
"""Helper function to apply formatting (bold, code, etc.)"""
|
|
94
|
+
formatted_text = []
|
|
95
|
+
for text in rich_texts:
|
|
96
|
+
plain_text = text["text"]["content"]
|
|
97
|
+
annotations = text.get("annotations", {})
|
|
98
|
+
|
|
99
|
+
# Apply formatting
|
|
100
|
+
if annotations.get("bold"):
|
|
101
|
+
plain_text = f"**{plain_text}**"
|
|
102
|
+
if annotations.get("code"):
|
|
103
|
+
plain_text = f"`{plain_text}`"
|
|
104
|
+
|
|
105
|
+
formatted_text.append(plain_text)
|
|
106
|
+
|
|
107
|
+
return "".join(formatted_text)
|
|
108
|
+
|
|
109
|
+
def get_parameterized_one_liner(self, params) -> str:
|
|
110
|
+
url: str = params["url"]
|
|
111
|
+
return f"fetched notion webpage {url}"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class NotionToolset(InternetBaseToolset):
|
|
115
|
+
def __init__(self):
|
|
116
|
+
super().__init__(
|
|
117
|
+
name="notion",
|
|
118
|
+
description="Fetch notion webpages",
|
|
119
|
+
icon_url="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Notion-logo.svg/2048px-Notion-logo.svg.png",
|
|
120
|
+
docs_url="https://docs.robusta.dev/master/configuration/holmesgpt/toolsets/notion.html",
|
|
121
|
+
tools=[
|
|
122
|
+
FetchNotion(self),
|
|
123
|
+
],
|
|
124
|
+
tags=[
|
|
125
|
+
ToolsetTag.CORE,
|
|
126
|
+
],
|
|
127
|
+
is_default=False,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def prerequisites_callable(self, config: Dict[str, Any]) -> Tuple[bool, str]:
|
|
131
|
+
if not config or not config.get("additional_headers", {}):
|
|
132
|
+
return (
|
|
133
|
+
False,
|
|
134
|
+
"Notion toolset is misconfigured. Authorization header is required.",
|
|
135
|
+
)
|
|
136
|
+
self.additional_headers = config["additional_headers"]
|
|
137
|
+
return True, ""
|