holmesgpt 0.14.0a0__py3-none-any.whl → 0.14.1__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 (82) hide show
  1. holmes/__init__.py +1 -1
  2. holmes/clients/robusta_client.py +15 -4
  3. holmes/common/env_vars.py +8 -1
  4. holmes/config.py +66 -139
  5. holmes/core/investigation.py +1 -2
  6. holmes/core/llm.py +295 -52
  7. holmes/core/models.py +2 -0
  8. holmes/core/safeguards.py +4 -4
  9. holmes/core/supabase_dal.py +14 -8
  10. holmes/core/tool_calling_llm.py +110 -102
  11. holmes/core/tools.py +260 -25
  12. holmes/core/tools_utils/data_types.py +81 -0
  13. holmes/core/tools_utils/tool_context_window_limiter.py +33 -0
  14. holmes/core/tools_utils/tool_executor.py +2 -2
  15. holmes/core/toolset_manager.py +150 -3
  16. holmes/core/transformers/__init__.py +23 -0
  17. holmes/core/transformers/base.py +62 -0
  18. holmes/core/transformers/llm_summarize.py +174 -0
  19. holmes/core/transformers/registry.py +122 -0
  20. holmes/core/transformers/transformer.py +31 -0
  21. holmes/main.py +5 -0
  22. holmes/plugins/prompts/_fetch_logs.jinja2 +10 -1
  23. holmes/plugins/toolsets/aks-node-health.yaml +46 -0
  24. holmes/plugins/toolsets/aks.yaml +64 -0
  25. holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +17 -15
  26. holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +8 -4
  27. holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +7 -3
  28. holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +3 -3
  29. holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +3 -3
  30. holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +7 -3
  31. holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +4 -4
  32. holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +7 -3
  33. holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +7 -3
  34. holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +7 -3
  35. holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +7 -3
  36. holmes/plugins/toolsets/bash/bash_toolset.py +6 -6
  37. holmes/plugins/toolsets/bash/common/bash.py +7 -7
  38. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +5 -3
  39. holmes/plugins/toolsets/datadog/datadog_api.py +490 -24
  40. holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +21 -10
  41. holmes/plugins/toolsets/datadog/toolset_datadog_general.py +344 -205
  42. holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +189 -17
  43. holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +95 -30
  44. holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +10 -10
  45. holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +20 -20
  46. holmes/plugins/toolsets/git.py +21 -21
  47. holmes/plugins/toolsets/grafana/common.py +2 -2
  48. holmes/plugins/toolsets/grafana/toolset_grafana.py +4 -4
  49. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +5 -4
  50. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +123 -23
  51. holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +165 -307
  52. holmes/plugins/toolsets/internet/internet.py +3 -3
  53. holmes/plugins/toolsets/internet/notion.py +3 -3
  54. holmes/plugins/toolsets/investigator/core_investigation.py +3 -3
  55. holmes/plugins/toolsets/kafka.py +18 -18
  56. holmes/plugins/toolsets/kubernetes.yaml +58 -0
  57. holmes/plugins/toolsets/kubernetes_logs.py +6 -6
  58. holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
  59. holmes/plugins/toolsets/logging_utils/logging_api.py +1 -1
  60. holmes/plugins/toolsets/mcp/toolset_mcp.py +4 -4
  61. holmes/plugins/toolsets/newrelic.py +5 -5
  62. holmes/plugins/toolsets/opensearch/opensearch.py +5 -5
  63. holmes/plugins/toolsets/opensearch/opensearch_logs.py +7 -7
  64. holmes/plugins/toolsets/opensearch/opensearch_traces.py +10 -10
  65. holmes/plugins/toolsets/prometheus/prometheus.py +841 -351
  66. holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +39 -2
  67. holmes/plugins/toolsets/prometheus/utils.py +28 -0
  68. holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +6 -4
  69. holmes/plugins/toolsets/robusta/robusta.py +10 -10
  70. holmes/plugins/toolsets/runbook/runbook_fetcher.py +4 -4
  71. holmes/plugins/toolsets/servicenow/servicenow.py +6 -6
  72. holmes/plugins/toolsets/utils.py +88 -0
  73. holmes/utils/config_utils.py +91 -0
  74. holmes/utils/env.py +7 -0
  75. holmes/utils/holmes_status.py +2 -1
  76. holmes/utils/sentry_helper.py +41 -0
  77. holmes/utils/stream.py +9 -0
  78. {holmesgpt-0.14.0a0.dist-info → holmesgpt-0.14.1.dist-info}/METADATA +10 -14
  79. {holmesgpt-0.14.0a0.dist-info → holmesgpt-0.14.1.dist-info}/RECORD +82 -72
  80. {holmesgpt-0.14.0a0.dist-info → holmesgpt-0.14.1.dist-info}/LICENSE.txt +0 -0
  81. {holmesgpt-0.14.0a0.dist-info → holmesgpt-0.14.1.dist-info}/WHEEL +0 -0
  82. {holmesgpt-0.14.0a0.dist-info → holmesgpt-0.14.1.dist-info}/entry_points.txt +0 -0
