Source code for fastrag.serve.config
"""Frozen configuration for :func:`fastrag.serve.create_app`."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from fastrag.exceptions import ConfigurationError
from fastrag.serve._rate_limit import parse_rate
[docs]
@dataclass(frozen=True, kw_only=True)
class ServeConfig:
"""Configuration for the serving app.
Frozen and keyword-only. Nothing under ``src/fastrag/`` reads
``os.environ`` — the caller reads the environment and passes the result
in, exactly as ``08_sources.py`` already does for the chat model. Fail
fast rather than start in a degraded state: every field is validated in
``__post_init__``, raising
:class:`~fastrag.exceptions.ConfigurationError` earlier than startup, at
the line that got it wrong.
Args:
allowed_origins: Origins allowed by CORS. The one required field,
with no default. A ``"*"`` wildcard is rejected: credentialed
requests cannot use ``Access-Control-Allow-Origin: *``. The app
configures CORS from this list itself, so :func:`~fastrag.serve.create_app`
callers never add their own ``CORSMiddleware``.
cookie_secure: Whether the session cookie carries the ``Secure`` flag.
cookie_domain: Optional ``Domain`` for the session cookie.
session_ttl_days: Days a session (and its chats) live without activity.
expiry_bump_min_days: Minimum headroom before an expiry bump is written.
max_request_bytes: Reject a request body larger than this with ``413``.
max_content_chars: Reject a ``content`` longer than this with ``422``.
max_messages_per_chat: Cap on messages per chat (``422`` beyond).
max_chats_per_session: Cap on chats per session (``422`` beyond).
generation_timeout_s: End-to-end budget (retrieval + generation) per
generation; a timeout lands as ``TIMEOUT``.
keepalive_interval_s: Seconds between SSE keep-alive comments, and
between pings on the chat's lock connection while it answers.
Keep it below the idle timeout of any proxy in front of the app
or of Postgres.
log_level: Root logger level for the JSON log output.
bearer_tokens: Enable the bearer-token path and ``POST /v1/sessions``.
Off by default — see "Bearer tokens, in full" in the serving
design notes.
metrics_enabled: Whether ``GET /metrics`` is mounted at all. On by
default; every series is still collected when this is ``False``,
only the route that exposes them is withheld.
metrics_token: Optional bearer token guarding ``GET /metrics``.
``None`` (the default) leaves the endpoint unguarded — reasonable
for a scrape target reachable only from inside a private network.
When set, a request without a matching ``Authorization: Bearer``
header gets ``401`` and no metric text.
trust_proxy_headers: Whether the leftmost entry of an incoming
``X-Forwarded-For`` header is trusted as the real client IP,
instead of ``request.client.host``. Off by default: this stack
usually runs behind a reverse proxy, but trusting a
client-settable header unconditionally is a trivial rate-limit
and audit-log bypass. Turn this on only when a proxy in front of
this app is known to set (and not merely pass through) the
header.
rate_limiting_enabled: Whether the three limits below are enforced at
all. Off by default, so an existing caller or the test suite is
unaffected until it opts in.
rate_limit_generation: Limit on ``POST /v1/chats``,
``POST /v1/chats/{id}/messages``, and
``POST /v1/chats/{id}/messages/{mid}``, as a ``"10/minute"``-style
string (``<count>/<second|minute|hour|day>``), per client IP.
rate_limit_read: Limit on ``GET /v1/chats`` and ``GET /v1/chats/{id}``,
same format, per client IP.
rate_limit_new_session: Limit on the branch of a request that mints a
new session (``POST /v1/chats`` and ``POST /v1/sessions`` when
neither presents a live session), same format, per client IP —
not a limit on those endpoints as a whole, which
``rate_limit_generation`` already covers for ``POST /v1/chats``.
geoip_database_path: Path to a MaxMind GeoLite2-Country ``.mmdb``
file, for resolving a session's ``country``. ``None`` (the
default) disables country resolution entirely; nothing is
downloaded or vendored by this package — see the serving guide
for where to get one. Requires the ``geoip`` extra
(``geoip2``); without it, country resolution is silently
unavailable even when a path is set.
"""
allowed_origins: list[str]
cookie_secure: bool = True
cookie_domain: str | None = None
session_ttl_days: int = 30
expiry_bump_min_days: int = 1
max_request_bytes: int = 32768
max_content_chars: int = 4000
max_messages_per_chat: int = 200
max_chats_per_session: int = 100
generation_timeout_s: float = 120.0
keepalive_interval_s: float = 15.0
log_level: str = "INFO"
bearer_tokens: bool = False
metrics_enabled: bool = True
metrics_token: str | None = None
trust_proxy_headers: bool = False
rate_limiting_enabled: bool = False
rate_limit_generation: str = "10/minute"
rate_limit_read: str = "60/minute"
rate_limit_new_session: str = "20/hour"
geoip_database_path: Path | None = None
def __post_init__(self) -> None:
"""Validate every field, failing fast rather than at first request."""
if "*" in self.allowed_origins:
raise ConfigurationError(
"ServeConfig.allowed_origins must not contain a wildcard; "
"credentialed requests cannot use Access-Control-Allow-Origin: *"
)
for name in (
"session_ttl_days",
"expiry_bump_min_days",
"max_request_bytes",
"max_content_chars",
"max_messages_per_chat",
"max_chats_per_session",
):
if getattr(self, name) < 1:
raise ConfigurationError(f"ServeConfig.{name} must be at least 1")
if self.generation_timeout_s <= 0:
raise ConfigurationError(
"ServeConfig.generation_timeout_s must be positive"
)
if self.keepalive_interval_s <= 0:
raise ConfigurationError(
"ServeConfig.keepalive_interval_s must be positive"
)
if self.metrics_token is not None and not self.metrics_token:
raise ConfigurationError(
"ServeConfig.metrics_token must not be an empty string; use "
"None to leave /metrics unguarded"
)
for name in (
"rate_limit_generation",
"rate_limit_read",
"rate_limit_new_session",
):
try:
parse_rate(getattr(self, name))
except ConfigurationError as exc:
raise ConfigurationError(f"ServeConfig.{name}: {exc}") from exc