holmesgpt 0.13.3a0__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 CHANGED
@@ -1,5 +1,5 @@
1
1
  # This is patched by github actions during release
2
- __version__ = "0.13.3-alpha"
2
+ __version__ = "0.14.0-alpha"
3
3
 
4
4
  # Re-export version functions from version module for backward compatibility
5
5
  from .version import (
@@ -495,9 +495,19 @@ class ToolCallingLLM:
495
495
  if future in futures_tool_numbers
496
496
  else None
497
497
  )
498
- tool_call_result = self.handle_tool_call_approval(
499
- tool_call_result=tool_call_result, tool_number=tool_number
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 _directly_invoke_tool(
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
- tool_response = None
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
- if (not tool) or (tool_params is None):
529
- logging.warning(
530
- f"Skipping tool execution for {tool_name}: args: {tool_params}"
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 _invoke_llm_tool_call(
559
+ def _get_tool_call_result(
573
560
  self,
574
- tool_to_call: ChatCompletionMessageToolCall,
561
+ tool_call_id: str,
562
+ tool_name: str,
563
+ tool_arguments: str,
575
564
  previous_tool_calls: list[dict],
576
- trace_span=DummySpan(),
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._directly_invoke_tool(
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
- def handle_tool_call_approval(
646
- self, tool_call_result: ToolCallResult, tool_number: Optional[int]
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://docs.datadoghq.com/api/latest/",
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://docs.datadoghq.com/api/latest/logs/",
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://docs.datadoghq.com/api/latest/metrics/",
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://docs.datadoghq.com/api/latest/spans/",
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()
@@ -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://docs.github.com/en/rest",
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()