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
|
@@ -4,12 +4,9 @@ from typing import List, Optional
|
|
|
4
4
|
import sentry_sdk
|
|
5
5
|
|
|
6
6
|
from holmes.core.tools import (
|
|
7
|
-
StructuredToolResult,
|
|
8
7
|
Tool,
|
|
9
|
-
StructuredToolResultStatus,
|
|
10
8
|
Toolset,
|
|
11
9
|
ToolsetStatusEnum,
|
|
12
|
-
ToolInvokeContext,
|
|
13
10
|
)
|
|
14
11
|
from holmes.core.tools_utils.toolset_utils import filter_out_default_logging_toolset
|
|
15
12
|
|
|
@@ -47,21 +44,6 @@ class ToolExecutor:
|
|
|
47
44
|
)
|
|
48
45
|
self.tools_by_name[tool.name] = tool
|
|
49
46
|
|
|
50
|
-
def invoke(
|
|
51
|
-
self, tool_name: str, params: dict, context: ToolInvokeContext
|
|
52
|
-
) -> StructuredToolResult:
|
|
53
|
-
"""TODO: remove this function as it seems unused.
|
|
54
|
-
We call tool_executor.get_tool_by_name() and then tool.invoke() directly instead of this invoke function
|
|
55
|
-
"""
|
|
56
|
-
tool = self.get_tool_by_name(tool_name)
|
|
57
|
-
if not tool:
|
|
58
|
-
return StructuredToolResult(
|
|
59
|
-
status=StructuredToolResultStatus.ERROR,
|
|
60
|
-
error=f"Could not find tool named {tool_name}",
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
return tool.invoke(params, context)
|
|
64
|
-
|
|
65
47
|
def get_tool_by_name(self, name: str) -> Optional[Tool]:
|
|
66
48
|
if name in self.tools_by_name:
|
|
67
49
|
return self.tools_by_name[name]
|
holmes/core/toolset_manager.py
CHANGED
|
@@ -2,7 +2,7 @@ import concurrent.futures
|
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
4
|
import os
|
|
5
|
-
from typing import Any, List, Optional,
|
|
5
|
+
from typing import TYPE_CHECKING, Any, List, Optional, Union
|
|
6
6
|
|
|
7
7
|
from benedict import benedict
|
|
8
8
|
from pydantic import FilePath
|
|
@@ -18,6 +18,26 @@ if TYPE_CHECKING:
|
|
|
18
18
|
|
|
19
19
|
DEFAULT_TOOLSET_STATUS_LOCATION = os.path.join(config_path_dir, "toolsets_status.json")
|
|
20
20
|
|
|
21
|
+
# Mapping of deprecated toolset names to their new names
|
|
22
|
+
DEPRECATED_TOOLSET_NAMES: dict[str, str] = {
|
|
23
|
+
"coralogix/logs": "coralogix",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def handle_deprecated_toolset_name(
|
|
28
|
+
toolset_name: str, builtin_toolset_names: list[str]
|
|
29
|
+
) -> str:
|
|
30
|
+
if toolset_name in DEPRECATED_TOOLSET_NAMES:
|
|
31
|
+
new_name = DEPRECATED_TOOLSET_NAMES[toolset_name]
|
|
32
|
+
if new_name in builtin_toolset_names:
|
|
33
|
+
logging.warning(
|
|
34
|
+
f"The toolset name '{toolset_name}' is deprecated. "
|
|
35
|
+
f"Please use '{new_name}' instead. "
|
|
36
|
+
"The old name will continue to work but may be removed in a future version."
|
|
37
|
+
)
|
|
38
|
+
return new_name
|
|
39
|
+
return toolset_name
|
|
40
|
+
|
|
21
41
|
|
|
22
42
|
class ToolsetManager:
|
|
23
43
|
"""
|
|
@@ -34,9 +54,11 @@ class ToolsetManager:
|
|
|
34
54
|
custom_toolsets_from_cli: Optional[List[FilePath]] = None,
|
|
35
55
|
toolset_status_location: Optional[FilePath] = None,
|
|
36
56
|
global_fast_model: Optional[str] = None,
|
|
57
|
+
custom_runbook_catalogs: Optional[List[Union[str, FilePath]]] = None,
|
|
37
58
|
):
|
|
38
59
|
self.toolsets = toolsets
|
|
39
60
|
self.toolsets = toolsets or {}
|
|
61
|
+
self.custom_runbook_catalogs = custom_runbook_catalogs
|
|
40
62
|
if mcp_servers is not None:
|
|
41
63
|
for _, mcp_server in mcp_servers.items():
|
|
42
64
|
mcp_server["type"] = ToolsetType.MCP.value
|
|
@@ -86,7 +108,15 @@ class ToolsetManager:
|
|
|
86
108
|
3. custom toolset from config can override both built-in and add new custom toolsets # for backward compatibility
|
|
87
109
|
"""
|
|
88
110
|
# Load built-in toolsets
|
|
89
|
-
|
|
111
|
+
# Extract search paths from custom catalog files
|
|
112
|
+
additional_search_paths = None
|
|
113
|
+
if self.custom_runbook_catalogs:
|
|
114
|
+
additional_search_paths = [
|
|
115
|
+
os.path.dirname(os.path.abspath(str(catalog_path)))
|
|
116
|
+
for catalog_path in self.custom_runbook_catalogs
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
builtin_toolsets = load_builtin_toolsets(dal, additional_search_paths)
|
|
90
120
|
toolsets_by_name: dict[str, Toolset] = {
|
|
91
121
|
toolset.name: toolset for toolset in builtin_toolsets
|
|
92
122
|
}
|
|
@@ -164,6 +194,10 @@ class ToolsetManager:
|
|
|
164
194
|
builtin_toolsets_dict: dict[str, dict[str, Any]] = {}
|
|
165
195
|
custom_toolsets_dict: dict[str, dict[str, Any]] = {}
|
|
166
196
|
for toolset_name, toolset_config in toolsets.items():
|
|
197
|
+
toolset_name = handle_deprecated_toolset_name(
|
|
198
|
+
toolset_name, builtin_toolset_names
|
|
199
|
+
)
|
|
200
|
+
|
|
167
201
|
if toolset_name in builtin_toolset_names:
|
|
168
202
|
# build-in types was assigned when loaded
|
|
169
203
|
builtin_toolsets_dict[toolset_name] = toolset_config
|
|
@@ -464,6 +498,7 @@ class ToolsetManager:
|
|
|
464
498
|
IMPORTANT: This also forces recreation of transformer instances since they may already be created.
|
|
465
499
|
"""
|
|
466
500
|
import logging
|
|
501
|
+
|
|
467
502
|
from holmes.core.transformers import registry
|
|
468
503
|
|
|
469
504
|
logger = logging.getLogger(__name__)
|
holmes/core/tracing.py
CHANGED
|
@@ -41,7 +41,18 @@ def readable_timestamp():
|
|
|
41
41
|
|
|
42
42
|
def get_active_branch_name():
|
|
43
43
|
try:
|
|
44
|
-
# First check
|
|
44
|
+
# First check GitHub Actions environment variables (CI)
|
|
45
|
+
github_head_ref = os.environ.get("GITHUB_HEAD_REF") # Set for PRs
|
|
46
|
+
if github_head_ref:
|
|
47
|
+
return github_head_ref
|
|
48
|
+
|
|
49
|
+
github_ref = os.environ.get(
|
|
50
|
+
"GITHUB_REF", ""
|
|
51
|
+
) # Set for pushes: refs/heads/branch-name
|
|
52
|
+
if github_ref.startswith("refs/heads/"):
|
|
53
|
+
return github_ref.replace("refs/heads/", "")
|
|
54
|
+
|
|
55
|
+
# Check if .git is a file (worktree case)
|
|
45
56
|
git_path = Path(".git")
|
|
46
57
|
if git_path.is_file():
|
|
47
58
|
# Read the worktree git directory path
|
|
@@ -236,7 +247,7 @@ class BraintrustTracer:
|
|
|
236
247
|
else:
|
|
237
248
|
logging.warning("No active span found in Braintrust context")
|
|
238
249
|
|
|
239
|
-
return f"https://www.braintrust.dev/app/
|
|
250
|
+
return f"https://www.braintrust.dev/app/{BRAINTRUST_ORG}/p/{self.project}/experiments/{experiment_name}"
|
|
240
251
|
|
|
241
252
|
def wrap_llm(self, llm_module):
|
|
242
253
|
"""Wrap LiteLLM with Braintrust tracing if in active context, otherwise return unwrapped."""
|
|
@@ -6,8 +6,8 @@ before they are passed to the LLM for analysis.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from .base import BaseTransformer, TransformerError
|
|
9
|
-
from .registry import TransformerRegistry, registry
|
|
10
9
|
from .llm_summarize import LLMSummarizeTransformer
|
|
10
|
+
from .registry import TransformerRegistry, registry
|
|
11
11
|
from .transformer import Transformer
|
|
12
12
|
|
|
13
13
|
# Register built-in transformers
|
holmes/core/transformers/base.py
CHANGED
|
@@ -3,11 +3,12 @@ LLM Summarize Transformer for fast model summarization of large tool outputs.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import ClassVar, Optional
|
|
7
|
+
|
|
7
8
|
from pydantic import Field, PrivateAttr, StrictStr
|
|
8
9
|
|
|
10
|
+
from ..llm import LLM, DefaultLLM
|
|
9
11
|
from .base import BaseTransformer, TransformerError
|
|
10
|
-
from ..llm import DefaultLLM, LLM
|
|
11
12
|
|
|
12
13
|
logger = logging.getLogger(__name__)
|
|
13
14
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import litellm
|
|
5
|
+
from litellm.types.utils import ModelResponse
|
|
6
|
+
|
|
3
7
|
from holmes.core.llm import LLM
|
|
4
8
|
from holmes.plugins.prompts import load_and_render_prompt
|
|
5
|
-
from litellm.types.utils import ModelResponse
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
def strip_system_prompt(
|
|
@@ -16,9 +19,26 @@ def strip_system_prompt(
|
|
|
16
19
|
return conversation_history[:], None
|
|
17
20
|
|
|
18
21
|
|
|
22
|
+
def find_last_user_prompt(conversation_history: list[dict]) -> Optional[dict]:
|
|
23
|
+
if not conversation_history:
|
|
24
|
+
return None
|
|
25
|
+
last_user_prompt: Optional[dict] = None
|
|
26
|
+
for message in conversation_history:
|
|
27
|
+
if message.get("role") == "user":
|
|
28
|
+
last_user_prompt = message
|
|
29
|
+
return last_user_prompt
|
|
30
|
+
|
|
31
|
+
|
|
19
32
|
def compact_conversation_history(
|
|
20
33
|
original_conversation_history: list[dict], llm: LLM
|
|
21
34
|
) -> list[dict]:
|
|
35
|
+
"""
|
|
36
|
+
The compacted conversation history contains:
|
|
37
|
+
1. Original system prompt, uncompacted (if present)
|
|
38
|
+
2. Last user prompt, uncompacted (if present)
|
|
39
|
+
3. Compacted conversation history (role=assistant)
|
|
40
|
+
4. Compaction message (role=system)
|
|
41
|
+
"""
|
|
22
42
|
conversation_history, system_prompt_message = strip_system_prompt(
|
|
23
43
|
original_conversation_history
|
|
24
44
|
)
|
|
@@ -27,7 +47,16 @@ def compact_conversation_history(
|
|
|
27
47
|
)
|
|
28
48
|
conversation_history.append({"role": "user", "content": compaction_instructions})
|
|
29
49
|
|
|
30
|
-
|
|
50
|
+
# Set modify_params to handle providers like Anthropic that require tools
|
|
51
|
+
# when conversation history contains tool calls
|
|
52
|
+
original_modify_params = litellm.modify_params
|
|
53
|
+
try:
|
|
54
|
+
litellm.modify_params = True # necessary when using anthropic
|
|
55
|
+
response: ModelResponse = llm.completion(
|
|
56
|
+
messages=conversation_history, drop_params=True
|
|
57
|
+
) # type: ignore
|
|
58
|
+
finally:
|
|
59
|
+
litellm.modify_params = original_modify_params
|
|
31
60
|
response_message = None
|
|
32
61
|
if (
|
|
33
62
|
response
|
|
@@ -45,11 +74,17 @@ def compact_conversation_history(
|
|
|
45
74
|
compacted_conversation_history: list[dict] = []
|
|
46
75
|
if system_prompt_message:
|
|
47
76
|
compacted_conversation_history.append(system_prompt_message)
|
|
77
|
+
|
|
78
|
+
last_user_prompt = find_last_user_prompt(original_conversation_history)
|
|
79
|
+
if last_user_prompt:
|
|
80
|
+
compacted_conversation_history.append(last_user_prompt)
|
|
81
|
+
|
|
48
82
|
compacted_conversation_history.append(
|
|
49
83
|
response_message.model_dump(
|
|
50
84
|
exclude_defaults=True, exclude_unset=True, exclude_none=True
|
|
51
85
|
)
|
|
52
86
|
)
|
|
87
|
+
|
|
53
88
|
compacted_conversation_history.append(
|
|
54
89
|
{
|
|
55
90
|
"role": "system",
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Any, Optional
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import sentry_sdk
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
5
7
|
from holmes.common.env_vars import (
|
|
6
8
|
ENABLE_CONVERSATION_HISTORY_COMPACTION,
|
|
7
9
|
MAX_OUTPUT_TOKEN_RESERVATION,
|
|
@@ -16,7 +18,6 @@ from holmes.core.truncation.compaction import compact_conversation_history
|
|
|
16
18
|
from holmes.utils import sentry_helper
|
|
17
19
|
from holmes.utils.stream import StreamEvents, StreamMessage
|
|
18
20
|
|
|
19
|
-
|
|
20
21
|
TRUNCATION_NOTICE = "\n\n[TRUNCATED]"
|
|
21
22
|
|
|
22
23
|
|
holmes/interactive.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import os
|
|
3
|
+
import re
|
|
3
4
|
import subprocess
|
|
4
5
|
import tempfile
|
|
5
6
|
import threading
|
|
@@ -37,7 +38,7 @@ from holmes.core.feedback import (
|
|
|
37
38
|
UserFeedback,
|
|
38
39
|
)
|
|
39
40
|
from holmes.core.prompt import build_initial_ask_messages
|
|
40
|
-
from holmes.core.tool_calling_llm import ToolCallingLLM, ToolCallResult
|
|
41
|
+
from holmes.core.tool_calling_llm import LLMResult, ToolCallingLLM, ToolCallResult
|
|
41
42
|
from holmes.core.tools import StructuredToolResult, pretty_print_toolset_status
|
|
42
43
|
from holmes.core.tracing import DummyTracer
|
|
43
44
|
from holmes.utils.colors import (
|
|
@@ -49,8 +50,8 @@ from holmes.utils.colors import (
|
|
|
49
50
|
USER_COLOR,
|
|
50
51
|
)
|
|
51
52
|
from holmes.utils.console.consts import agent_name
|
|
53
|
+
from holmes.utils.file_utils import write_json_file
|
|
52
54
|
from holmes.version import check_version_async
|
|
53
|
-
import re
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
class SlashCommands(Enum):
|
|
@@ -952,18 +953,51 @@ def display_recent_tool_outputs(
|
|
|
952
953
|
)
|
|
953
954
|
|
|
954
955
|
|
|
956
|
+
def save_conversation_to_file(
|
|
957
|
+
json_output_file: str,
|
|
958
|
+
messages: List,
|
|
959
|
+
all_tool_calls_history: List[ToolCallResult],
|
|
960
|
+
console: Console,
|
|
961
|
+
) -> None:
|
|
962
|
+
"""Save the current conversation to a JSON file."""
|
|
963
|
+
try:
|
|
964
|
+
# Create LLMResult-like structure for consistency with non-interactive mode
|
|
965
|
+
conversation_result = LLMResult(
|
|
966
|
+
messages=messages,
|
|
967
|
+
tool_calls=all_tool_calls_history,
|
|
968
|
+
result=None, # No single result in interactive mode
|
|
969
|
+
total_cost=0.0, # TODO: Could aggregate costs from all responses if needed
|
|
970
|
+
total_tokens=0,
|
|
971
|
+
prompt_tokens=0,
|
|
972
|
+
completion_tokens=0,
|
|
973
|
+
metadata={
|
|
974
|
+
"session_type": "interactive",
|
|
975
|
+
"total_turns": len([m for m in messages if m.get("role") == "user"]),
|
|
976
|
+
},
|
|
977
|
+
)
|
|
978
|
+
write_json_file(json_output_file, conversation_result.model_dump())
|
|
979
|
+
console.print(
|
|
980
|
+
f"[bold {STATUS_COLOR}]Conversation saved to {json_output_file}[/bold {STATUS_COLOR}]"
|
|
981
|
+
)
|
|
982
|
+
except Exception as e:
|
|
983
|
+
logging.error(f"Failed to save conversation: {e}", exc_info=e)
|
|
984
|
+
console.print(
|
|
985
|
+
f"[bold {ERROR_COLOR}]Failed to save conversation: {e}[/bold {ERROR_COLOR}]"
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
|
|
955
989
|
def run_interactive_loop(
|
|
956
990
|
ai: ToolCallingLLM,
|
|
957
991
|
console: Console,
|
|
958
992
|
initial_user_input: Optional[str],
|
|
959
993
|
include_files: Optional[List[Path]],
|
|
960
|
-
post_processing_prompt: Optional[str],
|
|
961
994
|
show_tool_output: bool,
|
|
962
995
|
tracer=None,
|
|
963
996
|
runbooks=None,
|
|
964
997
|
system_prompt_additions: Optional[str] = None,
|
|
965
998
|
check_version: bool = True,
|
|
966
999
|
feedback_callback: Optional[FeedbackCallback] = None,
|
|
1000
|
+
json_output_file: Optional[str] = None,
|
|
967
1001
|
) -> None:
|
|
968
1002
|
# Initialize tracer - use DummyTracer if no tracer provided
|
|
969
1003
|
if tracer is None:
|
|
@@ -1134,6 +1168,9 @@ def run_interactive_loop(
|
|
|
1134
1168
|
continue
|
|
1135
1169
|
|
|
1136
1170
|
if command == SlashCommands.EXIT.command:
|
|
1171
|
+
console.print(
|
|
1172
|
+
f"[bold {STATUS_COLOR}]Exiting interactive mode.[/bold {STATUS_COLOR}]"
|
|
1173
|
+
)
|
|
1137
1174
|
return
|
|
1138
1175
|
elif command == SlashCommands.HELP.command:
|
|
1139
1176
|
console.print(
|
|
@@ -1230,7 +1267,6 @@ def run_interactive_loop(
|
|
|
1230
1267
|
)
|
|
1231
1268
|
response = ai.call(
|
|
1232
1269
|
messages,
|
|
1233
|
-
post_processing_prompt,
|
|
1234
1270
|
trace_span=trace_span,
|
|
1235
1271
|
tool_number_offset=len(all_tool_calls_history),
|
|
1236
1272
|
)
|
|
@@ -1263,9 +1299,21 @@ def run_interactive_loop(
|
|
|
1263
1299
|
)
|
|
1264
1300
|
|
|
1265
1301
|
console.print("")
|
|
1302
|
+
|
|
1303
|
+
# Save conversation after each AI response
|
|
1304
|
+
if json_output_file and messages:
|
|
1305
|
+
save_conversation_to_file(
|
|
1306
|
+
json_output_file, messages, all_tool_calls_history, console
|
|
1307
|
+
)
|
|
1266
1308
|
except typer.Abort:
|
|
1309
|
+
console.print(
|
|
1310
|
+
f"[bold {STATUS_COLOR}]Exiting interactive mode.[/bold {STATUS_COLOR}]"
|
|
1311
|
+
)
|
|
1267
1312
|
break
|
|
1268
1313
|
except EOFError: # Handle Ctrl+D
|
|
1314
|
+
console.print(
|
|
1315
|
+
f"[bold {STATUS_COLOR}]Exiting interactive mode.[/bold {STATUS_COLOR}]"
|
|
1316
|
+
)
|
|
1269
1317
|
break
|
|
1270
1318
|
except Exception as e:
|
|
1271
1319
|
logging.error("An error occurred during interactive mode:", exc_info=e)
|
|
@@ -1275,7 +1323,3 @@ def run_interactive_loop(
|
|
|
1275
1323
|
trace_url = tracer.get_trace_url()
|
|
1276
1324
|
if trace_url:
|
|
1277
1325
|
console.print(f"🔍 View trace: {trace_url}")
|
|
1278
|
-
|
|
1279
|
-
console.print(
|
|
1280
|
-
f"[bold {STATUS_COLOR}]Exiting interactive mode.[/bold {STATUS_COLOR}]"
|
|
1281
|
-
)
|
holmes/main.py
CHANGED
|
@@ -10,6 +10,7 @@ if add_custom_certificate(ADDITIONAL_CERTIFICATE):
|
|
|
10
10
|
# DO NOT ADD ANY IMPORTS OR CODE ABOVE THIS LINE
|
|
11
11
|
# IMPORTING ABOVE MIGHT INITIALIZE AN HTTPS CLIENT THAT DOESN'T TRUST THE CUSTOM CERTIFICATE
|
|
12
12
|
import sys
|
|
13
|
+
from holmes.utils.colors import USER_COLOR
|
|
13
14
|
import json
|
|
14
15
|
import logging
|
|
15
16
|
import socket
|
|
@@ -28,7 +29,7 @@ from holmes.config import (
|
|
|
28
29
|
SourceFactory,
|
|
29
30
|
SupportedTicketSources,
|
|
30
31
|
)
|
|
31
|
-
from holmes.core.prompt import build_initial_ask_messages
|
|
32
|
+
from holmes.core.prompt import build_initial_ask_messages, generate_user_prompt
|
|
32
33
|
from holmes.core.resource_instruction import ResourceInstructionDocument
|
|
33
34
|
from holmes.core.tools import pretty_print_toolset_status
|
|
34
35
|
from holmes.core.tracing import SpanType, TracingFactory
|
|
@@ -41,7 +42,6 @@ from holmes.utils.console.consts import system_prompt_help
|
|
|
41
42
|
from holmes.utils.console.logging import init_logging
|
|
42
43
|
from holmes.utils.console.result import handle_result
|
|
43
44
|
from holmes.utils.file_utils import write_json_file
|
|
44
|
-
from holmes.utils.colors import USER_COLOR
|
|
45
45
|
|
|
46
46
|
app = typer.Typer(add_completion=False, pretty_exceptions_show_locals=False)
|
|
47
47
|
investigate_app = typer.Typer(
|
|
@@ -137,13 +137,6 @@ opt_json_output_file: Optional[str] = typer.Option(
|
|
|
137
137
|
envvar="HOLMES_JSON_OUTPUT_FILE",
|
|
138
138
|
)
|
|
139
139
|
|
|
140
|
-
opt_post_processing_prompt: Optional[str] = typer.Option(
|
|
141
|
-
None,
|
|
142
|
-
"--post-processing-prompt",
|
|
143
|
-
help="Adds a prompt for post processing. (Preferable for chatty ai models)",
|
|
144
|
-
envvar="HOLMES_POST_PROCESSING_PROMPT",
|
|
145
|
-
)
|
|
146
|
-
|
|
147
140
|
opt_documents: Optional[str] = typer.Option(
|
|
148
141
|
None,
|
|
149
142
|
"--documents",
|
|
@@ -201,7 +194,6 @@ def ask(
|
|
|
201
194
|
),
|
|
202
195
|
json_output_file: Optional[str] = opt_json_output_file,
|
|
203
196
|
echo_request: bool = opt_echo_request,
|
|
204
|
-
post_processing_prompt: Optional[str] = opt_post_processing_prompt,
|
|
205
197
|
interactive: bool = typer.Option(
|
|
206
198
|
True,
|
|
207
199
|
"--interactive/--no-interactive",
|
|
@@ -261,6 +253,7 @@ def ask(
|
|
|
261
253
|
dal=None, # type: ignore
|
|
262
254
|
refresh_toolsets=refresh_toolsets, # flag to refresh the toolset status
|
|
263
255
|
tracer=tracer,
|
|
256
|
+
model_name=model,
|
|
264
257
|
)
|
|
265
258
|
|
|
266
259
|
if prompt_file and prompt:
|
|
@@ -298,11 +291,11 @@ def ask(
|
|
|
298
291
|
console,
|
|
299
292
|
prompt,
|
|
300
293
|
include_file,
|
|
301
|
-
post_processing_prompt,
|
|
302
294
|
show_tool_output,
|
|
303
295
|
tracer,
|
|
304
296
|
config.get_runbook_catalog(),
|
|
305
297
|
system_prompt_additions,
|
|
298
|
+
json_output_file=json_output_file,
|
|
306
299
|
)
|
|
307
300
|
return
|
|
308
301
|
|
|
@@ -319,7 +312,7 @@ def ask(
|
|
|
319
312
|
f'holmes ask "{prompt}"', span_type=SpanType.TASK
|
|
320
313
|
) as trace_span:
|
|
321
314
|
trace_span.log(input=prompt, metadata={"type": "user_question"})
|
|
322
|
-
response = ai.call(messages,
|
|
315
|
+
response = ai.call(messages, trace_span=trace_span)
|
|
323
316
|
trace_span.log(
|
|
324
317
|
output=response.result,
|
|
325
318
|
)
|
|
@@ -345,6 +338,7 @@ def ask(
|
|
|
345
338
|
issue,
|
|
346
339
|
show_tool_output,
|
|
347
340
|
False, # type: ignore
|
|
341
|
+
log_costs,
|
|
348
342
|
)
|
|
349
343
|
|
|
350
344
|
if trace_url:
|
|
@@ -390,7 +384,6 @@ def alertmanager(
|
|
|
390
384
|
system_prompt: Optional[str] = typer.Option(
|
|
391
385
|
"builtin://generic_investigation.jinja2", help=system_prompt_help
|
|
392
386
|
),
|
|
393
|
-
post_processing_prompt: Optional[str] = opt_post_processing_prompt,
|
|
394
387
|
):
|
|
395
388
|
"""
|
|
396
389
|
Investigate a Prometheus/Alertmanager alert
|
|
@@ -413,7 +406,7 @@ def alertmanager(
|
|
|
413
406
|
custom_runbooks=custom_runbooks,
|
|
414
407
|
)
|
|
415
408
|
|
|
416
|
-
ai = config.create_console_issue_investigator() # type: ignore
|
|
409
|
+
ai = config.create_console_issue_investigator(model_name=model) # type: ignore
|
|
417
410
|
|
|
418
411
|
source = config.create_alertmanager_source()
|
|
419
412
|
|
|
@@ -446,8 +439,6 @@ def alertmanager(
|
|
|
446
439
|
issue=issue,
|
|
447
440
|
prompt=system_prompt, # type: ignore
|
|
448
441
|
console=console,
|
|
449
|
-
instructions=None,
|
|
450
|
-
post_processing_prompt=post_processing_prompt,
|
|
451
442
|
)
|
|
452
443
|
results.append({"issue": issue.model_dump(), "result": result.model_dump()})
|
|
453
444
|
handle_result(result, console, destination, config, issue, False, True) # type: ignore
|
|
@@ -524,7 +515,6 @@ def jira(
|
|
|
524
515
|
system_prompt: Optional[str] = typer.Option(
|
|
525
516
|
"builtin://generic_investigation.jinja2", help=system_prompt_help
|
|
526
517
|
),
|
|
527
|
-
post_processing_prompt: Optional[str] = opt_post_processing_prompt,
|
|
528
518
|
):
|
|
529
519
|
"""
|
|
530
520
|
Investigate a Jira ticket
|
|
@@ -542,7 +532,7 @@ def jira(
|
|
|
542
532
|
custom_toolsets_from_cli=custom_toolsets,
|
|
543
533
|
custom_runbooks=custom_runbooks,
|
|
544
534
|
)
|
|
545
|
-
ai = config.create_console_issue_investigator() # type: ignore
|
|
535
|
+
ai = config.create_console_issue_investigator(model_name=model) # type: ignore
|
|
546
536
|
source = config.create_jira_source()
|
|
547
537
|
try:
|
|
548
538
|
issues = source.fetch_issues()
|
|
@@ -563,8 +553,6 @@ def jira(
|
|
|
563
553
|
issue=issue,
|
|
564
554
|
prompt=system_prompt, # type: ignore
|
|
565
555
|
console=console,
|
|
566
|
-
instructions=None,
|
|
567
|
-
post_processing_prompt=post_processing_prompt,
|
|
568
556
|
)
|
|
569
557
|
|
|
570
558
|
console.print(Rule())
|
|
@@ -617,7 +605,7 @@ def ticket(
|
|
|
617
605
|
system_prompt: Optional[str] = typer.Option(
|
|
618
606
|
"builtin://generic_ticket.jinja2", help=system_prompt_help
|
|
619
607
|
),
|
|
620
|
-
|
|
608
|
+
model: Optional[str] = opt_model,
|
|
621
609
|
):
|
|
622
610
|
"""
|
|
623
611
|
Fetch and print a Jira ticket from the specified source.
|
|
@@ -658,7 +646,7 @@ def ticket(
|
|
|
658
646
|
},
|
|
659
647
|
)
|
|
660
648
|
|
|
661
|
-
ai = ticket_source.config.create_console_issue_investigator()
|
|
649
|
+
ai = ticket_source.config.create_console_issue_investigator(model_name=model)
|
|
662
650
|
console.print(
|
|
663
651
|
f"[bold yellow]Analyzing ticket: {issue_to_investigate.name}...[/bold yellow]"
|
|
664
652
|
)
|
|
@@ -667,7 +655,8 @@ def ticket(
|
|
|
667
655
|
+ f" for issue '{issue_to_investigate.name}' with description:'{issue_to_investigate.description}'"
|
|
668
656
|
)
|
|
669
657
|
|
|
670
|
-
|
|
658
|
+
ticket_user_prompt = generate_user_prompt(prompt, context={})
|
|
659
|
+
result = ai.prompt_call(system_prompt, ticket_user_prompt)
|
|
671
660
|
|
|
672
661
|
console.print(Rule())
|
|
673
662
|
console.print(
|
|
@@ -688,14 +677,14 @@ def github(
|
|
|
688
677
|
),
|
|
689
678
|
github_owner: Optional[str] = typer.Option(
|
|
690
679
|
None,
|
|
691
|
-
help="The GitHub repository Owner, eg: if the repository url is https://github.com/
|
|
680
|
+
help="The GitHub repository Owner, eg: if the repository url is https://github.com/HolmesGPT/holmesgpt, the owner is HolmesGPT",
|
|
692
681
|
),
|
|
693
682
|
github_pat: str = typer.Option(
|
|
694
683
|
None,
|
|
695
684
|
),
|
|
696
685
|
github_repository: Optional[str] = typer.Option(
|
|
697
686
|
None,
|
|
698
|
-
help="The GitHub repository name, eg: if the repository url is https://github.com/
|
|
687
|
+
help="The GitHub repository name, eg: if the repository url is https://github.com/HolmesGPT/holmesgpt, the repository name is holmesgpt",
|
|
699
688
|
),
|
|
700
689
|
update: Optional[bool] = typer.Option(False, help="Update GitHub with AI results"),
|
|
701
690
|
github_query: Optional[str] = typer.Option(
|
|
@@ -714,7 +703,6 @@ def github(
|
|
|
714
703
|
system_prompt: Optional[str] = typer.Option(
|
|
715
704
|
"builtin://generic_investigation.jinja2", help=system_prompt_help
|
|
716
705
|
),
|
|
717
|
-
post_processing_prompt: Optional[str] = opt_post_processing_prompt,
|
|
718
706
|
):
|
|
719
707
|
"""
|
|
720
708
|
Investigate a GitHub issue
|
|
@@ -733,7 +721,7 @@ def github(
|
|
|
733
721
|
custom_toolsets_from_cli=custom_toolsets,
|
|
734
722
|
custom_runbooks=custom_runbooks,
|
|
735
723
|
)
|
|
736
|
-
ai = config.create_console_issue_investigator()
|
|
724
|
+
ai = config.create_console_issue_investigator(model_name=model)
|
|
737
725
|
source = config.create_github_source()
|
|
738
726
|
try:
|
|
739
727
|
issues = source.fetch_issues()
|
|
@@ -753,8 +741,6 @@ def github(
|
|
|
753
741
|
issue=issue,
|
|
754
742
|
prompt=system_prompt, # type: ignore
|
|
755
743
|
console=console,
|
|
756
|
-
instructions=None,
|
|
757
|
-
post_processing_prompt=post_processing_prompt,
|
|
758
744
|
)
|
|
759
745
|
|
|
760
746
|
console.print(Rule())
|
|
@@ -800,7 +786,6 @@ def pagerduty(
|
|
|
800
786
|
system_prompt: Optional[str] = typer.Option(
|
|
801
787
|
"builtin://generic_investigation.jinja2", help=system_prompt_help
|
|
802
788
|
),
|
|
803
|
-
post_processing_prompt: Optional[str] = opt_post_processing_prompt,
|
|
804
789
|
):
|
|
805
790
|
"""
|
|
806
791
|
Investigate a PagerDuty incident
|
|
@@ -817,7 +802,7 @@ def pagerduty(
|
|
|
817
802
|
custom_toolsets_from_cli=custom_toolsets,
|
|
818
803
|
custom_runbooks=custom_runbooks,
|
|
819
804
|
)
|
|
820
|
-
ai = config.create_console_issue_investigator()
|
|
805
|
+
ai = config.create_console_issue_investigator(model_name=model)
|
|
821
806
|
source = config.create_pagerduty_source()
|
|
822
807
|
try:
|
|
823
808
|
issues = source.fetch_issues()
|
|
@@ -839,8 +824,6 @@ def pagerduty(
|
|
|
839
824
|
issue=issue,
|
|
840
825
|
prompt=system_prompt, # type: ignore
|
|
841
826
|
console=console,
|
|
842
|
-
instructions=None,
|
|
843
|
-
post_processing_prompt=post_processing_prompt,
|
|
844
827
|
)
|
|
845
828
|
|
|
846
829
|
console.print(Rule())
|
|
@@ -885,7 +868,6 @@ def opsgenie(
|
|
|
885
868
|
system_prompt: Optional[str] = typer.Option(
|
|
886
869
|
"builtin://generic_investigation.jinja2", help=system_prompt_help
|
|
887
870
|
),
|
|
888
|
-
post_processing_prompt: Optional[str] = opt_post_processing_prompt,
|
|
889
871
|
documents: Optional[str] = opt_documents,
|
|
890
872
|
):
|
|
891
873
|
"""
|
|
@@ -903,7 +885,7 @@ def opsgenie(
|
|
|
903
885
|
custom_toolsets_from_cli=custom_toolsets,
|
|
904
886
|
custom_runbooks=custom_runbooks,
|
|
905
887
|
)
|
|
906
|
-
ai = config.create_console_issue_investigator()
|
|
888
|
+
ai = config.create_console_issue_investigator(model_name=model)
|
|
907
889
|
source = config.create_opsgenie_source()
|
|
908
890
|
try:
|
|
909
891
|
issues = source.fetch_issues()
|
|
@@ -922,8 +904,6 @@ def opsgenie(
|
|
|
922
904
|
issue=issue,
|
|
923
905
|
prompt=system_prompt, # type: ignore
|
|
924
906
|
console=console,
|
|
925
|
-
instructions=None,
|
|
926
|
-
post_processing_prompt=post_processing_prompt,
|
|
927
907
|
)
|
|
928
908
|
|
|
929
909
|
console.print(Rule())
|
holmes/plugins/interfaces.py
CHANGED