Skip to content

Commit 7d25621

Browse files
committed
[Feat] Add Isaacus embeddings provider
1 parent 9338727 commit 7d25621

File tree

13 files changed

+766
-3
lines changed

13 files changed

+766
-3
lines changed

docs/my-website/docs/embedding/supported_embedding.md

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -543,9 +543,56 @@ All models listed here https://docs.voyageai.com/embeddings/#models-and-specific
543543

544544
| Model Name | Function Call |
545545
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
546-
| voyage-01 | `embedding(model="voyage/voyage-01", input)` |
547-
| voyage-lite-01 | `embedding(model="voyage/voyage-lite-01", input)` |
548-
| voyage-lite-01-instruct | `embedding(model="voyage/voyage-lite-01-instruct", input)` |
546+
| voyage-01 | `embedding(model="voyage/voyage-01", input)` |
547+
| voyage-lite-01 | `embedding(model="voyage/voyage-lite-01", input)` |
548+
| voyage-lite-01-instruct | `embedding(model="voyage/voyage-lite-01-instruct", input)` |
549+
550+
## Isaacus AI Embedding Models
551+
552+
### Usage - Embedding
553+
```python
554+
from litellm import embedding
555+
import os
556+
557+
os.environ['ISAACUS_API_KEY'] = ""
558+
response = embedding(
559+
model="isaacus/kanon-2-embedder",
560+
input=["good morning from litellm"],
561+
)
562+
print(response)
563+
```
564+
565+
### Supported Models
566+
All models listed here https://docs.isaacus.com/api-reference/embeddings are supported
567+
568+
| Model Name | Function Call |
569+
|------------|---------------|
570+
| kanon-2-embedder | `embedding(model="isaacus/kanon-2-embedder", input)` |
571+
572+
**Supported optional params:**
573+
- `task` - Optimize embeddings for specific use case: `"retrieval/query"` (for search queries) or `"retrieval/document"` (for documents to be indexed)
574+
- `dimensions` - Optionally reduce embedding dimensionality (e.g., 256, 768, 1024)
575+
- `overflow_strategy` - How to handle text that exceeds model limits: `"drop_end"` (default, truncates text at the end)
576+
577+
```python
578+
# Example with optional params
579+
response = embedding(
580+
model="isaacus/kanon-2-embedder",
581+
input=["contract text"],
582+
task="retrieval/query", # or "retrieval/document"
583+
dimensions=1024,
584+
overflow_strategy="drop_end"
585+
)
586+
```
587+
588+
### Task Parameter
589+
590+
Isaacus embeddings support task-specific optimization to improve retrieval accuracy. Set the `task` parameter based on your use case:
591+
592+
- `"retrieval/query"` – for embedding search queries during retrieval
593+
- `"retrieval/document"` – for embedding documents during indexing
594+
595+
> **Note:** Using the correct task type ensures optimal performance for legal document retrieval.
549596
550597
### Provider-specific Params
551598

litellm/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
open_ai_embedding_models,
6969
cohere_embedding_models,
7070
bedrock_embedding_models,
71+
isaacus_embedding_models,
7172
known_tokenizer_config,
7273
BEDROCK_INVOKE_PROVIDERS_LITERAL,
7374
BEDROCK_EMBEDDING_PROVIDERS_LITERAL,
@@ -234,6 +235,7 @@
234235
datarobot_key: Optional[str] = None
235236
predibase_key: Optional[str] = None
236237
huggingface_key: Optional[str] = None
238+
isaacus_key: Optional[str] = None
237239
vertex_project: Optional[str] = None
238240
vertex_location: Optional[str] = None
239241
predibase_tenant_id: Optional[str] = None
@@ -1192,6 +1194,7 @@ def add_known_models():
11921194
VoyageContextualEmbeddingConfig,
11931195
)
11941196
from .llms.infinity.embedding.transformation import InfinityEmbeddingConfig
1197+
from .llms.isaacus.embedding.transformation import IsaacusEmbeddingConfig
11951198
from .llms.azure_ai.chat.transformation import AzureAIStudioConfig
11961199
from .llms.mistral.chat.transformation import MistralConfig
11971200
from .llms.openai.responses.transformation import OpenAIResponsesAPIConfig

litellm/constants.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,11 @@
895895
"twelvelabs.marengo-embed-2-7-v1:0",
896896
]
897897
)
898+
isaacus_embedding_models: set = set(
899+
[
900+
"kanon-2-embedder",
901+
]
902+
)
898903

