Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/server_configuration/environmental_variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Below is a list of some environmental values that require more in-depth explanat

Environmental variable | Description | Default
-----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------
`ROBOFLOW_REGION` | Roboflow region to run against: `us` or `eu`. Selects the default API/app hosts across `inference`, `inference-cli`, `inference-sdk` and `inference-models` (e.g. `eu` points the API at `https://api.roboflow.eu`). Explicit URL variables such as `API_BASE_URL` or `ROBOFLOW_API_HOST` always take precedence. | `us`
`ROBOFLOW_ENVIRONMENT` | Roboflow environment to run against: `prod` or `staging`. Combines with `ROBOFLOW_REGION` to pick default hosts (e.g. `eu` + `staging` points the API at `https://api.roboflow-eu.one`). Takes precedence over the legacy `PROJECT` variable (`roboflow-platform` vs `roboflow-staging`). | `prod`
`ONNXRUNTIME_EXECUTION_PROVIDERS` | List of execution providers in priority order, warning message will be displayed if provider is not supported on user platform | See [here](https://github.com/roboflow/inference/blob/main/inference/core/env.py#L262)
`SAM2_MAX_EMBEDDING_CACHE_SIZE` | The number of sam2 embeddings that will be held in memory. The embeddings will be held in gpu memory. Each embedding takes 16777216 bytes. | 100
`SAM2_MAX_LOGITS_CACHE_SIZE` | The number of sam2 logits that will be held in memory. The the logits will be in cpu memory. Each logit takes 262144 bytes. | 1000
Expand Down
15 changes: 9 additions & 6 deletions inference/core/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
InferenceModelsStackMissing,
ModelDependencyMissing,
)
from inference_sdk.regions import get_roboflow_region, resolve_roboflow_service_url

load_dotenv(os.getcwd() + "/.env")

Expand All @@ -39,6 +40,9 @@
# The project name, default is "roboflow-platform"
PROJECT = os.getenv("PROJECT", "roboflow-platform")

# Selected Roboflow region ("us" or "eu"), default is "us"
ROBOFLOW_REGION = get_roboflow_region()

# Allow numpy input, default is False
ALLOW_NUMPY_INPUT = str2bool(os.getenv("ALLOW_NUMPY_INPUT", False))
ALLOW_URL_INPUT = str2bool(os.getenv("ALLOW_URL_INPUT", True))
Expand Down Expand Up @@ -88,11 +92,7 @@
# Base URL for the API
API_BASE_URL = os.getenv(
"API_BASE_URL",
(
"https://api.roboflow.com"
if PROJECT == "roboflow-platform"
else "https://api.roboflow.one"
),
resolve_roboflow_service_url("api", region=ROBOFLOW_REGION, project=PROJECT),
)
API_PROXY_BASE_URL = os.getenv("API_PROXY_BASE_URL", API_BASE_URL)

Expand Down Expand Up @@ -689,7 +689,10 @@ def _reset_offline_mode_lock_after_fork() -> None:

# Enable the builder, default is False
ENABLE_BUILDER = str2bool(os.getenv("ENABLE_BUILDER", False))
BUILDER_ORIGIN = os.getenv("BUILDER_ORIGIN", "https://app.roboflow.com")
BUILDER_ORIGIN = os.getenv(
"BUILDER_ORIGIN",
resolve_roboflow_service_url("app", region=ROBOFLOW_REGION, project=PROJECT),
)

# Enable jupyter notebook server route, default is False
NOTEBOOK_ENABLED = str2bool(os.getenv("NOTEBOOK_ENABLED", False))
Expand Down
10 changes: 4 additions & 6 deletions inference_cli/lib/enterprise/inference_compiler/constants.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import os

from inference_cli.lib.env import PROJECT
from inference_sdk.regions import resolve_roboflow_service_url

