holmesgpt 0.14.1a0__py3-none-any.whl → 0.14.3a0__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 (73) hide show
  1. holmes/__init__.py +1 -1
  2. holmes/clients/robusta_client.py +5 -2
  3. holmes/common/env_vars.py +8 -2
  4. holmes/config.py +4 -7
  5. holmes/core/conversations.py +12 -2
  6. holmes/core/feedback.py +191 -0
  7. holmes/core/llm.py +52 -10
  8. holmes/core/models.py +101 -1
  9. holmes/core/supabase_dal.py +23 -9
  10. holmes/core/tool_calling_llm.py +206 -16
  11. holmes/core/tools.py +20 -7
  12. holmes/core/tools_utils/token_counting.py +13 -0
  13. holmes/core/tools_utils/tool_context_window_limiter.py +45 -23
  14. holmes/core/tools_utils/tool_executor.py +11 -6
  15. holmes/core/toolset_manager.py +7 -3
  16. holmes/core/truncation/dal_truncation_utils.py +23 -0
  17. holmes/interactive.py +146 -14
  18. holmes/plugins/prompts/_fetch_logs.jinja2 +13 -1
  19. holmes/plugins/runbooks/__init__.py +6 -1
  20. holmes/plugins/toolsets/__init__.py +11 -4
  21. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +9 -20
  22. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +2 -3
  23. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +2 -3
  24. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +6 -4
  25. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +6 -4
  26. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +2 -3
  27. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +6 -4
  28. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +2 -3
  29. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +2 -3
  30. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +2 -3
  31. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +2 -3
  32. holmes/plugins/toolsets/bash/bash_toolset.py +4 -7
  33. holmes/plugins/toolsets/cilium.yaml +284 -0
  34. holmes/plugins/toolsets/datadog/datadog_api.py +490 -24
  35. holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +21 -10
  36. holmes/plugins/toolsets/datadog/toolset_datadog_general.py +333 -199
  37. holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +181 -9
  38. holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +80 -22
  39. holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +5 -8
  40. holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +7 -12
  41. holmes/plugins/toolsets/git.py +14 -12
  42. holmes/plugins/toolsets/grafana/grafana_tempo_api.py +23 -42
  43. holmes/plugins/toolsets/grafana/toolset_grafana.py +2 -3
  44. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +2 -1
  45. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +21 -39
  46. holmes/plugins/toolsets/internet/internet.py +2 -3
  47. holmes/plugins/toolsets/internet/notion.py +2 -3
  48. holmes/plugins/toolsets/investigator/core_investigation.py +7 -9
  49. holmes/plugins/toolsets/kafka.py +7 -18
  50. holmes/plugins/toolsets/logging_utils/logging_api.py +80 -4
  51. holmes/plugins/toolsets/mcp/toolset_mcp.py +2 -3
  52. holmes/plugins/toolsets/newrelic/__init__.py +0 -0
  53. holmes/plugins/toolsets/newrelic/new_relic_api.py +125 -0
  54. holmes/plugins/toolsets/newrelic/newrelic.jinja2 +41 -0
  55. holmes/plugins/toolsets/newrelic/newrelic.py +211 -0
  56. holmes/plugins/toolsets/opensearch/opensearch.py +5 -12
  57. holmes/plugins/toolsets/opensearch/opensearch_traces.py +3 -6
  58. holmes/plugins/toolsets/prometheus/prometheus.py +808 -419
  59. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +27 -11
  60. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +3 -6
  61. holmes/plugins/toolsets/robusta/robusta.py +4 -9
  62. holmes/plugins/toolsets/runbook/runbook_fetcher.py +93 -13
  63. holmes/plugins/toolsets/servicenow/servicenow.py +5 -10
  64. holmes/utils/sentry_helper.py +1 -1
  65. holmes/utils/stream.py +22 -7
  66. holmes/version.py +34 -14
  67. {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/METADATA +7 -9
  68. {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/RECORD +71 -65
  69. holmes/core/tools_utils/data_types.py +0 -81
  70. holmes/plugins/toolsets/newrelic.py +0 -231
  71. {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/LICENSE.txt +0 -0
  72. {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/WHEEL +0 -0
  73. {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,23 @@
1
+ from holmes.common.env_vars import MAX_EVIDENCE_DATA_CHARACTERS_BEFORE_TRUNCATION
2
+
3
+
4
+ def truncate_string(data_str: str) -> str:
5
+ if data_str and len(data_str) > MAX_EVIDENCE_DATA_CHARACTERS_BEFORE_TRUNCATION:
6
+ return (
7
+ data_str[:MAX_EVIDENCE_DATA_CHARACTERS_BEFORE_TRUNCATION]
8
+ + "-- DATA TRUNCATED TO AVOID HITTING CONTEXT WINDOW LIMITS"
9
+ )
10
+ return data_str
11
+
12
+
13
+ def truncate_evidences_entities_if_necessary(evidence_list: list[dict]):
14
+ if (
15
+ not MAX_EVIDENCE_DATA_CHARACTERS_BEFORE_TRUNCATION
16
+ or MAX_EVIDENCE_DATA_CHARACTERS_BEFORE_TRUNCATION <= 0
17
+ ):
18
+ return
19
+
20
+ for evidence in evidence_list:
21
+ data = evidence.get("data")
22
+ if data:
23
+ evidence["data"] = truncate_string(str(data))
holmes/interactive.py CHANGED
@@ -26,9 +26,16 @@ from prompt_toolkit.widgets import TextArea
26
26
  from pygments.lexers import guess_lexer
27
27
  from rich.console import Console
28
28
  from rich.markdown import Markdown, Panel
29
+ from rich.markup import escape
29
30
 
30
31
  from holmes.common.env_vars import ENABLE_CLI_TOOL_APPROVAL
31
32
  from holmes.core.config import config_path_dir
33
+ from holmes.core.feedback import (
34
+ PRIVACY_NOTICE_BANNER,
35
+ Feedback,
36
+ FeedbackCallback,
37
+ UserFeedback,
38
+ )
32
39
  from holmes.core.prompt import build_initial_ask_messages
33
40
  from holmes.core.tool_calling_llm import ToolCallingLLM, ToolCallResult
34
41
  from holmes.core.tools import StructuredToolResult, pretty_print_toolset_status
@@ -62,19 +69,25 @@ class SlashCommands(Enum):
62
69
  )
63
70
  CONTEXT = ("/context", "Show conversation context size and token count")
64
71
  SHOW = ("/show", "Show specific tool output in scrollable view")
72
+ FEEDBACK = ("/feedback", "Provide feedback on the agent's response")
65
73
 
66
74
  def __init__(self, command, description):
67
75
  self.command = command
68
76
  self.description = description
69
77
 
70
78
 
71
- SLASH_COMMANDS_REFERENCE = {cmd.command: cmd.description for cmd in SlashCommands}
72
- ALL_SLASH_COMMANDS = [cmd.command for cmd in SlashCommands]
73
-
74
-
75
79
  class SlashCommandCompleter(Completer):
76
- def __init__(self):
77
- self.commands = SLASH_COMMANDS_REFERENCE
80
+ def __init__(self, unsupported_commands: Optional[List[str]] = None):
81
+ # Build commands dictionary, excluding unsupported commands
82
+ all_commands = {cmd.command: cmd.description for cmd in SlashCommands}
83
+ if unsupported_commands:
84
+ self.commands = {
85
+ cmd: desc
86
+ for cmd, desc in all_commands.items()
87
+ if cmd not in unsupported_commands
88
+ }
89
+ else:
90
+ self.commands = all_commands
78
91
 
79
92
  def get_completions(self, document, complete_event):
80
93
  text = document.text_before_cursor
@@ -811,6 +824,88 @@ def handle_last_command(
811
824
  )
812
825
 
813
826
 
827
+ def handle_feedback_command(
828
+ style: Style,
829
+ console: Console,
830
+ feedback: Feedback,
831
+ feedback_callback: FeedbackCallback,
832
+ ) -> None:
833
+ """Handle the /feedback command to collect user feedback."""
834
+ try:
835
+ # Create a temporary session without history for feedback prompts
836
+ temp_session = PromptSession(history=InMemoryHistory()) # type: ignore
837
+ # Prominent privacy notice to users
838
+ console.print(
839
+ f"[bold {HELP_COLOR}]Privacy Notice:[/bold {HELP_COLOR}] {PRIVACY_NOTICE_BANNER}"
840
+ )
841
+ # A "Cancel" button of equal discoverability to "Sent" or "Submit" buttons must be made available
842
+ console.print(
843
+ "[bold yellow]💡 Tip: Press Ctrl+C at any time to cancel feedback[/bold yellow]"
844
+ )
845
+
846
+ # Ask for thumbs up/down rating with validation
847
+ while True:
848
+ rating_prompt = temp_session.prompt(
849
+ [("class:prompt", "Was this response useful to you? 👍(y)/👎(n): ")],
850
+ style=style,
851
+ )
852
+
853
+ rating_lower = rating_prompt.lower().strip()
854
+ if rating_lower in ["y", "n"]:
855
+ break
856
+ else:
857
+ console.print(
858
+ "[bold red]Please enter only 'y' for yes or 'n' for no.[/bold red]"
859
+ )
860
+
861
+ # Determine rating
862
+ is_positive = rating_lower == "y"
863
+
864
+ # Ask for additional comments
865
+ comment_prompt = temp_session.prompt(
866
+ [
867
+ (
868
+ "class:prompt",
869
+ "Do you want to provide any additional comments for feedback? (press Enter to skip):\n",
870
+ )
871
+ ],
872
+ style=style,
873
+ )
874
+
875
+ comment = comment_prompt.strip() if comment_prompt.strip() else None
876
+
877
+ # Create UserFeedback object
878
+ user_feedback = UserFeedback(is_positive, comment)
879
+
880
+ if comment:
881
+ console.print(
882
+ f'[bold green]✓ Feedback recorded (rating={user_feedback.rating_emoji}, "{escape(comment)}")[/bold green]'
883
+ )
884
+ else:
885
+ console.print(
886
+ f"[bold green]✓ Feedback recorded (rating={user_feedback.rating_emoji}, no comment)[/bold green]"
887
+ )
888
+
889
+ # Final confirmation before submitting
890
+ final_confirmation = temp_session.prompt(
891
+ [("class:prompt", "\nDo you want to submit this feedback? (Y/n): ")],
892
+ style=style,
893
+ )
894
+
895
+ # If user says no, cancel the feedback
896
+ if final_confirmation.lower().strip().startswith("n"):
897
+ console.print("[dim]Feedback cancelled.[/dim]")
898
+ return
899
+
900
+ feedback.user_feedback = user_feedback
901
+ feedback_callback(feedback)
902
+ console.print("[bold green]Thank you for your feedback! 🙏[/bold green]")
903
+
904
+ except KeyboardInterrupt:
905
+ console.print("[dim]Feedback cancelled.[/dim]")
906
+ return
907
+
908
+
814
909
  def display_recent_tool_outputs(
815
910
  tool_calls: List[ToolCallResult],
816
911
  console: Console,
@@ -823,7 +918,10 @@ def display_recent_tool_outputs(
823
918
  for tool_call in tool_calls:
824
919
  tool_index = find_tool_index_in_history(tool_call, all_tool_calls_history)
825
920
  preview_output = format_tool_call_output(tool_call, tool_index)
826
- title = f"{tool_call.result.status.to_emoji()} {tool_call.description} -> returned {tool_call.result.return_code}"
921
+ title = (
922
+ f"{tool_call.result.status.to_emoji()} {tool_call.description} -> "
923
+ f"returned {tool_call.result.return_code}"
924
+ )
827
925
 
828
926
  console.print(
829
927
  Panel(
@@ -846,6 +944,7 @@ def run_interactive_loop(
846
944
  runbooks=None,
847
945
  system_prompt_additions: Optional[str] = None,
848
946
  check_version: bool = True,
947
+ feedback_callback: Optional[FeedbackCallback] = None,
849
948
  ) -> None:
850
949
  # Initialize tracer - use DummyTracer if no tracer provided
851
950
  if tracer is None:
@@ -874,7 +973,11 @@ def run_interactive_loop(
874
973
  ai.approval_callback = approval_handler
875
974
 
876
975
  # Create merged completer with slash commands, conditional executables, show command, and smart paths
877
- slash_completer = SlashCommandCompleter()
976
+ # TODO: remove unsupported_commands support once we implement feedback callback
977
+ unsupported_commands = []
978
+ if feedback_callback is None:
979
+ unsupported_commands.append(SlashCommands.FEEDBACK.command)
980
+ slash_completer = SlashCommandCompleter(unsupported_commands)
878
981
  executable_completer = ConditionalExecutableCompleter()
879
982
  show_completer = ShowCommandCompleter()
880
983
  path_completer = SmartPathCompleter()
@@ -891,6 +994,9 @@ def run_interactive_loop(
891
994
  if initial_user_input:
892
995
  history.append_string(initial_user_input)
893
996
 
997
+ feedback = Feedback()
998
+ feedback.metadata.update_llm(ai.llm)
999
+
894
1000
  # Create custom key bindings for Ctrl+C behavior
895
1001
  bindings = KeyBindings()
896
1002
  status_message = ""
@@ -963,7 +1069,15 @@ def run_interactive_loop(
963
1069
 
964
1070
  input_prompt = [("class:prompt", "User: ")]
965
1071
 
966
- console.print(WELCOME_BANNER)
1072
+ # TODO: merge the /feedback command description to WELCOME_BANNER once we implement feedback callback
1073
+ welcome_banner = WELCOME_BANNER
1074
+ if feedback_callback:
1075
+ welcome_banner = (
1076
+ welcome_banner.rstrip(".")
1077
+ + f", '{SlashCommands.FEEDBACK.command}' to share your thoughts."
1078
+ )
1079
+ console.print(welcome_banner)
1080
+
967
1081
  if initial_user_input:
968
1082
  console.print(
969
1083
  f"[bold {USER_COLOR}]User:[/bold {USER_COLOR}] {initial_user_input}"
@@ -985,14 +1099,18 @@ def run_interactive_loop(
985
1099
  if user_input.startswith("/"):
986
1100
  original_input = user_input.strip()
987
1101
  command = original_input.lower()
988
-
989
1102
  # Handle prefix matching for slash commands
990
- matches = [cmd for cmd in ALL_SLASH_COMMANDS if cmd.startswith(command)]
1103
+ matches = [
1104
+ cmd
1105
+ for cmd in slash_completer.commands.keys()
1106
+ if cmd.startswith(command)
1107
+ ]
991
1108
  if len(matches) == 1:
992
1109
  command = matches[0]
993
1110
  elif len(matches) > 1:
994
1111
  console.print(
995
- f"[bold {ERROR_COLOR}]Ambiguous command '{command}'. Matches: {', '.join(matches)}[/bold {ERROR_COLOR}]"
1112
+ f"[bold {ERROR_COLOR}]Ambiguous command '{command}'. "
1113
+ f"Matches: {', '.join(matches)}[/bold {ERROR_COLOR}]"
996
1114
  )
997
1115
  continue
998
1116
 
@@ -1002,13 +1120,20 @@ def run_interactive_loop(
1002
1120
  console.print(
1003
1121
  f"[bold {HELP_COLOR}]Available commands:[/bold {HELP_COLOR}]"
1004
1122
  )
1005
- for cmd, description in SLASH_COMMANDS_REFERENCE.items():
1123
+ for cmd, description in slash_completer.commands.items():
1124
+ # Only show feedback command if callback is available
1125
+ if (
1126
+ cmd == SlashCommands.FEEDBACK.command
1127
+ and feedback_callback is None
1128
+ ):
1129
+ continue
1006
1130
  console.print(f" [bold]{cmd}[/bold] - {description}")
1007
1131
  continue
1008
1132
  elif command == SlashCommands.CLEAR.command:
1009
1133
  console.clear()
1010
1134
  console.print(
1011
- f"[bold {STATUS_COLOR}]Screen cleared and context reset. You can now ask a new question.[/bold {STATUS_COLOR}]"
1135
+ f"[bold {STATUS_COLOR}]Screen cleared and context reset. "
1136
+ f"You can now ask a new question.[/bold {STATUS_COLOR}]"
1012
1137
  )
1013
1138
  messages = None
1014
1139
  last_response = None
@@ -1052,6 +1177,12 @@ def run_interactive_loop(
1052
1177
  if shared_input is None:
1053
1178
  continue # User chose not to share or no output, continue to next input
1054
1179
  user_input = shared_input
1180
+ elif (
1181
+ command == SlashCommands.FEEDBACK.command
1182
+ and feedback_callback is not None
1183
+ ):
1184
+ handle_feedback_command(style, console, feedback, feedback_callback)
1185
+ continue
1055
1186
  else:
1056
1187
  console.print(f"Unknown command: {command}")
1057
1188
  continue
@@ -1091,6 +1222,7 @@ def run_interactive_loop(
1091
1222
 
1092
1223
  messages = response.messages # type: ignore
1093
1224
  last_response = response
1225
+ feedback.metadata.add_llm_response(user_input, response.result)
1094
1226
 
1095
1227
  if response.tool_calls:
1096
1228
  all_tool_calls_history.extend(response.tool_calls)
@@ -11,6 +11,7 @@
11
11
  * IMPORTANT: ALWAYS inform the user about what logs you fetched. For example: "Here are pod logs for ..."
12
12
  * IMPORTANT: If logs commands have limits mention them. For example: "Showing last 100 lines of logs:"
13
13
  * IMPORTANT: If a filter was used, mention the filter. For example: "Logs filtered for 'error':"
14
+ * IMPORTANT: If a date range was used (even if just the default one and you didn't specify the parameter, mention the date range. For example: "Logs from last 1 hour..."
14
15
 
15
16
  {% if loki_ts and loki_ts.status == "enabled" -%}
16
17
  * For any logs, including for investigating kubernetes problems, use Loki
@@ -34,8 +35,18 @@ Tools to search and fetch logs from Coralogix.
34
35
  ### datadog/logs
35
36
  #### Datadog Logs Toolset
36
37
  Tools to search and fetch logs from Datadog.
37
- {% include '_default_log_prompt.jinja2' %}
38
+ * Use the tool `fetch_pod_logs` to access an application's logs.
39
+ * Do fetch application logs yourself and DO not ask users to do so
40
+ * If you have an alert/monitor try to figure out the time it fired
41
+ ** Then, use `start_time=-300` (5 minutes before `end_time`) and `end_time=<time monitor started firing>` when calling `fetch_pod_logs`.
42
+ ** If there are too many logs, or not enough, narrow or widen the timestamps
43
+ * If the user did not explicitly ask about a given timeframe, ignore the `start_time` and `end_time` so it will use the default.
44
+ * IMPORTANT: ALWAYS inform the user about the actual time period fetched (e.g., "Looking at logs from the last <X> days")
45
+ * IMPORTANT: If a limit was applied, ALWAYS tell the user how many logs were shown vs total (e.g., "Showing latest <Y> of <Z> logs")
46
+ * IMPORTANT: If any filters were applied, ALWAYS mention them explicitly
38
47
  {%- elif k8s_yaml_ts and k8s_yaml_ts.status == "enabled" -%}
48
+ ### Logs from newrelic
49
+ * you can fetch logs from newrelic if this is toolset is enabled
39
50
  ### kubernetes/logs
40
51
  #### Kubernetes Logs Toolset
41
52
  Tools to search and fetch logs from Kubernetes.
@@ -56,4 +67,5 @@ DO NOT use `--tail` or `| tail` when calling `kubectl logs` because you may miss
56
67
  ** 'opensearch/logs'
57
68
  ** 'coralogix/logs'
58
69
  ** 'datadog/logs'
70
+ ** 'newrelic'
59
71
  {%- endif -%}
@@ -108,9 +108,14 @@ def get_runbook_by_path(
108
108
  Returns:
109
109
  Full path to the runbook if found, None otherwise
110
110
  """
111
+ # Validate runbook_relative_path is not empty
112
+ if not runbook_relative_path or not runbook_relative_path.strip():
113
+ return None
114
+
111
115
  for search_path in search_paths:
112
116
  runbook_path = os.path.join(search_path, runbook_relative_path)
113
- if os.path.exists(runbook_path):
117
+ # Ensure it's a file, not a directory
118
+ if os.path.isfile(runbook_path):
114
119
  return runbook_path
115
120
 
116
121
  return None
@@ -7,7 +7,10 @@ import yaml # type: ignore
7
7
  from pydantic import ValidationError
8
8
 
9
9
  import holmes.utils.env as env_utils
10
- from holmes.common.env_vars import USE_LEGACY_KUBERNETES_LOGS
10
+ from holmes.common.env_vars import (
11
+ USE_LEGACY_KUBERNETES_LOGS,
12
+ DISABLE_PROMETHEUS_TOOLSET,
13
+ )
11
14
  from holmes.core.supabase_dal import SupabaseDal
12
15
  from holmes.core.tools import Toolset, ToolsetType, ToolsetYamlFromConfig, YAMLToolset
13
16
  from holmes.plugins.toolsets.atlas_mongodb.mongodb_atlas import MongoDBAtlasToolset
@@ -38,11 +41,10 @@ from holmes.plugins.toolsets.internet.notion import NotionToolset
38
41
  from holmes.plugins.toolsets.kafka import KafkaToolset
39
42
  from holmes.plugins.toolsets.kubernetes_logs import KubernetesLogsToolset
40
43
  from holmes.plugins.toolsets.mcp.toolset_mcp import RemoteMCPToolset
41
- from holmes.plugins.toolsets.newrelic import NewRelicToolset
44
+ from holmes.plugins.toolsets.newrelic.newrelic import NewRelicToolset
42
45
  from holmes.plugins.toolsets.opensearch.opensearch import OpenSearchToolset
43
46
  from holmes.plugins.toolsets.opensearch.opensearch_logs import OpenSearchLogsToolset
44
47
  from holmes.plugins.toolsets.opensearch.opensearch_traces import OpenSearchTracesToolset
45
- from holmes.plugins.toolsets.prometheus.prometheus import PrometheusToolset
46
48
  from holmes.plugins.toolsets.rabbitmq.toolset_rabbitmq import RabbitMQToolset
47
49
  from holmes.plugins.toolsets.robusta.robusta import RobustaToolset
48
50
  from holmes.plugins.toolsets.runbook.runbook_fetcher import RunbookToolset
@@ -89,7 +91,6 @@ def load_python_toolsets(dal: Optional[SupabaseDal]) -> List[Toolset]:
89
91
  DatadogMetricsToolset(),
90
92
  DatadogTracesToolset(),
91
93
  DatadogRDSToolset(),
92
- PrometheusToolset(),
93
94
  OpenSearchLogsToolset(),
94
95
  OpenSearchTracesToolset(),
95
96
  CoralogixLogsToolset(),
@@ -101,6 +102,12 @@ def load_python_toolsets(dal: Optional[SupabaseDal]) -> List[Toolset]:
101
102
  AzureSQLToolset(),
102
103
  ServiceNowToolset(),
103
104
  ]
105
+
106
+ if not DISABLE_PROMETHEUS_TOOLSET:
107
+ from holmes.plugins.toolsets.prometheus.prometheus import PrometheusToolset
108
+
109
+ toolsets.append(PrometheusToolset())
110
+
104
111
  if not USE_LEGACY_KUBERNETES_LOGS:
105
112
  toolsets.append(KubernetesLogsToolset())
106
113
 
@@ -4,6 +4,7 @@ from typing import Any, Dict, Tuple, List
4
4
  from holmes.core.tools import (
5
5
  CallablePrerequisite,
6
6
  Tool,
7
+ ToolInvokeContext,
7
8
  ToolParameter,
8
9
  Toolset,
9
10
  ToolsetTag,
@@ -66,8 +67,8 @@ class MongoDBAtlasToolset(Toolset):
66
67
  {"Accept": "application/vnd.atlas.2025-03-12+json"}
67
68
  )
68
69
  self._session.auth = HTTPDigestAuth(
69
- self.config.get("public_key"),
70
- self.config.get("private_key"),
70
+ self.config.get("public_key"), # type: ignore
71
+ self.config.get("private_key"), # type: ignore
71
72
  )
72
73
  return True, ""
73
74
  except Exception:
@@ -118,9 +119,7 @@ class ReturnProjectAlerts(MongoDBAtlasBaseTool):
118
119
  project_id = self.toolset.config.get("project_id", "")
119
120
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Project Alerts ({project_id})"
120
121
 
121
- def _invoke(
122
- self, params: dict, user_approved: bool = False
123
- ) -> StructuredToolResult:
122
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
124
123
  try:
125
124
  url = "https://cloud.mongodb.com/api/atlas/v2/groups/{project_id}/alerts".format(
126
125
  project_id=self.toolset.config.get("project_id")
@@ -145,9 +144,7 @@ class ReturnProjectProcesses(MongoDBAtlasBaseTool):
145
144
  project_id = self.toolset.config.get("project_id", "")
146
145
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Project Processes ({project_id})"
147
146
 
148
- def _invoke(
149
- self, params: dict, user_approved: bool = False
150
- ) -> StructuredToolResult:
147
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
151
148
  try:
152
149
  url = "https://cloud.mongodb.com/api/atlas/v2/groups/{project_id}/processes".format(
153
150
  project_id=self.toolset.config.get("project_id")
@@ -180,9 +177,7 @@ class ReturnProjectSlowQueries(MongoDBAtlasBaseTool):
180
177
  process_id = params.get("process_id", "")
181
178
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Slow Queries ({process_id})"
182
179
 
183
- def _invoke(
184
- self, params: dict, user_approved: bool = False
185
- ) -> StructuredToolResult:
180
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
186
181
  try:
187
182
  url = self.url.format(
188
183
  project_id=self.toolset.config.get("project_id"),
@@ -209,9 +204,7 @@ class ReturnEventsFromProject(MongoDBAtlasBaseTool):
209
204
  project_id = self.toolset.config.get("project_id", "")
210
205
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Project Events ({project_id})"
211
206
 
212
- def _invoke(
213
- self, params: dict, user_approved: bool = False
214
- ) -> StructuredToolResult:
207
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
215
208
  params.update({"itemsPerPage": 500})
216
209
  try:
217
210
  now_utc = datetime.now(timezone.utc)
@@ -268,9 +261,7 @@ class ReturnLogsForProcessInProject(MongoDBAtlasBaseTool):
268
261
  hostname = params.get("hostName", "")
269
262
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Host Logs ({hostname})"
270
263
 
271
- def _invoke(
272
- self, params: dict, user_approved: bool = False
273
- ) -> StructuredToolResult:
264
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
274
265
  one_hour_ago = datetime.now(timezone.utc) - timedelta(hours=1)
275
266
  try:
276
267
  url = self.url.format(
@@ -324,9 +315,7 @@ class ReturnEventTypeFromProject(MongoDBAtlasBaseTool):
324
315
  event_type = params.get("eventType", "")
325
316
  return f"{toolset_name_for_one_liner(self.toolset.name)}: Get Event Details ({event_type})"
326
317
 
327
- def _invoke(
328
- self, params: dict, user_approved: bool = False
329
- ) -> StructuredToolResult:
318
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
330
319
  try:
331
320
  url = self.url.format(projectId=self.toolset.config.get("project_id"))
332
321
 
@@ -4,6 +4,7 @@ from datetime import datetime, timezone
4
4
 
5
5
  from holmes.core.tools import (
6
6
  StructuredToolResult,
7
+ ToolInvokeContext,
7
8
  ToolParameter,
8
9
  StructuredToolResultStatus,
9
10
  )
@@ -217,9 +218,7 @@ class AnalyzeConnectionFailures(BaseAzureSQLTool):
217
218
 
218
219
  return "\n".join(report_sections)
219
220
 
220
- def _invoke(
221
- self, params: dict, user_approved: bool = False
222
- ) -> StructuredToolResult:
221
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
223
222
  try:
224
223
  # Get configuration
225
224
  db_config = self.toolset.database_config()
@@ -4,6 +4,7 @@ from datetime import datetime, timezone
4
4
 
5
5
  from holmes.core.tools import (
6
6
  StructuredToolResult,
7
+ ToolInvokeContext,
7
8
  ToolParameter,
8
9
  StructuredToolResultStatus,
9
10
  )
@@ -155,9 +156,7 @@ class AnalyzeDatabaseConnections(BaseAzureSQLTool):
155
156
 
156
157
  return "\n".join(report_sections)
157
158
 
158
- def _invoke(
159
- self, params: dict, user_approved: bool = False
160
- ) -> StructuredToolResult:
159
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
161
160
  try:
162
161
  hours_back = params.get("hours_back", 2)
163
162
 
@@ -2,7 +2,11 @@ import logging
2
2
  from typing import Dict
3
3
  from datetime import datetime, timezone
4
4
 
5
- from holmes.core.tools import StructuredToolResult, StructuredToolResultStatus
5
+ from holmes.core.tools import (
6
+ StructuredToolResult,
7
+ StructuredToolResultStatus,
8
+ ToolInvokeContext,
9
+ )
6
10
  from holmes.plugins.toolsets.azure_sql.azure_base_toolset import (
7
11
  BaseAzureSQLTool,
8
12
  BaseAzureSQLToolset,
@@ -131,9 +135,7 @@ class AnalyzeDatabaseHealthStatus(BaseAzureSQLTool):
131
135
 
132
136
  return "\n".join(report_sections)
133
137
 
134
- def _invoke(
135
- self, params: dict, user_approved: bool = False
136
- ) -> StructuredToolResult:
138
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
137
139
  try:
138
140
  db_config = self.toolset.database_config()
139
141
  client = self.toolset.api_client()
@@ -2,7 +2,11 @@ import logging
2
2
  from typing import Any, Dict, List, Tuple, cast
3
3
  from datetime import datetime, timezone
4
4
 
5
- from holmes.core.tools import StructuredToolResult, StructuredToolResultStatus
5
+ from holmes.core.tools import (
6
+ StructuredToolResult,
7
+ StructuredToolResultStatus,
8
+ ToolInvokeContext,
9
+ )
6
10
  from holmes.plugins.toolsets.azure_sql.azure_base_toolset import (
7
11
  BaseAzureSQLTool,
8
12
  BaseAzureSQLToolset,
@@ -192,9 +196,7 @@ class AnalyzeDatabasePerformance(BaseAzureSQLTool):
192
196
 
193
197
  return "\n".join(report_sections)
194
198
 
195
- def _invoke(
196
- self, params: dict, user_approved: bool = False
197
- ) -> StructuredToolResult:
199
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
198
200
  try:
199
201
  db_config = self.toolset.database_config()
200
202
  client = self.toolset.api_client()
@@ -4,6 +4,7 @@ from datetime import datetime, timezone
4
4
 
5
5
  from holmes.core.tools import (
6
6
  StructuredToolResult,
7
+ ToolInvokeContext,
7
8
  ToolParameter,
8
9
  StructuredToolResultStatus,
9
10
  )
@@ -253,9 +254,7 @@ class AnalyzeDatabaseStorage(BaseAzureSQLTool):
253
254
 
254
255
  return "\n".join(report_sections)
255
256
 
256
- def _invoke(
257
- self, params: dict, user_approved: bool = False
258
- ) -> StructuredToolResult:
257
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
259
258
  try:
260
259
  hours_back = params.get("hours_back", 24)
261
260
  top_tables = params.get("top_tables", 20)
@@ -2,7 +2,11 @@ import logging
2
2
  from typing import Dict
3
3
  from datetime import datetime, timezone
4
4
 
5
- from holmes.core.tools import StructuredToolResult, StructuredToolResultStatus
5
+ from holmes.core.tools import (
6
+ StructuredToolResult,
7
+ StructuredToolResultStatus,
8
+ ToolInvokeContext,
9
+ )
6
10
  from holmes.plugins.toolsets.azure_sql.azure_base_toolset import (
7
11
  BaseAzureSQLTool,
8
12
  BaseAzureSQLToolset,
@@ -147,9 +151,7 @@ class GetActiveAlerts(BaseAzureSQLTool):
147
151
 
148
152
  return "\n".join(report_sections)
149
153
 
150
- def _invoke(
151
- self, params: dict, user_approved: bool = False
152
- ) -> StructuredToolResult:
154
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
153
155
  try:
154
156
  db_config = self.toolset.database_config()
155
157
  api_client = self.toolset.api_client()
@@ -3,6 +3,7 @@ from typing import Dict, List, Tuple
3
3
 
4
4
  from holmes.core.tools import (
5
5
  StructuredToolResult,
6
+ ToolInvokeContext,
6
7
  ToolParameter,
7
8
  StructuredToolResultStatus,
8
9
  )
@@ -103,9 +104,7 @@ class GetSlowQueries(BaseAzureSQLTool):
103
104
 
104
105
  return "\n".join(report_sections)
105
106
 
106
- def _invoke(
107
- self, params: dict, user_approved: bool = False
108
- ) -> StructuredToolResult:
107
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
109
108
  try:
110
109
  top_count = params.get("top_count", 15)
111
110
  hours_back = params.get("hours_back", 2)
@@ -3,6 +3,7 @@ from typing import Dict, List, Tuple
3
3
 
4
4
  from holmes.core.tools import (
5
5
  StructuredToolResult,
6
+ ToolInvokeContext,
6
7
  ToolParameter,
7
8
  StructuredToolResultStatus,
8
9
  )
@@ -101,9 +102,7 @@ class GetTopCPUQueries(BaseAzureSQLTool):
101
102
 
102
103
  return "\n".join(report_sections)
103
104
 
104
- def _invoke(
105
- self, params: dict, user_approved: bool = False
106
- ) -> StructuredToolResult:
105
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
107
106
  try:
108
107
  top_count = params.get("top_count", 15)
109
108
  hours_back = params.get("hours_back", 2)
@@ -3,6 +3,7 @@ from typing import Dict, List, Tuple
3
3
 
4
4
  from holmes.core.tools import (
5
5
  StructuredToolResult,
6
+ ToolInvokeContext,
6
7
  ToolParameter,
7
8
  StructuredToolResultStatus,
8
9
  )
@@ -119,9 +120,7 @@ class GetTopDataIOQueries(BaseAzureSQLTool):
119
120
 
120
121
  return "\n".join(report_sections)
121
122
 
122
- def _invoke(
123
- self, params: dict, user_approved: bool = False
124
- ) -> StructuredToolResult:
123
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
125
124
  try:
126
125
  top_count = params.get("top_count", 15)
127
126
  hours_back = params.get("hours_back", 2)
@@ -3,6 +3,7 @@ from typing import Dict, List, Tuple
3
3
 
4
4
  from holmes.core.tools import (
5
5
  StructuredToolResult,
6
+ ToolInvokeContext,
6
7
  ToolParameter,
7
8
  StructuredToolResultStatus,
8
9
  )
@@ -111,9 +112,7 @@ class GetTopLogIOQueries(BaseAzureSQLTool):
111
112
 
112
113
  return "\n".join(report_sections)
113
114
 
114
- def _invoke(
115
- self, params: dict, user_approved: bool = False
116
- ) -> StructuredToolResult:
115
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
117
116
  try:
118
117
  top_count = params.get("top_count", 15)
119
118
  hours_back = params.get("hours_back", 2)