899904
known_tokenizer_config = {
900905
"mistralai/Mistral-7B-Instruct-v0.1": {

litellm/litellm_core_utils/get_llm_provider_logic.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,12 @@ def get_llm_provider( # noqa: PLR0915
372372
custom_llm_provider = "lemonade"
373373
elif model.startswith("heroku/"):
374374
custom_llm_provider = "heroku"
375+
# isaacus models
376+
elif (
377+
model.startswith("isaacus/")
378+
or model in litellm.isaacus_embedding_models
379+
):
380+
custom_llm_provider = "isaacus"
375381
# cometapi models
376382
elif model.startswith("cometapi/"):
377383
custom_llm_provider = "cometapi"

litellm/llms/isaacus/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Isaacus LLM Provider."""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .transformation import IsaacusEmbeddingConfig, IsaacusError
2+
3+
__all__ = ["IsaacusEmbeddingConfig", "IsaacusError"]
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"""
2+
Transformation logic from OpenAI /v1/embeddings format to Isaacus's /v1/embeddings format.
3+
4+
Reference: https://docs.isaacus.com/api-reference/embeddings
5+
"""
6+
7+
from typing import List, Optional, Union, cast
8+
9+
import httpx
10+
11+
from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj
12+
from litellm.llms.base_llm.chat.transformation import BaseLLMException
13+
from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig
14+
from litellm.secret_managers.main import get_secret_str
15+
from litellm.types.llms.openai import AllEmbeddingInputValues, AllMessageValues
16+
from litellm.types.utils import EmbeddingResponse, Usage
17+
18+
19+
class IsaacusError(BaseLLMException):
20+
def __init__(
21+
self,
22+
status_code: int,
23+
message: str,
24+
headers: Union[dict, httpx.Headers] = {},
25+
):
26+
self.status_code = status_code
27+
self.message = message
28+
self.request = httpx.Request(
29+
method="POST", url="https://api.isaacus.com/v1/embeddings"
30+
)
31+
self.response = httpx.Response(status_code=status_code, request=self.request)
32+
super().__init__(
33+
status_code=status_code,
34+
message=message,
35+
headers=headers,
36+
)
37+
38+
39+
class IsaacusEmbeddingConfig(BaseEmbeddingConfig):
40+
"""
41+
Reference: https://docs.isaacus.com/api-reference/embeddings
42+
43+
The Isaacus embeddings API provides access to the Kanon 2 Embedder for law.
44+
"""
45+
46+
def __init__(self) -> None:
47+
pass
48+
49+
def get_complete_url(
50+
self,
51+
api_base: Optional[str],
52+
api_key: Optional[str],
53+
model: str,
54+
optional_params: dict,
55+
litellm_params: dict,
56+
stream: Optional[bool] = None,
57+
) -> str:
58+
if api_base:
59+
if not api_base.endswith("/embeddings"):
60+
api_base = f"{api_base}/embeddings"
61+
return api_base
62+
return "https://api.isaacus.com/v1/embeddings"
63+
64+
def get_supported_openai_params(self, model: str) -> list:
65+
return ["dimensions"]
66+
67+
def map_openai_params(
68+
self,
69+
non_default_params: dict,
70+
optional_params: dict,
71+
model: str,
72+
drop_params: bool,
73+
) -> dict:
74+
"""
75+
Map OpenAI params to Isaacus params
76+
77+
Reference: https://docs.isaacus.com/api-reference/embeddings
78+
"""
79+
if "dimensions" in non_default_params:
80+
optional_params["dimensions"] = non_default_params["dimensions"]
81+
return optional_params
82+
83+
def validate_environment(
84+
self,
85+
headers: dict,
86+
model: str,
87+
messages: List[AllMessageValues],
88+
optional_params: dict,
89+
litellm_params: dict,
90+
api_key: Optional[str] = None,
91+
api_base: Optional[str] = None,
92+
) -> dict:
93+
if api_key is None:
94+
api_key = get_secret_str("ISAACUS_API_KEY")
95+
return {
96+
"Authorization": f"Bearer {api_key}",
97+
"Content-Type": "application/json",
98+
}
99+
100+
def transform_embedding_request(
101+
self,
102+
model: str,
103+
input: AllEmbeddingInputValues,
104+
optional_params: dict,
105+
headers: dict,
106+
) -> dict:
107+
"""
108+
Transform OpenAI-style embedding request to Isaacus format.
109+
110+
OpenAI uses 'input' while Isaacus uses 'texts'.
111+
"""
112+
# Convert input to list of strings if needed
113+
if isinstance(input, str):
114+
texts = [input]
115+
elif isinstance(input, list):
116+
if len(input) > 0 and isinstance(input[0], (list, int)):
117+
raise ValueError(
118+
"Isaacus does not support token array inputs. Input must be a string or list of strings."
119+
)
120+
texts = cast(List[str], input)
121+
else:
122+
texts = [input]
123+
124+
request_data = {
125+
"model": model,
126+
"texts": texts,
127+
}
128+
129+
# Add optional parameters
130+
# Isaacus-specific parameters: task, overflow_strategy, dimensions
131+
if "task" in optional_params:
132+
request_data["task"] = optional_params["task"]
133+
if "overflow_strategy" in optional_params:
134+
request_data["overflow_strategy"] = optional_params["overflow_strategy"]
135+
if "dimensions" in optional_params:
136+
request_data["dimensions"] = optional_params["dimensions"]
137+
138+
return request_data
139+
140+
def transform_embedding_response(
141+
self,
142+
model: str,
143+
raw_response: httpx.Response,
144+
model_response: EmbeddingResponse,
145+
logging_obj: LiteLLMLoggingObj,
146+
api_key: Optional[str] = None,
147+
request_data: dict = {},
148+
optional_params: dict = {},
149+
litellm_params: dict = {},
150+
) -> EmbeddingResponse:
151+
try:
152+
raw_response_json = raw_response.json()
153+
except Exception:
154+
raise IsaacusError(
155+
message=raw_response.text, status_code=raw_response.status_code
156+
)
157+
158+
# Transform Isaacus response format to OpenAI format
159+
# Isaacus format: {"embeddings": [{"embedding": [...], "index": 0}, ...], "usage": {"input_tokens": 10}}
160+
# OpenAI format: {"data": [{"embedding": [...], "index": 0, "object": "embedding"}], "model": "...", "usage": {...}}
161+
162+
embeddings_data = raw_response_json.get("embeddings", [])
163+
output_data = []
164+
165+
for emb_obj in embeddings_data:
166+
output_data.append(
167+
{
168+
"object": "embedding",
169+
"index": emb_obj.get("index", 0),
170+
"embedding": emb_obj.get("embedding", []),
171+
}
172+
)
173+
174+
model_response.model = model
175+
model_response.data = output_data
176+
model_response.object = "list"
177+
178+
# Set usage information
179+
# Isaacus returns usage with "input_tokens"
180+
usage_data = raw_response_json.get("usage", {})
181+
input_tokens = usage_data.get("input_tokens", 0)
182+
183+
usage = Usage(
184+
prompt_tokens=input_tokens,
185+
total_tokens=input_tokens,
186+
)
187+
model_response.usage = usage
188+
189+
return model_response
190+
191+
def get_error_class(
192+
self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers]
193+
) -> BaseLLMException:
194+
return IsaacusError(
195+
message=error_message, status_code=status_code, headers=headers
196+
)

litellm/main.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4579,6 +4579,24 @@ def embedding( # noqa: PLR0915
45794579
aembedding=aembedding,
45804580
litellm_params={},
45814581
)
4582+
elif custom_llm_provider == "isaacus":
4583+
api_key = (
4584+
api_key or litellm.isaacus_key or get_secret_str("ISAACUS_API_KEY")
4585+
)
4586+
response = base_llm_http_handler.embedding(
4587+
model=model,
4588+
input=input,
4589+
custom_llm_provider=custom_llm_provider,
4590+
api_base=api_base,
4591+
api_key=api_key,
4592+
logging_obj=logging,
4593+
timeout=timeout,
4594+
model_response=EmbeddingResponse(),
4595+
optional_params=optional_params,
4596+
client=client,
4597+
aembedding=aembedding,
4598+
litellm_params={},
4599+
)
45824600
elif custom_llm_provider == "watsonx":
45834601
credentials = IBMWatsonXMixin.get_watsonx_credentials(
45844602
optional_params=optional_params, api_key=api_key, api_base=api_base

litellm/types/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2510,6 +2510,7 @@ class LlmProviders(str, Enum):
25102510
GALADRIEL = "galadriel"
25112511
NEBIUS = "nebius"
25122512
INFINITY = "infinity"
2513+
ISAACUS = "isaacus"
25132514
DEEPGRAM = "deepgram"
25142515
ELEVENLABS = "elevenlabs"
25152516
NOVITA = "novita"

litellm/utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7219,6 +7219,8 @@ def get_provider_embedding_config(
72197219
return litellm.IBMWatsonXEmbeddingConfig()
72207220
elif litellm.LlmProviders.INFINITY == provider:
72217221
return litellm.InfinityEmbeddingConfig()
7222+
elif litellm.LlmProviders.ISAACUS == provider:
7223+
return litellm.IsaacusEmbeddingConfig()
72227224
elif litellm.LlmProviders.SAMBANOVA == provider:
72237225
return litellm.SambaNovaEmbeddingConfig()
72247226
elif (

0 commit comments

Comments
 (0)