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.
Files changed (188) hide show
  1. holmes/__init__.py +3 -5
  2. holmes/clients/robusta_client.py +20 -6
  3. holmes/common/env_vars.py +58 -3
  4. holmes/common/openshift.py +1 -1
  5. holmes/config.py +123 -148
  6. holmes/core/conversations.py +71 -15
  7. holmes/core/feedback.py +191 -0
  8. holmes/core/investigation.py +31 -39
  9. holmes/core/investigation_structured_output.py +3 -3
  10. holmes/core/issue.py +1 -1
  11. holmes/core/llm.py +508 -88
  12. holmes/core/models.py +108 -4
  13. holmes/core/openai_formatting.py +14 -1
  14. holmes/core/prompt.py +48 -3
  15. holmes/core/runbooks.py +1 -0
  16. holmes/core/safeguards.py +8 -6
  17. holmes/core/supabase_dal.py +295 -100
  18. holmes/core/tool_calling_llm.py +489 -428
  19. holmes/core/tools.py +325 -56
  20. holmes/core/tools_utils/token_counting.py +21 -0
  21. holmes/core/tools_utils/tool_context_window_limiter.py +40 -0
  22. holmes/core/tools_utils/tool_executor.py +0 -13
  23. holmes/core/tools_utils/toolset_utils.py +1 -0
  24. holmes/core/toolset_manager.py +191 -5
  25. holmes/core/tracing.py +19 -3
  26. holmes/core/transformers/__init__.py +23 -0
  27. holmes/core/transformers/base.py +63 -0
  28. holmes/core/transformers/llm_summarize.py +175 -0
  29. holmes/core/transformers/registry.py +123 -0
  30. holmes/core/transformers/transformer.py +32 -0
  31. holmes/core/truncation/compaction.py +94 -0
  32. holmes/core/truncation/dal_truncation_utils.py +23 -0
  33. holmes/core/truncation/input_context_window_limiter.py +219 -0
  34. holmes/interactive.py +228 -31
  35. holmes/main.py +23 -40
  36. holmes/plugins/interfaces.py +2 -1
  37. holmes/plugins/prompts/__init__.py +2 -1
  38. holmes/plugins/prompts/_fetch_logs.jinja2 +31 -6
  39. holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
  40. holmes/plugins/prompts/_runbook_instructions.jinja2 +24 -12
  41. holmes/plugins/prompts/base_user_prompt.jinja2 +7 -0
  42. holmes/plugins/prompts/conversation_history_compaction.jinja2 +89 -0
  43. holmes/plugins/prompts/generic_ask.jinja2 +0 -4
  44. holmes/plugins/prompts/generic_ask_conversation.jinja2 +0 -1
  45. holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +0 -1
  46. holmes/plugins/prompts/generic_investigation.jinja2 +0 -1
  47. holmes/plugins/prompts/investigation_procedure.jinja2 +50 -1
  48. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +0 -1
  49. holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +0 -1
  50. holmes/plugins/runbooks/__init__.py +145 -17
  51. holmes/plugins/runbooks/catalog.json +2 -0
  52. holmes/plugins/sources/github/__init__.py +4 -2
  53. holmes/plugins/sources/prometheus/models.py +1 -0
  54. holmes/plugins/toolsets/__init__.py +44 -27
  55. holmes/plugins/toolsets/aks-node-health.yaml +46 -0
  56. holmes/plugins/toolsets/aks.yaml +64 -0
  57. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +38 -47
  58. holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +3 -2
  59. holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +2 -1
  60. holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +3 -2
  61. holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +3 -1
  62. holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +3 -1
  63. holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +12 -13
  64. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +15 -12
  65. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +15 -12
  66. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +11 -11
  67. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +11 -9
  68. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +15 -12
  69. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +15 -15
  70. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +11 -8
  71. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +11 -8
  72. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +11 -8
  73. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +11 -8
  74. holmes/plugins/toolsets/azure_sql/utils.py +0 -32
  75. holmes/plugins/toolsets/bash/argocd/__init__.py +3 -3
  76. holmes/plugins/toolsets/bash/aws/__init__.py +4 -4
  77. holmes/plugins/toolsets/bash/azure/__init__.py +4 -4
  78. holmes/plugins/toolsets/bash/bash_toolset.py +11 -15
  79. holmes/plugins/toolsets/bash/common/bash.py +23 -13
  80. holmes/plugins/toolsets/bash/common/bash_command.py +1 -1
  81. holmes/plugins/toolsets/bash/common/stringify.py +1 -1
  82. holmes/plugins/toolsets/bash/kubectl/__init__.py +2 -1
  83. holmes/plugins/toolsets/bash/kubectl/constants.py +0 -1
  84. holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +3 -4
  85. holmes/plugins/toolsets/bash/parse_command.py +12 -13
  86. holmes/plugins/toolsets/cilium.yaml +284 -0
  87. holmes/plugins/toolsets/connectivity_check.py +124 -0
  88. holmes/plugins/toolsets/coralogix/api.py +132 -119
  89. holmes/plugins/toolsets/coralogix/coralogix.jinja2 +14 -0
  90. holmes/plugins/toolsets/coralogix/toolset_coralogix.py +219 -0
  91. holmes/plugins/toolsets/coralogix/utils.py +15 -79
  92. holmes/plugins/toolsets/datadog/datadog_api.py +525 -26
  93. holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +55 -11
  94. holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +3 -3
  95. holmes/plugins/toolsets/datadog/datadog_models.py +59 -0
  96. holmes/plugins/toolsets/datadog/datadog_url_utils.py +213 -0
  97. holmes/plugins/toolsets/datadog/instructions_datadog_traces.jinja2 +165 -28
  98. holmes/plugins/toolsets/datadog/toolset_datadog_general.py +417 -241
  99. holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +234 -214
  100. holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +167 -79
  101. holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +374 -363
  102. holmes/plugins/toolsets/elasticsearch/__init__.py +6 -0
  103. holmes/plugins/toolsets/elasticsearch/elasticsearch.py +834 -0
  104. holmes/plugins/toolsets/elasticsearch/opensearch_ppl_query_docs.jinja2 +1616 -0
  105. holmes/plugins/toolsets/elasticsearch/opensearch_query_assist.py +78 -0
  106. holmes/plugins/toolsets/elasticsearch/opensearch_query_assist_instructions.jinja2 +223 -0
  107. holmes/plugins/toolsets/git.py +54 -50
  108. holmes/plugins/toolsets/grafana/base_grafana_toolset.py +16 -4
  109. holmes/plugins/toolsets/grafana/common.py +13 -29
  110. holmes/plugins/toolsets/grafana/grafana_tempo_api.py +455 -0
  111. holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +25 -0
  112. holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +191 -0
  113. holmes/plugins/toolsets/grafana/loki_api.py +4 -0
  114. holmes/plugins/toolsets/grafana/toolset_grafana.py +293 -89
  115. holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +49 -0
  116. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
  117. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +820 -292
  118. holmes/plugins/toolsets/grafana/trace_parser.py +4 -3
  119. holmes/plugins/toolsets/internet/internet.py +15 -16
  120. holmes/plugins/toolsets/internet/notion.py +9 -11
  121. holmes/plugins/toolsets/investigator/core_investigation.py +44 -36
  122. holmes/plugins/toolsets/investigator/model.py +3 -1
  123. holmes/plugins/toolsets/json_filter_mixin.py +134 -0
  124. holmes/plugins/toolsets/kafka.py +36 -42
  125. holmes/plugins/toolsets/kubernetes.yaml +317 -113
  126. holmes/plugins/toolsets/kubernetes_logs.py +9 -9
  127. holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
  128. holmes/plugins/toolsets/logging_utils/logging_api.py +94 -8
  129. holmes/plugins/toolsets/mcp/toolset_mcp.py +218 -64
  130. holmes/plugins/toolsets/newrelic/new_relic_api.py +165 -0
  131. holmes/plugins/toolsets/newrelic/newrelic.jinja2 +65 -0
  132. holmes/plugins/toolsets/newrelic/newrelic.py +320 -0
  133. holmes/plugins/toolsets/openshift.yaml +283 -0
  134. holmes/plugins/toolsets/prometheus/prometheus.py +1202 -421
  135. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +54 -5
  136. holmes/plugins/toolsets/prometheus/utils.py +28 -0
  137. holmes/plugins/toolsets/rabbitmq/api.py +23 -4
  138. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +13 -14
  139. holmes/plugins/toolsets/robusta/robusta.py +239 -68
  140. holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
  141. holmes/plugins/toolsets/runbook/runbook_fetcher.py +157 -27
  142. holmes/plugins/toolsets/service_discovery.py +1 -1
  143. holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
  144. holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
  145. holmes/plugins/toolsets/utils.py +88 -0
  146. holmes/utils/config_utils.py +91 -0
  147. holmes/utils/connection_utils.py +31 -0
  148. holmes/utils/console/result.py +10 -0
  149. holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
  150. holmes/utils/env.py +7 -0
  151. holmes/utils/file_utils.py +2 -1
  152. holmes/utils/global_instructions.py +60 -11
  153. holmes/utils/holmes_status.py +6 -4
  154. holmes/utils/holmes_sync_toolsets.py +0 -2
  155. holmes/utils/krr_utils.py +188 -0
  156. holmes/utils/log.py +15 -0
  157. holmes/utils/markdown_utils.py +2 -3
  158. holmes/utils/memory_limit.py +58 -0
  159. holmes/utils/sentry_helper.py +64 -0
  160. holmes/utils/stream.py +69 -8
  161. holmes/utils/tags.py +4 -3
  162. holmes/version.py +37 -15
  163. holmesgpt-0.18.4.dist-info/LICENSE +178 -0
  164. {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/METADATA +35 -31
  165. holmesgpt-0.18.4.dist-info/RECORD +258 -0
  166. holmes/core/performance_timing.py +0 -72
  167. holmes/plugins/toolsets/aws.yaml +0 -80
  168. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +0 -112
  169. holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +0 -310
  170. holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +0 -739
  171. holmes/plugins/toolsets/grafana/grafana_api.py +0 -42
  172. holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
  173. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
  174. holmes/plugins/toolsets/newrelic.py +0 -231
  175. holmes/plugins/toolsets/opensearch/opensearch.py +0 -257
  176. holmes/plugins/toolsets/opensearch/opensearch_logs.py +0 -161
  177. holmes/plugins/toolsets/opensearch/opensearch_traces.py +0 -218
  178. holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +0 -12
  179. holmes/plugins/toolsets/opensearch/opensearch_utils.py +0 -166
  180. holmes/plugins/toolsets/servicenow/install.md +0 -37
  181. holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
  182. holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
  183. holmes/utils/keygen_utils.py +0 -6
  184. holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
  185. holmesgpt-0.13.2.dist-info/RECORD +0 -234
  186. /holmes/plugins/toolsets/{opensearch → newrelic}/__init__.py +0 -0
  187. {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/WHEEL +0 -0
  188. {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