Skip to content

Commit 0740719

Browse files
authored
feat: better litellm lazy loading / app speed-up (#806)
1 parent a56f6ee commit 0740719

File tree

18 files changed

+199
-177
lines changed

18 files changed

+199
-177
lines changed

.github/scripts/check_changelog_update.sh

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
#!/bin/bash
22

3-
echo "Fetching main branch..."
4-
git fetch origin main --depth=1
3+
# Use the provided base branch or default to 'develop'
4+
BASE_BRANCH=${1:-develop}
55

6-
echo "Identifying changed files between the current branch and main branch..."
7-
CHANGED_FILES=$(git diff --name-only origin/main | tr '\n' ' ')
6+
echo "Fetching $BASE_BRANCH branch..."
7+
git fetch origin $BASE_BRANCH --depth=1
8+
9+
echo "Identifying changed files between the current branch and $BASE_BRANCH branch..."
10+
CHANGED_FILES=$(git diff --name-only origin/$BASE_BRANCH | tr '\n' ' ')
811

912
if [ -z "$CHANGED_FILES" ]; then
1013
echo "No files have been changed in this branch."
@@ -29,7 +32,7 @@ fi
2932
echo "Found changes in the following packages: $CHANGED_PACKAGES"
3033

3134
# Look for "Changelog-ignore: <package-name>" in the commit message (possibly multiple entries in separate lines)
32-
IGNORED_PACKAGES=$(git log --pretty=format:%B origin/main..HEAD | grep -oP '^Changelog-ignore: \K[^ ]+' | sort -u)
35+
IGNORED_PACKAGES=$(git log --pretty=format:%B origin/$BASE_BRANCH..HEAD | grep -oP '^Changelog-ignore: \K[^ ]+' | sort -u)
3336

3437
for IGNORED_PACKAGE in $IGNORED_PACKAGES; do
3538
if echo "$CHANGED_PACKAGES" | grep -q "^$IGNORED_PACKAGE$"; then
@@ -42,7 +45,7 @@ for PACKAGE in $CHANGED_PACKAGES; do
4245
CHANGELOG="packages/$PACKAGE/CHANGELOG.md"
4346
echo "Checking changelog for package: $PACKAGE"
4447

45-
if ! diff -u <(git show origin/main:$CHANGELOG | grep -Pzo '(?s)(## Unreleased.*?)(?=\n## |\Z)' | tr -d '\0') <(grep -Pzo '(?s)(## Unreleased.*?)(?=\n## |\Z)' $CHANGELOG | tr -d '\0') | grep -q '^\+'; then
48+
if ! diff -u <(git show origin/$BASE_BRANCH:$CHANGELOG | grep -Pzo '(?s)(## Unreleased.*?)(?=\n## |\Z)' | tr -d '\0') <(grep -Pzo '(?s)(## Unreleased.*?)(?=\n## |\Z)' $CHANGELOG | tr -d '\0') | grep -q '^\+'; then
4649
echo "No updates detected in changelog for package $PACKAGE. Please add an entry under '## Unreleased'."
4750
exit 1
4851
fi

packages/ragbits-cli/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- feat: add current working directory to Python path for better CLI module discovery
6+
57
## 1.2.2 (2025-08-08)
68

79
### Changed

packages/ragbits-cli/src/ragbits/cli/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import importlib.util
22
import os
33
import pkgutil
4+
import sys
45
from pathlib import Path
56
from typing import Annotated
67

@@ -60,6 +61,7 @@ def autoregister() -> None:
6061
for module in pkgutil.iter_modules(ragbits.__path__)
6162
if module.ispkg and module.name != "cli" and (Path(module.module_finder.path) / module.name / "cli.py").exists() # type: ignore
6263
]
64+
sys.path.append(os.getcwd())
6365

6466
for module in cli_enabled_modules:
6567
register_func = importlib.import_module(f"ragbits.{module.name}.cli").register

packages/ragbits-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- feat: improve app startup speed with lazy LiteLLM loading
6+
57

68
- Feat: added support for IVFFlat indexing and Halfvec datatype
79

packages/ragbits-core/src/ragbits/core/embeddings/dense/litellm.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
from typing import Any, cast
1+
from typing import TYPE_CHECKING, Any, cast
22

3-
import litellm
43
from typing_extensions import Self
54

65
from ragbits.core.audit.traces import trace
@@ -14,6 +13,10 @@
1413
)
1514
from ragbits.core.options import Options
1615
from ragbits.core.types import NOT_GIVEN, NotGiven
16+
from ragbits.core.utils.lazy_litellm import LazyLiteLLM
17+
18+
if TYPE_CHECKING:
19+
from litellm import Router
1720

1821

