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
@@ -0,0 +1,56 @@
1
+ import json
2
+ import os
3
+ import logging
4
+ import yaml # type: ignore
5
+
6
+
7
+ def write_json_file(json_output_file: str, json_ob_to_dump):
8
+ try:
9
+ dirname = os.path.dirname(json_output_file)
10
+ if dirname:
11
+ os.makedirs(dirname, exist_ok=True)
12
+ with open(json_output_file, "w", encoding="utf-8") as f:
13
+ json.dump(json_ob_to_dump, f, ensure_ascii=False, indent=4, default=str)
14
+ except Exception:
15
+ logging.exception("Failed to create the json file.")
16
+ return
17
+
18
+
19
+ def load_yaml_file(
20
+ path: str, raise_error: bool = True, warn_not_found: bool = True
21
+ ) -> dict:
22
+ try:
23
+ with open(path, "r", encoding="utf-8") as file:
24
+ parsed_yaml = yaml.safe_load(file)
25
+ except yaml.YAMLError as err:
26
+ logging.warning(f"Error parsing YAML from {path}: {err}")
27
+ if raise_error:
28
+ raise err
29
+ return {}
30
+ except FileNotFoundError as err:
31
+ if warn_not_found:
32
+ logging.warning(f"file {path} was not found.")
33
+ if raise_error:
34
+ raise err
35
+ return {}
36
+ except Exception as err:
37
+ logging.warning(f"Failed to open file {path}: {err}")
38
+ if raise_error:
39
+ raise err
40
+ return {}
41
+
42
+ if not parsed_yaml:
43
+ message = f"No content found in file: {path}"
44
+ logging.warning(message)
45
+ if raise_error:
46
+ raise ValueError(message)
47
+ return {}
48
+
49
+ if not isinstance(parsed_yaml, dict):
50
+ message = f"Invalid format: YAML file {path} does not contain a dictionary at the root."
51
+ logging.warning(message)
52
+ if raise_error:
53
+ raise ValueError(message)
54
+ return {}
55
+
56
+ return parsed_yaml
@@ -0,0 +1,20 @@
1
+ from typing import List, Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class Instructions(BaseModel):
7
+ instructions: List[str] = []
8
+
9
+
10
+ def add_global_instructions_to_user_prompt(
11
+ user_prompt: str, global_instructions: Optional[Instructions]
12
+ ) -> str:
13
+ if (
14
+ global_instructions
15
+ and global_instructions.instructions
16
+ and len(global_instructions.instructions[0]) > 0
17
+ ):
18
+ instructions = "\n\n".join(global_instructions.instructions)
19
+ user_prompt += f"\n\nGlobal Instructions (use if relevant): {instructions}\n"
20
+ return user_prompt
@@ -0,0 +1,22 @@
1
+ from holmes.core.supabase_dal import SupabaseDal
2
+ from holmes.config import Config
3
+ from holmes import get_version # type: ignore
4
+ import logging
5
+
6
+
7
+ def update_holmes_status_in_db(dal: SupabaseDal, config: Config):
8
+ logging.info("Updating status of holmes")
9
+
10
+ if not config.cluster_name:
11
+ raise Exception(
12
+ "Cluster name is missing in the configuration. Please ensure 'CLUSTER_NAME' is defined in the environment variables, "
13
+ "or verify that a cluster name is provided in the Robusta configuration file."
14
+ )
15
+
16
+ dal.upsert_holmes_status(
17
+ {
18
+ "cluster_id": config.cluster_name,
19
+ "model": config.get_models_list(),
20
+ "version": get_version(),
21
+ }
22
+ )
@@ -0,0 +1,80 @@
1
+ import logging
2
+ from datetime import datetime
3
+ from typing import Any, List
4
+
5
+ import yaml # type: ignore
6
+
7
+ from holmes.config import Config
8
+ from holmes.core.supabase_dal import SupabaseDal
9
+ from holmes.core.tools import Toolset, ToolsetDBModel
10
+ from holmes.plugins.prompts import load_and_render_prompt
11
+
12
+
13
+ def log_toolsets_statuses(toolsets: List[Toolset]):
14
+ enabled_toolsets = [
15
+ toolset.name for toolset in toolsets if toolset.status.value == "enabled"
16
+ ]
17
+ disabled_toolsets = [
18
+ toolset.name for toolset in toolsets if toolset.status.value != "enabled"
19
+ ]
20
+ logging.info(f"Enabled toolsets: {enabled_toolsets}")
21
+ logging.info(f"Disabled toolsets: {disabled_toolsets}")
22
+
23
+
24
+ def holmes_sync_toolsets_status(dal: SupabaseDal, config: Config) -> None:
25
+ """
26
+ Method for synchronizing toolsets with the database:
27
+ 1) Fetch all built-in toolsets from the holmes/plugins/toolsets directory
28
+ 2) Load custom toolsets defined in /etc/holmes/config/custom_toolset.yaml
29
+ 3) Override default toolsets with corresponding custom configurations
30
+ and add any new custom toolsets that are not part of the defaults
31
+ 4) Run the check_prerequisites method for each toolset
32
+ 5) Use sync_toolsets to upsert toolset's status and remove toolsets that are not loaded from configs or folder with default directory
33
+ """
34
+ tool_executor = config.create_tool_executor(dal)
35
+
36
+ if not config.cluster_name:
37
+ raise Exception(
38
+ "Cluster name is missing in the configuration. Please ensure 'CLUSTER_NAME' is defined in the environment variables, "
39
+ "or verify that a cluster name is provided in the Robusta configuration file."
40
+ )
41
+
42
+ db_toolsets = []
43
+ updated_at = datetime.now().isoformat()
44
+ for toolset in tool_executor.toolsets:
45
+ # hiding disabled experimental toolsets from the docs
46
+ if toolset.experimental and not toolset.enabled:
47
+ continue
48
+ if not toolset.installation_instructions:
49
+ instructions = render_default_installation_instructions_for_toolset(toolset)
50
+ toolset.installation_instructions = instructions
51
+ db_toolsets.append(
52
+ ToolsetDBModel(
53
+ **toolset.model_dump(exclude_none=True),
54
+ toolset_name=toolset.name,
55
+ cluster_id=config.cluster_name,
56
+ account_id=dal.account_id,
57
+ updated_at=updated_at,
58
+ ).model_dump()
59
+ )
60
+ dal.sync_toolsets(db_toolsets, config.cluster_name)
61
+ log_toolsets_statuses(tool_executor.toolsets)
62
+
63
+
64
+ def render_default_installation_instructions_for_toolset(toolset: Toolset) -> str:
65
+ env_vars = toolset.get_environment_variables()
66
+ context: dict[str, Any] = {
67
+ "env_vars": env_vars if env_vars else [],
68
+ "toolset_name": toolset.name,
69
+ "is_default": toolset.is_default,
70
+ "enabled": toolset.enabled,
71
+ }
72
+
73
+ example_config = toolset.get_example_config()
74
+ if example_config:
75
+ context["example_config"] = yaml.dump(example_config)
76
+
77
+ installation_instructions = load_and_render_prompt(
78
+ "file://holmes/utils/default_toolset_installation_guide.jinja2", context
79
+ )
80
+ return installation_instructions
@@ -0,0 +1,55 @@
1
+ # based on https://github.com/kostyachum/python-markdown-plain-text/blob/main/markdown_plain_text/extention.py
2
+ # MIT licensed
3
+ from markdown import Extension, Markdown # type: ignore
4
+
5
+ from xml.etree.ElementTree import ProcessingInstruction
6
+ from xml.etree.ElementTree import Comment, ElementTree
7
+
8
+
9
+ def _serialize_plain_text(write, elem):
10
+ tag = elem.tag
11
+ text = elem.text
12
+ if tag is Comment:
13
+ pass
14
+ elif tag is ProcessingInstruction:
15
+ pass
16
+ elif tag is None:
17
+ if text:
18
+ write(text)
19
+ for e in elem:
20
+ _serialize_plain_text(write, e)
21
+ else:
22
+ if text:
23
+ if tag.lower() not in ["script", "style"]:
24
+ write(text)
25
+ for e in elem:
26
+ _serialize_plain_text(write, e)
27
+
28
+ if elem.tail:
29
+ write(elem.tail)
30
+
31
+
32
+ def _write_plain_text(root):
33
+ assert root is not None
34
+ data = []
35
+ write = data.append
36
+ _serialize_plain_text(write, root)
37
+ return "".join(data)
38
+
39
+
40
+ def to_plain_text(element):
41
+ return _write_plain_text(ElementTree(element).getroot())
42
+
43
+
44
+ class PlainTextExtension(Extension):
45
+ def extendMarkdown(self, md):
46
+ md.serializer = to_plain_text
47
+ md.stripTopLevelTags = False
48
+
49
+ # Extention register actually runs before the format is set and it ends up rewriting serializer that we have just changed
50
+ md.set_output_format = lambda x: x
51
+
52
+
53
+ def markdown_to_plain_text(text):
54
+ md = Markdown(extensions=[PlainTextExtension()])
55
+ return md.convert(text)
@@ -0,0 +1,54 @@
1
+ import sys
2
+ from pathlib import Path
3
+ from typing import Annotated, Any, Dict, List, Optional, Tuple, Type, Union
4
+
5
+ import typer
6
+ from benedict import benedict # type: ignore
7
+ from pydantic import BaseModel, BeforeValidator, ConfigDict, ValidationError
8
+
9
+ from holmes.plugins.prompts import load_prompt
10
+
11
+ PromptField = Annotated[str, BeforeValidator(lambda v: load_prompt(v))]
12
+
13
+
14
+ class RobustaBaseConfig(BaseModel):
15
+ model_config = ConfigDict(extra="forbid", validate_default=True)
16
+
17
+
18
+ def loc_to_dot_sep(loc: Tuple[Union[str, int], ...]) -> str:
19
+ path = ""
20
+ for i, x in enumerate(loc):
21
+ if isinstance(x, str):
22
+ if i > 0:
23
+ path += "."
24
+ path += x
25
+ elif isinstance(x, int):
26
+ path += f"[{x}]"
27
+ else:
28
+ raise TypeError("Unexpected type")
29
+ return path
30
+
31
+
32
+ def convert_errors(e: ValidationError) -> List[Dict[str, Any]]:
33
+ new_errors: List[Dict[str, Any]] = e.errors() # type: ignore
34
+ for error in new_errors:
35
+ error["loc"] = loc_to_dot_sep(error["loc"])
36
+ return new_errors
37
+
38
+
39
+ def load_model_from_file(
40
+ model: Type[BaseModel], file_path: Path, yaml_path: Optional[str] = None
41
+ ):
42
+ try:
43
+ contents = benedict(file_path, format="yaml")
44
+ if yaml_path is not None:
45
+ contents = contents[yaml_path]
46
+ return model.model_validate(contents)
47
+ except ValidationError as e:
48
+ print(e)
49
+ bad_fields = [e["loc"] for e in convert_errors(e)]
50
+ typer.secho(
51
+ f"Invalid config file at {file_path}. Check the fields {bad_fields}.\nSee detailed errors above.",
52
+ fg="red",
53
+ )
54
+ sys.exit()
@@ -0,0 +1,10 @@
1
+ from holmes.config import Config
2
+ from holmes.core.supabase_dal import SupabaseDal
3
+ from pydantic import SecretStr
4
+ from holmes.common.env_vars import load_bool
5
+
6
+
7
+ def load_robusta_api_key(dal: SupabaseDal, config: Config):
8
+ if load_bool("ROBUSTA_AI", False):
9
+ account_id, token = dal.get_ai_credentials()
10
+ config.api_key = SecretStr(f"{account_id} {token}")
holmes/utils/tags.py ADDED
@@ -0,0 +1,97 @@
1
+ import logging
2
+ from typing import Optional
3
+ from typing_extensions import Dict, List
4
+ import re
5
+ import json
6
+ from copy import deepcopy
7
+
8
+
9
+ def stringify_tag(tag: Dict[str, str]) -> Optional[str]:
10
+ """
11
+ This serializes a dictionary into something more readable to the LLM.
12
+ Although I have not seen much difference in quality of output, in theory this can help the LLM
13
+ understand better how to link the tag values with the tools.
14
+
15
+ Here are some examples of formatting (more can be found in the test for this function):
16
+ - { "type": "node", "name": "my-node" }
17
+ -> "node my-node"
18
+ - { "type": "issue", "id": "issue-id", "name": "KubeJobFailed", "subject_namespace": "my-namespace", "subject_name": "my-pod" }
19
+ -> issue issue-id (name=KubeJobFailed, subject_namespace=my-namespace, subject_name=my-pod)
20
+ """
21
+ type = tag.pop("type")
22
+ if not type:
23
+ return None
24
+
25
+ key = ""
26
+ if tag.get("id"):
27
+ key = tag.pop("id")
28
+ elif tag.get("name"):
29
+ key = tag.pop("name")
30
+
31
+ if not key:
32
+ return None
33
+
34
+ formatted_string = f"{type} {key}"
35
+
36
+ if len(tag) > 0:
37
+ keyVals = []
38
+ for k, v in tag.items():
39
+ keyVals.append(f"{k}={v}")
40
+ formatted_string += f" ({', '.join(keyVals)})"
41
+
42
+ return formatted_string
43
+
44
+
45
+ def format_tags_in_string(user_prompt: str) -> str:
46
+ """
47
+ Formats the tags included in a user's message.
48
+ E.g.
49
+ 'how many pods are running on << { "type": "node", "name": "my-node" } >>?'
50
+ -> 'how many pods are running on node my-node?'
51
+ """
52
+ try:
53
+ pattern = r"<<(.*?)>>"
54
+
55
+ def replace_match(match):
56
+ try:
57
+ json_str = match.group(1)
58
+ json_obj = json.loads(json_str)
59
+ formatted = stringify_tag(json_obj)
60
+ return formatted if formatted else match.group(0)
61
+ except (json.JSONDecodeError, AttributeError):
62
+ logging.warning(f"Failed to parse tag in string: {user_prompt}")
63
+ return match.group(0)
64
+
65
+ return re.sub(pattern, replace_match, user_prompt)
66
+ except Exception:
67
+ logging.warning(f"Failed to parse string: {user_prompt}")
68
+ return user_prompt
69
+
70
+
71
+ def parse_messages_tags(messages: List[Dict[str, str]]) -> List[Dict[str, str]]:
72
+ """
73
+ Parses the user messages for tags and format these.
74
+ System messages and llm responses are ignored and left as-is
75
+
76
+ This method returns a shallow copy of the messages list with the exception
77
+ of the messages that have been parsed.
78
+ """
79
+ formatted_messages = []
80
+ for message in messages:
81
+ original_message = message.get("content")
82
+ if message.get("role") == "user" and original_message:
83
+ formatted_str = format_tags_in_string(original_message)
84
+ if formatted_str != message.get("content"):
85
+ formatted_message = deepcopy(message)
86
+ formatted_message["content"] = formatted_str
87
+ formatted_messages.append(formatted_message)
88
+ logging.debug(
89
+ f"Message with tags '{original_message}' formatted to '{formatted_message}'"
90
+ )
91
+ else:
92
+ formatted_messages.append(message)
93
+
94
+ else:
95
+ formatted_messages.append(message)
96
+
97
+ return formatted_messages
@@ -0,0 +1,21 @@
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.