BerriAI/litellm · error · DeepKeepGuardrailAPIError

DeepKeep guardrail API failed

Error message

DeepKeep guardrail API failed: {error}

What it means

Raised by the DeepKeep guardrail hook when its POST to the DeepKeep AI Firewall fails and no fail-open path applies. In _handle_guardrail_request_error it wraps httpx Timeout, RequestError, and HTTPStatusError from raise_for_status() — i.e. any status other than 502/503/504, or those gateway statuses when unreachable_fallback is 'fail_closed' (Timeout always takes is_unreachable=True). The underlying exception text is embedded in the message, and the proxy log line 'DeepKeep guardrail API error: <error>' carries the detail.

Solutions

  1. Read the verbose proxy log line 'DeepKeep guardrail API error: <error>' to get the underlying status code and message.
  2. Verify DEEPKEEP_API_KEY, DEEPKEEP_API_BASE, and firewall_id by curl-ing the firewall endpoint directly with the same X-API-Key header.
  3. Set litellm_params: unreachable_fallback: fail_open in the guardrail config so 502/503/504 outages and timeouts pass traffic through instead of raising.
  4. If the key is valid but a 404 persists, confirm the firewall is deployed in the DeepKeep console and the firewall_id matches.
  5. Temporarily detach the deepkeep guardrail from guardrails-config to confirm the rest of the proxy is healthy.

Example fix

# before (config.yaml)
guardrails:
  - guardrail_name: deepkeep
    litellm_params:
      guardrail: deepkeep
      api_base: https://app.deepkeep.ai/firewall
      unreachable_fallback: fail_closed

# after
guardrails:
  - guardrail_name: deepkeep
    litellm_params:
      guardrail: deepkeep
      api_base: https://app.deepkeep.ai/firewall
      unreachable_fallback: fail_open
Defensive patterns

Strategy: fallback

Validate before calling

import os
missing = [v for v in ('DEEPKEEP_API_KEY', 'DEEPKEEP_API_BASE') if not os.getenv(v)]
if missing:
    raise SystemExit(f'deploy blocked, missing env: {missing}')

Type guard

from litellm.proxy.guardrails.guardrail_hooks.deepkeep.deepkeep import DeepKeepGuardrailAPIError

def is_deepkeep_api_error(exc: BaseException) -> bool:
    return isinstance(exc, DeepKeepGuardrailAPIError)

Try / catch

from litellm.proxy.guardrails.guardrail_hooks.deepkeep.deepkeep import DeepKeepGuardrailAPIError
try:
    result = await guardrail.apply_guardrail(inputs, request_data, 'request', logging_obj)
except DeepKeepGuardrailAPIError as e:
    logger.warning('DeepKeep unreachable, failing open: %s', e)
    result = dict(inputs)  # proceed unmoderated (or re-raise in strict mode)

Prevention

When it happens

Trigger: DeepKeep returns 401/403 (invalid X-API-Key), 404 (wrong firewall_id or api_base path), 429, or 5xx while unreachable_fallback='fail_closed'; the firewall hostname fails DNS resolution; the request exceeds the httpx client timeout; the response body is not valid JSON after a 200.

Common situations: Expired or rotated DeepKeep API key; typo in DEEPKEEP_API_BASE; firewall_id that does not exist in the DeepKeep tenant; a DeepKeep outage with fail-open not configured, so every proxied LLM call fails while the guardrail endpoint is down.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/958edd8c5cecd733. Report an issue: GitHub.

Appendix: source

Thrown at litellm/proxy/guardrails/guardrail_hooks/deepkeep/deepkeep.py:231

        self,
        error: Exception,
        inputs: GenericGuardrailAPIInputs,
        input_type: Literal["request", "response"],
        logging_obj: Optional["LiteLLMLoggingObj"],
        is_unreachable: bool = True,
    ) -> GenericGuardrailAPIInputs:
        """Handle errors from the DeepKeep API with fail-open/fail-closed logic."""
        if is_unreachable and self.unreachable_fallback == "fail_open":
            http_status_code: Final = getattr(getattr(error, "response", None), "status_code", None)
            return self._fail_open_passthrough(
                inputs=inputs,
                input_type=input_type,
                logging_obj=logging_obj,
                error=error,
                **({"http_status_code": http_status_code} if http_status_code else {}),
            )
        verbose_proxy_logger.error("DeepKeep guardrail API error: %s", str(error))
        raise DeepKeepGuardrailAPIError(f"DeepKeep guardrail API failed: {error}")

    @staticmethod
    def _build_return_inputs(
        *,
        response_json: dict[str, Any],
        texts: list,
        images: Any | None,
        tools: Any | None,
        tool_calls: Any | None,
        structured_messages: Any | None,
    ) -> GenericGuardrailAPIInputs:
        """Merge original inputs with any guardrail-modified values from the API response.

        Presence is checked with ``is not None`` (not truthiness) so that an
        intentional empty-list replacement such as ``texts: []`` or
        ``tool_calls: []`` is honoured and forwarded downstream rather than
        silently discarded in favour of the original content.
        """

View on GitHub (pinned to 77b7c6c40c)