PROD_ENVIRONMENT_NAME = "prod"
ROBOFLOW_ENVIRONMENT = os.getenv("ROBOFLOW_ENVIRONMENT", PROD_ENVIRONMENT_NAME)
ROBOFLOW_API_HOST = os.getenv(
"ROBOFLOW_API_HOST",
(
"https://api.roboflow.com"
if ROBOFLOW_ENVIRONMENT == PROD_ENVIRONMENT_NAME
else "https://api.roboflow.one"
),
"ROBOFLOW_API_HOST", resolve_roboflow_service_url("api", project=PROJECT)
)
ROBOFLOW_API_KEY = os.getenv("ROBOFLOW_API_KEY", None)
HTTP_CODES_TO_RETRY = {408, 429, 500, 502, 503, 504}
Expand Down
9 changes: 4 additions & 5 deletions inference_cli/lib/env.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import os

from inference_sdk.regions import get_roboflow_region, resolve_roboflow_service_url

CLI_LOG_LEVEL = os.getenv("CLI_LOG_LEVEL", "INFO")
ROBOFLOW_API_KEY = os.getenv("ROBOFLOW_API_KEY")
PROJECT = os.getenv("PROJECT", "roboflow-platform")
ROBOFLOW_REGION = get_roboflow_region()
API_BASE_URL = os.getenv(
"API_BASE_URL",
(
"https://api.roboflow.com"
if PROJECT == "roboflow-platform"
else "https://api.roboflow.one"
),
resolve_roboflow_service_url("api", region=ROBOFLOW_REGION, project=PROJECT),
)
26 changes: 21 additions & 5 deletions inference_models/inference_models/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,29 @@
)
)
ROBOFLOW_ENVIRONMENT = os.getenv("ROBOFLOW_ENVIRONMENT", "prod")
# Region / environment matrix mirroring inference_sdk.regions - inference-models
# is a standalone distribution and cannot depend on inference_sdk.
_ROBOFLOW_API_HOSTS = {
("us", "prod"): "https://api.roboflow.com",
("us", "staging"): "https://api.roboflow.one",
("eu", "prod"): "https://api.roboflow.eu",
("eu", "staging"): "https://api.roboflow-eu.one",
}
ROBOFLOW_REGION = os.getenv("ROBOFLOW_REGION", "us").strip().lower()
if ROBOFLOW_REGION not in {region for region, _ in _ROBOFLOW_API_HOSTS}:
warnings.warn(
f"Unknown ROBOFLOW_REGION {ROBOFLOW_REGION!r} - falling back to 'us'. "
"Supported regions: eu, us.",
)
ROBOFLOW_REGION = "us"
ROBOFLOW_API_HOST = os.getenv(
"ROBOFLOW_API_HOST",
(
"https://api.roboflow.com"
if ROBOFLOW_ENVIRONMENT.lower() == "prod"
else "https://api.roboflow.one"
),
_ROBOFLOW_API_HOSTS[
(
ROBOFLOW_REGION,
"prod" if ROBOFLOW_ENVIRONMENT.lower() == "prod" else "staging",
)
],
)
_legacy_license_server = os.getenv("LICENSE_SERVER")
SECURE_GATEWAY = os.getenv("SECURE_GATEWAY") or _legacy_license_server or None
Expand Down
77 changes: 77 additions & 0 deletions inference_models/tests/unit_tests/test_configuration.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import importlib
import os
from typing import Callable

import pytest

import inference_models.configuration
from inference_models.configuration import (
DEFAULT_RFDETR_PIPELINE_DEPTH,
MAX_RFDETR_PIPELINE_DEPTH,
Expand All @@ -8,6 +13,33 @@
)
from inference_models.errors import InvalidEnvVariable

REGION_ENVIRONMENT_KEYS = [
"ROBOFLOW_REGION",
"ROBOFLOW_ENVIRONMENT",
"ROBOFLOW_API_HOST",
]


@pytest.fixture
def reload_configuration() -> Callable[..., object]:
saved_environment = {
key: os.environ.pop(key) for key in REGION_ENVIRONMENT_KEYS if key in os.environ
}

