holmesgpt 0.13.2__py3-none-any.whl → 0.18.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- holmes/__init__.py +3 -5
- holmes/clients/robusta_client.py +20 -6
- holmes/common/env_vars.py +58 -3
- holmes/common/openshift.py +1 -1
- holmes/config.py +123 -148
- holmes/core/conversations.py +71 -15
- holmes/core/feedback.py +191 -0
- holmes/core/investigation.py +31 -39
- holmes/core/investigation_structured_output.py +3 -3
- holmes/core/issue.py +1 -1
- holmes/core/llm.py +508 -88
- holmes/core/models.py +108 -4
- holmes/core/openai_formatting.py +14 -1
- holmes/core/prompt.py +48 -3
- holmes/core/runbooks.py +1 -0
- holmes/core/safeguards.py +8 -6
- holmes/core/supabase_dal.py +295 -100
- holmes/core/tool_calling_llm.py +489 -428
- holmes/core/tools.py +325 -56
- holmes/core/tools_utils/token_counting.py +21 -0
- holmes/core/tools_utils/tool_context_window_limiter.py +40 -0
- holmes/core/tools_utils/tool_executor.py +0 -13
- holmes/core/tools_utils/toolset_utils.py +1 -0
- holmes/core/toolset_manager.py +191 -5
- holmes/core/tracing.py +19 -3
- holmes/core/transformers/__init__.py +23 -0
- holmes/core/transformers/base.py +63 -0
- holmes/core/transformers/llm_summarize.py +175 -0
- holmes/core/transformers/registry.py +123 -0
- holmes/core/transformers/transformer.py +32 -0
- holmes/core/truncation/compaction.py +94 -0
- holmes/core/truncation/dal_truncation_utils.py +23 -0
- holmes/core/truncation/input_context_window_limiter.py +219 -0
- holmes/interactive.py +228 -31
- holmes/main.py +23 -40
- holmes/plugins/interfaces.py +2 -1
- holmes/plugins/prompts/__init__.py +2 -1
- holmes/plugins/prompts/_fetch_logs.jinja2 +31 -6
- holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
- holmes/plugins/prompts/_runbook_instructions.jinja2 +24 -12
- holmes/plugins/prompts/base_user_prompt.jinja2 +7 -0
- holmes/plugins/prompts/conversation_history_compaction.jinja2 +89 -0
- holmes/plugins/prompts/generic_ask.jinja2 +0 -4
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +0 -1
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +0 -1
- holmes/plugins/prompts/generic_investigation.jinja2 +0 -1
- holmes/plugins/prompts/investigation_procedure.jinja2 +50 -1
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +0 -1
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +0 -1
- holmes/plugins/runbooks/__init__.py +145 -17
- holmes/plugins/runbooks/catalog.json +2 -0
- holmes/plugins/sources/github/__init__.py +4 -2
- holmes/plugins/sources/prometheus/models.py +1 -0
- holmes/plugins/toolsets/__init__.py +44 -27
- holmes/plugins/toolsets/aks-node-health.yaml +46 -0
- holmes/plugins/toolsets/aks.yaml +64 -0
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +38 -47
- holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +3 -2
- holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +2 -1
- holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +3 -2
- holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +3 -1
- holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +3 -1
- holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +12 -13
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +11 -11
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +11 -9
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +15 -12
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +15 -15
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +11 -8
- holmes/plugins/toolsets/azure_sql/utils.py +0 -32
- holmes/plugins/toolsets/bash/argocd/__init__.py +3 -3
- holmes/plugins/toolsets/bash/aws/__init__.py +4 -4
- holmes/plugins/toolsets/bash/azure/__init__.py +4 -4
- holmes/plugins/toolsets/bash/bash_toolset.py +11 -15
- holmes/plugins/toolsets/bash/common/bash.py +23 -13
- holmes/plugins/toolsets/bash/common/bash_command.py +1 -1
- holmes/plugins/toolsets/bash/common/stringify.py +1 -1
- holmes/plugins/toolsets/bash/kubectl/__init__.py +2 -1
- holmes/plugins/toolsets/bash/kubectl/constants.py +0 -1
- holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +3 -4
- holmes/plugins/toolsets/bash/parse_command.py +12 -13
- holmes/plugins/toolsets/cilium.yaml +284 -0
- holmes/plugins/toolsets/connectivity_check.py +124 -0
- holmes/plugins/toolsets/coralogix/api.py +132 -119
- holmes/plugins/toolsets/coralogix/coralogix.jinja2 +14 -0
- holmes/plugins/toolsets/coralogix/toolset_coralogix.py +219 -0
- holmes/plugins/toolsets/coralogix/utils.py +15 -79
- holmes/plugins/toolsets/datadog/datadog_api.py +525 -26
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +55 -11
- holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +3 -3
- holmes/plugins/toolsets/datadog/datadog_models.py +59 -0
- holmes/plugins/toolsets/datadog/datadog_url_utils.py +213 -0
- holmes/plugins/toolsets/datadog/instructions_datadog_traces.jinja2 +165 -28
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +417 -241
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +234 -214
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +167 -79
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +374 -363
- holmes/plugins/toolsets/elasticsearch/__init__.py +6 -0
- holmes/plugins/toolsets/elasticsearch/elasticsearch.py +834 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_ppl_query_docs.jinja2 +1616 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_query_assist.py +78 -0
- holmes/plugins/toolsets/elasticsearch/opensearch_query_assist_instructions.jinja2 +223 -0
- holmes/plugins/toolsets/git.py +54 -50
- holmes/plugins/toolsets/grafana/base_grafana_toolset.py +16 -4
- holmes/plugins/toolsets/grafana/common.py +13 -29
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +455 -0
- holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +25 -0
- holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +191 -0
- holmes/plugins/toolsets/grafana/loki_api.py +4 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +293 -89
- holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +49 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +820 -292
- holmes/plugins/toolsets/grafana/trace_parser.py +4 -3
- holmes/plugins/toolsets/internet/internet.py +15 -16
- holmes/plugins/toolsets/internet/notion.py +9 -11
- holmes/plugins/toolsets/investigator/core_investigation.py +44 -36
- holmes/plugins/toolsets/investigator/model.py +3 -1
- holmes/plugins/toolsets/json_filter_mixin.py +134 -0
- holmes/plugins/toolsets/kafka.py +36 -42
- holmes/plugins/toolsets/kubernetes.yaml +317 -113
- holmes/plugins/toolsets/kubernetes_logs.py +9 -9
- holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
- holmes/plugins/toolsets/logging_utils/logging_api.py +94 -8
- holmes/plugins/toolsets/mcp/toolset_mcp.py +218 -64
- holmes/plugins/toolsets/newrelic/new_relic_api.py +165 -0
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +65 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +320 -0
- holmes/plugins/toolsets/openshift.yaml +283 -0
- holmes/plugins/toolsets/prometheus/prometheus.py +1202 -421
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +54 -5
- holmes/plugins/toolsets/prometheus/utils.py +28 -0
- holmes/plugins/toolsets/rabbitmq/api.py +23 -4
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +13 -14
- holmes/plugins/toolsets/robusta/robusta.py +239 -68
- holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +157 -27
- holmes/plugins/toolsets/service_discovery.py +1 -1
- holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
- holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
- holmes/plugins/toolsets/utils.py +88 -0
- holmes/utils/config_utils.py +91 -0
- holmes/utils/connection_utils.py +31 -0
- holmes/utils/console/result.py +10 -0
- holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
- holmes/utils/env.py +7 -0
- holmes/utils/file_utils.py +2 -1
- holmes/utils/global_instructions.py +60 -11
- holmes/utils/holmes_status.py +6 -4
- holmes/utils/holmes_sync_toolsets.py +0 -2
- holmes/utils/krr_utils.py +188 -0
- holmes/utils/log.py +15 -0
- holmes/utils/markdown_utils.py +2 -3
- holmes/utils/memory_limit.py +58 -0
- holmes/utils/sentry_helper.py +64 -0
- holmes/utils/stream.py +69 -8
- holmes/utils/tags.py +4 -3
- holmes/version.py +37 -15
- holmesgpt-0.18.4.dist-info/LICENSE +178 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/METADATA +35 -31
- holmesgpt-0.18.4.dist-info/RECORD +258 -0
- holmes/core/performance_timing.py +0 -72
- holmes/plugins/toolsets/aws.yaml +0 -80
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +0 -112
- holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +0 -310
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +0 -739
- holmes/plugins/toolsets/grafana/grafana_api.py +0 -42
- holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
- holmes/plugins/toolsets/newrelic.py +0 -231
- holmes/plugins/toolsets/opensearch/opensearch.py +0 -257
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +0 -161
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +0 -218
- holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +0 -12
- holmes/plugins/toolsets/opensearch/opensearch_utils.py +0 -166
- holmes/plugins/toolsets/servicenow/install.md +0 -37
- holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
- holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
- holmes/utils/keygen_utils.py +0 -6
- holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
- holmesgpt-0.13.2.dist-info/RECORD +0 -234
- /holmes/plugins/toolsets/{opensearch → newrelic}/__init__.py +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/WHEEL +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.18.4.dist-info}/entry_points.txt +0 -0
holmes/core/conversations.py
CHANGED
|
@@ -4,15 +4,17 @@ import sentry_sdk
|
|
|
4
4
|
|
|
5
5
|
from holmes.config import Config
|
|
6
6
|
from holmes.core.models import (
|
|
7
|
-
ToolCallConversationResult,
|
|
8
7
|
IssueChatRequest,
|
|
8
|
+
ToolCallConversationResult,
|
|
9
9
|
WorkloadHealthChatRequest,
|
|
10
10
|
)
|
|
11
|
-
from holmes.
|
|
11
|
+
from holmes.core.prompt import generate_user_prompt
|
|
12
12
|
from holmes.core.tool_calling_llm import ToolCallingLLM
|
|
13
|
+
from holmes.plugins.prompts import load_and_render_prompt
|
|
14
|
+
from holmes.plugins.runbooks import RunbookCatalog
|
|
13
15
|
from holmes.utils.global_instructions import (
|
|
14
16
|
Instructions,
|
|
15
|
-
|
|
17
|
+
generate_runbooks_args,
|
|
16
18
|
)
|
|
17
19
|
|
|
18
20
|
DEFAULT_TOOL_SIZE = 10000
|
|
@@ -26,7 +28,8 @@ def calculate_tool_size(
|
|
|
26
28
|
return DEFAULT_TOOL_SIZE
|
|
27
29
|
|
|
28
30
|
context_window = ai.llm.get_context_window_size()
|
|
29
|
-
|
|
31
|
+
tokens = ai.llm.count_tokens(messages_without_tools)
|
|
32
|
+
message_size_without_tools = tokens.total_tokens
|
|
30
33
|
maximum_output_token = ai.llm.get_maximum_output_token()
|
|
31
34
|
|
|
32
35
|
tool_size = min(
|
|
@@ -63,6 +66,7 @@ def build_issue_chat_messages(
|
|
|
63
66
|
ai: ToolCallingLLM,
|
|
64
67
|
config: Config,
|
|
65
68
|
global_instructions: Optional[Instructions] = None,
|
|
69
|
+
runbooks: Optional[RunbookCatalog] = None,
|
|
66
70
|
):
|
|
67
71
|
"""
|
|
68
72
|
This function generates a list of messages for issue conversation and ensures that the message sequence adheres to the model's context window limitations
|
|
@@ -119,8 +123,13 @@ def build_issue_chat_messages(
|
|
|
119
123
|
tools_for_investigation = issue_chat_request.investigation_result.tools
|
|
120
124
|
|
|
121
125
|
if not conversation_history or len(conversation_history) == 0:
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
runbooks_ctx = generate_runbooks_args(
|
|
127
|
+
runbook_catalog=runbooks,
|
|
128
|
+
global_instructions=global_instructions,
|
|
129
|
+
)
|
|
130
|
+
user_prompt = generate_user_prompt(
|
|
131
|
+
user_prompt,
|
|
132
|
+
runbooks_ctx,
|
|
124
133
|
)
|
|
125
134
|
|
|
126
135
|
number_of_tools_for_investigation = len(tools_for_investigation) # type: ignore
|
|
@@ -133,6 +142,7 @@ def build_issue_chat_messages(
|
|
|
133
142
|
"issue": issue_chat_request.issue_type,
|
|
134
143
|
"toolsets": ai.tool_executor.toolsets,
|
|
135
144
|
"cluster_name": config.cluster_name,
|
|
145
|
+
"runbooks_enabled": True if runbooks else False,
|
|
136
146
|
},
|
|
137
147
|
)
|
|
138
148
|
messages = [
|
|
@@ -153,6 +163,7 @@ def build_issue_chat_messages(
|
|
|
153
163
|
"issue": issue_chat_request.issue_type,
|
|
154
164
|
"toolsets": ai.tool_executor.toolsets,
|
|
155
165
|
"cluster_name": config.cluster_name,
|
|
166
|
+
"runbooks_enabled": True if runbooks else False,
|
|
156
167
|
}
|
|
157
168
|
system_prompt_without_tools = load_and_render_prompt(
|
|
158
169
|
template_path, template_context_without_tools
|
|
@@ -186,6 +197,7 @@ def build_issue_chat_messages(
|
|
|
186
197
|
"issue": issue_chat_request.issue_type,
|
|
187
198
|
"toolsets": ai.tool_executor.toolsets,
|
|
188
199
|
"cluster_name": config.cluster_name,
|
|
200
|
+
"runbooks_enabled": True if runbooks else False,
|
|
189
201
|
}
|
|
190
202
|
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
191
203
|
template_path, truncated_template_context
|
|
@@ -201,8 +213,13 @@ def build_issue_chat_messages(
|
|
|
201
213
|
},
|
|
202
214
|
]
|
|
203
215
|
|
|
204
|
-
|
|
205
|
-
|
|
216
|
+
runbooks_ctx = generate_runbooks_args(
|
|
217
|
+
runbook_catalog=runbooks,
|
|
218
|
+
global_instructions=global_instructions,
|
|
219
|
+
)
|
|
220
|
+
user_prompt = generate_user_prompt(
|
|
221
|
+
user_prompt,
|
|
222
|
+
runbooks_ctx,
|
|
206
223
|
)
|
|
207
224
|
|
|
208
225
|
conversation_history.append(
|
|
@@ -227,6 +244,7 @@ def build_issue_chat_messages(
|
|
|
227
244
|
"issue": issue_chat_request.issue_type,
|
|
228
245
|
"toolsets": ai.tool_executor.toolsets,
|
|
229
246
|
"cluster_name": config.cluster_name,
|
|
247
|
+
"runbooks_enabled": True if runbooks else False,
|
|
230
248
|
}
|
|
231
249
|
system_prompt_without_tools = load_and_render_prompt(
|
|
232
250
|
template_path, template_context_without_tools
|
|
@@ -250,6 +268,7 @@ def build_issue_chat_messages(
|
|
|
250
268
|
"issue": issue_chat_request.issue_type,
|
|
251
269
|
"toolsets": ai.tool_executor.toolsets,
|
|
252
270
|
"cluster_name": config.cluster_name,
|
|
271
|
+
"runbooks_enabled": True if runbooks else False,
|
|
253
272
|
}
|
|
254
273
|
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
255
274
|
template_path, template_context
|
|
@@ -262,7 +281,11 @@ def build_issue_chat_messages(
|
|
|
262
281
|
|
|
263
282
|
|
|
264
283
|
def add_or_update_system_prompt(
|
|
265
|
-
conversation_history: List[Dict[str, str]],
|
|
284
|
+
conversation_history: List[Dict[str, str]],
|
|
285
|
+
ai: ToolCallingLLM,
|
|
286
|
+
config: Config,
|
|
287
|
+
additional_system_prompt: Optional[str] = None,
|
|
288
|
+
runbooks: Optional[RunbookCatalog] = None,
|
|
266
289
|
):
|
|
267
290
|
"""Either add the system prompt or replace an existing system prompt.
|
|
268
291
|
As a 'defensive' measure, this code will only replace an existing system prompt if it is the
|
|
@@ -274,9 +297,12 @@ def add_or_update_system_prompt(
|
|
|
274
297
|
context = {
|
|
275
298
|
"toolsets": ai.tool_executor.toolsets,
|
|
276
299
|
"cluster_name": config.cluster_name,
|
|
300
|
+
"runbooks_enabled": True if runbooks else False,
|
|
277
301
|
}
|
|
278
302
|
|
|
279
303
|
system_prompt = load_and_render_prompt(template_path, context)
|
|
304
|
+
if additional_system_prompt:
|
|
305
|
+
system_prompt = system_prompt + "\n" + additional_system_prompt
|
|
280
306
|
|
|
281
307
|
if not conversation_history or len(conversation_history) == 0:
|
|
282
308
|
conversation_history.append({"role": "system", "content": system_prompt})
|
|
@@ -303,6 +329,8 @@ def build_chat_messages(
|
|
|
303
329
|
ai: ToolCallingLLM,
|
|
304
330
|
config: Config,
|
|
305
331
|
global_instructions: Optional[Instructions] = None,
|
|
332
|
+
additional_system_prompt: Optional[str] = None,
|
|
333
|
+
runbooks: Optional[RunbookCatalog] = None,
|
|
306
334
|
) -> List[dict]:
|
|
307
335
|
"""
|
|
308
336
|
This function generates a list of messages for general chat conversation and ensures that the message sequence adheres to the model's context window limitations
|
|
@@ -358,10 +386,21 @@ def build_chat_messages(
|
|
|
358
386
|
conversation_history = conversation_history.copy()
|
|
359
387
|
|
|
360
388
|
conversation_history = add_or_update_system_prompt(
|
|
361
|
-
conversation_history=conversation_history,
|
|
389
|
+
conversation_history=conversation_history,
|
|
390
|
+
ai=ai,
|
|
391
|
+
config=config,
|
|
392
|
+
additional_system_prompt=additional_system_prompt,
|
|
393
|
+
runbooks=runbooks,
|
|
362
394
|
)
|
|
363
395
|
|
|
364
|
-
|
|
396
|
+
runbooks_ctx = generate_runbooks_args(
|
|
397
|
+
runbook_catalog=runbooks,
|
|
398
|
+
global_instructions=global_instructions,
|
|
399
|
+
)
|
|
400
|
+
ask = generate_user_prompt(
|
|
401
|
+
ask,
|
|
402
|
+
runbooks_ctx,
|
|
403
|
+
)
|
|
365
404
|
|
|
366
405
|
conversation_history.append( # type: ignore
|
|
367
406
|
{
|
|
@@ -369,6 +408,7 @@ def build_chat_messages(
|
|
|
369
408
|
"content": ask,
|
|
370
409
|
},
|
|
371
410
|
)
|
|
411
|
+
|
|
372
412
|
number_of_tools = len(
|
|
373
413
|
[message for message in conversation_history if message.get("role") == "tool"] # type: ignore
|
|
374
414
|
)
|
|
@@ -393,6 +433,7 @@ def build_workload_health_chat_messages(
|
|
|
393
433
|
ai: ToolCallingLLM,
|
|
394
434
|
config: Config,
|
|
395
435
|
global_instructions: Optional[Instructions] = None,
|
|
436
|
+
runbooks: Optional[RunbookCatalog] = None,
|
|
396
437
|
):
|
|
397
438
|
"""
|
|
398
439
|
This function generates a list of messages for workload health conversation and ensures that the message sequence adheres to the model's context window limitations
|
|
@@ -451,8 +492,13 @@ def build_workload_health_chat_messages(
|
|
|
451
492
|
resource = workload_health_chat_request.resource
|
|
452
493
|
|
|
453
494
|
if not conversation_history or len(conversation_history) == 0:
|
|
454
|
-
|
|
455
|
-
|
|
495
|
+
runbooks_ctx = generate_runbooks_args(
|
|
496
|
+
runbook_catalog=runbooks,
|
|
497
|
+
global_instructions=global_instructions,
|
|
498
|
+
)
|
|
499
|
+
user_prompt = generate_user_prompt(
|
|
500
|
+
user_prompt,
|
|
501
|
+
runbooks_ctx,
|
|
456
502
|
)
|
|
457
503
|
|
|
458
504
|
number_of_tools_for_workload = len(tools_for_workload) # type: ignore
|
|
@@ -465,6 +511,7 @@ def build_workload_health_chat_messages(
|
|
|
465
511
|
"resource": resource,
|
|
466
512
|
"toolsets": ai.tool_executor.toolsets,
|
|
467
513
|
"cluster_name": config.cluster_name,
|
|
514
|
+
"runbooks_enabled": True if runbooks else False,
|
|
468
515
|
},
|
|
469
516
|
)
|
|
470
517
|
messages = [
|
|
@@ -485,6 +532,7 @@ def build_workload_health_chat_messages(
|
|
|
485
532
|
"resource": resource,
|
|
486
533
|
"toolsets": ai.tool_executor.toolsets,
|
|
487
534
|
"cluster_name": config.cluster_name,
|
|
535
|
+
"runbooks_enabled": True if runbooks else False,
|
|
488
536
|
}
|
|
489
537
|
system_prompt_without_tools = load_and_render_prompt(
|
|
490
538
|
template_path, template_context_without_tools
|
|
@@ -518,6 +566,7 @@ def build_workload_health_chat_messages(
|
|
|
518
566
|
"resource": resource,
|
|
519
567
|
"toolsets": ai.tool_executor.toolsets,
|
|
520
568
|
"cluster_name": config.cluster_name,
|
|
569
|
+
"runbooks_enabled": True if runbooks else False,
|
|
521
570
|
}
|
|
522
571
|
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
523
572
|
template_path, truncated_template_context
|
|
@@ -533,8 +582,13 @@ def build_workload_health_chat_messages(
|
|
|
533
582
|
},
|
|
534
583
|
]
|
|
535
584
|
|
|
536
|
-
|
|
537
|
-
|
|
585
|
+
runbooks_ctx = generate_runbooks_args(
|
|
586
|
+
runbook_catalog=runbooks,
|
|
587
|
+
global_instructions=global_instructions,
|
|
588
|
+
)
|
|
589
|
+
user_prompt = generate_user_prompt(
|
|
590
|
+
user_prompt,
|
|
591
|
+
runbooks_ctx,
|
|
538
592
|
)
|
|
539
593
|
|
|
540
594
|
conversation_history.append(
|
|
@@ -559,6 +613,7 @@ def build_workload_health_chat_messages(
|
|
|
559
613
|
"resource": resource,
|
|
560
614
|
"toolsets": ai.tool_executor.toolsets,
|
|
561
615
|
"cluster_name": config.cluster_name,
|
|
616
|
+
"runbooks_enabled": True if runbooks else False,
|
|
562
617
|
}
|
|
563
618
|
system_prompt_without_tools = load_and_render_prompt(
|
|
564
619
|
template_path, template_context_without_tools
|
|
@@ -582,6 +637,7 @@ def build_workload_health_chat_messages(
|
|
|
582
637
|
"resource": resource,
|
|
583
638
|
"toolsets": ai.tool_executor.toolsets,
|
|
584
639
|
"cluster_name": config.cluster_name,
|
|
640
|
+
"runbooks_enabled": True if runbooks else False,
|
|
585
641
|
}
|
|
586
642
|
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
587
643
|
template_path, template_context
|
holmes/core/feedback.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
|
|
5
|
+
from .llm import LLM
|
|
6
|
+
|
|
7
|
+
DEFAULT_PRIVACY_NOTICE_BANNER = "Your feedback will be used to improve Holmesgpt's performance. Please avoid sharing sensitive personal information. By continuing, you consent to this data usage."
|
|
8
|
+
PRIVACY_NOTICE_BANNER = os.environ.get(
|
|
9
|
+
"PRIVACY_NOTICE_BANNER", DEFAULT_PRIVACY_NOTICE_BANNER
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FeedbackInfoBase(ABC):
|
|
14
|
+
"""Abstract base class for all feedback-related classes that must implement to_dict()."""
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def to_dict(self) -> dict:
|
|
18
|
+
"""Convert to dictionary representation. Must be implemented by all subclasses."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class FeedbackLLM(FeedbackInfoBase):
|
|
23
|
+
"""Class to represent a LLM in the feedback."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, model: str, max_context_size: int):
|
|
26
|
+
self.model = model
|
|
27
|
+
self.max_context_size = max_context_size
|
|
28
|
+
|
|
29
|
+
def update_from_llm(self, llm: LLM):
|
|
30
|
+
self.model = llm.model
|
|
31
|
+
self.max_context_size = llm.get_context_window_size()
|
|
32
|
+
|
|
33
|
+
def to_dict(self) -> dict:
|
|
34
|
+
"""Convert to dictionary representation."""
|
|
35
|
+
return self.__dict__
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# TODO: extend the FeedbackLLMResponse to include each tool call results details used for evaluate the overall response.
|
|
39
|
+
# Currenlty tool call details in plan:
|
|
40
|
+
# - toolcall parameter and success/failure, toolcall truncation size
|
|
41
|
+
# - Holmes plan (todo list)
|
|
42
|
+
# - Holmes intermediate output
|
|
43
|
+
class FeedbackLLMResponse(FeedbackInfoBase):
|
|
44
|
+
"""Class to represent a LLM response in the feedback"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, user_ask: str, response: str):
|
|
47
|
+
self.user_ask = user_ask
|
|
48
|
+
self.response = response
|
|
49
|
+
|
|
50
|
+
def to_dict(self) -> dict:
|
|
51
|
+
"""Convert to dictionary representation."""
|
|
52
|
+
return self.__dict__
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class FeedbackMetadata(FeedbackInfoBase):
|
|
56
|
+
"""Class to store feedback metadata."""
|
|
57
|
+
|
|
58
|
+
def __init__(self):
|
|
59
|
+
# In iteration mode, there can be multiple ask and response pairs.
|
|
60
|
+
self.llm_responses = []
|
|
61
|
+
self.llm = FeedbackLLM("", 0)
|
|
62
|
+
|
|
63
|
+
def add_llm_response(self, user_ask: str, response: str) -> None:
|
|
64
|
+
"""Add a LLM response to the metadata."""
|
|
65
|
+
llm_response = FeedbackLLMResponse(user_ask, response)
|
|
66
|
+
self.llm_responses.append(llm_response)
|
|
67
|
+
|
|
68
|
+
def update_llm(self, llm: LLM) -> None:
|
|
69
|
+
"""Update the LLM information in the metadata."""
|
|
70
|
+
self.llm.update_from_llm(llm)
|
|
71
|
+
|
|
72
|
+
def to_dict(self) -> dict:
|
|
73
|
+
"""Convert to dictionary representation."""
|
|
74
|
+
return {
|
|
75
|
+
"llm_responses": [resp.to_dict() for resp in self.llm_responses],
|
|
76
|
+
"llm": self.llm.to_dict(),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class UserFeedback(FeedbackInfoBase):
|
|
81
|
+
"""Class to store user rate and comment to the AI response."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, is_positive: bool, comment: Optional[str]):
|
|
84
|
+
self.is_positive = is_positive
|
|
85
|
+
self.comment = comment
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def rating_text(self) -> str:
|
|
89
|
+
"""Return human-readable rating text."""
|
|
90
|
+
return "useful" if self.is_positive else "not useful"
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def rating_emoji(self) -> str:
|
|
94
|
+
"""Return emoji representation of the rating."""
|
|
95
|
+
return "👍" if self.is_positive else "👎"
|
|
96
|
+
|
|
97
|
+
def __str__(self) -> str:
|
|
98
|
+
"""Return string representation of the feedback."""
|
|
99
|
+
if self.comment:
|
|
100
|
+
return f"Rating: {self.rating_text}. Comment: {self.comment}"
|
|
101
|
+
else:
|
|
102
|
+
return f"Rating: {self.rating_text}. No additional comment."
|
|
103
|
+
|
|
104
|
+
def to_dict(self) -> dict:
|
|
105
|
+
"""Convert to dictionary representation."""
|
|
106
|
+
return {
|
|
107
|
+
"is_positive": self.is_positive,
|
|
108
|
+
"comment": self.comment,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Feedback(FeedbackInfoBase):
|
|
113
|
+
"""Class to store overall feedback data used to evaluate the AI response."""
|
|
114
|
+
|
|
115
|
+
def __init__(self):
|
|
116
|
+
self.metadata = FeedbackMetadata()
|
|
117
|
+
self.user_feedback: Optional[UserFeedback] = None
|
|
118
|
+
|
|
119
|
+
def set_user_feedback(self, user_feedback: UserFeedback) -> None:
|
|
120
|
+
"""Set the user feedback."""
|
|
121
|
+
self.user_feedback = user_feedback
|
|
122
|
+
|
|
123
|
+
def to_dict(self) -> dict:
|
|
124
|
+
"""Convert to dictionary representation."""
|
|
125
|
+
return {
|
|
126
|
+
"metadata": self.metadata.to_dict(),
|
|
127
|
+
"user_feedback": self.user_feedback.to_dict()
|
|
128
|
+
if self.user_feedback
|
|
129
|
+
else None,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
FeedbackCallback = Callable[[Feedback], None]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def feedback_callback_example(feedback: Feedback) -> None:
|
|
137
|
+
"""
|
|
138
|
+
Example implementation of a feedback callback function.
|
|
139
|
+
|
|
140
|
+
This function demonstrates how to process feedback data using to_dict() methods
|
|
141
|
+
and could be used for:
|
|
142
|
+
- Logging feedback to files or databases
|
|
143
|
+
- Sending feedback to analytics services
|
|
144
|
+
- Training data collection
|
|
145
|
+
- User satisfaction monitoring
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
feedback: Feedback object containing user feedback and metadata
|
|
149
|
+
"""
|
|
150
|
+
print("\n=== Feedback Received ===")
|
|
151
|
+
|
|
152
|
+
# Convert entire feedback to dict first - this is the main data structure
|
|
153
|
+
feedback_dict = feedback.to_dict()
|
|
154
|
+
print(f"Complete feedback dictionary keys: {list(feedback_dict.keys())}")
|
|
155
|
+
|
|
156
|
+
# How to check user feedback using to_dict()
|
|
157
|
+
print("\n1. Checking User Feedback:")
|
|
158
|
+
user_feedback_dict = (
|
|
159
|
+
feedback.user_feedback.to_dict() if feedback.user_feedback else None
|
|
160
|
+
)
|
|
161
|
+
if user_feedback_dict:
|
|
162
|
+
print(f" User feedback dict: {user_feedback_dict}")
|
|
163
|
+
print(f" Is positive: {user_feedback_dict['is_positive']}")
|
|
164
|
+
print(f" Comment: {user_feedback_dict['comment'] or 'None'}")
|
|
165
|
+
# You can also access properties through the object:
|
|
166
|
+
print(f" Rating emoji: {feedback.user_feedback.rating_emoji}") # type: ignore
|
|
167
|
+
print(f" Rating text: {feedback.user_feedback.rating_text}") # type: ignore
|
|
168
|
+
else:
|
|
169
|
+
print(" No user feedback provided (user_feedback is None)")
|
|
170
|
+
|
|
171
|
+
# How to check LLM information using to_dict()
|
|
172
|
+
print("\n2. Checking LLM Information:")
|
|
173
|
+
metadata_dict = feedback.metadata.to_dict()
|
|
174
|
+
llm_dict = metadata_dict["llm"]
|
|
175
|
+
print(f" LLM dict: {llm_dict}")
|
|
176
|
+
print(f" Model: {llm_dict['model']}")
|
|
177
|
+
print(f" Max context size: {llm_dict['max_context_size']}")
|
|
178
|
+
|
|
179
|
+
# How to check ask and response pairs using to_dict()
|
|
180
|
+
print("\n3. Checking Ask and Response History:")
|
|
181
|
+
llm_responses_dict = metadata_dict["llm_responses"]
|
|
182
|
+
print(f" Number of exchanges: {len(llm_responses_dict)}")
|
|
183
|
+
|
|
184
|
+
for i, response_dict in enumerate(llm_responses_dict, 1):
|
|
185
|
+
print(f" Exchange {i} dict: {list(response_dict.keys())}")
|
|
186
|
+
user_ask = response_dict["user_ask"]
|
|
187
|
+
ai_response = response_dict["response"]
|
|
188
|
+
print(f" User ask: {user_ask}")
|
|
189
|
+
print(f" AI response: {ai_response}")
|
|
190
|
+
|
|
191
|
+
print("=== End Feedback ===\n")
|
holmes/core/investigation.py
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
from holmes.common.env_vars import HOLMES_POST_PROCESSING_PROMPT
|
|
5
4
|
from holmes.config import Config
|
|
6
|
-
from holmes.core.investigation_structured_output import process_response_into_sections
|
|
7
|
-
from holmes.core.issue import Issue
|
|
8
|
-
from holmes.core.models import InvestigateRequest, InvestigationResult
|
|
9
|
-
from holmes.core.supabase_dal import SupabaseDal
|
|
10
|
-
from holmes.core.tracing import DummySpan, SpanType
|
|
11
|
-
from holmes.utils.global_instructions import add_global_instructions_to_user_prompt
|
|
12
|
-
|
|
13
5
|
from holmes.core.investigation_structured_output import (
|
|
14
6
|
DEFAULT_SECTIONS,
|
|
15
7
|
REQUEST_STRUCTURED_OUTPUT_FROM_LLM,
|
|
16
8
|
get_output_format_for_investigation,
|
|
9
|
+
process_response_into_sections,
|
|
17
10
|
)
|
|
18
|
-
|
|
11
|
+
from holmes.core.issue import Issue
|
|
12
|
+
from holmes.core.models import InvestigateRequest, InvestigationResult
|
|
13
|
+
from holmes.core.prompt import generate_user_prompt
|
|
14
|
+
from holmes.core.supabase_dal import SupabaseDal
|
|
15
|
+
from holmes.core.tracing import DummySpan, SpanType
|
|
19
16
|
from holmes.plugins.prompts import load_and_render_prompt
|
|
17
|
+
from holmes.plugins.runbooks import RunbookCatalog
|
|
18
|
+
from holmes.utils import sentry_helper
|
|
19
|
+
from holmes.utils.global_instructions import generate_runbooks_args
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def investigate_issues(
|
|
@@ -25,13 +25,10 @@ def investigate_issues(
|
|
|
25
25
|
config: Config,
|
|
26
26
|
model: Optional[str] = None,
|
|
27
27
|
trace_span=DummySpan(),
|
|
28
|
+
runbooks: Optional[RunbookCatalog] = None,
|
|
28
29
|
) -> InvestigationResult:
|
|
29
|
-
config.load_robusta_api_key(dal=dal)
|
|
30
30
|
context = dal.get_issue_data(investigate_request.context.get("robusta_issue_id"))
|
|
31
31
|
|
|
32
|
-
resource_instructions = dal.get_resource_instructions(
|
|
33
|
-
"alert", investigate_request.context.get("issue_type")
|
|
34
|
-
)
|
|
35
32
|
global_instructions = dal.get_global_instructions_for_account()
|
|
36
33
|
|
|
37
34
|
raw_data = investigate_request.model_dump()
|
|
@@ -56,21 +53,25 @@ def investigate_issues(
|
|
|
56
53
|
investigation = ai.investigate(
|
|
57
54
|
issue,
|
|
58
55
|
prompt=investigate_request.prompt_template,
|
|
59
|
-
post_processing_prompt=HOLMES_POST_PROCESSING_PROMPT,
|
|
60
|
-
instructions=resource_instructions,
|
|
61
56
|
global_instructions=global_instructions,
|
|
62
57
|
sections=investigate_request.sections,
|
|
63
58
|
trace_span=trace_span,
|
|
59
|
+
runbooks=runbooks,
|
|
64
60
|
)
|
|
65
61
|
|
|
66
62
|
(text_response, sections) = process_response_into_sections(investigation.result)
|
|
67
63
|
|
|
64
|
+
if sections is None:
|
|
65
|
+
sentry_helper.capture_sections_none(content=investigation.result)
|
|
66
|
+
|
|
68
67
|
logging.debug(f"text response: {text_response}")
|
|
69
68
|
return InvestigationResult(
|
|
70
69
|
analysis=text_response,
|
|
71
70
|
sections=sections,
|
|
72
71
|
tool_calls=investigation.tool_calls or [],
|
|
72
|
+
num_llm_calls=investigation.num_llm_calls,
|
|
73
73
|
instructions=investigation.instructions,
|
|
74
|
+
metadata=investigation.metadata,
|
|
74
75
|
)
|
|
75
76
|
|
|
76
77
|
|
|
@@ -80,7 +81,6 @@ def get_investigation_context(
|
|
|
80
81
|
config: Config,
|
|
81
82
|
request_structured_output_from_llm: Optional[bool] = None,
|
|
82
83
|
):
|
|
83
|
-
config.load_robusta_api_key(dal=dal)
|
|
84
84
|
ai = config.create_issue_investigator(dal=dal, model=investigate_request.model)
|
|
85
85
|
|
|
86
86
|
raw_data = investigate_request.model_dump()
|
|
@@ -96,18 +96,7 @@ def get_investigation_context(
|
|
|
96
96
|
raw=raw_data,
|
|
97
97
|
)
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
instructions = dal.get_resource_instructions(
|
|
102
|
-
"alert", investigate_request.context.get("issue_type")
|
|
103
|
-
)
|
|
104
|
-
if instructions is not None and instructions.instructions:
|
|
105
|
-
runbooks.extend(instructions.instructions)
|
|
106
|
-
if instructions is not None and len(instructions.documents) > 0:
|
|
107
|
-
docPrompts = []
|
|
108
|
-
for document in instructions.documents:
|
|
109
|
-
docPrompts.append(f"* fetch information from this URL: {document.url}\n")
|
|
110
|
-
runbooks.extend(docPrompts)
|
|
99
|
+
issue_instructions = ai.runbook_manager.get_instructions_for_issue(issue)
|
|
111
100
|
|
|
112
101
|
# This section is about setting vars to request the LLM to return structured output.
|
|
113
102
|
# It does not mean that Holmes will not return structured sections for investigation as it is
|
|
@@ -132,6 +121,7 @@ def get_investigation_context(
|
|
|
132
121
|
else:
|
|
133
122
|
logging.info("Structured output is disabled for this request")
|
|
134
123
|
|
|
124
|
+
runbook_catalog = config.get_runbook_catalog()
|
|
135
125
|
system_prompt = load_and_render_prompt(
|
|
136
126
|
investigate_request.prompt_template,
|
|
137
127
|
{
|
|
@@ -140,21 +130,23 @@ def get_investigation_context(
|
|
|
140
130
|
"structured_output": request_structured_output_from_llm,
|
|
141
131
|
"toolsets": ai.tool_executor.toolsets,
|
|
142
132
|
"cluster_name": config.cluster_name,
|
|
133
|
+
"runbooks_enabled": True if runbook_catalog else False,
|
|
143
134
|
},
|
|
144
135
|
)
|
|
145
|
-
|
|
146
|
-
user_prompt = ""
|
|
147
|
-
if runbooks:
|
|
148
|
-
for runbook_str in runbooks:
|
|
149
|
-
user_prompt += f"* {runbook_str}\n"
|
|
150
|
-
|
|
151
|
-
user_prompt = f'My instructions to check \n"""{user_prompt}"""'
|
|
136
|
+
base_user = ""
|
|
152
137
|
|
|
153
138
|
global_instructions = dal.get_global_instructions_for_account()
|
|
154
|
-
|
|
155
|
-
|
|
139
|
+
runbooks_ctx = generate_runbooks_args(
|
|
140
|
+
runbook_catalog=runbook_catalog,
|
|
141
|
+
global_instructions=global_instructions,
|
|
142
|
+
issue_instructions=issue_instructions,
|
|
156
143
|
)
|
|
157
144
|
|
|
158
|
-
|
|
145
|
+
base_user = f"{base_user}\n #This is context from the issue:\n{issue.raw}"
|
|
146
|
+
|
|
147
|
+
user_prompt = generate_user_prompt(
|
|
148
|
+
base_user,
|
|
149
|
+
runbooks_ctx,
|
|
150
|
+
)
|
|
159
151
|
|
|
160
|
-
return ai, system_prompt, user_prompt, response_format, sections,
|
|
152
|
+
return ai, system_prompt, user_prompt, response_format, sections, issue_instructions
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from typing import Any, Dict, Optional, Tuple
|
|
3
1
|
import json
|
|
2
|
+
import logging
|
|
4
3
|
import re
|
|
5
4
|
from contextlib import suppress
|
|
6
|
-
from
|
|
5
|
+
from typing import Any, Dict, Optional, Tuple
|
|
7
6
|
|
|
7
|
+
from holmes.common.env_vars import load_bool
|
|
8
8
|
|
|
9
9
|
REQUEST_STRUCTURED_OUTPUT_FROM_LLM = load_bool(
|
|
10
10
|
"REQUEST_STRUCTURED_OUTPUT_FROM_LLM", True
|