holmesgpt 0.11.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/.git_archival.json +7 -0
- holmes/__init__.py +76 -0
- holmes/__init__.py.bak +76 -0
- holmes/clients/robusta_client.py +24 -0
- holmes/common/env_vars.py +47 -0
- holmes/config.py +526 -0
- holmes/core/__init__.py +0 -0
- holmes/core/conversations.py +578 -0
- holmes/core/investigation.py +152 -0
- holmes/core/investigation_structured_output.py +264 -0
- holmes/core/issue.py +54 -0
- holmes/core/llm.py +250 -0
- holmes/core/models.py +157 -0
- holmes/core/openai_formatting.py +51 -0
- holmes/core/performance_timing.py +72 -0
- holmes/core/prompt.py +42 -0
- holmes/core/resource_instruction.py +17 -0
- holmes/core/runbooks.py +26 -0
- holmes/core/safeguards.py +120 -0
- holmes/core/supabase_dal.py +540 -0
- holmes/core/tool_calling_llm.py +798 -0
- holmes/core/tools.py +566 -0
- holmes/core/tools_utils/__init__.py +0 -0
- holmes/core/tools_utils/tool_executor.py +65 -0
- holmes/core/tools_utils/toolset_utils.py +52 -0
- holmes/core/toolset_manager.py +418 -0
- holmes/interactive.py +229 -0
- holmes/main.py +1041 -0
- holmes/plugins/__init__.py +0 -0
- holmes/plugins/destinations/__init__.py +6 -0
- holmes/plugins/destinations/slack/__init__.py +2 -0
- holmes/plugins/destinations/slack/plugin.py +163 -0
- holmes/plugins/interfaces.py +32 -0
- holmes/plugins/prompts/__init__.py +48 -0
- holmes/plugins/prompts/_current_date_time.jinja2 +1 -0
- holmes/plugins/prompts/_default_log_prompt.jinja2 +11 -0
- holmes/plugins/prompts/_fetch_logs.jinja2 +36 -0
- holmes/plugins/prompts/_general_instructions.jinja2 +86 -0
- holmes/plugins/prompts/_global_instructions.jinja2 +12 -0
- holmes/plugins/prompts/_runbook_instructions.jinja2 +13 -0
- holmes/plugins/prompts/_toolsets_instructions.jinja2 +56 -0
- holmes/plugins/prompts/generic_ask.jinja2 +36 -0
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +32 -0
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +50 -0
- holmes/plugins/prompts/generic_investigation.jinja2 +42 -0
- holmes/plugins/prompts/generic_post_processing.jinja2 +13 -0
- holmes/plugins/prompts/generic_ticket.jinja2 +12 -0
- holmes/plugins/prompts/investigation_output_format.jinja2 +32 -0
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +84 -0
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +39 -0
- holmes/plugins/runbooks/README.md +22 -0
- holmes/plugins/runbooks/__init__.py +100 -0
- holmes/plugins/runbooks/catalog.json +14 -0
- holmes/plugins/runbooks/jira.yaml +12 -0
- holmes/plugins/runbooks/kube-prometheus-stack.yaml +10 -0
- holmes/plugins/runbooks/networking/dns_troubleshooting_instructions.md +66 -0
- holmes/plugins/runbooks/upgrade/upgrade_troubleshooting_instructions.md +44 -0
- holmes/plugins/sources/github/__init__.py +77 -0
- holmes/plugins/sources/jira/__init__.py +123 -0
- holmes/plugins/sources/opsgenie/__init__.py +93 -0
- holmes/plugins/sources/pagerduty/__init__.py +147 -0
- holmes/plugins/sources/prometheus/__init__.py +0 -0
- holmes/plugins/sources/prometheus/models.py +104 -0
- holmes/plugins/sources/prometheus/plugin.py +154 -0
- holmes/plugins/toolsets/__init__.py +171 -0
- holmes/plugins/toolsets/aks-node-health.yaml +65 -0
- holmes/plugins/toolsets/aks.yaml +86 -0
- holmes/plugins/toolsets/argocd.yaml +70 -0
- holmes/plugins/toolsets/atlas_mongodb/instructions.jinja2 +8 -0
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +307 -0
- holmes/plugins/toolsets/aws.yaml +76 -0
- holmes/plugins/toolsets/azure_sql/__init__.py +0 -0
- holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +600 -0
- holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +309 -0
- holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +445 -0
- holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +251 -0
- holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +317 -0
- holmes/plugins/toolsets/azure_sql/azure_base_toolset.py +55 -0
- holmes/plugins/toolsets/azure_sql/azure_sql_instructions.jinja2 +137 -0
- holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +183 -0
- holmes/plugins/toolsets/azure_sql/install.md +66 -0
- holmes/plugins/toolsets/azure_sql/tools/__init__.py +1 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +324 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +243 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +205 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +249 -0
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +373 -0
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +237 -0
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +172 -0
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +170 -0
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +188 -0
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +180 -0
- holmes/plugins/toolsets/azure_sql/utils.py +83 -0
- holmes/plugins/toolsets/bash/__init__.py +0 -0
- holmes/plugins/toolsets/bash/bash_instructions.jinja2 +14 -0
- holmes/plugins/toolsets/bash/bash_toolset.py +208 -0
- holmes/plugins/toolsets/bash/common/bash.py +52 -0
- holmes/plugins/toolsets/bash/common/config.py +14 -0
- holmes/plugins/toolsets/bash/common/stringify.py +25 -0
- holmes/plugins/toolsets/bash/common/validators.py +24 -0
- holmes/plugins/toolsets/bash/grep/__init__.py +52 -0
- holmes/plugins/toolsets/bash/kubectl/__init__.py +100 -0
- holmes/plugins/toolsets/bash/kubectl/constants.py +96 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_describe.py +66 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_events.py +88 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +108 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_logs.py +20 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_run.py +46 -0
- holmes/plugins/toolsets/bash/kubectl/kubectl_top.py +81 -0
- holmes/plugins/toolsets/bash/parse_command.py +103 -0
- holmes/plugins/toolsets/confluence.yaml +19 -0
- holmes/plugins/toolsets/consts.py +5 -0
- holmes/plugins/toolsets/coralogix/api.py +158 -0
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +103 -0
- holmes/plugins/toolsets/coralogix/utils.py +181 -0
- holmes/plugins/toolsets/datadog.py +153 -0
- holmes/plugins/toolsets/docker.yaml +46 -0
- holmes/plugins/toolsets/git.py +756 -0
- holmes/plugins/toolsets/grafana/__init__.py +0 -0
- holmes/plugins/toolsets/grafana/base_grafana_toolset.py +54 -0
- holmes/plugins/toolsets/grafana/common.py +68 -0
- holmes/plugins/toolsets/grafana/grafana_api.py +31 -0
- holmes/plugins/toolsets/grafana/loki_api.py +89 -0
- holmes/plugins/toolsets/grafana/tempo_api.py +124 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +102 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +102 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +10 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +299 -0
- holmes/plugins/toolsets/grafana/trace_parser.py +195 -0
- holmes/plugins/toolsets/helm.yaml +42 -0
- holmes/plugins/toolsets/internet/internet.py +275 -0
- holmes/plugins/toolsets/internet/notion.py +137 -0
- holmes/plugins/toolsets/kafka.py +638 -0
- holmes/plugins/toolsets/kubernetes.yaml +255 -0
- holmes/plugins/toolsets/kubernetes_logs.py +426 -0
- holmes/plugins/toolsets/kubernetes_logs.yaml +42 -0
- holmes/plugins/toolsets/logging_utils/__init__.py +0 -0
- holmes/plugins/toolsets/logging_utils/logging_api.py +217 -0
- holmes/plugins/toolsets/logging_utils/types.py +0 -0
- holmes/plugins/toolsets/mcp/toolset_mcp.py +135 -0
- holmes/plugins/toolsets/newrelic.py +222 -0
- holmes/plugins/toolsets/opensearch/__init__.py +0 -0
- holmes/plugins/toolsets/opensearch/opensearch.py +245 -0
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +151 -0
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +211 -0
- holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +12 -0
- holmes/plugins/toolsets/opensearch/opensearch_utils.py +166 -0
- holmes/plugins/toolsets/prometheus/prometheus.py +818 -0
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +38 -0
- holmes/plugins/toolsets/rabbitmq/api.py +398 -0
- holmes/plugins/toolsets/rabbitmq/rabbitmq_instructions.jinja2 +37 -0
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +222 -0
- holmes/plugins/toolsets/robusta/__init__.py +0 -0
- holmes/plugins/toolsets/robusta/robusta.py +235 -0
- holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +24 -0
- holmes/plugins/toolsets/runbook/__init__.py +0 -0
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +78 -0
- holmes/plugins/toolsets/service_discovery.py +92 -0
- holmes/plugins/toolsets/servicenow/install.md +37 -0
- holmes/plugins/toolsets/servicenow/instructions.jinja2 +3 -0
- holmes/plugins/toolsets/servicenow/servicenow.py +198 -0
- holmes/plugins/toolsets/slab.yaml +20 -0
- holmes/plugins/toolsets/utils.py +137 -0
- holmes/plugins/utils.py +14 -0
- holmes/utils/__init__.py +0 -0
- holmes/utils/cache.py +84 -0
- holmes/utils/cert_utils.py +40 -0
- holmes/utils/default_toolset_installation_guide.jinja2 +44 -0
- holmes/utils/definitions.py +13 -0
- holmes/utils/env.py +53 -0
- holmes/utils/file_utils.py +56 -0
- holmes/utils/global_instructions.py +20 -0
- holmes/utils/holmes_status.py +22 -0
- holmes/utils/holmes_sync_toolsets.py +80 -0
- holmes/utils/markdown_utils.py +55 -0
- holmes/utils/pydantic_utils.py +54 -0
- holmes/utils/robusta.py +10 -0
- holmes/utils/tags.py +97 -0
- holmesgpt-0.11.5.dist-info/LICENSE.txt +21 -0
- holmesgpt-0.11.5.dist-info/METADATA +400 -0
- holmesgpt-0.11.5.dist-info/RECORD +183 -0
- holmesgpt-0.11.5.dist-info/WHEEL +4 -0
- holmesgpt-0.11.5.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
|
+
|
|
3
|
+
import sentry_sdk
|
|
4
|
+
|
|
5
|
+
from holmes.core.models import (
|
|
6
|
+
ToolCallConversationResult,
|
|
7
|
+
IssueChatRequest,
|
|
8
|
+
WorkloadHealthChatRequest,
|
|
9
|
+
)
|
|
10
|
+
from holmes.plugins.prompts import load_and_render_prompt
|
|
11
|
+
from holmes.core.tool_calling_llm import ToolCallingLLM
|
|
12
|
+
from holmes.utils.global_instructions import (
|
|
13
|
+
Instructions,
|
|
14
|
+
add_global_instructions_to_user_prompt,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
DEFAULT_TOOL_SIZE = 10000
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@sentry_sdk.trace
|
|
21
|
+
def calculate_tool_size(
|
|
22
|
+
ai: ToolCallingLLM, messages_without_tools: list[dict], number_of_tools: int
|
|
23
|
+
) -> int:
|
|
24
|
+
if number_of_tools == 0:
|
|
25
|
+
return DEFAULT_TOOL_SIZE
|
|
26
|
+
|
|
27
|
+
context_window = ai.llm.get_context_window_size()
|
|
28
|
+
message_size_without_tools = ai.llm.count_tokens_for_message(messages_without_tools)
|
|
29
|
+
maximum_output_token = ai.llm.get_maximum_output_token()
|
|
30
|
+
|
|
31
|
+
tool_size = min(
|
|
32
|
+
DEFAULT_TOOL_SIZE,
|
|
33
|
+
int(
|
|
34
|
+
(context_window - message_size_without_tools - maximum_output_token)
|
|
35
|
+
/ number_of_tools
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
return tool_size
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def truncate_tool_outputs(
|
|
42
|
+
tools: list, tool_size: int
|
|
43
|
+
) -> list[ToolCallConversationResult]:
|
|
44
|
+
return [
|
|
45
|
+
ToolCallConversationResult(
|
|
46
|
+
name=tool.name,
|
|
47
|
+
description=tool.description,
|
|
48
|
+
output=tool.output[:tool_size],
|
|
49
|
+
)
|
|
50
|
+
for tool in tools
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def truncate_tool_messages(conversation_history: list, tool_size: int) -> None:
|
|
55
|
+
for message in conversation_history:
|
|
56
|
+
if message.get("role") == "tool":
|
|
57
|
+
message["content"] = message["content"][:tool_size]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_issue_chat_messages(
|
|
61
|
+
issue_chat_request: IssueChatRequest,
|
|
62
|
+
ai: ToolCallingLLM,
|
|
63
|
+
global_instructions: Optional[Instructions] = None,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
This function generates a list of messages for issue conversation and ensures that the message sequence adheres to the model's context window limitations
|
|
67
|
+
by truncating tool outputs as necessary before sending to llm.
|
|
68
|
+
|
|
69
|
+
We always expect conversation_history to be passed in the openAI format which is supported by litellm and passed back by us.
|
|
70
|
+
That's why we assume that first message in the conversation is system message and truncate tools for it.
|
|
71
|
+
|
|
72
|
+
System prompt handling:
|
|
73
|
+
1. For new conversations (empty conversation_history):
|
|
74
|
+
- Creates a new system prompt using generic_ask_for_issue_conversation.jinja2 template
|
|
75
|
+
- Includes investigation analysis, tools (if any), and issue type information
|
|
76
|
+
- If there are tools, calculates appropriate tool size and truncates tool outputs
|
|
77
|
+
|
|
78
|
+
2. For existing conversations:
|
|
79
|
+
- Preserves the conversation history
|
|
80
|
+
- Updates the first message (system prompt) with recalculated content
|
|
81
|
+
- Truncates tool outputs if necessary to fit context window
|
|
82
|
+
- Maintains the original conversation flow while ensuring context limits
|
|
83
|
+
|
|
84
|
+
Example structure of conversation history:
|
|
85
|
+
conversation_history = [
|
|
86
|
+
# System prompt
|
|
87
|
+
{"role": "system", "content": "...."},
|
|
88
|
+
# User message
|
|
89
|
+
{"role": "user", "content": "Can you get the weather forecast for today?"},
|
|
90
|
+
# Assistant initiates a tool call
|
|
91
|
+
{
|
|
92
|
+
"role": "assistant",
|
|
93
|
+
"content": None,
|
|
94
|
+
"tool_call": {
|
|
95
|
+
"name": "get_weather",
|
|
96
|
+
"arguments": "{\"location\": \"San Francisco\"}"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
# Tool/Function response
|
|
100
|
+
{
|
|
101
|
+
"role": "tool",
|
|
102
|
+
"name": "get_weather",
|
|
103
|
+
"content": "{\"forecast\": \"Sunny, 70 degrees Fahrenheit.\"}"
|
|
104
|
+
},
|
|
105
|
+
# Assistant's final response to the user
|
|
106
|
+
{
|
|
107
|
+
"role": "assistant",
|
|
108
|
+
"content": "The weather in San Francisco today is sunny with a high of 70 degrees Fahrenheit."
|
|
109
|
+
},
|
|
110
|
+
]
|
|
111
|
+
"""
|
|
112
|
+
template_path = "builtin://generic_ask_for_issue_conversation.jinja2"
|
|
113
|
+
|
|
114
|
+
conversation_history = issue_chat_request.conversation_history
|
|
115
|
+
user_prompt = issue_chat_request.ask
|
|
116
|
+
investigation_analysis = issue_chat_request.investigation_result.result
|
|
117
|
+
tools_for_investigation = issue_chat_request.investigation_result.tools
|
|
118
|
+
|
|
119
|
+
if not conversation_history or len(conversation_history) == 0:
|
|
120
|
+
user_prompt = add_global_instructions_to_user_prompt(
|
|
121
|
+
user_prompt, global_instructions
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
number_of_tools_for_investigation = len(tools_for_investigation) # type: ignore
|
|
125
|
+
if number_of_tools_for_investigation == 0:
|
|
126
|
+
system_prompt = load_and_render_prompt(
|
|
127
|
+
template_path,
|
|
128
|
+
{
|
|
129
|
+
"investigation": investigation_analysis,
|
|
130
|
+
"tools_called_for_investigation": tools_for_investigation,
|
|
131
|
+
"issue": issue_chat_request.issue_type,
|
|
132
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
133
|
+
},
|
|
134
|
+
)
|
|
135
|
+
messages = [
|
|
136
|
+
{
|
|
137
|
+
"role": "system",
|
|
138
|
+
"content": system_prompt,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"role": "user",
|
|
142
|
+
"content": user_prompt,
|
|
143
|
+
},
|
|
144
|
+
]
|
|
145
|
+
return messages
|
|
146
|
+
|
|
147
|
+
template_context_without_tools = {
|
|
148
|
+
"investigation": investigation_analysis,
|
|
149
|
+
"tools_called_for_investigation": None,
|
|
150
|
+
"issue": issue_chat_request.issue_type,
|
|
151
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
152
|
+
}
|
|
153
|
+
system_prompt_without_tools = load_and_render_prompt(
|
|
154
|
+
template_path, template_context_without_tools
|
|
155
|
+
)
|
|
156
|
+
messages_without_tools = [
|
|
157
|
+
{
|
|
158
|
+
"role": "system",
|
|
159
|
+
"content": system_prompt_without_tools,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"role": "user",
|
|
163
|
+
"content": user_prompt,
|
|
164
|
+
},
|
|
165
|
+
]
|
|
166
|
+
tool_size = calculate_tool_size(
|
|
167
|
+
ai, messages_without_tools, number_of_tools_for_investigation
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
truncated_investigation_result_tool_calls = [
|
|
171
|
+
ToolCallConversationResult(
|
|
172
|
+
name=tool.name,
|
|
173
|
+
description=tool.description,
|
|
174
|
+
output=tool.output[:tool_size],
|
|
175
|
+
)
|
|
176
|
+
for tool in tools_for_investigation # type: ignore
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
truncated_template_context = {
|
|
180
|
+
"investigation": investigation_analysis,
|
|
181
|
+
"tools_called_for_investigation": truncated_investigation_result_tool_calls,
|
|
182
|
+
"issue": issue_chat_request.issue_type,
|
|
183
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
184
|
+
}
|
|
185
|
+
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
186
|
+
template_path, truncated_template_context
|
|
187
|
+
)
|
|
188
|
+
return [
|
|
189
|
+
{
|
|
190
|
+
"role": "system",
|
|
191
|
+
"content": system_prompt_with_truncated_tools,
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"role": "user",
|
|
195
|
+
"content": user_prompt,
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
user_prompt = add_global_instructions_to_user_prompt(
|
|
200
|
+
user_prompt, global_instructions
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
conversation_history.append(
|
|
204
|
+
{
|
|
205
|
+
"role": "user",
|
|
206
|
+
"content": user_prompt,
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
number_of_tools = len(tools_for_investigation) + len( # type: ignore
|
|
210
|
+
[message for message in conversation_history if message.get("role") == "tool"]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if number_of_tools == 0:
|
|
214
|
+
return conversation_history
|
|
215
|
+
|
|
216
|
+
conversation_history_without_tools = [
|
|
217
|
+
message for message in conversation_history if message.get("role") != "tool"
|
|
218
|
+
]
|
|
219
|
+
template_context_without_tools = {
|
|
220
|
+
"investigation": investigation_analysis,
|
|
221
|
+
"tools_called_for_investigation": None,
|
|
222
|
+
"issue": issue_chat_request.issue_type,
|
|
223
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
224
|
+
}
|
|
225
|
+
system_prompt_without_tools = load_and_render_prompt(
|
|
226
|
+
template_path, template_context_without_tools
|
|
227
|
+
)
|
|
228
|
+
conversation_history_without_tools[0]["content"] = system_prompt_without_tools
|
|
229
|
+
|
|
230
|
+
tool_size = calculate_tool_size(
|
|
231
|
+
ai, conversation_history_without_tools, number_of_tools
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
truncated_investigation_result_tool_calls = [
|
|
235
|
+
ToolCallConversationResult(
|
|
236
|
+
name=tool.name, description=tool.description, output=tool.output[:tool_size]
|
|
237
|
+
)
|
|
238
|
+
for tool in tools_for_investigation # type: ignore
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
template_context = {
|
|
242
|
+
"investigation": investigation_analysis,
|
|
243
|
+
"tools_called_for_investigation": truncated_investigation_result_tool_calls,
|
|
244
|
+
"issue": issue_chat_request.issue_type,
|
|
245
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
246
|
+
}
|
|
247
|
+
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
248
|
+
template_path, template_context
|
|
249
|
+
)
|
|
250
|
+
conversation_history[0]["content"] = system_prompt_with_truncated_tools
|
|
251
|
+
|
|
252
|
+
truncate_tool_messages(conversation_history, tool_size)
|
|
253
|
+
|
|
254
|
+
return conversation_history
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def add_or_update_system_prompt(
|
|
258
|
+
conversation_history: List[Dict[str, str]], ai: ToolCallingLLM
|
|
259
|
+
):
|
|
260
|
+
"""Either add the system prompt or replace an existing system prompt.
|
|
261
|
+
As a 'defensive' measure, this code will only replace an existing system prompt if it is the
|
|
262
|
+
first message in the conversation history.
|
|
263
|
+
This code will add a new system prompt if no message with role 'system' exists in the conversation history.
|
|
264
|
+
|
|
265
|
+
"""
|
|
266
|
+
template_path = "builtin://generic_ask_conversation.jinja2"
|
|
267
|
+
context = {
|
|
268
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
system_prompt = load_and_render_prompt(template_path, context)
|
|
272
|
+
|
|
273
|
+
if not conversation_history or len(conversation_history) == 0:
|
|
274
|
+
conversation_history.append({"role": "system", "content": system_prompt})
|
|
275
|
+
elif conversation_history[0]["role"] == "system":
|
|
276
|
+
conversation_history[0]["content"] = system_prompt
|
|
277
|
+
else:
|
|
278
|
+
existing_system_prompt = next(
|
|
279
|
+
(
|
|
280
|
+
message
|
|
281
|
+
for message in conversation_history
|
|
282
|
+
if message.get("role") == "system"
|
|
283
|
+
),
|
|
284
|
+
None,
|
|
285
|
+
)
|
|
286
|
+
if not existing_system_prompt:
|
|
287
|
+
conversation_history.insert(0, {"role": "system", "content": system_prompt})
|
|
288
|
+
|
|
289
|
+
return conversation_history
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def build_chat_messages(
|
|
293
|
+
ask: str,
|
|
294
|
+
conversation_history: Optional[List[Dict[str, str]]],
|
|
295
|
+
ai: ToolCallingLLM,
|
|
296
|
+
global_instructions: Optional[Instructions] = None,
|
|
297
|
+
) -> List[dict]:
|
|
298
|
+
"""
|
|
299
|
+
This function generates a list of messages for general chat conversation and ensures that the message sequence adheres to the model's context window limitations
|
|
300
|
+
by truncating tool outputs as necessary before sending to llm.
|
|
301
|
+
|
|
302
|
+
We always expect conversation_history to be passed in the openAI format which is supported by litellm and passed back by us.
|
|
303
|
+
That's why we assume that first message in the conversation is system message and truncate tools for it.
|
|
304
|
+
|
|
305
|
+
System prompt handling:
|
|
306
|
+
1. For new conversations (empty conversation_history):
|
|
307
|
+
- Creates a new system prompt using generic_ask_conversation.jinja2 template
|
|
308
|
+
- Uses an empty template context (no specific analysis or tools required)
|
|
309
|
+
- Adds global instructions to the user prompt if provided
|
|
310
|
+
|
|
311
|
+
2. For existing conversations:
|
|
312
|
+
- Preserves the conversation history as is
|
|
313
|
+
- Replaces any existing system prompt with new one if it exists
|
|
314
|
+
- Only truncates tool messages if they exist in the conversation
|
|
315
|
+
- Maintains the original conversation flow while ensuring context limits
|
|
316
|
+
|
|
317
|
+
Example structure of conversation history:
|
|
318
|
+
conversation_history = [
|
|
319
|
+
# System prompt for general chat
|
|
320
|
+
{"role": "system", "content": "...."},
|
|
321
|
+
# User message with a general question
|
|
322
|
+
{"role": "user", "content": "Can you analyze the logs from my application?"},
|
|
323
|
+
# Assistant initiates a tool call
|
|
324
|
+
{
|
|
325
|
+
"role": "assistant",
|
|
326
|
+
"content": None,
|
|
327
|
+
"tool_call": {
|
|
328
|
+
"name": "fetch_application_logs",
|
|
329
|
+
"arguments": "{\"service\": \"backend\", \"time_range\": \"last_hour\"}"
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
# Tool/Function response
|
|
333
|
+
{
|
|
334
|
+
"role": "tool",
|
|
335
|
+
"name": "fetch_application_logs",
|
|
336
|
+
"content": "{\"log_entries\": [\"Error in processing request\", \"Connection timeout\"]}"
|
|
337
|
+
},
|
|
338
|
+
# Assistant's final response to the user
|
|
339
|
+
{
|
|
340
|
+
"role": "assistant",
|
|
341
|
+
"content": "I've analyzed your application logs and found some issues: there are error messages related to request processing and connection timeouts."
|
|
342
|
+
},
|
|
343
|
+
]
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
if not conversation_history:
|
|
347
|
+
conversation_history = []
|
|
348
|
+
else:
|
|
349
|
+
conversation_history = conversation_history.copy()
|
|
350
|
+
|
|
351
|
+
conversation_history = add_or_update_system_prompt(
|
|
352
|
+
conversation_history=conversation_history, ai=ai
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
ask = add_global_instructions_to_user_prompt(ask, global_instructions)
|
|
356
|
+
|
|
357
|
+
conversation_history.append( # type: ignore
|
|
358
|
+
{
|
|
359
|
+
"role": "user",
|
|
360
|
+
"content": ask,
|
|
361
|
+
},
|
|
362
|
+
)
|
|
363
|
+
number_of_tools = len(
|
|
364
|
+
[message for message in conversation_history if message.get("role") == "tool"] # type: ignore
|
|
365
|
+
)
|
|
366
|
+
if number_of_tools == 0:
|
|
367
|
+
return conversation_history # type: ignore
|
|
368
|
+
|
|
369
|
+
conversation_history_without_tools = [
|
|
370
|
+
message
|
|
371
|
+
for message in conversation_history # type: ignore
|
|
372
|
+
if message.get("role") != "tool" # type: ignore
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
tool_size = calculate_tool_size(
|
|
376
|
+
ai, conversation_history_without_tools, number_of_tools
|
|
377
|
+
)
|
|
378
|
+
truncate_tool_messages(conversation_history, tool_size) # type: ignore
|
|
379
|
+
return conversation_history # type: ignore
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def build_workload_health_chat_messages(
|
|
383
|
+
workload_health_chat_request: WorkloadHealthChatRequest,
|
|
384
|
+
ai: ToolCallingLLM,
|
|
385
|
+
global_instructions: Optional[Instructions] = None,
|
|
386
|
+
):
|
|
387
|
+
"""
|
|
388
|
+
This function generates a list of messages for workload health conversation and ensures that the message sequence adheres to the model's context window limitations
|
|
389
|
+
by truncating tool outputs as necessary before sending to llm.
|
|
390
|
+
|
|
391
|
+
We always expect conversation_history to be passed in the openAI format which is supported by litellm and passed back by us.
|
|
392
|
+
That's why we assume that first message in the conversation is system message and truncate tools for it.
|
|
393
|
+
|
|
394
|
+
System prompt handling:
|
|
395
|
+
1. For new conversations (empty conversation_history):
|
|
396
|
+
- Creates a new system prompt using kubernetes_workload_chat.jinja2 template
|
|
397
|
+
- Includes workload analysis, tools (if any), and resource information
|
|
398
|
+
- If there are tools, calculates appropriate tool size and truncates tool outputs
|
|
399
|
+
|
|
400
|
+
2. For existing conversations:
|
|
401
|
+
- Preserves the conversation history
|
|
402
|
+
- Updates the first message (system prompt) with recalculated content
|
|
403
|
+
- Truncates tool outputs if necessary to fit context window
|
|
404
|
+
- Maintains the original conversation flow while ensuring context limits
|
|
405
|
+
|
|
406
|
+
Example structure of conversation history:
|
|
407
|
+
conversation_history = [
|
|
408
|
+
# System prompt with workload analysis
|
|
409
|
+
{"role": "system", "content": "...."},
|
|
410
|
+
# User message asking about workload health
|
|
411
|
+
{"role": "user", "content": "What's the current health status of my deployment?"},
|
|
412
|
+
# Assistant initiates a tool call
|
|
413
|
+
{
|
|
414
|
+
"role": "assistant",
|
|
415
|
+
"content": None,
|
|
416
|
+
"tool_call": {
|
|
417
|
+
"name": "check_workload_metrics",
|
|
418
|
+
"arguments": "{\"namespace\": \"default\", \"workload\": \"my-deployment\"}"
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
# Tool/Function response
|
|
422
|
+
{
|
|
423
|
+
"role": "tool",
|
|
424
|
+
"name": "check_workload_metrics",
|
|
425
|
+
"content": "{\"cpu_usage\": \"45%\", \"memory_usage\": \"60%\", \"status\": \"Running\"}"
|
|
426
|
+
},
|
|
427
|
+
# Assistant's final response to the user
|
|
428
|
+
{
|
|
429
|
+
"role": "assistant",
|
|
430
|
+
"content": "Your deployment is running normally with CPU usage at 45% and memory usage at 60%."
|
|
431
|
+
},
|
|
432
|
+
]
|
|
433
|
+
"""
|
|
434
|
+
|
|
435
|
+
template_path = "builtin://kubernetes_workload_chat.jinja2"
|
|
436
|
+
|
|
437
|
+
conversation_history = workload_health_chat_request.conversation_history
|
|
438
|
+
user_prompt = workload_health_chat_request.ask
|
|
439
|
+
workload_analysis = workload_health_chat_request.workload_health_result.analysis
|
|
440
|
+
tools_for_workload = workload_health_chat_request.workload_health_result.tools
|
|
441
|
+
resource = workload_health_chat_request.resource
|
|
442
|
+
|
|
443
|
+
if not conversation_history or len(conversation_history) == 0:
|
|
444
|
+
user_prompt = add_global_instructions_to_user_prompt(
|
|
445
|
+
user_prompt, global_instructions
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
number_of_tools_for_workload = len(tools_for_workload) # type: ignore
|
|
449
|
+
if number_of_tools_for_workload == 0:
|
|
450
|
+
system_prompt = load_and_render_prompt(
|
|
451
|
+
template_path,
|
|
452
|
+
{
|
|
453
|
+
"workload_analysis": workload_analysis,
|
|
454
|
+
"tools_called_for_workload": tools_for_workload,
|
|
455
|
+
"resource": resource,
|
|
456
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
457
|
+
},
|
|
458
|
+
)
|
|
459
|
+
messages = [
|
|
460
|
+
{
|
|
461
|
+
"role": "system",
|
|
462
|
+
"content": system_prompt,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"role": "user",
|
|
466
|
+
"content": user_prompt,
|
|
467
|
+
},
|
|
468
|
+
]
|
|
469
|
+
return messages
|
|
470
|
+
|
|
471
|
+
template_context_without_tools = {
|
|
472
|
+
"workload_analysis": workload_analysis,
|
|
473
|
+
"tools_called_for_workload": None,
|
|
474
|
+
"resource": resource,
|
|
475
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
476
|
+
}
|
|
477
|
+
system_prompt_without_tools = load_and_render_prompt(
|
|
478
|
+
template_path, template_context_without_tools
|
|
479
|
+
)
|
|
480
|
+
messages_without_tools = [
|
|
481
|
+
{
|
|
482
|
+
"role": "system",
|
|
483
|
+
"content": system_prompt_without_tools,
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
"role": "user",
|
|
487
|
+
"content": user_prompt,
|
|
488
|
+
},
|
|
489
|
+
]
|
|
490
|
+
tool_size = calculate_tool_size(
|
|
491
|
+
ai, messages_without_tools, number_of_tools_for_workload
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
truncated_workload_result_tool_calls = [
|
|
495
|
+
ToolCallConversationResult(
|
|
496
|
+
name=tool.name,
|
|
497
|
+
description=tool.description,
|
|
498
|
+
output=tool.output[:tool_size],
|
|
499
|
+
)
|
|
500
|
+
for tool in tools_for_workload # type: ignore
|
|
501
|
+
]
|
|
502
|
+
|
|
503
|
+
truncated_template_context = {
|
|
504
|
+
"workload_analysis": workload_analysis,
|
|
505
|
+
"tools_called_for_workload": truncated_workload_result_tool_calls,
|
|
506
|
+
"resource": resource,
|
|
507
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
508
|
+
}
|
|
509
|
+
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
510
|
+
template_path, truncated_template_context
|
|
511
|
+
)
|
|
512
|
+
return [
|
|
513
|
+
{
|
|
514
|
+
"role": "system",
|
|
515
|
+
"content": system_prompt_with_truncated_tools,
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
"role": "user",
|
|
519
|
+
"content": user_prompt,
|
|
520
|
+
},
|
|
521
|
+
]
|
|
522
|
+
|
|
523
|
+
user_prompt = add_global_instructions_to_user_prompt(
|
|
524
|
+
user_prompt, global_instructions
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
conversation_history.append(
|
|
528
|
+
{
|
|
529
|
+
"role": "user",
|
|
530
|
+
"content": user_prompt,
|
|
531
|
+
}
|
|
532
|
+
)
|
|
533
|
+
number_of_tools = len(tools_for_workload) + len( # type: ignore
|
|
534
|
+
[message for message in conversation_history if message.get("role") == "tool"]
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
if number_of_tools == 0:
|
|
538
|
+
return conversation_history
|
|
539
|
+
|
|
540
|
+
conversation_history_without_tools = [
|
|
541
|
+
message for message in conversation_history if message.get("role") != "tool"
|
|
542
|
+
]
|
|
543
|
+
template_context_without_tools = {
|
|
544
|
+
"workload_analysis": workload_analysis,
|
|
545
|
+
"tools_called_for_workload": None,
|
|
546
|
+
"resource": resource,
|
|
547
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
548
|
+
}
|
|
549
|
+
system_prompt_without_tools = load_and_render_prompt(
|
|
550
|
+
template_path, template_context_without_tools
|
|
551
|
+
)
|
|
552
|
+
conversation_history_without_tools[0]["content"] = system_prompt_without_tools
|
|
553
|
+
|
|
554
|
+
tool_size = calculate_tool_size(
|
|
555
|
+
ai, conversation_history_without_tools, number_of_tools
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
truncated_workload_result_tool_calls = [
|
|
559
|
+
ToolCallConversationResult(
|
|
560
|
+
name=tool.name, description=tool.description, output=tool.output[:tool_size]
|
|
561
|
+
)
|
|
562
|
+
for tool in tools_for_workload # type: ignore
|
|
563
|
+
]
|
|
564
|
+
|
|
565
|
+
template_context = {
|
|
566
|
+
"workload_analysis": workload_analysis,
|
|
567
|
+
"tools_called_for_workload": truncated_workload_result_tool_calls,
|
|
568
|
+
"resource": resource,
|
|
569
|
+
"toolsets": ai.tool_executor.toolsets,
|
|
570
|
+
}
|
|
571
|
+
system_prompt_with_truncated_tools = load_and_render_prompt(
|
|
572
|
+
template_path, template_context
|
|
573
|
+
)
|
|
574
|
+
conversation_history[0]["content"] = system_prompt_with_truncated_tools
|
|
575
|
+
|
|
576
|
+
truncate_tool_messages(conversation_history, tool_size)
|
|
577
|
+
|
|
578
|
+
return conversation_history
|