def _reload(**environment: str) -> object:
for key in REGION_ENVIRONMENT_KEYS:
os.environ.pop(key, None)
os.environ.update(environment)
return importlib.reload(inference_models.configuration)

try:
yield _reload
finally:
for key in REGION_ENVIRONMENT_KEYS:
os.environ.pop(key, None)
os.environ.update(saved_environment)
importlib.reload(inference_models.configuration)


def test_parse_rfdetr_pipeline_depth_uses_default_when_env_missing() -> None:
assert parse_rfdetr_pipeline_depth(None) == DEFAULT_RFDETR_PIPELINE_DEPTH
Expand Down Expand Up @@ -48,3 +80,48 @@ def test_get_rfdetr_pipeline_depth_rejects_invalid_environment(
monkeypatch.setenv("RFDETR_PIPELINE_DEPTH", value)
with pytest.raises(InvalidEnvVariable):
get_rfdetr_pipeline_depth()


def test_roboflow_api_host_defaults_to_us_production(reload_configuration) -> None:
configuration = reload_configuration()
assert configuration.ROBOFLOW_REGION == "us"
assert configuration.ROBOFLOW_API_HOST == "https://api.roboflow.com"


@pytest.mark.parametrize(
"region, environment, expected_api_host",
[
("us", "prod", "https://api.roboflow.com"),
("us", "staging", "https://api.roboflow.one"),
("eu", "prod", "https://api.roboflow.eu"),
("eu", "staging", "https://api.roboflow-eu.one"),
],
)
def test_roboflow_api_host_follows_region_and_environment_matrix(
reload_configuration,
region: str,
environment: str,
expected_api_host: str,
) -> None:
configuration = reload_configuration(
ROBOFLOW_REGION=region, ROBOFLOW_ENVIRONMENT=environment
)
assert configuration.ROBOFLOW_API_HOST == expected_api_host


def test_explicit_roboflow_api_host_beats_region_selection(
reload_configuration,
) -> None:
configuration = reload_configuration(
ROBOFLOW_REGION="eu", ROBOFLOW_API_HOST="https://api.example.com"
)
assert configuration.ROBOFLOW_API_HOST == "https://api.example.com"


def test_unknown_roboflow_region_warns_and_falls_back_to_us(
reload_configuration,
) -> None:
with pytest.warns(UserWarning, match="Unknown ROBOFLOW_REGION"):
configuration = reload_configuration(ROBOFLOW_REGION="mars")
assert configuration.ROBOFLOW_REGION == "us"
assert configuration.ROBOFLOW_API_HOST == "https://api.roboflow.com"
5 changes: 4 additions & 1 deletion inference_sdk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import threading
from typing import Iterable, Optional, Tuple

from inference_sdk.regions import resolve_roboflow_service_url
from inference_sdk.utils.environment import str2bool

execution_id = contextvars.ContextVar("execution_id", default=None)
Expand Down Expand Up @@ -182,6 +183,8 @@ def summarize(self, max_detail_bytes: int = 4096) -> Tuple[float, Optional[str]]
"https://infer.roboflow.com",
"https://serverless.roboflow.com",
"https://serverless.roboflow.one",
"https://serverless.roboflow.eu",
"https://serverless.roboflow-eu.one",
"https://asyncinfer.roboflow.com",
"https://asyncinfer.roboflow.one",
}
Expand All @@ -203,7 +206,7 @@ def summarize(self, max_detail_bytes: int = 4096) -> Tuple[float, Optional[str]]
) # 256KB max buffered before backpressure

# Roboflow API base URL for TURN config and other services
RF_API_BASE_URL = os.getenv("RF_API_BASE_URL", "https://api.roboflow.com")
RF_API_BASE_URL = os.getenv("RF_API_BASE_URL", resolve_roboflow_service_url("api"))


