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.
- holmes/__init__.py +1 -1
- holmes/clients/robusta_client.py +17 -4
- holmes/common/env_vars.py +40 -1
- holmes/config.py +114 -144
- holmes/core/conversations.py +53 -14
- holmes/core/feedback.py +191 -0
- holmes/core/investigation.py +18 -22
- holmes/core/llm.py +489 -88
- holmes/core/models.py +103 -1
- holmes/core/openai_formatting.py +13 -0
- holmes/core/prompt.py +1 -1
- holmes/core/safeguards.py +4 -4
- holmes/core/supabase_dal.py +293 -100
- holmes/core/tool_calling_llm.py +423 -323
- holmes/core/tools.py +311 -33
- holmes/core/tools_utils/token_counting.py +14 -0
- holmes/core/tools_utils/tool_context_window_limiter.py +57 -0
- holmes/core/tools_utils/tool_executor.py +13 -8
- holmes/core/toolset_manager.py +155 -4
- holmes/core/tracing.py +6 -1
- holmes/core/transformers/__init__.py +23 -0
- holmes/core/transformers/base.py +62 -0
- holmes/core/transformers/llm_summarize.py +174 -0
- holmes/core/transformers/registry.py +122 -0
- holmes/core/transformers/transformer.py +31 -0
- holmes/core/truncation/compaction.py +59 -0
- holmes/core/truncation/dal_truncation_utils.py +23 -0
- holmes/core/truncation/input_context_window_limiter.py +218 -0
- holmes/interactive.py +177 -24
- holmes/main.py +7 -4
- holmes/plugins/prompts/_fetch_logs.jinja2 +26 -1
- holmes/plugins/prompts/_general_instructions.jinja2 +1 -2
- holmes/plugins/prompts/_runbook_instructions.jinja2 +23 -12
- holmes/plugins/prompts/conversation_history_compaction.jinja2 +88 -0
- holmes/plugins/prompts/generic_ask.jinja2 +2 -4
- holmes/plugins/prompts/generic_ask_conversation.jinja2 +2 -1
- holmes/plugins/prompts/generic_ask_for_issue_conversation.jinja2 +2 -1
- holmes/plugins/prompts/generic_investigation.jinja2 +2 -1
- holmes/plugins/prompts/investigation_procedure.jinja2 +48 -0
- holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +2 -1
- holmes/plugins/prompts/kubernetes_workload_chat.jinja2 +2 -1
- holmes/plugins/runbooks/__init__.py +117 -18
- holmes/plugins/runbooks/catalog.json +2 -0
- holmes/plugins/toolsets/__init__.py +21 -8
- holmes/plugins/toolsets/aks-node-health.yaml +46 -0
- holmes/plugins/toolsets/aks.yaml +64 -0
- holmes/plugins/toolsets/atlas_mongodb/mongodb_atlas.py +26 -36
- holmes/plugins/toolsets/azure_sql/azure_sql_toolset.py +0 -1
- holmes/plugins/toolsets/azure_sql/tools/analyze_connection_failures.py +10 -7
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_connections.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_health_status.py +8 -6
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_performance.py +8 -6
- holmes/plugins/toolsets/azure_sql/tools/analyze_database_storage.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/get_active_alerts.py +9 -7
- holmes/plugins/toolsets/azure_sql/tools/get_slow_queries.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/get_top_cpu_queries.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/get_top_data_io_queries.py +9 -6
- holmes/plugins/toolsets/azure_sql/tools/get_top_log_io_queries.py +9 -6
- holmes/plugins/toolsets/bash/bash_toolset.py +10 -13
- holmes/plugins/toolsets/bash/common/bash.py +7 -7
- holmes/plugins/toolsets/cilium.yaml +284 -0
- holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +5 -3
- holmes/plugins/toolsets/datadog/datadog_api.py +490 -24
- holmes/plugins/toolsets/datadog/datadog_logs_instructions.jinja2 +21 -10
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +349 -216
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +190 -19
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +101 -44
- holmes/plugins/toolsets/datadog/toolset_datadog_rds.py +13 -16
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +25 -31
- holmes/plugins/toolsets/git.py +51 -46
- holmes/plugins/toolsets/grafana/common.py +15 -3
- holmes/plugins/toolsets/grafana/grafana_api.py +46 -24
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +454 -0
- holmes/plugins/toolsets/grafana/loki/instructions.jinja2 +9 -0
- holmes/plugins/toolsets/grafana/loki/toolset_grafana_loki.py +117 -0
- holmes/plugins/toolsets/grafana/toolset_grafana.py +211 -91
- holmes/plugins/toolsets/grafana/toolset_grafana_dashboard.jinja2 +27 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +246 -11
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +653 -293
- holmes/plugins/toolsets/grafana/trace_parser.py +1 -1
- holmes/plugins/toolsets/internet/internet.py +6 -7
- holmes/plugins/toolsets/internet/notion.py +5 -6
- holmes/plugins/toolsets/investigator/core_investigation.py +42 -34
- holmes/plugins/toolsets/kafka.py +25 -36
- holmes/plugins/toolsets/kubernetes.yaml +58 -84
- holmes/plugins/toolsets/kubernetes_logs.py +6 -6
- holmes/plugins/toolsets/kubernetes_logs.yaml +32 -0
- holmes/plugins/toolsets/logging_utils/logging_api.py +80 -4
- holmes/plugins/toolsets/mcp/toolset_mcp.py +181 -55
- holmes/plugins/toolsets/newrelic/__init__.py +0 -0
- holmes/plugins/toolsets/newrelic/new_relic_api.py +125 -0
- holmes/plugins/toolsets/newrelic/newrelic.jinja2 +41 -0
- holmes/plugins/toolsets/newrelic/newrelic.py +163 -0
- holmes/plugins/toolsets/opensearch/opensearch.py +10 -17
- holmes/plugins/toolsets/opensearch/opensearch_logs.py +7 -7
- holmes/plugins/toolsets/opensearch/opensearch_ppl_query_docs.jinja2 +1616 -0
- holmes/plugins/toolsets/opensearch/opensearch_query_assist.py +78 -0
- holmes/plugins/toolsets/opensearch/opensearch_query_assist_instructions.jinja2 +223 -0
- holmes/plugins/toolsets/opensearch/opensearch_traces.py +13 -16
- holmes/plugins/toolsets/openshift.yaml +283 -0
- holmes/plugins/toolsets/prometheus/prometheus.py +915 -390
- holmes/plugins/toolsets/prometheus/prometheus_instructions.jinja2 +43 -2
- holmes/plugins/toolsets/prometheus/utils.py +28 -0
- holmes/plugins/toolsets/rabbitmq/toolset_rabbitmq.py +9 -10
- holmes/plugins/toolsets/robusta/robusta.py +236 -65
- holmes/plugins/toolsets/robusta/robusta_instructions.jinja2 +26 -9
- holmes/plugins/toolsets/runbook/runbook_fetcher.py +137 -26
- holmes/plugins/toolsets/service_discovery.py +1 -1
- holmes/plugins/toolsets/servicenow_tables/instructions.jinja2 +83 -0
- holmes/plugins/toolsets/servicenow_tables/servicenow_tables.py +426 -0
- holmes/plugins/toolsets/utils.py +88 -0
- holmes/utils/config_utils.py +91 -0
- holmes/utils/default_toolset_installation_guide.jinja2 +1 -22
- holmes/utils/env.py +7 -0
- holmes/utils/global_instructions.py +75 -10
- holmes/utils/holmes_status.py +2 -1
- holmes/utils/holmes_sync_toolsets.py +0 -2
- holmes/utils/krr_utils.py +188 -0
- holmes/utils/sentry_helper.py +41 -0
- holmes/utils/stream.py +61 -7
- holmes/version.py +34 -14
- holmesgpt-0.16.2a0.dist-info/LICENSE +178 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/METADATA +29 -27
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/RECORD +126 -102
- holmes/core/performance_timing.py +0 -72
- holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
- holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +0 -110
- holmes/plugins/toolsets/newrelic.py +0 -231
- holmes/plugins/toolsets/servicenow/install.md +0 -37
- holmes/plugins/toolsets/servicenow/instructions.jinja2 +0 -3
- holmes/plugins/toolsets/servicenow/servicenow.py +0 -219
- holmesgpt-0.13.2.dist-info/LICENSE.txt +0 -21
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/WHEEL +0 -0
- {holmesgpt-0.13.2.dist-info → holmesgpt-0.16.2a0.dist-info}/entry_points.txt +0 -0
|
@@ -9,8 +9,9 @@ from holmes.core.tools import (
|
|
|
9
9
|
CallablePrerequisite,
|
|
10
10
|
StructuredToolResult,
|
|
11
11
|
Tool,
|
|
12
|
+
ToolInvokeContext,
|
|
12
13
|
ToolParameter,
|
|
13
|
-
|
|
14
|
+
StructuredToolResultStatus,
|
|
14
15
|
Toolset,
|
|
15
16
|
ToolsetTag,
|
|
16
17
|
)
|
|
@@ -69,7 +70,7 @@ class GenerateRDSPerformanceReport(BaseDatadogRDSTool):
|
|
|
69
70
|
def __init__(self, toolset: "DatadogRDSToolset"):
|
|
70
71
|
super().__init__(
|
|
71
72
|
name="datadog_rds_performance_report",
|
|
72
|
-
description="Generate a comprehensive performance report for a specific RDS instance including latency, resource utilization, and storage metrics with analysis",
|
|
73
|
+
description="[datadog/rds toolset] Generate a comprehensive performance report for a specific RDS instance including latency, resource utilization, and storage metrics with analysis",
|
|
73
74
|
parameters={
|
|
74
75
|
"db_instance_identifier": ToolParameter(
|
|
75
76
|
description="The RDS database instance identifier",
|
|
@@ -92,12 +93,10 @@ class GenerateRDSPerformanceReport(BaseDatadogRDSTool):
|
|
|
92
93
|
toolset=toolset,
|
|
93
94
|
)
|
|
94
95
|
|
|
95
|
-
def _invoke(
|
|
96
|
-
self, params: dict, user_approved: bool = False
|
|
97
|
-
) -> StructuredToolResult:
|
|
96
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
98
97
|
if not self.toolset.dd_config:
|
|
99
98
|
return StructuredToolResult(
|
|
100
|
-
status=
|
|
99
|
+
status=StructuredToolResultStatus.ERROR,
|
|
101
100
|
error=TOOLSET_CONFIG_MISSING_ERROR,
|
|
102
101
|
params=params,
|
|
103
102
|
)
|
|
@@ -150,7 +149,7 @@ class GenerateRDSPerformanceReport(BaseDatadogRDSTool):
|
|
|
150
149
|
formatted_report = self._format_report(report)
|
|
151
150
|
|
|
152
151
|
return StructuredToolResult(
|
|
153
|
-
status=
|
|
152
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
154
153
|
data=formatted_report,
|
|
155
154
|
params=params,
|
|
156
155
|
)
|
|
@@ -158,7 +157,7 @@ class GenerateRDSPerformanceReport(BaseDatadogRDSTool):
|
|
|
158
157
|
except Exception as e:
|
|
159
158
|
logging.error(f"Error generating RDS performance report: {str(e)}")
|
|
160
159
|
return StructuredToolResult(
|
|
161
|
-
status=
|
|
160
|
+
status=StructuredToolResultStatus.ERROR,
|
|
162
161
|
error=f"Failed to generate RDS performance report: {str(e)}",
|
|
163
162
|
params=params,
|
|
164
163
|
)
|
|
@@ -364,7 +363,7 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
|
|
|
364
363
|
def __init__(self, toolset: "DatadogRDSToolset"):
|
|
365
364
|
super().__init__(
|
|
366
365
|
name="datadog_rds_top_worst_performing",
|
|
367
|
-
description="Get a summarized report of the top worst performing RDS instances based on latency, CPU utilization, and error rates",
|
|
366
|
+
description="[datadog/rds toolset] Get a summarized report of the top worst performing RDS instances based on latency, CPU utilization, and error rates",
|
|
368
367
|
parameters={
|
|
369
368
|
"top_n": ToolParameter(
|
|
370
369
|
description=f"Number of worst performing instances to return (default: {DEFAULT_TOP_INSTANCES})",
|
|
@@ -392,12 +391,10 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
|
|
|
392
391
|
toolset=toolset,
|
|
393
392
|
)
|
|
394
393
|
|
|
395
|
-
def _invoke(
|
|
396
|
-
self, params: dict, user_approved: bool = False
|
|
397
|
-
) -> StructuredToolResult:
|
|
394
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
398
395
|
if not self.toolset.dd_config:
|
|
399
396
|
return StructuredToolResult(
|
|
400
|
-
status=
|
|
397
|
+
status=StructuredToolResultStatus.ERROR,
|
|
401
398
|
error=TOOLSET_CONFIG_MISSING_ERROR,
|
|
402
399
|
params=params,
|
|
403
400
|
)
|
|
@@ -416,7 +413,7 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
|
|
|
416
413
|
|
|
417
414
|
if not instances:
|
|
418
415
|
return StructuredToolResult(
|
|
419
|
-
status=
|
|
416
|
+
status=StructuredToolResultStatus.NO_DATA,
|
|
420
417
|
data="No RDS instances found with metrics in the specified time range",
|
|
421
418
|
params=params,
|
|
422
419
|
)
|
|
@@ -443,7 +440,7 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
|
|
|
443
440
|
report += f"\n\nInstances:\n{json.dumps(worst_performers, indent=2)}"
|
|
444
441
|
|
|
445
442
|
return StructuredToolResult(
|
|
446
|
-
status=
|
|
443
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
447
444
|
data=report,
|
|
448
445
|
params=params,
|
|
449
446
|
)
|
|
@@ -451,7 +448,7 @@ class GetTopWorstPerformingRDSInstances(BaseDatadogRDSTool):
|
|
|
451
448
|
except Exception as e:
|
|
452
449
|
logging.error(f"Error getting top worst performing RDS instances: {str(e)}")
|
|
453
450
|
return StructuredToolResult(
|
|
454
|
-
status=
|
|
451
|
+
status=StructuredToolResultStatus.ERROR,
|
|
455
452
|
error=f"Failed to get top worst performing RDS instances: {str(e)}",
|
|
456
453
|
params=params,
|
|
457
454
|
)
|
|
@@ -9,10 +9,11 @@ from typing import Any, Dict, Optional, Tuple
|
|
|
9
9
|
from holmes.core.tools import (
|
|
10
10
|
CallablePrerequisite,
|
|
11
11
|
Tool,
|
|
12
|
+
ToolInvokeContext,
|
|
12
13
|
ToolParameter,
|
|
13
14
|
Toolset,
|
|
14
15
|
StructuredToolResult,
|
|
15
|
-
|
|
16
|
+
StructuredToolResultStatus,
|
|
16
17
|
ToolsetTag,
|
|
17
18
|
)
|
|
18
19
|
from holmes.plugins.toolsets.datadog.datadog_api import (
|
|
@@ -49,7 +50,7 @@ class DatadogTracesToolset(Toolset):
|
|
|
49
50
|
super().__init__(
|
|
50
51
|
name="datadog/traces",
|
|
51
52
|
description="Toolset for interacting with Datadog APM to fetch and analyze traces",
|
|
52
|
-
docs_url="https://
|
|
53
|
+
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/datadog/",
|
|
53
54
|
icon_url="https://imgix.datadoghq.com//img/about/presskit/DDlogo.jpg",
|
|
54
55
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
55
56
|
tools=[
|
|
@@ -57,7 +58,6 @@ class DatadogTracesToolset(Toolset):
|
|
|
57
58
|
FetchDatadogTraceById(toolset=self),
|
|
58
59
|
FetchDatadogSpansByFilter(toolset=self),
|
|
59
60
|
],
|
|
60
|
-
experimental=True,
|
|
61
61
|
tags=[ToolsetTag.CORE],
|
|
62
62
|
)
|
|
63
63
|
self._reload_instructions()
|
|
@@ -157,7 +157,7 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
|
|
|
157
157
|
def __init__(self, toolset: "DatadogTracesToolset"):
|
|
158
158
|
super().__init__(
|
|
159
159
|
name="fetch_datadog_traces",
|
|
160
|
-
description="Fetch a list of traces from Datadog with optional filters",
|
|
160
|
+
description="[datadog/traces toolset] Fetch a list of traces from Datadog with optional filters",
|
|
161
161
|
parameters={
|
|
162
162
|
"service": ToolParameter(
|
|
163
163
|
description="Filter by service name",
|
|
@@ -211,13 +211,11 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
|
|
|
211
211
|
filter_str = ", ".join(filters) if filters else "all"
|
|
212
212
|
return f"{toolset_name_for_one_liner(self.toolset.name)}: Fetch Traces ({filter_str})"
|
|
213
213
|
|
|
214
|
-
def _invoke(
|
|
215
|
-
self, params: dict, user_approved: bool = False
|
|
216
|
-
) -> StructuredToolResult:
|
|
214
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
217
215
|
"""Execute the tool to fetch traces."""
|
|
218
216
|
if not self.toolset.dd_config:
|
|
219
217
|
return StructuredToolResult(
|
|
220
|
-
status=
|
|
218
|
+
status=StructuredToolResultStatus.ERROR,
|
|
221
219
|
error="Datadog configuration not initialized",
|
|
222
220
|
params=params,
|
|
223
221
|
)
|
|
@@ -306,13 +304,13 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
|
|
|
306
304
|
formatted_output = format_traces_list(spans, limit=params.get("limit", 50))
|
|
307
305
|
if not formatted_output:
|
|
308
306
|
return StructuredToolResult(
|
|
309
|
-
status=
|
|
307
|
+
status=StructuredToolResultStatus.NO_DATA,
|
|
310
308
|
params=params,
|
|
311
309
|
data="No matching traces found.",
|
|
312
310
|
)
|
|
313
311
|
|
|
314
312
|
return StructuredToolResult(
|
|
315
|
-
status=
|
|
313
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
316
314
|
data=formatted_output,
|
|
317
315
|
params=params,
|
|
318
316
|
)
|
|
@@ -331,7 +329,7 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
|
|
|
331
329
|
error_msg = f"Exception while querying Datadog: {str(e)}"
|
|
332
330
|
|
|
333
331
|
return StructuredToolResult(
|
|
334
|
-
status=
|
|
332
|
+
status=StructuredToolResultStatus.ERROR,
|
|
335
333
|
error=error_msg,
|
|
336
334
|
params=params,
|
|
337
335
|
invocation=(
|
|
@@ -344,7 +342,7 @@ class FetchDatadogTracesList(BaseDatadogTracesTool):
|
|
|
344
342
|
except Exception as e:
|
|
345
343
|
logging.exception(e, exc_info=True)
|
|
346
344
|
return StructuredToolResult(
|
|
347
|
-
status=
|
|
345
|
+
status=StructuredToolResultStatus.ERROR,
|
|
348
346
|
error=f"Unexpected error: {str(e)}",
|
|
349
347
|
params=params,
|
|
350
348
|
invocation=(
|
|
@@ -361,7 +359,7 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
|
|
|
361
359
|
def __init__(self, toolset: "DatadogTracesToolset"):
|
|
362
360
|
super().__init__(
|
|
363
361
|
name="fetch_datadog_trace_by_id",
|
|
364
|
-
description="Fetch detailed information about a specific trace by its ID",
|
|
362
|
+
description="[datadog/traces toolset] Fetch detailed information about a specific trace by its ID",
|
|
365
363
|
parameters={
|
|
366
364
|
"trace_id": ToolParameter(
|
|
367
365
|
description="The trace ID to fetch details for",
|
|
@@ -377,13 +375,11 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
|
|
|
377
375
|
trace_id = params.get("trace_id", "unknown")
|
|
378
376
|
return f"{toolset_name_for_one_liner(self.toolset.name)}: Fetch Trace Details ({trace_id})"
|
|
379
377
|
|
|
380
|
-
def _invoke(
|
|
381
|
-
self, params: dict, user_approved: bool = False
|
|
382
|
-
) -> StructuredToolResult:
|
|
378
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
383
379
|
"""Execute the tool to fetch trace details."""
|
|
384
380
|
if not self.toolset.dd_config:
|
|
385
381
|
return StructuredToolResult(
|
|
386
|
-
status=
|
|
382
|
+
status=StructuredToolResultStatus.ERROR,
|
|
387
383
|
error="Datadog configuration not initialized",
|
|
388
384
|
params=params,
|
|
389
385
|
)
|
|
@@ -391,7 +387,7 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
|
|
|
391
387
|
trace_id = params.get("trace_id")
|
|
392
388
|
if not trace_id:
|
|
393
389
|
return StructuredToolResult(
|
|
394
|
-
status=
|
|
390
|
+
status=StructuredToolResultStatus.ERROR,
|
|
395
391
|
error="trace_id parameter is required",
|
|
396
392
|
params=params,
|
|
397
393
|
)
|
|
@@ -445,13 +441,13 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
|
|
|
445
441
|
formatted_output = format_trace_hierarchy(trace_id, spans)
|
|
446
442
|
if not formatted_output:
|
|
447
443
|
return StructuredToolResult(
|
|
448
|
-
status=
|
|
444
|
+
status=StructuredToolResultStatus.NO_DATA,
|
|
449
445
|
params=params,
|
|
450
446
|
data=f"No trace found for trace_id: {trace_id}",
|
|
451
447
|
)
|
|
452
448
|
|
|
453
449
|
return StructuredToolResult(
|
|
454
|
-
status=
|
|
450
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
455
451
|
data=formatted_output,
|
|
456
452
|
params=params,
|
|
457
453
|
)
|
|
@@ -470,7 +466,7 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
|
|
|
470
466
|
error_msg = f"Exception while querying Datadog: {str(e)}"
|
|
471
467
|
|
|
472
468
|
return StructuredToolResult(
|
|
473
|
-
status=
|
|
469
|
+
status=StructuredToolResultStatus.ERROR,
|
|
474
470
|
error=error_msg,
|
|
475
471
|
params=params,
|
|
476
472
|
invocation=(
|
|
@@ -483,7 +479,7 @@ class FetchDatadogTraceById(BaseDatadogTracesTool):
|
|
|
483
479
|
except Exception as e:
|
|
484
480
|
logging.exception(e, exc_info=True)
|
|
485
481
|
return StructuredToolResult(
|
|
486
|
-
status=
|
|
482
|
+
status=StructuredToolResultStatus.ERROR,
|
|
487
483
|
error=f"Unexpected error: {str(e)}",
|
|
488
484
|
params=params,
|
|
489
485
|
invocation=(
|
|
@@ -500,7 +496,7 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
|
|
|
500
496
|
def __init__(self, toolset: "DatadogTracesToolset"):
|
|
501
497
|
super().__init__(
|
|
502
498
|
name="fetch_datadog_spans",
|
|
503
|
-
description="Search for spans in Datadog with detailed filters",
|
|
499
|
+
description="[datadog/traces toolset] Search for spans in Datadog with detailed filters",
|
|
504
500
|
parameters={
|
|
505
501
|
"query": ToolParameter(
|
|
506
502
|
description="Datadog search query (e.g., 'service:web-app @http.status_code:500')",
|
|
@@ -560,13 +556,11 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
|
|
|
560
556
|
filter_str = ", ".join(filters) if filters else "all"
|
|
561
557
|
return f"{toolset_name_for_one_liner(self.toolset.name)}: Search Spans ({filter_str})"
|
|
562
558
|
|
|
563
|
-
def _invoke(
|
|
564
|
-
self, params: dict, user_approved: bool = False
|
|
565
|
-
) -> StructuredToolResult:
|
|
559
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
566
560
|
"""Execute the tool to search spans."""
|
|
567
561
|
if not self.toolset.dd_config:
|
|
568
562
|
return StructuredToolResult(
|
|
569
|
-
status=
|
|
563
|
+
status=StructuredToolResultStatus.ERROR,
|
|
570
564
|
error="Datadog configuration not initialized",
|
|
571
565
|
params=params,
|
|
572
566
|
)
|
|
@@ -654,13 +648,13 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
|
|
|
654
648
|
formatted_output = format_spans_search(spans)
|
|
655
649
|
if not formatted_output:
|
|
656
650
|
return StructuredToolResult(
|
|
657
|
-
status=
|
|
651
|
+
status=StructuredToolResultStatus.NO_DATA,
|
|
658
652
|
params=params,
|
|
659
653
|
data="No matching spans found.",
|
|
660
654
|
)
|
|
661
655
|
|
|
662
656
|
return StructuredToolResult(
|
|
663
|
-
status=
|
|
657
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
664
658
|
data=formatted_output,
|
|
665
659
|
params=params,
|
|
666
660
|
)
|
|
@@ -678,7 +672,7 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
|
|
|
678
672
|
error_msg = f"Exception while querying Datadog: {str(e)}"
|
|
679
673
|
|
|
680
674
|
return StructuredToolResult(
|
|
681
|
-
status=
|
|
675
|
+
status=StructuredToolResultStatus.ERROR,
|
|
682
676
|
error=error_msg,
|
|
683
677
|
params=params,
|
|
684
678
|
invocation=(
|
|
@@ -691,7 +685,7 @@ class FetchDatadogSpansByFilter(BaseDatadogTracesTool):
|
|
|
691
685
|
except Exception as e:
|
|
692
686
|
logging.exception(e, exc_info=True)
|
|
693
687
|
return StructuredToolResult(
|
|
694
|
-
status=
|
|
688
|
+
status=StructuredToolResultStatus.ERROR,
|
|
695
689
|
error=f"Unexpected error: {str(e)}",
|
|
696
690
|
params=params,
|
|
697
691
|
invocation=(
|
holmes/plugins/toolsets/git.py
CHANGED
|
@@ -4,7 +4,11 @@ import requests # type: ignore
|
|
|
4
4
|
import os
|
|
5
5
|
from typing import Any, Optional, Dict, List, Tuple
|
|
6
6
|
from pydantic import BaseModel
|
|
7
|
-
from holmes.core.tools import
|
|
7
|
+
from holmes.core.tools import (
|
|
8
|
+
StructuredToolResult,
|
|
9
|
+
StructuredToolResultStatus,
|
|
10
|
+
ToolInvokeContext,
|
|
11
|
+
)
|
|
8
12
|
|
|
9
13
|
from holmes.core.tools import (
|
|
10
14
|
Toolset,
|
|
@@ -20,10 +24,12 @@ class GitHubConfig(BaseModel):
|
|
|
20
24
|
git_repo: str
|
|
21
25
|
git_credentials: str
|
|
22
26
|
git_branch: str = "main"
|
|
27
|
+
git_url: str = "https://api.github.com"
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
class GitToolset(Toolset):
|
|
26
31
|
git_repo: Optional[str] = None
|
|
32
|
+
git_url: Optional[str] = None
|
|
27
33
|
git_credentials: Optional[str] = None
|
|
28
34
|
git_branch: Optional[str] = None
|
|
29
35
|
_created_branches: set[str] = set() # Track branches created by the tool
|
|
@@ -33,7 +39,7 @@ class GitToolset(Toolset):
|
|
|
33
39
|
super().__init__(
|
|
34
40
|
name="git",
|
|
35
41
|
description="Runs git commands to read repos and create PRs",
|
|
36
|
-
docs_url="https://
|
|
42
|
+
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/github/",
|
|
37
43
|
icon_url="https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg",
|
|
38
44
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
39
45
|
tools=[
|
|
@@ -75,6 +81,7 @@ class GitToolset(Toolset):
|
|
|
75
81
|
|
|
76
82
|
try:
|
|
77
83
|
self.git_repo = os.getenv("GIT_REPO") or config.get("git_repo")
|
|
84
|
+
self.git_url = os.getenv("GIT_URL") or config.get("git_url")
|
|
78
85
|
self.git_credentials = os.getenv("GIT_CREDENTIALS") or config.get(
|
|
79
86
|
"git_credentials"
|
|
80
87
|
)
|
|
@@ -82,7 +89,9 @@ class GitToolset(Toolset):
|
|
|
82
89
|
"git_branch", "main"
|
|
83
90
|
)
|
|
84
91
|
|
|
85
|
-
if not all(
|
|
92
|
+
if not all(
|
|
93
|
+
[self.git_repo, self.git_url, self.git_credentials, self.git_branch]
|
|
94
|
+
):
|
|
86
95
|
logging.error("Missing one or more required Git configuration values.")
|
|
87
96
|
return False, "Missing one or more required Git configuration values."
|
|
88
97
|
return True, ""
|
|
@@ -96,7 +105,7 @@ class GitToolset(Toolset):
|
|
|
96
105
|
def list_open_prs(self) -> List[Dict[str, Any]]:
|
|
97
106
|
"""Helper method to list all open PRs in the repository."""
|
|
98
107
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
99
|
-
url = f"
|
|
108
|
+
url = f"{self.git_url}/repos/{self.git_repo}/pulls?state=open"
|
|
100
109
|
resp = requests.get(url, headers=headers)
|
|
101
110
|
if resp.status_code != 200:
|
|
102
111
|
raise Exception(self._sanitize_error(f"Error listing PRs: {resp.text}"))
|
|
@@ -105,9 +114,7 @@ class GitToolset(Toolset):
|
|
|
105
114
|
def get_branch_ref(self, branch_name: str) -> Optional[str]:
|
|
106
115
|
"""Get the SHA of a branch reference."""
|
|
107
116
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
108
|
-
url =
|
|
109
|
-
f"https://api.github.com/repos/{self.git_repo}/git/refs/heads/{branch_name}"
|
|
110
|
-
)
|
|
117
|
+
url = f"{self.git_url}/repos/{self.git_repo}/git/refs/heads/{branch_name}"
|
|
111
118
|
resp = requests.get(url, headers=headers)
|
|
112
119
|
if resp.status_code == 404:
|
|
113
120
|
return None
|
|
@@ -120,7 +127,7 @@ class GitToolset(Toolset):
|
|
|
120
127
|
def create_branch(self, branch_name: str, base_sha: str) -> None:
|
|
121
128
|
"""Create a new branch from a base SHA."""
|
|
122
129
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
123
|
-
url = f"
|
|
130
|
+
url = f"{self.git_url}/repos/{self.git_repo}/git/refs"
|
|
124
131
|
resp = requests.post(
|
|
125
132
|
url,
|
|
126
133
|
headers=headers,
|
|
@@ -136,7 +143,7 @@ class GitToolset(Toolset):
|
|
|
136
143
|
def get_file_content(self, filepath: str, branch: str) -> tuple[str, str]:
|
|
137
144
|
"""Get file content and SHA from a specific branch."""
|
|
138
145
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
139
|
-
url = f"
|
|
146
|
+
url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}?ref={branch}"
|
|
140
147
|
resp = requests.get(url, headers=headers)
|
|
141
148
|
if resp.status_code == 404:
|
|
142
149
|
raise Exception(f"File not found: {filepath}")
|
|
@@ -150,7 +157,7 @@ class GitToolset(Toolset):
|
|
|
150
157
|
) -> None:
|
|
151
158
|
"""Update a file in a specific branch."""
|
|
152
159
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
153
|
-
url = f"
|
|
160
|
+
url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}"
|
|
154
161
|
encoded_content = base64.b64encode(content.encode()).decode()
|
|
155
162
|
resp = requests.put(
|
|
156
163
|
url,
|
|
@@ -168,7 +175,7 @@ class GitToolset(Toolset):
|
|
|
168
175
|
def create_pr(self, title: str, head: str, base: str, body: str) -> str:
|
|
169
176
|
"""Create a new pull request."""
|
|
170
177
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
171
|
-
url = f"
|
|
178
|
+
url = f"{self.git_url}/repos/{self.git_repo}/pulls"
|
|
172
179
|
resp = requests.post(
|
|
173
180
|
url,
|
|
174
181
|
headers=headers,
|
|
@@ -188,7 +195,7 @@ class GitToolset(Toolset):
|
|
|
188
195
|
def get_pr_details(self, pr_number: int) -> Dict[str, Any]:
|
|
189
196
|
"""Get details of a specific PR."""
|
|
190
197
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
191
|
-
url = f"
|
|
198
|
+
url = f"{self.git_url}/repos/{self.git_repo}/pulls/{pr_number}"
|
|
192
199
|
resp = requests.get(url, headers=headers)
|
|
193
200
|
if resp.status_code != 200:
|
|
194
201
|
raise Exception(
|
|
@@ -215,7 +222,7 @@ class GitToolset(Toolset):
|
|
|
215
222
|
|
|
216
223
|
# Update file
|
|
217
224
|
headers = {"Authorization": f"token {self.git_credentials}"}
|
|
218
|
-
url = f"
|
|
225
|
+
url = f"{self.git_url}/repos/{self.git_repo}/contents/{filepath}"
|
|
219
226
|
encoded_content = base64.b64encode(content.encode()).decode()
|
|
220
227
|
data = {
|
|
221
228
|
"message": message,
|
|
@@ -250,16 +257,18 @@ class GitReadFileWithLineNumbers(Tool):
|
|
|
250
257
|
)
|
|
251
258
|
|
|
252
259
|
def _invoke(
|
|
253
|
-
self,
|
|
260
|
+
self,
|
|
261
|
+
params: dict,
|
|
262
|
+
context: ToolInvokeContext,
|
|
254
263
|
) -> StructuredToolResult:
|
|
255
264
|
filepath = params["filepath"]
|
|
256
265
|
try:
|
|
257
266
|
headers = {"Authorization": f"token {self.toolset.git_credentials}"}
|
|
258
|
-
url = f"
|
|
267
|
+
url = f"{self.toolset.git_url}/repos/{self.toolset.git_repo}/contents/{filepath}"
|
|
259
268
|
resp = requests.get(url, headers=headers)
|
|
260
269
|
if resp.status_code != 200:
|
|
261
270
|
return StructuredToolResult(
|
|
262
|
-
status=
|
|
271
|
+
status=StructuredToolResultStatus.ERROR,
|
|
263
272
|
data=self.toolset._sanitize_error(
|
|
264
273
|
f"Error fetching file: {resp.text}"
|
|
265
274
|
),
|
|
@@ -268,13 +277,13 @@ class GitReadFileWithLineNumbers(Tool):
|
|
|
268
277
|
content = base64.b64decode(resp.json()["content"]).decode().splitlines()
|
|
269
278
|
numbered = "\n".join(f"{i+1}: {line}" for i, line in enumerate(content))
|
|
270
279
|
return StructuredToolResult(
|
|
271
|
-
status=
|
|
280
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
272
281
|
data=numbered,
|
|
273
282
|
params=params,
|
|
274
283
|
)
|
|
275
284
|
except Exception as e:
|
|
276
285
|
return StructuredToolResult(
|
|
277
|
-
status=
|
|
286
|
+
status=StructuredToolResultStatus.ERROR,
|
|
278
287
|
data=self.toolset._sanitize_error(str(e)),
|
|
279
288
|
params=params,
|
|
280
289
|
)
|
|
@@ -296,15 +305,17 @@ class GitListFiles(Tool):
|
|
|
296
305
|
)
|
|
297
306
|
|
|
298
307
|
def _invoke(
|
|
299
|
-
self,
|
|
308
|
+
self,
|
|
309
|
+
params: dict,
|
|
310
|
+
context: ToolInvokeContext,
|
|
300
311
|
) -> StructuredToolResult:
|
|
301
312
|
try:
|
|
302
313
|
headers = {"Authorization": f"token {self.toolset.git_credentials}"}
|
|
303
|
-
url = f"
|
|
314
|
+
url = f"{self.toolset.git_url}/repos/{self.toolset.git_repo}/git/trees/{self.toolset.git_branch}?recursive=1"
|
|
304
315
|
resp = requests.get(url, headers=headers)
|
|
305
316
|
if resp.status_code != 200:
|
|
306
317
|
return StructuredToolResult(
|
|
307
|
-
status=
|
|
318
|
+
status=StructuredToolResultStatus.ERROR,
|
|
308
319
|
data=self.toolset._sanitize_error(
|
|
309
320
|
f"Error listing files: {resp.text}"
|
|
310
321
|
),
|
|
@@ -312,13 +323,13 @@ class GitListFiles(Tool):
|
|
|
312
323
|
)
|
|
313
324
|
paths = [entry["path"] for entry in resp.json()["tree"]]
|
|
314
325
|
return StructuredToolResult(
|
|
315
|
-
status=
|
|
326
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
316
327
|
data=paths,
|
|
317
328
|
params=params,
|
|
318
329
|
)
|
|
319
330
|
except Exception as e:
|
|
320
331
|
return StructuredToolResult(
|
|
321
|
-
status=
|
|
332
|
+
status=StructuredToolResultStatus.ERROR,
|
|
322
333
|
data=self.toolset._sanitize_error(str(e)),
|
|
323
334
|
params=params,
|
|
324
335
|
)
|
|
@@ -338,9 +349,7 @@ class GitListOpenPRs(Tool):
|
|
|
338
349
|
toolset=toolset, # type: ignore
|
|
339
350
|
)
|
|
340
351
|
|
|
341
|
-
def _invoke(
|
|
342
|
-
self, params: dict, user_approved: bool = False
|
|
343
|
-
) -> StructuredToolResult:
|
|
352
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
344
353
|
try:
|
|
345
354
|
prs = self.toolset.list_open_prs()
|
|
346
355
|
formatted = [
|
|
@@ -353,13 +362,13 @@ class GitListOpenPRs(Tool):
|
|
|
353
362
|
for pr in prs
|
|
354
363
|
]
|
|
355
364
|
return StructuredToolResult(
|
|
356
|
-
status=
|
|
365
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
357
366
|
data=formatted,
|
|
358
367
|
params=params,
|
|
359
368
|
)
|
|
360
369
|
except Exception as e:
|
|
361
370
|
return StructuredToolResult(
|
|
362
|
-
status=
|
|
371
|
+
status=StructuredToolResultStatus.ERROR,
|
|
363
372
|
data=self.toolset._sanitize_error(str(e)),
|
|
364
373
|
params=params,
|
|
365
374
|
)
|
|
@@ -408,19 +417,17 @@ class GitExecuteChanges(Tool):
|
|
|
408
417
|
toolset=toolset, # type: ignore
|
|
409
418
|
)
|
|
410
419
|
|
|
411
|
-
def _invoke(
|
|
412
|
-
self, params: dict, user_approved: bool = False
|
|
413
|
-
) -> StructuredToolResult:
|
|
420
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
414
421
|
def error(msg: str) -> StructuredToolResult:
|
|
415
422
|
return StructuredToolResult(
|
|
416
|
-
status=
|
|
423
|
+
status=StructuredToolResultStatus.ERROR,
|
|
417
424
|
data=self.toolset._sanitize_error(msg),
|
|
418
425
|
params=params,
|
|
419
426
|
)
|
|
420
427
|
|
|
421
428
|
def success(msg: Any) -> StructuredToolResult:
|
|
422
429
|
return StructuredToolResult(
|
|
423
|
-
status=
|
|
430
|
+
status=StructuredToolResultStatus.SUCCESS, data=msg, params=params
|
|
424
431
|
)
|
|
425
432
|
|
|
426
433
|
def modify_lines(lines: List[str]) -> List[str]:
|
|
@@ -628,9 +635,7 @@ class GitUpdatePR(Tool):
|
|
|
628
635
|
toolset=toolset, # type: ignore
|
|
629
636
|
)
|
|
630
637
|
|
|
631
|
-
def _invoke(
|
|
632
|
-
self, params: dict, user_approved: bool = False
|
|
633
|
-
) -> StructuredToolResult:
|
|
638
|
+
def _invoke(self, params: dict, context: ToolInvokeContext) -> StructuredToolResult:
|
|
634
639
|
try:
|
|
635
640
|
line = params["line"]
|
|
636
641
|
filename = params["filename"]
|
|
@@ -643,24 +648,24 @@ class GitUpdatePR(Tool):
|
|
|
643
648
|
# Validate inputs
|
|
644
649
|
if not commit_message.strip():
|
|
645
650
|
return StructuredToolResult(
|
|
646
|
-
status=
|
|
651
|
+
status=StructuredToolResultStatus.ERROR,
|
|
647
652
|
error="Tool call failed to run: Commit message cannot be empty",
|
|
648
653
|
)
|
|
649
654
|
if not filename.strip():
|
|
650
655
|
return StructuredToolResult(
|
|
651
|
-
status=
|
|
656
|
+
status=StructuredToolResultStatus.ERROR,
|
|
652
657
|
error="Tool call failed to run: Filename cannot be empty",
|
|
653
658
|
)
|
|
654
659
|
if line < 1:
|
|
655
660
|
return StructuredToolResult(
|
|
656
|
-
status=
|
|
661
|
+
status=StructuredToolResultStatus.ERROR,
|
|
657
662
|
error="Tool call failed to run: Line number must be positive",
|
|
658
663
|
)
|
|
659
664
|
|
|
660
665
|
# Verify this is a PR created by our tool
|
|
661
666
|
if not self.toolset.is_created_pr(pr_number):
|
|
662
667
|
return StructuredToolResult(
|
|
663
|
-
status=
|
|
668
|
+
status=StructuredToolResultStatus.ERROR,
|
|
664
669
|
error=f"Tool call failed to run: PR #{pr_number} was not created by this tool. Only PRs created using git_execute_changes can be updated.",
|
|
665
670
|
)
|
|
666
671
|
|
|
@@ -714,7 +719,7 @@ class GitUpdatePR(Tool):
|
|
|
714
719
|
del content_lines[line - 1]
|
|
715
720
|
else:
|
|
716
721
|
return StructuredToolResult(
|
|
717
|
-
status=
|
|
722
|
+
status=StructuredToolResultStatus.ERROR,
|
|
718
723
|
error=f"Tool call failed to run: Invalid command: {command}",
|
|
719
724
|
)
|
|
720
725
|
|
|
@@ -722,7 +727,7 @@ class GitUpdatePR(Tool):
|
|
|
722
727
|
|
|
723
728
|
if dry_run:
|
|
724
729
|
return StructuredToolResult(
|
|
725
|
-
status=
|
|
730
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
726
731
|
data=f"DRY RUN: Updated content for PR #{pr_number}:\n\n{updated_content}",
|
|
727
732
|
)
|
|
728
733
|
|
|
@@ -731,13 +736,13 @@ class GitUpdatePR(Tool):
|
|
|
731
736
|
pr_number, filename, updated_content, commit_message
|
|
732
737
|
)
|
|
733
738
|
return StructuredToolResult(
|
|
734
|
-
status=
|
|
739
|
+
status=StructuredToolResultStatus.SUCCESS,
|
|
735
740
|
data=f"Added commit to PR #{pr_number} successfully",
|
|
736
741
|
)
|
|
737
742
|
|
|
738
743
|
except Exception as e:
|
|
739
744
|
return StructuredToolResult(
|
|
740
|
-
status=
|
|
745
|
+
status=StructuredToolResultStatus.ERROR,
|
|
741
746
|
error=self.toolset._sanitize_error(
|
|
742
747
|
f"Tool call failed to run: Error updating PR: {str(e)}"
|
|
743
748
|
),
|
|
@@ -745,14 +750,14 @@ class GitUpdatePR(Tool):
|
|
|
745
750
|
|
|
746
751
|
except requests.exceptions.RequestException as e:
|
|
747
752
|
return StructuredToolResult(
|
|
748
|
-
status=
|
|
753
|
+
status=StructuredToolResultStatus.ERROR,
|
|
749
754
|
error=self.toolset._sanitize_error(
|
|
750
755
|
f"Tool call failed to run: Network error: {str(e)}"
|
|
751
756
|
),
|
|
752
757
|
)
|
|
753
758
|
except Exception as e:
|
|
754
759
|
return StructuredToolResult(
|
|
755
|
-
status=
|
|
760
|
+
status=StructuredToolResultStatus.ERROR,
|
|
756
761
|
error=self.toolset._sanitize_error(
|
|
757
762
|
f"Tool call failed to run: Unexpected error: {str(e)}"
|
|
758
763
|
),
|