@@ -3,33 +3,34 @@ from typing import Any, Dict, Tuple, cast, List
3
3
 
4
4
  import yaml # type: ignore
5
5
 
6
- from holmes.common.env_vars import load_bool
6
+ from holmes.common.env_vars import load_bool, MAX_GRAPH_POINTS
7
7
  from holmes.core.tools import (
8
8
  StructuredToolResult,
9
9
  Tool,
10
10
  ToolParameter,
11
- ToolResultStatus,
11
+ StructuredToolResultStatus,
12
12
  )
13
+ from holmes.plugins.toolsets.consts import STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION
13
14
  from holmes.plugins.toolsets.grafana.base_grafana_toolset import BaseGrafanaToolset
14
15
  from holmes.plugins.toolsets.grafana.common import (
15
16
  GrafanaTempoConfig,
16
17
  )
17
18
  from holmes.plugins.toolsets.grafana.grafana_tempo_api import GrafanaTempoAPI
18
19
  from holmes.plugins.toolsets.logging_utils.logging_api import (
19
- DEFAULT_TIME_SPAN_SECONDS,
20
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS,
20
21
  )
21
22
  from holmes.plugins.toolsets.utils import (
22
23
  toolset_name_for_one_liner,
23
24
  process_timestamps_to_int,
25
+ standard_start_datetime_tool_param_description,
26
+ adjust_step_for_max_points,
27
+ seconds_to_duration_string,
28
+ duration_string_to_seconds,
24
29
  )
25
30
 
26
31
  TEMPO_LABELS_ADD_PREFIX = load_bool("TEMPO_LABELS_ADD_PREFIX", True)
27
32
  TEMPO_API_USE_POST = False # Use GET method for direct API mapping
28
33
 
29
- ONE_HOUR_IN_SECONDS = 3600
30
- DEFAULT_TRACES_TIME_SPAN_SECONDS = DEFAULT_TIME_SPAN_SECONDS # 7 days
31
- DEFAULT_TAGS_TIME_SPAN_SECONDS = 8 * ONE_HOUR_IN_SECONDS # 8 hours
32
-
33
34
 
34
35
  class BaseGrafanaTempoToolset(BaseGrafanaToolset):
35
36
  config_class = GrafanaTempoConfig
@@ -109,213 +110,19 @@ class BaseGrafanaTempoToolset(BaseGrafanaToolset):
109
110
 
110
111
  return filters
111
112
 
