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.

Files changed (183) hide show
  1. holmes/.git_archival.json +7 -0
  2. holmes/__init__.py +76 -0
  3. holmes/__init__.py.bak +76 -0
  4. holmes/clients/robusta_client.py +24 -0
  5. holmes/common/env_vars.py +47 -0
  6. holmes/config.py +526 -0
  7. holmes/core/__init__.py +0 -0
  8. holmes/core/conversations.py +578 -0
  9. holmes/core/investigation.py +152 -0
  10. holmes/core/investigation_structured_output.py +264 -0
  11. holmes/core/issue.py +54 -0
  12. holmes/core/llm.py +250 -0
  13. holmes/core/models.py +157 -0
  14. holmes/core/openai_formatting.py +51 -0
  15. holmes/core/performance_timing.py +72 -0
  16. holmes/core/prompt.py +42 -0
  17. holmes/core/resource_instruction.py +17 -0
  18. holmes/core/runbooks.py +26 -0
  19. holmes/core/safeguards.py +120 -0
  20. holmes/core/supabase_dal.py +540 -0
  21. holmes/core/tool_calling_llm.py +798 -0
  22. holmes/core/tools.py +566 -0
  23. holmes/core/tools_utils/__init__.py +0 -0
  24. holmes/core/tools_utils/tool_executor.py +65 -0
  25. holmes/core/tools_utils/toolset_utils.py +52 -0
  26. holmes/core/toolset_manager.py +418 -0
  27. holmes/interactive.py +229 -0
  28. holmes/main.py +1041 -0
  29. holmes/plugins/__init__.py +0 -0
  30. holmes/plugins/destinations/__init__.py +6 -0
  31. holmes/plugins/destinations/slack/__init__.py +2 -0
  32. holmes/plugins/destinations/slack/plugin.py +163 -0
  33. holmes/plugins/interfaces.py +32 -0
  34. holmes/plugins/prompts/__init__.py +48 -0
  35. holmes/plugins/prompts/_current_date_time.jinja2 +1 -0
  36. holmes/plugins/prompts/_default_log_prompt.jinja2 +11 -0
  37. holmes/plugins/prompts/_fetch_logs.jinja2 +36 -0
  38. holmes/plugins/prompts/_general_instructions.jinja2 +86 -0
  39. holmes/plugins/prompts/_global_instructions.jinja2 +12 -0
  40. holmes/plugins/prompts/_runbook_instructions.jinja2 +13 -0
  41. holmes/plugins/prompts/_toolsets_instructions.jinja2 +56 -0
  42. holmes/plugins/prompts/generic_ask.jinja2 +36 -0
  43. holmes/plugins/prompts/generic_ask_conversation.jinja2 +32 -0
  44. holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +50 -0
  45. holmes/plugins/prompts/generic_investigation.jinja2 +42 -0
  46. holmes/plugins/prompts/generic_post_processing.jinja2 +13 -0
  47. holmes/plugins/prompts/generic_ticket.jinja2 +12 -0
  48. holmes/plugins/prompts/investigation_output_format.jinja2 +32 -0
  49. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +84 -0
  50. holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +39 -0
  51. holmes/plugins/runbooks/README.md +22 -0
  52. holmes/plugins/runbooks/__init__.py +100 -0
  53. holmes/plugins/runbooks/catalog.json +14 -0
  54. holmes/plugins/runbooks/jira.yaml +12 -0
  55. holmes/plugins/runbooks/kube-prometheus-stack.yaml +10 -0
  56. holmes/plugins/runbooks/networking/dns_troubleshooting_instructions.md +66 -0
  57. holmes/plugins/runbooks/upgrade/upgrade_troubleshooting_instructions.md +44 -0
  58. holmes/plugins/sources/github/__init__.py +77 -0
  59. holmes/plugins/sources/jira/__init__.py +123 -0
  60. holmes/plugins/sources/opsgenie/__init__.py +93 -0
  61. holmes/plugins/sources/pagerduty/__init__.py +147 -0
  62. holmes/plugins/sources/prometheus/__init__.py +0 -0
  63. holmes/plugins/sources/prometheus/models.py +104 -0
  64. holmes/plugins/sources/prometheus/plugin.py +154 -0
  65. holmes/plugins/toolsets/__init__.py +171 -0
  66. holmes/plugins/toolsets/aks-node-health.yaml +65 -0
  67. holmes/plugins/toolsets/aks.yaml +86 -0
  68. holmes/plugins/toolsets/argocd.yaml +70 -0
  69. holmes/plugins/toolsets/atlas_mongodb/instructions.jinja2 +8 -0
  70. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +307 -0
  71. holmes/plugins/toolsets/aws.yaml +76 -0
  72. holmes/plugins/toolsets/azure_sql/__init__.py +0 -0
  73. holmes/plugins/toolsets/azure_sql/apis/alert_monitoring_api.py +600 -0
  74. holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +309 -0
  75. holmes/plugins/toolsets/azure_sql/apis/connection_failure_api.py +445 -0
  76. holmes/plugins/toolsets/azure_sql/apis/connection_monitoring_api.py +251 -0
  77. holmes/plugins/toolsets/azure_sql/apis/storage_analysis_api.py +317 -0
  78. holmes/plugins/toolsets/azure_sql/azure_base_toolset.py +55 -0
  79. holmes/plugins/toolsets/azure_sql/azure_sql_instructions.jinja2 +137 -0
  80. holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +183 -0
  81. holmes/plugins/toolsets/azure_sql/install.md +66 -0
  82. holmes/plugins/toolsets/azure_sql/tools/__init__.py +1 -0
  83. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +324 -0
  84. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +243 -0
  85. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +205 -0
  86. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +249 -0
  87. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +373 -0
  88. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +237 -0
  89. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +172 -0
  90. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +170 -0
  91. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +188 -0
  92. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +180 -0
  93. holmes/plugins/toolsets/azure_sql/utils.py +83 -0
  94. holmes/plugins/toolsets/bash/__init__.py +0 -0
  95. holmes/plugins/toolsets/bash/bash_instructions.jinja2 +14 -0
  96. holmes/plugins/toolsets/bash/bash_toolset.py +208 -0
  97. holmes/plugins/toolsets/bash/common/bash.py +52 -0
  98. holmes/plugins/toolsets/bash/common/config.py +14 -0
  99. holmes/plugins/toolsets/bash/common/stringify.py +25 -0
  100. holmes/plugins/toolsets/bash/common/validators.py +24 -0
  101. holmes/plugins/toolsets/bash/grep/__init__.py +52 -0
  102. holmes/plugins/toolsets/bash/kubectl/__init__.py +100 -0
  103. holmes/plugins/toolsets/bash/kubectl/constants.py +96 -0
  104. holmes/plugins/toolsets/bash/kubectl/kubectl_describe.py +66 -0
  105. holmes/plugins/toolsets/bash/kubectl/kubectl_events.py +88 -0
  106. holmes/plugins/toolsets/bash/kubectl/kubectl_get.py +108 -0
  107. holmes/plugins/toolsets/bash/kubectl/kubectl_logs.py +20 -0
  108. holmes/plugins/toolsets/bash/kubectl/kubectl_run.py +46 -0
  109. holmes/plugins/toolsets/bash/kubectl/kubectl_top.py +81 -0
  110. holmes/plugins/toolsets/bash/parse_command.py +103 -0
  111. holmes/plugins/toolsets/confluence.yaml +19 -0
  112. holmes/plugins/toolsets/consts.py +5 -0
  113. holmes/plugins/toolsets/coralogix/api.py +158 -0
  114. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +103 -0
  115. holmes/plugins/toolsets/coralogix/utils.py +181 -0
  116. holmes/plugins/toolsets/datadog.py +153 -0
  117. holmes/plugins/toolsets/docker.yaml +46 -0
  118. holmes/plugins/toolsets/git.py +756 -0
  119. holmes/plugins/toolsets/grafana/__init__.py +0 -0
  120. holmes/plugins/toolsets/grafana/base_grafana_toolset.py +54 -0
  121. holmes/plugins/toolsets/grafana/common.py +68 -0
  122. holmes/plugins/toolsets/grafana/grafana_api.py +31 -0
  123. holmes/plugins/toolsets/grafana/loki_api.py +89 -0
  124. holmes/plugins/toolsets/grafana/tempo_api.py +124 -0
  125. holmes/plugins/toolsets/grafana/toolset_grafana.py +102 -0
  126. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +102 -0
  127. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +10 -0
  128. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +299 -0
  129. holmes/plugins/toolsets/grafana/trace_parser.py +195 -0
  130. holmes/plugins/toolsets/helm.yaml +42 -0
  131. holmes/plugins/toolsets/internet/internet.py +275 -0
  132. holmes/plugins/toolsets/internet/notion.py +137 -0
  133. holmes/plugins/toolsets/kafka.py +638 -0
  134. holmes/plugins/toolsets/kubernetes.yaml +255 -0
  135. holmes/plugins/toolsets/kubernetes_logs.py +426 -0
  136. holmes/plugins/toolsets/kubernetes_logs.yaml +42 -0
  137. holmes/plugins/toolsets/logging_utils/__init__.py +0 -0
  138. holmes/plugins/toolsets/logging_utils/logging_api.py +217 -0
  139. holmes/plugins/toolsets/logging_utils/types.py +0 -0
  140. holmes/plugins/toolsets/mcp/toolset_mcp.py +135 -0
  141. holmes/plugins/toolsets/newrelic.py +222 -0
  142. holmes/plugins/toolsets/opensearch/__init__.py +0 -0
  143. holmes/plugins/toolsets/opensearch/opensearch.py +245 -0
  144. holmes/plugins/toolsets/opensearch/opensearch_logs.py +151 -0
  145. holmes/plugins/toolsets/opensearch/opensearch_traces.py +211 -0
  146. holmes/plugins/toolsets/opensearch/opensearch_traces_instructions.jinja2 +12 -0
  147. holmes/plugins/toolsets/opensearch/opensearch_utils.py +166 -0
  148. holmes/plugins/toolsets/prometheus/prometheus.py +818 -0
  149. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +38 -0
  150. holmes/plugins/toolsets/rabbitmq/api.py +398 -0
  151. holmes/plugins/toolsets/rabbitmq/rabbitmq_instructions.jinja2 +37 -0
  152. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +222 -0
  153. holmes/plugins/toolsets/robusta/__init__.py +0 -0
  154. holmes/plugins/toolsets/robusta/robusta.py +235 -0
  155. holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +24 -0
  156. holmes/plugins/toolsets/runbook/__init__.py +0 -0
  157. holmes/plugins/toolsets/runbook/runbook_fetcher.py +78 -0
  158. holmes/plugins/toolsets/service_discovery.py +92 -0
  159. holmes/plugins/toolsets/servicenow/install.md +37 -0
  160. holmes/plugins/toolsets/servicenow/instructions.jinja2 +3 -0
  161. holmes/plugins/toolsets/servicenow/servicenow.py +198 -0
  162. holmes/plugins/toolsets/slab.yaml +20 -0
  163. holmes/plugins/toolsets/utils.py +137 -0
  164. holmes/plugins/utils.py +14 -0
  165. holmes/utils/__init__.py +0 -0
  166. holmes/utils/cache.py +84 -0
  167. holmes/utils/cert_utils.py +40 -0
  168. holmes/utils/default_toolset_installation_guide.jinja2 +44 -0
  169. holmes/utils/definitions.py +13 -0
  170. holmes/utils/env.py +53 -0
  171. holmes/utils/file_utils.py +56 -0
  172. holmes/utils/global_instructions.py +20 -0
  173. holmes/utils/holmes_status.py +22 -0
  174. holmes/utils/holmes_sync_toolsets.py +80 -0
  175. holmes/utils/markdown_utils.py +55 -0
  176. holmes/utils/pydantic_utils.py +54 -0
  177. holmes/utils/robusta.py +10 -0
  178. holmes/utils/tags.py +97 -0
  179. holmesgpt-0.11.5.dist-info/LICENSE.txt +21 -0
  180. holmesgpt-0.11.5.dist-info/METADATA +400 -0
  181. holmesgpt-0.11.5.dist-info/RECORD +183 -0
  182. holmesgpt-0.11.5.dist-info/WHEEL +4 -0
  183. 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