Skip to content

[Feature] Add a request interval function #281

Description

@63bd2249

Many LLM providers have a rate limit, specifically RPM. Add an interval between requests can meet the rate limit.

I've added it successfully with the help of AI. I hope this can be added into new versions.

1. src/config.py

A. Add to _RELOADABLE_ENV_SETTINGS (near the other performance settings, e.g. after PARALLEL_TRANSLATIONS):

    ('PARALLEL_TRANSLATIONS', 'PARALLEL_TRANSLATIONS', '1'),
    ('REQUEST_INTERVAL',      'REQUEST_INTERVAL',      '0'),   # <-- add this
    ('MAX_TOKENS_PER_CHUNK',   'MAX_TOKENS_PER_CHUNK',   '450'),

B. Treat it as a float in _apply_reloadable_env_settings.

Find:

_INT_ATTRS = {'PARALLEL_TRANSLATIONS', 'MAX_TOKENS_PER_CHUNK'}

Change to something like:

_INT_ATTRS = {'PARALLEL_TRANSLATIONS', 'MAX_TOKENS_PER_CHUNK'}
_FLOAT_ATTRS = {'REQUEST_INTERVAL'}

Then inside the loop that converts values:

        if attr in _NOTIFY_BOOL_ATTRS:
            g[attr] = str(raw).strip().lower() == 'true'
        elif attr in _NOTIFY_INT_ATTRS or attr in _INT_ATTRS:
            try:
                g[attr] = int(raw)
            except (TypeError, ValueError):
                g[attr] = int(default)
        elif attr in _FLOAT_ATTRS:
            try:
                g[attr] = max(0.0, float(raw))
            except (TypeError, ValueError):
                g[attr] = float(default)
        else:
            g[attr] = raw

2. .env.example

Add near the performance section:

# Minimum delay (seconds) between successful LLM requests.
# 0 = no delay. Useful to stay under provider rate limits.
REQUEST_INTERVAL=0

3. src/api/blueprints/config_routes.py

A. Return the value in GET /api/config (around the parallel_translations lines):

            "parallel_translations": int(_config.PARALLEL_TRANSLATIONS),
            "max_parallel_translations": int(_config.MAX_PARALLEL_TRANSLATIONS),
            "request_interval": float(getattr(_config, "REQUEST_INTERVAL", 0) or 0),  # <-- add

B. Allow saving it – add to allowed_keys in save_settings():

            'PARALLEL_TRANSLATIONS',
            'REQUEST_INTERVAL',          # <-- add
            'DISABLE_AUTO_PAUSE',

C. Validate / clamp (in the same block that clamps PARALLEL_TRANSLATIONS):

                    elif key == 'REQUEST_INTERVAL':
                        try:
                            n = float(safe_value)
                        except (TypeError, ValueError):
                            continue
                        safe_value = str(max(0.0, n))

4. Web UI – HTML

In src/web/templates/translation_interface.html, right after the Parallel requests block (#parallelWorkersGroup), add:

                    <!-- Request interval (global minimum delay between LLM calls) -->
                    <div class="form-group" style="margin-bottom: 15px;">
                        <label data-i18n="settings:request_interval_label" for="requestInterval">Request interval (seconds)</label>
                        <div class="neu-inset-light">
                            <input type="number" class="form-control" id="requestInterval"
                                   min="0" step="0.5" value="0"
                                   data-i18n-attr="title:settings:request_interval_input_title"
                                   title="Minimum delay between successful LLM requests. 0 = no delay.">
                        </div>
                        <small data-i18n="settings:request_interval_help"
                               style="color: var(--text-muted-light); font-size: 0.6875rem; margin-top: 0.5rem; display: block;">
                            Hard minimum delay after every successful LLM response before the next request is sent.
                            Useful to stay under provider rate limits. 0 disables the delay.
                        </small>
                    </div>

5. src/web/static/js/core/settings-manager.js

A. Mark the field as dirty (in envDirtyElements):

            { id: 'parallelWorkers', event: 'input' },
            { id: 'maxTokensPerChunk', event: 'input' },
            { id: 'requestInterval', event: 'input' },   // <-- add

B. Save it (inside saveAllSettings, next to the parallel workers block):

            // Save request interval (minimum delay between successful LLM calls)
            const requestIntervalInput = DomHelpers.getElement('requestInterval');
            if (requestIntervalInput) {
                const ri = parseFloat(requestIntervalInput.value);
                envSettings['REQUEST_INTERVAL'] = String(
                    Number.isFinite(ri) && ri >= 0 ? ri : 0
                );
            }

6. src/web/static/js/ui/form-manager.js

In loadDefaultConfig(), after the parallel workers seeding:

            // Request interval (global minimum delay between LLM requests)
            if (config.request_interval !== undefined && config.request_interval !== null) {
                const requestIntervalInput = DomHelpers.getElement('requestInterval');
                if (requestIntervalInput) {
                    requestIntervalInput.value = String(config.request_interval);
                }
            }

7. Enforce the delay src/core/llm_client.py.

A. Add the import at the top of the file

import asyncio
import src.config as cfg

(Keep any existing imports.)

B. Replace the two methods

    async def generate(self, prompt: str, system_prompt: Optional[str] = None,
                      timeout: int = None) -> Optional[LLMResponse]:
        """
        Generate a response from the LLM (alias for make_request for backward compatibility)

        Args:
            prompt: The user prompt to send
            system_prompt: Optional system prompt (role/instructions)
            timeout: Request timeout in seconds

        Returns:
            LLMResponse with content and token usage info, or None if failed
        """
        provider = self._get_provider()

        if timeout:
            response = await provider.generate(prompt, timeout, system_prompt=system_prompt)
        else:
            response = await provider.generate(prompt, system_prompt=system_prompt)

        # Hard minimum interval between successful LLM requests
        if response is not None:
            interval = float(getattr(cfg, "REQUEST_INTERVAL", 0) or 0)
            if interval > 0:
                await asyncio.sleep(interval)

        return response

    async def make_request(self, prompt: str, model: Optional[str] = None,
                    timeout: int = None, system_prompt: Optional[str] = None) -> Optional[LLMResponse]:
        """
        Make a request to the LLM API with error handling and retries

        Args:
            prompt: The user prompt to send (content to process)
            model: Model to use (defaults to instance model)
            timeout: Request timeout in seconds
            system_prompt: Optional system prompt (role/instructions)

        Returns:
            LLMResponse with content and token usage info, or None if failed
        """
        provider = self._get_provider()

        # Update model if specified
        if model:
            provider.model = model

        if timeout:
            response = await provider.generate(prompt, timeout, system_prompt=system_prompt)
        else:
            response = await provider.generate(prompt, system_prompt=system_prompt)

        # Hard minimum interval between successful LLM requests
        if response is not None:
            interval = float(getattr(cfg, "REQUEST_INTERVAL", 0) or 0)
            if interval > 0:
                await asyncio.sleep(interval)

        return response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions