holmesgpt 0.11.5__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.

Potentially problematic release.


This version of holmesgpt might be problematic. Click here for more details.

Files changed (183) hide show
  1. holmes/.git_archival.json +7 -0
  2. holmes/__init__.py +76 -0
  3. holmes/__init__.py.bak +76 -0
  4. holmes/clients/robusta_client.py +24 -0
  5. holmes/common/env_vars.py +47 -0
  6. holmes/config.py +526 -0
  7. holmes/core/__init__.py +0 -0
  8. holmes/core/conversations.py +578 -0
  9. holmes/core/investigation.py +152 -0
  10. holmes/core/investigation_structured_output.py +264 -0
  11. holmes/core/issue.py +54 -0
  12. holmes/core/llm.py +250 -0
  13. holmes/core/models.py +157 -0
  14. holmes/core/openai_formatting.py +51 -0
  15. holmes/core/performance_timing.py +72 -0
  16. holmes/core/prompt.py +42 -0
  17. holmes/core/resource_instruction.py +17 -0
  18. holmes/core/runbooks.py +26 -0
  19. holmes/core/safeguards.py +120 -0
  20. holmes/core/supabase_dal.py +540 -0
  21. holmes/core/tool_calling_llm.py +798 -0
  22. holmes/core/tools.py +566 -0
  23. holmes/core/tools_utils/__init__.py +0 -0
  24. holmes/core/tools_utils/tool_executor.py +65 -0
  25. holmes/core/tools_utils/toolset_utils.py +52 -0
  26. holmes/core/toolset_manager.py +418 -0
  27. holmes/interactive.py +229 -0
  28. holmes/main.py +1041 -0
  29. holmes/plugins/__init__.py +0 -0
  30. holmes/plugins/destinations/__init__.py +6 -0
  31. holmes/plugins/destinations/slack/__init__.py +2 -0
  32. holmes/plugins/destinations/slack/plugin.py +163 -0
  33. holmes/plugins/interfaces.py +32 -0
  34. holmes/plugins/prompts/__init__.py +48 -0
  35. holmes/plugins/prompts/_current_date_time.jinja2 +1 -0
  36. holmes/plugins/prompts/_default_log_prompt.jinja2 +11 -0
  37. holmes/plugins/prompts/_fetch_logs.jinja2 +36 -0
  38. holmes/plugins/prompts/_general_instructions.jinja2 +86 -0
  39. holmes/plugins/prompts/_global_instructions.jinja2 +12 -0
  40. holmes/plugins/prompts/_runbook_instructions.jinja2 +13 -0
  41. holmes/plugins/prompts/_toolsets_instructions.jinja2 +56 -0
  42. holmes/plugins/prompts/generic_ask.jinja2 +36 -0
  43. holmes/plugins/prompts/generic_ask_conversation.jinja2 +32 -0
  44. holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +50 -0
  45. holmes/plugins/prompts/generic_investigation.jinja2 +42 -0
  46. holmes/plugins/prompts/generic_post_processing.jinja2 +13 -0
  47. holmes/plugins/prompts/generic_ticket.jinja2 +12 -0
  48. holmes/plugins/prompts/investigation_output_format.jinja2 +32 -0
  49. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +84 -0
  50. holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +39 -0
  51. holmes/plugins/runbooks/README.md +22 -0
  52. holmes/plugins/runbooks/__init__.py +100 -0
  53. holmes/plugins/runbooks/catalog.json +14 -0
  54. holmes/plugins/runbooks/jira.yaml +12 -0
  55. holmes/plugins/runbooks/kube-prometheus-stack.yaml +10 -0
  56. holmes/plugins/runbooks/networking/dns_troubleshooting_instructions.md +66 -0
  57. holmes/plugins/runbooks/upgrade/upgrade_troubleshooting_instructions.md +44 -0
  58. holmes/plugins/sources/github/__init__.py +77 -0
  59. holmes/plugins/sources/jira/__init__.py +123 -0
  60. holmes/plugins/sources/opsgenie/__init__.py +93 -0
  61. holmes/plugins/sources/pagerduty/__init__.py +147 -0
  62. holmes/plugins/sources/prometheus/__init__.py +0 -0
  63. holmes/plugins/sources/prometheus/models.py +104 -0
  64. holmes/plugins/sources/prometheus/plugin.py +154 -0
  65. holmes/plugins/toolsets/__init__.py +171 -0
  66. holmes/plugins/toolsets/aks-node-health.yaml +65 -0
  67. holmes/plugins/toolsets/aks.yaml +86 -0
  68. holmes/plugins/toolsets/argocd.yaml +70 -0
  69. holmes/plugins/toolsets/atlas_mongodb/instructions.jinja2 +8 -0
  70. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +307 -0
  71. holmes/plugins/toolsets/aws.yaml +76 -0
  72. holmes/plugins/toolsets/azure_sql/__init__.py +0 -0
  73. holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +600 -0
  74. holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +309 -0
  75. holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +445 -0
  76. holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +251 -0
  77. holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +317 -0
  78. holmes/plugins/toolsets/azure_sql/azure_base_toolset.py +55 -0
  79. holmes/plugins/toolsets/azure_sql/azure_sql_instructions.jinja2 +137 -0
  80. holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +183 -0
  81. holmes/plugins/toolsets/azure_sql/install.md +66 -0
  82. holmes/plugins/toolsets/azure_sql/tools/__init__.py +1 -0
  83. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +324 -0
  84. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +243 -0
  85. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +205 -0
  86. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +249 -0
  87. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +373 -0
  88. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +237 -0
  89. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +172 -0
  90. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +170 -0
  91. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +188 -0
  92. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +180 -0
  93. holmes/plugins/toolsets/azure_sql/utils.py +83 -0
  94. holmes/plugins/toolsets/bash/__init__.py +0 -0
  95. holmes/plugins/toolsets/bash/bash_instructions.jinja2 +14 -0
  96. holmes/plugins/toolsets/bash/bash_toolset.py +208 -0
  97. holmes/plugins/toolsets/bash/common/bash.py +52 -0
  98. holmes/plugins/toolsets/bash/common/config.py +14 -0
  99. holmes/plugins/toolsets/bash/common/stringify.py +25 -0
  100. holmes/plugins/toolsets/bash/common/validators.py +24 -0
  101. holmes/plugins/toolsets/bash/grep/__init__.py +52 -0
  102. holmes/plugins/toolsets/bash/kubectl/__init__.py +100 -0
  103. holmes/plugins/toolsets/bash/kubectl/constants.py +96 -0
  104. holmes/plugins/toolsets/bash/kubectl/kubectl_describe.py +66 -0
  105. holmes/plugins/toolsets/bash/kubectl/kubectl_events.py +88 -0
  106. holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +108 -0
  107. holmes/plugins/toolsets/bash/kubectl/kubectl_logs.py +20 -0
  108. holmes/plugins/toolsets/bash/kubectl/kubectl_run.py +46 -0
  109. holmes/plugins/toolsets/bash/kubectl/kubectl_top.py +81 -0
  110. holmes/plugins/toolsets/bash/parse_command.py +103 -0
  111. holmes/plugins/toolsets/confluence.yaml +19 -0
  112. holmes/plugins/toolsets/consts.py +5 -0
  113. holmes/plugins/toolsets/coralogix/api.py +158 -0
  114. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +103 -0
  115. holmes/plugins/toolsets/coralogix/utils.py +181 -0
  116. holmes/plugins/toolsets/datadog.py +153 -0
  117. holmes/plugins/toolsets/docker.yaml +46 -0
  118. holmes/plugins/toolsets/git.py +756 -0
  119. holmes/plugins/toolsets/grafana/__init__.py +0 -0
  120. holmes/plugins/toolsets/grafana/base_grafana_toolset.py +54 -0
  121. holmes/plugins/toolsets/grafana/common.py +68 -0
  122. holmes/plugins/toolsets/grafana/grafana_api.py +31 -0
  123. holmes/plugins/toolsets/grafana/loki_api.py +89 -0
  124. holmes/plugins/toolsets/grafana/tempo_api.py +124 -0
  125. holmes/plugins/toolsets/grafana/toolset_grafana.py +102 -0
  126. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +102 -0
  127. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +10 -0
  128. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +299 -0
  129. holmes/plugins/toolsets/grafana/trace_parser.py +195 -0
  130. holmes/plugins/toolsets/helm.yaml +42 -0
  131. holmes/plugins/toolsets/internet/internet.py +275 -0
  132. holmes/plugins/toolsets/internet/notion.py +137 -0
  133. holmes/plugins/toolsets/kafka.py +638 -0
  134. holmes/plugins/toolsets/kubernetes.yaml +255 -0
  135. holmes/plugins/toolsets/kubernetes_logs.py +426 -0
  136. holmes/plugins/toolsets/kubernetes_logs.yaml +42 -0
  137. holmes/plugins/toolsets/logging_utils/__init__.py +0 -0
  138. holmes/plugins/toolsets/logging_utils/logging_api.py +217 -0
  139. holmes/plugins/toolsets/logging_utils/types.py +0 -0
  140. holmes/plugins/toolsets/mcp/toolset_mcp.py +135 -0
  141. holmes/plugins/toolsets/newrelic.py +222 -0
  142. holmes/plugins/toolsets/opensearch/__init__.py +0 -0
  143. holmes/plugins/toolsets/opensearch/opensearch.py +245 -0
  144. holmes/plugins/toolsets/opensearch/opensearch_logs.py +151 -0
  145. holmes/plugins/toolsets/opensearch/opensearch_traces.py +211 -0
  146. holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +12 -0
  147. holmes/plugins/toolsets/opensearch/opensearch_utils.py +166 -0
  148. holmes/plugins/toolsets/prometheus/prometheus.py +818 -0
  149. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +38 -0
  150. holmes/plugins/toolsets/rabbitmq/api.py +398 -0
  151. holmes/plugins/toolsets/rabbitmq/rabbitmq_instructions.jinja2 +37 -0
  152. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +222 -0
  153. holmes/plugins/toolsets/robusta/__init__.py +0 -0
  154. holmes/plugins/toolsets/robusta/robusta.py +235 -0
  155. holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +24 -0
  156. holmes/plugins/toolsets/runbook/__init__.py +0 -0
  157. holmes/plugins/toolsets/runbook/runbook_fetcher.py +78 -0
  158. holmes/plugins/toolsets/service_discovery.py +92 -0
  159. holmes/plugins/toolsets/servicenow/install.md +37 -0
  160. holmes/plugins/toolsets/servicenow/instructions.jinja2 +3 -0
  161. holmes/plugins/toolsets/servicenow/servicenow.py +198 -0
  162. holmes/plugins/toolsets/slab.yaml +20 -0
  163. holmes/plugins/toolsets/utils.py +137 -0
  164. holmes/plugins/utils.py +14 -0
  165. holmes/utils/__init__.py +0 -0
  166. holmes/utils/cache.py +84 -0
  167. holmes/utils/cert_utils.py +40 -0
  168. holmes/utils/default_toolset_installation_guide.jinja2 +44 -0
  169. holmes/utils/definitions.py +13 -0
  170. holmes/utils/env.py +53 -0
  171. holmes/utils/file_utils.py +56 -0
  172. holmes/utils/global_instructions.py +20 -0
  173. holmes/utils/holmes_status.py +22 -0
  174. holmes/utils/holmes_sync_toolsets.py +80 -0
  175. holmes/utils/markdown_utils.py +55 -0
  176. holmes/utils/pydantic_utils.py +54 -0
  177. holmes/utils/robusta.py +10 -0
  178. holmes/utils/tags.py +97 -0
  179. holmesgpt-0.11.5.dist-info/LICENSE.txt +21 -0
  180. holmesgpt-0.11.5.dist-info/METADATA +400 -0
  181. holmesgpt-0.11.5.dist-info/RECORD +183 -0
  182. holmesgpt-0.11.5.dist-info/WHEEL +4 -0
  183. holmesgpt-0.11.5.dist-info/entry_points.txt +3 -0
