holmesgpt 0.13.3__py3-none-any.whl → 0.14.0a0__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/core/tool_calling_llm.py +95 -78
- holmes/core/tracing.py +6 -1
- holmes/plugins/toolsets/datadog/toolset_datadog_general.py +1 -2
- holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +1 -2
- holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +1 -2
- holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +1 -2
- holmes/plugins/toolsets/git.py +1 -1
- holmes/plugins/toolsets/grafana/common.py +12 -0
- holmes/plugins/toolsets/grafana/grafana_tempo_api.py +473 -0
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.jinja2 +146 -11
- holmes/plugins/toolsets/grafana/toolset_grafana_tempo.py +773 -259
- holmes/plugins/toolsets/grafana/trace_parser.py +1 -1
- holmes/plugins/toolsets/newrelic.py +3 -3
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/METADATA +2 -2
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/RECORD +19 -19
- holmes/plugins/toolsets/grafana/tempo_api.py +0 -124
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/LICENSE.txt +0 -0
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/WHEEL +0 -0
- {holmesgpt-0.13.3.dist-info → holmesgpt-0.14.0a0.dist-info}/entry_points.txt +0 -0
holmes/__init__.py
CHANGED
holmes/core/tool_calling_llm.py
CHANGED
|
@@ -495,9 +495,19 @@ class ToolCallingLLM:
|
|
|
495
495
|
if future in futures_tool_numbers
|
|
496
496
|
else None
|
|
497
497
|
)
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
498
|
+
|
|
499
|
+
if (
|
|
500
|
+
tool_call_result.result.status
|
|
501
|
+
== ToolResultStatus.APPROVAL_REQUIRED
|
|
502
|
+
):
|
|
503
|
+
with trace_span.start_span(type="tool") as tool_span:
|
|
504
|
+
tool_call_result = self._handle_tool_call_approval(
|
|
505
|
+
tool_call_result=tool_call_result,
|
|
506
|
+
tool_number=tool_number,
|
|
507
|
+
)
|
|
508
|
+
ToolCallingLLM._log_tool_call_result(
|
|
509
|
+
tool_span, tool_call_result
|
|
510
|
+
)
|
|
501
511
|
|
|
502
512
|
tool_calls.append(tool_call_result.as_tool_result_response())
|
|
503
513
|
messages.append(tool_call_result.as_tool_call_message())
|
|
@@ -513,31 +523,28 @@ class ToolCallingLLM:
|
|
|
513
523
|
|
|
514
524
|
raise Exception(f"Too many LLM calls - exceeded max_steps: {i}/{max_steps}")
|
|
515
525
|
|
|
516
|
-
def
|
|
526
|
+
def _directly_invoke_tool_call(
|
|
517
527
|
self,
|
|
518
528
|
tool_name: str,
|
|
519
529
|
tool_params: dict,
|
|
520
530
|
user_approved: bool,
|
|
521
|
-
trace_span=DummySpan(),
|
|
522
531
|
tool_number: Optional[int] = None,
|
|
523
532
|
) -> StructuredToolResult:
|
|
524
|
-
tool_span = trace_span.start_span(name=tool_name, type="tool")
|
|
525
533
|
tool = self.tool_executor.get_tool_by_name(tool_name)
|
|
526
|
-
|
|
534
|
+
if not tool:
|
|
535
|
+
logging.warning(
|
|
536
|
+
f"Skipping tool execution for {tool_name}: args: {tool_params}"
|
|
537
|
+
)
|
|
538
|
+
return StructuredToolResult(
|
|
539
|
+
status=ToolResultStatus.ERROR,
|
|
540
|
+
error=f"Failed to find tool {tool_name}",
|
|
541
|
+
params=tool_params,
|
|
542
|
+
)
|
|
543
|
+
|
|
527
544
|
try:
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
)
|
|
532
|
-
tool_response = StructuredToolResult(
|
|
533
|
-
status=ToolResultStatus.ERROR,
|
|
534
|
-
error=f"Failed to find tool {tool_name}",
|
|
535
|
-
params=tool_params,
|
|
536
|
-
)
|
|
537
|
-
else:
|
|
538
|
-
tool_response = tool.invoke(
|
|
539
|
-
tool_params, tool_number=tool_number, user_approved=user_approved
|
|
540
|
-
)
|
|
545
|
+
tool_response = tool.invoke(
|
|
546
|
+
tool_params, tool_number=tool_number, user_approved=user_approved
|
|
547
|
+
)
|
|
541
548
|
except Exception as e:
|
|
542
549
|
logging.error(
|
|
543
550
|
f"Tool call to {tool_name} failed with an Exception", exc_info=True
|
|
@@ -547,57 +554,16 @@ class ToolCallingLLM:
|
|
|
547
554
|
error=f"Tool call failed: {e}",
|
|
548
555
|
params=tool_params,
|
|
549
556
|
)
|
|
550
|
-
|
|
551
|
-
# Log error to trace span
|
|
552
|
-
tool_span.log(
|
|
553
|
-
input=tool_params, output=str(e), metadata={"status": "ERROR"}
|
|
554
|
-
)
|
|
555
|
-
|
|
556
|
-
tool_span.log(
|
|
557
|
-
input=tool_params,
|
|
558
|
-
output=tool_response.data,
|
|
559
|
-
metadata={
|
|
560
|
-
"status": tool_response.status.value,
|
|
561
|
-
"error": tool_response.error,
|
|
562
|
-
"description": tool.get_parameterized_one_liner(tool_params)
|
|
563
|
-
if tool
|
|
564
|
-
else "",
|
|
565
|
-
"structured_tool_result": tool_response,
|
|
566
|
-
},
|
|
567
|
-
)
|
|
568
|
-
tool_span.end()
|
|
569
|
-
|
|
570
557
|
return tool_response
|
|
571
558
|
|
|
572
|
-
def
|
|
559
|
+
def _get_tool_call_result(
|
|
573
560
|
self,
|
|
574
|
-
|
|
561
|
+
tool_call_id: str,
|
|
562
|
+
tool_name: str,
|
|
563
|
+
tool_arguments: str,
|
|
575
564
|
previous_tool_calls: list[dict],
|
|
576
|
-
|
|
577
|
-
tool_number=None,
|
|
565
|
+
tool_number: Optional[int] = None,
|
|
578
566
|
) -> ToolCallResult:
|
|
579
|
-
# Handle the union type - ChatCompletionMessageToolCall can be either
|
|
580
|
-
# ChatCompletionMessageFunctionToolCall (with 'function' field and type='function')
|
|
581
|
-
# or ChatCompletionMessageCustomToolCall (with 'custom' field and type='custom').
|
|
582
|
-
# We use hasattr to check for the 'function' attribute as it's more flexible
|
|
583
|
-
# and doesn't require importing the specific type.
|
|
584
|
-
if hasattr(tool_to_call, "function"):
|
|
585
|
-
tool_name = tool_to_call.function.name
|
|
586
|
-
tool_arguments = tool_to_call.function.arguments
|
|
587
|
-
else:
|
|
588
|
-
# This is a custom tool call - we don't support these currently
|
|
589
|
-
logging.error(f"Unsupported custom tool call: {tool_to_call}")
|
|
590
|
-
return ToolCallResult(
|
|
591
|
-
tool_call_id=tool_to_call.id,
|
|
592
|
-
tool_name="unknown",
|
|
593
|
-
description="NA",
|
|
594
|
-
result=StructuredToolResult(
|
|
595
|
-
status=ToolResultStatus.ERROR,
|
|
596
|
-
error="Custom tool calls are not supported",
|
|
597
|
-
params=None,
|
|
598
|
-
),
|
|
599
|
-
)
|
|
600
|
-
|
|
601
567
|
tool_params = {}
|
|
602
568
|
try:
|
|
603
569
|
tool_params = json.loads(tool_arguments)
|
|
@@ -606,8 +572,6 @@ class ToolCallingLLM:
|
|
|
606
572
|
f"Failed to parse arguments for tool: {tool_name}. args: {tool_arguments}"
|
|
607
573
|
)
|
|
608
574
|
|
|
609
|
-
tool_call_id = tool_to_call.id
|
|
610
|
-
|
|
611
575
|
tool_response = prevent_overly_repeated_tool_call(
|
|
612
576
|
tool_name=tool_name,
|
|
613
577
|
tool_params=tool_params,
|
|
@@ -615,11 +579,10 @@ class ToolCallingLLM:
|
|
|
615
579
|
)
|
|
616
580
|
|
|
617
581
|
if not tool_response:
|
|
618
|
-
tool_response = self.
|
|
582
|
+
tool_response = self._directly_invoke_tool_call(
|
|
619
583
|
tool_name=tool_name,
|
|
620
584
|
tool_params=tool_params,
|
|
621
585
|
user_approved=False,
|
|
622
|
-
trace_span=trace_span,
|
|
623
586
|
tool_number=tool_number,
|
|
624
587
|
)
|
|
625
588
|
|
|
@@ -635,6 +598,7 @@ class ToolCallingLLM:
|
|
|
635
598
|
)
|
|
636
599
|
|
|
637
600
|
tool = self.tool_executor.get_tool_by_name(tool_name)
|
|
601
|
+
|
|
638
602
|
return ToolCallResult(
|
|
639
603
|
tool_call_id=tool_call_id,
|
|
640
604
|
tool_name=tool_name,
|
|
@@ -642,22 +606,77 @@ class ToolCallingLLM:
|
|
|
642
606
|
result=tool_response,
|
|
643
607
|
)
|
|
644
608
|
|
|
645
|
-
|
|
646
|
-
|
|
609
|
+
@staticmethod
|
|
610
|
+
def _log_tool_call_result(tool_span, tool_call_result: ToolCallResult):
|
|
611
|
+
tool_span.set_attributes(name=tool_call_result.tool_name)
|
|
612
|
+
tool_span.log(
|
|
613
|
+
input=tool_call_result.result.params,
|
|
614
|
+
output=tool_call_result.result.data,
|
|
615
|
+
error=tool_call_result.result.error,
|
|
616
|
+
metadata={
|
|
617
|
+
"status": tool_call_result.result.status,
|
|
618
|
+
"description": tool_call_result.description,
|
|
619
|
+
},
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
def _invoke_llm_tool_call(
|
|
623
|
+
self,
|
|
624
|
+
tool_to_call: ChatCompletionMessageToolCall,
|
|
625
|
+
previous_tool_calls: list[dict],
|
|
626
|
+
trace_span=None,
|
|
627
|
+
tool_number=None,
|
|
628
|
+
) -> ToolCallResult:
|
|
629
|
+
if trace_span is None:
|
|
630
|
+
trace_span = DummySpan()
|
|
631
|
+
with trace_span.start_span(type="tool") as tool_span:
|
|
632
|
+
if not hasattr(tool_to_call, "function"):
|
|
633
|
+
# Handle the union type - ChatCompletionMessageToolCall can be either
|
|
634
|
+
# ChatCompletionMessageFunctionToolCall (with 'function' field and type='function')
|
|
635
|
+
# or ChatCompletionMessageCustomToolCall (with 'custom' field and type='custom').
|
|
636
|
+
# We use hasattr to check for the 'function' attribute as it's more flexible
|
|
637
|
+
# and doesn't require importing the specific type.
|
|
638
|
+
tool_name = "Unknown_Custom_Tool"
|
|
639
|
+
logging.error(f"Unsupported custom tool call: {tool_to_call}")
|
|
640
|
+
tool_call_result = ToolCallResult(
|
|
641
|
+
tool_call_id=tool_to_call.id,
|
|
642
|
+
tool_name=tool_name,
|
|
643
|
+
description="NA",
|
|
644
|
+
result=StructuredToolResult(
|
|
645
|
+
status=ToolResultStatus.ERROR,
|
|
646
|
+
error="Custom tool calls are not supported",
|
|
647
|
+
params=None,
|
|
648
|
+
),
|
|
649
|
+
)
|
|
650
|
+
else:
|
|
651
|
+
tool_name = tool_to_call.function.name
|
|
652
|
+
tool_arguments = tool_to_call.function.arguments
|
|
653
|
+
tool_id = tool_to_call.id
|
|
654
|
+
tool_call_result = self._get_tool_call_result(
|
|
655
|
+
tool_id,
|
|
656
|
+
tool_name,
|
|
657
|
+
tool_arguments,
|
|
658
|
+
previous_tool_calls=previous_tool_calls,
|
|
659
|
+
tool_number=tool_number,
|
|
660
|
+
)
|
|
661
|
+
ToolCallingLLM._log_tool_call_result(tool_span, tool_call_result)
|
|
662
|
+
return tool_call_result
|
|
663
|
+
|
|
664
|
+
def _handle_tool_call_approval(
|
|
665
|
+
self,
|
|
666
|
+
tool_call_result: ToolCallResult,
|
|
667
|
+
tool_number: Optional[int],
|
|
647
668
|
) -> ToolCallResult:
|
|
648
669
|
"""
|
|
649
670
|
Handle approval for a single tool call if required.
|
|
650
671
|
|
|
651
672
|
Args:
|
|
652
673
|
tool_call_result: A single tool call result that may require approval
|
|
674
|
+
tool_number: The tool call number
|
|
653
675
|
|
|
654
676
|
Returns:
|
|
655
677
|
Updated tool call result with approved/denied status
|
|
656
678
|
"""
|
|
657
679
|
|
|
658
|
-
if tool_call_result.result.status != ToolResultStatus.APPROVAL_REQUIRED:
|
|
659
|
-
return tool_call_result
|
|
660
|
-
|
|
661
680
|
# If no approval callback, convert to ERROR because it is assumed the client may not be able to handle approvals
|
|
662
681
|
if not self.approval_callback:
|
|
663
682
|
tool_call_result.result.status = ToolResultStatus.ERROR
|
|
@@ -670,12 +689,10 @@ class ToolCallingLLM:
|
|
|
670
689
|
logging.debug(
|
|
671
690
|
f"User approved command: {tool_call_result.result.invocation}"
|
|
672
691
|
)
|
|
673
|
-
|
|
674
|
-
new_response = self._directly_invoke_tool(
|
|
692
|
+
new_response = self._directly_invoke_tool_call(
|
|
675
693
|
tool_name=tool_call_result.tool_name,
|
|
676
694
|
tool_params=tool_call_result.result.params or {},
|
|
677
695
|
user_approved=True,
|
|
678
|
-
trace_span=DummySpan(),
|
|
679
696
|
tool_number=tool_number,
|
|
680
697
|
)
|
|
681
698
|
tool_call_result.result = new_response
|
holmes/core/tracing.py
CHANGED
|
@@ -101,7 +101,7 @@ class SpanType(Enum):
|
|
|
101
101
|
class DummySpan:
|
|
102
102
|
"""A no-op span implementation for when tracing is disabled."""
|
|
103
103
|
|
|
104
|
-
def start_span(self, name: str, span_type=None, **kwargs):
|
|
104
|
+
def start_span(self, name: Optional[str] = None, span_type=None, **kwargs):
|
|
105
105
|
return DummySpan()
|
|
106
106
|
|
|
107
107
|
def log(self, *args, **kwargs):
|
|
@@ -110,6 +110,11 @@ class DummySpan:
|
|
|
110
110
|
def end(self):
|
|
111
111
|
pass
|
|
112
112
|
|
|
113
|
+
def set_attributes(
|
|
114
|
+
self, name: Optional[str] = None, type=None, span_attributes=None
|
|
115
|
+
) -> None:
|
|
116
|
+
pass
|
|
117
|
+
|
|
113
118
|
def __enter__(self):
|
|
114
119
|
return self
|
|
115
120
|
|
|
@@ -170,7 +170,7 @@ class DatadogGeneralToolset(Toolset):
|
|
|
170
170
|
super().__init__(
|
|
171
171
|
name="datadog/general",
|
|
172
172
|
description="General-purpose Datadog API access for read-only operations including monitors, dashboards, SLOs, incidents, synthetics, and more",
|
|
173
|
-
docs_url="https://
|
|
173
|
+
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/datadog/",
|
|
174
174
|
icon_url="https://imgix.datadoghq.com//img/about/presskit/DDlogo.jpg",
|
|
175
175
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
176
176
|
tools=[
|
|
@@ -178,7 +178,6 @@ class DatadogGeneralToolset(Toolset):
|
|
|
178
178
|
DatadogAPIPostSearch(toolset=self),
|
|
179
179
|
ListDatadogAPIResources(toolset=self),
|
|
180
180
|
],
|
|
181
|
-
experimental=True,
|
|
182
181
|
tags=[ToolsetTag.CORE],
|
|
183
182
|
)
|
|
184
183
|
template_file_path = os.path.abspath(
|
|
@@ -151,11 +151,10 @@ class DatadogLogsToolset(BasePodLoggingToolset):
|
|
|
151
151
|
super().__init__(
|
|
152
152
|
name="datadog/logs",
|
|
153
153
|
description="Toolset for fetching logs from Datadog, including historical data for pods no longer in the cluster",
|
|
154
|
-
docs_url="https://
|
|
154
|
+
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/datadog/",
|
|
155
155
|
icon_url="https://imgix.datadoghq.com//img/about/presskit/DDlogo.jpg",
|
|
156
156
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
157
157
|
tools=[], # Initialize with empty tools first
|
|
158
|
-
experimental=True,
|
|
159
158
|
tags=[ToolsetTag.CORE],
|
|
160
159
|
)
|
|
161
160
|
# Now that parent is initialized and self.name exists, create the tool
|
|
@@ -571,7 +571,7 @@ class DatadogMetricsToolset(Toolset):
|
|
|
571
571
|
super().__init__(
|
|
572
572
|
name="datadog/metrics",
|
|
573
573
|
description="Toolset for fetching metrics and metadata from Datadog, including historical data for pods no longer in the cluster",
|
|
574
|
-
docs_url="https://
|
|
574
|
+
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/datadog/",
|
|
575
575
|
icon_url="https://imgix.datadoghq.com//img/about/presskit/DDlogo.jpg",
|
|
576
576
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
577
577
|
tools=[
|
|
@@ -580,7 +580,6 @@ class DatadogMetricsToolset(Toolset):
|
|
|
580
580
|
QueryMetricsMetadata(toolset=self),
|
|
581
581
|
ListMetricTags(toolset=self),
|
|
582
582
|
],
|
|
583
|
-
experimental=True,
|
|
584
583
|
tags=[ToolsetTag.CORE],
|
|
585
584
|
)
|
|
586
585
|
self._reload_instructions()
|
|
@@ -49,7 +49,7 @@ class DatadogTracesToolset(Toolset):
|
|
|
49
49
|
super().__init__(
|
|
50
50
|
name="datadog/traces",
|
|
51
51
|
description="Toolset for interacting with Datadog APM to fetch and analyze traces",
|
|
52
|
-
docs_url="https://
|
|
52
|
+
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/datadog/",
|
|
53
53
|
icon_url="https://imgix.datadoghq.com//img/about/presskit/DDlogo.jpg",
|
|
54
54
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
55
55
|
tools=[
|
|
@@ -57,7 +57,6 @@ class DatadogTracesToolset(Toolset):
|
|
|
57
57
|
FetchDatadogTraceById(toolset=self),
|
|
58
58
|
FetchDatadogSpansByFilter(toolset=self),
|
|
59
59
|
],
|
|
60
|
-
experimental=True,
|
|
61
60
|
tags=[ToolsetTag.CORE],
|
|
62
61
|
)
|
|
63
62
|
self._reload_instructions()
|
holmes/plugins/toolsets/git.py
CHANGED
|
@@ -33,7 +33,7 @@ class GitToolset(Toolset):
|
|
|
33
33
|
super().__init__(
|
|
34
34
|
name="git",
|
|
35
35
|
description="Runs git commands to read repos and create PRs",
|
|
36
|
-
docs_url="https://
|
|
36
|
+
docs_url="https://holmesgpt.dev/data-sources/builtin-toolsets/github/",
|
|
37
37
|
icon_url="https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg",
|
|
38
38
|
prerequisites=[CallablePrerequisite(callable=self.prerequisites_callable)],
|
|
39
39
|
tools=[
|
|
@@ -66,3 +66,15 @@ def ensure_grafana_uid_or_return_error_result(
|
|
|
66
66
|
)
|
|
67
67
|
else:
|
|
68
68
|
return None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class GrafanaTempoLabelsConfig(BaseModel):
|
|
72
|
+
pod: str = "k8s.pod.name"
|
|
73
|
+
namespace: str = "k8s.namespace.name"
|
|
74
|
+
deployment: str = "k8s.deployment.name"
|
|
75
|
+
node: str = "k8s.node.name"
|
|
76
|
+
service: str = "service.name"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class GrafanaTempoConfig(GrafanaConfig):
|
|
80
|
+
labels: GrafanaTempoLabelsConfig = GrafanaTempoLabelsConfig()
|