holmesgpt 0.13.2__py3-none-any.whl → 0.16.2a0__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 +1 -1
- holmes/clients/robusta_client.py +17 -4
- holmes/common/env_vars.py +40 -1
- holmes/config.py +114 -144
- holmes/core/conversations.py +53 -14
- holmes/core/feedback.py +191 -0
- holmes/core/investigation.py +18 -22
- holmes/core/llm.py +489 -88
- holmes/core/models.py +103 -1
- holmes/core/openai_formatting.py +13 -0
- holmes/core/prompt.py +1 -1
- holmes/core/safeguards.py +4 -4
- holmes/core/supabase_dal.py +293 -100
- holmes/core/tool_calling_llm.py +423 -323
- holmes/core/tools.py +311 -33
- holmes/core/tools_utils/token_counting.py +14 -0
- holmes/core/tools_utils/tool_context_window_limiter.py +57 -0
- holmes/core/tools_utils/tool_executor.py +13 -8
- holmes/core/toolset_manager.py +155 -4
- holmes/core/tracing.py +6 -1
- holmes/core/transformers/__init__.py +23 -0
- holmes/core/transformers/base.py +62 -0
- holmes/core/transformers/llm_summarize.py +174 -0
- holmes/core/transformers/registry.py +122 -0
- holmes/core/transformers/transformer.py +31 -0
- holmes/core/truncation/compaction.py +59 -0
- holmes/core/truncation/dal_truncation_utils.py +23 -0
- holmes/core/truncation/input_context_window_limiter.py +218 -0
- holmes/interactive.py +177 -24
- holmes/main.py +7 -4
- holmes/plugins/prompts/_fetch_logs.jinja2 +26 -1
- holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
- holmes/plugins/prompts/_runbook_instructions.jinja2 +23 -12
- holmes/plugins/prompts/conversation_history_compaction.jinja2 +88 -0
- holmes/plugins/prompts/generic_ask.jinja2 +2 -4
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +2 -1
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +2 -1
- holmes/plugins/prompts/generic_investigation.jinja2 +2 -1
- holmes/plugins/prompts/investigation_procedure.jinja2 +48 -0
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +2 -1
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +2 -1
- holmes/plugins/runbooks/__init__.py +117 -18
- holmes/plugins/runbooks/catalog.json +2 -0
- holmes/plugins/toolsets/__init__.py +21 -8
- holmes/plugins/toolsets/aks-node-health.yaml +46 -0
- holmes/plugins/toolsets/aks.yaml +64 -0
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +26 -36
- holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +0 -1
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +10 -7
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +8 -6
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +8 -6
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +9 -7
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +9 -6
- holmes/plugins/toolsets/bash/bash_toolset.py +10 -13
- holmes/plugins/toolsets/bash/common/bash.py +7 -7
- holmes/plugins/toolsets/cilium.yaml +284 -0
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +5 -3
- holmes/plugins/toolsets/datadog/datadog_api.py +490 -24
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +21 -10
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +349 -216
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +190 -19
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +101 -44
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +13 -16
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +25 -31
- holmes/plugins/toolsets/git.py +51 -46
- holmes/plugins/toolsets/grafana/common.py +15 -3
- holmes/plugins/toolsets/grafana/grafana_api.py +46 -24
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +454 -0
- holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +9 -0
- holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +117 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +211 -91
- holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +27 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +653 -293
- holmes/plugins/toolsets/grafana/trace_parser.py +1 -1
- holmes/plugins/toolsets/internet/internet.py +6 -7
- holmes/plugins/toolsets/internet/notion.py +5 -6
- holmes/plugins/toolsets/investigator/core_investigation.py +42 -34
- holmes/plugins/toolsets/kafka.py +25 -36
- holmes/plugins/toolsets/kubernetes.yaml +58 -84
- holmes/plugins/toolsets/kubernetes_logs.py +6 -6
- holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
- holmes/plugins/toolsets/logging_utils/logging_api.py +80 -4
- holmes/plugins/toolsets/mcp/toolset_mcp.py +181 -55
- holmes/plugins/toolsets/newrelic/__init__.py +0 -0
- holmes/plugins/toolsets/newrelic/new_relic_api.py +125 -0
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +41 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +163 -0
- holmes/plugins/toolsets/opensearch/opensearch.py +10 -17
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +7 -7
- holmes/plugins/toolsets/opensearch/opensearch_ppl_query_docs.jinja2 +1616 -0
- holmes/plugins/toolsets/opensearch/opensearch_query_assist.py +78 -0
- holmes/plugins/toolsets/opensearch/opensearch_query_assist_instructions.jinja2 +223 -0
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +13 -16
- holmes/plugins/toolsets/openshift.yaml +283 -0
- holmes/plugins/toolsets/prometheus/prometheus.py +915 -390
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +43 -2
- holmes/plugins/toolsets/prometheus/utils.py +28 -0
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +9 -10
- holmes/plugins/toolsets/robusta/robusta.py +236 -65
- holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +137 -26
- 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/default_toolset_installation_guide.jinja2 +1 -22
- holmes/utils/env.py +7 -0
- holmes/utils/global_instructions.py +75 -10
- holmes/utils/holmes_status.py +2 -1
- holmes/utils/holmes_sync_toolsets.py +0 -2
- holmes/utils/krr_utils.py +188 -0
- holmes/utils/sentry_helper.py +41 -0
- holmes/utils/stream.py +61 -7
- holmes/version.py +34 -14
- holmesgpt-0.16.2a0.dist-info/LICENSE +178 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/METADATA +29 -27
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/RECORD +126 -102
- holmes/core/performance_timing.py +0 -72
- 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/servicenow/install.md +0 -37
- holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
- holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
- holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/WHEEL +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/entry_points.txt +0 -0
|
@@ -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 {}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
## Configuration
|
|
2
|
-
|
|
3
|
-
[Full guide for reference](https://www.servicenow.com/docs/bundle/yokohama-platform-security/page/integrate/authentication/task/configure-api-key.html)
|
|
4
|
-
|
|
5
|
-
### Create an inbound authentication profile.
|
|
6
|
-
|
|
7
|
-
1. Navigate to All > System Web Services > API Access Policies > Inbound Authentication Profiles.
|
|
8
|
-
2. Select New.
|
|
9
|
-
3. Select Create API Key authentication profiles
|
|
10
|
-
4. Auth Parameter > add x-sn-apikey: Auth Header
|
|
11
|
-
5. Submit the form.
|
|
12
|
-
|
|
13
|
-
### Create a REST API key
|
|
14
|
-
|
|
15
|
-
1. Navigate to All > System Web Services > API Access Policies > REST API Key.
|
|
16
|
-
2. Select New.
|
|
17
|
-
3. Set name, description and user. Set expiry date if desired. > Submit.
|
|
18
|
-
4. Open the record that was created to view the token generated by the ServiceNow AI Platform for the user.
|
|
19
|
-
|
|
20
|
-
### Create a REST API Access policy
|
|
21
|
-
|
|
22
|
-
1. Navigate to All > System Web Services > REST API Access Policies.
|
|
23
|
-
2. Select New.
|
|
24
|
-
3. REST API = Table API
|
|
25
|
-
4. Uncheck Apply to all tables > Select table > change_request
|
|
26
|
-
5. in select profile from step 1 (API Key)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Use your `instance name` and `api_key` to set up Service Now configuration.
|
|
30
|
-
```yaml
|
|
31
|
-
toolsets:
|
|
32
|
-
ServiceNow:
|
|
33
|
-
enabled: true
|
|
34
|
-
config:
|
|
35
|
-
api_key: <api-token>
|
|
36
|
-
instance: <dev1234..>
|
|
37
|
-
```
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
* ALWAYS fetch changes from servicenow, USE servicenow_return_changes_in_timerange to see changes in the relevant time range.
|
|
2
|
-
* If you are investigating an issue on some subject , USE servicenow_return_changes_with_keyword with the object name to find related changes.
|
|
3
|
-
* If you find a ServiceNow change that seems relevant to your investigation or the user question, USE servicenow_return_change_details with the change sys_id to get further information and improve your answer if possible.
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import requests # type: ignore
|
|
2
|
-
import logging
|
|
3
|
-
import os
|
|
4
|
-
from typing import Any, Dict, Tuple, List
|
|
5
|
-
from holmes.core.tools import (
|
|
6
|
-
CallablePrerequisite,
|
|
7
|
-
Tool,
|
|
8
|
-
ToolParameter,
|
|
9
|
-
Toolset,
|
|
10
|
-
ToolsetTag,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
from pydantic import BaseModel, PrivateAttr
|
|
14
|
-
from holmes.core.tools import StructuredToolResult, ToolResultStatus
|
|
15
|
-
from holmes.plugins.toolsets.utils import (
|
|
16
|
-
process_timestamps_to_rfc3339,
|
|
17
|
-
standard_start_datetime_tool_param_description,
|
|
18
|
-
toolset_name_for_one_liner,
|
|
19
|
-
)
|
|
20
|
-
from holmes.plugins.toolsets.logging_utils.logging_api import (
|
|
21
|
-
DEFAULT_TIME_SPAN_SECONDS,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class ServiceNowConfig(BaseModel):
|
|
26
|
-
api_key: str
|
|
27
|
-
instance: str
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class ServiceNowToolset(Toolset):
|
|
31
|
-
name: str = "ServiceNow"
|
|
32
|
-
description: str = "Database containing changes information related to keys, workloads or any service."
|
|
33
|
-
tags: List[ToolsetTag] = [ToolsetTag.CORE]
|
|
34
|
-
_session: requests.Session = PrivateAttr(default=requests.Session())
|
|
35
|
-
|
|
36
|
-
def __init__(self):
|
|
37
|
-
super().__init__(
|
|
38
|
-
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
39
|
-
experimental=True,
|
|
40
|
-
tools=[
|
|
41
|
-
ReturnChangesInTimerange(toolset=self),
|
|
42
|
-
ReturnChange(toolset=self),
|
|
43
|
-
ReturnChangesWithKeyword(toolset=self),
|
|
44
|
-
],
|
|
45
|
-
)
|
|
46
|
-
instructions_filepath = os.path.abspath(
|
|
47
|
-
os.path.join(os.path.dirname(__file__), "instructions.jinja2")
|
|
48
|
-
)
|
|
49
|
-
self._load_llm_instructions(jinja_template=f"file://{instructions_filepath}")
|
|
50
|
-
|
|
51
|
-
def prerequisites_callable(self, config: dict[str, Any]) -> Tuple[bool, str]:
|
|
52
|
-
if not config:
|
|
53
|
-
return False, "Missing config credentials."
|
|
54
|
-
|
|
55
|
-
try:
|
|
56
|
-
self.config: Dict = ServiceNowConfig(**config).model_dump()
|
|
57
|
-
self._session.headers.update(
|
|
58
|
-
{
|
|
59
|
-
"x-sn-apikey": self.config.get("api_key"),
|
|
60
|
-
}
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
url = f"https://{self.config.get('instance')}.service-now.com/api/now/v2/table/change_request"
|
|
64
|
-
response = self._session.get(url=url, params={"sysparm_limit": 1})
|
|
65
|
-
|
|
66
|
-
return response.ok, ""
|
|
67
|
-
except Exception as e:
|
|
68
|
-
logging.exception(
|
|
69
|
-
"Invalid ServiceNow config. Failed to set up ServiceNow toolset"
|
|
70
|
-
)
|
|
71
|
-
return False, f"Invalid ServiceNow config {e}"
|
|
72
|
-
|
|
73
|
-
def get_example_config(self) -> Dict[str, Any]:
|
|
74
|
-
example_config = ServiceNowConfig(
|
|
75
|
-
api_key="now_xxxxxxxxxxxxxxxx", instance="dev12345"
|
|
76
|
-
)
|
|
77
|
-
return example_config.model_dump()
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class ServiceNowBaseTool(Tool):
|
|
81
|
-
toolset: ServiceNowToolset
|
|
82
|
-
|
|
83
|
-
def return_result(
|
|
84
|
-
self, response: requests.Response, params: Any, field: str = "result"
|
|
85
|
-
) -> StructuredToolResult:
|
|
86
|
-
response.raise_for_status()
|
|
87
|
-
res = response.json()
|
|
88
|
-
return StructuredToolResult(
|
|
89
|
-
status=ToolResultStatus.SUCCESS
|
|
90
|
-
if res.get(field, [])
|
|
91
|
-
else ToolResultStatus.NO_DATA,
|
|
92
|
-
data=res,
|
|
93
|
-
params=params,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
def get_parameterized_one_liner(self, params) -> str:
|
|
97
|
-
# Default implementation - will be overridden by subclasses
|
|
98
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: ServiceNow {self.name} {params}"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class ReturnChangesInTimerange(ServiceNowBaseTool):
|
|
102
|
-
name: str = "servicenow_return_changes_in_timerange"
|
|
103
|
-
description: str = "Returns all changes requests from a specific time range. These changes tickets can apply to all components. default to changes from the last 1 hour."
|
|
104
|
-
parameters: Dict[str, ToolParameter] = {
|
|
105
|
-
"start": ToolParameter(
|
|
106
|
-
description=standard_start_datetime_tool_param_description(
|
|
107
|
-
DEFAULT_TIME_SPAN_SECONDS
|
|
108
|
-
),
|
|
109
|
-
type="string",
|
|
110
|
-
required=False,
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
def get_parameterized_one_liner(self, params) -> str:
|
|
115
|
-
start = params.get("start", "last hour")
|
|
116
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Change Requests ({start})"
|
|
117
|
-
|
|
118
|
-
def _invoke(
|
|
119
|
-
self, params: dict, user_approved: bool = False
|
|
120
|
-
) -> StructuredToolResult:
|
|
121
|
-
parsed_params = {}
|
|
122
|
-
try:
|
|
123
|
-
(start, _) = process_timestamps_to_rfc3339(
|
|
124
|
-
start_timestamp=params.get("start"),
|
|
125
|
-
end_timestamp=None,
|
|
126
|
-
default_time_span_seconds=DEFAULT_TIME_SPAN_SECONDS,
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
url = f"https://{self.toolset.config.get('instance')}.service-now.com/api/now/v2/table/change_request"
|
|
130
|
-
parsed_params.update(
|
|
131
|
-
{
|
|
132
|
-
"sysparm_fields": "sys_id,number,short_description,type,active,sys_updated_on"
|
|
133
|
-
}
|
|
134
|
-
)
|
|
135
|
-
parsed_params.update({"sysparm_query": f"sys_updated_on>={start}"})
|
|
136
|
-
|
|
137
|
-
response = self.toolset._session.get(url=url, params=parsed_params)
|
|
138
|
-
return self.return_result(response, parsed_params)
|
|
139
|
-
except Exception as e:
|
|
140
|
-
logging.exception(self.get_parameterized_one_liner(params))
|
|
141
|
-
return StructuredToolResult(
|
|
142
|
-
status=ToolResultStatus.ERROR,
|
|
143
|
-
data=f"Exception {self.name}: {str(e)}",
|
|
144
|
-
params=params,
|
|
145
|
-
)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
class ReturnChange(ServiceNowBaseTool):
|
|
149
|
-
name: str = "servicenow_return_change_details"
|
|
150
|
-
description: str = "Returns detailed information for one specific ServiceNow change"
|
|
151
|
-
parameters: Dict[str, ToolParameter] = {
|
|
152
|
-
"sys_id": ToolParameter(
|
|
153
|
-
description="The unique identifier of the change. Use servicenow_return_changes_in_timerange tool to fetch list of changes and use 'sys_id' for further information",
|
|
154
|
-
type="string",
|
|
155
|
-
required=True,
|
|
156
|
-
)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
def get_parameterized_one_liner(self, params) -> str:
|
|
160
|
-
sys_id = params.get("sys_id", "")
|
|
161
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Change Details ({sys_id})"
|
|
162
|
-
|
|
163
|
-
def _invoke(
|
|
164
|
-
self, params: dict, user_approved: bool = False
|
|
165
|
-
) -> StructuredToolResult:
|
|
166
|
-
try:
|
|
167
|
-
url = "https://{instance}.service-now.com/api/now/v2/table/change_request/{sys_id}".format(
|
|
168
|
-
instance=self.toolset.config.get("instance"),
|
|
169
|
-
sys_id=params.get("sys_id"),
|
|
170
|
-
)
|
|
171
|
-
response = self.toolset._session.get(url=url)
|
|
172
|
-
return self.return_result(response, params)
|
|
173
|
-
except Exception as e:
|
|
174
|
-
logging.exception(self.get_parameterized_one_liner(params))
|
|
175
|
-
return StructuredToolResult(
|
|
176
|
-
status=ToolResultStatus.ERROR,
|
|
177
|
-
data=f"Exception {self.name}: {str(e)}",
|
|
178
|
-
params=params,
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
class ReturnChangesWithKeyword(ServiceNowBaseTool):
|
|
183
|
-
name: str = "servicenow_return_changes_with_keyword"
|
|
184
|
-
description: str = "Returns all changes requests where a keyword is contained in the description. good for finding changes related to a key, workload or any object."
|
|
185
|
-
parameters: Dict[str, ToolParameter] = {
|
|
186
|
-
"keyword": ToolParameter(
|
|
187
|
-
description="key, workload or object name. Keyword that will filter service now changes that are related to this keyword or object.",
|
|
188
|
-
type="string",
|
|
189
|
-
required=True,
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
def get_parameterized_one_liner(self, params) -> str:
|
|
194
|
-
keyword = params.get("keyword", "")
|
|
195
|
-
return f"{toolset_name_for_one_liner(self.toolset.name)}: Search Changes ({keyword})"
|
|
196
|
-
|
|
197
|
-
def _invoke(
|
|
198
|
-
self, params: dict, user_approved: bool = False
|
|
199
|
-
) -> StructuredToolResult:
|
|
200
|
-
parsed_params = {}
|
|
201
|
-
try:
|
|
202
|
-
url = f"https://{self.toolset.config.get('instance')}.service-now.com/api/now/v2/table/change_request"
|
|
203
|
-
parsed_params.update(
|
|
204
|
-
{
|
|
205
|
-
"sysparm_fields": "sys_id,number,short_description,type,active,sys_updated_on"
|
|
206
|
-
}
|
|
207
|
-
)
|
|
208
|
-
parsed_params.update(
|
|
209
|
-
{"sysparm_query": f"short_descriptionLIKE{params.get('keyword')}"}
|
|
210
|
-
)
|
|
211
|
-
response = self.toolset._session.get(url=url, params=parsed_params)
|
|
212
|
-
return self.return_result(response, parsed_params)
|
|
213
|
-
except Exception as e:
|
|
214
|
-
logging.exception(self.get_parameterized_one_liner(params))
|
|
215
|
-
return StructuredToolResult(
|
|
216
|
-
status=ToolResultStatus.ERROR,
|
|
217
|
-
data=f"Exception {self.name}: {str(e)}",
|
|
218
|
-
params=params,
|
|
219
|
-
)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 Robusta Dev Ltd
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
File without changes
|
|
File without changes
|