holmes/config.py ADDED
@@ -0,0 +1,526 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ import os.path
5
+ from enum import Enum
6
+ from pathlib import Path
7
+ from typing import Any, List, Optional, Union
8
+
9
+ import yaml # type: ignore
10
+ from pydantic import BaseModel, ConfigDict, FilePath, SecretStr
11
+
12
+ from holmes import get_version # type: ignore
13
+ from holmes.clients.robusta_client import HolmesInfo, fetch_holmes_info
14
+ from holmes.common.env_vars import ROBUSTA_AI, ROBUSTA_API_ENDPOINT, ROBUSTA_CONFIG_PATH
15
+ from holmes.core.llm import LLM, DefaultLLM
16
+ from holmes.core.runbooks import RunbookManager
17
+ from holmes.core.supabase_dal import SupabaseDal
18
+ from holmes.core.tool_calling_llm import IssueInvestigator, ToolCallingLLM
19
+ from holmes.core.tools_utils.tool_executor import ToolExecutor
20
+ from holmes.core.toolset_manager import ToolsetManager
21
+ from holmes.plugins.destinations.slack import SlackDestination
22
+ from holmes.plugins.runbooks import (
23
+ RunbookCatalog,
24
+ load_builtin_runbooks,
25
+ load_runbook_catalog,
26
+ load_runbooks_from_file,
27
+ )
28
+ from holmes.plugins.sources.github import GitHubSource
29
+ from holmes.plugins.sources.jira import JiraServiceManagementSource, JiraSource
30
+ from holmes.plugins.sources.opsgenie import OpsGenieSource
31
+ from holmes.plugins.sources.pagerduty import PagerDutySource
32
+ from holmes.plugins.sources.prometheus.plugin import AlertManagerSource
33
+ from holmes.utils.definitions import RobustaConfig
34
+ from holmes.utils.env import replace_env_vars_values
35
+ from holmes.utils.file_utils import load_yaml_file
36
+ from holmes.utils.pydantic_utils import RobustaBaseConfig, load_model_from_file
37
+
38
+ DEFAULT_CONFIG_LOCATION = os.path.expanduser("~/.holmes/config.yaml")
39
+ MODEL_LIST_FILE_LOCATION = os.environ.get(
40
+ "MODEL_LIST_FILE_LOCATION", "/etc/holmes/config/model_list.yaml"
41
+ )
42
+
43
+
44
+ class SupportedTicketSources(str, Enum):
45
+ JIRA_SERVICE_MANAGEMENT = "jira-service-management"
46
+ PAGERDUTY = "pagerduty"
47
+
48
+
49
+ def is_old_toolset_config(
50
+ toolsets: Union[dict[str, dict[str, Any]], List[dict[str, Any]]],
51
+ ) -> bool:
52
+ # old config is a list of toolsets
53
+ if isinstance(toolsets, list):
54
+ return True
55
+ return False
56
+
57
+
58
+ def parse_models_file(path: str):
59
+ models = load_yaml_file(path, raise_error=False, warn_not_found=False)
60
+
61
+ for model, params in models.items():
62
+ params = replace_env_vars_values(params)
63
+
64
+ return models
65
+
66
+
67
+ class Config(RobustaBaseConfig):
68
+ api_key: Optional[SecretStr] = (
69
+ None # if None, read from OPENAI_API_KEY or AZURE_OPENAI_ENDPOINT env var
70
+ )
71
+ model: Optional[str] = "gpt-4o"
72
+ max_steps: int = 10
73
+ cluster_name: Optional[str] = None
74
+
75
+ alertmanager_url: Optional[str] = None
76
+ alertmanager_username: Optional[str] = None
77
+ alertmanager_password: Optional[str] = None
78
+ alertmanager_alertname: Optional[str] = None
79
+ alertmanager_label: Optional[List[str]] = []
80
+ alertmanager_file: Optional[FilePath] = None
81
+
82
+ jira_url: Optional[str] = None
83
+ jira_username: Optional[str] = None
84
+ jira_api_key: Optional[SecretStr] = None
85
+ jira_query: Optional[str] = ""
86
+
87
+ github_url: Optional[str] = None
88
+ github_owner: Optional[str] = None
89
+ github_pat: Optional[SecretStr] = None
90
+ github_repository: Optional[str] = None
91
+ github_query: str = ""
92
+
93
+ slack_token: Optional[SecretStr] = None
94
+ slack_channel: Optional[str] = None
95
+
96
+ pagerduty_api_key: Optional[SecretStr] = None
97
+ pagerduty_user_email: Optional[str] = None
98
+ pagerduty_incident_key: Optional[str] = None
99
+
100
+ opsgenie_api_key: Optional[SecretStr] = None
101
+ opsgenie_team_integration_key: Optional[SecretStr] = None
102
+ opsgenie_query: Optional[str] = None
103
+
104
+ custom_runbooks: List[FilePath] = []
105
+
106
+ # custom_toolsets is passed from config file, and be used to override built-in toolsets, provides 'stable' customized toolset.
107
+ # The status of custom toolsets can be cached.
108
+ custom_toolsets: Optional[List[FilePath]] = None
109
+ # custom_toolsets_from_cli is passed from CLI option `--custom-toolsets` as 'experimental' custom toolsets.
110
+ # The status of toolset here won't be cached, so the toolset from cli will always be loaded when specified in the CLI.
111
+ custom_toolsets_from_cli: Optional[List[FilePath]] = None
112
+
113
+ toolsets: Optional[dict[str, dict[str, Any]]] = None
114
+
115
+ _server_tool_executor: Optional[ToolExecutor] = None
116
+
117
+ _version: Optional[str] = None
118
+ _holmes_info: Optional[HolmesInfo] = None
119
+
120
+ _toolset_manager: Optional[ToolsetManager] = None
121
+
122
+ @property
123
+ def is_latest_version(self) -> bool:
124
+ if (
125
+ not self._holmes_info
126
+ or not self._holmes_info.latest_version
127
+ or not self._version
128
+ ):
129
+ # We couldn't resolve version, assume we are running the latest version
130
+ return True
131
+ if self._version.startswith("dev-"):
132
+ # dev versions are considered to be the latest version
133
+ return True
134
+
135
+ return self._version.startswith(self._holmes_info.latest_version)
136
+
137
+ @property
138
+ def toolset_manager(self) -> ToolsetManager:
139
+ if not self._toolset_manager:
140
+ self._toolset_manager = ToolsetManager(
141
+ toolsets=self.toolsets,
142
+ custom_toolsets=self.custom_toolsets,
143
+ custom_toolsets_from_cli=self.custom_toolsets_from_cli,
144
+ )
145
+ return self._toolset_manager
146
+
147
+ def model_post_init(self, __context: Any) -> None:
148
+ self._version = get_version()
149
+ self._holmes_info = fetch_holmes_info()
150
+ self._model_list = parse_models_file(MODEL_LIST_FILE_LOCATION)
151
+ if ROBUSTA_AI:
152
+ self._model_list["Robusta"] = {
153
+ "base_url": ROBUSTA_API_ENDPOINT,
154
+ }
155
+
156
+ def log_useful_info(self):
157
+ if self._model_list:
158
+ logging.info(f"loaded models: {list(self._model_list.keys())}")
159
+
160
+ if not self.is_latest_version and self._holmes_info:
161
+ logging.warning(
162
+ f"You are running version {self._version} of holmes, but the latest version is {self._holmes_info.latest_version}. Please update.",
163
+ )
164
+
165
+ @classmethod
166
+ def load_from_file(cls, config_file: Optional[Path], **kwargs) -> "Config":
167
+ """
168
+ Load configuration from file and merge with CLI options.
169
+
170
+ Args:
171
+ config_file: Path to configuration file
172
+ **kwargs: CLI options to override config file values
173
+
174
+ Returns:
175
+ Config instance with merged settings
176
+ """
177
+ config_from_file: Optional[Config] = None
178
+ if config_file is not None and config_file.exists():
179
+ logging.debug(f"Loading config from {config_file}")
180
+ config_from_file = load_model_from_file(cls, config_file)
181
+
182
+ cli_options = {k: v for k, v in kwargs.items() if v is not None and v != []}
183
+
184
+ if config_from_file is None:
185
+ result = cls(**cli_options)
186
+ else:
187
+ logging.debug(f"Overriding config from cli options {cli_options}")
188
+ merged_config = config_from_file.dict()
189
+ merged_config.update(cli_options)
190
+ result = cls(**merged_config)
191
+
192
+ result.log_useful_info()
193
+ return result
194
+
195
+ @classmethod
196
+ def load_from_env(cls):
197
+ kwargs = {}
198
+ for field_name in [
199
+ "model",
200
+ "api_key",
201
+ "max_steps",
202
+ "alertmanager_url",
203
+ "alertmanager_username",
204
+ "alertmanager_password",
205
+ "jira_url",
206
+ "jira_username",
207
+ "jira_api_key",
208
+ "jira_query",
209
+ "slack_token",
210
+ "slack_channel",
211
+ "github_url",
212
+ "github_owner",
213
+ "github_repository",
214
+ "github_pat",
215
+ "github_query",
216
+ # TODO
217
+ # custom_runbooks
218
+ ]:
219
+ val = os.getenv(field_name.upper(), None)
220
+ if val is not None:
221
+ kwargs[field_name] = val
222
+ kwargs["cluster_name"] = Config.__get_cluster_name()
223
+ result = cls(**kwargs)
224
+ result.log_useful_info()
225
+ return result
226
+
227
+ @staticmethod
228
+ def __get_cluster_name() -> Optional[str]:
229
+ config_file_path = ROBUSTA_CONFIG_PATH
230
+ env_cluster_name = os.environ.get("CLUSTER_NAME")
231
+ if env_cluster_name:
232
+ return env_cluster_name
233
+
234
+ if not os.path.exists(config_file_path):
235
+ logging.info(f"No robusta config in {config_file_path}")
236
+ return None
237
+
238
+ logging.info(f"loading config {config_file_path}")
239
+ with open(config_file_path) as file:
240
+ yaml_content = yaml.safe_load(file)
241
+ config = RobustaConfig(**yaml_content)
242
+ return config.global_config.get("cluster_name")
243
+
244
+ return None
245
+
246
+ @staticmethod
247
+ def get_runbook_catalog() -> Optional[RunbookCatalog]:
248
+ # TODO(mainred): besides the built-in runbooks, we need to allow the user to bring their own runbooks
249
+ runbook_catalog = load_runbook_catalog()
250
+ return runbook_catalog
251
+
252
+ def create_console_tool_executor(self, dal: Optional[SupabaseDal]) -> ToolExecutor:
253
+ """
254
+ Creates a ToolExecutor instance configured for CLI usage. This executor manages the available tools
255
+ and their execution in the command-line interface.
256
+
257
+ The method loads toolsets in this order, with later sources overriding earlier ones:
258
+ 1. Built-in toolsets (tagged as CORE or CLI)
259
+ 2. toolsets from config file will override and be merged into built-in toolsets with the same name.
260
+ 3. Custom toolsets from config files which can not override built-in toolsets
261
+ """
262
+ cli_toolsets = self.toolset_manager.list_console_toolsets(dal=dal)
263
+ return ToolExecutor(cli_toolsets)
264
+
265
+ def create_tool_executor(self, dal: Optional[SupabaseDal]) -> ToolExecutor:
266
+ """
267
+ Creates ToolExecutor for the server endpoints
268
+ """
269
+
270
+ if self._server_tool_executor:
271
+ return self._server_tool_executor
272
+
273
+ toolsets = self.toolset_manager.list_server_toolsets(dal=dal)
274
+
275
+ self._server_tool_executor = ToolExecutor(toolsets)
276
+
277
+ logging.debug(
278
+ f"Starting AI session with tools: {[tn for tn in self._server_tool_executor.tools_by_name.keys()]}"
279
+ )
280
+
281
+ return self._server_tool_executor
282
+
283
+ def create_console_toolcalling_llm(
284
+ self, dal: Optional[SupabaseDal] = None
285
+ ) -> ToolCallingLLM:
286
+ tool_executor = self.create_console_tool_executor(dal)
287
+ return ToolCallingLLM(tool_executor, self.max_steps, self._get_llm())
288
+
289
+ def create_toolcalling_llm(
290
+ self, dal: Optional[SupabaseDal] = None, model: Optional[str] = None
291
+ ) -> ToolCallingLLM:
292
+ tool_executor = self.create_tool_executor(dal)
293
+ return ToolCallingLLM(tool_executor, self.max_steps, self._get_llm(model))
294
+
295
+ def create_issue_investigator(
296
+ self, dal: Optional[SupabaseDal] = None, model: Optional[str] = None
297
+ ) -> IssueInvestigator:
298
+ all_runbooks = load_builtin_runbooks()
299
+ for runbook_path in self.custom_runbooks:
300
+ all_runbooks.extend(load_runbooks_from_file(runbook_path))
301
+
302
+ runbook_manager = RunbookManager(all_runbooks)
303
+ tool_executor = self.create_tool_executor(dal)
304
+ return IssueInvestigator(
305
+ tool_executor, runbook_manager, self.max_steps, self._get_llm(model)
306
+ )
307
+
308
+ def create_console_issue_investigator(
309
+ self, dal: Optional[SupabaseDal] = None
310
+ ) -> IssueInvestigator:
311
+ all_runbooks = load_builtin_runbooks()
312
+ for runbook_path in self.custom_runbooks:
313
+ all_runbooks.extend(load_runbooks_from_file(runbook_path))
314
+
315
+ runbook_manager = RunbookManager(all_runbooks)
316
+ tool_executor = self.create_console_tool_executor(dal=dal)
317
+ return IssueInvestigator(
318
+ tool_executor, runbook_manager, self.max_steps, self._get_llm()
319
+ )
320
+
321
+ def validate_jira_config(self):
322
+ if self.jira_url is None:
323
+ raise ValueError("--jira-url must be specified")
324
+ if not (
325
+ self.jira_url.startswith("http://") or self.jira_url.startswith("https://")
326
+ ):
327
+ raise ValueError("--jira-url must start with http:// or https://")
328
+ if self.jira_username is None:
329
+ raise ValueError("--jira-username must be specified")
330
+ if self.jira_api_key is None:
331
+ raise ValueError("--jira-api-key must be specified")
332
+
333
+ def create_jira_source(self) -> JiraSource:
334
+ self.validate_jira_config()
335
+
336
+ return JiraSource(
337
+ url=self.jira_url, # type: ignore
338
+ username=self.jira_username, # type: ignore
339
+ api_key=self.jira_api_key.get_secret_value(), # type: ignore
340
+ jql_query=self.jira_query, # type: ignore
341
+ )
342
+
343
+ def create_jira_service_management_source(self) -> JiraServiceManagementSource:
344
+ self.validate_jira_config()
345
+
346
+ return JiraServiceManagementSource(
347
+ url=self.jira_url, # type: ignore
348
+ username=self.jira_username, # type: ignore
349
+ api_key=self.jira_api_key.get_secret_value(), # type: ignore
350
+ jql_query=self.jira_query, # type: ignore
351
+ )
352
+
353
+ def create_github_source(self) -> GitHubSource:
354
+ if not self.github_url or not (
355
+ self.github_url.startswith("http://")
356
+ or self.github_url.startswith("https://")
357
+ ):
358
+ raise ValueError("--github-url must start with http:// or https://")
359
+ if self.github_owner is None:
360
+ raise ValueError("--github-owner must be specified")
361
+ if self.github_repository is None:
362
+ raise ValueError("--github-repository must be specified")
363
+ if self.github_pat is None:
364
+ raise ValueError("--github-pat must be specified")
365
+
366
+ return GitHubSource(
367
+ url=self.github_url,
368
+ owner=self.github_owner,
369
+ pat=self.github_pat.get_secret_value(),
370
+ repository=self.github_repository,
371
+ query=self.github_query,
372
+ )
373
+
374
+ def create_pagerduty_source(self) -> PagerDutySource:
375
+ if self.pagerduty_api_key is None:
376
+ raise ValueError("--pagerduty-api-key must be specified")
377
+
378
+ return PagerDutySource(
379
+ api_key=self.pagerduty_api_key.get_secret_value(),
380
+ user_email=self.pagerduty_user_email, # type: ignore
381
+ incident_key=self.pagerduty_incident_key,
382
+ )
383
+
384
+ def create_opsgenie_source(self) -> OpsGenieSource:
385
+ if self.opsgenie_api_key is None:
386
+ raise ValueError("--opsgenie-api-key must be specified")
387
+
388
+ return OpsGenieSource(
389
+ api_key=self.opsgenie_api_key.get_secret_value(),
390
+ query=self.opsgenie_query, # type: ignore
391
+ team_integration_key=(
392
+ self.opsgenie_team_integration_key.get_secret_value()
393
+ if self.opsgenie_team_integration_key
394
+ else None
395
+ ),
396
+ )
397
+
398
+ def create_alertmanager_source(self) -> AlertManagerSource:
399
+ return AlertManagerSource(
400
+ url=self.alertmanager_url, # type: ignore
401
+ username=self.alertmanager_username,
402
+ alertname_filter=self.alertmanager_alertname, # type: ignore
403
+ label_filter=self.alertmanager_label, # type: ignore
404
+ filepath=self.alertmanager_file,
405
+ )
406
+
407
+ def create_slack_destination(self):
408
+ if self.slack_token is None:
409
+ raise ValueError("--slack-token must be specified")
410
+ if self.slack_channel is None:
411
+ raise ValueError("--slack-channel must be specified")
412
+ return SlackDestination(self.slack_token.get_secret_value(), self.slack_channel)
413
+
414
+ def _get_llm(self, model_key: Optional[str] = None) -> LLM:
415
+ api_key = self.api_key.get_secret_value() if self.api_key else None
416
+ model = self.model
417
+ model_params = {}
418
+ if self._model_list:
419
+ # get requested model or the first credentials if no model requested.
420
+ model_params = (
421
+ self._model_list.get(model_key, {}).copy()
422
+ if model_key
423
+ else next(iter(self._model_list.values())).copy()
424
+ )
425
+ api_key = model_params.pop("api_key", api_key)
426
+ model = model_params.pop("model", model)
427
+
428
+ return DefaultLLM(model, api_key, model_params) # type: ignore
429
+
430
+ def get_models_list(self) -> List[str]:
431
+ if self._model_list:
432
+ return json.dumps(list(self._model_list.keys())) # type: ignore
433
+
434
+ return json.dumps([self.model]) # type: ignore
435
+
436
+
437
+ class TicketSource(BaseModel):
438
+ config: Config
439
+ output_instructions: list[str]
440
+ source: Union[JiraServiceManagementSource, PagerDutySource]
441
+
442
+ model_config = ConfigDict(arbitrary_types_allowed=True)
443
+
444
+
445
+ class SourceFactory(BaseModel):
446
+ @staticmethod
447
+ def create_source(
448
+ source: SupportedTicketSources,
449
+ config_file: Optional[Path],
450
+ ticket_url: Optional[str],
451
+ ticket_username: Optional[str],
452
+ ticket_api_key: Optional[str],
453
+ ticket_id: Optional[str],
454
+ ) -> TicketSource:
455
+ supported_sources = [s.value for s in SupportedTicketSources]
456
+ if source not in supported_sources:
457
+ raise ValueError(
458
+ f"Source '{source}' is not supported. Supported sources: {', '.join(supported_sources)}"
459
+ )
460
+
461
+ if source == SupportedTicketSources.JIRA_SERVICE_MANAGEMENT:
462
+ config = Config.load_from_file(
463
+ config_file=config_file,
464
+ api_key=None,
465
+ model=None,
466
+ max_steps=None,
467
+ jira_url=ticket_url,
468
+ jira_username=ticket_username,
469
+ jira_api_key=ticket_api_key,
470
+ jira_query=None,
471
+ custom_toolsets=None,
472
+ custom_runbooks=None,
473
+ )
474
+
475
+ if not (
476
+ config.jira_url
477
+ and config.jira_username
478
+ and config.jira_api_key
479
+ and ticket_id
480
+ ):
481
+ raise ValueError(
482
+ "URL, username, API key, and ticket ID are required for jira-service-management"
483
+ )
484
+
485
+ output_instructions = [
486
+ "All output links/urls must **always** be of this format : [link text here|http://your.url.here.com] and **never*** the format [link text here](http://your.url.here.com)"
487
+ ]
488
+ source_instance = config.create_jira_service_management_source()
489
+ return TicketSource(
490
+ config=config,
491
+ output_instructions=output_instructions,
492
+ source=source_instance,
493
+ )
494
+
495
+ elif source == SupportedTicketSources.PAGERDUTY:
496
+ config = Config.load_from_file(
497
+ config_file=config_file,
498
+ api_key=None,
499
+ model=None,
500
+ max_steps=None,
501
+ pagerduty_api_key=ticket_api_key,
502
+ pagerduty_user_email=ticket_username,
503
+ pagerduty_incident_key=None,
504
+ custom_toolsets=None,
505
+ custom_runbooks=None,
506
+ )
507
+
508
+ if not (
509
+ config.pagerduty_user_email and config.pagerduty_api_key and ticket_id
510
+ ):
511
+ raise ValueError(
512
+ "username, API key, and ticket ID are required for pagerduty"
513
+ )
514
+
515
+ output_instructions = [
516
+ "All output links/urls must **always** be of this format : \n link text here: http://your.url.here.com\n **never*** use the url the format [link text here](http://your.url.here.com)"
517
+ ]
518
+ source_instance = config.create_pagerduty_source() # type: ignore
519
+ return TicketSource(
520
+ config=config,
521
+ output_instructions=output_instructions,
522
+ source=source_instance,
523
+ )
524
+
525
+ else:
526
+ raise NotImplementedError(f"Source '{source}' is not yet implemented")
File without changes