class InferenceSDKDeprecationWarning(Warning):
Expand Down
99 changes: 99 additions & 0 deletions inference_sdk/regions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""Canonical Roboflow region / environment selection.

Single source of truth for resolving default Roboflow service URLs from the
two public switches:

* ``ROBOFLOW_REGION`` - ``us`` (default) or ``eu``
* ``ROBOFLOW_ENVIRONMENT`` - ``prod`` (default) or ``staging``

Explicit URL env variables (``API_BASE_URL``, ``ROBOFLOW_API_HOST``,
``RF_API_BASE_URL``, ...) always take precedence over values resolved here -
callers apply this module's output only as the default. The legacy
``PROJECT`` variable (``roboflow-platform`` vs anything else) is honoured as
an environment signal wherever it was honoured before, with
``ROBOFLOW_ENVIRONMENT`` taking precedence when both are set.
"""

import os
import warnings
from typing import Optional

PROD_ENVIRONMENT_NAME = "prod"
STAGING_ENVIRONMENT_NAME = "staging"
US_PROD_PROJECT_NAME = "roboflow-platform"

DEFAULT_REGION = "us"
DEFAULT_ENVIRONMENT = PROD_ENVIRONMENT_NAME

ROBOFLOW_SERVICE_URLS = {
("us", "prod"): {
"api": "https://api.roboflow.com",
"app": "https://app.roboflow.com",
"serverless": "https://serverless.roboflow.com",
},
("us", "staging"): {
"api": "https://api.roboflow.one",
"app": "https://app.roboflow.one",
"serverless": "https://serverless.roboflow.one",
},
("eu", "prod"): {
"api": "https://api.roboflow.eu",
"app": "https://app.roboflow.eu",
"serverless": "https://serverless.roboflow.eu",
},
("eu", "staging"): {
"api": "https://api.roboflow-eu.one",
"app": "https://app.roboflow-eu.one",
"serverless": "https://serverless.roboflow-eu.one",
},
}

SUPPORTED_REGIONS = sorted({region for region, _ in ROBOFLOW_SERVICE_URLS})


def get_roboflow_region() -> str:
"""Return the selected Roboflow region (``us`` or ``eu``).

Unknown values warn on stderr and fall back to ``us`` - never raise at
import time.
"""
region = os.getenv("ROBOFLOW_REGION", DEFAULT_REGION).strip().lower()
if region not in SUPPORTED_REGIONS:
warnings.warn(
f"Unknown ROBOFLOW_REGION {region!r} - falling back to "
f"{DEFAULT_REGION!r}. Supported regions: {', '.join(SUPPORTED_REGIONS)}.",
)
return DEFAULT_REGION
return region


def get_roboflow_environment(project: Optional[str] = None) -> str:
"""Return the selected Roboflow environment (``prod`` or ``staging``).

``ROBOFLOW_ENVIRONMENT`` wins when set (any value other than ``prod``
selects staging, matching historical behaviour). Otherwise the legacy
``project`` signal decides (``roboflow-platform`` means prod), and with
neither present the environment defaults to prod.
"""
environment = os.getenv("ROBOFLOW_ENVIRONMENT")
if environment is not None:
if environment.strip().lower() == PROD_ENVIRONMENT_NAME:
return PROD_ENVIRONMENT_NAME
return STAGING_ENVIRONMENT_NAME
if project is not None and project != US_PROD_PROJECT_NAME:
return STAGING_ENVIRONMENT_NAME
return DEFAULT_ENVIRONMENT


def resolve_roboflow_service_url(
service: str,
region: Optional[str] = None,
environment: Optional[str] = None,
project: Optional[str] = None,
) -> str:
"""Resolve the default URL for a Roboflow service in the selected region / environment."""
if region is None:
region = get_roboflow_region()
if environment is None:
environment = get_roboflow_environment(project=project)
return ROBOFLOW_SERVICE_URLS[(region, environment)][service]
Loading