holmesgpt 0.13.2__py3-none-any.whl → 0.18.4__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.
- holmes/__init__.py +3 -5
- holmes/clients/robusta_client.py +20 -6
- holmes/common/env_vars.py +58 -3
- holmes/common/openshift.py +1 -1
- holmes/config.py +123 -148
- holmes/core/conversations.py +71 -15
- holmes/core/feedback.py +191 -0
- holmes/core/investigation.py +31 -39
- holmes/core/investigation_structured_output.py +3 -3
- holmes/core/issue.py +1 -1
- holmes/core/llm.py +508 -88
- holmes/core/models.py +108 -4
- holmes/core/openai_formatting.py +14 -1
- holmes/core/prompt.py +48 -3
- holmes/core/runbooks.py +1 -0
- holmes/core/safeguards.py +8 -6
- holmes/core/supabase_dal.py +295 -100
- holmes/core/tool_calling_llm.py +489 -428
- holmes/core/tools.py +325 -56
- holmes/core/tools_utils/token_counting.py +21 -0
- holmes/core/tools_utils/tool_context_window_limiter.py +40 -0
- holmes/core/tools_utils/tool_executor.py +0 -13
- holmes/core/tools_utils/toolset_utils.py +1 -0
- holmes/core/toolset_manager.py +191 -5
- holmes/core/tracing.py +19 -3
- holmes/core/transformers/__init__.py +23 -0
- holmes/core/transformers/base.py +63 -0
- holmes/core/transformers/llm_summarize.py +175 -0
- holmes/core/transformers/registry.py +123 -0
- holmes/core/transformers/transformer.py +32 -0
- holmes/core/truncation/compaction.py +94 -0
- holmes/core/truncation/dal_truncation_utils.py +23 -0
- holmes/core/truncation/input_context_window_limiter.py +219 -0
- holmes/interactive.py +228 -31
- holmes/main.py +23 -40
- holmes/plugins/interfaces.py +2 -1
- holmes/plugins/prompts/__init__.py +2 -1
- holmes/plugins/prompts/_fetch_logs.jinja2 +31 -6
- holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
- holmes/plugins/prompts/_runbook_instructions.jinja2 +24 -12
- holmes/plugins/prompts/base_user_prompt.jinja2 +7 -0
- holmes/plugins/prompts/conversation_history_compaction.jinja2 +89 -0
- holmes/plugins/prompts/generic_ask.jinja2 +0 -4
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +0 -1
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +0 -1
- holmes/plugins/prompts/generic_investigation.jinja2 +0 -1
- holmes/plugins/prompts/investigation_procedure.jinja2 +50 -1
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +0 -1
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +0 -1
- holmes/plugins/runbooks/__init__.py +145 -17
- holmes/plugins/runbooks/catalog.json +2 -0
- holmes/plugins/sources/github/__init__.py +4 -2
- holmes/plugins/sources/prometheus/models.py +1 -0
- holmes/plugins/toolsets/__init__.py +44 -27
- holmes/plugins/toolsets/aks-node-health.yaml +46 -0
- holmes/plugins/toolsets/aks.yaml +64 -0
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +38 -47
- holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +3 -2
- holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +2 -1
- holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +3 -2
- holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +3 -1
- holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +3 -1
- holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +12 -13
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +11 -11
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +11 -9
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +15 -15
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/utils.py +0 -32
- holmes/plugins/toolsets/bash/argocd/__init__.py +3 -3
- holmes/plugins/toolsets/bash/aws/__init__.py +4 -4
- holmes/plugins/toolsets/bash/azure/__init__.py +4 -4
- holmes/plugins/toolsets/bash/bash_toolset.py +11 -15
- holmes/plugins/toolsets/bash/common/bash.py +23 -13
- holmes/plugins/toolsets/bash/common/bash_command.py +1 -1
- holmes/plugins/toolsets/bash/common/stringify.py +1 -1
- holmes/plugins/toolsets/bash/kubectl/__init__.py +2 -1
- holmes/plugins/toolsets/bash/kubectl/constants.py +0 -1
- holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +3 -4
- holmes/plugins/toolsets/bash/parse_command.py +12 -13
- holmes/plugins/toolsets/cilium.yaml +284 -0
- holmes/plugins/toolsets/connectivity_check.py +124 -0
- holmes/plugins/toolsets/coralogix/api.py +132 -119
- holmes/plugins/toolsets/coralogix/coralogix.jinja2 +14 -0
- holmes/plugins/toolsets/coralogix/toolset_coralogix.py +219 -0
- holmes/plugins/toolsets/coralogix/utils.py +15 -79
- holmes/plugins/toolsets/datadog/datadog_api.py +525 -26
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +55 -11
- holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +3 -3
- holmes/plugins/toolsets/datadog/datadog_models.py +59 -0
- holmes/plugins/toolsets/datadog/datadog_url_utils.py +213 -0
- holmes/plugins/toolsets/datadog/instructions_datadog_traces.jinja2 +165 -28
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +417 -241
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +234 -214
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +167 -79
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +374 -363
- holmes/plugins/toolsets/elasticsearch/__init__.py +6 -0
- holmes/plugins/toolsets/elasticsearch/elasticsearch.py +834 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_ppl_query_docs.jinja2 +1616 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_query_assist.py +78 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_query_assist_instructions.jinja2 +223 -0
- holmes/plugins/toolsets/git.py +54 -50
- holmes/plugins/toolsets/grafana/base_grafana_toolset.py +16 -4
- holmes/plugins/toolsets/grafana/common.py +13 -29
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +455 -0
- holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +25 -0
- holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +191 -0
- holmes/plugins/toolsets/grafana/loki_api.py +4 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +293 -89
- holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +49 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +820 -292
- holmes/plugins/toolsets/grafana/trace_parser.py +4 -3
- holmes/plugins/toolsets/internet/internet.py +15 -16
- holmes/plugins/toolsets/internet/notion.py +9 -11
- holmes/plugins/toolsets/investigator/core_investigation.py +44 -36
- holmes/plugins/toolsets/investigator/model.py +3 -1
- holmes/plugins/toolsets/json_filter_mixin.py +134 -0
- holmes/plugins/toolsets/kafka.py +36 -42
- holmes/plugins/toolsets/kubernetes.yaml +317 -113
- holmes/plugins/toolsets/kubernetes_logs.py +9 -9
- holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
- holmes/plugins/toolsets/logging_utils/logging_api.py +94 -8
- holmes/plugins/toolsets/mcp/toolset_mcp.py +218 -64
- holmes/plugins/toolsets/newrelic/new_relic_api.py +165 -0
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +65 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +320 -0
- holmes/plugins/toolsets/openshift.yaml +283 -0
- holmes/plugins/toolsets/prometheus/prometheus.py +1202 -421
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +54 -5
- holmes/plugins/toolsets/prometheus/utils.py +28 -0
- holmes/plugins/toolsets/rabbitmq/api.py +23 -4
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +13 -14
- holmes/plugins/toolsets/robusta/robusta.py +239 -68
- holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +157 -27
- holmes/plugins/toolsets/service_discovery.py +1 -1
- holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
- holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
- holmes/plugins/toolsets/utils.py +88 -0
- holmes/utils/config_utils.py +91 -0
- holmes/utils/connection_utils.py +31 -0
- holmes/utils/console/result.py +10 -0
- holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
- holmes/utils/env.py +7 -0
- holmes/utils/file_utils.py +2 -1
- holmes/utils/global_instructions.py +60 -11
- holmes/utils/holmes_status.py +6 -4
- holmes/utils/holmes_sync_toolsets.py +0 -2
- holmes/utils/krr_utils.py +188 -0
- holmes/utils/log.py +15 -0
- holmes/utils/markdown_utils.py +2 -3
- holmes/utils/memory_limit.py +58 -0
- holmes/utils/sentry_helper.py +64 -0
- holmes/utils/stream.py +69 -8
- holmes/utils/tags.py +4 -3
- holmes/version.py +37 -15
- holmesgpt-0.18.4.dist-info/LICENSE +178 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/METADATA +35 -31
- holmesgpt-0.18.4.dist-info/RECORD +258 -0
- holmes/core/performance_timing.py +0 -72
- holmes/plugins/toolsets/aws.yaml +0 -80
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +0 -112
- holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +0 -310
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +0 -739
- holmes/plugins/toolsets/grafana/grafana_api.py +0 -42
- holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
- holmes/plugins/toolsets/newrelic.py +0 -231
- holmes/plugins/toolsets/opensearch/opensearch.py +0 -257
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +0 -161
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +0 -218
- holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +0 -12
- holmes/plugins/toolsets/opensearch/opensearch_utils.py +0 -166
- holmes/plugins/toolsets/servicenow/install.md +0 -37
- holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
- holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
- holmes/utils/keygen_utils.py +0 -6
- holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
- holmesgpt-0.13.2.dist-info/RECORD +0 -234
- /holmes/plugins/toolsets/{opensearch → newrelic}/__init__.py +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/WHEEL +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/entry_points.txt +0 -0
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Any, Dict, List, Optional, Tuple
|
|
3
|
-
|
|
4
|
-
from opensearchpy import OpenSearch
|
|
5
|
-
from pydantic import BaseModel, ConfigDict
|
|
6
|
-
|
|
7
|
-
from holmes.core.tools import (
|
|
8
|
-
CallablePrerequisite,
|
|
9
|
-
StructuredToolResult,
|
|
10
|
-
Tool,
|
|
11
|
-
ToolParameter,
|
|
12
|
-
ToolResultStatus,
|
|
13
|
-
Toolset,
|
|
14
|
-
ToolsetTag,
|
|
15
|
-
)
|
|
16
|
-
from holmes.plugins.toolsets.consts import TOOLSET_CONFIG_MISSING_ERROR
|
|
17
|
-
from holmes.plugins.toolsets.utils import toolset_name_for_one_liner
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class OpenSearchHttpAuth(BaseModel):
|
|
21
|
-
username: str
|
|
22
|
-
password: str
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class OpenSearchHost(BaseModel):
|
|
26
|
-
host: str
|
|
27
|
-
port: int = 9200
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class OpenSearchCluster(BaseModel):
|
|
31
|
-
hosts: list[OpenSearchHost]
|
|
32
|
-
headers: Optional[dict[str, Any]] = None
|
|
33
|
-
use_ssl: bool = True
|
|
34
|
-
ssl_assert_hostname: bool = False
|
|
35
|
-
verify_certs: bool = False
|
|
36
|
-
ssl_show_warn: bool = False
|
|
37
|
-
http_auth: Optional[OpenSearchHttpAuth] = None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class OpenSearchConfig(BaseModel):
|
|
41
|
-
opensearch_clusters: list[OpenSearchCluster]
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class OpenSearchClient:
|
|
45
|
-
def __init__(self, **kwargs):
|
|
46
|
-
# Handle http_auth explicitly
|
|
47
|
-
if "http_auth" in kwargs:
|
|
48
|
-
http_auth = kwargs.pop("http_auth")
|
|
49
|
-
if isinstance(http_auth, dict):
|
|
50
|
-
kwargs["http_auth"] = (
|
|
51
|
-
http_auth.get("username"),
|
|
52
|
-
http_auth.get("password"),
|
|
53
|
-
)
|
|
54
|
-
# Initialize OpenSearch client
|
|
55
|
-
self.hosts = [host.get("host") for host in kwargs.get("hosts", [])]
|
|
56
|
-
self.client = OpenSearch(**kwargs)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def get_client(clients: List[OpenSearchClient], host: Optional[str]):
|
|
60
|
-
if len(clients) == 1:
|
|
61
|
-
return clients[0]
|
|
62
|
-
|
|
63
|
-
if not host:
|
|
64
|
-
raise Exception("Missing host to resolve opensearch client")
|
|
65
|
-
|
|
66
|
-
for client in clients:
|
|
67
|
-
found = any(host in client.hosts for client in clients)
|
|
68
|
-
if found:
|
|
69
|
-
return client
|
|
70
|
-
|
|
71
|
-
raise Exception(
|
|
72
|
-
f"Failed to resolve opensearch client. Could not find a matching host: {host}"
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
class BaseOpenSearchTool(Tool):
|
|
77
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
78
|
-
toolset: "OpenSearchToolset"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
class ListShards(BaseOpenSearchTool):
|
|
82
|
-
def __init__(self, toolset: "OpenSearchToolset"):
|
|
83
|
-
super().__init__(
|
|
84
|
-
name="opensearch_list_shards",
|
|
85
|
-
description="List the shards within an opensearch cluster",
|
|
86
|
-
parameters={
|
|
87
|
-
"host": ToolParameter(
|
|
88
|
-
description="The cluster host",
|
|
89
|
-
type="string",
|
|
90
|
-
required=True,
|
|
91
|
-
)
|
|
92
|
-
},
|
|
93
|
-
toolset=toolset,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
def _invoke(
|
|
97
|
-
self, params: dict, user_approved: bool = False
|
|
98
|
-
) -> StructuredToolResult:
|
|
99
|
-
client = get_client(self.toolset.clients, host=params.get("host", ""))
|
|
100
|
-
shards = client.client.cat.shards()
|
|
101
|
-
return StructuredToolResult(
|
|
102
|
-
status=ToolResultStatus.SUCCESS,
|
|
103
|
-
data=str(shards),
|
|
104
|
-
params=params,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
108
|
-
host = params.get("host", "")
|
|
109
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: List Shards ({host})"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
class GetClusterSettings(BaseOpenSearchTool):
|
|
113
|
-
def __init__(self, toolset: "OpenSearchToolset"):
|
|
114
|
-
super().__init__(
|
|
115
|
-
name="opensearch_get_cluster_settings",
|
|
116
|
-
description="Retrieve the cluster's settings",
|
|
117
|
-
parameters={
|
|
118
|
-
"host": ToolParameter(
|
|
119
|
-
description="The cluster host",
|
|
120
|
-
type="string",
|
|
121
|
-
required=True,
|
|
122
|
-
)
|
|
123
|
-
},
|
|
124
|
-
toolset=toolset,
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
def _invoke(
|
|
128
|
-
self, params: dict, user_approved: bool = False
|
|
129
|
-
) -> StructuredToolResult:
|
|
130
|
-
client = get_client(self.toolset.clients, host=params.get("host"))
|
|
131
|
-
response = client.client.cluster.get_settings(
|
|
132
|
-
include_defaults=True, flat_settings=True
|
|
133
|
-
)
|
|
134
|
-
return StructuredToolResult(
|
|
135
|
-
status=ToolResultStatus.SUCCESS,
|
|
136
|
-
data=str(response),
|
|
137
|
-
params=params,
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
def get_parameterized_one_liner(self, params) -> str:
|
|
141
|
-
host = params.get("host", "")
|
|
142
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Cluster Settings ({host})"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
class GetClusterHealth(BaseOpenSearchTool):
|
|
146
|
-
def __init__(self, toolset: "OpenSearchToolset"):
|
|
147
|
-
super().__init__(
|
|
148
|
-
name="opensearch_get_cluster_health",
|
|
149
|
-
description="Retrieve the cluster's health",
|
|
150
|
-
parameters={
|
|
151
|
-
"host": ToolParameter(
|
|
152
|
-
description="The cluster host",
|
|
153
|
-
type="string",
|
|
154
|
-
required=True,
|
|
155
|
-
)
|
|
156
|
-
},
|
|
157
|
-
toolset=toolset,
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
def _invoke(
|
|
161
|
-
self, params: dict, user_approved: bool = False
|
|
162
|
-
) -> StructuredToolResult:
|
|
163
|
-
client = get_client(self.toolset.clients, host=params.get("host", ""))
|
|
164
|
-
health = client.client.cluster.health()
|
|
165
|
-
return StructuredToolResult(
|
|
166
|
-
status=ToolResultStatus.SUCCESS,
|
|
167
|
-
data=str(health),
|
|
168
|
-
params=params,
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
def get_parameterized_one_liner(self, params) -> str:
|
|
172
|
-
host = params.get("host", "")
|
|
173
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: Check Cluster Health ({host})"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
class ListOpenSearchHosts(BaseOpenSearchTool):
|
|
177
|
-
def __init__(self, toolset: "OpenSearchToolset"):
|
|
178
|
-
super().__init__(
|
|
179
|
-
name="opensearch_list_hosts",
|
|
180
|
-
description="List all OpenSearch hosts in the cluster",
|
|
181
|
-
parameters={},
|
|
182
|
-
toolset=toolset,
|
|
183
|
-
)
|
|
184
|
-
|
|
185
|
-
def _invoke(
|
|
186
|
-
self, params: dict, user_approved: bool = False
|
|
187
|
-
) -> StructuredToolResult:
|
|
188
|
-
hosts = [host for client in self.toolset.clients for host in client.hosts]
|
|
189
|
-
return StructuredToolResult(
|
|
190
|
-
status=ToolResultStatus.SUCCESS,
|
|
191
|
-
data=str(hosts),
|
|
192
|
-
params=params,
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
def get_parameterized_one_liner(self, params: Dict) -> str:
|
|
196
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: List OpenSearch Hosts"
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
class OpenSearchToolset(Toolset):
|
|
200
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
201
|
-
clients: List[OpenSearchClient] = []
|
|
202
|
-
|
|
203
|
-
def __init__(self):
|
|
204
|
-
super().__init__(
|
|
205
|
-
name="opensearch/status",
|
|
206
|
-
enabled=False,
|
|
207
|
-
description="Provide cluster metadata information like health, shards, settings.",
|
|
208
|
-
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/opensearch-status/",
|
|
209
|
-
icon_url="https://opensearch.org/assets/brand/PNG/Mark/opensearch_mark_default.png",
|
|
210
|
-
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
211
|
-
tools=[
|
|
212
|
-
ListShards(self),
|
|
213
|
-
GetClusterSettings(self),
|
|
214
|
-
GetClusterHealth(self),
|
|
215
|
-
ListOpenSearchHosts(self),
|
|
216
|
-
],
|
|
217
|
-
tags=[
|
|
218
|
-
ToolsetTag.CORE,
|
|
219
|
-
],
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
def prerequisites_callable(self, config: dict[str, Any]) -> Tuple[bool, str]:
|
|
223
|
-
if not config:
|
|
224
|
-
return False, TOOLSET_CONFIG_MISSING_ERROR
|
|
225
|
-
|
|
226
|
-
try:
|
|
227
|
-
os_config = OpenSearchConfig(**config)
|
|
228
|
-
errors = []
|
|
229
|
-
for cluster in os_config.opensearch_clusters:
|
|
230
|
-
try:
|
|
231
|
-
logging.info("Setting up OpenSearch client")
|
|
232
|
-
cluster_kwargs = cluster.model_dump()
|
|
233
|
-
client = OpenSearchClient(**cluster_kwargs)
|
|
234
|
-
if client.client.cluster.health(params={"timeout": 5}):
|
|
235
|
-
self.clients.append(client)
|
|
236
|
-
except Exception as e:
|
|
237
|
-
message = f"Failed to set up opensearch client {str(cluster.hosts)}. {str(e)}"
|
|
238
|
-
logging.exception(message)
|
|
239
|
-
errors.append(message)
|
|
240
|
-
|
|
241
|
-
return len(self.clients) > 0, "\n".join(errors)
|
|
242
|
-
except Exception as e:
|
|
243
|
-
logging.exception("Failed to set up OpenSearch toolset")
|
|
244
|
-
return False, str(e)
|
|
245
|
-
|
|
246
|
-
def get_example_config(self) -> Dict[str, Any]:
|
|
247
|
-
example_config = OpenSearchConfig(
|
|
248
|
-
opensearch_clusters=[
|
|
249
|
-
OpenSearchCluster(
|
|
250
|
-
hosts=[OpenSearchHost(host="YOUR OPENSEACH HOST")],
|
|
251
|
-
headers={"Authorization": "{{ env.OPENSEARCH_BEARER_TOKEN }}"},
|
|
252
|
-
use_ssl=True,
|
|
253
|
-
ssl_assert_hostname=False,
|
|
254
|
-
)
|
|
255
|
-
]
|
|
256
|
-
)
|
|
257
|
-
return example_config.model_dump()
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Any, Dict, Optional, Tuple, Set
|
|
3
|
-
|
|
4
|
-
import requests # type: ignore
|
|
5
|
-
from requests import RequestException # type: ignore
|
|
6
|
-
from urllib.parse import urljoin
|
|
7
|
-
|
|
8
|
-
from holmes.core.tools import (
|
|
9
|
-
CallablePrerequisite,
|
|
10
|
-
StructuredToolResult,
|
|
11
|
-
ToolResultStatus,
|
|
12
|
-
ToolsetTag,
|
|
13
|
-
)
|
|
14
|
-
from holmes.plugins.toolsets.logging_utils.logging_api import (
|
|
15
|
-
BasePodLoggingToolset,
|
|
16
|
-
FetchPodLogsParams,
|
|
17
|
-
LoggingCapability,
|
|
18
|
-
PodLoggingTool,
|
|
19
|
-
process_time_parameters,
|
|
20
|
-
DEFAULT_TIME_SPAN_SECONDS,
|
|
21
|
-
)
|
|
22
|
-
from holmes.plugins.toolsets.opensearch.opensearch_utils import (
|
|
23
|
-
OpenSearchLoggingConfig,
|
|
24
|
-
add_auth_header,
|
|
25
|
-
build_query,
|
|
26
|
-
format_logs,
|
|
27
|
-
opensearch_health_check,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
LOGS_FIELDS_CACHE_KEY = "cached_logs_fields"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class OpenSearchLogsToolset(BasePodLoggingToolset):
|
|
34
|
-
"""Implementation of the unified logging API for OpenSearch logs"""
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def supported_capabilities(self) -> Set[LoggingCapability]:
|
|
38
|
-
"""OpenSearch only supports phrase matching, not regex or exclude filters"""
|
|
39
|
-
return set() # No regex support, no exclude filter
|
|
40
|
-
|
|
41
|
-
def __init__(self):
|
|
42
|
-
super().__init__(
|
|
43
|
-
name="opensearch/logs",
|
|
44
|
-
description="OpenSearch integration to fetch logs",
|
|
45
|
-
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/opensearch-logs/",
|
|
46
|
-
icon_url="https://opensearch.org/wp-content/uploads/2025/01/opensearch_mark_default.png",
|
|
47
|
-
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
48
|
-
tools=[], # Initialize with empty tools first
|
|
49
|
-
tags=[
|
|
50
|
-
ToolsetTag.CORE,
|
|
51
|
-
],
|
|
52
|
-
)
|
|
53
|
-
# Now that parent is initialized and self.name exists, create the tool
|
|
54
|
-
self.tools = [PodLoggingTool(self)]
|
|
55
|
-
|
|
56
|
-
def get_example_config(self) -> Dict[str, Any]:
|
|
57
|
-
example_config = OpenSearchLoggingConfig(
|
|
58
|
-
opensearch_url="YOUR OPENSEARCH LOGS URL",
|
|
59
|
-
index_pattern="YOUR OPENSEARCH LOGS INDEX NAME",
|
|
60
|
-
opensearch_auth_header="YOUR OPENSEARCH LOGS AUTH HEADER (Optional)",
|
|
61
|
-
)
|
|
62
|
-
return example_config.model_dump()
|
|
63
|
-
|
|
64
|
-
def prerequisites_callable(self, config: dict[str, Any]) -> Tuple[bool, str]:
|
|
65
|
-
if not config:
|
|
66
|
-
return False, "Missing OpenSearch configuration. Check your config."
|
|
67
|
-
|
|
68
|
-
self.config = OpenSearchLoggingConfig(**config)
|
|
69
|
-
|
|
70
|
-
return opensearch_health_check(self.config)
|
|
71
|
-
|
|
72
|
-
@property
|
|
73
|
-
def opensearch_config(self) -> Optional[OpenSearchLoggingConfig]:
|
|
74
|
-
return self.config
|
|
75
|
-
|
|
76
|
-
def logger_name(self) -> str:
|
|
77
|
-
return "OpenSearch"
|
|
78
|
-
|
|
79
|
-
def fetch_pod_logs(self, params: FetchPodLogsParams) -> StructuredToolResult:
|
|
80
|
-
if not self.opensearch_config:
|
|
81
|
-
return StructuredToolResult(
|
|
82
|
-
status=ToolResultStatus.ERROR,
|
|
83
|
-
error="Missing OpenSearch configuration",
|
|
84
|
-
params=params.model_dump(),
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
try:
|
|
88
|
-
start_time = None
|
|
89
|
-
end_time = None
|
|
90
|
-
if params.start_time or params.end_time:
|
|
91
|
-
start_time, end_time = process_time_parameters(
|
|
92
|
-
params.start_time, params.end_time, DEFAULT_TIME_SPAN_SECONDS
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
query = build_query(
|
|
96
|
-
config=self.opensearch_config,
|
|
97
|
-
namespace=params.namespace,
|
|
98
|
-
pod_name=params.pod_name,
|
|
99
|
-
start_time=start_time,
|
|
100
|
-
end_time=end_time,
|
|
101
|
-
match=params.filter,
|
|
102
|
-
limit=params.limit,
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
headers = {"Content-Type": "application/json"}
|
|
106
|
-
headers.update(
|
|
107
|
-
add_auth_header(self.opensearch_config.opensearch_auth_header)
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
url = urljoin(
|
|
111
|
-
self.opensearch_config.opensearch_url,
|
|
112
|
-
f"/{self.opensearch_config.index_pattern}/_search",
|
|
113
|
-
)
|
|
114
|
-
logs_response = requests.post(
|
|
115
|
-
url=url,
|
|
116
|
-
timeout=180,
|
|
117
|
-
verify=True,
|
|
118
|
-
json=query,
|
|
119
|
-
headers=headers,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
if logs_response.status_code == 200:
|
|
123
|
-
response = logs_response.json()
|
|
124
|
-
logs = format_logs(
|
|
125
|
-
logs=response.get("hits", {}).get("hits", []),
|
|
126
|
-
config=self.opensearch_config,
|
|
127
|
-
)
|
|
128
|
-
return StructuredToolResult(
|
|
129
|
-
status=ToolResultStatus.SUCCESS,
|
|
130
|
-
data=logs,
|
|
131
|
-
params=params.model_dump(),
|
|
132
|
-
)
|
|
133
|
-
else:
|
|
134
|
-
return StructuredToolResult(
|
|
135
|
-
status=ToolResultStatus.ERROR,
|
|
136
|
-
return_code=logs_response.status_code,
|
|
137
|
-
error=logs_response.text,
|
|
138
|
-
params=params.model_dump(),
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
except requests.Timeout:
|
|
142
|
-
logging.warning("Timeout while fetching OpenSearch logs", exc_info=True)
|
|
143
|
-
return StructuredToolResult(
|
|
144
|
-
status=ToolResultStatus.ERROR,
|
|
145
|
-
error="Request timed out while fetching OpenSearch logs",
|
|
146
|
-
params=params.model_dump(),
|
|
147
|
-
)
|
|
148
|
-
except RequestException as e:
|
|
149
|
-
logging.warning("Failed to fetch OpenSearch logs", exc_info=True)
|
|
150
|
-
return StructuredToolResult(
|
|
151
|
-
status=ToolResultStatus.ERROR,
|
|
152
|
-
error=f"Network error while fetching OpenSearch logs: {str(e)}",
|
|
153
|
-
params=params.model_dump(),
|
|
154
|
-
)
|
|
155
|
-
except Exception as e:
|
|
156
|
-
logging.warning("Failed to process OpenSearch logs", exc_info=True)
|
|
157
|
-
return StructuredToolResult(
|
|
158
|
-
status=ToolResultStatus.ERROR,
|
|
159
|
-
error=f"Unexpected error: {str(e)}",
|
|
160
|
-
params=params.model_dump(),
|
|
161
|
-
)
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import logging
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import requests # type: ignore
|
|
6
|
-
from cachetools import TTLCache # type: ignore
|
|
7
|
-
from holmes.core.tools import (
|
|
8
|
-
CallablePrerequisite,
|
|
9
|
-
Tool,
|
|
10
|
-
ToolParameter,
|
|
11
|
-
ToolsetTag,
|
|
12
|
-
)
|
|
13
|
-
import json
|
|
14
|
-
from requests import RequestException
|
|
15
|
-
|
|
16
|
-
from holmes.plugins.toolsets.opensearch.opensearch_utils import (
|
|
17
|
-
BaseOpenSearchToolset,
|
|
18
|
-
add_auth_header,
|
|
19
|
-
get_search_url,
|
|
20
|
-
)
|
|
21
|
-
from holmes.core.tools import StructuredToolResult, ToolResultStatus
|
|
22
|
-
from holmes.plugins.toolsets.utils import get_param_or_raise, toolset_name_for_one_liner
|
|
23
|
-
|
|
24
|
-
TRACES_FIELDS_CACHE_KEY = "cached_traces_fields"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class GetTracesFields(Tool):
|
|
28
|
-
def __init__(self, toolset: BaseOpenSearchToolset):
|
|
29
|
-
super().__init__(
|
|
30
|
-
name="get_traces_fields",
|
|
31
|
-
description="Get all the fields in the traces documents",
|
|
32
|
-
parameters={},
|
|
33
|
-
)
|
|
34
|
-
self._toolset = toolset
|
|
35
|
-
self._cache = None
|
|
36
|
-
|
|
37
|
-
def _invoke(
|
|
38
|
-
self, params: dict, user_approved: bool = False
|
|
39
|
-
) -> StructuredToolResult:
|
|
40
|
-
try:
|
|
41
|
-
if not self._cache and self._toolset.opensearch_config.fields_ttl_seconds:
|
|
42
|
-
self._cache = TTLCache(
|
|
43
|
-
maxsize=5, ttl=self._toolset.opensearch_config.fields_ttl_seconds
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
if self._cache:
|
|
47
|
-
cached_response = self._cache.get(TRACES_FIELDS_CACHE_KEY, None)
|
|
48
|
-
if cached_response:
|
|
49
|
-
logging.debug("traces fields returned from cache")
|
|
50
|
-
return StructuredToolResult(
|
|
51
|
-
status=ToolResultStatus.SUCCESS,
|
|
52
|
-
data=cached_response,
|
|
53
|
-
params=params,
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
body = {
|
|
57
|
-
"size": 1,
|
|
58
|
-
"_source": False,
|
|
59
|
-
"script_fields": {
|
|
60
|
-
"all_fields": {
|
|
61
|
-
"script": {
|
|
62
|
-
"lang": "painless",
|
|
63
|
-
"source": "Map fields = new HashMap(); List stack = new ArrayList(); stack.add(['prefix': '', 'obj': params['_source']]); while (!stack.isEmpty()) { Map item = stack.remove(stack.size() - 1); String prefix = item.get('prefix'); Map obj = item.get('obj'); for (entry in obj.entrySet()) { String fullPath = prefix.isEmpty() ? entry.getKey() : prefix + '.' + entry.getKey(); fields.put(fullPath, true); if (entry.getValue() instanceof Map) { stack.add(['prefix': fullPath, 'obj': entry.getValue()]); } } } return fields.keySet();",
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
headers = {"Content-Type": "application/json"}
|
|
69
|
-
headers.update(
|
|
70
|
-
add_auth_header(self._toolset.opensearch_config.opensearch_auth_header)
|
|
71
|
-
)
|
|
72
|
-
logs_response = requests.get(
|
|
73
|
-
url=get_search_url(self._toolset.opensearch_config),
|
|
74
|
-
timeout=180,
|
|
75
|
-
verify=True,
|
|
76
|
-
data=json.dumps(body),
|
|
77
|
-
headers=headers,
|
|
78
|
-
)
|
|
79
|
-
logs_response.raise_for_status()
|
|
80
|
-
response = json.dumps(logs_response.json())
|
|
81
|
-
if self._cache:
|
|
82
|
-
self._cache[TRACES_FIELDS_CACHE_KEY] = response
|
|
83
|
-
return StructuredToolResult(
|
|
84
|
-
status=ToolResultStatus.SUCCESS,
|
|
85
|
-
data=response,
|
|
86
|
-
params=params,
|
|
87
|
-
)
|
|
88
|
-
except requests.Timeout:
|
|
89
|
-
logging.warning(
|
|
90
|
-
"Timeout while fetching opensearch traces fields", exc_info=True
|
|
91
|
-
)
|
|
92
|
-
return StructuredToolResult(
|
|
93
|
-
status=ToolResultStatus.ERROR,
|
|
94
|
-
error="Request timed out while fetching opensearch traces fields",
|
|
95
|
-
params=params,
|
|
96
|
-
)
|
|
97
|
-
except RequestException as e:
|
|
98
|
-
logging.warning("Failed to fetch opensearch traces fields", exc_info=True)
|
|
99
|
-
return StructuredToolResult(
|
|
100
|
-
status=ToolResultStatus.ERROR,
|
|
101
|
-
error=f"Network error while opensearch traces fields: {str(e)}",
|
|
102
|
-
params=params,
|
|
103
|
-
)
|
|
104
|
-
except Exception as e:
|
|
105
|
-
logging.warning("Failed to process opensearch traces fields", exc_info=True)
|
|
106
|
-
return StructuredToolResult(
|
|
107
|
-
status=ToolResultStatus.ERROR,
|
|
108
|
-
error=f"Unexpected error: {str(e)}",
|
|
109
|
-
params=params,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
def get_parameterized_one_liner(self, params) -> str:
|
|
113
|
-
return f"{toolset_name_for_one_liner(self._toolset.name)}: List Trace Fields"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
class TracesSearchQuery(Tool):
|
|
117
|
-
def __init__(self, toolset: "OpenSearchTracesToolset"):
|
|
118
|
-
super().__init__(
|
|
119
|
-
name="traces_in_range_search",
|
|
120
|
-
description="Get traces in a specified time range for an opensearch query",
|
|
121
|
-
parameters={
|
|
122
|
-
"query": ToolParameter(
|
|
123
|
-
description="An OpenSearch search query. It should be a stringified json, matching opensearch search syntax. "
|
|
124
|
-
"The query must contain a 'range' on the startTimeMillis field. From a given startTimeMillis, until a given startTimeMillis. This is time in milliseconds",
|
|
125
|
-
type="string",
|
|
126
|
-
),
|
|
127
|
-
},
|
|
128
|
-
)
|
|
129
|
-
self._toolset = toolset
|
|
130
|
-
self._cache = None
|
|
131
|
-
|
|
132
|
-
def _invoke(
|
|
133
|
-
self, params: dict, user_approved: bool = False
|
|
134
|
-
) -> StructuredToolResult:
|
|
135
|
-
err_msg = ""
|
|
136
|
-
try:
|
|
137
|
-
body = json.loads(get_param_or_raise(params, "query"))
|
|
138
|
-
full_query = body
|
|
139
|
-
full_query["size"] = int(
|
|
140
|
-
os.environ.get("OPENSEARCH_TRACES_SEARCH_SIZE", "5000")
|
|
141
|
-
)
|
|
142
|
-
logging.debug(f"opensearch traces search query: {full_query}")
|
|
143
|
-
headers = {"Content-Type": "application/json"}
|
|
144
|
-
headers.update(
|
|
145
|
-
add_auth_header(self._toolset.opensearch_config.opensearch_auth_header)
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
logs_response = requests.get(
|
|
149
|
-
url=get_search_url(self._toolset.opensearch_config),
|
|
150
|
-
timeout=180,
|
|
151
|
-
verify=True,
|
|
152
|
-
data=json.dumps(full_query),
|
|
153
|
-
headers=headers,
|
|
154
|
-
)
|
|
155
|
-
if logs_response.status_code > 300:
|
|
156
|
-
err_msg = logs_response.text
|
|
157
|
-
|
|
158
|
-
logs_response.raise_for_status()
|
|
159
|
-
return StructuredToolResult(
|
|
160
|
-
status=ToolResultStatus.SUCCESS,
|
|
161
|
-
data=json.dumps(logs_response.json()),
|
|
162
|
-
params=params,
|
|
163
|
-
)
|
|
164
|
-
except requests.Timeout:
|
|
165
|
-
logging.warning(
|
|
166
|
-
"Timeout while fetching opensearch traces search", exc_info=True
|
|
167
|
-
)
|
|
168
|
-
return StructuredToolResult(
|
|
169
|
-
status=ToolResultStatus.ERROR,
|
|
170
|
-
error=f"Request timed out while fetching opensearch traces search {err_msg}",
|
|
171
|
-
params=params,
|
|
172
|
-
)
|
|
173
|
-
except RequestException as e:
|
|
174
|
-
logging.warning("Failed to fetch opensearch traces search", exc_info=True)
|
|
175
|
-
return StructuredToolResult(
|
|
176
|
-
status=ToolResultStatus.ERROR,
|
|
177
|
-
error=f"Network error while opensearch traces search {err_msg} : {str(e)}",
|
|
178
|
-
params=params,
|
|
179
|
-
)
|
|
180
|
-
except Exception as e:
|
|
181
|
-
logging.warning(
|
|
182
|
-
"Failed to process opensearch traces search ", exc_info=True
|
|
183
|
-
)
|
|
184
|
-
return StructuredToolResult(
|
|
185
|
-
status=ToolResultStatus.ERROR,
|
|
186
|
-
error=f"Unexpected error {err_msg}: {str(e)}",
|
|
187
|
-
params=params,
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
def get_parameterized_one_liner(self, params) -> str:
|
|
191
|
-
query = params.get("query", "")
|
|
192
|
-
return (
|
|
193
|
-
f"{toolset_name_for_one_liner(self._toolset.name)}: Search Traces ({query})"
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
class OpenSearchTracesToolset(BaseOpenSearchToolset):
|
|
198
|
-
def __init__(self):
|
|
199
|
-
super().__init__(
|
|
200
|
-
name="opensearch/traces",
|
|
201
|
-
description="OpenSearch integration to fetch traces",
|
|
202
|
-
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/opensearch-status/",
|
|
203
|
-
icon_url="https://opensearch.org/assets/brand/PNG/Mark/opensearch_mark_default.png",
|
|
204
|
-
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
205
|
-
tools=[
|
|
206
|
-
GetTracesFields(toolset=self),
|
|
207
|
-
TracesSearchQuery(toolset=self),
|
|
208
|
-
],
|
|
209
|
-
tags=[
|
|
210
|
-
ToolsetTag.CORE,
|
|
211
|
-
],
|
|
212
|
-
)
|
|
213
|
-
template_file_path = os.path.abspath(
|
|
214
|
-
os.path.join(
|
|
215
|
-
os.path.dirname(__file__), "opensearch_traces_instructions.jinja2"
|
|
216
|
-
)
|
|
217
|
-
)
|
|
218
|
-
self._load_llm_instructions(jinja_template=f"file://{template_file_path}")
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Traces and Spans
|
|
2
|
-
* All applications are instrumented and has spans and traces.
|
|
3
|
-
* ALWAYS, first, use get_traces_fields to understand the documents fields, then ALWAYS, search for trace documents based on the fields recieved in the previous call. You MUST filter by the fields names that appears here
|
|
4
|
-
* When investigating network errors, latency, or performance issues, ALWAYS check traces and spans
|
|
5
|
-
* The search query should always contain the root 'query' element in the search expression
|
|
6
|
-
* Search traces using the `fetch_opensearch_logs` tool
|
|
7
|
-
* try to search traces based on what you're looking for, and the trace fields available
|
|
8
|
-
* Always build the query with '.keyword' when doing exact match
|
|
9
|
-
* if time range is not specified, get logs for the last hour
|
|
10
|
-
* When investigating numeric errors, these are error codes
|
|
11
|
-
* Always search for traces BEFORE checking logs. If the logs contain information that can lead to another trace search, do another trace search
|
|
12
|
-
* ALWAYS check for traces to learn about the traffic, latency and errors
|