1+ """
2+ Redis based strategy interface.
3+
4+ This module defines an abstract base class that combines load‑balancing
5+ strategy selection with Redis‑backed health‑checking. It is used by the
6+ router to coordinate provider allocation across multiple processes or
7+ hosts, guaranteeing that a particular provider is assigned to at most one
8+ consumer at any time.
9+
10+ The implementation relies on Lua scripts for atomic acquire/release
11+ operations and uses a per‑model Redis hash to store lock flags.
12+ """
13+
114import random
215import logging
316
1124except ImportError :
1225 REDIS_IS_AVAILABLE = False
1326
14- from llm_router_api .base .constants import REDIS_PORT , REDIS_HOST
27+ from llm_router_api .base .constants import (
28+ REDIS_PORT ,
29+ REDIS_HOST ,
30+ REDIS_DB ,
31+ REDIS_PASSWORD ,
32+ )
1533from llm_router_api .core .lb .strategy_interface import ChooseProviderStrategyI
1634from llm_router_api .core .monitor .redis_health_interface import (
1735 RedisBasedHealthCheckInterface ,
@@ -24,22 +42,34 @@ class RedisBasedStrategyInterface(
2442 """
2543 Strategy that selects the first free provider for a model using Redis.
2644
27- The class inherits from
28- :class:`~llm_router_api.core.lb.strategy.ChooseProviderStrategyI`
29- and adds Redis‑based coordination. It ensures that at most one consumer
30- holds a particular provider at any time, even when multiple workers run
31- concurrently on different hosts.
32-
33- Parameters are forwarded to the base class where appropriate, and Redis
34- connection details can be customised via the constructor arguments.
45+ This class merges two responsibilities:
46+
47+ * **Load‑balancing** – Implements the
48+ :class:`~llm_router_api.core.lb.strategy.ChooseProviderStrategyI`
49+ contract, choosing a provider for a given model.
50+ * **Health‑checking** – Inherits from
51+ :class:`~llm_router_api.core.monitor.redis_health_interface.RedisBasedHealthCheckInterface`
52+ to monitor provider health via Redis.
53+
54+ By storing a per‑model hash in Redis where each field represents a
55+ provider’s lock flag, the strategy guarantees that only one worker can
56+ acquire a provider at a time, even when the workers run on different
57+ machines. The acquisition and release are performed atomically using
58+ Lua scripts, eliminating race conditions.
59+
60+ The constructor accepts optional Redis connection parameters so the
61+ strategy can be pointed at any Redis instance, and a ``strategy_prefix``
62+ can be used to namespace keys when multiple strategies share the same
63+ Redis server.
3564 """
3665
3766 def __init__ (
3867 self ,
3968 models_config_path : str ,
4069 redis_host : str = REDIS_HOST ,
70+ redis_password : str = REDIS_PASSWORD ,
4171 redis_port : int = REDIS_PORT ,
42- redis_db : int = 0 ,
72+ redis_db : int = REDIS_DB ,
4373 timeout : int = 60 ,
4474 check_interval : float = 0.1 ,
4575 monitor_check_interval : float = 30 ,
@@ -82,6 +112,7 @@ def __init__(
82112 redis_host = redis_host ,
83113 redis_port = redis_port ,
84114 redis_db = redis_db ,
115+ redis_password = redis_password ,
85116 clear_buffers = clear_buffers ,
86117 logger = logger ,
87118 check_interval = monitor_check_interval ,
@@ -127,6 +158,32 @@ def init_provider(
127158 providers : List [Dict ],
128159 options : Optional [Dict [str , Any ]] = None ,
129160 ) -> Tuple [str | None , bool ]:
161+ """
162+ Prepare Redis structures for a model and optionally enable random choice.
163+
164+ This method is invoked once per model during strategy start‑up. It
165+ registers the providers with the monitoring subsystem, ensures that a
166+ Redis hash exists for the model, and populates the hash fields with a
167+ ``'false'`` value (meaning *free*) if the hash is missing. The returned
168+ ``redis_key`` is the base key used for all subsequent lock operations.
169+
170+ Parameters
171+ ----------
172+ model_name: str
173+ The logical name of the model (e.g., ``"gpt‑4"``).
174+ providers: List[Dict]
175+ A list of provider configuration dictionaries.
176+ options: dict | None, optional
177+ If ``options.get("random_choice")`` is true, the caller intends to
178+ acquire a provider at random; the flag is propagated to the caller.
179+
180+ Returns
181+ -------
182+ Tuple[str | None, bool]
183+ ``(redis_key, is_random)`` where ``redis_key`` is the Redis hash key
184+ for the model (or ``None`` if ``providers`` is empty) and ``is_random``
185+ reflects the ``random_choice`` option.
186+ """
130187 if not providers :
131188 return None , False
132189 # Register providers for monitoring (only once per model)
@@ -153,6 +210,24 @@ def _get_redis_key(self, model_name: str) -> str:
153210 return f"model:{ model_name } "
154211
155212 def _host_key (self , host_name : str ) -> str :
213+ """
214+ Return a Redis key that uniquely identifies a host.
215+
216+ Host names may contain characters that are unsuitable for Redis keys.
217+ This method sanitises the name by replacing any character listed in
218+ ``self.REPLACE_PROVIDER_KEY`` with an underscore and then prefixes it
219+ with ``"host:"``.
220+
221+ Parameters
222+ ----------
223+ host_name: str
224+ The raw host identifier (e.g., a hostname or IP address).
225+
226+ Returns
227+ -------
228+ str
229+ A safe Redis key such as ``"host:my_server_01"``.
230+ """
156231 for ch in self .REPLACE_PROVIDER_KEY :
157232 host_name = host_name .replace (ch , "_" )
158233
@@ -266,6 +341,26 @@ def _try_acquire_random_provider(
266341 def _get_active_providers (
267342 self , model_name : str , providers : List [Dict ]
268343 ) -> List [Dict ]:
344+ """
345+ Retrieve the list of currently active providers for a model.
346+
347+ The method delegates to the monitoring component, which tracks the
348+ health status of each provider. Only providers whose health check
349+ reports *active* are returned.
350+
351+ Parameters
352+ ----------
353+ model_name: str
354+ The logical name of the model.
355+ providers: List[Dict]
356+ The full provider configuration list (kept for signature compatibility;
357+ it is not used directly).
358+
359+ Returns
360+ -------
361+ List[Dict]
362+ A list containing the configuration dictionaries of active providers.
363+ """
269364 active_providers = self ._monitor .get_providers (
270365 model_name = model_name , only_active = True
271366 )
0 commit comments