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.
- holmes/__init__.py +1 -1
- holmes/clients/robusta_client.py +5 -2
- holmes/common/env_vars.py +8 -2
- holmes/config.py +4 -7
- holmes/core/conversations.py +12 -2
- holmes/core/feedback.py +191 -0
- holmes/core/llm.py +52 -10
- holmes/core/models.py +101 -1
- holmes/core/supabase_dal.py +23 -9
- holmes/core/tool_calling_llm.py +206 -16
- holmes/core/tools.py +20 -7
- holmes/core/tools_utils/token_counting.py +13 -0
- holmes/core/tools_utils/tool_context_window_limiter.py +45 -23
- holmes/core/tools_utils/tool_executor.py +11 -6
- holmes/core/toolset_manager.py +7 -3
- holmes/core/truncation/dal_truncation_utils.py +23 -0
- holmes/interactive.py +146 -14
- holmes/plugins/prompts/_fetch_logs.jinja2 +13 -1
- holmes/plugins/runbooks/__init__.py +6 -1
- holmes/plugins/toolsets/__init__.py +11 -4
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +9 -20
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +6 -4
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +6 -4
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +6 -4
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +2 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +2 -3
- holmes/plugins/toolsets/bash/bash_toolset.py +4 -7
- holmes/plugins/toolsets/cilium.yaml +284 -0
- holmes/plugins/toolsets/datadog/datadog_api.py +490 -24
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +21 -10
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +333 -199
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +181 -9
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +80 -22
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +5 -8
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +7 -12
- holmes/plugins/toolsets/git.py +14 -12
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +23 -42
- holmes/plugins/toolsets/grafana/toolset_grafana.py +2 -3
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +2 -1
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +21 -39
- holmes/plugins/toolsets/internet/internet.py +2 -3
- holmes/plugins/toolsets/internet/notion.py +2 -3
- holmes/plugins/toolsets/investigator/core_investigation.py +7 -9
- holmes/plugins/toolsets/kafka.py +7 -18
- holmes/plugins/toolsets/logging_utils/logging_api.py +80 -4
- holmes/plugins/toolsets/mcp/toolset_mcp.py +2 -3
- holmes/plugins/toolsets/newrelic/__init__.py +0 -0
- holmes/plugins/toolsets/newrelic/new_relic_api.py +125 -0
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +41 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +211 -0
- holmes/plugins/toolsets/opensearch/opensearch.py +5 -12
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +3 -6
- holmes/plugins/toolsets/prometheus/prometheus.py +808 -419
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +27 -11
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +3 -6
- holmes/plugins/toolsets/robusta/robusta.py +4 -9
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +93 -13
- holmes/plugins/toolsets/servicenow/servicenow.py +5 -10
- holmes/utils/sentry_helper.py +1 -1
- holmes/utils/stream.py +22 -7
- holmes/version.py +34 -14
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/METADATA +7 -9
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/RECORD +71 -65
- holmes/core/tools_utils/data_types.py +0 -81
- holmes/plugins/toolsets/newrelic.py +0 -231
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/LICENSE.txt +0 -0
- {holmesgpt-0.14.1a0.dist-info → holmesgpt-0.14.3a0.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 = [
|
|
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}'.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|