holmesgpt 0.13.2__py3-none-any.whl → 0.18.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- holmes/__init__.py +3 -5
- holmes/clients/robusta_client.py +20 -6
- holmes/common/env_vars.py +58 -3
- holmes/common/openshift.py +1 -1
- holmes/config.py +123 -148
- holmes/core/conversations.py +71 -15
- holmes/core/feedback.py +191 -0
- holmes/core/investigation.py +31 -39
- holmes/core/investigation_structured_output.py +3 -3
- holmes/core/issue.py +1 -1
- holmes/core/llm.py +508 -88
- holmes/core/models.py +108 -4
- holmes/core/openai_formatting.py +14 -1
- holmes/core/prompt.py +48 -3
- holmes/core/runbooks.py +1 -0
- holmes/core/safeguards.py +8 -6
- holmes/core/supabase_dal.py +295 -100
- holmes/core/tool_calling_llm.py +489 -428
- holmes/core/tools.py +325 -56
- holmes/core/tools_utils/token_counting.py +21 -0
- holmes/core/tools_utils/tool_context_window_limiter.py +40 -0
- holmes/core/tools_utils/tool_executor.py +0 -13
- holmes/core/tools_utils/toolset_utils.py +1 -0
- holmes/core/toolset_manager.py +191 -5
- holmes/core/tracing.py +19 -3
- holmes/core/transformers/__init__.py +23 -0
- holmes/core/transformers/base.py +63 -0
- holmes/core/transformers/llm_summarize.py +175 -0
- holmes/core/transformers/registry.py +123 -0
- holmes/core/transformers/transformer.py +32 -0
- holmes/core/truncation/compaction.py +94 -0
- holmes/core/truncation/dal_truncation_utils.py +23 -0
- holmes/core/truncation/input_context_window_limiter.py +219 -0
- holmes/interactive.py +228 -31
- holmes/main.py +23 -40
- holmes/plugins/interfaces.py +2 -1
- holmes/plugins/prompts/__init__.py +2 -1
- holmes/plugins/prompts/_fetch_logs.jinja2 +31 -6
- holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
- holmes/plugins/prompts/_runbook_instructions.jinja2 +24 -12
- holmes/plugins/prompts/base_user_prompt.jinja2 +7 -0
- holmes/plugins/prompts/conversation_history_compaction.jinja2 +89 -0
- holmes/plugins/prompts/generic_ask.jinja2 +0 -4
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +0 -1
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +0 -1
- holmes/plugins/prompts/generic_investigation.jinja2 +0 -1
- holmes/plugins/prompts/investigation_procedure.jinja2 +50 -1
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +0 -1
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +0 -1
- holmes/plugins/runbooks/__init__.py +145 -17
- holmes/plugins/runbooks/catalog.json +2 -0
- holmes/plugins/sources/github/__init__.py +4 -2
- holmes/plugins/sources/prometheus/models.py +1 -0
- holmes/plugins/toolsets/__init__.py +44 -27
- holmes/plugins/toolsets/aks-node-health.yaml +46 -0
- holmes/plugins/toolsets/aks.yaml +64 -0
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +38 -47
- holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +3 -2
- holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +2 -1
- holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +3 -2
- holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +3 -1
- holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +3 -1
- holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +12 -13
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +11 -11
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +11 -9
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +15 -15
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/utils.py +0 -32
- holmes/plugins/toolsets/bash/argocd/__init__.py +3 -3
- holmes/plugins/toolsets/bash/aws/__init__.py +4 -4
- holmes/plugins/toolsets/bash/azure/__init__.py +4 -4
- holmes/plugins/toolsets/bash/bash_toolset.py +11 -15
- holmes/plugins/toolsets/bash/common/bash.py +23 -13
- holmes/plugins/toolsets/bash/common/bash_command.py +1 -1
- holmes/plugins/toolsets/bash/common/stringify.py +1 -1
- holmes/plugins/toolsets/bash/kubectl/__init__.py +2 -1
- holmes/plugins/toolsets/bash/kubectl/constants.py +0 -1
- holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +3 -4
- holmes/plugins/toolsets/bash/parse_command.py +12 -13
- holmes/plugins/toolsets/cilium.yaml +284 -0
- holmes/plugins/toolsets/connectivity_check.py +124 -0
- holmes/plugins/toolsets/coralogix/api.py +132 -119
- holmes/plugins/toolsets/coralogix/coralogix.jinja2 +14 -0
- holmes/plugins/toolsets/coralogix/toolset_coralogix.py +219 -0
- holmes/plugins/toolsets/coralogix/utils.py +15 -79
- holmes/plugins/toolsets/datadog/datadog_api.py +525 -26
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +55 -11
- holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +3 -3
- holmes/plugins/toolsets/datadog/datadog_models.py +59 -0
- holmes/plugins/toolsets/datadog/datadog_url_utils.py +213 -0
- holmes/plugins/toolsets/datadog/instructions_datadog_traces.jinja2 +165 -28
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +417 -241
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +234 -214
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +167 -79
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +374 -363
- holmes/plugins/toolsets/elasticsearch/__init__.py +6 -0
- holmes/plugins/toolsets/elasticsearch/elasticsearch.py +834 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_ppl_query_docs.jinja2 +1616 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_query_assist.py +78 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_query_assist_instructions.jinja2 +223 -0
- holmes/plugins/toolsets/git.py +54 -50
- holmes/plugins/toolsets/grafana/base_grafana_toolset.py +16 -4
- holmes/plugins/toolsets/grafana/common.py +13 -29
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +455 -0
- holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +25 -0
- holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +191 -0
- holmes/plugins/toolsets/grafana/loki_api.py +4 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +293 -89
- holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +49 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +820 -292
- holmes/plugins/toolsets/grafana/trace_parser.py +4 -3
- holmes/plugins/toolsets/internet/internet.py +15 -16
- holmes/plugins/toolsets/internet/notion.py +9 -11
- holmes/plugins/toolsets/investigator/core_investigation.py +44 -36
- holmes/plugins/toolsets/investigator/model.py +3 -1
- holmes/plugins/toolsets/json_filter_mixin.py +134 -0
- holmes/plugins/toolsets/kafka.py +36 -42
- holmes/plugins/toolsets/kubernetes.yaml +317 -113
- holmes/plugins/toolsets/kubernetes_logs.py +9 -9
- holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
- holmes/plugins/toolsets/logging_utils/logging_api.py +94 -8
- holmes/plugins/toolsets/mcp/toolset_mcp.py +218 -64
- holmes/plugins/toolsets/newrelic/new_relic_api.py +165 -0
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +65 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +320 -0
- holmes/plugins/toolsets/openshift.yaml +283 -0
- holmes/plugins/toolsets/prometheus/prometheus.py +1202 -421
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +54 -5
- holmes/plugins/toolsets/prometheus/utils.py +28 -0
- holmes/plugins/toolsets/rabbitmq/api.py +23 -4
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +13 -14
- holmes/plugins/toolsets/robusta/robusta.py +239 -68
- holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +157 -27
- holmes/plugins/toolsets/service_discovery.py +1 -1
- holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
- holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
- holmes/plugins/toolsets/utils.py +88 -0
- holmes/utils/config_utils.py +91 -0
- holmes/utils/connection_utils.py +31 -0
- holmes/utils/console/result.py +10 -0
- holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
- holmes/utils/env.py +7 -0
- holmes/utils/file_utils.py +2 -1
- holmes/utils/global_instructions.py +60 -11
- holmes/utils/holmes_status.py +6 -4
- holmes/utils/holmes_sync_toolsets.py +0 -2
- holmes/utils/krr_utils.py +188 -0
- holmes/utils/log.py +15 -0
- holmes/utils/markdown_utils.py +2 -3
- holmes/utils/memory_limit.py +58 -0
- holmes/utils/sentry_helper.py +64 -0
- holmes/utils/stream.py +69 -8
- holmes/utils/tags.py +4 -3
- holmes/version.py +37 -15
- holmesgpt-0.18.4.dist-info/LICENSE +178 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/METADATA +35 -31
- holmesgpt-0.18.4.dist-info/RECORD +258 -0
- holmes/core/performance_timing.py +0 -72
- holmes/plugins/toolsets/aws.yaml +0 -80
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +0 -112
- holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +0 -310
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +0 -739
- holmes/plugins/toolsets/grafana/grafana_api.py +0 -42
- holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
- holmes/plugins/toolsets/newrelic.py +0 -231
- holmes/plugins/toolsets/opensearch/opensearch.py +0 -257
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +0 -161
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +0 -218
- holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +0 -12
- holmes/plugins/toolsets/opensearch/opensearch_utils.py +0 -166
- holmes/plugins/toolsets/servicenow/install.md +0 -37
- holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
- holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
- holmes/utils/keygen_utils.py +0 -6
- holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
- holmesgpt-0.13.2.dist-info/RECORD +0 -234
- /holmes/plugins/toolsets/{opensearch → newrelic}/__init__.py +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/WHEEL +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/entry_points.txt +0 -0
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import logging
|
|
3
|
-
from typing import Optional, Any, cast
|
|
4
|
-
from urllib.parse import urljoin
|
|
5
|
-
|
|
6
|
-
from pydantic import BaseModel
|
|
7
|
-
import requests # type: ignore
|
|
8
|
-
|
|
9
|
-
from holmes.core.tools import Toolset
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class OpenSearchLoggingLabelsConfig(BaseModel):
|
|
13
|
-
pod: str = "kubernetes.pod_name"
|
|
14
|
-
namespace: str = "kubernetes.namespace_name"
|
|
15
|
-
timestamp: str = "@timestamp"
|
|
16
|
-
message: str = "message"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class BaseOpenSearchConfig(BaseModel):
|
|
20
|
-
opensearch_url: str
|
|
21
|
-
index_pattern: str
|
|
22
|
-
opensearch_auth_header: Optional[str] = None
|
|
23
|
-
# Setting to None will disable the cache
|
|
24
|
-
fields_ttl_seconds: Optional[int] = 14400 # 4 hours
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class OpenSearchLoggingConfig(BaseOpenSearchConfig):
|
|
28
|
-
labels: OpenSearchLoggingLabelsConfig = OpenSearchLoggingLabelsConfig()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def add_auth_header(auth_header: Optional[str]) -> dict[str, Any]:
|
|
32
|
-
results = {}
|
|
33
|
-
if auth_header:
|
|
34
|
-
results["Authorization"] = auth_header
|
|
35
|
-
return results
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def get_search_url(config: BaseOpenSearchConfig) -> str:
|
|
39
|
-
return urljoin(config.opensearch_url, f"/{config.index_pattern}/_search")
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def opensearch_health_check(config: BaseOpenSearchConfig) -> tuple[bool, str]:
|
|
43
|
-
url = get_search_url(config)
|
|
44
|
-
try:
|
|
45
|
-
headers = {"Content-Type": "application/json"}
|
|
46
|
-
headers.update(add_auth_header(config.opensearch_auth_header))
|
|
47
|
-
health_response = requests.get(
|
|
48
|
-
url=url,
|
|
49
|
-
verify=True,
|
|
50
|
-
data=json.dumps({"size": 1}),
|
|
51
|
-
headers=headers,
|
|
52
|
-
)
|
|
53
|
-
health_response.raise_for_status()
|
|
54
|
-
return True, ""
|
|
55
|
-
except Exception as e:
|
|
56
|
-
logging.info("Failed to initialize opensearch toolset", exc_info=True)
|
|
57
|
-
return False, f"Failed to initialize opensearch toolset. url={url}. {str(e)}"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def format_log_to_json(log_line: Any) -> str:
|
|
61
|
-
try:
|
|
62
|
-
return json.dumps(log_line)
|
|
63
|
-
except Exception:
|
|
64
|
-
# Handle potential serialization errors (e.g., non-serializable objects)
|
|
65
|
-
return str(log_line)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def format_logs(
|
|
69
|
-
logs: list[dict[str, Any]],
|
|
70
|
-
config: OpenSearchLoggingConfig,
|
|
71
|
-
) -> str:
|
|
72
|
-
if not logs or not isinstance(logs, list):
|
|
73
|
-
return ""
|
|
74
|
-
|
|
75
|
-
# Get field names from config or use defaults
|
|
76
|
-
message_field = config.labels.message
|
|
77
|
-
|
|
78
|
-
formatted_lines = []
|
|
79
|
-
|
|
80
|
-
for hit in logs:
|
|
81
|
-
# Ensure hit is a dictionary and has _source
|
|
82
|
-
if not isinstance(hit, dict):
|
|
83
|
-
formatted_lines.append(format_log_to_json(hit))
|
|
84
|
-
continue
|
|
85
|
-
source = hit.get("_source")
|
|
86
|
-
if not isinstance(source, dict):
|
|
87
|
-
formatted_lines.append(format_log_to_json(hit))
|
|
88
|
-
continue
|
|
89
|
-
|
|
90
|
-
message = source.get(message_field, None)
|
|
91
|
-
|
|
92
|
-
if message and not isinstance(message, str):
|
|
93
|
-
message = str(message) # Convert non-strings
|
|
94
|
-
|
|
95
|
-
if message:
|
|
96
|
-
formatted_lines.append(message)
|
|
97
|
-
else:
|
|
98
|
-
# fallback displaying the logs line as-is
|
|
99
|
-
formatted_lines.append(format_log_to_json(hit))
|
|
100
|
-
|
|
101
|
-
return "\n".join(formatted_lines)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def build_query(
|
|
105
|
-
config: OpenSearchLoggingConfig,
|
|
106
|
-
namespace: str,
|
|
107
|
-
pod_name: str,
|
|
108
|
-
start_time: Optional[str] = None,
|
|
109
|
-
end_time: Optional[str] = None,
|
|
110
|
-
match: Optional[str] = None,
|
|
111
|
-
limit: Optional[int] = None,
|
|
112
|
-
) -> dict[str, Any]:
|
|
113
|
-
size = limit if limit else 5000
|
|
114
|
-
|
|
115
|
-
pod_field = config.labels.pod
|
|
116
|
-
namespace_field = config.labels.namespace
|
|
117
|
-
timestamp_field = config.labels.timestamp
|
|
118
|
-
message_field = config.labels.message
|
|
119
|
-
|
|
120
|
-
must_constraints: list[dict] = [
|
|
121
|
-
{"term": {f"{pod_field}.keyword": pod_name}},
|
|
122
|
-
{"term": {f"{namespace_field}.keyword": namespace}},
|
|
123
|
-
]
|
|
124
|
-
|
|
125
|
-
query = {
|
|
126
|
-
"size": size,
|
|
127
|
-
"sort": [{timestamp_field: {"order": "asc"}}],
|
|
128
|
-
"query": {"bool": {"must": must_constraints}},
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
# Add timestamp range if provided
|
|
132
|
-
if start_time or end_time:
|
|
133
|
-
range_query: dict = {"range": {timestamp_field: {}}}
|
|
134
|
-
if start_time:
|
|
135
|
-
range_query["range"][timestamp_field]["gte"] = start_time
|
|
136
|
-
if end_time:
|
|
137
|
-
range_query["range"][timestamp_field]["lte"] = end_time
|
|
138
|
-
|
|
139
|
-
must_constraints.append(range_query)
|
|
140
|
-
|
|
141
|
-
# Add message filter if provided
|
|
142
|
-
if match:
|
|
143
|
-
must_constraints.append({"match_phrase": {message_field: match}})
|
|
144
|
-
|
|
145
|
-
return query
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
class BaseOpenSearchToolset(Toolset):
|
|
149
|
-
def get_example_config(self) -> dict[str, Any]:
|
|
150
|
-
example_config = BaseOpenSearchConfig(
|
|
151
|
-
opensearch_url="YOUR OPENSEARCH URL",
|
|
152
|
-
index_pattern="YOUR OPENSEARCH INDEX NAME",
|
|
153
|
-
opensearch_auth_header="YOUR OPENSEARCH AUTH HEADER (Optional)",
|
|
154
|
-
)
|
|
155
|
-
return example_config.model_dump()
|
|
156
|
-
|
|
157
|
-
def prerequisites_callable(self, config: dict[str, Any]) -> tuple[bool, str]:
|
|
158
|
-
if not config:
|
|
159
|
-
return False, "Missing opensearch traces URL. Check your config"
|
|
160
|
-
else:
|
|
161
|
-
self.config = BaseOpenSearchConfig(**config)
|
|
162
|
-
return opensearch_health_check(self.config)
|
|
163
|
-
|
|
164
|
-
@property
|
|
165
|
-
def opensearch_config(self) -> BaseOpenSearchConfig:
|
|
166
|
-
return cast(BaseOpenSearchConfig, self.config)
|
|
@@ -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
|
-
)
|
holmes/utils/keygen_utils.py
DELETED
|
@@ -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.
|