112
-
113
- def validate_params(params: Dict[str, Any], expected_params: List[str]):
114
- for param in expected_params:
115
- if param in params and params[param] not in (None, "", [], {}):
116
- return None
117
-
118
- return f"At least one of the following argument is expected but none were set: {expected_params}"
119
-
120
-
121
- # class GetTempoTraces(Tool):
122
- # def __init__(self, toolset: BaseGrafanaTempoToolset):
123
- # super().__init__(
124
- # name="fetch_tempo_traces",
125
- # description="""Lists Tempo traces. At least one of `service_name`, `pod_name` or `deployment_name` argument is required.""",
126
- # parameters={
127
- # "min_duration": ToolParameter(
128
- # description="The minimum duration of traces to fetch, e.g., '5s' for 5 seconds.",
129
- # type="string",
130
- # required=True,
131
- # ),
132
- # "service_name": ToolParameter(
133
- # description="Filter traces by service name",
134
- # type="string",
135
- # required=False,
136
- # ),
137
- # "pod_name": ToolParameter(
138
- # description="Filter traces by pod name",
139
- # type="string",
140
- # required=False,
141
- # ),
142
- # "namespace_name": ToolParameter(
143
- # description="Filter traces by namespace",
144
- # type="string",
145
- # required=False,
146
- # ),
147
- # "deployment_name": ToolParameter(
148
- # description="Filter traces by deployment name",
149
- # type="string",
150
- # required=False,
151
- # ),
152
- # "node_name": ToolParameter(
153
- # description="Filter traces by node",
154
- # type="string",
155
- # required=False,
156
- # ),
157
- # "start_datetime": ToolParameter(
158
- # description=f"The beginning time boundary for the trace search period. String in RFC3339 format. If a negative integer, the number of seconds relative to the end_timestamp. Defaults to -{DEFAULT_TRACES_TIME_SPAN_SECONDS}",
159
- # type="string",
160
- # required=False,
161
- # ),
162
- # "end_datetime": ToolParameter(
163
- # description="The ending time boundary for the trace search period. String in RFC3339 format. Defaults to NOW().",
164
- # type="string",
165
- # required=False,
166
- # ),
167
- # "limit": ToolParameter(
168
- # description="Maximum number of traces to return. Defaults to 50",
169
- # type="string",
170
- # required=False,
171
- # ),
172
- # "sort": ToolParameter(
173
- # description="One of 'descending', 'ascending' or 'none' for no sorting. Defaults to descending",
174
- # type="string",
175
- # required=False,
176
- # ),
177
- # },
178
- # )
179
- # self._toolset = toolset
180
- #
181
- # def _invoke(self, params: Dict, user_approved: bool = False) -> StructuredToolResult:
182
- # # Create API instance
183
- # api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
184
- #
185
- # invalid_params_error = validate_params(
186
- # params, ["service_name", "pod_name", "deployment_name"]
187
- # )
188
- # if invalid_params_error:
189
- # return StructuredToolResult(
190
- # status=ToolResultStatus.ERROR,
191
- # error=invalid_params_error,
192
- # params=params,
193
- # )
194
- #
195
- # start, end = process_timestamps_to_int(
196
- # params.get("start_datetime"),
197
- # params.get("end_datetime"),
198
- # default_time_span_seconds=DEFAULT_TRACES_TIME_SPAN_SECONDS,
199
- # )
200
- #
201
- # filters = self._toolset.build_k8s_filters(params, use_exact_match=True)
202
- #
203
- # filters.append(f'duration>{get_param_or_raise(params, "min_duration")}')
204
- #
205
- # query = " && ".join(filters)
206
- # query = f"{{{query}}}"
207
- #
208
- # traces = api.search_traces_by_query(
209
- # q=query,
210
- # start=start,
211
- # end=end,
212
- # limit=params.get("limit", 50),
213
- # )
214
- # return StructuredToolResult(
215
- # status=ToolResultStatus.SUCCESS,
216
- # data=format_traces_list(traces),
217
- # params=params,
218
- # invocation=query,
219
- # )
220
- #
221
- # def get_parameterized_one_liner(self, params: Dict) -> str:
222
- # return f"{toolset_name_for_one_liner(self._toolset.name)}: Fetched Tempo Traces (min_duration={params.get('min_duration')})"
223
-
224
-
225
- # class GetTempoTags(Tool):
226
- # def __init__(self, toolset: BaseGrafanaTempoToolset):
227
- # super().__init__(
228
- # name="fetch_tempo_tags",
229
- # description="List the tags available in Tempo",
230
- # parameters={
231
- # "start_datetime": ToolParameter(
232
- # description=f"The beginning time boundary for the search period. String in RFC3339 format. If a negative integer, the number of seconds relative to the end_timestamp. Defaults to -{DEFAULT_TAGS_TIME_SPAN_SECONDS}",
233
- # type="string",
234
- # required=False,
235
- # ),
236
- # "end_datetime": ToolParameter(
237
- # description="The ending time boundary for the search period. String in RFC3339 format. Defaults to NOW().",
238
- # type="string",
239
- # required=False,
240
- # ),
241
- # },
242
- # )
243
- # self._toolset = toolset
244
- #
245
- # def _invoke(self, params: Dict, user_approved: bool = False) -> StructuredToolResult:
246
- # # Create API instance
247
- # api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
248
- #
249
- # start, end = process_timestamps_to_int(
250
- # start=params.get("start_datetime"),
251
- # end=params.get("end_datetime"),
252
- # default_time_span_seconds=DEFAULT_TAGS_TIME_SPAN_SECONDS,
253
- # )
254
- #
255
- # try:
256
- # data = api.search_tag_names_v2(start=start, end=end)
257
- # return StructuredToolResult(
258
- # status=ToolResultStatus.SUCCESS,
259
- # data=yaml.dump(data.get("scopes")),
260
- # params=params,
261
- # )
262
- # except Exception as e:
263
- # return StructuredToolResult(
264
- # status=ToolResultStatus.ERROR,
265
- # error=f"Failed to retrieve tags: {str(e)}",
266
- # params=params,
267
- # )
268
- #
269
- # def get_parameterized_one_liner(self, params: Dict) -> str:
270
- # return f"{toolset_name_for_one_liner(self._toolset.name)}: Fetched Tempo tags"
271
- #
272
-
273
-
274
- # class GetTempoTraceById(Tool):
275
- # def __init__(self, toolset: BaseGrafanaTempoToolset):
276
- # super().__init__(
277
- # name="fetch_tempo_trace_by_id",
278
- # description="""Retrieves detailed information about a Tempo trace using its trace ID. Use this to investigate a trace.""",
279
- # parameters={
280
- # "trace_id": ToolParameter(
281
- # description="The unique trace ID to fetch.",
282
- # type="string",
283
- # required=True,
284
- # ),
285
- # },
286
- # )
287
- # self._toolset = toolset
288
- #
289
- # def _invoke(self, params: Dict, user_approved: bool = False) -> StructuredToolResult:
290
- # # Create API instance
291
- # api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
292
- #
293
- # labels_mapping = self._toolset.grafana_config.labels
294
- # labels = list(labels_mapping.model_dump().values())
295
- #
296
- # # Get raw trace data
297
- # trace_data = api.query_trace_by_id_v2(
298
- # trace_id=get_param_or_raise(params, "trace_id")
299
- # )
300
- #
301
- # # Process the trace data (new API returns raw data)
302
- # formatted_trace = process_trace(trace_data, labels)
303
- #
304
- # return StructuredToolResult(
305
- # status=ToolResultStatus.SUCCESS,
306
- # data=formatted_trace,
307
- # params=params,
308
- # )
309
- #
310
- # def get_parameterized_one_liner(self, params: Dict) -> str:
311
- # return f"{toolset_name_for_one_liner(self._toolset.name)}: Fetched Tempo Trace (trace_id={params.get('trace_id')})"
312
- #
113
+ @staticmethod
114
+ def adjust_start_end_time(params: Dict) -> Tuple[int, int]:
115
+ return process_timestamps_to_int(
116
+ start=params.get("start"),
117
+ end=params.get("end"),
118
+ default_time_span_seconds=DEFAULT_GRAPH_TIME_SPAN_SECONDS,
119
+ )
313
120
 
