diff --git a/README.md b/README.md index 43fe4a7..7b36f4d 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,7 @@ All keys live in `.repi/config.json` (see `config.example.json` for the full sch | `UI_PORT` | `3000` | Port the web UI binds to (read by `repi ui`) | | `WATCHER_CONFIG_REFRESH_SECS` | `30` | How often the worker polls for config changes | | `OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama endpoint | +| `LLM_MAX_CALLS_PER_MIN` | `10` | Maximum LLM API calls per minute in the ReAct loop. Increase for high-tier API plans. | ## Development diff --git a/repi/core/config.py b/repi/core/config.py index e96ad84..417ecb3 100644 --- a/repi/core/config.py +++ b/repi/core/config.py @@ -57,6 +57,7 @@ class Settings(BaseSettings): OLLAMA_BASE_URL: str = "http://localhost:11434" WATCHER_CONFIG_REFRESH_SECS: int = 30 + LLM_MAX_CALLS_PER_MIN: int = Field(default=60, ge=1) # "fastembed" (ONNX Runtime, ~50 MB) or "torch" via sentence-transformers # (~790 MB). Vectors are byte-identical; the choice is image size / RSS. diff --git a/repi/core/container.py b/repi/core/container.py index 56875e6..faa735f 100644 --- a/repi/core/container.py +++ b/repi/core/container.py @@ -3,8 +3,10 @@ from sqlalchemy.orm import sessionmaker from sqlmodel import select, func from sqlmodel.ext.asyncio.session import AsyncSession +import importlib from repi.core.config import settings + from repi.core.cache import cache from repi.retrieval.pgvector_store import PgVectorStore from repi.retrieval.fts_factory import create_fts_retriever @@ -237,7 +239,8 @@ async def cached_find_logs_by_id(**kwargs): store=store, enable_reflection=settings.ENABLE_REFLECTION, reflection_interval=settings.REFLECTION_INTERVAL, - ) + llm_max_calls_per_min=settings.LLM_MAX_CALLS_PER_MIN, + ) def get_investigation_store(self, session: AsyncSession) -> InvestigationStore: return InvestigationStore(session) diff --git a/repi/investigation/react_loop.py b/repi/investigation/react_loop.py index a74ab94..54578cb 100644 --- a/repi/investigation/react_loop.py +++ b/repi/investigation/react_loop.py @@ -107,6 +107,7 @@ def __init__( enable_reflection: bool = True, reflection_interval: int = 3, max_reflections: int = 2, + llm_max_calls_per_min: int = 60, ) -> None: self.llm = llm self.tools = tools @@ -120,6 +121,7 @@ def __init__( self.enable_reflection = enable_reflection self.reflection_interval = reflection_interval self.max_reflections = max_reflections + self.llm_max_calls_per_min = llm_max_calls_per_min self._llm_call_timestamps: list[float] = [] @staticmethod @@ -145,7 +147,7 @@ def _ledger_summary(ledger: dict[str, dict]) -> str: async def _wait_for_rate_limit(self): now = time.time() self._llm_call_timestamps = [t for t in self._llm_call_timestamps if now - t < 60] - while len(self._llm_call_timestamps) >= 3: + while len(self._llm_call_timestamps) >= self.llm_max_calls_per_min: wait_time = 60 - (now - self._llm_call_timestamps[0]) + 1 logger.warning(f"Rate limit: Waiting {wait_time:.1f}s...") await asyncio.sleep(wait_time)