holmesgpt 0.12.3a1__py3-none-any.whl → 0.12.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of holmesgpt might be problematic. Click here for more details.
- holmes/__init__.py +1 -1
- holmes/config.py +75 -33
- holmes/core/config.py +5 -0
- holmes/core/conversations.py +17 -2
- holmes/core/investigation.py +1 -0
- holmes/core/llm.py +1 -2
- holmes/core/prompt.py +29 -4
- holmes/core/supabase_dal.py +49 -13
- holmes/core/tool_calling_llm.py +26 -1
- holmes/core/tools.py +2 -1
- holmes/core/tools_utils/tool_executor.py +1 -0
- holmes/core/toolset_manager.py +10 -3
- holmes/core/tracing.py +78 -11
- holmes/interactive.py +110 -20
- holmes/main.py +13 -18
- holmes/plugins/destinations/slack/plugin.py +19 -9
- holmes/plugins/prompts/_ai_safety.jinja2 +43 -0
- holmes/plugins/prompts/_fetch_logs.jinja2 +11 -1
- holmes/plugins/prompts/_general_instructions.jinja2 +8 -37
- holmes/plugins/prompts/_permission_errors.jinja2 +6 -0
- holmes/plugins/prompts/_runbook_instructions.jinja2 +13 -5
- holmes/plugins/prompts/_toolsets_instructions.jinja2 +22 -14
- holmes/plugins/prompts/generic_ask.jinja2 +6 -0
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +1 -0
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +1 -0
- holmes/plugins/prompts/generic_investigation.jinja2 +1 -0
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +2 -2
- holmes/plugins/runbooks/__init__.py +20 -4
- holmes/plugins/toolsets/__init__.py +7 -9
- holmes/plugins/toolsets/aks-node-health.yaml +0 -8
- holmes/plugins/toolsets/argocd.yaml +4 -1
- holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +1 -1
- holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +2 -0
- holmes/plugins/toolsets/confluence.yaml +1 -1
- holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +54 -4
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +150 -6
- holmes/plugins/toolsets/kubernetes.yaml +13 -7
- holmes/plugins/toolsets/prometheus/prometheus.py +2 -6
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +2 -2
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +65 -6
- holmes/plugins/toolsets/service_discovery.py +1 -1
- holmes/plugins/toolsets/slab.yaml +1 -1
- holmes/utils/colors.py +7 -0
- holmes/utils/console/consts.py +5 -0
- holmes/utils/console/result.py +2 -1
- holmes/utils/keygen_utils.py +6 -0
- holmes/version.py +2 -2
- holmesgpt-0.12.5.dist-info/METADATA +258 -0
- {holmesgpt-0.12.3a1.dist-info → holmesgpt-0.12.5.dist-info}/RECORD +52 -47
- holmesgpt-0.12.3a1.dist-info/METADATA +0 -400
- {holmesgpt-0.12.3a1.dist-info → holmesgpt-0.12.5.dist-info}/LICENSE.txt +0 -0
- {holmesgpt-0.12.3a1.dist-info → holmesgpt-0.12.5.dist-info}/WHEEL +0 -0
- {holmesgpt-0.12.3a1.dist-info → holmesgpt-0.12.5.dist-info}/entry_points.txt +0 -0
holmes/__init__.py
CHANGED
holmes/config.py
CHANGED
|
@@ -4,36 +4,40 @@ import os
|
|
|
4
4
|
import os.path
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, List, Optional, Union
|
|
7
|
+
from typing import TYPE_CHECKING, Any, List, Optional, Union
|
|
8
8
|
|
|
9
9
|
import yaml # type: ignore
|
|
10
10
|
from pydantic import BaseModel, ConfigDict, FilePath, SecretStr
|
|
11
11
|
|
|
12
12
|
from holmes.common.env_vars import ROBUSTA_AI, ROBUSTA_API_ENDPOINT, ROBUSTA_CONFIG_PATH
|
|
13
|
-
from holmes.core.llm import LLM, DefaultLLM
|
|
14
|
-
from holmes.core.runbooks import RunbookManager
|
|
15
|
-
from holmes.core.supabase_dal import SupabaseDal
|
|
16
|
-
from holmes.core.tool_calling_llm import IssueInvestigator, ToolCallingLLM
|
|
17
13
|
from holmes.core.tools_utils.tool_executor import ToolExecutor
|
|
18
14
|
from holmes.core.toolset_manager import ToolsetManager
|
|
19
|
-
from holmes.plugins.destinations.slack import SlackDestination
|
|
20
15
|
from holmes.plugins.runbooks import (
|
|
21
16
|
RunbookCatalog,
|
|
22
17
|
load_builtin_runbooks,
|
|
23
18
|
load_runbook_catalog,
|
|
24
19
|
load_runbooks_from_file,
|
|
25
20
|
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
from holmes.
|
|
30
|
-
from holmes.
|
|
21
|
+
|
|
22
|
+
# Source plugin imports moved to their respective create methods to speed up startup
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from holmes.core.llm import LLM
|
|
25
|
+
from holmes.core.supabase_dal import SupabaseDal
|
|
26
|
+
from holmes.core.tool_calling_llm import IssueInvestigator, ToolCallingLLM
|
|
27
|
+
from holmes.plugins.destinations.slack import SlackDestination
|
|
28
|
+
from holmes.plugins.sources.github import GitHubSource
|
|
29
|
+
from holmes.plugins.sources.jira import JiraServiceManagementSource, JiraSource
|
|
30
|
+
from holmes.plugins.sources.opsgenie import OpsGenieSource
|
|
31
|
+
from holmes.plugins.sources.pagerduty import PagerDutySource
|
|
32
|
+
from holmes.plugins.sources.prometheus.plugin import AlertManagerSource
|
|
33
|
+
|
|
34
|
+
from holmes.core.config import config_path_dir
|
|
31
35
|
from holmes.utils.definitions import RobustaConfig
|
|
32
36
|
from holmes.utils.env import replace_env_vars_values
|
|
33
37
|
from holmes.utils.file_utils import load_yaml_file
|
|
34
38
|
from holmes.utils.pydantic_utils import RobustaBaseConfig, load_model_from_file
|
|
35
39
|
|
|
36
|
-
DEFAULT_CONFIG_LOCATION = os.path.
|
|
40
|
+
DEFAULT_CONFIG_LOCATION = os.path.join(config_path_dir, "config.yaml")
|
|
37
41
|
MODEL_LIST_FILE_LOCATION = os.environ.get(
|
|
38
42
|
"MODEL_LIST_FILE_LOCATION", "/etc/holmes/config/model_list.yaml"
|
|
39
43
|
)
|
|
@@ -111,6 +115,7 @@ class Config(RobustaBaseConfig):
|
|
|
111
115
|
should_try_robusta_ai: bool = False # if True, we will try to load the Robusta AI model, in cli we aren't trying to load it.
|
|
112
116
|
|
|
113
117
|
toolsets: Optional[dict[str, dict[str, Any]]] = None
|
|
118
|
+
mcp_servers: Optional[dict[str, dict[str, Any]]] = None
|
|
114
119
|
|
|
115
120
|
_server_tool_executor: Optional[ToolExecutor] = None
|
|
116
121
|
|
|
@@ -121,6 +126,7 @@ class Config(RobustaBaseConfig):
|
|
|
121
126
|
if not self._toolset_manager:
|
|
122
127
|
self._toolset_manager = ToolsetManager(
|
|
123
128
|
toolsets=self.toolsets,
|
|
129
|
+
mcp_servers=self.mcp_servers,
|
|
124
130
|
custom_toolsets=self.custom_toolsets,
|
|
125
131
|
custom_toolsets_from_cli=self.custom_toolsets_from_cli,
|
|
126
132
|
)
|
|
@@ -246,7 +252,7 @@ class Config(RobustaBaseConfig):
|
|
|
246
252
|
return runbook_catalog
|
|
247
253
|
|
|
248
254
|
def create_console_tool_executor(
|
|
249
|
-
self, dal: Optional[SupabaseDal], refresh_status: bool = False
|
|
255
|
+
self, dal: Optional["SupabaseDal"], refresh_status: bool = False
|
|
250
256
|
) -> ToolExecutor:
|
|
251
257
|
"""
|
|
252
258
|
Creates a ToolExecutor instance configured for CLI usage. This executor manages the available tools
|
|
@@ -262,7 +268,7 @@ class Config(RobustaBaseConfig):
|
|
|
262
268
|
)
|
|
263
269
|
return ToolExecutor(cli_toolsets)
|
|
264
270
|
|
|
265
|
-
def create_tool_executor(self, dal: Optional[SupabaseDal]) -> ToolExecutor:
|
|
271
|
+
def create_tool_executor(self, dal: Optional["SupabaseDal"]) -> ToolExecutor:
|
|
266
272
|
"""
|
|
267
273
|
Creates ToolExecutor for the server endpoints
|
|
268
274
|
"""
|
|
@@ -282,53 +288,73 @@ class Config(RobustaBaseConfig):
|
|
|
282
288
|
|
|
283
289
|
def create_console_toolcalling_llm(
|
|
284
290
|
self,
|
|
285
|
-
dal: Optional[SupabaseDal] = None,
|
|
291
|
+
dal: Optional["SupabaseDal"] = None,
|
|
286
292
|
refresh_toolsets: bool = False,
|
|
287
293
|
tracer=None,
|
|
288
|
-
) -> ToolCallingLLM:
|
|
294
|
+
) -> "ToolCallingLLM":
|
|
289
295
|
tool_executor = self.create_console_tool_executor(dal, refresh_toolsets)
|
|
296
|
+
from holmes.core.tool_calling_llm import ToolCallingLLM
|
|
297
|
+
|
|
290
298
|
return ToolCallingLLM(
|
|
291
299
|
tool_executor, self.max_steps, self._get_llm(tracer=tracer)
|
|
292
300
|
)
|
|
293
301
|
|
|
294
302
|
def create_toolcalling_llm(
|
|
295
303
|
self,
|
|
296
|
-
dal: Optional[SupabaseDal] = None,
|
|
304
|
+
dal: Optional["SupabaseDal"] = None,
|
|
297
305
|
model: Optional[str] = None,
|
|
298
306
|
tracer=None,
|
|
299
|
-
) -> ToolCallingLLM:
|
|
307
|
+
) -> "ToolCallingLLM":
|
|
300
308
|
tool_executor = self.create_tool_executor(dal)
|
|
309
|
+
from holmes.core.tool_calling_llm import ToolCallingLLM
|
|
310
|
+
|
|
301
311
|
return ToolCallingLLM(
|
|
302
312
|
tool_executor, self.max_steps, self._get_llm(model, tracer)
|
|
303
313
|
)
|
|
304
314
|
|
|
305
315
|
def create_issue_investigator(
|
|
306
316
|
self,
|
|
307
|
-
dal: Optional[SupabaseDal] = None,
|
|
317
|
+
dal: Optional["SupabaseDal"] = None,
|
|
308
318
|
model: Optional[str] = None,
|
|
309
319
|
tracer=None,
|
|
310
|
-
) -> IssueInvestigator:
|
|
320
|
+
) -> "IssueInvestigator":
|
|
311
321
|
all_runbooks = load_builtin_runbooks()
|
|
312
322
|
for runbook_path in self.custom_runbooks:
|
|
313
323
|
all_runbooks.extend(load_runbooks_from_file(runbook_path))
|
|
314
324
|
|
|
325
|
+
from holmes.core.runbooks import RunbookManager
|
|
326
|
+
|
|
315
327
|
runbook_manager = RunbookManager(all_runbooks)
|
|
316
328
|
tool_executor = self.create_tool_executor(dal)
|
|
329
|
+
from holmes.core.tool_calling_llm import IssueInvestigator
|
|
330
|
+
|
|
317
331
|
return IssueInvestigator(
|
|
318
|
-
tool_executor,
|
|
332
|
+
tool_executor=tool_executor,
|
|
333
|
+
runbook_manager=runbook_manager,
|
|
334
|
+
max_steps=self.max_steps,
|
|
335
|
+
llm=self._get_llm(model, tracer),
|
|
336
|
+
cluster_name=self.cluster_name,
|
|
319
337
|
)
|
|
320
338
|
|
|
321
339
|
def create_console_issue_investigator(
|
|
322
|
-
self, dal: Optional[SupabaseDal] = None
|
|
323
|
-
) -> IssueInvestigator:
|
|
340
|
+
self, dal: Optional["SupabaseDal"] = None
|
|
341
|
+
) -> "IssueInvestigator":
|
|
324
342
|
all_runbooks = load_builtin_runbooks()
|
|
325
343
|
for runbook_path in self.custom_runbooks:
|
|
326
344
|
all_runbooks.extend(load_runbooks_from_file(runbook_path))
|
|
327
345
|
|
|
346
|
+
from holmes.core.runbooks import RunbookManager
|
|
347
|
+
|
|
328
348
|
runbook_manager = RunbookManager(all_runbooks)
|
|
329
349
|
tool_executor = self.create_console_tool_executor(dal=dal)
|
|
350
|
+
from holmes.core.tool_calling_llm import IssueInvestigator
|
|
351
|
+
|
|
330
352
|
return IssueInvestigator(
|
|
331
|
-
tool_executor,
|
|
353
|
+
tool_executor=tool_executor,
|
|
354
|
+
runbook_manager=runbook_manager,
|
|
355
|
+
max_steps=self.max_steps,
|
|
356
|
+
llm=self._get_llm(),
|
|
357
|
+
cluster_name=self.cluster_name,
|
|
332
358
|
)
|
|
333
359
|
|
|
334
360
|
def validate_jira_config(self):
|
|
@@ -343,7 +369,9 @@ class Config(RobustaBaseConfig):
|
|
|
343
369
|
if self.jira_api_key is None:
|
|
344
370
|
raise ValueError("--jira-api-key must be specified")
|
|
345
371
|
|
|
346
|
-
def create_jira_source(self) -> JiraSource:
|
|
372
|
+
def create_jira_source(self) -> "JiraSource":
|
|
373
|
+
from holmes.plugins.sources.jira import JiraSource
|
|
374
|
+
|
|
347
375
|
self.validate_jira_config()
|
|
348
376
|
|
|
349
377
|
return JiraSource(
|
|
@@ -353,7 +381,9 @@ class Config(RobustaBaseConfig):
|
|
|
353
381
|
jql_query=self.jira_query, # type: ignore
|
|
354
382
|
)
|
|
355
383
|
|
|
356
|
-
def create_jira_service_management_source(self) -> JiraServiceManagementSource:
|
|
384
|
+
def create_jira_service_management_source(self) -> "JiraServiceManagementSource":
|
|
385
|
+
from holmes.plugins.sources.jira import JiraServiceManagementSource
|
|
386
|
+
|
|
357
387
|
self.validate_jira_config()
|
|
358
388
|
|
|
359
389
|
return JiraServiceManagementSource(
|
|
@@ -363,7 +393,9 @@ class Config(RobustaBaseConfig):
|
|
|
363
393
|
jql_query=self.jira_query, # type: ignore
|
|
364
394
|
)
|
|
365
395
|
|
|
366
|
-
def create_github_source(self) -> GitHubSource:
|
|
396
|
+
def create_github_source(self) -> "GitHubSource":
|
|
397
|
+
from holmes.plugins.sources.github import GitHubSource
|
|
398
|
+
|
|
367
399
|
if not self.github_url or not (
|
|
368
400
|
self.github_url.startswith("http://")
|
|
369
401
|
or self.github_url.startswith("https://")
|
|
@@ -384,7 +416,9 @@ class Config(RobustaBaseConfig):
|
|
|
384
416
|
query=self.github_query,
|
|
385
417
|
)
|
|
386
418
|
|
|
387
|
-
def create_pagerduty_source(self) -> PagerDutySource:
|
|
419
|
+
def create_pagerduty_source(self) -> "PagerDutySource":
|
|
420
|
+
from holmes.plugins.sources.pagerduty import PagerDutySource
|
|
421
|
+
|
|
388
422
|
if self.pagerduty_api_key is None:
|
|
389
423
|
raise ValueError("--pagerduty-api-key must be specified")
|
|
390
424
|
|
|
@@ -394,7 +428,9 @@ class Config(RobustaBaseConfig):
|
|
|
394
428
|
incident_key=self.pagerduty_incident_key,
|
|
395
429
|
)
|
|
396
430
|
|
|
397
|
-
def create_opsgenie_source(self) -> OpsGenieSource:
|
|
431
|
+
def create_opsgenie_source(self) -> "OpsGenieSource":
|
|
432
|
+
from holmes.plugins.sources.opsgenie import OpsGenieSource
|
|
433
|
+
|
|
398
434
|
if self.opsgenie_api_key is None:
|
|
399
435
|
raise ValueError("--opsgenie-api-key must be specified")
|
|
400
436
|
|
|
@@ -408,7 +444,9 @@ class Config(RobustaBaseConfig):
|
|
|
408
444
|
),
|
|
409
445
|
)
|
|
410
446
|
|
|
411
|
-
def create_alertmanager_source(self) -> AlertManagerSource:
|
|
447
|
+
def create_alertmanager_source(self) -> "AlertManagerSource":
|
|
448
|
+
from holmes.plugins.sources.prometheus.plugin import AlertManagerSource
|
|
449
|
+
|
|
412
450
|
return AlertManagerSource(
|
|
413
451
|
url=self.alertmanager_url, # type: ignore
|
|
414
452
|
username=self.alertmanager_username,
|
|
@@ -417,14 +455,16 @@ class Config(RobustaBaseConfig):
|
|
|
417
455
|
filepath=self.alertmanager_file,
|
|
418
456
|
)
|
|
419
457
|
|
|
420
|
-
def create_slack_destination(self):
|
|
458
|
+
def create_slack_destination(self) -> "SlackDestination":
|
|
459
|
+
from holmes.plugins.destinations.slack import SlackDestination
|
|
460
|
+
|
|
421
461
|
if self.slack_token is None:
|
|
422
462
|
raise ValueError("--slack-token must be specified")
|
|
423
463
|
if self.slack_channel is None:
|
|
424
464
|
raise ValueError("--slack-channel must be specified")
|
|
425
465
|
return SlackDestination(self.slack_token.get_secret_value(), self.slack_channel)
|
|
426
466
|
|
|
427
|
-
def _get_llm(self, model_key: Optional[str] = None, tracer=None) -> LLM:
|
|
467
|
+
def _get_llm(self, model_key: Optional[str] = None, tracer=None) -> "LLM":
|
|
428
468
|
api_key = self.api_key.get_secret_value() if self.api_key else None
|
|
429
469
|
model = self.model
|
|
430
470
|
model_params = {}
|
|
@@ -438,6 +478,8 @@ class Config(RobustaBaseConfig):
|
|
|
438
478
|
api_key = model_params.pop("api_key", api_key)
|
|
439
479
|
model = model_params.pop("model", model)
|
|
440
480
|
|
|
481
|
+
from holmes.core.llm import DefaultLLM
|
|
482
|
+
|
|
441
483
|
return DefaultLLM(model, api_key, model_params, tracer) # type: ignore
|
|
442
484
|
|
|
443
485
|
def get_models_list(self) -> List[str]:
|
|
@@ -450,7 +492,7 @@ class Config(RobustaBaseConfig):
|
|
|
450
492
|
class TicketSource(BaseModel):
|
|
451
493
|
config: Config
|
|
452
494
|
output_instructions: list[str]
|
|
453
|
-
source: Union[JiraServiceManagementSource, PagerDutySource]
|
|
495
|
+
source: Union["JiraServiceManagementSource", "PagerDutySource"]
|
|
454
496
|
|
|
455
497
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
456
498
|
|
holmes/core/config.py
ADDED
holmes/core/conversations.py
CHANGED
|
@@ -2,6 +2,7 @@ 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
7
|
ToolCallConversationResult,
|
|
7
8
|
IssueChatRequest,
|
|
@@ -60,6 +61,7 @@ def truncate_tool_messages(conversation_history: list, tool_size: int) -> None:
|
|
|
60
61
|
def build_issue_chat_messages(
|
|
61
62
|
issue_chat_request: IssueChatRequest,
|
|
62
63
|
ai: ToolCallingLLM,
|
|
64
|
+
config: Config,
|
|
63
65
|
global_instructions: Optional[Instructions] = None,
|
|
64
66
|
):
|
|
65
67
|
"""
|
|
@@ -130,6 +132,7 @@ def build_issue_chat_messages(
|
|
|
130
132
|
"tools_called_for_investigation": tools_for_investigation,
|
|
131
133
|
"issue": issue_chat_request.issue_type,
|
|
132
134
|
"toolsets": ai.tool_executor.toolsets,
|
|
135
|
+
"cluster_name": config.cluster_name,
|
|
133
136
|
},
|
|
134
137
|
)
|
|
135
138
|
messages = [
|
|
@@ -149,6 +152,7 @@ def build_issue_chat_messages(
|
|
|
149
152
|
"tools_called_for_investigation": None,
|
|
150
153
|
"issue": issue_chat_request.issue_type,
|
|
151
154
|
"toolsets": ai.tool_executor.toolsets,
|
|
155
|
+
"cluster_name": config.cluster_name,
|
|
152
156
|
}
|
|
153
157
|
system_prompt_without_tools = load_and_render_prompt(
|
|
154
158
|
template_path, template_context_without_tools
|
|
@@ -181,6 +185,7 @@ def build_issue_chat_messages(
|
|
|
181
185
|
"tools_called_for_investigation": truncated_investigation_result_tool_calls,
|
|
182
186
|
"issue": issue_chat_request.issue_type,
|
|
183
187
|
"toolsets": ai.tool_executor.toolsets,
|
|
188
|
+
"cluster_name": config.cluster_name,
|
|
184
189
|
}
|
|
185
190
|
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
186
191
|
template_path, truncated_template_context
|
|
@@ -221,6 +226,7 @@ def build_issue_chat_messages(
|
|
|
221
226
|
"tools_called_for_investigation": None,
|
|
222
227
|
"issue": issue_chat_request.issue_type,
|
|
223
228
|
"toolsets": ai.tool_executor.toolsets,
|
|
229
|
+
"cluster_name": config.cluster_name,
|
|
224
230
|
}
|
|
225
231
|
system_prompt_without_tools = load_and_render_prompt(
|
|
226
232
|
template_path, template_context_without_tools
|
|
@@ -243,6 +249,7 @@ def build_issue_chat_messages(
|
|
|
243
249
|
"tools_called_for_investigation": truncated_investigation_result_tool_calls,
|
|
244
250
|
"issue": issue_chat_request.issue_type,
|
|
245
251
|
"toolsets": ai.tool_executor.toolsets,
|
|
252
|
+
"cluster_name": config.cluster_name,
|
|
246
253
|
}
|
|
247
254
|
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
248
255
|
template_path, template_context
|
|
@@ -255,7 +262,7 @@ def build_issue_chat_messages(
|
|
|
255
262
|
|
|
256
263
|
|
|
257
264
|
def add_or_update_system_prompt(
|
|
258
|
-
conversation_history: List[Dict[str, str]], ai: ToolCallingLLM
|
|
265
|
+
conversation_history: List[Dict[str, str]], ai: ToolCallingLLM, config: Config
|
|
259
266
|
):
|
|
260
267
|
"""Either add the system prompt or replace an existing system prompt.
|
|
261
268
|
As a 'defensive' measure, this code will only replace an existing system prompt if it is the
|
|
@@ -266,6 +273,7 @@ def add_or_update_system_prompt(
|
|
|
266
273
|
template_path = "builtin://generic_ask_conversation.jinja2"
|
|
267
274
|
context = {
|
|
268
275
|
"toolsets": ai.tool_executor.toolsets,
|
|
276
|
+
"cluster_name": config.cluster_name,
|
|
269
277
|
}
|
|
270
278
|
|
|
271
279
|
system_prompt = load_and_render_prompt(template_path, context)
|
|
@@ -293,6 +301,7 @@ def build_chat_messages(
|
|
|
293
301
|
ask: str,
|
|
294
302
|
conversation_history: Optional[List[Dict[str, str]]],
|
|
295
303
|
ai: ToolCallingLLM,
|
|
304
|
+
config: Config,
|
|
296
305
|
global_instructions: Optional[Instructions] = None,
|
|
297
306
|
) -> List[dict]:
|
|
298
307
|
"""
|
|
@@ -349,7 +358,7 @@ def build_chat_messages(
|
|
|
349
358
|
conversation_history = conversation_history.copy()
|
|
350
359
|
|
|
351
360
|
conversation_history = add_or_update_system_prompt(
|
|
352
|
-
conversation_history=conversation_history, ai=ai
|
|
361
|
+
conversation_history=conversation_history, ai=ai, config=config
|
|
353
362
|
)
|
|
354
363
|
|
|
355
364
|
ask = add_global_instructions_to_user_prompt(ask, global_instructions)
|
|
@@ -382,6 +391,7 @@ def build_chat_messages(
|
|
|
382
391
|
def build_workload_health_chat_messages(
|
|
383
392
|
workload_health_chat_request: WorkloadHealthChatRequest,
|
|
384
393
|
ai: ToolCallingLLM,
|
|
394
|
+
config: Config,
|
|
385
395
|
global_instructions: Optional[Instructions] = None,
|
|
386
396
|
):
|
|
387
397
|
"""
|
|
@@ -454,6 +464,7 @@ def build_workload_health_chat_messages(
|
|
|
454
464
|
"tools_called_for_workload": tools_for_workload,
|
|
455
465
|
"resource": resource,
|
|
456
466
|
"toolsets": ai.tool_executor.toolsets,
|
|
467
|
+
"cluster_name": config.cluster_name,
|
|
457
468
|
},
|
|
458
469
|
)
|
|
459
470
|
messages = [
|
|
@@ -473,6 +484,7 @@ def build_workload_health_chat_messages(
|
|
|
473
484
|
"tools_called_for_workload": None,
|
|
474
485
|
"resource": resource,
|
|
475
486
|
"toolsets": ai.tool_executor.toolsets,
|
|
487
|
+
"cluster_name": config.cluster_name,
|
|
476
488
|
}
|
|
477
489
|
system_prompt_without_tools = load_and_render_prompt(
|
|
478
490
|
template_path, template_context_without_tools
|
|
@@ -505,6 +517,7 @@ def build_workload_health_chat_messages(
|
|
|
505
517
|
"tools_called_for_workload": truncated_workload_result_tool_calls,
|
|
506
518
|
"resource": resource,
|
|
507
519
|
"toolsets": ai.tool_executor.toolsets,
|
|
520
|
+
"cluster_name": config.cluster_name,
|
|
508
521
|
}
|
|
509
522
|
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
510
523
|
template_path, truncated_template_context
|
|
@@ -545,6 +558,7 @@ def build_workload_health_chat_messages(
|
|
|
545
558
|
"tools_called_for_workload": None,
|
|
546
559
|
"resource": resource,
|
|
547
560
|
"toolsets": ai.tool_executor.toolsets,
|
|
561
|
+
"cluster_name": config.cluster_name,
|
|
548
562
|
}
|
|
549
563
|
system_prompt_without_tools = load_and_render_prompt(
|
|
550
564
|
template_path, template_context_without_tools
|
|
@@ -567,6 +581,7 @@ def build_workload_health_chat_messages(
|
|
|
567
581
|
"tools_called_for_workload": truncated_workload_result_tool_calls,
|
|
568
582
|
"resource": resource,
|
|
569
583
|
"toolsets": ai.tool_executor.toolsets,
|
|
584
|
+
"cluster_name": config.cluster_name,
|
|
570
585
|
}
|
|
571
586
|
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
572
587
|
template_path, template_context
|
holmes/core/investigation.py
CHANGED
holmes/core/llm.py
CHANGED
|
@@ -12,7 +12,6 @@ import litellm
|
|
|
12
12
|
import os
|
|
13
13
|
from holmes.common.env_vars import (
|
|
14
14
|
THINKING,
|
|
15
|
-
TEMPERATURE,
|
|
16
15
|
)
|
|
17
16
|
|
|
18
17
|
|
|
@@ -218,6 +217,7 @@ class DefaultLLM(LLM):
|
|
|
218
217
|
if self.args.get("thinking", None):
|
|
219
218
|
litellm.modify_params = True
|
|
220
219
|
|
|
220
|
+
self.args.setdefault("temperature", temperature)
|
|
221
221
|
# Get the litellm module to use (wrapped or unwrapped)
|
|
222
222
|
litellm_to_use = self.tracer.wrap_llm(litellm) if self.tracer else litellm
|
|
223
223
|
|
|
@@ -225,7 +225,6 @@ class DefaultLLM(LLM):
|
|
|
225
225
|
model=self.model,
|
|
226
226
|
api_key=self.api_key,
|
|
227
227
|
messages=messages,
|
|
228
|
-
temperature=temperature or self.args.pop("temperature", TEMPERATURE),
|
|
229
228
|
response_format=response_format,
|
|
230
229
|
drop_params=drop_params,
|
|
231
230
|
stream=stream,
|
holmes/core/prompt.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from rich.console import Console
|
|
2
|
-
from typing import Optional, List, Dict
|
|
2
|
+
from typing import Optional, List, Dict, Any, Union
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from holmes.plugins.prompts import load_and_render_prompt
|
|
5
|
+
from holmes.plugins.runbooks import RunbookCatalog
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
def append_file_to_user_prompt(user_prompt: str, file_path: Path) -> str:
|
|
7
9
|
with file_path.open("r") as f:
|
|
8
|
-
user_prompt += f"\n\n<attached-file path='{file_path.absolute()}
|
|
10
|
+
user_prompt += f"\n\n<attached-file path='{file_path.absolute()}'>\n{f.read()}\n</attached-file>"
|
|
9
11
|
|
|
10
12
|
return user_prompt
|
|
11
13
|
|
|
@@ -25,11 +27,34 @@ def append_all_files_to_user_prompt(
|
|
|
25
27
|
|
|
26
28
|
def build_initial_ask_messages(
|
|
27
29
|
console: Console,
|
|
28
|
-
system_prompt_rendered: str,
|
|
29
30
|
initial_user_prompt: str,
|
|
30
31
|
file_paths: Optional[List[Path]],
|
|
32
|
+
tool_executor: Any, # ToolExecutor type
|
|
33
|
+
runbooks: Union[RunbookCatalog, Dict, None] = None,
|
|
34
|
+
system_prompt_additions: Optional[str] = None,
|
|
31
35
|
) -> List[Dict]:
|
|
32
|
-
"""Build the initial messages for the AI call.
|
|
36
|
+
"""Build the initial messages for the AI call.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
console: Rich console for output
|
|
40
|
+
initial_user_prompt: The user's prompt
|
|
41
|
+
file_paths: Optional list of files to include
|
|
42
|
+
tool_executor: The tool executor with available toolsets
|
|
43
|
+
runbooks: Optional runbook catalog
|
|
44
|
+
system_prompt_additions: Optional additional system prompt content
|
|
45
|
+
"""
|
|
46
|
+
# Load and render system prompt internally
|
|
47
|
+
system_prompt_template = "builtin://generic_ask.jinja2"
|
|
48
|
+
template_context = {
|
|
49
|
+
"toolsets": tool_executor.toolsets,
|
|
50
|
+
"runbooks": runbooks or {},
|
|
51
|
+
"system_prompt_additions": system_prompt_additions or "",
|
|
52
|
+
}
|
|
53
|
+
system_prompt_rendered = load_and_render_prompt(
|
|
54
|
+
system_prompt_template, template_context
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Append files to user prompt
|
|
33
58
|
user_prompt_with_files = append_all_files_to_user_prompt(
|
|
34
59
|
console, initial_user_prompt, file_paths
|
|
35
60
|
)
|
holmes/core/supabase_dal.py
CHANGED
|
@@ -7,6 +7,7 @@ import threading
|
|
|
7
7
|
from datetime import datetime, timedelta
|
|
8
8
|
from typing import Dict, List, Optional, Tuple
|
|
9
9
|
from uuid import uuid4
|
|
10
|
+
import gzip
|
|
10
11
|
|
|
11
12
|
import yaml # type: ignore
|
|
12
13
|
from cachetools import TTLCache # type: ignore
|
|
@@ -291,6 +292,52 @@ class SupabaseDal:
|
|
|
291
292
|
|
|
292
293
|
return changes_data
|
|
293
294
|
|
|
295
|
+
def unzip_evidence_file(self, data):
|
|
296
|
+
try:
|
|
297
|
+
evidence_list = json.loads(data.get("data", "[]"))
|
|
298
|
+
if not evidence_list:
|
|
299
|
+
return data
|
|
300
|
+
|
|
301
|
+
evidence = evidence_list[0]
|
|
302
|
+
raw_data = evidence.get("data")
|
|
303
|
+
|
|
304
|
+
if evidence.get("type") != "gz" or not raw_data:
|
|
305
|
+
return data
|
|
306
|
+
|
|
307
|
+
# Strip "b'...'" or 'b"..."' markers if present
|
|
308
|
+
if raw_data.startswith("b'") and raw_data.endswith("'"):
|
|
309
|
+
raw_data = raw_data[2:-1]
|
|
310
|
+
elif raw_data.startswith('b"') and raw_data.endswith('"'):
|
|
311
|
+
raw_data = raw_data[2:-1]
|
|
312
|
+
|
|
313
|
+
gz_bytes = base64.b64decode(raw_data)
|
|
314
|
+
decompressed = gzip.decompress(gz_bytes).decode("utf-8")
|
|
315
|
+
|
|
316
|
+
evidence["data"] = decompressed
|
|
317
|
+
data["data"] = json.dumps([evidence])
|
|
318
|
+
return data
|
|
319
|
+
|
|
320
|
+
except Exception:
|
|
321
|
+
logging.exception(f"Unknown issue unzipping gz finding: {data}")
|
|
322
|
+
return data
|
|
323
|
+
|
|
324
|
+
def extract_relevant_issues(self, evidence):
|
|
325
|
+
enrichment_blacklist = {"text_file", "graph", "ai_analysis", "holmes"}
|
|
326
|
+
data = [
|
|
327
|
+
enrich
|
|
328
|
+
for enrich in evidence.data
|
|
329
|
+
if enrich.get("enrichment_type") not in enrichment_blacklist
|
|
330
|
+
]
|
|
331
|
+
|
|
332
|
+
unzipped_files = [
|
|
333
|
+
self.unzip_evidence_file(enrich)
|
|
334
|
+
for enrich in evidence.data
|
|
335
|
+
if enrich.get("enrichment_type") == "text_file"
|
|
336
|
+
]
|
|
337
|
+
|
|
338
|
+
data.extend(unzipped_files)
|
|
339
|
+
return data
|
|
340
|
+
|
|
294
341
|
def get_issue_data(self, issue_id: Optional[str]) -> Optional[Dict]:
|
|
295
342
|
# TODO this could be done in a single atomic SELECT, but there is no
|
|
296
343
|
# foreign key relation between Issues and Evidence.
|
|
@@ -320,12 +367,7 @@ class SupabaseDal:
|
|
|
320
367
|
.filter("issue_id", "eq", issue_id)
|
|
321
368
|
.execute()
|
|
322
369
|
)
|
|
323
|
-
|
|
324
|
-
data = [
|
|
325
|
-
enrich
|
|
326
|
-
for enrich in evidence.data
|
|
327
|
-
if enrich.get("enrichment_type") not in enrichment_blacklist
|
|
328
|
-
]
|
|
370
|
+
data = self.extract_relevant_issues(evidence)
|
|
329
371
|
|
|
330
372
|
issue_data["evidence"] = data
|
|
331
373
|
|
|
@@ -470,13 +512,7 @@ class SupabaseDal:
|
|
|
470
512
|
.execute()
|
|
471
513
|
)
|
|
472
514
|
|
|
473
|
-
|
|
474
|
-
data = [
|
|
475
|
-
evidence.get("data")
|
|
476
|
-
for evidence in res.data
|
|
477
|
-
if evidence.get("enrichment_type") not in enrichment_blacklist
|
|
478
|
-
]
|
|
479
|
-
return data
|
|
515
|
+
return self.extract_relevant_issues(res)
|
|
480
516
|
|
|
481
517
|
except Exception:
|
|
482
518
|
logging.exception("failed to fetch workload issues data", exc_info=True)
|
holmes/core/tool_calling_llm.py
CHANGED
|
@@ -15,7 +15,11 @@ from pydantic import BaseModel
|
|
|
15
15
|
from pydantic_core import from_json
|
|
16
16
|
from rich.console import Console
|
|
17
17
|
|
|
18
|
-
from holmes.common.env_vars import
|
|
18
|
+
from holmes.common.env_vars import (
|
|
19
|
+
ROBUSTA_API_ENDPOINT,
|
|
20
|
+
STREAM_CHUNKS_PER_PARSE,
|
|
21
|
+
TEMPERATURE,
|
|
22
|
+
)
|
|
19
23
|
from holmes.core.investigation_structured_output import (
|
|
20
24
|
DEFAULT_SECTIONS,
|
|
21
25
|
REQUEST_STRUCTURED_OUTPUT_FROM_LLM,
|
|
@@ -40,6 +44,7 @@ from holmes.utils.global_instructions import (
|
|
|
40
44
|
from holmes.utils.tags import format_tags_in_string, parse_messages_tags
|
|
41
45
|
from holmes.core.tools_utils.tool_executor import ToolExecutor
|
|
42
46
|
from holmes.core.tracing import DummySpan
|
|
47
|
+
from holmes.utils.colors import AI_COLOR
|
|
43
48
|
|
|
44
49
|
|
|
45
50
|
def format_tool_result_data(tool_result: StructuredToolResult) -> str:
|
|
@@ -285,6 +290,7 @@ class ToolCallingLLM:
|
|
|
285
290
|
messages=parse_messages_tags(messages),
|
|
286
291
|
tools=tools,
|
|
287
292
|
tool_choice=tool_choice,
|
|
293
|
+
temperature=TEMPERATURE,
|
|
288
294
|
response_format=response_format,
|
|
289
295
|
drop_params=True,
|
|
290
296
|
)
|
|
@@ -328,6 +334,15 @@ class ToolCallingLLM:
|
|
|
328
334
|
|
|
329
335
|
tools_to_call = getattr(response_message, "tool_calls", None)
|
|
330
336
|
text_response = response_message.content
|
|
337
|
+
|
|
338
|
+
if (
|
|
339
|
+
hasattr(response_message, "reasoning_content")
|
|
340
|
+
and response_message.reasoning_content
|
|
341
|
+
):
|
|
342
|
+
logging.debug(
|
|
343
|
+
f"[bold {AI_COLOR}]AI (reasoning) 🤔:[/bold {AI_COLOR}] {response_message.reasoning_content}\n"
|
|
344
|
+
)
|
|
345
|
+
|
|
331
346
|
if not tools_to_call:
|
|
332
347
|
# For chatty models post process and summarize the result
|
|
333
348
|
# this only works for calls where user prompt is explicitly passed through
|
|
@@ -357,6 +372,11 @@ class ToolCallingLLM:
|
|
|
357
372
|
messages=messages,
|
|
358
373
|
)
|
|
359
374
|
|
|
375
|
+
if text_response and text_response.strip():
|
|
376
|
+
logging.info(f"[bold {AI_COLOR}]AI:[/bold {AI_COLOR}] {text_response}")
|
|
377
|
+
logging.info(
|
|
378
|
+
f"The AI requested [bold]{len(tools_to_call) if tools_to_call else 0}[/bold] tool call(s)."
|
|
379
|
+
)
|
|
360
380
|
perf_timing.measure("pre-tool-calls")
|
|
361
381
|
with concurrent.futures.ThreadPoolExecutor(max_workers=16) as executor:
|
|
362
382
|
futures = []
|
|
@@ -610,6 +630,7 @@ class ToolCallingLLM:
|
|
|
610
630
|
"messages": parse_messages_tags(messages), # type: ignore
|
|
611
631
|
"tools": tools,
|
|
612
632
|
"tool_choice": tool_choice,
|
|
633
|
+
"temperature": TEMPERATURE,
|
|
613
634
|
"response_format": response_format,
|
|
614
635
|
"stream": True,
|
|
615
636
|
"drop_param": True,
|
|
@@ -634,6 +655,7 @@ class ToolCallingLLM:
|
|
|
634
655
|
messages=parse_messages_tags(messages), # type: ignore
|
|
635
656
|
tools=tools,
|
|
636
657
|
tool_choice=tool_choice,
|
|
658
|
+
temperature=TEMPERATURE,
|
|
637
659
|
response_format=response_format,
|
|
638
660
|
stream=False,
|
|
639
661
|
drop_params=True,
|
|
@@ -745,9 +767,11 @@ class IssueInvestigator(ToolCallingLLM):
|
|
|
745
767
|
runbook_manager: RunbookManager,
|
|
746
768
|
max_steps: int,
|
|
747
769
|
llm: LLM,
|
|
770
|
+
cluster_name: Optional[str],
|
|
748
771
|
):
|
|
749
772
|
super().__init__(tool_executor, max_steps, llm)
|
|
750
773
|
self.runbook_manager = runbook_manager
|
|
774
|
+
self.cluster_name = cluster_name
|
|
751
775
|
|
|
752
776
|
def investigate(
|
|
753
777
|
self,
|
|
@@ -806,6 +830,7 @@ class IssueInvestigator(ToolCallingLLM):
|
|
|
806
830
|
"sections": sections,
|
|
807
831
|
"structured_output": request_structured_output_from_llm,
|
|
808
832
|
"toolsets": self.tool_executor.toolsets,
|
|
833
|
+
"cluster_name": self.cluster_name,
|
|
809
834
|
},
|
|
810
835
|
)
|
|
811
836
|
|