infiniflow/ragflow · error · ValueError

KEENABLE_API_URL must be an https:// URL with a host, got {b

Error message

KEENABLE_API_URL must be an https:// URL with a host, got {base!r}

What it means

ValueError raised by _base_url() in the Keenable tool when the KEENABLE_API_URL environment variable is not a usable HTTPS URL. The helper strips trailing '/', parses the URL, and only accepts https:// any host, or http:// strictly for localhost/127.0.0.1/::1 (local dev). Anything else — http:// to a remote host, missing scheme, no hostname — raises.

Source

Thrown at agent/tools/keenable.py:38

from urllib.parse import urlsplit

import requests

from agent.tools.base import ToolBase, ToolMeta, ToolParamBase
from common.connection_utils import timeout


def _base_url() -> str:
    """Resolve the Keenable API base URL from ``KEENABLE_API_URL`` (HTTPS enforced)."""
    base = (os.environ.get("KEENABLE_API_URL") or "https://api.keenable.ai").rstrip("/")
    parsed = urlsplit(base)
    if parsed.hostname:
        if parsed.scheme == "https":
            return base
        # Permit plain http only against a loopback host (local dev).
        if parsed.scheme == "http" and parsed.hostname in {"localhost", "127.0.0.1", "::1"}:
            return base
    raise ValueError(f"KEENABLE_API_URL must be an https:// URL with a host, got {base!r}")


def _request(method: str, public_path: str, keyed_path: str, api_key: str, *, params=None, json=None, timeout_s: int = 30):
    """Call the keyed endpoint with X-API-Key when a key is set, else the keyless public one."""
    api_key = (api_key or "").strip()
    headers = {
        "User-Agent": "keenable-ragflow",
        # Attribution header the Keenable backend segments traffic by.
        "X-Keenable-Title": "RAGFlow",
    }
    if api_key:
        path = keyed_path
        headers["X-API-Key"] = api_key
    else:
        path = public_path
    resp = requests.request(method, f"{_base_url()}{path}", headers=headers, params=params, json=json, timeout=timeout_s)
    resp.raise_for_status()
    return resp.json()

View on GitHub (pinned to 554fb1133a)

Solutions

  1. Set KEENABLE_API_URL to a full https URL with a host, e.g. https://api.keenable.ai.
  2. If you truly need plain http for local testing, the host must be exactly localhost, 127.0.0.1, or ::1.
  3. If the URL sits behind an http-only proxy, put a TLS terminator (nginx/caddy) in front and point the variable at the https endpoint.
  4. Unset the variable entirely to fall back to the default https://api.keenable.ai.

Example fix

# before
export KEENABLE_API_URL=http://api.keenable.ai   # ValueError

# after
export KEENABLE_API_URL=https://api.keenable.ai
# local dev only:
export KEENABLE_API_URL=http://127.0.0.1:8080
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlsplit
def valid_keenable_url(v: str) -> bool:
    p = urlsplit((v or "").rstrip("/"))
    return (p.scheme == "https" and bool(p.hostname)) or \
           (p.scheme == "http" and p.hostname in {"localhost", "127.0.0.1", "::1"})

Try / catch

try:
        keenable_search._invoke(query=q)
    except ValueError as e:
        if "KEENABLE_API_URL" in str(e):
            fix_environment(); restart_service()
        raise

Prevention

When it happens

Trigger: Setting KEENABLE_API_URL=http://api.keenable.ai (plain http off-loopback), KEENABLE_API_URL=api.keenable.ai (no scheme, parsed.hostname is None), an empty string after rstrip, or a URL like 'https://<spaces>' that fails host parsing.

Common situations: Operators copying an internal http:// endpoint URL from staging into production env config; docker-compose env files missing the scheme; typos like 'https:/api.keenable.ai' (single slash yields no netloc); proxies terminating TLS prompting someone to 'downgrade' to http.

Related errors


AI-assisted analysis of infiniflow/ragflow@554fb1133a (2026-08-15). Data as JSON: /api/errors/6f1d0ede90b71cf6. Report an issue: GitHub.