Skip to content

Commit 9238e1d

Browse files
Merge pull request #14687 from timelfrink/fix/issue-14685-bedrock-titan-v2-encoding-format
Fix: Bedrock Titan V2 encoding_format parameter support
2 parents 114d077 + 63c26d7 commit 9238e1d

File tree

4 files changed

+187
-23
lines changed

4 files changed

+187
-23
lines changed

docs/my-website/docs/providers/bedrock.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1822,6 +1822,59 @@ Here's an example of using a bedrock model with LiteLLM. For a complete list, re
18221822
| Mixtral 8x7B Instruct | `completion(model='bedrock/mistral.mixtral-8x7b-instruct-v0:1', messages=messages)` | `os.environ['AWS_ACCESS_KEY_ID']`, `os.environ['AWS_SECRET_ACCESS_KEY']`, `os.environ['AWS_REGION_NAME']` |
18231823

18241824

1825+
## Bedrock Embedding
1826+
1827+
### API keys
1828+
This can be set as env variables or passed as **params to litellm.embedding()**
1829+
```python
1830+
import os
1831+
os.environ["AWS_ACCESS_KEY_ID"] = "" # Access key
1832+
os.environ["AWS_SECRET_ACCESS_KEY"] = "" # Secret access key
1833+
os.environ["AWS_REGION_NAME"] = "" # us-east-1, us-east-2, us-west-1, us-west-2
1834+
```
1835+
1836+
### Usage
1837+
```python
1838+
from litellm import embedding
1839+
response = embedding(
1840+
model="bedrock/amazon.titan-embed-text-v1",
1841+
input=["good morning from litellm"],
1842+
)
1843+
print(response)
1844+
```
1845+
1846+
#### Titan V2 - encoding_format support
1847+
```python
1848+
from litellm import embedding
1849+
# Float format (default)
1850+
response = embedding(
1851+
model="bedrock/amazon.titan-embed-text-v2:0",
1852+
input=["good morning from litellm"],
1853+
encoding_format="float" # Returns float array
1854+
)
1855+
1856+
# Binary format
1857+
response = embedding(
1858+
model="bedrock/amazon.titan-embed-text-v2:0",
1859+
input=["good morning from litellm"],
1860+
encoding_format="base64" # Returns base64 encoded binary
1861+
)
1862+
```
1863+
1864+
## Supported AWS Bedrock Embedding Models
1865+
1866+
| Model Name | Usage | Supported Additional OpenAI params |
1867+
|----------------------|---------------------------------------------|-----|
1868+
| Titan Embeddings V2 | `embedding(model="bedrock/amazon.titan-embed-text-v2:0", input=input)` | `dimensions`, `encoding_format` |
1869+
| Titan Embeddings - V1 | `embedding(model="bedrock/amazon.titan-embed-text-v1", input=input)` | [here](https://github.com/BerriAI/litellm/blob/f5905e100068e7a4d61441d7453d7cf5609c2121/litellm/llms/bedrock/embed/amazon_titan_g1_transformation.py#L53)
1870+
| Titan Multimodal Embeddings | `embedding(model="bedrock/amazon.titan-embed-image-v1", input=input)` | [here](https://github.com/BerriAI/litellm/blob/f5905e100068e7a4d61441d7453d7cf5609c2121/litellm/llms/bedrock/embed/amazon_titan_multimodal_transformation.py#L28) |
1871+
| Cohere Embeddings - English | `embedding(model="bedrock/cohere.embed-english-v3", input=input)` | [here](https://github.com/BerriAI/litellm/blob/f5905e100068e7a4d61441d7453d7cf5609c2121/litellm/llms/bedrock/embed/cohere_transformation.py#L18)
1872+
| Cohere Embeddings - Multilingual | `embedding(model="bedrock/cohere.embed-multilingual-v3", input=input)` | [here](https://github.com/BerriAI/litellm/blob/f5905e100068e7a4d61441d7453d7cf5609c2121/litellm/llms/bedrock/embed/cohere_transformation.py#L18)
1873+
1874+
### Advanced - [Drop Unsupported Params](https://docs.litellm.ai/docs/completion/drop_params#openai-proxy-usage)
1875+
1876+
### Advanced - [Pass model/provider-specific Params](https://docs.litellm.ai/docs/completion/provider_specific_params#proxy-usage)
1877+
18251878
## Image Generation
18261879
Use this for stable diffusion, and amazon nova canvas on bedrock
18271880

litellm/llms/bedrock/embed/amazon_titan_v2_transformation.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"""
1111

1212
import types
13-
from typing import List, Optional
13+
from typing import List, Optional, Union
1414

1515
from litellm.types.llms.bedrock import (
1616
AmazonTitanV2EmbeddingRequest,
@@ -30,9 +30,7 @@ class AmazonTitanV2Config:
3030
normalize: Optional[bool] = None
3131
dimensions: Optional[int] = None
3232

33-
def __init__(
34-
self, normalize: Optional[bool] = None, dimensions: Optional[int] = None
35-
) -> None:
33+
def __init__(self, normalize: Optional[bool] = None, dimensions: Optional[int] = None) -> None:
3634
locals_ = locals().copy()
3735
for key, value in locals_.items():
3836
if key != "self" and value is not None:
@@ -57,32 +55,56 @@ def get_config(cls):
5755
}
5856

5957
def get_supported_openai_params(self) -> List[str]:
60-
return ["dimensions"]
58+
return ["dimensions", "encoding_format"]
6159

62-
def map_openai_params(
63-
self, non_default_params: dict, optional_params: dict
64-
) -> dict:
60+
def map_openai_params(self, non_default_params: dict, optional_params: dict) -> dict:
6561
for k, v in non_default_params.items():
6662
if k == "dimensions":
6763
optional_params["dimensions"] = v
64+
elif k == "encoding_format":
65+
# Map OpenAI encoding_format to AWS embeddingTypes
66+
if v == "float":
67+
optional_params["embeddingTypes"] = ["float"]
68+
elif v == "base64":
69+
# base64 maps to binary format in AWS
70+
optional_params["embeddingTypes"] = ["binary"]
71+
else:
72+
# For any other encoding format, default to float
73+
optional_params["embeddingTypes"] = ["float"]
6874
return optional_params
6975

70-
def _transform_request(
71-
self, input: str, inference_params: dict
72-
) -> AmazonTitanV2EmbeddingRequest:
76+
def _transform_request(self, input: str, inference_params: dict) -> AmazonTitanV2EmbeddingRequest:
7377
return AmazonTitanV2EmbeddingRequest(inputText=input, **inference_params) # type: ignore
7478

75-
def _transform_response(
76-
self, response_list: List[dict], model: str
77-
) -> EmbeddingResponse:
79+
def _transform_response(self, response_list: List[dict], model: str) -> EmbeddingResponse:
7880
total_prompt_tokens = 0
7981

8082
transformed_responses: List[Embedding] = []
8183
for index, response in enumerate(response_list):
8284
_parsed_response = AmazonTitanV2EmbeddingResponse(**response) # type: ignore
85+
86+
# According to AWS docs, embeddingsByType is always present
87+
# If binary was requested (encoding_format="base64"), use binary data
88+
# Otherwise, use float data from embeddingsByType or fallback to embedding field
89+
embedding_data: Union[List[float], List[int]]
90+
91+
if ("embeddingsByType" in _parsed_response and
92+
"binary" in _parsed_response["embeddingsByType"]):
93+
# Use binary data if available (for encoding_format="base64")
94+
embedding_data = _parsed_response["embeddingsByType"]["binary"]
95+
elif ("embeddingsByType" in _parsed_response and
96+
"float" in _parsed_response["embeddingsByType"]):
97+
# Use float data from embeddingsByType
98+
embedding_data = _parsed_response["embeddingsByType"]["float"]
99+
elif "embedding" in _parsed_response:
100+
# Fallback to legacy embedding field
101+
embedding_data = _parsed_response["embedding"]
102+
else:
103+
raise ValueError(f"No embedding data found in response: {response}")
104+
83105
transformed_responses.append(
84106
Embedding(
85-
embedding=_parsed_response["embedding"],
107+
embedding=embedding_data,
86108
index=index,
87109
object="embedding",
88110
)

litellm/types/llms/bedrock.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,15 +328,22 @@ class CohereEmbeddingResponse(TypedDict):
328328
texts: List[str]
329329

330330

331-
class AmazonTitanV2EmbeddingRequest(TypedDict):
332-
inputText: str
331+
class AmazonTitanV2EmbeddingRequest(TypedDict, total=False):
332+
inputText: Required[str]
333333
dimensions: int
334334
normalize: bool
335+
embeddingTypes: List[Literal["float", "binary"]]
335336

336337

337-
class AmazonTitanV2EmbeddingResponse(TypedDict):
338-
embedding: List[float]
339-
inputTextTokenCount: int
338+
class AmazonTitanV2EmbeddingsByType(TypedDict, total=False):
339+
binary: List[int] # Array of integers for binary format
340+
float: List[float] # Array of floats for float format
341+
342+
343+
class AmazonTitanV2EmbeddingResponse(TypedDict, total=False):
344+
embedding: List[float] # Legacy field - array of floats (backward compatibility)
345+
embeddingsByType: AmazonTitanV2EmbeddingsByType # New format per AWS schema
346+
inputTextTokenCount: Required[int] # Always present in AWS response
340347

341348

342349
class AmazonTitanG1EmbeddingRequest(TypedDict):

tests/test_litellm/llms/bedrock/embed/test_bedrock_embedding.py

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import os
33
import sys
44
from unittest.mock import Mock, patch
5+
56
import pytest
67

78
sys.path.insert(0, os.path.abspath("../../../../..")) # Adds the parent directory to the system path
89
import litellm
9-
from litellm.llms.custom_httpx.http_handler import HTTPHandler, AsyncHTTPHandler
10+
from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler
1011

1112
# Mock responses for different embedding models
1213
titan_embedding_response = {
@@ -146,7 +147,7 @@ def test_bedrock_embedding_with_sigv4():
146147
"""Test embedding falls back to SigV4 auth when no bearer token is provided"""
147148
litellm.set_verbose = True
148149
model = "bedrock/amazon.titan-embed-text-v1"
149-
150+
150151
with patch("litellm.llms.bedrock.embed.embedding.BedrockEmbedding.embeddings") as mock_bedrock_embed:
151152
mock_embedding_response = litellm.EmbeddingResponse()
152153
mock_embedding_response.data = [{"embedding": [0.1, 0.2, 0.3]}]
@@ -159,4 +160,85 @@ def test_bedrock_embedding_with_sigv4():
159160
)
160161

161162
assert isinstance(response, litellm.EmbeddingResponse)
162-
mock_bedrock_embed.assert_called_once()
163+
mock_bedrock_embed.assert_called_once()
164+
165+
166+
def test_bedrock_titan_v2_encoding_format_float():
167+
"""Test amazon.titan-embed-text-v2:0 with encoding_format=float parameter"""
168+
litellm.set_verbose = True
169+
client = HTTPHandler()
170+
test_api_key = "test-bearer-token-12345"
171+
model = "bedrock/amazon.titan-embed-text-v2:0"
172+
173+
# Mock response with embeddingsByType for binary format (addressing issue #14680)
174+
titan_v2_response = {
175+
"embedding": [0.1, 0.2, 0.3],
176+
"inputTextTokenCount": 10
177+
}
178+
179+
with patch.object(client, "post") as mock_post:
180+
mock_response = Mock()
181+
mock_response.status_code = 200
182+
mock_response.text = json.dumps(titan_v2_response)
183+
mock_response.json = lambda: json.loads(mock_response.text)
184+
mock_post.return_value = mock_response
185+
186+
response = litellm.embedding(
187+
model=model,
188+
input=test_input,
189+
encoding_format="float", # This should work but currently throws UnsupportedParamsError
190+
client=client,
191+
aws_region_name="us-east-1",
192+
aws_bedrock_runtime_endpoint="https://bedrock-runtime.us-east-1.amazonaws.com",
193+
api_key=test_api_key
194+
)
195+
196+
assert isinstance(response, litellm.EmbeddingResponse)
197+
assert isinstance(response.data[0]['embedding'], list)
198+
assert len(response.data[0]['embedding']) == 3
199+
200+
# Verify that the request contains embeddingTypes: ["float"] instead of encoding_format
201+
request_body = json.loads(mock_post.call_args.kwargs.get("data", "{}"))
202+
assert "embeddingTypes" in request_body
203+
assert request_body["embeddingTypes"] == ["float"]
204+
assert "encoding_format" not in request_body
205+
206+
207+
def test_bedrock_titan_v2_encoding_format_base64():
208+
"""Test amazon.titan-embed-text-v2:0 with encoding_format=base64 parameter (maps to binary)"""
209+
litellm.set_verbose = True
210+
client = HTTPHandler()
211+
test_api_key = "test-bearer-token-12345"
212+
model = "bedrock/amazon.titan-embed-text-v2:0"
213+
214+
# Mock response with embeddingsByType for binary format
215+
titan_v2_binary_response = {
216+
"embeddingsByType": {
217+
"binary": "YmluYXJ5X2VtYmVkZGluZ19kYXRh" # base64 encoded binary data
218+
},
219+
"inputTextTokenCount": 10
220+
}
221+
222+
with patch.object(client, "post") as mock_post:
223+
mock_response = Mock()
224+
mock_response.status_code = 200
225+
mock_response.text = json.dumps(titan_v2_binary_response)
226+
mock_response.json = lambda: json.loads(mock_response.text)
227+
mock_post.return_value = mock_response
228+
229+
response = litellm.embedding(
230+
model=model,
231+
input=test_input,
232+
encoding_format="base64", # This should map to embeddingTypes: ["binary"]
233+
client=client,
234+
aws_region_name="us-east-1",
235+
aws_bedrock_runtime_endpoint="https://bedrock-runtime.us-east-1.amazonaws.com",
236+
api_key=test_api_key
237+
)
238+
239+
assert isinstance(response, litellm.EmbeddingResponse)
240+
241+
# Verify that the request contains embeddingTypes: ["binary"] for base64 encoding
242+
request_body = json.loads(mock_post.call_args.kwargs.get("data", "{}"))
243+
assert "embeddingTypes" in request_body
244+
assert request_body["embeddingTypes"] == ["binary"]

0 commit comments

Comments
 (0)