use crate::error::Result;
const COHERE_EMBED_DEFAULT_INPUT_TYPE: &str = "search_document";
pub(super) fn transform_bedrock_embed_request(body: &mut serde_json::Value) -> Result<()> {
use crate::error::LiterLlmError;
use serde_json::json;
let model = body.get("model").and_then(|m| m.as_str()).unwrap_or("").to_owned();
let input = body.get("input").cloned().unwrap_or_default();
let texts: Vec<String> = match &input {
serde_json::Value::String(s) => vec![s.clone()],
serde_json::Value::Array(arr) if arr.iter().all(serde_json::Value::is_string) => {
arr.iter().filter_map(|v| v.as_str().map(ToOwned::to_owned)).collect()
}
_ => {
return Err(LiterLlmError::BadRequest {
message: "Bedrock embedding adapters support text input only; use a multimodal-compatible custom provider for image embeddings".into(),
status: 400,
});
}
};
if model.starts_with("amazon.titan-embed") {
if texts.len() > 1 {
return Err(LiterLlmError::BadRequest {
message: format!(
"Bedrock Titan embedding model '{model}' accepts a single input per call, but \
{} were supplied; issue one request per input (batch Titan embedding is a \
separate asynchronous job API)",
texts.len()
),
status: 400,
});
}
let text = texts.first().cloned().unwrap_or_default();
let mut new_body = json!({"inputText": text});
if let Some(dimensions) = body.get("dimensions") {
new_body["dimensions"] = dimensions.clone();
}
*body = new_body;
return Ok(());
}
if model.starts_with("cohere.embed") {
*body = json!({
"texts": texts,
"input_type": COHERE_EMBED_DEFAULT_INPUT_TYPE
});
return Ok(());
}
Err(LiterLlmError::BadRequest {
message: format!(
"unsupported Bedrock embedding model '{model}': liter-llm currently supports \
amazon.titan-embed-* and cohere.embed-* embedding models"
),
status: 400,
})
}
pub(super) fn transform_bedrock_embed_response(body: &mut serde_json::Value) -> Result<()> {
use serde_json::json;
if let Some(embedding) = body.get("embedding").cloned() {
let prompt_tokens = body.get("inputTextTokenCount").and_then(|v| v.as_u64()).unwrap_or(0);
*body = json!({
"object": "list",
"data": [{"object": "embedding", "embedding": embedding, "index": 0}],
"model": "",
"usage": {
"prompt_tokens": prompt_tokens,
"completion_tokens": 0,
"total_tokens": prompt_tokens
}
});
return Ok(());
}
if let Some(embeddings) = body.get("embeddings").and_then(|e| e.as_array()).cloned() {
let data: Vec<serde_json::Value> = embeddings
.into_iter()
.enumerate()
.map(|(index, embedding)| json!({"object": "embedding", "embedding": embedding, "index": index}))
.collect();
*body = json!({
"object": "list",
"data": data,
"model": "",
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0}
});
return Ok(());
}
Ok(())
}