holmesgpt 0.11.5__py3-none-any.whl → 0.12.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.

Files changed (41) hide show
  1. holmes/__init__.py +1 -1
  2. holmes/common/env_vars.py +8 -4
  3. holmes/config.py +54 -14
  4. holmes/core/investigation_structured_output.py +7 -0
  5. holmes/core/llm.py +14 -4
  6. holmes/core/models.py +24 -0
  7. holmes/core/tool_calling_llm.py +48 -6
  8. holmes/core/tools.py +7 -4
  9. holmes/core/toolset_manager.py +24 -5
  10. holmes/core/tracing.py +224 -0
  11. holmes/interactive.py +761 -44
  12. holmes/main.py +59 -127
  13. holmes/plugins/prompts/_fetch_logs.jinja2 +4 -0
  14. holmes/plugins/prompts/kubernetes_workload_ask.jinja2 +2 -10
  15. holmes/plugins/toolsets/__init__.py +10 -2
  16. holmes/plugins/toolsets/azure_sql/apis/azure_sql_api.py +2 -1
  17. holmes/plugins/toolsets/coralogix/toolset_coralogix_logs.py +3 -0
  18. holmes/plugins/toolsets/datadog/datadog_api.py +161 -0
  19. holmes/plugins/toolsets/datadog/datadog_metrics_instructions.jinja2 +26 -0
  20. holmes/plugins/toolsets/datadog/datadog_traces_formatter.py +310 -0
  21. holmes/plugins/toolsets/datadog/instructions_datadog_traces.jinja2 +51 -0
  22. holmes/plugins/toolsets/datadog/toolset_datadog_logs.py +267 -0
  23. holmes/plugins/toolsets/datadog/toolset_datadog_metrics.py +488 -0
  24. holmes/plugins/toolsets/datadog/toolset_datadog_traces.py +689 -0
  25. holmes/plugins/toolsets/grafana/toolset_grafana_loki.py +3 -0
  26. holmes/plugins/toolsets/internet/internet.py +1 -1
  27. holmes/plugins/toolsets/logging_utils/logging_api.py +9 -3
  28. holmes/plugins/toolsets/opensearch/opensearch_logs.py +3 -0
  29. holmes/plugins/toolsets/utils.py +6 -2
  30. holmes/utils/cache.py +4 -4
  31. holmes/utils/console/consts.py +2 -0
  32. holmes/utils/console/logging.py +95 -0
  33. holmes/utils/console/result.py +37 -0
  34. holmes/utils/robusta.py +2 -3
  35. {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0a0.dist-info}/METADATA +3 -4
  36. {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0a0.dist-info}/RECORD +39 -30
  37. {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0a0.dist-info}/WHEEL +1 -1
  38. holmes/__init__.py.bak +0 -76
  39. holmes/plugins/toolsets/datadog.py +0 -153
  40. {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0a0.dist-info}/LICENSE.txt +0 -0
  41. {holmesgpt-0.11.5.dist-info → holmesgpt-0.12.0a0.dist-info}/entry_points.txt +0 -0
