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,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
- )
@@ -1,6 +0,0 @@
1
- import random
2
- import string
3
-
4
-
5
- def generate_random_key():
6
- return "".join(random.choices(string.ascii_letters + string.digits, k=4))
@@ -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.