holmesgpt 0.11.5__py3-none-any.whl → 0.12.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of holmesgpt might be problematic. Click here for more details.
- holmes/__init__.py +1 -1
- holmes/common/env_vars.py +8 -4
- holmes/config.py +52 -13
- holmes/core/investigation_structured_output.py +7 -0
- holmes/core/llm.py +14 -4
- holmes/core/models.py +24 -0
- holmes/core/tool_calling_llm.py +48 -6
- holmes/core/tools.py +7 -4
- holmes/core/toolset_manager.py +24 -5
- holmes/core/tracing.py +224 -0
- holmes/interactive.py +761 -44
- holmes/main.py +59 -127
- holmes/plugins/prompts/_fetch_logs.jinja2 +4 -0
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +2 -10
- holmes/plugins/toolsets/__init__.py +10 -2
- holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +2 -1
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +3 -0
- holmes/plugins/toolsets/datadog/datadog_api.py +161 -0
- holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +26 -0
- holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +310 -0
- holmes/plugins/toolsets/datadog/instructions_datadog_traces.jinja2 +51 -0
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +267 -0
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +488 -0
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +689 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +3 -0
- holmes/plugins/toolsets/internet/internet.py +1 -1
- holmes/plugins/toolsets/logging_utils/logging_api.py +9 -3
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +3 -0
- holmes/plugins/toolsets/utils.py +6 -2
- holmes/utils/cache.py +4 -4
- holmes/utils/console/consts.py +2 -0
- holmes/utils/console/logging.py +95 -0
- holmes/utils/console/result.py +37 -0
- {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0.dist-info}/METADATA +3 -4
- {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0.dist-info}/RECORD +38 -29
- {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0.dist-info}/WHEEL +1 -1
- holmes/__init__.py.bak +0 -76
- holmes/plugins/toolsets/datadog.py +0 -153
- {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0.dist-info}/LICENSE.txt +0 -0
- {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Optional, Dict, Tuple
|
|
5
|
+
from holmes.core.tools import (
|
|
6
|
+
CallablePrerequisite,
|
|
7
|
+
StructuredToolResult,
|
|
8
|
+
Tool,
|
|
9
|
+
ToolParameter,
|
|
10
|
+
ToolResultStatus,
|
|
11
|
+
Toolset,
|
|
12
|
+
ToolsetTag,
|
|
13
|
+
)
|
|
14
|
+
from holmes.plugins.toolsets.consts import (
|
|
15
|
+
TOOLSET_CONFIG_MISSING_ERROR,
|
|
16
|
+
STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
|
|
17
|
+
)
|
|
18
|
+
from holmes.plugins.toolsets.datadog.datadog_api import (
|
|
19
|
+
DatadogBaseConfig,
|
|
20
|
+
DataDogRequestError,
|
|
21
|
+
execute_datadog_http_request,
|
|
22
|
+
get_headers,
|
|
23
|
+
MAX_RETRY_COUNT_ON_RATE_LIMIT,
|
|
24
|
+
)
|
|
25
|
+
from holmes.plugins.toolsets.utils import (
|
|
26
|
+
get_param_or_raise,
|
|
27
|
+
process_timestamps_to_int,
|
|
28
|
+
standard_start_datetime_tool_param_description,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
DEFAULT_TIME_SPAN_SECONDS = 3600
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DatadogMetricsConfig(DatadogBaseConfig):
|
|
35
|
+
default_limit: int = 1000
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BaseDatadogMetricsTool(Tool):
|
|
39
|
+
toolset: "DatadogMetricsToolset"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
ACTIVE_METRICS_DEFAULT_LOOK_BACK_HOURS = 24
|
|
43
|
+
ACTIVE_METRICS_DEFAULT_TIME_SPAN_SECONDS = 24 * 60 * 60
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ListActiveMetrics(BaseDatadogMetricsTool):
|
|
47
|
+
def __init__(self, toolset: "DatadogMetricsToolset"):
|
|
48
|
+
super().__init__(
|
|
49
|
+
name="list_active_datadog_metrics",
|
|
50
|
+
description=f"List active metrics from the last {ACTIVE_METRICS_DEFAULT_LOOK_BACK_HOURS} hours. This includes metrics that have actively reported data points.",
|
|
51
|
+
parameters={
|
|
52
|
+
"from_time": ToolParameter(
|
|
53
|
+
description=f"Start time for listing metrics. Can be an RFC3339 formatted datetime (e.g. '2023-03-01T10:30:00Z') or a negative integer for relative seconds from now (e.g. -86400 for 24 hours ago). Defaults to {ACTIVE_METRICS_DEFAULT_LOOK_BACK_HOURS} hours ago",
|
|
54
|
+
type="string",
|
|
55
|
+
required=False,
|
|
56
|
+
),
|
|
57
|
+
"host": ToolParameter(
|
|
58
|
+
description="Filter metrics by hostname",
|
|
59
|
+
type="string",
|
|
60
|
+
required=False,
|
|
61
|
+
),
|
|
62
|
+
"tag_filter": ToolParameter(
|
|
63
|
+
description="Filter metrics by tags in the format tag:value",
|
|
64
|
+
type="string",
|
|
65
|
+
required=False,
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
toolset=toolset,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def _invoke(self, params: Any) -> StructuredToolResult:
|
|
72
|
+
if not self.toolset.dd_config:
|
|
73
|
+
return StructuredToolResult(
|
|
74
|
+
status=ToolResultStatus.ERROR,
|
|
75
|
+
error=TOOLSET_CONFIG_MISSING_ERROR,
|
|
76
|
+
params=params,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
url = None
|
|
80
|
+
query_params = None
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
from_time_str = params.get("from_time")
|
|
84
|
+
|
|
85
|
+
from_time, _ = process_timestamps_to_int(
|
|
86
|
+
start=from_time_str,
|
|
87
|
+
end=None,
|
|
88
|
+
default_time_span_seconds=ACTIVE_METRICS_DEFAULT_TIME_SPAN_SECONDS,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
url = f"{self.toolset.dd_config.site_api_url}/api/v1/metrics"
|
|
92
|
+
headers = get_headers(self.toolset.dd_config)
|
|
93
|
+
|
|
94
|
+
query_params = {
|
|
95
|
+
"from": from_time,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if params.get("host"):
|
|
99
|
+
query_params["host"] = params["host"]
|
|
100
|
+
|
|
101
|
+
if params.get("tag_filter"):
|
|
102
|
+
query_params["tag_filter"] = params["tag_filter"]
|
|
103
|
+
|
|
104
|
+
data = execute_datadog_http_request(
|
|
105
|
+
url=url,
|
|
106
|
+
headers=headers,
|
|
107
|
+
timeout=self.toolset.dd_config.request_timeout,
|
|
108
|
+
method="GET",
|
|
109
|
+
payload_or_params=query_params,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
metrics = data.get("metrics", [])
|
|
113
|
+
|
|
114
|
+
output = ["Metric Name"]
|
|
115
|
+
output.append("-" * 50)
|
|
116
|
+
|
|
117
|
+
for metric in sorted(metrics):
|
|
118
|
+
output.append(metric)
|
|
119
|
+
|
|
120
|
+
return StructuredToolResult(
|
|
121
|
+
status=ToolResultStatus.SUCCESS,
|
|
122
|
+
data="\n".join(output),
|
|
123
|
+
params=params,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
except DataDogRequestError as e:
|
|
127
|
+
logging.exception(e, exc_info=True)
|
|
128
|
+
|
|
129
|
+
if e.status_code == 429:
|
|
130
|
+
error_msg = f"Datadog API rate limit exceeded. Failed after {MAX_RETRY_COUNT_ON_RATE_LIMIT} retry attempts."
|
|
131
|
+
elif e.status_code == 403:
|
|
132
|
+
error_msg = (
|
|
133
|
+
f"Permission denied. Ensure your Datadog Application Key has the 'metrics_read' "
|
|
134
|
+
f"and 'timeseries_query' permissions. Error: {str(e)}"
|
|
135
|
+
)
|
|
136
|
+
else:
|
|
137
|
+
error_msg = f"Exception while querying Datadog: {str(e)}"
|
|
138
|
+
|
|
139
|
+
return StructuredToolResult(
|
|
140
|
+
status=ToolResultStatus.ERROR,
|
|
141
|
+
error=error_msg,
|
|
142
|
+
params=params,
|
|
143
|
+
invocation=json.dumps({"url": url, "params": query_params})
|
|
144
|
+
if url and query_params
|
|
145
|
+
else None,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logging.exception(
|
|
150
|
+
f"Failed to query Datadog metrics for params: {params}", exc_info=True
|
|
151
|
+
)
|
|
152
|
+
return StructuredToolResult(
|
|
153
|
+
status=ToolResultStatus.ERROR,
|
|
154
|
+
error=f"Exception while querying Datadog: {str(e)}",
|
|
155
|
+
params=params,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def get_parameterized_one_liner(self, params) -> str:
|
|
159
|
+
filters = []
|
|
160
|
+
if params.get("host"):
|
|
161
|
+
filters.append(f"host={params['host']}")
|
|
162
|
+
if params.get("tag_filter"):
|
|
163
|
+
filters.append(f"tag_filter={params['tag_filter']}")
|
|
164
|
+
filter_str = f" with filters: {', '.join(filters)}" if filters else ""
|
|
165
|
+
return f"List active Datadog metrics{filter_str}"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class QueryMetrics(BaseDatadogMetricsTool):
|
|
169
|
+
def __init__(self, toolset: "DatadogMetricsToolset"):
|
|
170
|
+
super().__init__(
|
|
171
|
+
name="query_datadog_metrics",
|
|
172
|
+
description="Query timeseries data for a specific metric",
|
|
173
|
+
parameters={
|
|
174
|
+
"query": ToolParameter(
|
|
175
|
+
description="The metric query string (e.g., 'system.cpu.user{host:myhost}')",
|
|
176
|
+
type="string",
|
|
177
|
+
required=True,
|
|
178
|
+
),
|
|
179
|
+
"from_time": ToolParameter(
|
|
180
|
+
description=standard_start_datetime_tool_param_description(
|
|
181
|
+
DEFAULT_TIME_SPAN_SECONDS
|
|
182
|
+
),
|
|
183
|
+
type="string",
|
|
184
|
+
required=False,
|
|
185
|
+
),
|
|
186
|
+
"to_time": ToolParameter(
|
|
187
|
+
description=STANDARD_END_DATETIME_TOOL_PARAM_DESCRIPTION,
|
|
188
|
+
type="string",
|
|
189
|
+
required=False,
|
|
190
|
+
),
|
|
191
|
+
},
|
|
192
|
+
toolset=toolset,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _invoke(self, params: Any) -> StructuredToolResult:
|
|
196
|
+
if not self.toolset.dd_config:
|
|
197
|
+
return StructuredToolResult(
|
|
198
|
+
status=ToolResultStatus.ERROR,
|
|
199
|
+
error=TOOLSET_CONFIG_MISSING_ERROR,
|
|
200
|
+
params=params,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
url = None
|
|
204
|
+
query_params = None
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
query = get_param_or_raise(params, "query")
|
|
208
|
+
|
|
209
|
+
(from_time, to_time) = process_timestamps_to_int(
|
|
210
|
+
start=params.get("from_time"),
|
|
211
|
+
end=params.get("to_time"),
|
|
212
|
+
default_time_span_seconds=DEFAULT_TIME_SPAN_SECONDS,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
url = f"{self.toolset.dd_config.site_api_url}/api/v1/query"
|
|
216
|
+
headers = get_headers(self.toolset.dd_config)
|
|
217
|
+
|
|
218
|
+
query_params = {
|
|
219
|
+
"query": query,
|
|
220
|
+
"from": from_time,
|
|
221
|
+
"to": to_time,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
data = execute_datadog_http_request(
|
|
225
|
+
url=url,
|
|
226
|
+
headers=headers,
|
|
227
|
+
timeout=self.toolset.dd_config.request_timeout,
|
|
228
|
+
method="GET",
|
|
229
|
+
payload_or_params=query_params,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
series = data.get("series", [])
|
|
233
|
+
|
|
234
|
+
if not series:
|
|
235
|
+
return StructuredToolResult(
|
|
236
|
+
status=ToolResultStatus.NO_DATA,
|
|
237
|
+
error="The query returned no data. Please check your query syntax and time range.",
|
|
238
|
+
params=params,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
response_data = {
|
|
242
|
+
"status": "success",
|
|
243
|
+
"query": query,
|
|
244
|
+
"from_time": from_time,
|
|
245
|
+
"to_time": to_time,
|
|
246
|
+
"series": series,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return StructuredToolResult(
|
|
250
|
+
status=ToolResultStatus.SUCCESS,
|
|
251
|
+
data=json.dumps(response_data, indent=2),
|
|
252
|
+
params=params,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
except DataDogRequestError as e:
|
|
256
|
+
logging.exception(e, exc_info=True)
|
|
257
|
+
|
|
258
|
+
if e.status_code == 429:
|
|
259
|
+
error_msg = f"Datadog API rate limit exceeded. Failed after {MAX_RETRY_COUNT_ON_RATE_LIMIT} retry attempts."
|
|
260
|
+
elif e.status_code == 403:
|
|
261
|
+
error_msg = (
|
|
262
|
+
f"Permission denied. Ensure your Datadog Application Key has the 'metrics_read' "
|
|
263
|
+
f"and 'timeseries_query' permissions. Error: {str(e)}"
|
|
264
|
+
)
|
|
265
|
+
else:
|
|
266
|
+
error_msg = f"Exception while querying Datadog: {str(e)}"
|
|
267
|
+
|
|
268
|
+
return StructuredToolResult(
|
|
269
|
+
status=ToolResultStatus.ERROR,
|
|
270
|
+
error=error_msg,
|
|
271
|
+
params=params,
|
|
272
|
+
invocation=json.dumps({"url": url, "params": query_params})
|
|
273
|
+
if url and query_params
|
|
274
|
+
else None,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logging.exception(
|
|
279
|
+
f"Failed to query Datadog metrics for params: {params}", exc_info=True
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return StructuredToolResult(
|
|
283
|
+
status=ToolResultStatus.ERROR,
|
|
284
|
+
error=f"Exception while querying Datadog: {str(e)}",
|
|
285
|
+
params=params,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def get_parameterized_one_liner(self, params) -> str:
|
|
289
|
+
query = params.get("query", "<no query>")
|
|
290
|
+
return f"Query Datadog metrics: {query}"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class QueryMetricsMetadata(BaseDatadogMetricsTool):
|
|
294
|
+
def __init__(self, toolset: "DatadogMetricsToolset"):
|
|
295
|
+
super().__init__(
|
|
296
|
+
name="get_datadog_metric_metadata",
|
|
297
|
+
description="Get metadata about one or more metrics including their type, description, unit, and other properties",
|
|
298
|
+
parameters={
|
|
299
|
+
"metric_names": ToolParameter(
|
|
300
|
+
description="Comma-separated list of metric names to get metadata for (e.g., 'system.cpu.user, system.mem.used')",
|
|
301
|
+
type="string",
|
|
302
|
+
required=True,
|
|
303
|
+
),
|
|
304
|
+
},
|
|
305
|
+
toolset=toolset,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def _invoke(self, params: Any) -> StructuredToolResult:
|
|
309
|
+
if not self.toolset.dd_config:
|
|
310
|
+
return StructuredToolResult(
|
|
311
|
+
status=ToolResultStatus.ERROR,
|
|
312
|
+
error=TOOLSET_CONFIG_MISSING_ERROR,
|
|
313
|
+
params=params,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
try:
|
|
317
|
+
metric_names_str = get_param_or_raise(params, "metric_names")
|
|
318
|
+
|
|
319
|
+
metric_names = [
|
|
320
|
+
name.strip()
|
|
321
|
+
for name in metric_names_str.split(",")
|
|
322
|
+
if name.strip() # Filter out empty strings
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
if not metric_names:
|
|
326
|
+
return StructuredToolResult(
|
|
327
|
+
status=ToolResultStatus.ERROR,
|
|
328
|
+
error="metric_names cannot be empty",
|
|
329
|
+
params=params,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
headers = get_headers(self.toolset.dd_config)
|
|
333
|
+
|
|
334
|
+
results = {}
|
|
335
|
+
errors = {}
|
|
336
|
+
|
|
337
|
+
for metric_name in metric_names:
|
|
338
|
+
try:
|
|
339
|
+
url = f"{self.toolset.dd_config.site_api_url}/api/v1/metrics/{metric_name}"
|
|
340
|
+
|
|
341
|
+
data = execute_datadog_http_request(
|
|
342
|
+
url=url,
|
|
343
|
+
headers=headers,
|
|
344
|
+
payload_or_params={},
|
|
345
|
+
timeout=self.toolset.dd_config.request_timeout,
|
|
346
|
+
method="GET",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
results[metric_name] = data
|
|
350
|
+
|
|
351
|
+
except DataDogRequestError as e:
|
|
352
|
+
if e.status_code == 404:
|
|
353
|
+
errors[metric_name] = "Metric not found"
|
|
354
|
+
elif e.status_code == 429:
|
|
355
|
+
errors[metric_name] = (
|
|
356
|
+
f"Datadog API rate limit exceeded. Failed after {MAX_RETRY_COUNT_ON_RATE_LIMIT} retry attempts."
|
|
357
|
+
)
|
|
358
|
+
else:
|
|
359
|
+
errors[metric_name] = f"Error {e.status_code}: {str(e)}"
|
|
360
|
+
except Exception as e:
|
|
361
|
+
errors[metric_name] = f"Exception: {str(e)}"
|
|
362
|
+
|
|
363
|
+
response_data = {
|
|
364
|
+
"metrics_metadata": results,
|
|
365
|
+
"errors": errors,
|
|
366
|
+
"total_requested": len(metric_names),
|
|
367
|
+
"successful": len(results),
|
|
368
|
+
"failed": len(errors),
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if not results and errors:
|
|
372
|
+
return StructuredToolResult(
|
|
373
|
+
status=ToolResultStatus.ERROR,
|
|
374
|
+
error="Failed to retrieve metadata for all metrics",
|
|
375
|
+
data=json.dumps(response_data, indent=2),
|
|
376
|
+
params=params,
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
return StructuredToolResult(
|
|
380
|
+
status=ToolResultStatus.SUCCESS,
|
|
381
|
+
data=json.dumps(response_data, indent=2),
|
|
382
|
+
params=params,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
except Exception as e:
|
|
386
|
+
logging.exception(
|
|
387
|
+
f"Failed to query Datadog metric metadata for params: {params}",
|
|
388
|
+
exc_info=True,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return StructuredToolResult(
|
|
392
|
+
status=ToolResultStatus.ERROR,
|
|
393
|
+
error=f"Exception while querying Datadog: {str(e)}",
|
|
394
|
+
params=params,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
def get_parameterized_one_liner(self, params) -> str:
|
|
398
|
+
metric_names = params.get("metric_names", [])
|
|
399
|
+
if isinstance(metric_names, list):
|
|
400
|
+
if len(metric_names) == 1:
|
|
401
|
+
return f"Get Datadog metric metadata for: {metric_names[0]}"
|
|
402
|
+
elif len(metric_names) > 1:
|
|
403
|
+
return f"Get Datadog metric metadata for {len(metric_names)} metrics"
|
|
404
|
+
return "Get Datadog metric metadata"
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class DatadogMetricsToolset(Toolset):
|
|
408
|
+
dd_config: Optional[DatadogMetricsConfig] = None
|
|
409
|
+
|
|
410
|
+
def __init__(self):
|
|
411
|
+
super().__init__(
|
|
412
|
+
name="datadog/metrics",
|
|
413
|
+
description="Toolset for interacting with Datadog to fetch metrics and metadata",
|
|
414
|
+
docs_url="https://docs.datadoghq.com/api/latest/metrics/",
|
|
415
|
+
icon_url="https://imgix.datadoghq.com//img/about/presskit/DDlogo.jpg",
|
|
416
|
+
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
417
|
+
tools=[
|
|
418
|
+
ListActiveMetrics(toolset=self),
|
|
419
|
+
QueryMetrics(toolset=self),
|
|
420
|
+
QueryMetricsMetadata(toolset=self),
|
|
421
|
+
],
|
|
422
|
+
experimental=True,
|
|
423
|
+
tags=[ToolsetTag.CORE],
|
|
424
|
+
)
|
|
425
|
+
self._reload_instructions()
|
|
426
|
+
|
|
427
|
+
def _perform_healthcheck(self, dd_config: DatadogMetricsConfig) -> Tuple[bool, str]:
|
|
428
|
+
try:
|
|
429
|
+
logging.info("Performing Datadog metrics configuration healthcheck...")
|
|
430
|
+
|
|
431
|
+
url = f"{dd_config.site_api_url}/api/v1/validate"
|
|
432
|
+
headers = get_headers(dd_config)
|
|
433
|
+
|
|
434
|
+
data = execute_datadog_http_request(
|
|
435
|
+
url=url,
|
|
436
|
+
headers=headers,
|
|
437
|
+
payload_or_params={},
|
|
438
|
+
timeout=dd_config.request_timeout,
|
|
439
|
+
method="GET",
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
if data.get("valid", False):
|
|
443
|
+
logging.info("Datadog metrics healthcheck completed successfully")
|
|
444
|
+
return True, ""
|
|
445
|
+
else:
|
|
446
|
+
error_msg = "Datadog API key validation failed"
|
|
447
|
+
logging.error(f"Datadog metrics healthcheck failed: {error_msg}")
|
|
448
|
+
return False, f"Datadog metrics healthcheck failed: {error_msg}"
|
|
449
|
+
|
|
450
|
+
except Exception as e:
|
|
451
|
+
logging.exception("Failed during Datadog metrics healthcheck")
|
|
452
|
+
return False, f"Healthcheck failed with exception: {str(e)}"
|
|
453
|
+
|
|
454
|
+
def prerequisites_callable(self, config: dict[str, Any]) -> Tuple[bool, str]:
|
|
455
|
+
if not config:
|
|
456
|
+
return (
|
|
457
|
+
False,
|
|
458
|
+
TOOLSET_CONFIG_MISSING_ERROR,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
try:
|
|
462
|
+
dd_config = DatadogMetricsConfig(**config)
|
|
463
|
+
self.dd_config = dd_config
|
|
464
|
+
|
|
465
|
+
success, error_msg = self._perform_healthcheck(dd_config)
|
|
466
|
+
return success, error_msg
|
|
467
|
+
|
|
468
|
+
except Exception as e:
|
|
469
|
+
logging.exception("Failed to set up Datadog metrics toolset")
|
|
470
|
+
return (False, f"Failed to parse Datadog configuration: {str(e)}")
|
|
471
|
+
|
|
472
|
+
def get_example_config(self) -> Dict[str, Any]:
|
|
473
|
+
return {
|
|
474
|
+
"dd_api_key": "your-datadog-api-key",
|
|
475
|
+
"dd_app_key": "your-datadog-application-key",
|
|
476
|
+
"site_api_url": "https://api.datadoghq.com",
|
|
477
|
+
"default_limit": 1000,
|
|
478
|
+
"request_timeout": 60,
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
def _reload_instructions(self):
|
|
482
|
+
"""Load Datadog metrics specific troubleshooting instructions."""
|
|
483
|
+
template_file_path = os.path.abspath(
|
|
484
|
+
os.path.join(
|
|
485
|
+
os.path.dirname(__file__), "datadog_metrics_instructions.jinja2"
|
|
486
|
+
)
|
|
487
|
+
)
|
|
488
|
+
self._load_llm_instructions(jinja_template=f"file://{template_file_path}")
|