314
121
 
315
122
  class FetchTracesSimpleComparison(Tool):
316
123
  def __init__(self, toolset: BaseGrafanaTempoToolset):
317
124
  super().__init__(
318
- name="fetch_tempo_traces_comparative_sample",
125
+ name="tempo_fetch_traces_comparative_sample",
319
126
  description="""Fetches statistics and representative samples of fast, slow, and typical traces for performance analysis. Requires either a `base_query` OR at least one of `service_name`, `pod_name`, `namespace_name`, `deployment_name`, `node_name`.
320
127
 
321
128
  Important: call this tool first when investigating performance issues via traces. This tool provides comprehensive analysis for identifying patterns.
@@ -351,7 +158,11 @@ Examples:
351
158
  required=False,
352
159
  ),
353
160
  "base_query": ToolParameter(
354
- description="Custom TraceQL filter",
161
+ description=(
162
+ "Custom TraceQL filter. Supports span/resource attributes, "
163
+ "duration, and aggregates (count(), avg(), min(), max(), sum()). "
164
+ "Examples: '{span.http.status_code>=400}', '{duration>100ms}'"
165
+ ),
355
166
  type="string",
356
167
  required=False,
357
168
  ),
@@ -360,13 +171,15 @@ Examples:
360
171
  type="integer",
361
172
  required=False,
362
173
  ),
363
- "start_datetime": ToolParameter(
364
- description=f"The beginning time boundary for the trace search period. String in RFC3339 format. If a negative integer, the number of seconds relative to the end_timestamp. Defaults to -{DEFAULT_TRACES_TIME_SPAN_SECONDS}",
174
+ "start": ToolParameter(
175
+ description=standard_start_datetime_tool_param_description(
176
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS
177
+ ),
365
178
  type="string",
366
179
  required=False,
367
180
  ),
368
- "end_datetime": ToolParameter(
369
- description="The ending time boundary for the trace search period. String in RFC3339 format. Defaults to NOW().",
181
+ "end": ToolParameter(
182
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
370
183
  type="string",
371
184
  required=False,
372
185
  ),
@@ -374,6 +187,14 @@ Examples:
374
187
  )
375
188
  self._toolset = toolset
376
189
 
190
+ @staticmethod
191
+ def validate_params(params: Dict[str, Any], expected_params: List[str]):
192
+ for param in expected_params:
193
+ if param in params and params[param] not in (None, "", [], {}):
194
+ return None
195
+
196
+ return f"At least one of the following argument is expected but none were set: {expected_params}"
197
+
377
198
  def _invoke(
378
199
  self, params: dict, user_approved: bool = False
379
200
  ) -> StructuredToolResult:
@@ -386,7 +207,7 @@ Examples:
386
207
  filters = self._toolset.build_k8s_filters(params, use_exact_match=False)
387
208
 
388
209
  # Validate that at least one parameter was provided
389
- invalid_params_error = validate_params(
210
+ invalid_params_error = FetchTracesSimpleComparison.validate_params(
390
211
  params,
391
212
  [
392
213
  "service_name",
@@ -398,7 +219,7 @@ Examples:
398
219
  )
399
220
  if invalid_params_error:
400
221
  return StructuredToolResult(
401
- status=ToolResultStatus.ERROR,
222
+ status=StructuredToolResultStatus.ERROR,
402
223
  error=invalid_params_error,
403
224
  params=params,
404
225
  )
@@ -407,11 +228,7 @@ Examples:
407
228
 
408
229
  sample_count = params.get("sample_count", 3)
409
230
 
410
- start, end = process_timestamps_to_int(
411
- params.get("start_datetime"),
412
- params.get("end_datetime"),
413
- default_time_span_seconds=DEFAULT_TRACES_TIME_SPAN_SECONDS,
414
- )
231
+ start, end = BaseGrafanaTempoToolset.adjust_start_end_time(params)
415
232
 
416
233
  # Create API instance
417
234
  api = GrafanaTempoAPI(
@@ -441,7 +258,7 @@ Examples:
441
258
  traces = all_traces_response.get("traces", [])
442
259
  if not traces:
443
260
  return StructuredToolResult(
444
- status=ToolResultStatus.SUCCESS,
261
+ status=StructuredToolResultStatus.SUCCESS,
445
262
  data="No traces found matching the query",
446
263
  params=params,
447
264
  )
@@ -518,14 +335,14 @@ Examples:
518
335
 
519
336
  # Return as YAML for readability
520
337
  return StructuredToolResult(
521
- status=ToolResultStatus.SUCCESS,
338
+ status=StructuredToolResultStatus.SUCCESS,
522
339
  data=yaml.dump(result, default_flow_style=False, sort_keys=False),
523
340
  params=params,
524
341
  )
525
342
 
526
343
  except Exception as e:
527
344
  return StructuredToolResult(
528
- status=ToolResultStatus.ERROR,
345
+ status=StructuredToolResultStatus.ERROR,
529
346
  error=f"Error fetching traces: {str(e)}",
530
347
  params=params,
531
348
  )
@@ -534,21 +351,39 @@ Examples:
534
351
  return f"{toolset_name_for_one_liner(self._toolset.name)}: Simple Tempo Traces Comparison"
535
352
 
536
353
 
537
- # New tools matching GrafanaTempoAPI methods
538
-
539
-
540
354
  class SearchTracesByQuery(Tool):
541
355
  def __init__(self, toolset: BaseGrafanaTempoToolset):
542
356
  super().__init__(
543
- name="search_traces_by_query",
357
+ name="tempo_search_traces_by_query",
544
358
  description=(
545
359
  "Search for traces using TraceQL query language. "
546
- "Uses the Tempo API endpoint: GET /api/search with 'q' parameter. "
547
- 'TraceQL allows complex filtering like: {resource.service.name="api"} && {span.http.status_code=500}'
360
+ "Uses the Tempo API endpoint: GET /api/search with 'q' parameter.\n\n"
361
+ "TraceQL can select traces based on:\n"
362
+ "- Span and resource attributes\n"
363
+ "- Timing and duration\n"
364
+ "- Aggregate functions:\n"
365
+ " • count() - Count number of spans\n"
366
+ " • avg(attribute) - Calculate average\n"
367
+ " • min(attribute) - Find minimum value\n"
368
+ " • max(attribute) - Find maximum value\n"
369
+ " • sum(attribute) - Sum values\n\n"
370
+ "Examples:\n"
371
+ '- Specific operation: {resource.service.name = "frontend" && name = "POST /api/orders"}\n'
372
+ '- Error traces: {resource.service.name="frontend" && name = "POST /api/orders" && status = error}\n'
373
+ '- HTTP errors: {resource.service.name="frontend" && name = "POST /api/orders" && span.http.status_code >= 500}\n'
374
+ '- Multi-service: {span.service.name="frontend" && name = "GET /api/products/{id}"} && {span.db.system="postgresql"}\n'
375
+ "- With aggregates: { status = error } | by(resource.service.name) | count() > 1"
548
376
  ),
549
377
  parameters={
550
378
  "q": ToolParameter(
551
- description="TraceQL query (e.g., '{resource.service.name=\"api\" && span.http.status_code=500}')",
379
+ description=(
380
+ "TraceQL query. Supports filtering by span/resource attributes, "
381
+ "duration, and aggregate functions (count(), avg(), min(), max(), sum()). "
382
+ "Examples: '{resource.service.name = \"frontend\"}', "
383
+ '\'{resource.service.name="frontend" && name = "POST /api/orders" && status = error}\', '
384
+ '\'{resource.service.name="frontend" && name = "POST /api/orders" && span.http.status_code >= 500}\', '
385
+ "'{} | count() > 10'"
386
+ ),
552
387
  type="string",
553
388
  required=True,
554
389
  ),
@@ -558,13 +393,15 @@ class SearchTracesByQuery(Tool):
558
393
  required=False,
559
394
  ),
560
395
  "start": ToolParameter(
561
- description="Start time in Unix epoch seconds",
562
- type="integer",
396
+ description=standard_start_datetime_tool_param_description(
397
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS
398
+ ),
399
+ type="string",
563
400
  required=False,
564
401
  ),
565
402
  "end": ToolParameter(
566
- description="End time in Unix epoch seconds",
567
- type="integer",
403
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
404
+ type="string",
568
405
  required=False,
569
406
  ),
570
407
  "spss": ToolParameter(
@@ -581,22 +418,24 @@ class SearchTracesByQuery(Tool):
581
418
  ) -> StructuredToolResult:
582
419
  api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
583
420
 
421
+ start, end = BaseGrafanaTempoToolset.adjust_start_end_time(params)
422
+
584
423
  try:
585
424
  result = api.search_traces_by_query(
586
425
  q=params["q"],
587
426
  limit=params.get("limit"),
588
- start=params.get("start"),
589
- end=params.get("end"),
427
+ start=start,
428
+ end=end,
590
429
  spss=params.get("spss"),
591
430
  )
592
431
  return StructuredToolResult(
593
- status=ToolResultStatus.SUCCESS,
432
+ status=StructuredToolResultStatus.SUCCESS,
594
433
  data=yaml.dump(result, default_flow_style=False),
595
434
  params=params,
596
435
  )
597
436
  except Exception as e:
598
437
  return StructuredToolResult(
599
- status=ToolResultStatus.ERROR,
438
+ status=StructuredToolResultStatus.ERROR,
600
439
  error=str(e),
601
440
  params=params,
602
441
  )
@@ -608,15 +447,15 @@ class SearchTracesByQuery(Tool):
608
447
  class SearchTracesByTags(Tool):
609
448
  def __init__(self, toolset: BaseGrafanaTempoToolset):
610
449
  super().__init__(
611
- name="search_traces_by_tags",
450
+ name="tempo_search_traces_by_tags",
612
451
  description=(
613
452
  "Search for traces using logfmt-encoded tags. "
614
453
  "Uses the Tempo API endpoint: GET /api/search with 'tags' parameter. "
615
- 'Example: resource.service.name="api" http.status_code="500"'
454
+ 'Example: service.name="api" http.status_code="500"'
616
455
  ),
617
456
  parameters={
618
457
  "tags": ToolParameter(
619
- description='Logfmt-encoded span/process attributes (e.g., \'resource.service.name="api" http.status_code="500"\')',
458
+ description='Logfmt-encoded span/process attributes (e.g., \'service.name="api" http.status_code="500"\')',
620
459
  type="string",
621
460
  required=True,
622
461
  ),
@@ -636,13 +475,15 @@ class SearchTracesByTags(Tool):
636
475
  required=False,
637
476
  ),
638
477
  "start": ToolParameter(
639
- description="Start time in Unix epoch seconds",
640
- type="integer",
478
+ description=standard_start_datetime_tool_param_description(
479
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS
480
+ ),
481
+ type="string",
641
482
  required=False,
642
483
  ),
643
484
  "end": ToolParameter(
644
- description="End time in Unix epoch seconds",
645
- type="integer",
485
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
486
+ type="string",
646
487
  required=False,
647
488
  ),
648
489
  "spss": ToolParameter(
@@ -659,24 +500,26 @@ class SearchTracesByTags(Tool):
659
500
  ) -> StructuredToolResult:
660
501
  api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
661
502
 
503
+ start, end = BaseGrafanaTempoToolset.adjust_start_end_time(params)
504
+
662
505
  try:
663
506
  result = api.search_traces_by_tags(
664
507
  tags=params["tags"],
665
508
  min_duration=params.get("min_duration"),
666
509
  max_duration=params.get("max_duration"),
667
510
  limit=params.get("limit"),
668
- start=params.get("start"),
669
- end=params.get("end"),
511
+ start=start,
512
+ end=end,
670
513
  spss=params.get("spss"),
671
514
  )
672
515
  return StructuredToolResult(
673
- status=ToolResultStatus.SUCCESS,
516
+ status=StructuredToolResultStatus.SUCCESS,
674
517
  data=yaml.dump(result, default_flow_style=False),
675
518
  params=params,
676
519
  )
677
520
  except Exception as e:
678
521
  return StructuredToolResult(
679
- status=ToolResultStatus.ERROR,
522
+ status=StructuredToolResultStatus.ERROR,
680
523
  error=str(e),
681
524
  params=params,
682
525
  )
@@ -688,7 +531,7 @@ class SearchTracesByTags(Tool):
688
531
  class QueryTraceById(Tool):
689
532
  def __init__(self, toolset: BaseGrafanaTempoToolset):
690
533
  super().__init__(
691
- name="query_trace_by_id",
534
+ name="tempo_query_trace_by_id",
692
535
  description=(
693
536
  "Retrieve detailed trace information by trace ID. "
694
537
  "Uses the Tempo API endpoint: GET /api/v2/traces/{trace_id}. "
@@ -701,13 +544,15 @@ class QueryTraceById(Tool):
701
544
  required=True,
702
545
  ),
703
546
  "start": ToolParameter(
704
- description="Optional start time in Unix epoch seconds",
705
- type="integer",
547
+ description=standard_start_datetime_tool_param_description(
548
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS
549
+ ),
550
+ type="string",
706
551
  required=False,
707
552
  ),
708
553
  "end": ToolParameter(
709
- description="Optional end time in Unix epoch seconds",
710
- type="integer",
554
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
555
+ type="string",
711
556
  required=False,
712
557
  ),
713
558
  },
@@ -719,22 +564,24 @@ class QueryTraceById(Tool):
719
564
  ) -> StructuredToolResult:
720
565
  api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
721
566
 
567
+ start, end = BaseGrafanaTempoToolset.adjust_start_end_time(params)
568
+
722
569
  try:
723
570
  trace_data = api.query_trace_by_id_v2(
724
571
  trace_id=params["trace_id"],
725
- start=params.get("start"),
726
- end=params.get("end"),
572
+ start=start,
573
+ end=end,
727
574
  )
728
575
 
729
576
  # Return raw trace data as YAML for readability
730
577
  return StructuredToolResult(
731
- status=ToolResultStatus.SUCCESS,
578
+ status=StructuredToolResultStatus.SUCCESS,
732
579
  data=yaml.dump(trace_data, default_flow_style=False),
733
580
  params=params,
734
581
  )
735
582
  except Exception as e:
736
583
  return StructuredToolResult(
737
- status=ToolResultStatus.ERROR,
584
+ status=StructuredToolResultStatus.ERROR,
738
585
  error=str(e),
739
586
  params=params,
740
587
  )
@@ -746,7 +593,7 @@ class QueryTraceById(Tool):
746
593
  class SearchTagNames(Tool):
747
594
  def __init__(self, toolset: BaseGrafanaTempoToolset):
748
595
  super().__init__(
749
- name="search_tag_names",
596
+ name="tempo_search_tag_names",
750
597
  description=(
751
598
  "Discover available tag names across traces. "
752
599
  "Uses the Tempo API endpoint: GET /api/v2/search/tags. "
@@ -764,13 +611,15 @@ class SearchTagNames(Tool):
764
611
  required=False,
765
612
  ),
766
613
  "start": ToolParameter(
767
- description="Start time in Unix epoch seconds",
768
- type="integer",
614
+ description=standard_start_datetime_tool_param_description(
615
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS
616
+ ),
617
+ type="string",
769
618
  required=False,
770
619
  ),
771
620
  "end": ToolParameter(
772
- description="End time in Unix epoch seconds",
773
- type="integer",
621
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
622
+ type="string",
774
623
  required=False,
775
624
  ),
776
625
  "limit": ToolParameter(
@@ -792,23 +641,25 @@ class SearchTagNames(Tool):
792
641
  ) -> StructuredToolResult:
793
642
  api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
794
643
 
644
+ start, end = BaseGrafanaTempoToolset.adjust_start_end_time(params)
645
+
795
646
  try:
796
647
  result = api.search_tag_names_v2(
797
648
  scope=params.get("scope"),
798
649
  q=params.get("q"),
799
- start=params.get("start"),
800
- end=params.get("end"),
650
+ start=start,
651
+ end=end,
801
652
  limit=params.get("limit"),
802
653
  max_stale_values=params.get("max_stale_values"),
803
654
  )
804
655
  return StructuredToolResult(
805
- status=ToolResultStatus.SUCCESS,
656
+ status=StructuredToolResultStatus.SUCCESS,
806
657
  data=yaml.dump(result, default_flow_style=False),
807
658
  params=params,
808
659
  )
809
660
  except Exception as e:
810
661
  return StructuredToolResult(
811
- status=ToolResultStatus.ERROR,
662
+ status=StructuredToolResultStatus.ERROR,
812
663
  error=str(e),
813
664
  params=params,
814
665
  )
@@ -820,7 +671,7 @@ class SearchTagNames(Tool):
820
671
  class SearchTagValues(Tool):
821
672
  def __init__(self, toolset: BaseGrafanaTempoToolset):
822
673
  super().__init__(
823
- name="search_tag_values",
674
+ name="tempo_search_tag_values",
824
675
  description=(
825
676
  "Get all values for a specific tag. "
826
677
  "Uses the Tempo API endpoint: GET /api/v2/search/tag/{tag}/values. "
@@ -838,13 +689,15 @@ class SearchTagValues(Tool):
838
689
  required=False,
839
690
  ),
840
691
  "start": ToolParameter(
841
- description="Start time in Unix epoch seconds",
842
- type="integer",
692
+ description=standard_start_datetime_tool_param_description(
693
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS
694
+ ),
695
+ type="string",
843
696
  required=False,
844
697
  ),
845
698
  "end": ToolParameter(
846
- description="End time in Unix epoch seconds",
847
- type="integer",
699
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
700
+ type="string",
848
701
  required=False,
849
702
  ),
850
703
  "limit": ToolParameter(
@@ -866,23 +719,25 @@ class SearchTagValues(Tool):
866
719
  ) -> StructuredToolResult:
867
720
  api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
868
721
 
722
+ start, end = BaseGrafanaTempoToolset.adjust_start_end_time(params)
723
+
869
724
  try:
870
725
  result = api.search_tag_values_v2(
871
726
  tag=params["tag"],
872
727
  q=params.get("q"),
873
- start=params.get("start"),
874
- end=params.get("end"),
728
+ start=start,
729
+ end=end,
875
730
  limit=params.get("limit"),
876
731
  max_stale_values=params.get("max_stale_values"),
877
732
  )
878
733
  return StructuredToolResult(
879
- status=ToolResultStatus.SUCCESS,
734
+ status=StructuredToolResultStatus.SUCCESS,
880
735
  data=yaml.dump(result, default_flow_style=False),
881
736
  params=params,
882
737
  )
883
738
  except Exception as e:
884
739
  return StructuredToolResult(
885
- status=ToolResultStatus.ERROR,
740
+ status=StructuredToolResultStatus.ERROR,
886
741
  error=str(e),
887
742
  params=params,
888
743
  )
@@ -894,7 +749,7 @@ class SearchTagValues(Tool):
894
749
  class QueryMetricsInstant(Tool):
895
750
  def __init__(self, toolset: BaseGrafanaTempoToolset):
896
751
  super().__init__(
897
- name="query_metrics_instant",
752
+ name="tempo_query_metrics_instant",
898
753
  description=(
899
754
  "Compute a single TraceQL metric value across time range. "
900
755
  "Uses the Tempo API endpoint: GET /api/metrics/query. "
@@ -924,17 +779,14 @@ class QueryMetricsInstant(Tool):
924
779
  required=True,
925
780
  ),
926
781
  "start": ToolParameter(
927
- description="Start time (Unix seconds/nanoseconds/RFC3339)",
782
+ description=standard_start_datetime_tool_param_description(
783
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS
784
+ ),
928
785
  type="string",
929
786
  required=False,
930
787
  ),
931
788
  "end": ToolParameter(
932
- description="End time (Unix seconds/nanoseconds/RFC3339)",
933
- type="string",
934
- required=False,
935
- ),
936
- "since": ToolParameter(
937
- description="Duration string (e.g., '1h', '30m')",
789
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
938
790
  type="string",
939
791
  required=False,
940
792
  ),
@@ -947,21 +799,22 @@ class QueryMetricsInstant(Tool):
947
799
  ) -> StructuredToolResult:
948
800
  api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
949
801
 
802
+ start, end = BaseGrafanaTempoToolset.adjust_start_end_time(params)
803
+
950
804
  try:
951
805
  result = api.query_metrics_instant(
952
806
  q=params["q"],
953
- start=params.get("start"),
954
- end=params.get("end"),
955
- since=params.get("since"),
807
+ start=start,
808
+ end=end,
956
809
  )
957
810
  return StructuredToolResult(
958
- status=ToolResultStatus.SUCCESS,
811
+ status=StructuredToolResultStatus.SUCCESS,
959
812
  data=yaml.dump(result, default_flow_style=False),
960
813
  params=params,
961
814
  )
962
815
  except Exception as e:
963
816
  return StructuredToolResult(
964
- status=ToolResultStatus.ERROR,
817
+ status=StructuredToolResultStatus.ERROR,
965
818
  error=str(e),
966
819
  params=params,
967
820
  )
@@ -975,7 +828,7 @@ class QueryMetricsInstant(Tool):
975
828
  class QueryMetricsRange(Tool):
976
829
  def __init__(self, toolset: BaseGrafanaTempoToolset):
977
830
  super().__init__(
978
- name="query_metrics_range",
831
+ name="tempo_query_metrics_range",
979
832
  description=(
980
833
  "Get time series data from TraceQL metrics queries. "
981
834
  "Uses the Tempo API endpoint: GET /api/metrics/query_range. "
@@ -1007,17 +860,14 @@ class QueryMetricsRange(Tool):
1007
860
  required=False,
1008
861
  ),
1009
862
  "start": ToolParameter(
1010
- description="Start time (Unix seconds/nanoseconds/RFC3339)",
863
+ description=standard_start_datetime_tool_param_description(
864
+ DEFAULT_GRAPH_TIME_SPAN_SECONDS
865
+ ),
1011
866
  type="string",
1012
867
  required=False,
1013
868
  ),
1014
869
  "end": ToolParameter(
1015
- description="End time (Unix seconds/nanoseconds/RFC3339)",
1016
- type="string",
1017
- required=False,
1018
- ),
1019
- "since": ToolParameter(
1020
- description="Duration string (e.g., '3h', '1d')",
870
+ description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
1021
871
  type="string",
1022
872
  required=False,
1023
873
  ),
@@ -1035,23 +885,34 @@ class QueryMetricsRange(Tool):
1035
885
  ) -> StructuredToolResult:
1036
886
  api = GrafanaTempoAPI(self._toolset.grafana_config, use_post=TEMPO_API_USE_POST)
1037
887
 
888
+ start, end = BaseGrafanaTempoToolset.adjust_start_end_time(params)
889
+
890
+ # Calculate appropriate step
891
+ step_param = params.get("step")
892
+ step_seconds = duration_string_to_seconds(step_param) if step_param else None
893
+ adjusted_step = adjust_step_for_max_points(
894
+ end - start,
895
+ int(MAX_GRAPH_POINTS),
896
+ step_seconds,
897
+ )
898
+ step = seconds_to_duration_string(adjusted_step)
899
+
1038
900
  try:
1039
901
  result = api.query_metrics_range(
1040
902
  q=params["q"],
1041
- step=params.get("step"),
1042
- start=params.get("start"),
1043
- end=params.get("end"),
1044
- since=params.get("since"),
903
+ step=step,
904
+ start=start,
905
+ end=end,
1045
906
  exemplars=params.get("exemplars"),
1046
907
  )
1047
908
  return StructuredToolResult(
1048
- status=ToolResultStatus.SUCCESS,
909
+ status=StructuredToolResultStatus.SUCCESS,
1049
910
  data=yaml.dump(result, default_flow_style=False),
1050
911
  params=params,
1051
912
  )
1052
913
  except Exception as e:
1053
914
  return StructuredToolResult(
1054
- status=ToolResultStatus.ERROR,
915
+ status=StructuredToolResultStatus.ERROR,
1055
916
  error=str(e),
1056
917
  params=params,
1057
918
  )
@@ -1069,9 +930,6 @@ class GrafanaTempoToolset(BaseGrafanaTempoToolset):
1069
930
  docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/grafanatempo/",
1070
931
  tools=[
1071
932
  FetchTracesSimpleComparison(self),
1072
- # GetTempoTraces(self),
1073
- # GetTempoTraceById(self),
1074
- # GetTempoTags(self),
1075
933
  SearchTracesByQuery(self),
1076
934
  SearchTracesByTags(self),
1077
935
  QueryTraceById(self),