1922
class LiteLLMEmbedderOptions(Options):
@@ -28,7 +31,7 @@ class LiteLLMEmbedderOptions(Options):
2831
encoding_format: str | None | NotGiven = NOT_GIVEN
2932

3033

31-
class LiteLLMEmbedder(DenseEmbedder[LiteLLMEmbedderOptions]):
34+
class LiteLLMEmbedder(DenseEmbedder[LiteLLMEmbedderOptions], LazyLiteLLM):
3235
"""
3336
Client for creating text embeddings using LiteLLM API.
3437
"""
@@ -44,7 +47,7 @@ def __init__(
4447
base_url: str | None = None, # Alias for api_base
4548
api_key: str | None = None,
4649
api_version: str | None = None,
47-
router: litellm.Router | None = None,
50+
router: "Router | None" = None,
4851
) -> None:
4952
"""
5053
Constructs the LiteLLMEmbeddingClient.
@@ -119,7 +122,7 @@ async def embed_text(self, data: list[str], options: LiteLLMEmbedderOptions | No
119122
options=merged_options.dict(),
120123
) as outputs:
121124
try:
122-
entrypoint = self.router or litellm
125+
entrypoint = self.router or self._litellm
123126
response = await entrypoint.aembedding(
124127
input=data,
125128
model=self.model_name,
@@ -128,11 +131,11 @@ async def embed_text(self, data: list[str], options: LiteLLMEmbedderOptions | No
128131
api_version=self.api_version,
129132
**merged_options.dict(),
130133
)
131-
except litellm.openai.APIConnectionError as exc:
134+
except self._litellm.openai.APIConnectionError as exc:
132135
raise EmbeddingConnectionError() from exc
133-
except litellm.openai.APIStatusError as exc:
136+
except self._litellm.openai.APIStatusError as exc:
134137
raise EmbeddingStatusError(exc.message, exc.status_code) from exc
135-
except litellm.openai.APIResponseValidationError as exc:
138+
except self._litellm.openai.APIResponseValidationError as exc:
136139
raise EmbeddingResponseError() from exc
137140

138141
if not response.data:
@@ -158,7 +161,7 @@ def from_config(cls, config: dict[str, Any]) -> Self:
158161
LiteLLMEmbedder: An initialized LiteLLMEmbedder instance.
159162
"""
160163
if "router" in config:
161-
router = litellm.router.Router(model_list=config["router"])
164+
router = cls._get_litellm_module().router.Router(model_list=config["router"])
162165
config["router"] = router
163166

164167
# Map base_url to api_base if present
Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,5 @@
1-
import threading
2-
from concurrent.futures import Future, ThreadPoolExecutor
3-
from functools import cache
4-
51
from .base import LLM, ToolCall, Usage
2+
from .litellm import LiteLLM, LiteLLMOptions
63
from .local import LocalLLM, LocalLLMOptions
74

8-
_import_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="litellm-import")
9-
_litellm_future: Future[tuple[type, type]] | None = None
10-
_import_lock = threading.Lock()
11-
12-
13-
@cache
14-
def _import_litellm() -> tuple[type, type]:
15-
from .litellm import LiteLLM, LiteLLMOptions
16-
17-
return LiteLLM, LiteLLMOptions
18-
19-
20-
def _start_litellm_import() -> None:
21-
global _litellm_future # noqa: PLW0603
22-
with _import_lock:
23-
if _litellm_future is None:
24-
_litellm_future = _import_executor.submit(_import_litellm)
25-
26-
27-
def __getattr__(name: str) -> type:
28-
if name in ("LiteLLM", "LiteLLMOptions"):
29-
_start_litellm_import()
30-
if _litellm_future is not None:
31-
LiteLLM, LiteLLMOptions = _litellm_future.result()
32-
else:
33-
# Fallback to synchronous import if future is None
34-
LiteLLM, LiteLLMOptions = _import_litellm()
35-
36-
if name == "LiteLLM":
37-
return LiteLLM
38-
elif name == "LiteLLMOptions":
39-
return LiteLLMOptions
40-
41-
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
42-
43-
44-
# Dynamic __all__ to handle lazy-loaded LiteLLM imports
45-
__all__ = ["LLM", "LocalLLM", "LocalLLMOptions", "ToolCall", "Usage"]
46-
47-
48-
def __dir__() -> list[str]:
49-
"""Return available module attributes including lazy-loaded ones."""
50-
return __all__ + ["LiteLLM", "LiteLLMOptions"]
5+
__all__ = ["LLM", "LiteLLM", "LiteLLMOptions", "LocalLLM", "LocalLLMOptions", "ToolCall", "Usage"]

0 commit comments

Comments
 (0)