holmes/core/tracing.py ADDED
@@ -0,0 +1,224 @@
1
+ import os
2
+ import logging
3
+ from typing import Optional, Any, Union
4
+ from enum import Enum
5
+
6
+ try:
7
+ import braintrust
8
+ from braintrust import Span, SpanTypeAttribute
9
+
10
+ BRAINTRUST_AVAILABLE = True
11
+ except ImportError:
12
+ BRAINTRUST_AVAILABLE = False
13
+ # Type aliases for when braintrust is not available
14
+ from typing import TYPE_CHECKING
15
+
16
+ if TYPE_CHECKING:
17
+ from braintrust import Span, SpanTypeAttribute
18
+ else:
19
+ Span = Any
20
+ SpanTypeAttribute = Any
21
+
22
+
23
+ def _is_noop_span(span) -> bool:
24
+ """Check if a span is a Braintrust NoopSpan (inactive span)."""
25
+ return span is None or str(type(span)).endswith("_NoopSpan'>")
26
+
27
+
28
+ class SpanType(Enum):
29
+ """Standard span types for tracing categorization."""
30
+
31
+ LLM = "llm"
32
+ TOOL = "tool"
33
+ TASK = "task"
34
+ SCORE = "score"
35
+
36
+
37
+ class DummySpan:
38
+ """A no-op span implementation for when tracing is disabled."""
39
+
40
+ def start_span(self, name: str, span_type: Optional[SpanType] = None, **kwargs):
41
+ return DummySpan()
42
+
43
+ def log(self, *args, **kwargs):
44
+ pass
45
+
46
+ def end(self):
47
+ pass
48
+
49
+ def __enter__(self):
50
+ return self
51
+
52
+ def __exit__(self, exc_type, exc_val, exc_tb):
53
+ pass
54
+
55
+
56
+ class DummyTracer:
57
+ """A no-op tracer implementation for when tracing is disabled."""
58
+
59
+ def start_experiment(self, experiment_name=None, metadata=None):
60
+ """No-op experiment creation."""
61
+ return None
62
+
63
+ def start_trace(self, name: str, span_type=None):
64
+ """No-op trace creation."""
65
+ return DummySpan()
66
+
67
+ def get_trace_url(self):
68
+ return None
69
+
70
+ def wrap_llm(self, llm_module):
71
+ """No-op LLM wrapping for dummy tracer."""
72
+ return llm_module
73
+
74
+
75
+ class BraintrustTracer:
76
+ """Braintrust implementation of tracing."""
77
+
78
+ def __init__(self, project: str = "HolmesGPT-CLI"):
79
+ if not BRAINTRUST_AVAILABLE:
80
+ raise ImportError("braintrust package is required for BraintrustTracer")
81
+
82
+ self.project = project
83
+
84
+ def start_experiment(
85
+ self, experiment_name: Optional[str] = None, metadata: Optional[dict] = None
86
+ ):
87
+ """Create and start a new Braintrust experiment.
88
+
89
+ Args:
90
+ experiment_name: Name for the experiment, auto-generated if None
91
+ metadata: Metadata to attach to experiment
92
+
93
+ Returns:
94
+ Braintrust experiment object
95
+ """
96
+ if not os.environ.get("BRAINTRUST_API_KEY"):
97
+ return None
98
+
99
+ return braintrust.init(
100
+ project=self.project,
101
+ experiment=experiment_name,
102
+ metadata=metadata or {},
103
+ update=True,
104
+ )
105
+
106
+ def start_trace(
107
+ self, name: str, span_type: Optional[SpanType] = None
108
+ ) -> Union[Span, DummySpan]:
109
+ """Start a trace span in current Braintrust context.
110
+
111
+ Args:
112
+ name: Span name
113
+ span_type: Type of span for categorization
114
+
115
+ Returns:
116
+ Span that can be used as context manager
117
+ """
118
+ if not os.environ.get("BRAINTRUST_API_KEY"):
119
+ return DummySpan()
120
+
121
+ # Add span type to kwargs if provided
122
+ kwargs = {}
123
+ if span_type:
124
+ kwargs["type"] = getattr(SpanTypeAttribute, span_type.name)
125
+
126
+ # Use current Braintrust context (experiment or parent span)
127
+ current_span = braintrust.current_span()
128
+ if not _is_noop_span(current_span):
129
+ return current_span.start_span(name=name, **kwargs)
130
+
131
+ # Fallback to current experiment
132
+ current_experiment = braintrust.current_experiment()
133
+ if current_experiment:
134
+ return current_experiment.start_span(name=name, **kwargs)
135
+
136
+ return DummySpan()
137
+
138
+ def get_trace_url(self) -> Optional[str]:
139
+ """Get URL to view the trace in Braintrust."""
140
+ logging.info("Getting trace URL for Braintrust")
141
+ if not os.environ.get("BRAINTRUST_API_KEY"):
142
+ logging.warning("BRAINTRUST_API_KEY not set, cannot get trace URL")
143
+ return None
144
+
145
+ # Get current experiment from Braintrust context
146
+ current_experiment = braintrust.current_experiment()
147
+ if not current_experiment:
148
+ logging.warning("No current experiment found in Braintrust context")
149
+ return None
150
+
151
+ experiment_name = getattr(current_experiment, "name", None)
152
+ if not experiment_name:
153
+ logging.warning("No experiment name found in current Braintrust context")
154
+ return None
155
+
156
+ current_span = braintrust.current_span()
157
+ if not _is_noop_span(current_span):
158
+ span_id = getattr(current_span, "span_id", None)
159
+ id_attr = getattr(current_span, "id", None)
160
+ if span_id and id_attr:
161
+ return f"https://www.braintrust.dev/app/robustadev/p/{self.project}/experiments/{experiment_name}?c=&tg=false&r={id_attr}&s={span_id}"
162
+ else:
163
+ logging.warning("No active span found in Braintrust context")
164
+
165
+ return f"https://www.braintrust.dev/app/robustadev/p/{self.project}/experiments/{experiment_name}"
166
+
167
+ def wrap_llm(self, llm_module):
168
+ """Wrap LiteLLM with Braintrust tracing if in active context, otherwise return unwrapped."""
169
+ if not BRAINTRUST_AVAILABLE or not os.environ.get("BRAINTRUST_API_KEY"):
170
+ return llm_module
171
+
172
+ from braintrust.oai import ChatCompletionWrapper
173
+
174
+ class WrappedLiteLLM:
175
+ def __init__(self, original_module):
176
+ self._original_module = original_module
177
+ self._chat_wrapper = ChatCompletionWrapper(
178
+ create_fn=original_module.completion,
179
+ acreate_fn=None,
180
+ )
181
+
182
+ def completion(self, **kwargs):
183
+ return self._chat_wrapper.create(**kwargs)
184
+
185
+ def __getattr__(self, name):
186
+ return getattr(self._original_module, name)
187
+
188
+ return WrappedLiteLLM(llm_module)
189
+
190
+
191
+ class TracingFactory:
192
+ """Factory for creating tracer instances."""
193
+
194
+ @staticmethod
195
+ def create_tracer(trace_type: Optional[str], project: str):
196
+ """Create a tracer instance based on the trace type.
197
+
198
+ Args:
199
+ trace_type: Type of tracing ('braintrust', etc.)
200
+ project: Project name for tracing
201
+
202
+ Returns:
203
+ Tracer instance if tracing enabled, DummySpan if disabled
204
+ """
205
+ if not trace_type:
206
+ return DummyTracer()
207
+
208
+ if trace_type.lower() == "braintrust":
209
+ if not BRAINTRUST_AVAILABLE:
210
+ logging.warning(
211
+ "Braintrust tracing requested but braintrust package not available"
212
+ )
213
+ return DummyTracer()
214
+
215
+ if not os.environ.get("BRAINTRUST_API_KEY"):
216
+ logging.warning(
217
+ "Braintrust tracing requested but BRAINTRUST_API_KEY not set"
218
+ )
219
+ return DummyTracer()
220
+
221
+ return BraintrustTracer(project=project)
222
+
223
+ logging.warning(f"Unknown trace type: {trace_type}")
224
+ return DummyTracer()