From 902465045951946bbe57dc70114742f7b0980b94 Mon Sep 17 00:00:00 2001 From: ykd007 Date: Thu, 14 May 2026 14:39:43 +0530 Subject: [PATCH 1/3] fix(prometheus): add Thanos compatibility mode via thanos_compatible flag --- .../prometheus_provider.py | 59 +++++++++++++++---- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/keep/providers/prometheus_provider/prometheus_provider.py b/keep/providers/prometheus_provider/prometheus_provider.py index 7a9e2abf21..902d200982 100644 --- a/keep/providers/prometheus_provider/prometheus_provider.py +++ b/keep/providers/prometheus_provider/prometheus_provider.py @@ -49,6 +49,14 @@ class PrometheusProviderAuthConfig: }, default=True, ) + thanos_compatible: bool = dataclasses.field( + metadata={ + "description": "Enable Thanos compatibility mode", + "hint": "Set to true when connecting to a Thanos Querier — disables the /api/v1/alerts endpoint which Thanos does not support", + "sensitive": False, + }, + default=False, + ) class PrometheusProvider(BaseProvider, ProviderHealthMixin): @@ -112,7 +120,11 @@ def validate_config(self): def validate_scopes(self) -> dict[str, bool | str]: validated_scopes = {"connectivity": True} try: - self._get_alerts() + if self.authentication_config.thanos_compatible: + # Thanos doesn't support /api/v1/alerts; verify connectivity via instant query instead + self._query("up") + else: + self._get_alerts() except Exception as e: validated_scopes["connectivity"] = str(e) return validated_scopes @@ -156,15 +168,42 @@ def _get_alerts(self) -> list[AlertDto]: auth = HTTPBasicAuth( self.authentication_config.username, self.authentication_config.password ) - response = requests.get( - f"{self.authentication_config.url}/api/v1/alerts", - auth=auth, - verify=self.authentication_config.verify, - ) - response.raise_for_status() - if not response.ok: - return [] - alerts_data = response.json().get("data", {}) + + if self.authentication_config.thanos_compatible: + # Thanos does not expose /api/v1/alerts (Alertmanager endpoint). + # Fall back to querying active ALERTS{} via PromQL which works on both + # Thanos Querier and regular Prometheus. + response = requests.get( + f"{self.authentication_config.url}/api/v1/query", + params={"query": "ALERTS{alertstate='firing'}"}, + auth=auth, + verify=self.authentication_config.verify, + ) + response.raise_for_status() + results = response.json().get("data", {}).get("result", []) + # Convert instant-query vector results into alert-shaped dicts + alerts_data = {"alerts": []} + for r in results: + metric = r.get("metric", {}) + alerts_data["alerts"].append( + { + "labels": metric, + "annotations": {}, + "state": metric.get("alertstate", "firing"), + "fingerprint": None, + } + ) + else: + response = requests.get( + f"{self.authentication_config.url}/api/v1/alerts", + auth=auth, + verify=self.authentication_config.verify, + ) + response.raise_for_status() + if not response.ok: + return [] + alerts_data = response.json().get("data", {}) + alert_dtos = self._format_alert(alerts_data) return alert_dtos From b929a8e6c3b772bcfd902578ecab22616aa246b5 Mon Sep 17 00:00:00 2001 From: ykd007 Date: Thu, 14 May 2026 14:41:18 +0530 Subject: [PATCH 2/3] =?UTF-8?q?fix(prometheus):=20use=20/api/v1/rules=20fo?= =?UTF-8?q?r=20Thanos=20mode=20=E2=80=94=20preserves=20annotations,=20fing?= =?UTF-8?q?erprints,=20pending=20alerts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../prometheus_provider.py | 62 +++++++++++++------ 1 file changed, 43 insertions(+), 19 deletions(-) diff --git a/keep/providers/prometheus_provider/prometheus_provider.py b/keep/providers/prometheus_provider/prometheus_provider.py index 902d200982..338c57d44d 100644 --- a/keep/providers/prometheus_provider/prometheus_provider.py +++ b/keep/providers/prometheus_provider/prometheus_provider.py @@ -121,8 +121,22 @@ def validate_scopes(self) -> dict[str, bool | str]: validated_scopes = {"connectivity": True} try: if self.authentication_config.thanos_compatible: - # Thanos doesn't support /api/v1/alerts; verify connectivity via instant query instead - self._query("up") + # Thanos doesn't support /api/v1/alerts; verify via /api/v1/rules instead + base_url = str(self.authentication_config.url).rstrip("/") + requests.get( + f"{base_url}/api/v1/rules", + params={"type": "alert"}, + auth=( + HTTPBasicAuth( + self.authentication_config.username, + self.authentication_config.password, + ) + if self.authentication_config.username + and self.authentication_config.password + else None + ), + verify=self.authentication_config.verify, + ).raise_for_status() else: self._get_alerts() except Exception as e: @@ -170,29 +184,39 @@ def _get_alerts(self) -> list[AlertDto]: ) if self.authentication_config.thanos_compatible: - # Thanos does not expose /api/v1/alerts (Alertmanager endpoint). - # Fall back to querying active ALERTS{} via PromQL which works on both - # Thanos Querier and regular Prometheus. + # Thanos Querier does not expose /api/v1/alerts (that endpoint only exists + # on Prometheus's rules evaluator). Use /api/v1/rules?type=alert instead — + # it is supported by Thanos and returns annotations, state, and activeAt. + import hashlib + import json as _json + + base_url = str(self.authentication_config.url).rstrip("/") response = requests.get( - f"{self.authentication_config.url}/api/v1/query", - params={"query": "ALERTS{alertstate='firing'}"}, + f"{base_url}/api/v1/rules", + params={"type": "alert"}, auth=auth, verify=self.authentication_config.verify, ) response.raise_for_status() - results = response.json().get("data", {}).get("result", []) - # Convert instant-query vector results into alert-shaped dicts + rule_groups = response.json().get("data", {}).get("groups", []) alerts_data = {"alerts": []} - for r in results: - metric = r.get("metric", {}) - alerts_data["alerts"].append( - { - "labels": metric, - "annotations": {}, - "state": metric.get("alertstate", "firing"), - "fingerprint": None, - } - ) + for group in rule_groups: + for rule in group.get("rules", []): + if rule.get("type") != "alerting": + continue + for alert in rule.get("alerts", []): + labels = alert.get("labels", {}) + fp_src = _json.dumps(labels, sort_keys=True) + fingerprint = hashlib.md5(fp_src.encode()).hexdigest() + alerts_data["alerts"].append( + { + "labels": labels, + "annotations": alert.get("annotations", {}), + "state": alert.get("state", "firing"), + "activeAt": alert.get("activeAt"), + "fingerprint": fingerprint, + } + ) else: response = requests.get( f"{self.authentication_config.url}/api/v1/alerts", From 629692930c6886f348cd437c0550d1846ed96186 Mon Sep 17 00:00:00 2001 From: Yash Dhawan Date: Thu, 28 May 2026 18:40:09 +0530 Subject: [PATCH 3/3] regenerate provider docs snippets after rebase onto main --- .../snippets/providers/anthropic-snippet-autogenerated.mdx | 7 +++++-- .../providers/prometheus-snippet-autogenerated.mdx | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/snippets/providers/anthropic-snippet-autogenerated.mdx b/docs/snippets/providers/anthropic-snippet-autogenerated.mdx index 297b766ecc..d5a63f60b0 100644 --- a/docs/snippets/providers/anthropic-snippet-autogenerated.mdx +++ b/docs/snippets/providers/anthropic-snippet-autogenerated.mdx @@ -1,9 +1,11 @@ -{/* This snippet is automatically generated using scripts/docs_render_provider_snippets.py +{/* This snippet is automatically generated using scripts/docs_render_provider_snippets.py Do not edit it manually, as it will be overwritten */} ## Authentication This provider requires authentication. - **api_key**: Anthropic API Key (required: True, sensitive: True) +- **model**: Claude model to use (required: False, sensitive: False) +- **system_prompt**: System prompt that sets Claude's role for all requests in this provider. (required: False, sensitive: False) ## In workflows @@ -19,8 +21,9 @@ steps: config: "{{ provider.my_provider_name }}" with: prompt: {value} # The prompt to query the model with. - model: {value} # The model to query. + model: {value} # The model to query (overrides provider config). max_tokens: {value} # The maximum number of tokens to generate. + system_prompt: {value} # System prompt override for this call. structured_output_format: {value} # The structured output format to use. ``` diff --git a/docs/snippets/providers/prometheus-snippet-autogenerated.mdx b/docs/snippets/providers/prometheus-snippet-autogenerated.mdx index c574844c38..099e172e81 100644 --- a/docs/snippets/providers/prometheus-snippet-autogenerated.mdx +++ b/docs/snippets/providers/prometheus-snippet-autogenerated.mdx @@ -7,9 +7,10 @@ This provider requires authentication. - **username**: Prometheus username (required: False, sensitive: False) - **password**: Prometheus password (required: False, sensitive: True) - **verify**: Verify SSL certificates (required: False, sensitive: False) +- **thanos_compatible**: Enable Thanos compatibility mode (required: False, sensitive: False) Certain scopes may be required to perform specific actions or queries via the provider. Below is a summary of relevant scopes and their use cases: -- **connectivity**: Connectivity Test (mandatory) +- **connectivity**: Connectivity Test (mandatory) @@ -25,7 +26,7 @@ steps: provider: prometheus config: "{{ provider.my_provider_name }}" with: - query: {value} + query: {value} ```