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,42 +0,0 @@
1
- import logging
2
- import requests # type: ignore
3
- from typing import Tuple
4
- import backoff
5
-
6
- from holmes.plugins.toolsets.grafana.common import (
7
- GrafanaConfig,
8
- build_headers,
9
- get_base_url,
10
- )
11
-
12
-
13
- @backoff.on_exception(
14
- backoff.expo, # Exponential backoff
15
- requests.exceptions.RequestException, # Retry on request exceptions
16
- max_tries=5, # Maximum retries
17
- giveup=lambda e: isinstance(e, requests.exceptions.HTTPError)
18
- and e.response.status_code < 500,
19
- )
20
- def grafana_health_check(config: GrafanaConfig) -> Tuple[bool, str]:
21
- base_url = get_base_url(config)
22
- url = f"{base_url}/{config.healthcheck}"
23
- try:
24
- headers_ = build_headers(api_key=config.api_key, additional_headers=None)
25
-
26
- response = requests.get(url, headers=headers_, timeout=10) # Added timeout
27
- response.raise_for_status()
28
- return True, ""
29
- except Exception as e:
30
- logging.error(f"Failed to fetch grafana health status at {url}", exc_info=True)
31
- error_msg = f"Failed to fetch grafana health status at {url}. {str(e)}"
32
-
33
- # Add helpful hint if this looks like a common misconfiguration
34
- if config.grafana_datasource_uid and ":3100" in config.url:
35
- error_msg += (
36
- "\n\nPossible configuration issue: grafana_datasource_uid is set but URL contains port 3100 "
37
- "(typically used for direct Loki connections). Please verify:\n"
38
- "- If connecting directly to Loki: remove grafana_datasource_uid from config\n"
39
- "- If connecting via Grafana proxy: ensure URL points to Grafana (usually port 3000)"
40
- )
41
-
42
- return False, error_msg
@@ -1,124 +0,0 @@
1
- import requests # type: ignore
2
- from typing import Dict, List, Optional
3
- import backoff
4
-
5
- from holmes.plugins.toolsets.grafana.common import build_headers
6
- from holmes.plugins.toolsets.grafana.trace_parser import process_trace
7
-
8
-
9
- def execute_tempo_query_with_retry(
10
- base_url: str,
11
- api_key: Optional[str],
12
- headers: Optional[Dict[str, str]],
13
- query_params: dict,
14
- retries: int = 3,
15
- timeout: int = 5,
16
- ):
17
- """
18
- Execute a Tempo API query through Grafana with retries and timeout.
19
-
20
- Args:
21
- tempo_datasource_uid: The UID of the Tempo datasource.
22
- query_params: Query parameters for the API.
23
- retries: Number of retries for the request.
24
- timeout: Timeout for each request in seconds.
25
-
26
- Returns:
27
- List of trace results.
28
- """
29
- url = f"{base_url}/api/search"
30
-
31
- @backoff.on_exception(
32
- backoff.expo, # Exponential backoff
33
- requests.exceptions.RequestException, # Retry on request exceptions
34
- max_tries=retries, # Maximum retries
35
- giveup=lambda e: isinstance(e, requests.exceptions.HTTPError)
36
- and e.response.status_code < 500,
37
- )
38
- def make_request():
39
- response = requests.post(
40
- url,
41
- headers=build_headers(api_key=api_key, additional_headers=headers),
42
- json=query_params,
43
- timeout=timeout, # Set timeout for the request
44
- )
45
- response.raise_for_status() # Raise an error for non-2xx responses
46
- return response.json()
47
-
48
- try:
49
- return make_request()
50
- except requests.exceptions.RequestException as e:
51
- raise Exception(f"Request to Tempo API failed after retries: {e}")
52
-
53
-
54
- def query_tempo_traces(
55
- base_url: str,
56
- api_key: Optional[str],
57
- headers: Optional[Dict[str, str]],
58
- query: Optional[str],
59
- start: int,
60
- end: int,
61
- limit: int,
62
- ) -> Dict:
63
- query_params = {
64
- "start": str(start),
65
- "end": str(end),
66
- "limit": str(limit),
67
- }
68
-
69
- if query:
70
- query_params["q"] = query
71
- data = execute_tempo_query_with_retry(
72
- base_url=base_url,
73
- api_key=api_key,
74
- headers=headers,
75
- query_params=query_params,
76
- )
77
- return data
78
-
79
-
80
- def query_tempo_trace_by_id(
81
- base_url: str,
82
- api_key: Optional[str],
83
- headers: Optional[Dict[str, str]],
84
- trace_id: str,
85
- key_labels: List[str],
86
- retries: int = 3,
87
- timeout: int = 5,
88
- ) -> str:
89
- """
90
- Query Tempo for a specific trace by its ID with retries and backoff.
91
-
92
- Args:
93
- tempo_datasource_id: The ID of the Tempo datasource.
94
- trace_id: The trace ID to retrieve.
95
- retries: Number of retries for the request.
96
- timeout: Timeout for each request in seconds.
97
-
98
- Returns:
99
- A formatted trace details string
100
- """
101
- url = f"{base_url}/api/traces/{trace_id}"
102
-
103
- @backoff.on_exception(
104
- backoff.expo,
105
- requests.exceptions.RequestException,
106
- max_tries=retries,
107
- giveup=lambda e: isinstance(e, requests.exceptions.HTTPError)
108
- and e.response.status_code < 500,
109
- )
110
- def make_request():
111
- response = requests.get(
112
- url,
113
- headers=build_headers(api_key=api_key, additional_headers=headers),
114
- timeout=timeout,
115
- )
116
- response.raise_for_status()
117
- return process_trace(response.json(), key_labels)
118
-
119
- try:
120
- return make_request()
121
- except requests.exceptions.RequestException as e:
122
- raise Exception(
123
- f"Failed to retrieve trace by ID after retries: {e} \n for URL: {url}"
124
- )
@@ -1,110 +0,0 @@
1
- from typing import Any, cast, Set
2
- from pydantic import BaseModel
3
-
4
- from holmes.core.tools import CallablePrerequisite
5
- from holmes.plugins.toolsets.grafana.common import (
6
- GrafanaConfig,
7
- format_log,
8
- get_base_url,
9
- )
10
- from holmes.plugins.toolsets.grafana.grafana_api import grafana_health_check
11
- from holmes.plugins.toolsets.logging_utils.logging_api import (
12
- BasePodLoggingToolset,
13
- FetchPodLogsParams,
14
- LoggingCapability,
15
- PodLoggingTool,
16
- DEFAULT_TIME_SPAN_SECONDS,
17
- )
18
- from holmes.plugins.toolsets.utils import (
19
- process_timestamps_to_rfc3339,
20
- )
21
-
22
- from holmes.plugins.toolsets.grafana.loki_api import (
23
- query_loki_logs_by_label,
24
- )
25
- from holmes.core.tools import StructuredToolResult, ToolResultStatus
26
-
27
-
28
- class GrafanaLokiLabelsConfig(BaseModel):
29
- pod: str = "pod"
30
- namespace: str = "namespace"
31
-
32
-
33
- class GrafanaLokiConfig(GrafanaConfig):
34
- labels: GrafanaLokiLabelsConfig = GrafanaLokiLabelsConfig()
35
-
36
-
37
- class GrafanaLokiToolset(BasePodLoggingToolset):
38
- @property
39
- def supported_capabilities(self) -> Set[LoggingCapability]:
40
- """Loki only supports substring matching, not regex or exclude filters"""
41
- return set() # No regex support, no exclude filter
42
-
43
- def __init__(self):
44
- super().__init__(
45
- name="grafana/loki",
46
- description="Fetches kubernetes pods logs from Loki",
47
- icon_url="https://grafana.com/media/docs/loki/logo-grafana-loki.png",
48
- docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/grafanaloki/",
49
- prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
50
- tools=[], # Initialize with empty tools first
51
- )
52
- # Now that parent is initialized and self.name exists, create the tool
53
- self.tools = [PodLoggingTool(self)]
54
-
55
- def prerequisites_callable(self, config: dict[str, Any]) -> tuple[bool, str]:
56
- if not config:
57
- return False, "Missing Loki configuration. Check your config."
58
-
59
- self.config = GrafanaLokiConfig(**config)
60
-
61
- return grafana_health_check(self.config)
62
-
63
- def get_example_config(self):
64
- example_config = GrafanaLokiConfig(
65
- api_key="YOUR API KEY",
66
- url="YOUR GRAFANA URL",
67
- grafana_datasource_uid="<UID of the loki datasource to use>",
68
- )
69
- return example_config.model_dump()
70
-
71
- @property
72
- def grafana_config(self) -> GrafanaLokiConfig:
73
- return cast(GrafanaLokiConfig, self.config)
74
-
75
- def logger_name(self) -> str:
76
- return "Loki"
77
-
78
- def fetch_pod_logs(self, params: FetchPodLogsParams) -> StructuredToolResult:
79
- (start, end) = process_timestamps_to_rfc3339(
80
- start_timestamp=params.start_time,
81
- end_timestamp=params.end_time,
82
- default_time_span_seconds=DEFAULT_TIME_SPAN_SECONDS,
83
- )
84
-
85
- base_url = get_base_url(self.grafana_config)
86
- logs = query_loki_logs_by_label(
87
- base_url=base_url,
88
- api_key=self.grafana_config.api_key,
89
- headers=self.grafana_config.headers,
90
- filter=params.filter,
91
- namespace=params.namespace,
92
- namespace_search_key=self.grafana_config.labels.namespace,
93
- label=self.grafana_config.labels.pod,
94
- label_value=params.pod_name,
95
- start=start,
96
- end=end,
97
- limit=params.limit or 2000,
98
- )
99
- if logs:
100
- logs.sort(key=lambda x: x["timestamp"])
101
- return StructuredToolResult(
102
- status=ToolResultStatus.SUCCESS,
103
- data="\n".join([format_log(log) for log in logs]),
104
- params=params.model_dump(),
105
- )
106
- else:
107
- return StructuredToolResult(
108
- status=ToolResultStatus.NO_DATA,
109
- params=params.model_dump(),
110
- )
@@ -1,231 +0,0 @@
1
- import requests # type: ignore
2
- import logging
3
- from typing import Any, Optional, Dict
4
- from holmes.core.tools import (
5
- CallablePrerequisite,
6
- Tool,
7
- ToolParameter,
8
- Toolset,
9
- ToolsetTag,
10
- )
11
- from pydantic import BaseModel
12
- from holmes.core.tools import StructuredToolResult, ToolResultStatus
13
- from holmes.plugins.toolsets.utils import get_param_or_raise, toolset_name_for_one_liner
14
-
15
-
16
- class BaseNewRelicTool(Tool):
17
- toolset: "NewRelicToolset"
18
-
19
-
20
- class GetLogs(BaseNewRelicTool):
21
- def __init__(self, toolset: "NewRelicToolset"):
22
- super().__init__(
23
- name="newrelic_get_logs",
24
- description="Retrieve logs from New Relic",
25
- parameters={
26
- "app": ToolParameter(
27
- description="The application name to filter logs",
28
- type="string",
29
- required=True,
30
- ),
31
- "since": ToolParameter(
32
- description="Time range to fetch logs (e.g., '1 hour ago')",
33
- type="string",
34
- required=True,
35
- ),
36
- },
37
- toolset=toolset,
38
- )
39
-
40
- def _invoke(
41
- self, params: dict, user_approved: bool = False
42
- ) -> StructuredToolResult:
43
- def success(msg: Any) -> StructuredToolResult:
44
- return StructuredToolResult(
45
- status=ToolResultStatus.SUCCESS,
46
- data=msg,
47
- params=params,
48
- )
49
-
50
- def error(msg: str) -> StructuredToolResult:
51
- return StructuredToolResult(
52
- status=ToolResultStatus.ERROR,
53
- data=msg,
54
- params=params,
55
- )
56
-
57
- app = params.get("app")
58
- since = params.get("since")
59
-
60
- query = {
61
- "query": f"""
62
- {{
63
- actor {{
64
- account(id: {self.toolset.nr_account_id}) {{
65
- nrql(query: \"SELECT * FROM Log WHERE app = '{app}' SINCE {since}\") {{
66
- results
67
- }}
68
- }}
69
- }}
70
- }}
71
- """
72
- }
73
-
74
- url = "https://api.newrelic.com/graphql"
75
- headers = {
76
- "Content-Type": "application/json",
77
- "Api-Key": self.toolset.nr_api_key,
78
- }
79
-
80
- try:
81
- logging.info(f"Getting New Relic logs for app {app} since {since}")
82
- response = requests.post(url, headers=headers, json=query)
83
-
84
- if response.status_code == 200:
85
- return success(response.json())
86
- else:
87
- return error(
88
- f"Failed to fetch logs. Status code: {response.status_code}\n{response.text}"
89
- )
90
- except Exception as e:
91
- logging.exception("Exception while fetching logs")
92
- return error(f"Error while fetching logs: {str(e)}")
93
-
94
- def get_parameterized_one_liner(self, params) -> str:
95
- app = params.get("app", "")
96
- since = params.get("since", "")
97
- return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Logs ({app} - {since})"
98
-
99
-
100
- class GetTraces(BaseNewRelicTool):
101
- def __init__(self, toolset: "NewRelicToolset"):
102
- super().__init__(
103
- name="newrelic_get_traces",
104
- description="Retrieve traces from New Relic",
105
- parameters={
106
- "duration": ToolParameter(
107
- description="Minimum trace duration in seconds",
108
- type="number",
109
- required=True,
110
- ),
111
- "trace_id": ToolParameter(
112
- description="Specific trace ID to fetch details (optional)",
113
- type="string",
114
- required=False,
115
- ),
116
- },
117
- toolset=toolset,
118
- )
119
-
120
- def _invoke(
121
- self, params: dict, user_approved: bool = False
122
- ) -> StructuredToolResult:
123
- def success(msg: Any) -> StructuredToolResult:
124
- return StructuredToolResult(
125
- status=ToolResultStatus.SUCCESS,
126
- data=msg,
127
- params=params,
128
- )
129
-
130
- def error(msg: str) -> StructuredToolResult:
131
- return StructuredToolResult(
132
- status=ToolResultStatus.ERROR,
133
- data=msg,
134
- params=params,
135
- )
136
-
137
- duration = get_param_or_raise(params, "duration")
138
- trace_id = params.get("trace_id")
139
-
140
- if trace_id:
141
- query_string = f"SELECT * FROM Span WHERE trace.id = '{trace_id}' and duration.ms > {duration * 1000} and span.kind != 'internal'"
142
- else:
143
- query_string = f"SELECT * FROM Span WHERE duration.ms > {duration * 1000} and span.kind != 'internal'"
144
-
145
- query = {
146
- "query": f"""
147
- {{
148
- actor {{
149
- account(id: {self.toolset.nr_account_id}) {{
150
- nrql(query: \"{query_string}\") {{
151
- results
152
- }}
153
- }}
154
- }}
155
- }}
156
- """
157
- }
158
-
159
- url = "https://api.newrelic.com/graphql"
160
- headers = {
161
- "Content-Type": "application/json",
162
- "Api-Key": self.toolset.nr_api_key,
163
- }
164
-
165
- try:
166
- logging.info(f"Getting New Relic traces with duration > {duration}s")
167
- response = requests.post(url, headers=headers, json=query)
168
-
169
- if response.status_code == 200:
170
- return success(response.json())
171
- else:
172
- return error(
173
- f"Failed to fetch traces. Status code: {response.status_code}\n{response.text}"
174
- )
175
- except Exception as e:
176
- logging.exception("Exception while fetching traces")
177
- return error(f"Error while fetching traces: {str(e)}")
178
-
179
- def get_parameterized_one_liner(self, params) -> str:
180
- if "trace_id" in params and params["trace_id"]:
181
- trace_id = params.get("trace_id", "")
182
- return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Trace Details ({trace_id})"
183
- duration = params.get("duration", "")
184
- return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Traces (>{duration}s)"
185
-
186
-
187
- class NewrelicConfig(BaseModel):
188
- nr_api_key: Optional[str] = None
189
- nr_account_id: Optional[str] = None
190
-
191
-
192
- class NewRelicToolset(Toolset):
193
- nr_api_key: Optional[str] = None
194
- nr_account_id: Optional[str] = None
195
-
196
- def __init__(self):
197
- super().__init__(
198
- name="newrelic",
199
- description="Toolset for interacting with New Relic to fetch logs and traces",
200
- docs_url="https://docs.newrelic.com/docs/apis/nerdgraph-api/",
201
- icon_url="https://companieslogo.com/img/orig/NEWR-de5fcb2e.png?t=1720244493",
202
- prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
203
- tools=[
204
- GetLogs(self),
205
- GetTraces(self),
206
- ],
207
- experimental=True,
208
- tags=[ToolsetTag.CORE],
209
- )
210
-
211
- def prerequisites_callable(
212
- self, config: dict[str, Any]
213
- ) -> tuple[bool, Optional[str]]:
214
- if not config:
215
- return False, "No configuration provided"
216
-
217
- try:
218
- nr_config = NewrelicConfig(**config)
219
- self.nr_account_id = nr_config.nr_account_id
220
- self.nr_api_key = nr_config.nr_api_key
221
-
222
- if not self.nr_account_id or not self.nr_api_key:
223
- return False, "New Relic account ID or API key is missing"
224
-
225
- return True, None
226
- except Exception as e:
227
- logging.exception("Failed to set up New Relic toolset")
228
- return False, str(e)
229
-
230
- def get_example_config(self) -> Dict[str, Any]:
231
- return {}