Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion embeddings/manticoresearch_text_embeddings.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ using LoadModelFn = TextModelResult(*)(const char*,
const char*,
uintptr_t,
int32_t,
bool);
bool,
int32_t);

using FreeModelResultFn = void(*)(TextModelResult);

Expand Down
6 changes: 6 additions & 0 deletions embeddings/src/error_handling_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// Should fail due to empty API key (basic validation)
Expand Down Expand Up @@ -82,6 +83,7 @@ mod tests {
api_url_c.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// All should fail and have proper error messages
Expand Down Expand Up @@ -241,6 +243,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// Should fail due to empty API key (basic validation)
Expand Down Expand Up @@ -316,6 +319,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

if should_be_valid {
Expand Down Expand Up @@ -406,6 +410,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

if should_be_valid && model_id.starts_with("openai/") {
Expand Down Expand Up @@ -469,6 +474,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// Should fail with invalid API key
Expand Down
3 changes: 2 additions & 1 deletion embeddings/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type LoadModelFn = extern "C" fn(
usize, // api_url length
i32, // api_timeout: timeout in seconds (0 means use default, positive value is timeout in seconds)
bool, // use_gpu flag
i32, // max_input_tokens: cap on tokens per input text (0 means the model's own limit)
) -> TextModelResult;

type FreeModelResultFn = extern "C" fn(TextModelResult);
Expand Down Expand Up @@ -62,7 +63,7 @@ pub struct EmbedLib {
const VERSION_STR: &[u8] = concat!(env!("EMBEDDINGS_VERSION_STR"), "\0").as_bytes();

const LIB: EmbedLib = EmbedLib {
version: 4usize,
version: 5usize,
version_str: VERSION_STR.as_ptr() as *const c_char,
load_model: TextModelWrapper::load_model,
free_model_result: TextModelWrapper::free_model_result,
Expand Down
9 changes: 9 additions & 0 deletions embeddings/src/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mod tests {
api_url: None,
api_timeout: None,
use_gpu: Some(false),
max_input_tokens: None,
};

let result = create_model(options);
Expand Down Expand Up @@ -58,6 +59,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

if result.model.is_null() {
Expand Down Expand Up @@ -91,6 +93,7 @@ mod tests {
api_url: None,
api_timeout: None,
use_gpu: Some(false),
max_input_tokens: None,
};

let result = create_model(options);
Expand All @@ -113,6 +116,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

assert!(ffi_result.model.is_null());
Expand Down Expand Up @@ -142,6 +146,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// Should have error due to empty API key (basic validation)
Expand Down Expand Up @@ -180,6 +185,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

if !model_result.model.is_null() {
Expand Down Expand Up @@ -268,6 +274,7 @@ mod tests {
api_url: None,
api_timeout: None,
use_gpu: Some(use_gpu),
max_input_tokens: None,
};

let result = create_model(options);
Expand Down Expand Up @@ -316,6 +323,7 @@ mod tests {
api_url: None,
api_timeout: None,
use_gpu: Some(false),
max_input_tokens: None,
};

let result = create_model(options);
Expand Down Expand Up @@ -402,6 +410,7 @@ mod tests {
api_url_c.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// Should fail for all these cases
Expand Down
4 changes: 4 additions & 0 deletions embeddings/src/model/create_model_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fn test_create_model_allows_custom_openai_model_when_custom_api_url_is_set() {
api_url: Some("http://localhost:8080/v1/embeddings".to_string()),
api_timeout: None,
use_gpu: None,
max_input_tokens: None,
});

assert!(model.is_ok());
Expand All @@ -28,6 +29,7 @@ fn test_create_model_with_custom_url_still_uses_prefixed_jina_as_remote_signal()
api_url: Some("http://localhost:8080/v1/embeddings".to_string()),
api_timeout: None,
use_gpu: None,
max_input_tokens: None,
});

assert!(model.is_ok());
Expand All @@ -47,6 +49,7 @@ fn test_create_model_supports_explicit_openai_colon_syntax() {
api_url: Some("http://localhost:8080/v1/embeddings".to_string()),
api_timeout: None,
use_gpu: None,
max_input_tokens: None,
});

assert!(model.is_ok());
Expand All @@ -66,6 +69,7 @@ fn test_create_model_supports_explicit_openai_colon_syntax_with_simple_model() {
api_url: Some("http://localhost:8080/v1/embeddings".to_string()),
api_timeout: None,
use_gpu: None,
max_input_tokens: None,
});

assert!(model.is_ok());
Expand Down
6 changes: 6 additions & 0 deletions embeddings/src/model/ffi_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Unlimited timeout
false,
0, // max_input_tokens: model's own limit
);

if result.model.is_null() {
Expand Down Expand Up @@ -173,6 +174,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// Should fail with invalid model
Expand Down Expand Up @@ -208,6 +210,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// Should fail with empty API key (basic validation)
Expand Down Expand Up @@ -243,6 +246,7 @@ mod tests {
api_url.as_bytes().len(),
0, // Use default timeout
false,
0, // max_input_tokens: model's own limit
);

// Should fail with missing API key
Expand Down Expand Up @@ -475,6 +479,7 @@ mod tests {
api_url: None,
api_timeout: None,
use_gpu: Some(true),
max_input_tokens: None,
};

let options2 = ModelOptions {
Expand All @@ -484,6 +489,7 @@ mod tests {
api_url: None,
api_timeout: None,
use_gpu: None,
max_input_tokens: None,
};

assert_eq!(options1.model_id, "test-model");
Expand Down
31 changes: 31 additions & 0 deletions embeddings/src/model/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1118,11 +1118,42 @@ pub enum LocalModel {
}

impl LocalModel {
/// Loads a local model. `max_input_tokens` caps the tokens taken from each input
/// text; `None` keeps the model's own limit. The cap can only lower the limit.
pub fn new(
model_id: &str,
cache_path: PathBuf,
use_gpu: bool,
hf_token: Option<&str>,
max_input_tokens: Option<usize>,
) -> Result<Self, Box<dyn Error>> {
let mut model = Self::new_uncapped(model_id, cache_path, use_gpu, hf_token)?;
if let Some(cap) = max_input_tokens {
model.cap_max_input_len(cap);
}
Ok(model)
}

/// Lowers `max_input_len` of the loaded architecture to `cap` (never raises it).
/// All predict paths truncate on that field, so the cap applies everywhere.
fn cap_max_input_len(&mut self, cap: usize) {
if cap == 0 {
return;
}
match self {
LocalModel::Bert(m) => m.max_input_len = m.max_input_len.min(cap),
LocalModel::T5(m) => m.max_input_len = m.max_input_len.min(cap),
LocalModel::Causal(m) => m.max_input_len = m.max_input_len.min(cap),
LocalModel::Quantized(m) => m.max_input_len = m.max_input_len.min(cap),
LocalModel::Onnx(m) => m.max_input_len = m.max_input_len.min(cap),
}
}

fn new_uncapped(
model_id: &str,
cache_path: PathBuf,
use_gpu: bool,
hf_token: Option<&str>,
) -> Result<Self, Box<dyn Error>> {
let model_info = build_model_info(cache_path, model_id, "main", hf_token)?;
let config = std::fs::read_to_string(&model_info.config_path)
Expand Down
Loading