holmesgpt 0.13.2__py3-none-any.whl → 0.16.2a0__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.
Files changed (134) hide show
  1. holmes/__init__.py +1 -1
  2. holmes/clients/robusta_client.py +17 -4
  3. holmes/common/env_vars.py +40 -1
  4. holmes/config.py +114 -144
  5. holmes/core/conversations.py +53 -14
  6. holmes/core/feedback.py +191 -0
  7. holmes/core/investigation.py +18 -22
  8. holmes/core/llm.py +489 -88
  9. holmes/core/models.py +103 -1
  10. holmes/core/openai_formatting.py +13 -0
  11. holmes/core/prompt.py +1 -1
  12. holmes/core/safeguards.py +4 -4
  13. holmes/core/supabase_dal.py +293 -100
  14. holmes/core/tool_calling_llm.py +423 -323
  15. holmes/core/tools.py +311 -33
  16. holmes/core/tools_utils/token_counting.py +14 -0
  17. holmes/core/tools_utils/tool_context_window_limiter.py +57 -0
  18. holmes/core/tools_utils/tool_executor.py +13 -8
  19. holmes/core/toolset_manager.py +155 -4
  20. holmes/core/tracing.py +6 -1
  21. holmes/core/transformers/__init__.py +23 -0
  22. holmes/core/transformers/base.py +62 -0
  23. holmes/core/transformers/llm_summarize.py +174 -0
  24. holmes/core/transformers/registry.py +122 -0
  25. holmes/core/transformers/transformer.py +31 -0
  26. holmes/core/truncation/compaction.py +59 -0
  27. holmes/core/truncation/dal_truncation_utils.py +23 -0
  28. holmes/core/truncation/input_context_window_limiter.py +218 -0
  29. holmes/interactive.py +177 -24
  30. holmes/main.py +7 -4
  31. holmes/plugins/prompts/_fetch_logs.jinja2 +26 -1
  32. holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
  33. holmes/plugins/prompts/_runbook_instructions.jinja2 +23 -12
  34. holmes/plugins/prompts/conversation_history_compaction.jinja2 +88 -0
  35. holmes/plugins/prompts/generic_ask.jinja2 +2 -4
  36. holmes/plugins/prompts/generic_ask_conversation.jinja2 +2 -1
  37. holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +2 -1
  38. holmes/plugins/prompts/generic_investigation.jinja2 +2 -1
  39. holmes/plugins/prompts/investigation_procedure.jinja2 +48 -0
  40. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +2 -1
  41. holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +2 -1
  42. holmes/plugins/runbooks/__init__.py +117 -18
  43. holmes/plugins/runbooks/catalog.json +2 -0
  44. holmes/plugins/toolsets/__init__.py +21 -8
  45. holmes/plugins/toolsets/aks-node-health.yaml +46 -0
  46. holmes/plugins/toolsets/aks.yaml +64 -0
  47. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +26 -36
  48. holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +0 -1
  49. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +10 -7
  50. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +9 -6
  51. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +8 -6
  52. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +8 -6
  53. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +9 -6
  54. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +9 -7
  55. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +9 -6
  56. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +9 -6
  57. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +9 -6
  58. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +9 -6
  59. holmes/plugins/toolsets/bash/bash_toolset.py +10 -13
  60. holmes/plugins/toolsets/bash/common/bash.py +7 -7
  61. holmes/plugins/toolsets/cilium.yaml +284 -0
  62. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +5 -3
  63. holmes/plugins/toolsets/datadog/datadog_api.py +490 -24
  64. holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +21 -10
  65. holmes/plugins/toolsets/datadog/toolset_datadog_general.py +349 -216
  66. holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +190 -19
  67. holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +101 -44
  68. holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +13 -16
  69. holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +25 -31
  70. holmes/plugins/toolsets/git.py +51 -46
  71. holmes/plugins/toolsets/grafana/common.py +15 -3
  72. holmes/plugins/toolsets/grafana/grafana_api.py +46 -24
  73. holmes/plugins/toolsets/grafana/grafana_tempo_api.py +454 -0
  74. holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +9 -0
  75. holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +117 -0
  76. holmes/plugins/toolsets/grafana/toolset_grafana.py +211 -91
  77. holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +27 -0
  78. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
  79. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +653 -293
  80. holmes/plugins/toolsets/grafana/trace_parser.py +1 -1
  81. holmes/plugins/toolsets/internet/internet.py +6 -7
  82. holmes/plugins/toolsets/internet/notion.py +5 -6
  83. holmes/plugins/toolsets/investigator/core_investigation.py +42 -34
  84. holmes/plugins/toolsets/kafka.py +25 -36
  85. holmes/plugins/toolsets/kubernetes.yaml +58 -84
  86. holmes/plugins/toolsets/kubernetes_logs.py +6 -6
  87. holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
  88. holmes/plugins/toolsets/logging_utils/logging_api.py +80 -4
  89. holmes/plugins/toolsets/mcp/toolset_mcp.py +181 -55
  90. holmes/plugins/toolsets/newrelic/__init__.py +0 -0
  91. holmes/plugins/toolsets/newrelic/new_relic_api.py +125 -0
  92. holmes/plugins/toolsets/newrelic/newrelic.jinja2 +41 -0
  93. holmes/plugins/toolsets/newrelic/newrelic.py +163 -0
  94. holmes/plugins/toolsets/opensearch/opensearch.py +10 -17
  95. holmes/plugins/toolsets/opensearch/opensearch_logs.py +7 -7
  96. holmes/plugins/toolsets/opensearch/opensearch_ppl_query_docs.jinja2 +1616 -0
  97. holmes/plugins/toolsets/opensearch/opensearch_query_assist.py +78 -0
  98. holmes/plugins/toolsets/opensearch/opensearch_query_assist_instructions.jinja2 +223 -0
  99. holmes/plugins/toolsets/opensearch/opensearch_traces.py +13 -16
  100. holmes/plugins/toolsets/openshift.yaml +283 -0
  101. holmes/plugins/toolsets/prometheus/prometheus.py +915 -390
  102. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +43 -2
  103. holmes/plugins/toolsets/prometheus/utils.py +28 -0
  104. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +9 -10
  105. holmes/plugins/toolsets/robusta/robusta.py +236 -65
  106. holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
  107. holmes/plugins/toolsets/runbook/runbook_fetcher.py +137 -26
  108. holmes/plugins/toolsets/service_discovery.py +1 -1
  109. holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
  110. holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
  111. holmes/plugins/toolsets/utils.py +88 -0
  112. holmes/utils/config_utils.py +91 -0
  113. holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
  114. holmes/utils/env.py +7 -0
  115. holmes/utils/global_instructions.py +75 -10
  116. holmes/utils/holmes_status.py +2 -1
  117. holmes/utils/holmes_sync_toolsets.py +0 -2
  118. holmes/utils/krr_utils.py +188 -0
  119. holmes/utils/sentry_helper.py +41 -0
  120. holmes/utils/stream.py +61 -7
  121. holmes/version.py +34 -14
  122. holmesgpt-0.16.2a0.dist-info/LICENSE +178 -0
  123. {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/METADATA +29 -27
  124. {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/RECORD +126 -102
  125. holmes/core/performance_timing.py +0 -72
  126. holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
  127. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
  128. holmes/plugins/toolsets/newrelic.py +0 -231
  129. holmes/plugins/toolsets/servicenow/install.md +0 -37
  130. holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
  131. holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
  132. holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
  133. {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/WHEEL +0 -0
  134. {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,117 @@
1
+ from typing import Dict
2
+ import os
3
+ import json
4
+ from holmes.core.tools import (
5
+ Tool,
6
+ ToolInvokeContext,
7
+ ToolParameter,
8
+ )
9
+ from holmes.plugins.toolsets.consts import (
10
+ STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
11
+ )
12
+
13
+ from holmes.plugins.toolsets.grafana.common import get_base_url
14
+ from holmes.plugins.toolsets.grafana.toolset_grafana import BaseGrafanaToolset
15
+ from holmes.plugins.toolsets.utils import (
16
+ process_timestamps_to_rfc3339,
17
+ standard_start_datetime_tool_param_description,
18
+ )
19
+ from holmes.plugins.toolsets.logging_utils.logging_api import (
20
+ DEFAULT_TIME_SPAN_SECONDS,
21
+ DEFAULT_LOG_LIMIT,
22
+ )
23
+ from holmes.plugins.toolsets.grafana.loki_api import (
24
+ execute_loki_query,
25
+ )
26
+
27
+ from holmes.plugins.toolsets.utils import toolset_name_for_one_liner
28
+ from holmes.core.tools import StructuredToolResult, StructuredToolResultStatus
29
+
30
+
31
+ class GrafanaLokiToolset(BaseGrafanaToolset):
32
+ def __init__(self):
33
+ super().__init__(
34
+ name="grafana/loki",
35
+ description="Runs loki log queries using Grafana Loki or Loki directly.",
36
+ icon_url="https://grafana.com/media/docs/loki/logo-grafana-loki.png",
37
+ docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/grafanaloki/",
38
+ tools=[],
39
+ )
40
+
41
+ self.tools = [LokiQuery(toolset=self)]
42
+ instructions_filepath = os.path.abspath(
43
+ os.path.join(os.path.dirname(__file__), "instructions.jinja2")
44
+ )
45
+ self._load_llm_instructions(jinja_template=f"file://{instructions_filepath}")
46
+
47
+
48
+ class LokiQuery(Tool):
49
+ toolset: GrafanaLokiToolset
50
+ name: str = "grafana_loki_query"
51
+ description: str = "Run a query against Grafana Loki using LogQL query language."
52
+ parameters: Dict[str, ToolParameter] = {
53
+ "query": ToolParameter(
54
+ description="LogQL query string.",
55
+ type="string",
56
+ required=True,
57
+ ),
58
+ "start": ToolParameter(
59
+ description=standard_start_datetime_tool_param_description(
60
+ DEFAULT_TIME_SPAN_SECONDS
61
+ ),
62
+ type="string",
63
+ required=False,
64
+ ),
65
+ "end": ToolParameter(
66
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
67
+ type="string",
68
+ required=False,
69
+ ),
70
+ "limit": ToolParameter(
71
+ description="Maximum number of entries to return (default: 100)",
72
+ type="integer",
73
+ required=False,
74
+ ),
75
+ }
76
+
77
+ def get_parameterized_one_liner(self, params) -> str:
78
+ return f"{toolset_name_for_one_liner(self.toolset.name)}: loki query {params}"
79
+
80
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
81
+ (start, end) = process_timestamps_to_rfc3339(
82
+ start_timestamp=params.get("start"),
83
+ end_timestamp=params.get("end"),
84
+ default_time_span_seconds=DEFAULT_TIME_SPAN_SECONDS,
85
+ )
86
+
87
+ config = self.toolset._grafana_config
88
+ try:
89
+ data = execute_loki_query(
90
+ base_url=get_base_url(config),
91
+ api_key=config.api_key,
92
+ headers=config.headers,
93
+ query=params.get(
94
+ "query", '{query="no_query_fallback"}'
95
+ ), # make sure a string returns. fall back to query that will return nothing.
96
+ start=start,
97
+ end=end,
98
+ limit=params.get("limit") or DEFAULT_LOG_LIMIT,
99
+ )
100
+ if data:
101
+ return StructuredToolResult(
102
+ status=StructuredToolResultStatus.SUCCESS,
103
+ data=json.dumps(data),
104
+ params=params,
105
+ )
106
+ else:
107
+ return StructuredToolResult(
108
+ status=StructuredToolResultStatus.NO_DATA,
109
+ params=params,
110
+ )
111
+ except Exception as e:
112
+ return StructuredToolResult(
113
+ status=StructuredToolResultStatus.ERROR,
114
+ params=params,
115
+ error=str(e),
116
+ url=f"{get_base_url(config)}/loki/api/v1/query_range",
117
+ )
@@ -1,127 +1,247 @@
1
- from typing import Dict, List
2
- from urllib.parse import urlencode, urljoin
1
+ import os
2
+ from typing import ClassVar, Dict, Optional, Type, cast
3
+ from urllib.parse import urljoin
4
+ from abc import ABC
3
5
  from holmes.core.tools import (
4
6
  StructuredToolResult,
5
7
  Tool,
8
+ ToolInvokeContext,
6
9
  ToolParameter,
7
- ToolResultStatus,
10
+ StructuredToolResultStatus,
8
11
  )
9
12
  from holmes.plugins.toolsets.grafana.base_grafana_toolset import BaseGrafanaToolset
10
13
  import requests # type: ignore
11
- import logging
12
14
 
15
+ from holmes.plugins.toolsets.grafana.common import (
16
+ get_base_url,
17
+ GrafanaConfig,
18
+ build_headers,
19
+ )
13
20
  from holmes.plugins.toolsets.utils import toolset_name_for_one_liner
14
21
 
15
22
 
16
- class ListAndBuildGrafanaDashboardURLs(Tool):
17
- def __init__(self, toolset: BaseGrafanaToolset):
23
+ class GrafanaDashboardConfig(GrafanaConfig):
24
+ """Configuration specific to Grafana Dashboard toolset with api/health as default healthcheck"""
25
+
26
+ healthcheck: Optional[str] = "api/health"
27
+
28
+
29
+ class GrafanaToolset(BaseGrafanaToolset):
30
+ config_class: ClassVar[Type[GrafanaDashboardConfig]] = GrafanaDashboardConfig
31
+
32
+ def __init__(self):
33
+ super().__init__(
34
+ name="grafana/dashboards",
35
+ description="Provides tools for interacting with Grafana dashboards",
36
+ icon_url="https://w7.pngwing.com/pngs/434/923/png-transparent-grafana-hd-logo-thumbnail.png",
37
+ docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/grafanadashboards/",
38
+ tools=[
39
+ SearchDashboards(self),
40
+ GetDashboardByUID(self),
41
+ GetHomeDashboard(self),
42
+ GetDashboardTags(self),
43
+ ],
44
+ )
45
+
46
+ self._load_llm_instructions_from_file(
47
+ os.path.dirname(__file__), "toolset_grafana_dashboard.jinja2"
48
+ )
49
+
50
+ @property
51
+ def grafana_config(self) -> GrafanaDashboardConfig:
52
+ return cast(GrafanaDashboardConfig, self._grafana_config)
53
+
54
+
55
+ class BaseGrafanaTool(Tool, ABC):
56
+ """Base class for Grafana tools with common HTTP request functionality."""
57
+
58
+ def __init__(self, toolset: GrafanaToolset, *args, **kwargs):
59
+ super().__init__(*args, **kwargs)
60
+ self._toolset = toolset
61
+
62
+ def _make_grafana_request(
63
+ self,
64
+ endpoint: str,
65
+ params: dict,
66
+ query_params: Optional[Dict] = None,
67
+ timeout: int = 30,
68
+ ) -> StructuredToolResult:
69
+ """Make a GET request to Grafana API and return structured result.
70
+
71
+ Args:
72
+ endpoint: API endpoint path (e.g., "/api/search")
73
+ params: Original parameters passed to the tool
74
+ query_params: Optional query parameters for the request
75
+
76
+ Returns:
77
+ StructuredToolResult with the API response data
78
+ """
79
+ url = urljoin(get_base_url(self._toolset.grafana_config), endpoint)
80
+ headers = build_headers(
81
+ api_key=self._toolset.grafana_config.api_key,
82
+ additional_headers=self._toolset.grafana_config.headers,
83
+ )
84
+
85
+ response = requests.get(
86
+ url, headers=headers, params=query_params, timeout=timeout
87
+ )
88
+ response.raise_for_status()
89
+ data = response.json()
90
+
91
+ return StructuredToolResult(
92
+ status=StructuredToolResultStatus.SUCCESS,
93
+ data=data,
94
+ url=url,
95
+ params=params,
96
+ )
97
+
98
+
99
+ class SearchDashboards(BaseGrafanaTool):
100
+ def __init__(self, toolset: GrafanaToolset):
18
101
  super().__init__(
19
- name="list_and_build_grafana_dashboard_urls",
20
- description="Lists all available Grafana dashboard urls",
102
+ toolset=toolset,
103
+ name="grafana_search_dashboards",
104
+ description="Search for Grafana dashboards and folders using the /api/search endpoint",
21
105
  parameters={
22
- "cluster_name": ToolParameter(
23
- description="The cluster name. Defaults to None.",
106
+ "query": ToolParameter(
107
+ description="Search text to filter dashboards",
24
108
  type="string",
25
109
  required=False,
26
110
  ),
27
- "namespace": ToolParameter(
28
- description="The namespace for filtering dashboards.",
111
+ "tag": ToolParameter(
112
+ description="Search dashboards by tag",
29
113
  type="string",
30
114
  required=False,
31
115
  ),
32
- "node_name": ToolParameter(
33
- description="The node name to filter for node-related dashboards.",
116
+ "type": ToolParameter(
117
+ description="Filter by type: 'dash-folder' or 'dash-db'",
34
118
  type="string",
35
119
  required=False,
36
120
  ),
37
- "pod_name": ToolParameter(
38
- description="The pod name to filter dashboards.",
121
+ "dashboardIds": ToolParameter(
122
+ description="List of dashboard IDs to filter (comma-separated)",
39
123
  type="string",
40
124
  required=False,
41
125
  ),
126
+ "dashboardUIDs": ToolParameter(
127
+ description="List of dashboard UIDs to search for (comma-separated)",
128
+ type="string",
129
+ required=False,
130
+ ),
131
+ "folderUIDs": ToolParameter(
132
+ description="List of folder UIDs to search within (comma-separated)",
133
+ type="string",
134
+ required=False,
135
+ ),
136
+ "starred": ToolParameter(
137
+ description="Return only starred dashboards",
138
+ type="boolean",
139
+ required=False,
140
+ ),
141
+ "limit": ToolParameter(
142
+ description="Maximum results (default 1000, max 5000)",
143
+ type="integer",
144
+ required=False,
145
+ ),
146
+ "page": ToolParameter(
147
+ description="Page number for pagination",
148
+ type="integer",
149
+ required=False,
150
+ ),
42
151
  },
43
152
  )
44
- self._toolset = toolset
45
153
 
46
- def _invoke(
47
- self, params: dict, user_approved: bool = False
48
- ) -> StructuredToolResult:
49
- url = urljoin(
50
- self._toolset._grafana_config.url, "/api/search?query=&type=dash-db"
154
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
155
+ query_params = {}
156
+ if params.get("query"):
157
+ query_params["query"] = params["query"]
158
+ if params.get("tag"):
159
+ query_params["tag"] = params["tag"]
160
+ if params.get("type"):
161
+ query_params["type"] = params["type"]
162
+ if params.get("dashboardIds"):
163
+ # Check if dashboardIds also needs to be passed as multiple params
164
+ dashboard_ids = params["dashboardIds"].split(",")
165
+ query_params["dashboardIds"] = [
166
+ dashboard_id.strip()
167
+ for dashboard_id in dashboard_ids
168
+ if dashboard_id.strip()
169
+ ]
170
+ if params.get("dashboardUIDs"):
171
+ # Handle dashboardUIDs as a list - split comma-separated values
172
+ dashboard_uids = params["dashboardUIDs"].split(",")
173
+ query_params["dashboardUIDs"] = [
174
+ uid.strip() for uid in dashboard_uids if uid.strip()
175
+ ]
176
+ if params.get("folderUIDs"):
177
+ # Check if folderUIDs also needs to be passed as multiple params
178
+ folder_uids = params["folderUIDs"].split(",")
179
+ query_params["folderUIDs"] = [
180
+ uid.strip() for uid in folder_uids if uid.strip()
181
+ ]
182
+ if params.get("starred") is not None:
183
+ query_params["starred"] = str(params["starred"]).lower()
184
+ if params.get("limit"):
185
+ query_params["limit"] = params["limit"]
186
+ if params.get("page"):
187
+ query_params["page"] = params["page"]
188
+
189
+ return self._make_grafana_request("/api/search", params, query_params)
190
+
191
+ def get_parameterized_one_liner(self, params: Dict) -> str:
192
+ return f"{toolset_name_for_one_liner(self._toolset.name)}: Search Dashboards"
193
+
194
+
195
+ class GetDashboardByUID(BaseGrafanaTool):
196
+ def __init__(self, toolset: GrafanaToolset):
197
+ super().__init__(
198
+ toolset=toolset,
199
+ name="grafana_get_dashboard_by_uid",
200
+ description="Get a dashboard by its UID using the /api/dashboards/uid/:uid endpoint",
201
+ parameters={
202
+ "uid": ToolParameter(
203
+ description="The unique identifier of the dashboard",
204
+ type="string",
205
+ required=True,
206
+ ),
207
+ },
51
208
  )
52
- headers = {"Authorization": f"Bearer {self._toolset._grafana_config.api_key}"}
53
-
54
- try:
55
- response = requests.get(url, headers=headers)
56
- response.raise_for_status()
57
- dashboards = response.json()
58
- formatted_dashboards: List[str] = []
59
- base_url = (
60
- self._toolset._grafana_config.external_url
61
- or self._toolset._grafana_config.url
62
- )
63
- for dash in dashboards:
64
- dashboard_url = urljoin(
65
- base_url,
66
- f"/d/{dash['uid']}/{dash['uri'].split('/')[-1]}",
67
- )
68
-
69
- params_dict = {
70
- "var-cluster": params.get("cluster_name", ""),
71
- "var-namespace": params.get("namespace", ""),
72
- "var-pod": params.get("pod_name", ""),
73
- "var-node": params.get("node_name", ""),
74
- "var-datasource": self._toolset._grafana_config.grafana_datasource_uid,
75
- "refresh": "5s",
76
- }
77
-
78
- # If filtering for nodes, ensure only node-related dashboards are included
79
- if params.get("node_name") and "node" not in dash["title"].lower():
80
- continue
81
-
82
- # we add all params since if the dashboard isnt configured for a param it will ignore it if it is added
83
- query_string = urlencode({k: v for k, v in params_dict.items() if v})
84
- dashboard_url = (
85
- f"{dashboard_url}?{query_string}" if query_string else dashboard_url
86
- )
87
-
88
- formatted_dashboards.append(
89
- f"Title: {dash['title']}\nURL: {dashboard_url}\n"
90
- )
91
-
92
- return StructuredToolResult(
93
- status=ToolResultStatus.SUCCESS
94
- if formatted_dashboards
95
- else ToolResultStatus.NO_DATA,
96
- data="\n".join(formatted_dashboards)
97
- if formatted_dashboards
98
- else "No dashboards found.",
99
- url=url,
100
- params=params,
101
- )
102
- except requests.RequestException as e:
103
- logging.error(f"Error fetching dashboards: {str(e)}")
104
- return StructuredToolResult(
105
- status=ToolResultStatus.ERROR,
106
- error=f"Error fetching dashboards: {str(e)}",
107
- url=url,
108
- params=params,
109
- )
209
+
210
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
211
+ uid = params["uid"]
212
+ return self._make_grafana_request(f"/api/dashboards/uid/{uid}", params)
110
213
 
111
214
  def get_parameterized_one_liner(self, params: Dict) -> str:
112
- return (
113
- f"{toolset_name_for_one_liner(self._toolset.name)}: List Grafana Dashboards"
215
+ return f"{toolset_name_for_one_liner(self._toolset.name)}: Get Dashboard {params.get('uid', '')}"
216
+
217
+
218
+ class GetHomeDashboard(BaseGrafanaTool):
219
+ def __init__(self, toolset: GrafanaToolset):
220
+ super().__init__(
221
+ toolset=toolset,
222
+ name="grafana_get_home_dashboard",
223
+ description="Get the home dashboard using the /api/dashboards/home endpoint",
224
+ parameters={},
114
225
  )
115
226
 
227
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
228
+ return self._make_grafana_request("/api/dashboards/home", params)
116
229
 
117
- class GrafanaToolset(BaseGrafanaToolset):
118
- def __init__(self):
230
+ def get_parameterized_one_liner(self, params: Dict) -> str:
231
+ return f"{toolset_name_for_one_liner(self._toolset.name)}: Get Home Dashboard"
232
+
233
+
234
+ class GetDashboardTags(BaseGrafanaTool):
235
+ def __init__(self, toolset: GrafanaToolset):
119
236
  super().__init__(
120
- name="grafana/grafana",
121
- description="Provides tools for interacting with Grafana dashboards",
122
- icon_url="https://w7.pngwing.com/pngs/434/923/png-transparent-grafana-hd-logo-thumbnail.png",
123
- docs_url="",
124
- tools=[
125
- ListAndBuildGrafanaDashboardURLs(self),
126
- ],
237
+ toolset=toolset,
238
+ name="grafana_get_dashboard_tags",
239
+ description="Get all tags used across dashboards using the /api/dashboards/tags endpoint",
240
+ parameters={},
127
241
  )
242
+
243
+ def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
244
+ return self._make_grafana_request("/api/dashboards/tags", params)
245
+
246
+ def get_parameterized_one_liner(self, params: Dict) -> str:
247
+ return f"{toolset_name_for_one_liner(self._toolset.name)}: Get Dashboard Tags"
@@ -0,0 +1,27 @@
1
+ ---
2
+
3
+ ## **Instruction: Prometheus Dashboard Query Execution**
4
+
5
+ **When running Prometheus queries from Grafana dashboards:**
6
+
7
+ ### **Key Rules:**
8
+ 1. **Always run as range queries** (not instant queries) to match dashboard behavior
9
+ 2. **Use dashboard's time range** from JSON: `"time": {"from": "now-1h", "to": "now"}`
10
+ 3. **Substitute variables:**
11
+ - `$__rate_interval` → `1m` (1-hour range) or `5m` (3+ hour range)
12
+ - `$cluster` → omit if empty
13
+ 4. **Copy query exactly** including `ceil()`, `sort_desc()`, etc.
14
+
15
+ ### **Example:**
16
+ ```
17
+ Dashboard: "now-1h" + query with [$__rate_interval]
18
+
19
+ Execute: start="-3600" + query with [1m]
20
+ ```
21
+
22
+ ### **Ignore:**
23
+ - `"refresh": "10s"` (UI setting, not query-related)
24
+
25
+ **This ensures you see the same time series data and historical spikes that users see on their dashboards.**
26
+
27
+ ---