holmesgpt 0.16.2a0__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 +4 -3
- holmes/common/env_vars.py +18 -2
- holmes/common/openshift.py +1 -1
- holmes/config.py +11 -6
- holmes/core/conversations.py +30 -13
- holmes/core/investigation.py +21 -25
- holmes/core/investigation_structured_output.py +3 -3
- holmes/core/issue.py +1 -1
- holmes/core/llm.py +50 -31
- holmes/core/models.py +19 -17
- holmes/core/openai_formatting.py +1 -1
- holmes/core/prompt.py +47 -2
- holmes/core/runbooks.py +1 -0
- holmes/core/safeguards.py +4 -2
- holmes/core/supabase_dal.py +4 -2
- holmes/core/tool_calling_llm.py +102 -141
- holmes/core/tools.py +19 -28
- holmes/core/tools_utils/token_counting.py +9 -2
- holmes/core/tools_utils/tool_context_window_limiter.py +13 -30
- holmes/core/tools_utils/tool_executor.py +0 -18
- holmes/core/tools_utils/toolset_utils.py +1 -0
- holmes/core/toolset_manager.py +37 -2
- holmes/core/tracing.py +13 -2
- holmes/core/transformers/__init__.py +1 -1
- holmes/core/transformers/base.py +1 -0
- holmes/core/transformers/llm_summarize.py +3 -2
- holmes/core/transformers/registry.py +2 -1
- holmes/core/transformers/transformer.py +1 -0
- holmes/core/truncation/compaction.py +37 -2
- holmes/core/truncation/input_context_window_limiter.py +3 -2
- holmes/interactive.py +52 -8
- holmes/main.py +17 -37
- holmes/plugins/interfaces.py +2 -1
- holmes/plugins/prompts/__init__.py +2 -1
- holmes/plugins/prompts/_fetch_logs.jinja2 +5 -5
- holmes/plugins/prompts/_runbook_instructions.jinja2 +2 -1
- holmes/plugins/prompts/base_user_prompt.jinja2 +7 -0
- holmes/plugins/prompts/conversation_history_compaction.jinja2 +2 -1
- holmes/plugins/prompts/generic_ask.jinja2 +0 -2
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +0 -2
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +0 -2
- holmes/plugins/prompts/generic_investigation.jinja2 +0 -2
- holmes/plugins/prompts/investigation_procedure.jinja2 +2 -1
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +0 -2
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +0 -2
- holmes/plugins/runbooks/__init__.py +32 -3
- holmes/plugins/sources/github/__init__.py +4 -2
- holmes/plugins/sources/prometheus/models.py +1 -0
- holmes/plugins/toolsets/__init__.py +30 -26
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +13 -12
- 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 -12
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +7 -7
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +7 -7
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +3 -5
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +3 -3
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +7 -7
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +6 -8
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +3 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +3 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +3 -3
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +3 -3
- 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 +2 -3
- holmes/plugins/toolsets/bash/common/bash.py +19 -9
- 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/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 +36 -3
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +34 -1
- 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 +71 -28
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +224 -375
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +67 -36
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +360 -343
- holmes/plugins/toolsets/elasticsearch/__init__.py +6 -0
- holmes/plugins/toolsets/elasticsearch/elasticsearch.py +834 -0
- holmes/plugins/toolsets/git.py +7 -8
- holmes/plugins/toolsets/grafana/base_grafana_toolset.py +16 -4
- holmes/plugins/toolsets/grafana/common.py +2 -30
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +2 -1
- holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +18 -2
- holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +92 -18
- holmes/plugins/toolsets/grafana/loki_api.py +4 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +109 -25
- holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +22 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +201 -33
- holmes/plugins/toolsets/grafana/trace_parser.py +3 -2
- holmes/plugins/toolsets/internet/internet.py +10 -10
- holmes/plugins/toolsets/internet/notion.py +5 -6
- holmes/plugins/toolsets/investigator/core_investigation.py +3 -3
- holmes/plugins/toolsets/investigator/model.py +3 -1
- holmes/plugins/toolsets/json_filter_mixin.py +134 -0
- holmes/plugins/toolsets/kafka.py +12 -7
- holmes/plugins/toolsets/kubernetes.yaml +260 -30
- holmes/plugins/toolsets/kubernetes_logs.py +3 -3
- holmes/plugins/toolsets/logging_utils/logging_api.py +16 -6
- holmes/plugins/toolsets/mcp/toolset_mcp.py +88 -60
- holmes/plugins/toolsets/newrelic/new_relic_api.py +41 -1
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +24 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +212 -55
- holmes/plugins/toolsets/prometheus/prometheus.py +358 -102
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +11 -3
- holmes/plugins/toolsets/rabbitmq/api.py +23 -4
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +5 -5
- holmes/plugins/toolsets/robusta/robusta.py +5 -5
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +25 -6
- holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +1 -1
- holmes/plugins/toolsets/utils.py +1 -1
- holmes/utils/config_utils.py +1 -1
- holmes/utils/connection_utils.py +31 -0
- holmes/utils/console/result.py +10 -0
- holmes/utils/file_utils.py +2 -1
- holmes/utils/global_instructions.py +10 -26
- holmes/utils/holmes_status.py +4 -3
- 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 +23 -0
- holmes/utils/stream.py +12 -5
- holmes/utils/tags.py +4 -3
- holmes/version.py +3 -1
- {holmesgpt-0.16.2a0.dist-info → holmesgpt-0.18.4.dist-info}/METADATA +12 -10
- holmesgpt-0.18.4.dist-info/RECORD +258 -0
- holmes/plugins/toolsets/aws.yaml +0 -80
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +0 -114
- holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +0 -310
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +0 -736
- holmes/plugins/toolsets/grafana/grafana_api.py +0 -64
- holmes/plugins/toolsets/opensearch/__init__.py +0 -0
- holmes/plugins/toolsets/opensearch/opensearch.py +0 -250
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +0 -161
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +0 -215
- holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +0 -12
- holmes/plugins/toolsets/opensearch/opensearch_utils.py +0 -166
- holmes/utils/keygen_utils.py +0 -6
- holmesgpt-0.16.2a0.dist-info/RECORD +0 -258
- holmes/plugins/toolsets/{opensearch → elasticsearch}/opensearch_ppl_query_docs.jinja2 +0 -0
- holmes/plugins/toolsets/{opensearch → elasticsearch}/opensearch_query_assist.py +2 -2
- /holmes/plugins/toolsets/{opensearch → elasticsearch}/opensearch_query_assist_instructions.jinja2 +0 -0
- {holmesgpt-0.16.2a0.dist-info → holmesgpt-0.18.4.dist-info}/LICENSE +0 -0
- {holmesgpt-0.16.2a0.dist-info → holmesgpt-0.18.4.dist-info}/WHEEL +0 -0
- {holmesgpt-0.16.2a0.dist-info → holmesgpt-0.18.4.dist-info}/entry_points.txt +0 -0
holmes/__init__.py
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# This is patched by github actions during release
|
|
2
|
-
__version__ = "0.
|
|
2
|
+
__version__ = "0.18.4"
|
|
3
3
|
|
|
4
4
|
# Re-export version functions from version module for backward compatibility
|
|
5
|
-
from .version import
|
|
6
|
-
|
|
7
|
-
is_official_release as is_official_release,
|
|
8
|
-
)
|
|
5
|
+
from .version import get_version as get_version
|
|
6
|
+
from .version import is_official_release as is_official_release
|
holmes/clients/robusta_client.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import Optional, Dict, Any
|
|
3
|
-
import requests # type: ignore
|
|
4
2
|
from functools import cache
|
|
3
|
+
from typing import Any, Dict, Optional
|
|
4
|
+
|
|
5
|
+
import requests # type: ignore
|
|
5
6
|
from pydantic import BaseModel, ConfigDict
|
|
7
|
+
|
|
6
8
|
from holmes.common.env_vars import ROBUSTA_API_ENDPOINT
|
|
7
9
|
|
|
8
10
|
HOLMES_GET_INFO_URL = f"{ROBUSTA_API_ENDPOINT}/api/holmes/get_info"
|
|
@@ -25,7 +27,6 @@ class RobustaModelsResponse(BaseModel):
|
|
|
25
27
|
models: Dict[str, RobustaModel]
|
|
26
28
|
|
|
27
29
|
|
|
28
|
-
@cache
|
|
29
30
|
def fetch_robusta_models(
|
|
30
31
|
account_id: str, token: str
|
|
31
32
|
) -> Optional[RobustaModelsResponse]:
|
holmes/common/env_vars.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import os
|
|
2
1
|
import json
|
|
2
|
+
import os
|
|
3
|
+
import platform
|
|
3
4
|
from typing import Optional
|
|
4
5
|
|
|
5
6
|
# Recommended models for different providers
|
|
@@ -35,7 +36,6 @@ STORE_URL = os.environ.get("STORE_URL", "")
|
|
|
35
36
|
STORE_API_KEY = os.environ.get("STORE_API_KEY", "")
|
|
36
37
|
STORE_EMAIL = os.environ.get("STORE_EMAIL", "")
|
|
37
38
|
STORE_PASSWORD = os.environ.get("STORE_PASSWORD", "")
|
|
38
|
-
HOLMES_POST_PROCESSING_PROMPT = os.environ.get("HOLMES_POST_PROCESSING_PROMPT", "")
|
|
39
39
|
ROBUSTA_AI = load_bool("ROBUSTA_AI", None)
|
|
40
40
|
LOAD_ALL_ROBUSTA_MODELS = load_bool("LOAD_ALL_ROBUSTA_MODELS", True)
|
|
41
41
|
ROBUSTA_API_ENDPOINT = os.environ.get("ROBUSTA_API_ENDPOINT", "https://api.robusta.dev")
|
|
@@ -53,6 +53,15 @@ THINKING = os.environ.get("THINKING", "")
|
|
|
53
53
|
REASONING_EFFORT = os.environ.get("REASONING_EFFORT", "").strip().lower()
|
|
54
54
|
TEMPERATURE = float(os.environ.get("TEMPERATURE", "0.00000001"))
|
|
55
55
|
|
|
56
|
+
# Set default memory limit based on CPU architecture
|
|
57
|
+
# ARM architectures typically need more memory
|
|
58
|
+
_default_memory_limit = (
|
|
59
|
+
1500 if platform.machine().lower() in ("arm64", "aarch64", "arm") else 800
|
|
60
|
+
)
|
|
61
|
+
TOOL_MEMORY_LIMIT_MB = int(
|
|
62
|
+
os.environ.get("TOOL_MEMORY_LIMIT_MB", _default_memory_limit)
|
|
63
|
+
)
|
|
64
|
+
|
|
56
65
|
STREAM_CHUNKS_PER_PARSE = int(
|
|
57
66
|
os.environ.get("STREAM_CHUNKS_PER_PARSE", 80)
|
|
58
67
|
) # Empirical value with 6~ parsing calls. Consider using larger value if LLM response is long as to reduce markdown to section calls.
|
|
@@ -113,3 +122,10 @@ RESET_REPEATED_TOOL_CALL_CHECK_AFTER_COMPACTION = load_bool(
|
|
|
113
122
|
)
|
|
114
123
|
|
|
115
124
|
SSE_READ_TIMEOUT = float(os.environ.get("SSE_READ_TIMEOUT", "120"))
|
|
125
|
+
|
|
126
|
+
LLM_REQUEST_TIMEOUT = float(os.environ.get("LLM_REQUEST_TIMEOUT", "600"))
|
|
127
|
+
|
|
128
|
+
ENABLE_CONNECTION_KEEPALIVE = load_bool("ENABLE_CONNECTION_KEEPALIVE", False)
|
|
129
|
+
KEEPALIVE_IDLE = int(os.environ.get("KEEPALIVE_IDLE", 2))
|
|
130
|
+
KEEPALIVE_INTVL = int(os.environ.get("KEEPALIVE_INTVL", 2))
|
|
131
|
+
KEEPALIVE_CNT = int(os.environ.get("KEEPALIVE_CNT", 5))
|
holmes/common/openshift.py
CHANGED
holmes/config.py
CHANGED
|
@@ -84,6 +84,7 @@ class Config(RobustaBaseConfig):
|
|
|
84
84
|
opsgenie_query: Optional[str] = None
|
|
85
85
|
|
|
86
86
|
custom_runbooks: List[FilePath] = []
|
|
87
|
+
custom_runbook_catalogs: List[Union[str, FilePath]] = []
|
|
87
88
|
|
|
88
89
|
# custom_toolsets is passed from config file, and be used to override built-in toolsets, provides 'stable' customized toolset.
|
|
89
90
|
# The status of custom toolsets can be cached.
|
|
@@ -114,6 +115,7 @@ class Config(RobustaBaseConfig):
|
|
|
114
115
|
custom_toolsets=self.custom_toolsets,
|
|
115
116
|
custom_toolsets_from_cli=self.custom_toolsets_from_cli,
|
|
116
117
|
global_fast_model=self.fast_model,
|
|
118
|
+
custom_runbook_catalogs=self.custom_runbook_catalogs,
|
|
117
119
|
)
|
|
118
120
|
return self._toolset_manager
|
|
119
121
|
|
|
@@ -224,8 +226,9 @@ class Config(RobustaBaseConfig):
|
|
|
224
226
|
return None
|
|
225
227
|
|
|
226
228
|
def get_runbook_catalog(self) -> Optional[RunbookCatalog]:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
+
runbook_catalog = load_runbook_catalog(
|
|
230
|
+
dal=self.dal, custom_catalog_paths=self.custom_runbook_catalogs
|
|
231
|
+
)
|
|
229
232
|
return runbook_catalog
|
|
230
233
|
|
|
231
234
|
def create_console_tool_executor(
|
|
@@ -285,12 +288,15 @@ class Config(RobustaBaseConfig):
|
|
|
285
288
|
dal: Optional["SupabaseDal"] = None,
|
|
286
289
|
refresh_toolsets: bool = False,
|
|
287
290
|
tracer=None,
|
|
291
|
+
model_name: Optional[str] = None,
|
|
288
292
|
) -> "ToolCallingLLM":
|
|
289
293
|
tool_executor = self.create_console_tool_executor(dal, refresh_toolsets)
|
|
290
294
|
from holmes.core.tool_calling_llm import ToolCallingLLM
|
|
291
295
|
|
|
292
296
|
return ToolCallingLLM(
|
|
293
|
-
tool_executor,
|
|
297
|
+
tool_executor,
|
|
298
|
+
self.max_steps,
|
|
299
|
+
self._get_llm(tracer=tracer, model_key=model_name),
|
|
294
300
|
)
|
|
295
301
|
|
|
296
302
|
def create_agui_toolcalling_llm(
|
|
@@ -344,7 +350,7 @@ class Config(RobustaBaseConfig):
|
|
|
344
350
|
)
|
|
345
351
|
|
|
346
352
|
def create_console_issue_investigator(
|
|
347
|
-
self, dal: Optional["SupabaseDal"] = None
|
|
353
|
+
self, dal: Optional["SupabaseDal"] = None, model_name: Optional[str] = None
|
|
348
354
|
) -> "IssueInvestigator":
|
|
349
355
|
all_runbooks = load_builtin_runbooks()
|
|
350
356
|
for runbook_path in self.custom_runbooks:
|
|
@@ -360,7 +366,7 @@ class Config(RobustaBaseConfig):
|
|
|
360
366
|
tool_executor=tool_executor,
|
|
361
367
|
runbook_manager=runbook_manager,
|
|
362
368
|
max_steps=self.max_steps,
|
|
363
|
-
llm=self._get_llm(),
|
|
369
|
+
llm=self._get_llm(model_key=model_name),
|
|
364
370
|
cluster_name=self.cluster_name,
|
|
365
371
|
)
|
|
366
372
|
|
|
@@ -478,7 +484,6 @@ class Config(RobustaBaseConfig):
|
|
|
478
484
|
model_params = model_entry.model_dump(exclude_none=True)
|
|
479
485
|
api_base = self.api_base
|
|
480
486
|
api_version = self.api_version
|
|
481
|
-
|
|
482
487
|
is_robusta_model = model_params.pop("is_robusta_model", False)
|
|
483
488
|
sentry_sdk.set_tag("is_robusta_model", is_robusta_model)
|
|
484
489
|
if is_robusta_model:
|
holmes/core/conversations.py
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
from typing import Dict, List, Optional
|
|
2
2
|
|
|
3
3
|
import sentry_sdk
|
|
4
|
+
|
|
4
5
|
from holmes.config import Config
|
|
5
6
|
from holmes.core.models import (
|
|
6
|
-
ToolCallConversationResult,
|
|
7
7
|
IssueChatRequest,
|
|
8
|
+
ToolCallConversationResult,
|
|
8
9
|
WorkloadHealthChatRequest,
|
|
9
10
|
)
|
|
10
|
-
from holmes.
|
|
11
|
+
from holmes.core.prompt import generate_user_prompt
|
|
11
12
|
from holmes.core.tool_calling_llm import ToolCallingLLM
|
|
13
|
+
from holmes.plugins.prompts import load_and_render_prompt
|
|
12
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
|
|
@@ -121,11 +123,14 @@ def build_issue_chat_messages(
|
|
|
121
123
|
tools_for_investigation = issue_chat_request.investigation_result.tools
|
|
122
124
|
|
|
123
125
|
if not conversation_history or len(conversation_history) == 0:
|
|
124
|
-
|
|
125
|
-
user_prompt=user_prompt,
|
|
126
|
+
runbooks_ctx = generate_runbooks_args(
|
|
126
127
|
runbook_catalog=runbooks,
|
|
127
128
|
global_instructions=global_instructions,
|
|
128
129
|
)
|
|
130
|
+
user_prompt = generate_user_prompt(
|
|
131
|
+
user_prompt,
|
|
132
|
+
runbooks_ctx,
|
|
133
|
+
)
|
|
129
134
|
|
|
130
135
|
number_of_tools_for_investigation = len(tools_for_investigation) # type: ignore
|
|
131
136
|
if number_of_tools_for_investigation == 0:
|
|
@@ -208,11 +213,14 @@ def build_issue_chat_messages(
|
|
|
208
213
|
},
|
|
209
214
|
]
|
|
210
215
|
|
|
211
|
-
|
|
212
|
-
user_prompt=user_prompt,
|
|
216
|
+
runbooks_ctx = generate_runbooks_args(
|
|
213
217
|
runbook_catalog=runbooks,
|
|
214
218
|
global_instructions=global_instructions,
|
|
215
219
|
)
|
|
220
|
+
user_prompt = generate_user_prompt(
|
|
221
|
+
user_prompt,
|
|
222
|
+
runbooks_ctx,
|
|
223
|
+
)
|
|
216
224
|
|
|
217
225
|
conversation_history.append(
|
|
218
226
|
{
|
|
@@ -385,11 +393,14 @@ def build_chat_messages(
|
|
|
385
393
|
runbooks=runbooks,
|
|
386
394
|
)
|
|
387
395
|
|
|
388
|
-
|
|
389
|
-
user_prompt=ask,
|
|
396
|
+
runbooks_ctx = generate_runbooks_args(
|
|
390
397
|
runbook_catalog=runbooks,
|
|
391
398
|
global_instructions=global_instructions,
|
|
392
399
|
)
|
|
400
|
+
ask = generate_user_prompt(
|
|
401
|
+
ask,
|
|
402
|
+
runbooks_ctx,
|
|
403
|
+
)
|
|
393
404
|
|
|
394
405
|
conversation_history.append( # type: ignore
|
|
395
406
|
{
|
|
@@ -481,11 +492,14 @@ def build_workload_health_chat_messages(
|
|
|
481
492
|
resource = workload_health_chat_request.resource
|
|
482
493
|
|
|
483
494
|
if not conversation_history or len(conversation_history) == 0:
|
|
484
|
-
|
|
485
|
-
user_prompt=user_prompt,
|
|
495
|
+
runbooks_ctx = generate_runbooks_args(
|
|
486
496
|
runbook_catalog=runbooks,
|
|
487
497
|
global_instructions=global_instructions,
|
|
488
498
|
)
|
|
499
|
+
user_prompt = generate_user_prompt(
|
|
500
|
+
user_prompt,
|
|
501
|
+
runbooks_ctx,
|
|
502
|
+
)
|
|
489
503
|
|
|
490
504
|
number_of_tools_for_workload = len(tools_for_workload) # type: ignore
|
|
491
505
|
if number_of_tools_for_workload == 0:
|
|
@@ -568,11 +582,14 @@ def build_workload_health_chat_messages(
|
|
|
568
582
|
},
|
|
569
583
|
]
|
|
570
584
|
|
|
571
|
-
|
|
572
|
-
user_prompt=user_prompt,
|
|
585
|
+
runbooks_ctx = generate_runbooks_args(
|
|
573
586
|
runbook_catalog=runbooks,
|
|
574
587
|
global_instructions=global_instructions,
|
|
575
588
|
)
|
|
589
|
+
user_prompt = generate_user_prompt(
|
|
590
|
+
user_prompt,
|
|
591
|
+
runbooks_ctx,
|
|
592
|
+
)
|
|
576
593
|
|
|
577
594
|
conversation_history.append(
|
|
578
595
|
{
|
holmes/core/investigation.py
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Optional
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
from holmes.common.env_vars import HOLMES_POST_PROCESSING_PROMPT
|
|
6
4
|
from holmes.config import Config
|
|
7
|
-
from holmes.core.investigation_structured_output import process_response_into_sections
|
|
8
|
-
from holmes.core.issue import Issue
|
|
9
|
-
from holmes.core.models import InvestigateRequest, InvestigationResult
|
|
10
|
-
from holmes.core.supabase_dal import SupabaseDal
|
|
11
|
-
from holmes.core.tracing import DummySpan, SpanType
|
|
12
|
-
from holmes.plugins.runbooks import RunbookCatalog
|
|
13
|
-
from holmes.utils.global_instructions import add_runbooks_to_user_prompt
|
|
14
|
-
|
|
15
5
|
from holmes.core.investigation_structured_output import (
|
|
16
6
|
DEFAULT_SECTIONS,
|
|
17
7
|
REQUEST_STRUCTURED_OUTPUT_FROM_LLM,
|
|
18
8
|
get_output_format_for_investigation,
|
|
9
|
+
process_response_into_sections,
|
|
19
10
|
)
|
|
20
|
-
|
|
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
|
|
21
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
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
def investigate_issues(
|
|
@@ -31,9 +29,6 @@ def investigate_issues(
|
|
|
31
29
|
) -> InvestigationResult:
|
|
32
30
|
context = dal.get_issue_data(investigate_request.context.get("robusta_issue_id"))
|
|
33
31
|
|
|
34
|
-
resource_instructions = dal.get_resource_instructions(
|
|
35
|
-
"alert", investigate_request.context.get("issue_type")
|
|
36
|
-
)
|
|
37
32
|
global_instructions = dal.get_global_instructions_for_account()
|
|
38
33
|
|
|
39
34
|
raw_data = investigate_request.model_dump()
|
|
@@ -58,8 +53,6 @@ def investigate_issues(
|
|
|
58
53
|
investigation = ai.investigate(
|
|
59
54
|
issue,
|
|
60
55
|
prompt=investigate_request.prompt_template,
|
|
61
|
-
post_processing_prompt=HOLMES_POST_PROCESSING_PROMPT,
|
|
62
|
-
instructions=resource_instructions,
|
|
63
56
|
global_instructions=global_instructions,
|
|
64
57
|
sections=investigate_request.sections,
|
|
65
58
|
trace_span=trace_span,
|
|
@@ -68,11 +61,15 @@ def investigate_issues(
|
|
|
68
61
|
|
|
69
62
|
(text_response, sections) = process_response_into_sections(investigation.result)
|
|
70
63
|
|
|
64
|
+
if sections is None:
|
|
65
|
+
sentry_helper.capture_sections_none(content=investigation.result)
|
|
66
|
+
|
|
71
67
|
logging.debug(f"text response: {text_response}")
|
|
72
68
|
return InvestigationResult(
|
|
73
69
|
analysis=text_response,
|
|
74
70
|
sections=sections,
|
|
75
71
|
tool_calls=investigation.tool_calls or [],
|
|
72
|
+
num_llm_calls=investigation.num_llm_calls,
|
|
76
73
|
instructions=investigation.instructions,
|
|
77
74
|
metadata=investigation.metadata,
|
|
78
75
|
)
|
|
@@ -101,10 +98,6 @@ def get_investigation_context(
|
|
|
101
98
|
|
|
102
99
|
issue_instructions = ai.runbook_manager.get_instructions_for_issue(issue)
|
|
103
100
|
|
|
104
|
-
resource_instructions = dal.get_resource_instructions(
|
|
105
|
-
"alert", investigate_request.context.get("issue_type")
|
|
106
|
-
)
|
|
107
|
-
|
|
108
101
|
# This section is about setting vars to request the LLM to return structured output.
|
|
109
102
|
# It does not mean that Holmes will not return structured sections for investigation as it is
|
|
110
103
|
# capable of splitting the markdown into sections
|
|
@@ -140,17 +133,20 @@ def get_investigation_context(
|
|
|
140
133
|
"runbooks_enabled": True if runbook_catalog else False,
|
|
141
134
|
},
|
|
142
135
|
)
|
|
143
|
-
|
|
136
|
+
base_user = ""
|
|
144
137
|
|
|
145
138
|
global_instructions = dal.get_global_instructions_for_account()
|
|
146
|
-
|
|
147
|
-
user_prompt=user_prompt,
|
|
139
|
+
runbooks_ctx = generate_runbooks_args(
|
|
148
140
|
runbook_catalog=runbook_catalog,
|
|
149
141
|
global_instructions=global_instructions,
|
|
150
142
|
issue_instructions=issue_instructions,
|
|
151
|
-
resource_instructions=resource_instructions,
|
|
152
143
|
)
|
|
153
144
|
|
|
154
|
-
|
|
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
|
+
)
|
|
155
151
|
|
|
156
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
|
holmes/core/issue.py
CHANGED
holmes/core/llm.py
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
import threading
|
|
4
5
|
from abc import abstractmethod
|
|
5
6
|
from math import floor
|
|
6
7
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
|
|
7
8
|
|
|
8
9
|
import litellm
|
|
10
|
+
import sentry_sdk
|
|
9
11
|
from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper
|
|
10
12
|
from litellm.types.utils import ModelResponse, TextCompletionResponse
|
|
11
|
-
import sentry_sdk
|
|
12
13
|
from pydantic import BaseModel, ConfigDict, SecretStr
|
|
13
14
|
from typing_extensions import Self
|
|
14
15
|
|
|
@@ -17,15 +18,15 @@ from holmes.clients.robusta_client import (
|
|
|
17
18
|
RobustaModelsResponse,
|
|
18
19
|
fetch_robusta_models,
|
|
19
20
|
)
|
|
20
|
-
|
|
21
21
|
from holmes.common.env_vars import (
|
|
22
|
+
EXTRA_HEADERS,
|
|
22
23
|
FALLBACK_CONTEXT_WINDOW_SIZE,
|
|
24
|
+
LLM_REQUEST_TIMEOUT,
|
|
23
25
|
LOAD_ALL_ROBUSTA_MODELS,
|
|
24
26
|
REASONING_EFFORT,
|
|
25
27
|
ROBUSTA_AI,
|
|
26
28
|
ROBUSTA_API_ENDPOINT,
|
|
27
29
|
THINKING,
|
|
28
|
-
EXTRA_HEADERS,
|
|
29
30
|
TOOL_MAX_ALLOCATED_CONTEXT_WINDOW_PCT,
|
|
30
31
|
TOOL_MAX_ALLOCATED_CONTEXT_WINDOW_TOKENS,
|
|
31
32
|
)
|
|
@@ -396,6 +397,12 @@ class DefaultLLM(LLM):
|
|
|
396
397
|
"reasoning_effort"
|
|
397
398
|
] # can be removed after next litelm version
|
|
398
399
|
|
|
400
|
+
existing_allowed = self.args.pop("allowed_openai_params", None)
|
|
401
|
+
if existing_allowed:
|
|
402
|
+
if allowed_openai_params is None:
|
|
403
|
+
allowed_openai_params = []
|
|
404
|
+
allowed_openai_params.extend(existing_allowed)
|
|
405
|
+
|
|
399
406
|
self.args.setdefault("temperature", temperature)
|
|
400
407
|
|
|
401
408
|
self._add_cache_control_to_last_message(messages)
|
|
@@ -414,6 +421,7 @@ class DefaultLLM(LLM):
|
|
|
414
421
|
drop_params=drop_params,
|
|
415
422
|
allowed_openai_params=allowed_openai_params,
|
|
416
423
|
stream=stream,
|
|
424
|
+
timeout=LLM_REQUEST_TIMEOUT,
|
|
417
425
|
**tools_args,
|
|
418
426
|
**self.args,
|
|
419
427
|
)
|
|
@@ -524,6 +532,7 @@ class LLMModelRegistry:
|
|
|
524
532
|
self._llms: dict[str, ModelEntry] = {}
|
|
525
533
|
self._default_robusta_model = None
|
|
526
534
|
self.dal = dal
|
|
535
|
+
self._lock = threading.RLock()
|
|
527
536
|
|
|
528
537
|
self._init_models()
|
|
529
538
|
|
|
@@ -549,6 +558,9 @@ class LLMModelRegistry:
|
|
|
549
558
|
|
|
550
559
|
def _should_load_config_model(self) -> bool:
|
|
551
560
|
if self.config.model is not None:
|
|
561
|
+
if self._llms and self.config.model in self._llms:
|
|
562
|
+
# model already loaded from file
|
|
563
|
+
return False
|
|
552
564
|
return True
|
|
553
565
|
|
|
554
566
|
# backward compatibility - in the past config.model was set by default to gpt-4o.
|
|
@@ -628,39 +640,46 @@ class LLMModelRegistry:
|
|
|
628
640
|
return True
|
|
629
641
|
|
|
630
642
|
def get_model_params(self, model_key: Optional[str] = None) -> ModelEntry:
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
643
|
+
with self._lock:
|
|
644
|
+
if not self._llms:
|
|
645
|
+
raise Exception("No llm models were loaded")
|
|
646
|
+
|
|
647
|
+
if model_key:
|
|
648
|
+
model_params = self._llms.get(model_key)
|
|
649
|
+
if model_params:
|
|
650
|
+
logging.info(f"Using selected model: {model_key}")
|
|
651
|
+
return model_params.model_copy()
|
|
652
|
+
|
|
653
|
+
if model_key.startswith("Robusta/"):
|
|
654
|
+
logging.warning("Resyncing Registry and Robusta models.")
|
|
655
|
+
self._init_models()
|
|
656
|
+
model_params = self._llms.get(model_key)
|
|
657
|
+
if model_params:
|
|
658
|
+
logging.info(f"Using selected model: {model_key}")
|
|
659
|
+
return model_params.model_copy()
|
|
660
|
+
|
|
661
|
+
logging.error(f"Couldn't find model: {model_key} in model list")
|
|
662
|
+
|
|
663
|
+
if self._default_robusta_model:
|
|
664
|
+
model_params = self._llms.get(self._default_robusta_model)
|
|
665
|
+
if model_params is not None:
|
|
666
|
+
logging.info(
|
|
667
|
+
f"Using default Robusta AI model: {self._default_robusta_model}"
|
|
668
|
+
)
|
|
669
|
+
return model_params.model_copy()
|
|
670
|
+
|
|
671
|
+
logging.error(
|
|
672
|
+
f"Couldn't find default Robusta AI model: {self._default_robusta_model} in model list"
|
|
647
673
|
)
|
|
648
|
-
return model_params.copy()
|
|
649
|
-
|
|
650
|
-
logging.error(
|
|
651
|
-
f"Couldn't find default Robusta AI model: {self._default_robusta_model} in model list"
|
|
652
|
-
)
|
|
653
|
-
|
|
654
|
-
model_key, first_model_params = next(iter(self._llms.items()))
|
|
655
|
-
logging.debug(f"Using first available model: {model_key}")
|
|
656
|
-
return first_model_params.copy()
|
|
657
674
|
|
|
658
|
-
|
|
659
|
-
|
|
675
|
+
model_key, first_model_params = next(iter(self._llms.items()))
|
|
676
|
+
logging.debug(f"Using first available model: {model_key}")
|
|
677
|
+
return first_model_params.model_copy()
|
|
660
678
|
|
|
661
679
|
@property
|
|
662
680
|
def models(self) -> dict[str, ModelEntry]:
|
|
663
|
-
|
|
681
|
+
with self._lock:
|
|
682
|
+
return self._llms
|
|
664
683
|
|
|
665
684
|
def _parse_models_file(self, path: str) -> dict[str, ModelEntry]:
|
|
666
685
|
models = load_yaml_file(path, raise_error=False, warn_not_found=False)
|
holmes/core/models.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from holmes.core.investigation_structured_output import InputSectionsDataType
|
|
3
|
-
from typing import Optional, List, Dict, Any, Union
|
|
4
|
-
from pydantic import BaseModel, model_validator, Field
|
|
5
2
|
from enum import Enum
|
|
3
|
+
from typing import Any, Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field, model_validator
|
|
6
6
|
|
|
7
|
+
from holmes.core.investigation_structured_output import InputSectionsDataType
|
|
7
8
|
from holmes.core.tools import StructuredToolResult, StructuredToolResultStatus
|
|
8
9
|
|
|
9
10
|
|
|
@@ -32,7 +33,11 @@ class ToolCallResult(BaseModel):
|
|
|
32
33
|
"tool_call_id": self.tool_call_id,
|
|
33
34
|
"role": "tool",
|
|
34
35
|
"name": self.tool_name,
|
|
35
|
-
"content": format_tool_result_data(
|
|
36
|
+
"content": format_tool_result_data(
|
|
37
|
+
tool_result=self.result,
|
|
38
|
+
tool_call_id=self.tool_call_id,
|
|
39
|
+
tool_name=self.tool_name,
|
|
40
|
+
),
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
def as_tool_result_response(self):
|
|
@@ -60,20 +65,16 @@ class ToolCallResult(BaseModel):
|
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
|
|
63
|
-
def format_tool_result_data(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if isinstance(tool_result.data, BaseModel):
|
|
70
|
-
tool_response = tool_result.data.model_dump_json(indent=2)
|
|
71
|
-
else:
|
|
72
|
-
tool_response = json.dumps(tool_result.data, indent=2)
|
|
73
|
-
except Exception:
|
|
74
|
-
tool_response = str(tool_result.data)
|
|
68
|
+
def format_tool_result_data(
|
|
69
|
+
tool_result: StructuredToolResult, tool_call_id: str, tool_name: str
|
|
70
|
+
) -> str:
|
|
71
|
+
tool_call_metadata = {"tool_name": tool_name, "tool_call_id": tool_call_id}
|
|
72
|
+
tool_response = f"tool_call_metadata={json.dumps(tool_call_metadata)}"
|
|
73
|
+
|
|
75
74
|
if tool_result.status == StructuredToolResultStatus.ERROR:
|
|
76
|
-
tool_response
|
|
75
|
+
tool_response += f"{tool_result.error or 'Tool execution failed'}:\n\n"
|
|
76
|
+
|
|
77
|
+
tool_response += tool_result.get_stringified_data()
|
|
77
78
|
|
|
78
79
|
if tool_result.params:
|
|
79
80
|
tool_response = (
|
|
@@ -87,6 +88,7 @@ class InvestigationResult(BaseModel):
|
|
|
87
88
|
analysis: Optional[str] = None
|
|
88
89
|
sections: Optional[Dict[str, Union[str, None]]] = None
|
|
89
90
|
tool_calls: List[ToolCallResult] = []
|
|
91
|
+
num_llm_calls: Optional[int] = None # Number of LLM API calls (turns)
|
|
90
92
|
instructions: List[str] = []
|
|
91
93
|
metadata: Optional[Dict[Any, Any]] = None
|
|
92
94
|
|
holmes/core/openai_formatting.py
CHANGED
holmes/core/prompt.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
from rich.console import Console
|
|
2
|
-
from typing import Optional, List, Dict, Any, Union
|
|
3
1
|
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
4
6
|
from holmes.plugins.prompts import load_and_render_prompt
|
|
5
7
|
from holmes.plugins.runbooks import RunbookCatalog
|
|
8
|
+
from holmes.utils.global_instructions import generate_runbooks_args
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
def append_file_to_user_prompt(user_prompt: str, file_path: Path) -> str:
|
|
@@ -35,6 +38,39 @@ def get_tasks_management_system_reminder() -> str:
|
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
|
|
41
|
+
def _has_content(value: Optional[str]) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Check if the value is a non-empty string and not None.
|
|
44
|
+
"""
|
|
45
|
+
return bool(value and isinstance(value, str) and value.strip())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _should_enable_runbooks(context: Dict[str, str]) -> bool:
|
|
49
|
+
return any(
|
|
50
|
+
(
|
|
51
|
+
_has_content(context.get("runbook_catalog")),
|
|
52
|
+
_has_content(context.get("custom_instructions")),
|
|
53
|
+
_has_content(context.get("global_instructions")),
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def generate_user_prompt(
|
|
59
|
+
user_prompt: str,
|
|
60
|
+
context: Dict[str, str],
|
|
61
|
+
) -> str:
|
|
62
|
+
runbooks_enabled = _should_enable_runbooks(context)
|
|
63
|
+
|
|
64
|
+
return load_and_render_prompt(
|
|
65
|
+
"builtin://base_user_prompt.jinja2",
|
|
66
|
+
context={
|
|
67
|
+
"user_prompt": user_prompt,
|
|
68
|
+
"runbooks_enabled": runbooks_enabled,
|
|
69
|
+
**context,
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
38
74
|
def build_initial_ask_messages(
|
|
39
75
|
console: Console,
|
|
40
76
|
initial_user_prompt: str,
|
|
@@ -70,6 +106,15 @@ def build_initial_ask_messages(
|
|
|
70
106
|
)
|
|
71
107
|
|
|
72
108
|
user_prompt_with_files += get_tasks_management_system_reminder()
|
|
109
|
+
|
|
110
|
+
runbooks_ctx = generate_runbooks_args(
|
|
111
|
+
runbook_catalog=runbooks, # type: ignore
|
|
112
|
+
)
|
|
113
|
+
user_prompt_with_files = generate_user_prompt(
|
|
114
|
+
user_prompt_with_files,
|
|
115
|
+
runbooks_ctx,
|
|
116
|
+
)
|
|
117
|
+
|
|
73
118
|
messages = [
|
|
74
119
|
{"role": "system", "content": system_prompt_rendered},
|
|
75
120
|
{"role": "user", "content": user_prompt_with_files},
|