22import logging
33
44import requests
5+ from prometrix import PrometheusQueryResult
56
67from robusta .core .model .base_params import (
78 AIInvestigateParams ,
9+ ChartValuesFormat ,
810 HolmesChatParams ,
911 HolmesConversationParams ,
1012 HolmesIssueChatParams ,
13+ HolmesWorkloadHealthChatParams ,
1114 HolmesWorkloadHealthParams ,
1215 ResourceInfo ,
13- HolmesWorkloadHealthChatParams
1416)
1517from robusta .core .model .events import ExecutionBaseEvent
1618from robusta .core .playbooks .actions_registry import action
19+ from robusta .core .playbooks .prometheus_enrichment_utils import build_chart_from_prometheus_result
1720from robusta .core .reporting import Finding , FindingSubject
1821from robusta .core .reporting .base import EnrichmentType
1922from robusta .core .reporting .consts import FindingSubjectType , FindingType
2023from robusta .core .reporting .holmes import (
24+ FileBlock ,
2125 HolmesChatRequest ,
2226 HolmesChatResult ,
2327 HolmesChatResultsBlock ,
2933 HolmesResultsBlock ,
3034 HolmesWorkloadHealthRequest ,
3135)
36+ from robusta .core .reporting .utils import convert_svg_to_png
3237from robusta .core .schedule .model import FixedDelayRepeat
3338from robusta .integrations .kubernetes .autogenerated .events import KubernetesAnyChangeEvent
3439from robusta .integrations .prometheus .utils import HolmesDiscovery
@@ -65,12 +70,18 @@ def ask_holmes(event: ExecutionBaseEvent, params: AIInvestigateParams):
6570 )
6671
6772 if params .stream :
68- with requests .post (f"{ holmes_url } /api/stream/investigate" , data = holmes_req .json (), stream = True , headers = {"Connection" : "keep-alive" }) as resp :
73+ with requests .post (
74+ f"{ holmes_url } /api/stream/investigate" ,
75+ data = holmes_req .json (),
76+ stream = True ,
77+ headers = {"Connection" : "keep-alive" },
78+ ) as resp :
6979 resp .raise_for_status ()
70- for line in resp .iter_content (chunk_size = None , decode_unicode = True ): # Avoid streaming chunks from holmes. send them as they arrive.
80+ for line in resp .iter_content (
81+ chunk_size = None , decode_unicode = True
82+ ): # Avoid streaming chunks from holmes. send them as they arrive.
7183 if line :
7284 event .ws (data = line )
73-
7485 return
7586
7687 else :
@@ -182,7 +193,9 @@ def build_conversation_title(params: HolmesConversationParams) -> str:
182193
183194
184195def add_labels_to_ask (params : HolmesConversationParams ) -> str :
185- label_string = f"the alert has the following labels: { params .context .get ('labels' )} " if params .context .get ("labels" ) else ""
196+ label_string = (
197+ f"the alert has the following labels: { params .context .get ('labels' )} " if params .context .get ("labels" ) else ""
198+ )
186199 ask = f"{ params .ask } , { label_string } " if label_string else params .ask
187200 logging .debug (f"holmes ask query: { ask } " )
188201 return ask
@@ -342,6 +355,34 @@ def holmes_chat(event: ExecutionBaseEvent, params: HolmesChatParams):
342355 result = requests .post (f"{ holmes_url } /api/chat" , data = holmes_req .json ())
343356 result .raise_for_status ()
344357 holmes_result = HolmesChatResult (** json .loads (result .text ))
358+ holmes_result .files = []
359+ if params .render_graph_images :
360+ try :
361+ for tool in holmes_result .tool_calls :
362+ if tool .tool_name != "execute_prometheus_range_query" :
363+ continue
364+
365+ json_content = json .loads (tool .result )
366+ query_result = PrometheusQueryResult (data = json_content .get ("data" , {}))
367+ try :
368+ output_type_str = json_content .get ("output_type" , "Plain" )
369+ output_type = ChartValuesFormat [output_type_str ]
370+ except KeyError :
371+ output_type = ChartValuesFormat .Plain # fallback in case of an invalid string
372+
373+ chart = build_chart_from_prometheus_result (
374+ query_result , json_content .get ("description" , "graph" ), values_format = output_type
375+ )
376+ contents = convert_svg_to_png (chart .render ())
377+ name = json_content .get ("description" , "graph" ).replace (" " , "_" )
378+ holmes_result .files .append (FileBlock (f"{ name } .png" , contents ))
379+
380+ holmes_result .tool_calls = [
381+ tool for tool in holmes_result .tool_calls if tool .tool_name != "execute_prometheus_range_query"
382+ ]
383+
384+ except Exception :
385+ logging .exception (f"Failed to convert tools to images" )
345386
346387 finding = Finding (
347388 title = "AI Ask Chat" ,
@@ -352,6 +393,7 @@ def holmes_chat(event: ExecutionBaseEvent, params: HolmesChatParams):
352393 finding_type = FindingType .AI_ANALYSIS ,
353394 failure = False ,
354395 )
396+
355397 finding .add_enrichment (
356398 [HolmesChatResultsBlock (holmes_result = holmes_result )], enrichment_type = EnrichmentType .ai_analysis
357399 )
0 commit comments