diff --git a/llama-cpp-bindings-sys/wrapper_chat_apply.cpp b/llama-cpp-bindings-sys/wrapper_chat_apply.cpp index 48d4c1799..35618c217 100644 --- a/llama-cpp-bindings-sys/wrapper_chat_apply.cpp +++ b/llama-cpp-bindings-sys/wrapper_chat_apply.cpp @@ -5,12 +5,11 @@ #include "llama.cpp/common/chat.h" #include "llama.cpp/common/json.h" #include "llama.cpp/include/llama.h" +#include "wrapper_report_current_exception.h" #include "wrapper_utils.h" #include -#include #include -#include #include #include @@ -86,19 +85,11 @@ extern "C" auto llama_rs_apply_chat_template( } return LLAMA_RS_APPLY_CHAT_TEMPLATE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_APPLY_CHAT_TEMPLATE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & ex) { - *out_error = llama_rs_dup_string(std::string(ex.what())); - if (*out_error == nullptr) { - return LLAMA_RS_APPLY_CHAT_TEMPLATE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_APPLY_CHAT_TEMPLATE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string(std::string("unknown c++ exception")); - if (*out_error == nullptr) { - return LLAMA_RS_APPLY_CHAT_TEMPLATE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_APPLY_CHAT_TEMPLATE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_APPLY_CHAT_TEMPLATE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_APPLY_CHAT_TEMPLATE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_APPLY_CHAT_TEMPLATE_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_chat_parse.cpp b/llama-cpp-bindings-sys/wrapper_chat_parse.cpp index 7fa33251c..b90302f7a 100644 --- a/llama-cpp-bindings-sys/wrapper_chat_parse.cpp +++ b/llama-cpp-bindings-sys/wrapper_chat_parse.cpp @@ -6,12 +6,11 @@ #include "llama.cpp/common/chat.h" #include "llama.cpp/common/json.h" #include "llama.cpp/include/llama.h" +#include "wrapper_report_current_exception.h" #include "wrapper_utils.h" #include -#include #include -#include #include #include @@ -35,24 +34,6 @@ void dup_or_set_alloc_flag(const std::string & source, char ** out_dup, bool * o *out_alloc_failed = (*out_dup == nullptr); } -auto report_current_parse_exception( - char ** out_error, - llama_rs_parse_chat_message_status thrown_status) -> llama_rs_parse_chat_message_status { - try { - throw; - } catch (const std::bad_alloc &) { - return LLAMA_RS_PARSE_CHAT_MESSAGE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & ex) { - *out_error = llama_rs_dup_string(std::string(ex.what())); - } catch (...) { - *out_error = llama_rs_dup_string(std::string("unknown c++ exception")); - } - if (*out_error == nullptr) { - return LLAMA_RS_PARSE_CHAT_MESSAGE_ERROR_STRING_ALLOCATION_FAILED; - } - return thrown_status; -} - auto parse_with_tools_parser( const common_peg_arena & chat_parser, const char * input, @@ -73,8 +54,11 @@ auto parse_with_tools_parser( return LLAMA_RS_PARSE_CHAT_MESSAGE_OK; } catch (...) { - return report_current_parse_exception( - out_error, LLAMA_RS_PARSE_CHAT_MESSAGE_LLAMA_CPP_THREW_CXX_EXCEPTION); + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_PARSE_CHAT_MESSAGE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_PARSE_CHAT_MESSAGE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_PARSE_CHAT_MESSAGE_ERROR_STRING_ALLOCATION_FAILED); } } } // namespace @@ -121,20 +105,12 @@ extern "C" auto llama_rs_chat_parser_create( *out_parser = parser_handle.release(); return LLAMA_RS_CHAT_PARSER_CREATE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_CHAT_PARSER_CREATE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & ex) { - *out_error = llama_rs_dup_string(std::string(ex.what())); - if (*out_error == nullptr) { - return LLAMA_RS_CHAT_PARSER_CREATE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_CHAT_PARSER_CREATE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string(std::string("unknown c++ exception")); - if (*out_error == nullptr) { - return LLAMA_RS_CHAT_PARSER_CREATE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_CHAT_PARSER_CREATE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_CHAT_PARSER_CREATE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_CHAT_PARSER_CREATE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_CHAT_PARSER_CREATE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -147,24 +123,12 @@ extern "C" auto llama_rs_chat_parser_free( try { const std::unique_ptr reclaimed(parser); return LLAMA_RS_CHAT_PARSER_FREE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_CHAT_PARSER_FREE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_CHAT_PARSER_FREE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_CHAT_PARSER_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_CHAT_PARSER_FREE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_CHAT_PARSER_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_CHAT_PARSER_FREE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_CHAT_PARSER_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION, + LLAMA_RS_CHAT_PARSER_FREE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -207,20 +171,12 @@ extern "C" auto llama_rs_chat_tools_parser_create( *out_tools_parser = tools_parser_handle.release(); return LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & ex) { - *out_error = llama_rs_dup_string(std::string(ex.what())); - if (*out_error == nullptr) { - return LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string(std::string("unknown c++ exception")); - if (*out_error == nullptr) { - return LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_CHAT_TOOLS_PARSER_CREATE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -233,24 +189,12 @@ extern "C" auto llama_rs_chat_tools_parser_free( try { const std::unique_ptr reclaimed(tools_parser); return LLAMA_RS_CHAT_TOOLS_PARSER_FREE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_CHAT_TOOLS_PARSER_FREE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_CHAT_TOOLS_PARSER_FREE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_CHAT_TOOLS_PARSER_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_CHAT_TOOLS_PARSER_FREE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_CHAT_TOOLS_PARSER_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_CHAT_TOOLS_PARSER_FREE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_CHAT_TOOLS_PARSER_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION, + LLAMA_RS_CHAT_TOOLS_PARSER_FREE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -291,24 +235,12 @@ extern "C" auto llama_rs_parsed_chat_free( try { const std::unique_ptr reclaimed(handle); return LLAMA_RS_PARSED_CHAT_FREE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_PARSED_CHAT_FREE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_FREE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_FREE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_PARSED_CHAT_FREE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_PARSED_CHAT_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION, + LLAMA_RS_PARSED_CHAT_FREE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -331,24 +263,12 @@ extern "C" auto llama_rs_parsed_chat_tool_call_count( try { *out_count = handle->message.tool_calls.size(); return LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_ERROR_STRING_ALLOCATION_FAILED); } } @@ -379,24 +299,12 @@ extern "C" auto llama_rs_parsed_chat_tool_call_id( return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_ERROR_STRING_ALLOCATION_FAILED; } return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_ERROR_STRING_ALLOCATION_FAILED); } } @@ -427,24 +335,12 @@ extern "C" auto llama_rs_parsed_chat_tool_call_name( return LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_ERROR_STRING_ALLOCATION_FAILED; } return LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_ERROR_STRING_ALLOCATION_FAILED); } } @@ -476,24 +372,12 @@ extern "C" auto llama_rs_parsed_chat_tool_call_arguments( return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_ERROR_STRING_ALLOCATION_FAILED; } return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_ERROR_STRING_ALLOCATION_FAILED); } } @@ -520,24 +404,12 @@ extern "C" auto llama_rs_parsed_chat_content( return LLAMA_RS_PARSED_CHAT_CONTENT_ERROR_STRING_ALLOCATION_FAILED; } return LLAMA_RS_PARSED_CHAT_CONTENT_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_PARSED_CHAT_CONTENT_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_CONTENT_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_CONTENT_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_CONTENT_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_CONTENT_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_PARSED_CHAT_CONTENT_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_PARSED_CHAT_CONTENT_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_PARSED_CHAT_CONTENT_ERROR_STRING_ALLOCATION_FAILED); } } @@ -564,23 +436,11 @@ extern "C" auto llama_rs_parsed_chat_reasoning_content( return LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_ERROR_STRING_ALLOCATION_FAILED; } return LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_common.cpp b/llama-cpp-bindings-sys/wrapper_common.cpp index 5f43c3e9b..cf653e7da 100644 --- a/llama-cpp-bindings-sys/wrapper_common.cpp +++ b/llama-cpp-bindings-sys/wrapper_common.cpp @@ -3,9 +3,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -13,6 +11,7 @@ #include "llama.cpp/common/json-schema-to-grammar.h" #include "llama.cpp/common/json.h" #include "llama.cpp/include/llama.h" +#include "wrapper_report_current_exception.h" #include "wrapper_utils.h" extern "C" auto llama_rs_json_schema_to_grammar( @@ -44,26 +43,18 @@ extern "C" auto llama_rs_json_schema_to_grammar( return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED; } return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_LLAMA_CPP_OUT_OF_MEMORY; } catch (const std::invalid_argument & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_INVALID_SCHEMA; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_exception_message( + out_error, + err.what(), + LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_INVALID_SCHEMA, + LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED); } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_JSON_SCHEMA_TO_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED); } } @@ -71,7 +62,7 @@ extern "C" auto llama_rs_string_dup(const char * value) -> char * { if (value == nullptr) { return nullptr; } - return llama_rs_dup_string(std::string(value)); + return llama_rs_dup_string(value); } extern "C" void llama_rs_string_free(char * ptr) { @@ -102,20 +93,12 @@ extern "C" auto llama_rs_sampler_init_grammar( return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_RETURNED_NULL; } return LLAMA_RS_SAMPLER_INIT_GRAMMAR_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_SAMPLER_INIT_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED); } } @@ -154,26 +137,18 @@ extern "C" auto llama_rs_sampler_init_grammar_lazy_patterns( return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_RETURNED_NULL; } return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_OUT_OF_MEMORY; } catch (const std::regex_error & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_INVALID_TRIGGER_PATTERN; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_exception_message( + out_error, + err.what(), + LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_INVALID_TRIGGER_PATTERN, + LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_ERROR_STRING_ALLOCATION_FAILED); } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_ERROR_STRING_ALLOCATION_FAILED); } } @@ -209,20 +184,12 @@ extern "C" auto llama_rs_memory_seq_pos_max( *out_position = llama_memory_seq_pos_max(mem, seq_id); return LLAMA_RS_MEMORY_SEQ_POS_MAX_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_MEMORY_SEQ_POS_MAX_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MEMORY_SEQ_POS_MAX_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_MEMORY_SEQ_POS_MAX_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MEMORY_SEQ_POS_MAX_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_MEMORY_SEQ_POS_MAX_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MEMORY_SEQ_POS_MAX_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MEMORY_SEQ_POS_MAX_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MEMORY_SEQ_POS_MAX_ERROR_STRING_ALLOCATION_FAILED); } } @@ -262,24 +229,12 @@ extern "C" auto llama_rs_encode( return LLAMA_RS_ENCODE_LLAMA_CPP_RETURNED_NONZERO_CODE; } return LLAMA_RS_ENCODE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_ENCODE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_ENCODE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_ENCODE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_ENCODE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_ENCODE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_ENCODE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_ENCODE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_ENCODE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -311,24 +266,12 @@ extern "C" auto llama_rs_memory_seq_add( } llama_memory_seq_add(mem, seq_id, pos_start, pos_end, shift); return LLAMA_RS_MEMORY_SEQ_ADD_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_MEMORY_SEQ_ADD_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MEMORY_SEQ_ADD_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MEMORY_SEQ_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MEMORY_SEQ_ADD_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MEMORY_SEQ_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MEMORY_SEQ_ADD_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MEMORY_SEQ_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MEMORY_SEQ_ADD_ERROR_STRING_ALLOCATION_FAILED); } } @@ -360,24 +303,12 @@ extern "C" auto llama_rs_memory_seq_div( } llama_memory_seq_div(mem, seq_id, pos_start, pos_end, divisor); return LLAMA_RS_MEMORY_SEQ_DIV_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_MEMORY_SEQ_DIV_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MEMORY_SEQ_DIV_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MEMORY_SEQ_DIV_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MEMORY_SEQ_DIV_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MEMORY_SEQ_DIV_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MEMORY_SEQ_DIV_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MEMORY_SEQ_DIV_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MEMORY_SEQ_DIV_ERROR_STRING_ALLOCATION_FAILED); } } @@ -405,20 +336,12 @@ extern "C" auto llama_rs_sampler_sample( try { *out_token = llama_sampler_sample(sampler, ctx, idx); return LLAMA_RS_SAMPLER_SAMPLE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_SAMPLER_SAMPLE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_SAMPLE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_SAMPLE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_SAMPLE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_SAMPLE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_SAMPLER_SAMPLE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_SAMPLER_SAMPLE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_SAMPLER_SAMPLE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -438,20 +361,12 @@ extern "C" auto llama_rs_sampler_accept( try { llama_sampler_accept(sampler, token); return LLAMA_RS_SAMPLER_ACCEPT_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_SAMPLER_ACCEPT_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_ACCEPT_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_ACCEPT_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_ACCEPT_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_ACCEPT_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_SAMPLER_ACCEPT_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_SAMPLER_ACCEPT_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_SAMPLER_ACCEPT_ERROR_STRING_ALLOCATION_FAILED); } } @@ -481,20 +396,12 @@ extern "C" auto llama_rs_load_model_from_file( return LLAMA_RS_LOAD_MODEL_FROM_FILE_LLAMA_CPP_RETURNED_NULL; } return LLAMA_RS_LOAD_MODEL_FROM_FILE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_LOAD_MODEL_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_LOAD_MODEL_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_LOAD_MODEL_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_LOAD_MODEL_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_LOAD_MODEL_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_LOAD_MODEL_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_LOAD_MODEL_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_LOAD_MODEL_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -524,20 +431,12 @@ extern "C" auto llama_rs_new_context_with_model( return LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_RETURNED_NULL; } return LLAMA_RS_NEW_CONTEXT_WITH_MODEL_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_NEW_CONTEXT_WITH_MODEL_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_NEW_CONTEXT_WITH_MODEL_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_NEW_CONTEXT_WITH_MODEL_ERROR_STRING_ALLOCATION_FAILED); } } @@ -573,20 +472,12 @@ extern "C" auto llama_rs_decode( return LLAMA_RS_DECODE_LLAMA_CPP_RETURNED_NONZERO_CODE; } return LLAMA_RS_DECODE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_DECODE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_DECODE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_DECODE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_DECODE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_DECODE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_DECODE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_DECODE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_DECODE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -623,20 +514,12 @@ extern "C" auto llama_rs_tokenize( vocab, text, text_len, tokens, n_tokens_max, add_special, parse_special); *out_returned_count = count; return LLAMA_RS_TOKENIZE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_TOKENIZE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_TOKENIZE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_TOKENIZE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -659,19 +542,11 @@ extern "C" auto llama_rs_sampler_apply( try { llama_sampler_apply(sampler, data_array); return LLAMA_RS_SAMPLER_APPLY_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_SAMPLER_APPLY_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_APPLY_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_APPLY_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_APPLY_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_SAMPLER_APPLY_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_SAMPLER_APPLY_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_SAMPLER_APPLY_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_SAMPLER_APPLY_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_context.cpp b/llama-cpp-bindings-sys/wrapper_context.cpp index e452c28c9..4b06b775a 100644 --- a/llama-cpp-bindings-sys/wrapper_context.cpp +++ b/llama-cpp-bindings-sys/wrapper_context.cpp @@ -1,19 +1,28 @@ #include "wrapper_context.h" -#include #include #include "llama.cpp/include/llama.h" #include "llama.cpp/src/llama-context.h" #include "llama.cpp/src/llama-ext.h" +#include "llama.cpp/src/llama-memory.h" #include "llama.cpp/src/llama-model.h" +#include "wrapper_report_current_exception.h" extern "C" auto llama_rs_context_decodes_batches_in_one_micro_batch(const struct llama_context * ctx) -> bool { return llama_get_memory(ctx) == nullptr || !ctx->get_cparams().causal_attn; } -extern "C" auto llama_rs_context_embedding_row_length(const struct llama_context * ctx) -> size_t { - return ctx->get_model().hparams.n_embd_out(); +extern "C" auto llama_rs_context_embeddings_enabled(const struct llama_context * ctx) -> bool { + return ctx->get_cparams().embeddings; +} + +extern "C" auto llama_rs_context_embeddings_nextn_enabled(const struct llama_context * ctx) -> bool { + return ctx->get_cparams().embeddings_nextn; +} + +extern "C" auto llama_rs_context_embeddings_nextn_masked(const struct llama_context * ctx) -> bool { + return ctx->get_cparams().embeddings_nextn_masked; } extern "C" void llama_rs_set_embeddings_nextn(struct llama_context * ctx, bool value, bool masked) { @@ -24,6 +33,55 @@ extern "C" auto llama_rs_get_embeddings_nextn_ith(struct llama_context * ctx, in return llama_get_embeddings_nextn_ith(ctx, token_index); } +extern "C" auto llama_rs_context_graph_produces_nextn_hidden_state( + struct llama_context * ctx, + bool * out_produces_nextn_hidden_state, + char ** out_error) -> llama_rs_context_nextn_hidden_state_probe_status { + if (out_produces_nextn_hidden_state != nullptr) { + *out_produces_nextn_hidden_state = false; + } + if (out_error != nullptr) { + *out_error = nullptr; + } + if (ctx == nullptr) { + return LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_CTX_ARG; + } + if (out_produces_nextn_hidden_state == nullptr) { + return LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_OUT_PRODUCES_NEXTN_HIDDEN_STATE_ARG; + } + if (out_error == nullptr) { + return LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_OUT_ERROR_ARG; + } + try { + constexpr uint32_t smallest_ubatch_tokens = 1; + constexpr uint32_t smallest_ubatch_sequences = 1; + constexpr uint32_t smallest_ubatch_outputs = 1; + constexpr bool split_graph_without_allocating_buffers = true; + + llama_memory_context_ptr memory_context; + llama_memory_t memory = llama_get_memory(ctx); + if (memory != nullptr) { + memory_context = memory->init_full(); + } + if (ctx->graph_reserve( + smallest_ubatch_tokens, + smallest_ubatch_sequences, + smallest_ubatch_outputs, + memory_context.get(), + split_graph_without_allocating_buffers) == nullptr) { + return LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_FAILED_TO_RESERVE_GRAPH; + } + *out_produces_nextn_hidden_state = ctx->get_gf_res_reserve()->get_h_nextn() != nullptr; + return LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_OK; + } catch (...) { + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_ERROR_STRING_ALLOCATION_FAILED); + } +} + extern "C" auto llama_rs_model_causal_attn(const struct llama_model * model) -> bool { return model->hparams.causal_attn; } diff --git a/llama-cpp-bindings-sys/wrapper_context.h b/llama-cpp-bindings-sys/wrapper_context.h index d5dfbce60..c8d58b28c 100644 --- a/llama-cpp-bindings-sys/wrapper_context.h +++ b/llama-cpp-bindings-sys/wrapper_context.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #ifdef __cplusplus @@ -11,14 +10,34 @@ extern "C" { struct llama_context; struct llama_model; +typedef enum llama_rs_context_nextn_hidden_state_probe_status { + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_OK = 0, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_CTX_ARG, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_OUT_PRODUCES_NEXTN_HIDDEN_STATE_ARG, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_OUT_ERROR_ARG, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_FAILED_TO_RESERVE_GRAPH, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_ERROR_STRING_ALLOCATION_FAILED, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_THREW_CXX_EXCEPTION, +} llama_rs_context_nextn_hidden_state_probe_status; + bool llama_rs_context_decodes_batches_in_one_micro_batch(const struct llama_context * ctx); -size_t llama_rs_context_embedding_row_length(const struct llama_context * ctx); +bool llama_rs_context_embeddings_enabled(const struct llama_context * ctx); + +bool llama_rs_context_embeddings_nextn_enabled(const struct llama_context * ctx); + +bool llama_rs_context_embeddings_nextn_masked(const struct llama_context * ctx); void llama_rs_set_embeddings_nextn(struct llama_context * ctx, bool value, bool masked); float * llama_rs_get_embeddings_nextn_ith(struct llama_context * ctx, int32_t token_index); +llama_rs_context_nextn_hidden_state_probe_status llama_rs_context_graph_produces_nextn_hidden_state( + struct llama_context * ctx, + bool * out_produces_nextn_hidden_state, + char ** out_error); + bool llama_rs_model_causal_attn(const struct llama_model * model); #ifdef __cplusplus diff --git a/llama-cpp-bindings-sys/wrapper_fit.cpp b/llama-cpp-bindings-sys/wrapper_fit.cpp index efd32ac88..7cb39e651 100644 --- a/llama-cpp-bindings-sys/wrapper_fit.cpp +++ b/llama-cpp-bindings-sys/wrapper_fit.cpp @@ -1,12 +1,10 @@ #include "wrapper_fit.h" #include "llama.h" #include "ggml.h" -#include "wrapper_utils.h" +#include "wrapper_report_current_exception.h" #include #include -#include -#include #include "llama.cpp/common/fit.h" @@ -77,19 +75,11 @@ extern "C" auto llama_rs_fit_params( } *out_unrecognized_status_code = static_cast(status); return LLAMA_RS_FIT_PARAMS_LLAMA_CPP_RETURNED_UNRECOGNIZED_STATUS_CODE; - } catch (const std::bad_alloc &) { - return LLAMA_RS_FIT_PARAMS_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_FIT_PARAMS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_FIT_PARAMS_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_FIT_PARAMS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_FIT_PARAMS_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_FIT_PARAMS_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_FIT_PARAMS_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_FIT_PARAMS_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_gbnf.cpp b/llama-cpp-bindings-sys/wrapper_gbnf.cpp index 94d52bac8..2cff7b500 100644 --- a/llama-cpp-bindings-sys/wrapper_gbnf.cpp +++ b/llama-cpp-bindings-sys/wrapper_gbnf.cpp @@ -1,10 +1,8 @@ #include "wrapper_gbnf.h" -#include "wrapper_utils.h" +#include "wrapper_report_current_exception.h" #include "llama.cpp/src/llama-grammar.h" -#include -#include extern "C" auto llama_rs_validate_gbnf( const struct llama_vocab * vocab, @@ -48,19 +46,11 @@ extern "C" auto llama_rs_validate_gbnf( llama_grammar_free_impl(grammar); return LLAMA_RS_GBNF_VALIDATION_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_GBNF_VALIDATION_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_GBNF_VALIDATION_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_GBNF_VALIDATION_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_GBNF_VALIDATION_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_GBNF_VALIDATION_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_GBNF_VALIDATION_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_GBNF_VALIDATION_THREW_CXX_EXCEPTION, + LLAMA_RS_GBNF_VALIDATION_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_mtmd.cpp b/llama-cpp-bindings-sys/wrapper_mtmd.cpp index 95b13defc..b56a0fb78 100644 --- a/llama-cpp-bindings-sys/wrapper_mtmd.cpp +++ b/llama-cpp-bindings-sys/wrapper_mtmd.cpp @@ -2,13 +2,12 @@ #include "llama.h" #include "tools/mtmd/mtmd.h" #include "tools/mtmd/mtmd-helper.h" -#include "wrapper_utils.h" +#include "wrapper_report_current_exception.h" +#include #include #include -#include -#include -#include +#include extern "C" auto llama_rs_mtmd_init_from_file( const char * mmproj_path, @@ -37,24 +36,12 @@ extern "C" auto llama_rs_mtmd_init_from_file( } *out_ctx = ctx; return LLAMA_RS_MTMD_INIT_FROM_FILE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_MTMD_INIT_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_INIT_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_INIT_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MTMD_INIT_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MTMD_INIT_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MTMD_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -86,24 +73,12 @@ extern "C" auto llama_rs_mtmd_bitmap_init_from_file( } *out_bitmap = bitmap; return LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -136,24 +111,12 @@ extern "C" auto llama_rs_mtmd_bitmap_init_from_buf( } *out_bitmap = bitmap; return LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MTMD_BITMAP_INIT_FROM_BUF_ERROR_STRING_ALLOCATION_FAILED); } } @@ -192,31 +155,19 @@ extern "C" auto llama_rs_mtmd_tokenize( case 1: return LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_BITMAP_COUNT_DOES_NOT_MATCH_MARKER_COUNT; case 2: - return LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_IMAGE_PREPROCESSING_ERROR; + return LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_PREPROCESSING_OR_TOKENIZATION_FAILURE; default: if (out_undocumented_return_code != nullptr) { *out_undocumented_return_code = result; } return LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_RETURNED_UNDOCUMENTED_NONZERO_CODE; } - } catch (const std::bad_alloc &) { - return LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MTMD_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED); } } @@ -243,6 +194,12 @@ extern "C" auto llama_rs_mtmd_tokenize_from_parts( if (num_parts > 0 && (parts == nullptr)) { return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_PARTS_ARG_WHEN_NUM_PARTS_NONZERO; } + const gsl::span part_span(parts, num_parts); + if (std::any_of(part_span.begin(), part_span.end(), [](const struct mtmd_input_part * part) -> bool { + return part == nullptr; + })) { + return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_PART_ELEMENT; + } try { int32_t const result = mtmd_tokenize_from_parts(ctx, output, parts, num_parts, add_special); @@ -252,31 +209,19 @@ extern "C" auto llama_rs_mtmd_tokenize_from_parts( case 1: return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MALFORMED_PART; case 2: - return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MEDIA_PREPROCESSING_ERROR; + return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_PREPROCESSING_OR_TOKENIZATION_FAILURE; default: if (out_undocumented_return_code != nullptr) { *out_undocumented_return_code = result; } return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_RETURNED_UNDOCUMENTED_NONZERO_CODE; } - } catch (const std::bad_alloc &) { - return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_ERROR_STRING_ALLOCATION_FAILED); } } @@ -307,24 +252,12 @@ extern "C" auto llama_rs_mtmd_encode_chunk( return LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_RETURNED_NONZERO_CODE; } return LLAMA_RS_MTMD_ENCODE_CHUNK_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_ENCODE_CHUNK_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_ENCODE_CHUNK_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MTMD_ENCODE_CHUNK_ERROR_STRING_ALLOCATION_FAILED); } } @@ -368,23 +301,11 @@ extern "C" auto llama_rs_mtmd_eval_chunk_single( return LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_RETURNED_NONZERO_CODE; } return LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_mtmd.h b/llama-cpp-bindings-sys/wrapper_mtmd.h index f5927a7a3..192d7a237 100644 --- a/llama-cpp-bindings-sys/wrapper_mtmd.h +++ b/llama-cpp-bindings-sys/wrapper_mtmd.h @@ -71,7 +71,7 @@ typedef enum llama_rs_mtmd_tokenize_status { LLAMA_RS_MTMD_TOKENIZE_NULL_TEXT_ARG, LLAMA_RS_MTMD_TOKENIZE_NULL_BITMAPS_ARG_WHEN_NUM_BITMAPS_NONZERO, LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_BITMAP_COUNT_DOES_NOT_MATCH_MARKER_COUNT, - LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_IMAGE_PREPROCESSING_ERROR, + LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_PREPROCESSING_OR_TOKENIZATION_FAILURE, LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_RETURNED_UNDOCUMENTED_NONZERO_CODE, LLAMA_RS_MTMD_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED, LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY, @@ -92,8 +92,9 @@ typedef enum llama_rs_mtmd_tokenize_from_parts_status { LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_CTX_ARG, LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_OUTPUT_ARG, LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_PARTS_ARG_WHEN_NUM_PARTS_NONZERO, + LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_PART_ELEMENT, LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MALFORMED_PART, - LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MEDIA_PREPROCESSING_ERROR, + LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_PREPROCESSING_OR_TOKENIZATION_FAILURE, LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_RETURNED_UNDOCUMENTED_NONZERO_CODE, LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_ERROR_STRING_ALLOCATION_FAILED, LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_OUT_OF_MEMORY, diff --git a/llama-cpp-bindings-sys/wrapper_reasoning.cpp b/llama-cpp-bindings-sys/wrapper_reasoning.cpp index a5f847bc4..327b673e5 100644 --- a/llama-cpp-bindings-sys/wrapper_reasoning.cpp +++ b/llama-cpp-bindings-sys/wrapper_reasoning.cpp @@ -5,12 +5,10 @@ #include "llama.cpp/common/chat.h" #include "llama.cpp/common/json.h" #include "llama.cpp/include/llama.h" -#include "wrapper_utils.h" +#include "wrapper_report_current_exception.h" #include -#include #include -#include #include #include #include @@ -109,20 +107,12 @@ extern "C" auto llama_rs_detect_reasoning_markers( *out_markers = detected.release(); return LLAMA_RS_DETECT_REASONING_MARKERS_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_DETECT_REASONING_MARKERS_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & ex) { - *out_error = llama_rs_dup_string(std::string(ex.what())); - if (*out_error == nullptr) { - return LLAMA_RS_DETECT_REASONING_MARKERS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_DETECT_REASONING_MARKERS_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string(std::string("unknown c++ exception")); - if (*out_error == nullptr) { - return LLAMA_RS_DETECT_REASONING_MARKERS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_DETECT_REASONING_MARKERS_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_DETECT_REASONING_MARKERS_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_DETECT_REASONING_MARKERS_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_DETECT_REASONING_MARKERS_ERROR_STRING_ALLOCATION_FAILED); } } @@ -160,23 +150,11 @@ extern "C" auto llama_rs_reasoning_markers_free( try { const std::unique_ptr reclaimed(markers); return LLAMA_RS_REASONING_MARKERS_FREE_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_REASONING_MARKERS_FREE_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string(err.what()); - if (*out_error == nullptr) { - return LLAMA_RS_REASONING_MARKERS_FREE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_REASONING_MARKERS_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION; } catch (...) { - if (out_error != nullptr) { - *out_error = llama_rs_dup_string("unknown c++ exception"); - if (*out_error == nullptr) { - return LLAMA_RS_REASONING_MARKERS_FREE_ERROR_STRING_ALLOCATION_FAILED; - } - } - return LLAMA_RS_REASONING_MARKERS_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_REASONING_MARKERS_FREE_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_REASONING_MARKERS_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION, + LLAMA_RS_REASONING_MARKERS_FREE_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_report_current_exception.h b/llama-cpp-bindings-sys/wrapper_report_current_exception.h new file mode 100644 index 000000000..2faa0b20f --- /dev/null +++ b/llama-cpp-bindings-sys/wrapper_report_current_exception.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +#include "wrapper_utils.h" + +template +auto llama_rs_report_exception_message( + char ** out_error, + const char * message, + TStatus reported_status, + TStatus error_string_allocation_failed_status) noexcept -> TStatus { + if (out_error == nullptr) { + return reported_status; + } + *out_error = llama_rs_dup_string(message); + if (*out_error == nullptr) { + return error_string_allocation_failed_status; + } + return reported_status; +} + +template +auto llama_rs_report_current_exception( + char ** out_error, + TStatus out_of_memory_status, + TStatus threw_cxx_exception_status, + TStatus error_string_allocation_failed_status) noexcept -> TStatus { + try { + throw; + } catch (const std::bad_alloc &) { + return out_of_memory_status; + } catch (const std::exception & err) { + return llama_rs_report_exception_message( + out_error, err.what(), threw_cxx_exception_status, error_string_allocation_failed_status); + } catch (...) { + return llama_rs_report_exception_message( + out_error, "unknown c++ exception", threw_cxx_exception_status, error_string_allocation_failed_status); + } +} diff --git a/llama-cpp-bindings-sys/wrapper_sampler_init.cpp b/llama-cpp-bindings-sys/wrapper_sampler_init.cpp index a71ed5320..ee26f71bc 100644 --- a/llama-cpp-bindings-sys/wrapper_sampler_init.cpp +++ b/llama-cpp-bindings-sys/wrapper_sampler_init.cpp @@ -1,25 +1,13 @@ #include "wrapper_sampler_init.h" #include "llama.cpp/include/llama.h" -#include "wrapper_utils.h" +#include "wrapper_report_current_exception.h" #include #include -#include -#include -#include namespace { -auto describe_init_failure(char ** out_error, const char * message) -> llama_rs_sampler_init_status { - *out_error = llama_rs_dup_string(std::string(message)); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_INIT_ERROR_STRING_ALLOCATION_FAILED; - } - - return LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_THREW_CXX_EXCEPTION; -} - template auto init_sampler( struct llama_sampler ** out_sampler, @@ -44,25 +32,15 @@ auto init_sampler( } return LLAMA_RS_SAMPLER_INIT_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - return describe_init_failure(out_error, err.what()); } catch (...) { - return describe_init_failure(out_error, "unknown c++ exception"); + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_SAMPLER_INIT_ERROR_STRING_ALLOCATION_FAILED); } } -auto describe_chain_add_failure(char ** out_error, const char * message) - -> llama_rs_sampler_chain_add_status { - *out_error = llama_rs_dup_string(std::string(message)); - if (*out_error == nullptr) { - return LLAMA_RS_SAMPLER_CHAIN_ADD_ERROR_STRING_ALLOCATION_FAILED; - } - - return LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION; -} - } // namespace extern "C" auto llama_rs_sampler_init( @@ -268,11 +246,11 @@ extern "C" auto llama_rs_sampler_chain_add( llama_sampler_chain_add(chain, sampler); return LLAMA_RS_SAMPLER_CHAIN_ADD_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - return describe_chain_add_failure(out_error, err.what()); } catch (...) { - return describe_chain_add_failure(out_error, "unknown c++ exception"); + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_SAMPLER_CHAIN_ADD_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_state.cpp b/llama-cpp-bindings-sys/wrapper_state.cpp index f40919868..0fef5235b 100644 --- a/llama-cpp-bindings-sys/wrapper_state.cpp +++ b/llama-cpp-bindings-sys/wrapper_state.cpp @@ -1,25 +1,13 @@ #include "wrapper_state.h" #include "llama.cpp/include/llama.h" -#include "wrapper_utils.h" +#include "wrapper_report_current_exception.h" #include #include -#include -#include -#include namespace { -auto describe_failure(char ** out_error, const char * message) -> llama_rs_state_data_status { - *out_error = llama_rs_dup_string(std::string(message)); - if (*out_error == nullptr) { - return LLAMA_RS_STATE_DATA_ERROR_STRING_ALLOCATION_FAILED; - } - - return LLAMA_RS_STATE_DATA_LLAMA_CPP_THREW_CXX_EXCEPTION; -} - auto validate_arguments( const struct llama_context * ctx, const void * buffer, @@ -66,12 +54,12 @@ extern "C" auto llama_rs_state_get_data( *out_byte_count = llama_state_get_data(ctx, dst, size); return LLAMA_RS_STATE_DATA_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - return describe_failure(out_error, err.what()); } catch (...) { - return describe_failure(out_error, "unknown c++ exception"); + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_STATE_DATA_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_STATE_DATA_ERROR_STRING_ALLOCATION_FAILED); } } @@ -98,12 +86,12 @@ extern "C" auto llama_rs_state_set_data( *out_byte_count = llama_state_set_data(ctx, src, size); return LLAMA_RS_STATE_DATA_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - return describe_failure(out_error, err.what()); } catch (...) { - return describe_failure(out_error, "unknown c++ exception"); + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_STATE_DATA_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_STATE_DATA_ERROR_STRING_ALLOCATION_FAILED); } } @@ -132,12 +120,12 @@ extern "C" auto llama_rs_state_seq_get_data( *out_byte_count = llama_state_seq_get_data_ext(ctx, dst, size, seq_id, flags); return LLAMA_RS_STATE_DATA_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - return describe_failure(out_error, err.what()); } catch (...) { - return describe_failure(out_error, "unknown c++ exception"); + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_STATE_DATA_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_STATE_DATA_ERROR_STRING_ALLOCATION_FAILED); } } @@ -166,11 +154,11 @@ extern "C" auto llama_rs_state_seq_set_data( *out_byte_count = llama_state_seq_set_data_ext(ctx, src, size, dest_seq_id, flags); return LLAMA_RS_STATE_DATA_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & err) { - return describe_failure(out_error, err.what()); } catch (...) { - return describe_failure(out_error, "unknown c++ exception"); + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_STATE_DATA_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_STATE_DATA_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_tool_calls.cpp b/llama-cpp-bindings-sys/wrapper_tool_calls.cpp index e42c0e64a..c742e50f8 100644 --- a/llama-cpp-bindings-sys/wrapper_tool_calls.cpp +++ b/llama-cpp-bindings-sys/wrapper_tool_calls.cpp @@ -6,11 +6,10 @@ #include "llama.cpp/common/chat.h" #include "llama.cpp/common/json.h" #include "llama.cpp/include/llama.h" +#include "wrapper_report_current_exception.h" #include "wrapper_utils.h" -#include #include -#include #include using wrapper_helpers::token_text_or_empty; @@ -159,20 +158,12 @@ extern "C" auto llama_rs_compute_tool_call_haystack( *out_haystack = haystack_dup; return LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & ex) { - *out_error = llama_rs_dup_string(std::string(ex.what())); - if (*out_error == nullptr) { - return LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string(std::string("unknown c++ exception")); - if (*out_error == nullptr) { - return LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_ERROR_STRING_ALLOCATION_FAILED); } } @@ -235,19 +226,11 @@ extern "C" auto llama_rs_diagnose_tool_call_synthetic_renders( *out_with_tools = b_dup.release(); return LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_OK; - } catch (const std::bad_alloc &) { - return LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_LLAMA_CPP_OUT_OF_MEMORY; - } catch (const std::exception & ex) { - *out_error = llama_rs_dup_string(std::string(ex.what())); - if (*out_error == nullptr) { - return LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_LLAMA_CPP_THREW_CXX_EXCEPTION; } catch (...) { - *out_error = llama_rs_dup_string(std::string("unknown c++ exception")); - if (*out_error == nullptr) { - return LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_ERROR_STRING_ALLOCATION_FAILED; - } - return LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_LLAMA_CPP_THREW_CXX_EXCEPTION; + return llama_rs_report_current_exception( + out_error, + LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_LLAMA_CPP_OUT_OF_MEMORY, + LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_LLAMA_CPP_THREW_CXX_EXCEPTION, + LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_ERROR_STRING_ALLOCATION_FAILED); } } diff --git a/llama-cpp-bindings-sys/wrapper_utils.h b/llama-cpp-bindings-sys/wrapper_utils.h index 60f325a9c..43ee99ce6 100644 --- a/llama-cpp-bindings-sys/wrapper_utils.h +++ b/llama-cpp-bindings-sys/wrapper_utils.h @@ -9,14 +9,22 @@ #include #include -static inline char * llama_rs_dup_string(const std::string & value) { - char * buffer = new (std::nothrow) char[value.size() + 1]; +static inline char * llama_rs_dup_bytes(const char * data, size_t size) noexcept { + char * buffer = new (std::nothrow) char[size + 1]; if (!buffer) { return nullptr; } - std::memcpy(buffer, value.data(), value.size()); - buffer[value.size()] = '\0'; + std::memcpy(buffer, data, size); + buffer[size] = '\0'; return buffer; } +static inline char * llama_rs_dup_string(const std::string & value) noexcept { + return llama_rs_dup_bytes(value.data(), value.size()); +} + +static inline char * llama_rs_dup_string(const char * value) noexcept { + return llama_rs_dup_bytes(value, std::strlen(value)); +} + #endif diff --git a/llama-cpp-bindings-tests/tests/chat_protocol.rs b/llama-cpp-bindings-tests/tests/chat_protocol.rs index 4827b03f6..0c4f13722 100644 --- a/llama-cpp-bindings-tests/tests/chat_protocol.rs +++ b/llama-cpp-bindings-tests/tests/chat_protocol.rs @@ -368,7 +368,7 @@ fn parses_with_input_null_byte_reports_the_input_as_the_source( let result = ChatMessageParser::new(fixture.model, "[]")?.parse("hello\0world", false); let Err(ParseChatMessageError::InputContainsNulByte(nul_error)) = result else { - anyhow::bail!("a NUL byte in the message must be reported against the message"); + bail!("a NUL byte in the message must be reported against the message"); }; assert_eq!(nul_error.nul_position(), 5); diff --git a/llama-cpp-bindings-tests/tests/context_state.rs b/llama-cpp-bindings-tests/tests/context_state.rs index 2461b825e..110738792 100644 --- a/llama-cpp-bindings-tests/tests/context_state.rs +++ b/llama-cpp-bindings-tests/tests/context_state.rs @@ -4,11 +4,14 @@ use std::sync::Arc; use std::sync::atomic::AtomicBool; use anyhow::Result; +use anyhow::anyhow; use llama_cpp_bindings::DecodeError; +use llama_cpp_bindings::EmbeddingsError; use llama_cpp_bindings::LlamaContextLoadError; use llama_cpp_bindings::LogitsError; use llama_cpp_bindings::context::LlamaContext; use llama_cpp_bindings::context::params::LlamaAttentionType; +use llama_cpp_bindings::context::params::LlamaPoolingType; use llama_cpp_bindings::error::ClearKvCacheSeqError; use llama_cpp_bindings::error::CopyKvCacheSeqError; use llama_cpp_bindings::error::KvCacheSeqAddError; @@ -363,9 +366,7 @@ fn embeddings_ith_returns_error_when_embeddings_disabled(fixture: &LlamaFixture< (*fixture.context_params).into_llama_context_params(), )?; - let result = context.embeddings_ith(0); - - assert!(result.is_err()); + assert_eq!(context.embeddings_ith(0), Err(EmbeddingsError::NotEnabled)); Ok(()) } @@ -387,9 +388,10 @@ fn embeddings_seq_ith_returns_error_when_embeddings_disabled( (*fixture.context_params).into_llama_context_params(), )?; - let result = context.embeddings_seq_ith(0); - - assert!(result.is_err()); + assert_eq!( + context.embeddings_seq_ith(0), + Err(EmbeddingsError::NotEnabled) + ); Ok(()) } @@ -571,7 +573,9 @@ fn embeddings_seq_ith_returns_null_embedding_error_for_invalid_seq( let mut context = LlamaContext::from_model( fixture.model, fixture.backend, - (*fixture.context_params).into_llama_context_params(), + (*fixture.context_params) + .into_llama_context_params() + .with_pooling_type(LlamaPoolingType::Last), )?; let tokens = fixture .model @@ -580,9 +584,12 @@ fn embeddings_seq_ith_returns_null_embedding_error_for_invalid_seq( batch.add_sequence(&tokens, 0, false)?; context.decode(&mut batch)?; - let result = context.embeddings_seq_ith(999); - - assert!(result.is_err()); + assert_eq!( + context.embeddings_seq_ith(999), + Err(EmbeddingsError::SequenceEmbeddingUnavailable { + sequence_index: 999 + }) + ); Ok(()) } @@ -1070,7 +1077,7 @@ fn kv_cache_seq_div_returns_error_for_mrope_model(fixture: &LlamaFixture<'_>) -> prime_kv_cache(fixture, &mut context)?; - let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow::anyhow!("2 is non-zero"))?; + let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow!("2 is non-zero"))?; let result = context.kv_cache_seq_div(0, Some(0), None, divisor); assert_eq!( @@ -1578,7 +1585,7 @@ fn kv_cache_seq_add_rejects_p1_exceeding_i32_max(fixture: &LlamaFixture<'_>) -> fn kv_cache_seq_div_rejects_p0_exceeding_i32_max(fixture: &LlamaFixture<'_>) -> Result<()> { let mut context = fixture.build_context()?; - let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow::anyhow!("2 is non-zero"))?; + let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow!("2 is non-zero"))?; let result = context.kv_cache_seq_div(0, Some(u32::MAX), None, divisor); assert_eq!( @@ -1626,7 +1633,7 @@ fn kv_cache_seq_div_rejects_p0_exceeding_i32_max(fixture: &LlamaFixture<'_>) -> fn kv_cache_seq_div_rejects_p1_exceeding_i32_max(fixture: &LlamaFixture<'_>) -> Result<()> { let mut context = fixture.build_context()?; - let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow::anyhow!("2 is non-zero"))?; + let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow!("2 is non-zero"))?; let result = context.kv_cache_seq_div(0, Some(0), Some(u32::MAX), divisor); assert_eq!( diff --git a/llama-cpp-bindings-tests/tests/embedding_models.rs b/llama-cpp-bindings-tests/tests/embedding_models.rs index 44058b205..4ace151ef 100644 --- a/llama-cpp-bindings-tests/tests/embedding_models.rs +++ b/llama-cpp-bindings-tests/tests/embedding_models.rs @@ -3,11 +3,13 @@ use std::time::Duration; use anyhow::Context; use anyhow::Result; +use anyhow::anyhow; use anyhow::bail; use llama_cpp_bindings::BareJsonToolCalls; use llama_cpp_bindings::ClearKvCacheSeqError; use llama_cpp_bindings::CopyKvCacheSeqError; use llama_cpp_bindings::DecodeError; +use llama_cpp_bindings::EmbeddingsError; use llama_cpp_bindings::EncodeError; use llama_cpp_bindings::KvCacheSeqAddError; use llama_cpp_bindings::KvCacheSeqDivError; @@ -507,9 +509,10 @@ fn embeddings_ith_returns_null_embedding_error_for_non_embedding_token( (*fixture.context_params).into_llama_context_params(), )?; - let result = context.embeddings_ith(999); - - assert!(result.is_err()); + assert_eq!( + context.embeddings_ith(999), + Err(EmbeddingsError::LogitsNotEnabled) + ); Ok(()) } @@ -575,7 +578,7 @@ fn kv_cache_seq_div_succeeds_on_embedding_model(fixture: &LlamaFixture<'_>) -> R prime_kv_cache(fixture, &mut context)?; - let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow::anyhow!("2 is non-zero"))?; + let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow!("2 is non-zero"))?; let result = context.kv_cache_seq_div(0, Some(0), None, divisor); assert!(result.is_ok()); @@ -676,7 +679,7 @@ fn kv_cache_operations_respect_an_embedding_context_without_memory( context.kv_cache_seq_add(0, None, None, 1), Err(KvCacheSeqAddError::MemoryHandleUnavailable) ); - let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow::anyhow!("2 is non-zero"))?; + let divisor = NonZeroU8::new(2).ok_or_else(|| anyhow!("2 is non-zero"))?; assert_eq!( context.kv_cache_seq_div(0, None, None, divisor), Err(KvCacheSeqDivError::MemoryHandleUnavailable) diff --git a/llama-cpp-bindings-tests/tests/embedding_row_width.rs b/llama-cpp-bindings-tests/tests/embedding_row_width.rs new file mode 100644 index 000000000..440a6a7b2 --- /dev/null +++ b/llama-cpp-bindings-tests/tests/embedding_row_width.rs @@ -0,0 +1,99 @@ +use anyhow::Result; +use llama_cpp_bindings::context::LlamaContext; +use llama_cpp_bindings::context::params::LlamaPoolingType; +use llama_cpp_bindings::llama_batch::LlamaBatch; +use llama_cpp_bindings::model::AddBos; +use llama_cpp_bindings::model::ParseSpecialTokens; +use llama_cpp_bindings_types::BatchCapacity; +use llama_cpp_test_harness::LlamaFixture; +use llama_cpp_test_harness::llama_test; + +const LFM2_COLBERT_OUTPUT_PROJECTION_WIDTH: usize = 128; +const QWEN3_RERANKER_CLASSIFIER_LABELS: usize = 2; +const TEXT: &str = "The parcel arrived on the last working day of the week."; + +fn context_after_decoding_text<'fixture>( + fixture: &'fixture LlamaFixture<'_>, + pooling_type: LlamaPoolingType, +) -> Result> { + let mut context = LlamaContext::from_model( + fixture.model, + fixture.backend, + (*fixture.context_params) + .into_llama_context_params() + .with_pooling_type(pooling_type), + )?; + let tokens = fixture + .model + .str_to_token(TEXT, AddBos::Always, ParseSpecialTokens::Never)?; + let mut batch = LlamaBatch::new(BatchCapacity::try_from(512)?, 1); + + batch.add_sequence(&tokens, 0, false)?; + context.decode(&mut batch)?; + + Ok(context) +} + +#[llama_test( + model_source = HuggingFace("LiquidAI/LFM2-ColBERT-350M-GGUF", "LFM2-ColBERT-350M-Q4_K_M.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 512, + n_ubatch = 512, + embeddings = true, +)] +fn token_embeddings_are_as_wide_as_the_output_projection(fixture: &LlamaFixture<'_>) -> Result<()> { + let context = context_after_decoding_text(fixture, LlamaPoolingType::None)?; + + assert_eq!( + context.embeddings_ith(0)?.len(), + LFM2_COLBERT_OUTPUT_PROJECTION_WIDTH + ); + + Ok(()) +} + +#[llama_test( + model_source = HuggingFace("LiquidAI/LFM2-ColBERT-350M-GGUF", "LFM2-ColBERT-350M-Q4_K_M.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 512, + n_ubatch = 512, + embeddings = true, +)] +fn pooled_sequence_embeddings_are_as_wide_as_the_output_projection( + fixture: &LlamaFixture<'_>, +) -> Result<()> { + let context = context_after_decoding_text(fixture, LlamaPoolingType::Mean)?; + + assert_eq!( + context.embeddings_seq_ith(0)?.len(), + LFM2_COLBERT_OUTPUT_PROJECTION_WIDTH + ); + + Ok(()) +} + +#[llama_test( + model_source = HuggingFace("ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF", "qwen3-reranker-0.6b-q8_0.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 512, + n_ubatch = 512, + embeddings = true, +)] +fn rank_pooled_sequence_embeddings_hold_one_score_per_classifier_label( + fixture: &LlamaFixture<'_>, +) -> Result<()> { + let context = context_after_decoding_text(fixture, LlamaPoolingType::Rank)?; + + assert_eq!( + context.embeddings_seq_ith(0)?.len(), + QWEN3_RERANKER_CLASSIFIER_LABELS + ); + + Ok(()) +} diff --git a/llama-cpp-bindings-tests/tests/generation_control.rs b/llama-cpp-bindings-tests/tests/generation_control.rs index eed72a0ad..6c472f9f7 100644 --- a/llama-cpp-bindings-tests/tests/generation_control.rs +++ b/llama-cpp-bindings-tests/tests/generation_control.rs @@ -4,6 +4,7 @@ use std::time::Duration; use anyhow::Context as _; use anyhow::Result; +use anyhow::anyhow; use anyhow::bail; use llama_cpp_bindings::BareJsonToolCalls; use llama_cpp_bindings::GbnfValidationError; @@ -159,10 +160,10 @@ fn grammar_sampler_constrains_output_to_yes_or_no(fixture: &LlamaFixture<'_>) -> let first_char = piece .chars() .next() - .ok_or_else(|| anyhow::anyhow!("piece should have at least one character"))? + .ok_or_else(|| anyhow!("piece should have at least one character"))? .to_lowercase() .next() - .ok_or_else(|| anyhow::anyhow!("lowercase iterator should yield a character"))?; + .ok_or_else(|| anyhow!("lowercase iterator should yield a character"))?; assert!( first_char == 'y' || first_char == 'n', @@ -1203,7 +1204,7 @@ fn json_schema_constrains_output(fixture: &LlamaFixture<'_>) -> Result<()> { let parsed = serde_json::Deserializer::from_str(&generated) .into_iter::() .next() - .ok_or_else(|| anyhow::anyhow!("model produced no JSON value"))??; + .ok_or_else(|| anyhow!("model produced no JSON value"))??; assert!(parsed.get("city").is_some()); assert!(parsed.get("temperature").is_some()); @@ -1641,7 +1642,7 @@ fn reset_rolls_back_accepted_tokens(fixture: &LlamaFixture<'_>) -> Result<()> { .str_to_token("yes", AddBos::Never, ParseSpecialTokens::Always)?; let first_allowed_token = *yes_tokens .first() - .ok_or_else(|| anyhow::anyhow!("the tokenizer must produce a token for \"yes\""))?; + .ok_or_else(|| anyhow!("the tokenizer must produce a token for \"yes\""))?; sampler .accept(first_allowed_token) diff --git a/llama-cpp-bindings-tests/tests/hidden_states.rs b/llama-cpp-bindings-tests/tests/hidden_states.rs index 036b8cfb6..2c66b1875 100644 --- a/llama-cpp-bindings-tests/tests/hidden_states.rs +++ b/llama-cpp-bindings-tests/tests/hidden_states.rs @@ -14,6 +14,7 @@ use llama_cpp_test_harness::llama_test; const STATE: &str = "Shoes arrived two weeks late and in the wrong size."; const FIRST_BRANCH: &str = " Which team should handle this ticket?"; const SECOND_BRANCH: &str = " Is the customer angry about the delay?"; +const OTHER_STATE: &str = "The weather in Oslo is cold, dark and windy all week."; fn tokenize(fixture: &LlamaFixture<'_>, text: &str) -> Result> { Ok(fixture @@ -284,3 +285,156 @@ fn tokenizing_without_special_token_parsing_keeps_control_markup_as_text( Ok(()) } + +fn decode_tokens( + context: &mut LlamaContext<'_>, + tokens: &[LlamaToken], + first_position: i32, + output_on_last_token: bool, +) -> Result { + let mut batch = LlamaBatch::new(BatchCapacity::try_from(512)?, 1); + + add_tokens(&mut batch, tokens, first_position, 0, output_on_last_token)?; + context.decode(&mut batch)?; + + Ok(batch.n_tokens() - 1) +} + +fn final_hidden_state_context<'fixture>( + fixture: &'fixture LlamaFixture<'_>, + embeddings: bool, +) -> Result> { + Ok(LlamaContext::from_model( + fixture.model, + fixture.backend, + (*fixture.context_params) + .into_llama_context_params() + .with_embeddings(embeddings) + .with_pooling_type(LlamaPoolingType::None), + )?) +} + +fn row_hidden_state_after_enabling_embeddings( + fixture: &LlamaFixture<'_>, + state: &[LlamaToken], + row: &[LlamaToken], +) -> Result> { + let mut context = final_hidden_state_context(fixture, false)?; + + decode_tokens(&mut context, state, 0, false)?; + context.set_embeddings(true); + + let row_last_index = decode_tokens(&mut context, row, i32::try_from(state.len())?, true)?; + + Ok(context.embeddings_ith(row_last_index)?.to_vec()) +} + +#[llama_test( + model_source = HuggingFace("Qwen/Qwen3-Embedding-0.6B-GGUF", "Qwen3-Embedding-0.6B-Q8_0.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 512, + n_ubatch = 512, +)] +fn final_hidden_states_after_enabling_embeddings_for_a_row_equal_those_of_an_embeddings_context( + fixture: &LlamaFixture<'_>, +) -> Result<()> { + let state = tokenize(fixture, STATE)?; + let row = tokenize(fixture, FIRST_BRANCH)?; + let toggled_row_hidden_state = + row_hidden_state_after_enabling_embeddings(fixture, &state, &row)?; + + let mut embeddings_context = final_hidden_state_context(fixture, true)?; + + decode_tokens(&mut embeddings_context, &state, 0, false)?; + + let row_last_index = decode_tokens( + &mut embeddings_context, + &row, + i32::try_from(state.len())?, + true, + )?; + + assert_eq!( + toggled_row_hidden_state.as_slice(), + embeddings_context.embeddings_ith(row_last_index)?, + ); + + Ok(()) +} + +#[llama_test( + model_source = HuggingFace("Qwen/Qwen3-Embedding-0.6B-GGUF", "Qwen3-Embedding-0.6B-Q8_0.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 512, + n_ubatch = 512, +)] +fn disabling_embeddings_makes_token_embeddings_unavailable( + fixture: &LlamaFixture<'_>, +) -> Result<()> { + let state = tokenize(fixture, STATE)?; + let mut context = final_hidden_state_context(fixture, true)?; + let last_index = decode_tokens(&mut context, &state, 0, true)?; + + context.set_embeddings(false); + + assert_eq!( + context.embeddings_ith(last_index), + Err(EmbeddingsError::NotEnabled) + ); + + Ok(()) +} + +#[llama_test( + model_source = HuggingFace("Qwen/Qwen3-Embedding-0.6B-GGUF", "Qwen3-Embedding-0.6B-Q8_0.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 512, + n_ubatch = 512, +)] +fn final_hidden_states_of_a_row_follow_the_state_decoded_before_it( + fixture: &LlamaFixture<'_>, +) -> Result<()> { + let row = tokenize(fixture, FIRST_BRANCH)?; + + assert_ne!( + row_hidden_state_after_enabling_embeddings(fixture, &tokenize(fixture, STATE)?, &row)?, + row_hidden_state_after_enabling_embeddings( + fixture, + &tokenize(fixture, OTHER_STATE)?, + &row + )?, + ); + + Ok(()) +} + +#[llama_test( + model_source = HuggingFace("Qwen/Qwen3-Embedding-0.6B-GGUF", "Qwen3-Embedding-0.6B-Q8_0.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 512, + n_ubatch = 512, +)] +fn nextn_embeddings_are_refused_and_stay_disabled_on_a_model_without_nextn_hidden_states( + fixture: &LlamaFixture<'_>, +) -> Result<()> { + let mut context = final_hidden_state_context(fixture, false)?; + + assert_eq!( + context.enable_masked_nextn_embeddings(), + Err(EmbeddingsError::NextnEmbeddingsUnsupported) + ); + assert_eq!( + context.nextn_embeddings_ith(0), + Err(EmbeddingsError::NextnEmbeddingsNotEnabled) + ); + + Ok(()) +} diff --git a/llama-cpp-bindings-tests/tests/main.rs b/llama-cpp-bindings-tests/tests/main.rs index cae2ba096..d98885281 100644 --- a/llama-cpp-bindings-tests/tests/main.rs +++ b/llama-cpp-bindings-tests/tests/main.rs @@ -6,6 +6,7 @@ mod backend_initialization; mod chat_protocol; mod context_state; mod embedding_models; +mod embedding_row_width; mod generation_control; mod hidden_states; mod model_derived_data_caching; @@ -13,7 +14,9 @@ mod model_introspection; mod model_loading_errors; mod multimodal_audio; mod multimodal_fusion; +mod multimodal_prompt_parts_tokenization; mod multimodal_vision; +mod sequence_embedding_availability; mod structured_chat_output; llama_cpp_test_harness::llama_tests_main!(); diff --git a/llama-cpp-bindings-tests/tests/model_introspection.rs b/llama-cpp-bindings-tests/tests/model_introspection.rs index 49ac9116f..8340a3f6f 100644 --- a/llama-cpp-bindings-tests/tests/model_introspection.rs +++ b/llama-cpp-bindings-tests/tests/model_introspection.rs @@ -5,6 +5,7 @@ use std::pin::pin; use anyhow::Context; use anyhow::Result; +use anyhow::anyhow; use llama_cpp_bindings::SampledToken; use llama_cpp_bindings::TokenAttrError; use llama_cpp_bindings::TokenOutsideVocabulary; @@ -741,7 +742,7 @@ fn fit_params_succeeds_with_test_model(fixture: &LlamaFixture<'_>) -> Result<()> let model_path_utf8 = fixture .model_path .to_str() - .ok_or_else(|| anyhow::anyhow!("model path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("model path is not valid UTF-8"))?; let model_path_c = CString::new(model_path_utf8)?; let mut params = pin!(LlamaModelParams::default()); @@ -757,7 +758,7 @@ fn fit_params_succeeds_with_test_model(fixture: &LlamaFixture<'_>) -> Result<()> llama_cpp_bindings_sys::GGML_LOG_LEVEL_NONE, ); - let fit = result.map_err(|fit_error| anyhow::anyhow!("fit_params failed: {fit_error:?}"))?; + let fit = result.map_err(|fit_error| anyhow!("fit_params failed: {fit_error:?}"))?; assert!(fit.n_ctx > 0); Ok(()) @@ -777,7 +778,7 @@ fn fit_params_aligns_the_extra_model_context_with_the_fitted_one( let model_path_utf8 = fixture .model_path .to_str() - .ok_or_else(|| anyhow::anyhow!("model path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("model path is not valid UTF-8"))?; let model_path_c = CString::new(model_path_utf8)?; let mut params = pin!(LlamaModelParams::default()); @@ -804,7 +805,7 @@ fn fit_params_aligns_the_extra_model_context_with_the_fitted_one( llama_cpp_bindings_sys::GGML_LOG_LEVEL_NONE, ); - let fit = result.map_err(|fit_error| anyhow::anyhow!("fit_params failed: {fit_error:?}"))?; + let fit = result.map_err(|fit_error| anyhow!("fit_params failed: {fit_error:?}"))?; assert!(fit.n_ctx > 0); assert_ne!( extra_context_params.n_ctx(), diff --git a/llama-cpp-bindings-tests/tests/multimodal_prompt_parts_tokenization.rs b/llama-cpp-bindings-tests/tests/multimodal_prompt_parts_tokenization.rs new file mode 100644 index 000000000..303883059 --- /dev/null +++ b/llama-cpp-bindings-tests/tests/multimodal_prompt_parts_tokenization.rs @@ -0,0 +1,180 @@ +use std::ffi::CString; + +use anyhow::Result; +use anyhow::anyhow; +use llama_cpp_bindings::model::AddBos; +use llama_cpp_bindings::model::ParseSpecialTokens; +use llama_cpp_bindings::mtmd::MtmdBitmap; +use llama_cpp_bindings::mtmd::MtmdInputChunkType; +use llama_cpp_bindings::mtmd::MtmdInputChunks; +use llama_cpp_bindings::mtmd::MtmdInputPart; +use llama_cpp_bindings::mtmd::MtmdInputTextPart; +use llama_cpp_bindings::mtmd::MtmdTokenizeError; +use llama_cpp_bindings::mtmd::mtmd_default_marker; +use llama_cpp_bindings::token::LlamaToken; +use llama_cpp_test_harness::LlamaFixture; +use llama_cpp_test_harness::llama_test; + +const SPECIAL_TOKEN_TEXT: &str = "<|im_start|>"; + +fn text_part(text: &str, parse_special: bool) -> MtmdInputPart<'static> { + MtmdInputPart::Text(MtmdInputTextPart { + text: text.to_owned(), + parse_special, + }) +} + +fn tokens_of_text_chunks(chunks: &MtmdInputChunks) -> Result> { + let mut tokens = Vec::new(); + + for chunk_index in 0..chunks.len() { + let chunk = chunks + .get(chunk_index) + .ok_or_else(|| anyhow!("missing chunk at index {chunk_index}"))?; + + if let Some(chunk_tokens) = chunk.text_tokens_of(chunk.chunk_type()?) { + tokens.extend_from_slice(chunk_tokens); + } + } + + Ok(tokens) +} + +#[llama_test( + model_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "Qwen3.5-0.8B-Q4_K_M.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 128, + n_ubatch = 64, + mmproj_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "mmproj-F16.gguf"), +)] +fn only_bitmap_parts_become_image_chunks(fixture: &LlamaFixture<'_>) -> Result<()> { + let mtmd_ctx = fixture + .mtmd_context + .expect("mmproj_file declared in attribute"); + let image_data = vec![128u8; 64 * 64 * 3]; + let bitmap = MtmdBitmap::from_image_data(64, 64, &image_data)?; + let chunks = mtmd_ctx.tokenize_from_parts( + &[ + text_part( + &format!("What does {} mean? ", mtmd_default_marker()?), + true, + ), + MtmdInputPart::Bitmap(&bitmap), + text_part(" Describe it.", true), + ], + true, + )?; + let mut image_chunks = 0; + + for chunk_index in 0..chunks.len() { + let chunk = chunks + .get(chunk_index) + .ok_or_else(|| anyhow!("missing chunk at index {chunk_index}"))?; + + if chunk.chunk_type()? == MtmdInputChunkType::Image { + image_chunks += 1; + } + } + + assert_eq!(image_chunks, 1); + + Ok(()) +} + +#[llama_test( + model_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "Qwen3.5-0.8B-Q4_K_M.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 128, + n_ubatch = 64, + mmproj_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "mmproj-F16.gguf"), +)] +fn a_media_marker_inside_a_text_part_tokenizes_as_plain_text( + fixture: &LlamaFixture<'_>, +) -> Result<()> { + let mtmd_ctx = fixture + .mtmd_context + .expect("mmproj_file declared in attribute"); + let marker = mtmd_default_marker()?; + let chunks = mtmd_ctx.tokenize_from_parts(&[text_part(marker, true)], false)?; + + assert_eq!( + tokens_of_text_chunks(&chunks)?, + fixture + .model + .str_to_token(marker, AddBos::Never, ParseSpecialTokens::Always)? + ); + + Ok(()) +} + +#[llama_test( + model_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "Qwen3.5-0.8B-Q4_K_M.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 128, + n_ubatch = 64, + mmproj_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "mmproj-F16.gguf"), +)] +fn parse_special_applies_to_each_text_part_on_its_own(fixture: &LlamaFixture<'_>) -> Result<()> { + let mtmd_ctx = fixture + .mtmd_context + .expect("mmproj_file declared in attribute"); + let chunks = mtmd_ctx.tokenize_from_parts( + &[ + text_part(SPECIAL_TOKEN_TEXT, true), + text_part(SPECIAL_TOKEN_TEXT, false), + ], + false, + )?; + + assert_eq!( + tokens_of_text_chunks(&chunks)?, + [ + fixture.model.str_to_token( + SPECIAL_TOKEN_TEXT, + AddBos::Never, + ParseSpecialTokens::Always + )?, + fixture.model.str_to_token( + SPECIAL_TOKEN_TEXT, + AddBos::Never, + ParseSpecialTokens::Never + )?, + ] + .concat() + ); + + Ok(()) +} + +#[llama_test( + model_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "Qwen3.5-0.8B-Q4_K_M.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 128, + n_ubatch = 64, + mmproj_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "mmproj-F16.gguf"), +)] +fn a_text_part_containing_a_nul_byte_is_rejected(fixture: &LlamaFixture<'_>) -> Result<()> { + let mtmd_ctx = fixture + .mtmd_context + .expect("mmproj_file declared in attribute"); + let text_with_nul_byte = "before\0after"; + + assert_eq!( + mtmd_ctx + .tokenize_from_parts(&[text_part(text_with_nul_byte, true)], false) + .err(), + CString::new(text_with_nul_byte) + .err() + .map(MtmdTokenizeError::CStringError) + ); + + Ok(()) +} diff --git a/llama-cpp-bindings-tests/tests/multimodal_vision.rs b/llama-cpp-bindings-tests/tests/multimodal_vision.rs index 3105009d6..bc7662ecb 100644 --- a/llama-cpp-bindings-tests/tests/multimodal_vision.rs +++ b/llama-cpp-bindings-tests/tests/multimodal_vision.rs @@ -1,5 +1,7 @@ use anyhow::Context; use anyhow::Result; +use anyhow::anyhow; +use anyhow::bail; use llama_cpp_bindings::BareJsonToolCalls; use llama_cpp_bindings::EvalMultimodalChunksParams; use llama_cpp_bindings::TokenUsage; @@ -14,9 +16,7 @@ use llama_cpp_bindings::mtmd::MtmdContextParams; use llama_cpp_bindings::mtmd::MtmdEvalError; use llama_cpp_bindings::mtmd::MtmdInputChunkType; use llama_cpp_bindings::mtmd::MtmdInputChunks; -use llama_cpp_bindings::mtmd::MtmdInputPart; use llama_cpp_bindings::mtmd::MtmdInputText; -use llama_cpp_bindings::mtmd::MtmdInputTextPart; use llama_cpp_bindings::mtmd::NonCausalChunkMicroBatchMismatch; use llama_cpp_bindings::mtmd::mtmd_default_marker; use llama_cpp_bindings::sampling::LlamaSampler; @@ -125,7 +125,7 @@ fn copy_creates_owned_duplicate(fixture: &LlamaFixture<'_>) -> Result<()> { let chunks = mtmd_ctx.tokenize(input_text, &[&bitmap])?; let first_chunk = chunks .get(0) - .ok_or_else(|| anyhow::anyhow!("missing first chunk"))?; + .ok_or_else(|| anyhow!("missing first chunk"))?; let copied = first_chunk.copy()?; assert!(copied.owned); @@ -168,7 +168,7 @@ fn encode_chunk_succeeds_for_image_chunk(fixture: &LlamaFixture<'_>) -> Result<( for chunk_index in 0..chunks.len() { let chunk = chunks .get(chunk_index) - .ok_or_else(|| anyhow::anyhow!("missing chunk at index {chunk_index}"))?; + .ok_or_else(|| anyhow!("missing chunk at index {chunk_index}"))?; if chunk.chunk_type() == Ok(MtmdInputChunkType::Image) { let result = mtmd_ctx.encode_chunk(&chunk); assert!(result.is_ok()); @@ -211,7 +211,7 @@ fn decode_use_non_causal_returns_bool_for_image_chunk(fixture: &LlamaFixture<'_> for chunk_index in 0..chunks.len() { let chunk = chunks .get(chunk_index) - .ok_or_else(|| anyhow::anyhow!("missing chunk at index {chunk_index}"))?; + .ok_or_else(|| anyhow!("missing chunk at index {chunk_index}"))?; if chunk.chunk_type() == Ok(MtmdInputChunkType::Image) { let value = mtmd_ctx.decode_use_non_causal(&chunk); let printed = format!("{value:?}"); @@ -222,7 +222,7 @@ fn decode_use_non_causal_returns_bool_for_image_chunk(fixture: &LlamaFixture<'_> return Ok(()); } } - anyhow::bail!("tokenization should produce at least one Image chunk"); + bail!("tokenization should produce at least one Image chunk"); } fn tokenize_synthetic(fixture: &LlamaFixture<'_>, prompt: &str) -> Result { @@ -261,7 +261,7 @@ fn text_chunk_has_text_type(fixture: &LlamaFixture<'_>) -> Result<()> { let chunks = tokenize_synthetic(fixture, "Hello world <__media__>")?; let first_chunk = chunks .get(0) - .ok_or_else(|| anyhow::anyhow!("missing first chunk"))?; + .ok_or_else(|| anyhow!("missing first chunk"))?; assert_eq!(first_chunk.chunk_type()?, MtmdInputChunkType::Text); Ok(()) } @@ -288,10 +288,10 @@ fn text_chunk_returns_text_tokens(fixture: &LlamaFixture<'_>) -> Result<()> { let chunks = tokenize_synthetic(fixture, "Hello world <__media__>")?; let first_chunk = chunks .get(0) - .ok_or_else(|| anyhow::anyhow!("missing first chunk"))?; + .ok_or_else(|| anyhow!("missing first chunk"))?; let tokens = first_chunk .text_tokens_of(first_chunk.chunk_type()?) - .ok_or_else(|| anyhow::anyhow!("the first chunk of a text prompt must carry tokens"))?; + .ok_or_else(|| anyhow!("the first chunk of a text prompt must carry tokens"))?; assert_ne!(tokens, Vec::::new()); @@ -320,7 +320,7 @@ fn chunk_n_tokens_is_positive(fixture: &LlamaFixture<'_>) -> Result<()> { let chunks = tokenize_synthetic(fixture, "Hello world <__media__>")?; let first_chunk = chunks .get(0) - .ok_or_else(|| anyhow::anyhow!("missing first chunk"))?; + .ok_or_else(|| anyhow!("missing first chunk"))?; assert!(first_chunk.n_tokens() > 0); Ok(()) } @@ -347,7 +347,7 @@ fn chunk_n_positions_is_positive(fixture: &LlamaFixture<'_>) -> Result<()> { let chunks = tokenize_synthetic(fixture, "Hello world <__media__>")?; let first_chunk = chunks .get(0) - .ok_or_else(|| anyhow::anyhow!("missing first chunk"))?; + .ok_or_else(|| anyhow!("missing first chunk"))?; assert!(first_chunk.n_positions() > 0); Ok(()) } @@ -374,7 +374,7 @@ fn text_chunk_id_returns_none(fixture: &LlamaFixture<'_>) -> Result<()> { let chunks = tokenize_synthetic(fixture, "Hello <__media__>")?; let first_chunk = chunks .get(0) - .ok_or_else(|| anyhow::anyhow!("missing first chunk"))?; + .ok_or_else(|| anyhow!("missing first chunk"))?; assert_eq!(first_chunk.chunk_type()?, MtmdInputChunkType::Text); assert!(first_chunk.id().is_none()); Ok(()) @@ -403,7 +403,7 @@ fn image_chunk_returns_none_for_text_tokens(fixture: &LlamaFixture<'_>) -> Resul for chunk_index in 0..chunks.len() { let chunk = chunks .get(chunk_index) - .ok_or_else(|| anyhow::anyhow!("missing chunk at index {chunk_index}"))?; + .ok_or_else(|| anyhow!("missing chunk at index {chunk_index}"))?; if chunk.chunk_type()? == MtmdInputChunkType::Image { assert_eq!(chunk.text_tokens_of(MtmdInputChunkType::Image), None); @@ -436,7 +436,7 @@ fn image_chunk_id_returns_some(fixture: &LlamaFixture<'_>) -> Result<()> { for chunk_index in 0..chunks.len() { let chunk = chunks .get(chunk_index) - .ok_or_else(|| anyhow::anyhow!("missing chunk at index {chunk_index}"))?; + .ok_or_else(|| anyhow!("missing chunk at index {chunk_index}"))?; if chunk.chunk_type() == Ok(MtmdInputChunkType::Image) { assert!(chunk.id().is_some()); return Ok(()); @@ -599,7 +599,7 @@ fn eval_synthetic_bitmap(fixture: &LlamaFixture<'_>, width: u32, height: u32) -> let n_positions = chunks.total_positions(); let required_n_ctx = u32::try_from(n_positions + 256)?; if fixture.context_params.n_ctx < required_n_ctx { - anyhow::bail!( + bail!( "fixture n_ctx ({}) below required ({}) for {}x{} image", fixture.context_params.n_ctx, required_n_ctx, @@ -921,7 +921,7 @@ fn eval_chunks_with_standard_image(fixture: &LlamaFixture<'_>) -> Result<()> { let image_path = fixtures.join("llamas.jpg"); let image_path_str = image_path .to_str() - .ok_or_else(|| anyhow::anyhow!("image path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("image path is not valid UTF-8"))?; let bitmap = MtmdBitmap::from_file(mtmd_ctx, image_path_str)?; let input_text = MtmdInputText { text: "What is in this image? <__media__>".to_string(), @@ -1258,7 +1258,7 @@ fn build_multimodal_chunks_and_eval_into_usage( let image_path = fixtures_dir().join("llamas.jpg"); let image_path_str = image_path .to_str() - .ok_or_else(|| anyhow::anyhow!("image path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("image path is not valid UTF-8"))?; let bitmap = MtmdBitmap::from_file(mtmd_ctx, image_path_str)?; let marker = mtmd_default_marker()?; @@ -1305,7 +1305,7 @@ fn prompt_tokens_match_text_chunk_total(fixture: &LlamaFixture<'_>) -> Result<() let (usage, expected) = build_multimodal_chunks_and_eval_into_usage(fixture)?; if usage.prompt_tokens != expected.text { - anyhow::bail!( + bail!( "prompt_tokens must equal sum of text-chunk n_tokens; expected {}, got {}", expected.text, usage.prompt_tokens @@ -1328,7 +1328,7 @@ fn input_image_tokens_match_image_chunk_total(fixture: &LlamaFixture<'_>) -> Res let (usage, expected) = build_multimodal_chunks_and_eval_into_usage(fixture)?; if usage.input_image_tokens != expected.image { - anyhow::bail!( + bail!( "input_image_tokens must equal sum of image-chunk n_tokens; expected {}, got {}", expected.image, usage.input_image_tokens @@ -1351,13 +1351,13 @@ fn input_audio_tokens_are_zero_for_image_only_input(fixture: &LlamaFixture<'_>) let (usage, expected) = build_multimodal_chunks_and_eval_into_usage(fixture)?; if expected.audio != 0 { - anyhow::bail!( + bail!( "fixture invariant: image-only multimodal input should produce zero audio chunk tokens, got {}", expected.audio ); } if usage.input_audio_tokens != 0 { - anyhow::bail!( + bail!( "input_audio_tokens must be zero when no audio chunks are evaluated; got {}", usage.input_audio_tokens ); @@ -1381,7 +1381,7 @@ fn completion_tokens_are_zero_after_eval_before_generation( let (usage, _expected) = build_multimodal_chunks_and_eval_into_usage(fixture)?; if usage.completion_tokens() != 0 { - anyhow::bail!( + bail!( "completion_tokens must be zero immediately after eval (no generation has occurred); got {}", usage.completion_tokens() ); @@ -1424,9 +1424,7 @@ fn text_chunk_records_prompt_tokens(fixture: &LlamaFixture<'_>) -> Result<()> { let text_chunk = (0..fitted_chunks.len()) .filter_map(|index| FittedMtmdInputChunk::at(&fitted_chunks, index)) .find(|chunk| chunk.chunk_type() == MtmdInputChunkType::Text) - .ok_or_else(|| { - anyhow::anyhow!("text-only tokenization should produce at least one text chunk") - })?; + .ok_or_else(|| anyhow!("text-only tokenization should produce at least one text chunk"))?; let n_tokens = u64::try_from(text_chunk.chunk().n_tokens())?; @@ -1436,19 +1434,19 @@ fn text_chunk_records_prompt_tokens(fixture: &LlamaFixture<'_>) -> Result<()> { let usage = classifier.usage(); if usage.prompt_tokens != n_tokens { - anyhow::bail!( + bail!( "text chunk must record n_tokens as prompt_tokens; expected {n_tokens}, got {}", usage.prompt_tokens ); } if usage.input_image_tokens != 0 { - anyhow::bail!( + bail!( "text chunk must not bump input_image_tokens; got {}", usage.input_image_tokens ); } if usage.input_audio_tokens != 0 { - anyhow::bail!( + bail!( "text chunk must not bump input_audio_tokens; got {}", usage.input_audio_tokens ); @@ -1475,7 +1473,7 @@ fn image_chunk_records_input_image_tokens_only(fixture: &LlamaFixture<'_>) -> Re let image_path = fixtures_dir().join("llamas.jpg"); let image_path_str = image_path .to_str() - .ok_or_else(|| anyhow::anyhow!("image path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("image path is not valid UTF-8"))?; let bitmap = MtmdBitmap::from_file(mtmd_ctx, image_path_str)?; let marker = mtmd_default_marker()?; @@ -1498,11 +1496,11 @@ fn image_chunk_records_input_image_tokens_only(fixture: &LlamaFixture<'_>) -> Re let image_chunk = (0..fitted_chunks.len()) .filter_map(|index| FittedMtmdInputChunk::at(&fitted_chunks, index)) .find(|chunk| chunk.chunk_type() == MtmdInputChunkType::Image) - .ok_or_else(|| anyhow::anyhow!("multimodal tokenization should produce an image chunk"))?; + .ok_or_else(|| anyhow!("multimodal tokenization should produce an image chunk"))?; let n_tokens = u64::try_from(image_chunk.chunk().n_tokens())?; if n_tokens == 0 { - anyhow::bail!("image chunk should report at least one token"); + bail!("image chunk should report at least one token"); } let mut classifier = model.sampled_token_classifier(BareJsonToolCalls::Detect)?; @@ -1511,19 +1509,19 @@ fn image_chunk_records_input_image_tokens_only(fixture: &LlamaFixture<'_>) -> Re let usage = classifier.usage(); if usage.input_image_tokens != n_tokens { - anyhow::bail!( + bail!( "image chunk must record n_tokens as input_image_tokens; expected {n_tokens}, got {}", usage.input_image_tokens ); } if usage.prompt_tokens != 0 { - anyhow::bail!( + bail!( "image chunk must not bump prompt_tokens; got {}", usage.prompt_tokens ); } if usage.input_audio_tokens != 0 { - anyhow::bail!( + bail!( "image chunk must not bump input_audio_tokens; got {}", usage.input_audio_tokens ); @@ -1567,12 +1565,12 @@ fn text_chunk_drives_marker_state_machine_to_reasoning(fixture: &LlamaFixture<'_ for index in 0..fitted_chunks.len() { let chunk = FittedMtmdInputChunk::at(&fitted_chunks, index) - .ok_or_else(|| anyhow::anyhow!("chunk index {index} must exist"))?; + .ok_or_else(|| anyhow!("chunk index {index} must exist"))?; classifier.ingest_fitted_chunk(&chunk); } if classifier.current_section() != llama_cpp_bindings::SampledTokenSection::Reasoning { - anyhow::bail!( + bail!( "text chunk replay must transition the classifier section to Reasoning when the \ prompt opens a `` block; got {:?}", classifier.current_section() @@ -1611,7 +1609,7 @@ fn gemma4_classifier_emits_reasoning_for_multimodal_thinking_prompt( let image_path = fixtures_dir().join("llamas.jpg"); let image_path_str = image_path .to_str() - .ok_or_else(|| anyhow::anyhow!("image path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("image path is not valid UTF-8"))?; let bitmap = MtmdBitmap::from_file(mtmd_ctx, image_path_str)?; let marker = mtmd_default_marker()?; @@ -1663,13 +1661,13 @@ fn gemma4_classifier_emits_reasoning_for_multimodal_thinking_prompt( let usage = classifier.usage(); if outcome.observed_reasoning == 0 { - anyhow::bail!( + bail!( "Gemma 4 multimodal + thinking: classifier must emit at least one Reasoning token \ when the prompt opens a `<|channel>thought` block; outcome={outcome:?}" ); } if usage.reasoning_tokens == 0 { - anyhow::bail!( + bail!( "Gemma 4 multimodal + thinking: usage.reasoning_tokens must be non-zero; usage={usage:?}" ); } @@ -1706,7 +1704,7 @@ fn mistral3_classifier_emits_reasoning_for_multimodal_thinking_prompt( let image_path = fixtures_dir().join("llamas.jpg"); let image_path_str = image_path .to_str() - .ok_or_else(|| anyhow::anyhow!("image path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("image path is not valid UTF-8"))?; let bitmap = MtmdBitmap::from_file(mtmd_ctx, image_path_str)?; let marker = mtmd_default_marker()?; @@ -1758,13 +1756,13 @@ fn mistral3_classifier_emits_reasoning_for_multimodal_thinking_prompt( let usage = classifier.usage(); if outcome.observed_reasoning == 0 { - anyhow::bail!( + bail!( "Mistral 3 multimodal + thinking: classifier must emit at least one Reasoning token \ when the model opens a `[THINK]` block; outcome={outcome:?}" ); } if usage.reasoning_tokens == 0 { - anyhow::bail!( + bail!( "Mistral 3 multimodal + thinking: usage.reasoning_tokens must be non-zero; usage={usage:?}" ); } @@ -1810,7 +1808,7 @@ fn qwen35_classifier_emits_reasoning_for_multimodal_thinking_prompt( let image_path = fixtures_dir().join("llamas.jpg"); let image_path_str = image_path .to_str() - .ok_or_else(|| anyhow::anyhow!("image path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("image path is not valid UTF-8"))?; let bitmap = MtmdBitmap::from_file(mtmd_ctx, image_path_str)?; let marker = mtmd_default_marker()?; @@ -1862,13 +1860,13 @@ fn qwen35_classifier_emits_reasoning_for_multimodal_thinking_prompt( let usage = classifier.usage(); if outcome.observed_reasoning == 0 { - anyhow::bail!( + bail!( "Qwen 3.5 multimodal + thinking: classifier must emit at least one Reasoning token \ when the prompt opens a `` block; outcome={outcome:?}" ); } if usage.reasoning_tokens == 0 { - anyhow::bail!( + bail!( "Qwen 3.5 multimodal + thinking: usage.reasoning_tokens must be non-zero; usage={usage:?}" ); } @@ -1905,7 +1903,7 @@ fn qwen36_classifier_emits_reasoning_for_multimodal_thinking_prompt( let image_path = fixtures_dir().join("llamas.jpg"); let image_path_str = image_path .to_str() - .ok_or_else(|| anyhow::anyhow!("image path is not valid UTF-8"))?; + .ok_or_else(|| anyhow!("image path is not valid UTF-8"))?; let bitmap = MtmdBitmap::from_file(mtmd_ctx, image_path_str)?; let marker = mtmd_default_marker()?; @@ -1957,12 +1955,12 @@ fn qwen36_classifier_emits_reasoning_for_multimodal_thinking_prompt( let usage = classifier.usage(); if outcome.observed_reasoning == 0 { - anyhow::bail!( + bail!( "Qwen 3.6 multimodal + thinking: classifier must emit at least one Reasoning token; outcome={outcome:?}" ); } if usage.reasoning_tokens == 0 { - anyhow::bail!( + bail!( "Qwen 3.6 multimodal + thinking: usage.reasoning_tokens must be non-zero; usage={usage:?}" ); } @@ -2124,48 +2122,3 @@ fn gemma3_classifier_leaves_the_prompt_untouched_when_a_non_causal_image_exceeds Ok(()) } - -#[llama_test( - model_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "Qwen3.5-0.8B-Q4_K_M.gguf"), - n_gpu_layers = 999, - load_mode = Mmap, - n_ctx = 512, - n_batch = 128, - n_ubatch = 64, - mmproj_source = HuggingFace("unsloth/Qwen3.5-0.8B-GGUF", "mmproj-F16.gguf"), -)] -fn tokenize_from_parts_reads_the_media_marker_in_a_text_part_as_text( - fixture: &LlamaFixture<'_>, -) -> Result<()> { - let mtmd_ctx = fixture - .mtmd_context - .expect("mmproj_file declared in attribute"); - let image_data = vec![128u8; 64 * 64 * 3]; - let bitmap = MtmdBitmap::from_image_data(64, 64, &image_data)?; - let chunks = mtmd_ctx.tokenize_from_parts( - &[ - MtmdInputPart::Text(MtmdInputTextPart::new( - &format!("What does {} mean? ", mtmd_default_marker()?), - true, - )?), - MtmdInputPart::Bitmap(&bitmap), - MtmdInputPart::Text(MtmdInputTextPart::new(" Describe it.", true)?), - ], - true, - )?; - let mut image_chunks = 0; - - for chunk_index in 0..chunks.len() { - let chunk = chunks - .get(chunk_index) - .ok_or_else(|| anyhow::anyhow!("missing chunk at index {chunk_index}"))?; - - if chunk.chunk_type()? == MtmdInputChunkType::Image { - image_chunks += 1; - } - } - - assert_eq!(image_chunks, 1); - - Ok(()) -} diff --git a/llama-cpp-bindings-tests/tests/sequence_embedding_availability.rs b/llama-cpp-bindings-tests/tests/sequence_embedding_availability.rs new file mode 100644 index 000000000..c69f126d0 --- /dev/null +++ b/llama-cpp-bindings-tests/tests/sequence_embedding_availability.rs @@ -0,0 +1,54 @@ +use anyhow::Result; +use llama_cpp_bindings::EmbeddingsError; +use llama_cpp_bindings::context::LlamaContext; +use llama_cpp_bindings::context::params::LlamaPoolingType; +use llama_cpp_bindings::llama_batch::LlamaBatch; +use llama_cpp_bindings::model::AddBos; +use llama_cpp_bindings::model::ParseSpecialTokens; +use llama_cpp_bindings_types::BatchCapacity; +use llama_cpp_test_harness::LlamaFixture; +use llama_cpp_test_harness::llama_test; + +const TEXT: &str = "The parcel arrived on the last working day of the week."; + +fn context_after_decoding_the_first_sequence<'fixture>( + fixture: &'fixture LlamaFixture<'_>, + pooling_type: LlamaPoolingType, +) -> Result> { + let mut context = LlamaContext::from_model( + fixture.model, + fixture.backend, + (*fixture.context_params) + .into_llama_context_params() + .with_pooling_type(pooling_type), + )?; + let tokens = fixture + .model + .str_to_token(TEXT, AddBos::Always, ParseSpecialTokens::Never)?; + let mut batch = LlamaBatch::new(BatchCapacity::try_from(512)?, 1); + + batch.add_sequence(&tokens, 0, false)?; + context.decode(&mut batch)?; + + Ok(context) +} + +#[llama_test( + model_source = HuggingFace("Qwen/Qwen3-Embedding-0.6B-GGUF", "Qwen3-Embedding-0.6B-Q8_0.gguf"), + n_gpu_layers = 999, + load_mode = Mmap, + n_ctx = 512, + n_batch = 512, + n_ubatch = 512, + embeddings = true, +)] +fn sequence_embeddings_need_a_pooling_context(fixture: &LlamaFixture<'_>) -> Result<()> { + let context = context_after_decoding_the_first_sequence(fixture, LlamaPoolingType::None)?; + + assert_eq!( + context.embeddings_seq_ith(0), + Err(EmbeddingsError::NonePoolType) + ); + + Ok(()) +} diff --git a/llama-cpp-bindings-types/src/lib.rs b/llama-cpp-bindings-types/src/lib.rs index 1f6516c66..a35133e72 100644 --- a/llama-cpp-bindings-types/src/lib.rs +++ b/llama-cpp-bindings-types/src/lib.rs @@ -8,6 +8,8 @@ pub mod batch_capacity_error; pub mod bracketed_json_shape; pub mod json_object_shape; pub mod key_value_xml_tags_shape; +pub mod model_provided_tool_call_id; +pub mod model_provided_tool_call_id_error; pub mod paired_quote_shape; pub mod parsed_chat_message; pub mod parsed_tool_call; @@ -26,6 +28,8 @@ pub use batch_capacity_error::BatchCapacityError; pub use bracketed_json_shape::BracketedJsonShape; pub use json_object_shape::JsonObjectShape; pub use key_value_xml_tags_shape::KeyValueXmlTagsShape; +pub use model_provided_tool_call_id::ModelProvidedToolCallId; +pub use model_provided_tool_call_id_error::ModelProvidedToolCallIdError; pub use paired_quote_shape::PairedQuoteShape; pub use parsed_chat_message::ParsedChatMessage; pub use parsed_tool_call::ParsedToolCall; diff --git a/llama-cpp-bindings-types/src/model_provided_tool_call_id.rs b/llama-cpp-bindings-types/src/model_provided_tool_call_id.rs new file mode 100644 index 000000000..0301ca3d3 --- /dev/null +++ b/llama-cpp-bindings-types/src/model_provided_tool_call_id.rs @@ -0,0 +1,51 @@ +use serde::Deserialize; +use serde::Serialize; + +use crate::model_provided_tool_call_id_error::ModelProvidedToolCallIdError; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(try_from = "String")] +pub struct ModelProvidedToolCallId(String); + +impl ModelProvidedToolCallId { + #[must_use] + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl TryFrom for ModelProvidedToolCallId { + type Error = ModelProvidedToolCallIdError; + + fn try_from(tool_call_id: String) -> Result { + if tool_call_id.is_empty() { + return Err(ModelProvidedToolCallIdError::Empty); + } + + Ok(Self(tool_call_id)) + } +} + +#[cfg(test)] +mod tests { + use super::ModelProvidedToolCallId; + use crate::model_provided_tool_call_id_error::ModelProvidedToolCallIdError; + + #[test] + fn rejects_an_empty_tool_call_id() { + assert_eq!( + ModelProvidedToolCallId::try_from(String::new()), + Err(ModelProvidedToolCallIdError::Empty) + ); + } + + #[test] + fn keeps_the_tool_call_id_the_model_gave() { + assert_eq!( + ModelProvidedToolCallId::try_from("call_7".to_owned()) + .as_ref() + .map(ModelProvidedToolCallId::as_str), + Ok("call_7") + ); + } +} diff --git a/llama-cpp-bindings-types/src/model_provided_tool_call_id_error.rs b/llama-cpp-bindings-types/src/model_provided_tool_call_id_error.rs new file mode 100644 index 000000000..f307ff6c3 --- /dev/null +++ b/llama-cpp-bindings-types/src/model_provided_tool_call_id_error.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Eq, PartialEq, thiserror::Error)] +pub enum ModelProvidedToolCallIdError { + #[error("a tool call id provided by the model must not be empty")] + Empty, +} diff --git a/llama-cpp-bindings-types/src/parsed_chat_message.rs b/llama-cpp-bindings-types/src/parsed_chat_message.rs index c32948e62..0e128309e 100644 --- a/llama-cpp-bindings-types/src/parsed_chat_message.rs +++ b/llama-cpp-bindings-types/src/parsed_chat_message.rs @@ -33,6 +33,8 @@ impl ParsedChatMessage { #[cfg(test)] mod tests { + use serde_json::json; + use super::ParsedChatMessage; use super::ParsedToolCall; use crate::parsed_tool_call_id::ParsedToolCallId; @@ -65,7 +67,7 @@ mod tests { vec![ParsedToolCall::new( ParsedToolCallId::NotProvidedByModel, "tool".to_owned(), - ToolCallArguments::default(), + ToolCallArguments::ValidJson(json!({})), )], ); @@ -78,9 +80,9 @@ mod tests { "hello".to_owned(), "thinking".to_owned(), vec![ParsedToolCall::new( - ParsedToolCallId::ModelProvided("id-1".to_owned()), + ParsedToolCallId::NotProvidedByModel, "tool".to_owned(), - ToolCallArguments::default(), + ToolCallArguments::ValidJson(json!({})), )], ); diff --git a/llama-cpp-bindings-types/src/parsed_tool_call.rs b/llama-cpp-bindings-types/src/parsed_tool_call.rs index fd390c190..e8b1f068c 100644 --- a/llama-cpp-bindings-types/src/parsed_tool_call.rs +++ b/llama-cpp-bindings-types/src/parsed_tool_call.rs @@ -34,15 +34,12 @@ mod tests { #[test] fn new_assigns_fields_in_order() { let parsed = ParsedToolCall::new( - ParsedToolCallId::ModelProvided("id-1".to_owned()), + ParsedToolCallId::NotProvidedByModel, "tool".to_owned(), ToolCallArguments::ValidJson(json!({})), ); - assert_eq!( - parsed.id, - ParsedToolCallId::ModelProvided("id-1".to_owned()) - ); + assert_eq!(parsed.id, ParsedToolCallId::NotProvidedByModel); assert_eq!(parsed.name, "tool"); assert_eq!(parsed.arguments, ToolCallArguments::ValidJson(json!({}))); } diff --git a/llama-cpp-bindings-types/src/parsed_tool_call_id.rs b/llama-cpp-bindings-types/src/parsed_tool_call_id.rs index a21116552..0cc180e1c 100644 --- a/llama-cpp-bindings-types/src/parsed_tool_call_id.rs +++ b/llama-cpp-bindings-types/src/parsed_tool_call_id.rs @@ -1,9 +1,26 @@ use serde::Deserialize; use serde::Serialize; +use crate::model_provided_tool_call_id::ModelProvidedToolCallId; + #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -#[serde(deny_unknown_fields)] pub enum ParsedToolCallId { - ModelProvided(String), + ModelProvided(ModelProvidedToolCallId), NotProvidedByModel, } + +#[cfg(test)] +mod tests { + use serde_json::error::Category; + + use super::ParsedToolCallId; + + #[test] + fn an_empty_model_provided_id_is_rejected_when_deserialized() { + assert_eq!( + serde_json::from_str::(r#"{"ModelProvided":""}"#) + .map_err(|deserialize_error| deserialize_error.classify()), + Err(Category::Data) + ); + } +} diff --git a/llama-cpp-bindings-types/src/tool_call_arguments.rs b/llama-cpp-bindings-types/src/tool_call_arguments.rs index 05c77e200..587ec130e 100644 --- a/llama-cpp-bindings-types/src/tool_call_arguments.rs +++ b/llama-cpp-bindings-types/src/tool_call_arguments.rs @@ -15,12 +15,6 @@ impl ToolCallArguments { } } -impl Default for ToolCallArguments { - fn default() -> Self { - Self::InvalidJson(String::new()) - } -} - #[cfg(test)] mod tests { use serde_json::json; @@ -65,12 +59,4 @@ mod tests { assert_eq!(result, ToolCallArguments::InvalidJson(String::new())); } - - #[test] - fn default_is_empty_invalid() { - assert_eq!( - ToolCallArguments::default(), - ToolCallArguments::InvalidJson(String::new()) - ); - } } diff --git a/llama-cpp-bindings/src/chat_message_parser.rs b/llama-cpp-bindings/src/chat_message_parser.rs index c2bdb8c44..81bb36439 100644 --- a/llama-cpp-bindings/src/chat_message_parser.rs +++ b/llama-cpp-bindings/src/chat_message_parser.rs @@ -4,6 +4,8 @@ use std::ffi::c_char; use std::ptr; use std::ptr::NonNull; +use llama_cpp_bindings_types::ModelProvidedToolCallId; +use llama_cpp_bindings_types::ModelProvidedToolCallIdError; use llama_cpp_bindings_types::ParsedChatMessage; use llama_cpp_bindings_types::ParsedToolCall; use llama_cpp_bindings_types::ParsedToolCallId; @@ -753,10 +755,11 @@ fn prefix_before_optional(text: &str, marker: Option<&str>) -> String { } fn tool_call_id_reported_by_llama_cpp(llama_cpp_tool_call_id: String) -> ParsedToolCallId { - if llama_cpp_tool_call_id.is_empty() { - ParsedToolCallId::NotProvidedByModel - } else { - ParsedToolCallId::ModelProvided(llama_cpp_tool_call_id) + match ModelProvidedToolCallId::try_from(llama_cpp_tool_call_id) { + Ok(model_provided_tool_call_id) => { + ParsedToolCallId::ModelProvided(model_provided_tool_call_id) + } + Err(ModelProvidedToolCallIdError::Empty) => ParsedToolCallId::NotProvidedByModel, } } @@ -1027,11 +1030,13 @@ mod tests { use std::mem::discriminant; use std::ptr; + use llama_cpp_bindings_types::ModelProvidedToolCallId; use llama_cpp_bindings_types::ParsedChatMessage; use llama_cpp_bindings_types::ParsedToolCall; use llama_cpp_bindings_types::ParsedToolCallId; use llama_cpp_bindings_types::ReasoningMarkers; use llama_cpp_bindings_types::ToolCallArguments; + use serde_json::json; use super::ReasoningSplit; use super::chat_tools_parser_create_status_to_result; @@ -1103,11 +1108,13 @@ mod tests { ) }; - let Err(ParseChatMessageError::MessageUnrecognized { message }) = result else { - panic!("the llama.cpp exception status must surface the wrapper message"); - }; + assert_eq!( + result.err(), + Some(ParseChatMessageError::MessageUnrecognized { + message: "the message could not be parsed".to_owned() + }) + ); - assert_eq!(message, "the message could not be parsed"); assert!( out_error.is_null(), "the reclaimed pointer must be nulled so the caller does not free it twice" @@ -1199,11 +1206,12 @@ mod tests { ) }; - let Err(ParseChatMessageError::Reported { message }) = result else { - panic!("the llama.cpp exception status must surface the wrapper message"); - }; - - assert_eq!(message, "content read failed"); + assert_eq!( + result.err(), + Some(ParseChatMessageError::Reported { + message: "content read failed".to_owned() + }) + ); } #[test] @@ -1270,11 +1278,12 @@ mod tests { ) }; - let Err(ParseChatMessageError::Reported { message }) = result else { - panic!("the llama.cpp exception status must surface the wrapper message"); - }; - - assert_eq!(message, "reasoning read failed"); + assert_eq!( + result.err(), + Some(ParseChatMessageError::Reported { + message: "reasoning read failed".to_owned() + }) + ); } #[test] @@ -1334,11 +1343,12 @@ mod tests { ) }; - let Err(ParseChatMessageError::Reported { message }) = result else { - panic!("the llama.cpp exception status must surface the wrapper message"); - }; - - assert_eq!(message, "tool-call count failed"); + assert_eq!( + result.err(), + Some(ParseChatMessageError::Reported { + message: "tool-call count failed".to_owned() + }) + ); } #[test] @@ -1388,10 +1398,10 @@ mod tests { ) }; - let Err(ParseChatMessageError::ToolCallIdIndexOutOfBounds { index }) = result else { - panic!("expected ToolCallIdIndexOutOfBounds, got {result:?}"); - }; - assert_eq!(index, 4); + assert_eq!( + result.err(), + Some(ParseChatMessageError::ToolCallIdIndexOutOfBounds { index: 4 }) + ); } #[test] @@ -1425,11 +1435,12 @@ mod tests { ) }; - let Err(ParseChatMessageError::Reported { message }) = result else { - panic!("the llama.cpp exception status must surface the wrapper message"); - }; - - assert_eq!(message, "tool-call id read failed"); + assert_eq!( + result.err(), + Some(ParseChatMessageError::Reported { + message: "tool-call id read failed".to_owned() + }) + ); } #[test] @@ -1480,10 +1491,10 @@ mod tests { ) }; - let Err(ParseChatMessageError::ToolCallNameIndexOutOfBounds { index }) = result else { - panic!("expected ToolCallNameIndexOutOfBounds, got {result:?}"); - }; - assert_eq!(index, 2); + assert_eq!( + result.err(), + Some(ParseChatMessageError::ToolCallNameIndexOutOfBounds { index: 2 }) + ); } #[test] @@ -1517,11 +1528,12 @@ mod tests { ) }; - let Err(ParseChatMessageError::Reported { message }) = result else { - panic!("the llama.cpp exception status must surface the wrapper message"); - }; - - assert_eq!(message, "tool-call name read failed"); + assert_eq!( + result.err(), + Some(ParseChatMessageError::Reported { + message: "tool-call name read failed".to_owned() + }) + ); } #[test] @@ -1572,10 +1584,10 @@ mod tests { ) }; - let Err(ParseChatMessageError::ToolCallArgumentsIndexOutOfBounds { index }) = result else { - panic!("expected ToolCallArgumentsIndexOutOfBounds, got {result:?}"); - }; - assert_eq!(index, 9); + assert_eq!( + result.err(), + Some(ParseChatMessageError::ToolCallArgumentsIndexOutOfBounds { index: 9 }) + ); } #[test] @@ -1609,11 +1621,12 @@ mod tests { ) }; - let Err(ParseChatMessageError::Reported { message }) = result else { - panic!("the llama.cpp exception status must surface the wrapper message"); - }; - - assert_eq!(message, "tool-call arguments read failed"); + assert_eq!( + result.err(), + Some(ParseChatMessageError::Reported { + message: "tool-call arguments read failed".to_owned() + }) + ); } #[test] @@ -1863,7 +1876,7 @@ mod tests { vec![ParsedToolCall::new( ParsedToolCallId::NotProvidedByModel, "tool".to_owned(), - ToolCallArguments::default(), + ToolCallArguments::ValidJson(json!({})), )], ); @@ -1886,8 +1899,9 @@ mod tests { #[test] fn a_non_empty_llama_cpp_tool_call_id_is_provided_by_the_model() { assert_eq!( - tool_call_id_reported_by_llama_cpp("abc123def".to_owned()), - ParsedToolCallId::ModelProvided("abc123def".to_owned()) + Ok(tool_call_id_reported_by_llama_cpp("abc123def".to_owned())), + ModelProvidedToolCallId::try_from("abc123def".to_owned()) + .map(ParsedToolCallId::ModelProvided) ); } @@ -1963,9 +1977,7 @@ mod tests { ) }; - let Err(ParseChatMessageError::NotEnoughMemory) = result else { - panic!("an error-string allocation failure must map to NotEnoughMemory"); - }; + assert_eq!(result.err(), Some(ParseChatMessageError::NotEnoughMemory)); } #[test] @@ -1977,9 +1989,10 @@ mod tests { ) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = result else { - panic!("a llama.cpp allocation failure must be reported as its own variant"); - }; + assert_eq!( + result.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } #[test] @@ -1994,274 +2007,291 @@ mod tests { ) }; - let Err(ParseChatMessageError::DestructorFailed { message }) = result else { - panic!("a throwing destructor must surface its message"); - }; - - assert_eq!(message, "the destructor threw"); + assert_eq!( + result.err(), + Some(ParseChatMessageError::DestructorFailed { + message: "the destructor threw".to_owned() + }) + ); } #[test] fn parsed_chat_free_unknown_status_is_preserved() { let result = unsafe { parsed_chat_free_status_to_result(255, ptr::null_mut()) }; - let Err(ParseChatMessageError::FfiStatus(status_error)) = result else { - panic!("an unrecognized status must be preserved verbatim"); - }; - assert_eq!( - status_error, - crate::FfiStatusError { + result.err(), + Some(ParseChatMessageError::FfiStatus(crate::FfiStatusError { operation: "llama_rs_parsed_chat_free", code: 255, - } + })) ); } #[test] - fn parse_chat_message_status_to_result_maps_every_contract_status() { + fn parse_chat_message_status_to_result_maps_null_tools_parser_arg() { let mut out_error_slot: *mut c_char = ptr::null_mut(); - let outcome_0 = unsafe { + let outcome = unsafe { parse_chat_message_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_NULL_TOOLS_PARSER_ARG, ptr::null_mut(), &raw mut out_error_slot, ) }; - let Err(ParseChatMessageError::FfiContract(contract_0)) = outcome_0 else { - panic!( - "LLAMA_RS_PARSE_CHAT_MESSAGE_NULL_TOOLS_PARSER_ARG must map to a contract error" - ); - }; assert_eq!( - contract_0, - crate::FfiContractError { - operation: "llama_rs_parse_chat_message", - detail: "was given a null tools parser argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parse_chat_message", + detail: "was given a null tools parser argument", + } + )) ); - let outcome_1 = unsafe { + } + + #[test] + fn parse_chat_message_status_to_result_maps_null_input_arg() { + let mut out_error_slot: *mut c_char = ptr::null_mut(); + let outcome = unsafe { parse_chat_message_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_NULL_INPUT_ARG, ptr::null_mut(), &raw mut out_error_slot, ) }; - let Err(ParseChatMessageError::FfiContract(contract_1)) = outcome_1 else { - panic!("LLAMA_RS_PARSE_CHAT_MESSAGE_NULL_INPUT_ARG must map to a contract error"); - }; assert_eq!( - contract_1, - crate::FfiContractError { - operation: "llama_rs_parse_chat_message", - detail: "was given a null input argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parse_chat_message", + detail: "was given a null input argument", + } + )) ); - let outcome_2 = unsafe { + } + + #[test] + fn parse_chat_message_status_to_result_maps_null_out_handle_arg() { + let mut out_error_slot: *mut c_char = ptr::null_mut(); + let outcome = unsafe { parse_chat_message_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_NULL_OUT_HANDLE_ARG, ptr::null_mut(), &raw mut out_error_slot, ) }; - let Err(ParseChatMessageError::FfiContract(contract_2)) = outcome_2 else { - panic!("LLAMA_RS_PARSE_CHAT_MESSAGE_NULL_OUT_HANDLE_ARG must map to a contract error"); - }; assert_eq!( - contract_2, - crate::FfiContractError { - operation: "llama_rs_parse_chat_message", - detail: "was given a null out_handle argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parse_chat_message", + detail: "was given a null out_handle argument", + } + )) ); - let outcome_3 = unsafe { + } + + #[test] + fn parse_chat_message_status_to_result_maps_null_out_error_arg() { + let mut out_error_slot: *mut c_char = ptr::null_mut(); + let outcome = unsafe { parse_chat_message_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_NULL_OUT_ERROR_ARG, ptr::null_mut(), &raw mut out_error_slot, ) }; - let Err(ParseChatMessageError::FfiContract(contract_3)) = outcome_3 else { - panic!("LLAMA_RS_PARSE_CHAT_MESSAGE_NULL_OUT_ERROR_ARG must map to a contract error"); - }; assert_eq!( - contract_3, - crate::FfiContractError { - operation: "llama_rs_parse_chat_message", - detail: "was given a null out_error argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parse_chat_message", + detail: "was given a null out_error argument", + } + )) ); - let outcome_4 = unsafe { + } + + #[test] + fn parse_chat_message_status_to_result_maps_llama_cpp_out_of_memory() { + let mut out_error_slot: *mut c_char = ptr::null_mut(); + let outcome = unsafe { parse_chat_message_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), &raw mut out_error_slot, ) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = outcome_4 else { - panic!( - "LLAMA_RS_PARSE_CHAT_MESSAGE_LLAMA_CPP_OUT_OF_MEMORY must map to LlamaCppOutOfMemory" - ); - }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } #[test] - fn parsed_chat_content_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { + fn parsed_chat_content_status_to_result_maps_null_handle_arg() { + let outcome = unsafe { parsed_chat_content_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_NULL_HANDLE_ARG, ptr::null_mut(), ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_0)) = outcome_0 else { - panic!("LLAMA_RS_PARSED_CHAT_CONTENT_NULL_HANDLE_ARG must map to a contract error"); - }; assert_eq!( - contract_0, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_content", - detail: "was given a null handle argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_content", + detail: "was given a null handle argument", + } + )) ); - let outcome_1 = unsafe { + } + + #[test] + fn parsed_chat_content_status_to_result_maps_null_out_string_arg() { + let outcome = unsafe { parsed_chat_content_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_NULL_OUT_STRING_ARG, ptr::null_mut(), ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_1)) = outcome_1 else { - panic!("LLAMA_RS_PARSED_CHAT_CONTENT_NULL_OUT_STRING_ARG must map to a contract error"); - }; assert_eq!( - contract_1, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_content", - detail: "was given a null out_string argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_content", + detail: "was given a null out_string argument", + } + )) ); - let outcome_2 = unsafe { + } + + #[test] + fn parsed_chat_content_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { parsed_chat_content_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), ptr::null_mut(), ) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = outcome_2 else { - panic!( - "LLAMA_RS_PARSED_CHAT_CONTENT_LLAMA_CPP_OUT_OF_MEMORY must map to LlamaCppOutOfMemory" - ); - }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } #[test] - fn parsed_chat_reasoning_content_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { + fn parsed_chat_reasoning_content_status_to_result_maps_null_handle_arg() { + let outcome = unsafe { parsed_chat_reasoning_content_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_NULL_HANDLE_ARG, ptr::null_mut(), ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_0)) = outcome_0 else { - panic!( - "LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_NULL_HANDLE_ARG must map to a contract error" - ); - }; assert_eq!( - contract_0, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_reasoning_content", - detail: "was given a null handle argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_reasoning_content", + detail: "was given a null handle argument", + } + )) ); - let outcome_1 = unsafe { + } + + #[test] + fn parsed_chat_reasoning_content_status_to_result_maps_null_out_string_arg() { + let outcome = unsafe { parsed_chat_reasoning_content_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_NULL_OUT_STRING_ARG, ptr::null_mut(), ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_1)) = outcome_1 else { - panic!( - "LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_NULL_OUT_STRING_ARG must map to a contract error" - ); - }; assert_eq!( - contract_1, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_reasoning_content", - detail: "was given a null out_string argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_reasoning_content", + detail: "was given a null out_string argument", + } + )) ); - let outcome_2 = unsafe { + } + + #[test] + fn parsed_chat_reasoning_content_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { parsed_chat_reasoning_content_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), ptr::null_mut()) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = outcome_2 else { - panic!( - "LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_LLAMA_CPP_OUT_OF_MEMORY must map to LlamaCppOutOfMemory" - ); - }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } #[test] - fn parsed_chat_tool_call_count_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { + fn parsed_chat_tool_call_count_status_to_result_maps_null_handle_arg() { + let outcome = unsafe { parsed_chat_tool_call_count_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_NULL_HANDLE_ARG, 0, ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_0)) = outcome_0 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_NULL_HANDLE_ARG must map to a contract error" - ); - }; assert_eq!( - contract_0, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_tool_call_count", - detail: "was given a null handle argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_tool_call_count", + detail: "was given a null handle argument", + } + )) ); - let outcome_1 = unsafe { + } + + #[test] + fn parsed_chat_tool_call_count_status_to_result_maps_null_out_count_arg() { + let outcome = unsafe { parsed_chat_tool_call_count_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_NULL_OUT_COUNT_ARG, 0, ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_1)) = outcome_1 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_NULL_OUT_COUNT_ARG must map to a contract error" - ); - }; assert_eq!( - contract_1, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_tool_call_count", - detail: "was given a null out_count argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_tool_call_count", + detail: "was given a null out_count argument", + } + )) ); - let outcome_2 = unsafe { + } + + #[test] + fn parsed_chat_tool_call_count_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { parsed_chat_tool_call_count_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_LLAMA_CPP_OUT_OF_MEMORY, 0, ptr::null_mut(), ) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = outcome_2 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_LLAMA_CPP_OUT_OF_MEMORY must map to LlamaCppOutOfMemory" - ); - }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } #[test] - fn parsed_chat_tool_call_id_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { + fn parsed_chat_tool_call_id_status_to_result_maps_null_handle_arg() { + let outcome = unsafe { parsed_chat_tool_call_id_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_NULL_HANDLE_ARG, 0, @@ -2269,19 +2299,20 @@ mod tests { ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_0)) = outcome_0 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_NULL_HANDLE_ARG must map to a contract error" - ); - }; assert_eq!( - contract_0, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_tool_call_id", - detail: "was given a null handle argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_tool_call_id", + detail: "was given a null handle argument", + } + )) ); - let outcome_1 = unsafe { + } + + #[test] + fn parsed_chat_tool_call_id_status_to_result_maps_null_out_string_arg() { + let outcome = unsafe { parsed_chat_tool_call_id_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_NULL_OUT_STRING_ARG, 0, @@ -2289,19 +2320,20 @@ mod tests { ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_1)) = outcome_1 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_NULL_OUT_STRING_ARG must map to a contract error" - ); - }; assert_eq!( - contract_1, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_tool_call_id", - detail: "was given a null out_string argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_tool_call_id", + detail: "was given a null out_string argument", + } + )) ); - let outcome_2 = unsafe { + } + + #[test] + fn parsed_chat_tool_call_id_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { parsed_chat_tool_call_id_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_LLAMA_CPP_OUT_OF_MEMORY, 0, @@ -2309,16 +2341,15 @@ mod tests { ptr::null_mut(), ) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = outcome_2 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_LLAMA_CPP_OUT_OF_MEMORY must map to LlamaCppOutOfMemory" - ); - }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } #[test] - fn parsed_chat_tool_call_name_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { + fn parsed_chat_tool_call_name_status_to_result_maps_null_handle_arg() { + let outcome = unsafe { parsed_chat_tool_call_name_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_NULL_HANDLE_ARG, 0, @@ -2326,19 +2357,20 @@ mod tests { ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_0)) = outcome_0 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_NULL_HANDLE_ARG must map to a contract error" - ); - }; assert_eq!( - contract_0, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_tool_call_name", - detail: "was given a null handle argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_tool_call_name", + detail: "was given a null handle argument", + } + )) ); - let outcome_1 = unsafe { + } + + #[test] + fn parsed_chat_tool_call_name_status_to_result_maps_null_out_string_arg() { + let outcome = unsafe { parsed_chat_tool_call_name_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_NULL_OUT_STRING_ARG, 0, @@ -2346,19 +2378,20 @@ mod tests { ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_1)) = outcome_1 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_NULL_OUT_STRING_ARG must map to a contract error" - ); - }; assert_eq!( - contract_1, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_tool_call_name", - detail: "was given a null out_string argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_tool_call_name", + detail: "was given a null out_string argument", + } + )) ); - let outcome_2 = unsafe { + } + + #[test] + fn parsed_chat_tool_call_name_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { parsed_chat_tool_call_name_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_LLAMA_CPP_OUT_OF_MEMORY, 0, @@ -2366,16 +2399,15 @@ mod tests { ptr::null_mut(), ) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = outcome_2 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_LLAMA_CPP_OUT_OF_MEMORY must map to LlamaCppOutOfMemory" - ); - }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } #[test] - fn parsed_chat_tool_call_arguments_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { + fn parsed_chat_tool_call_arguments_status_to_result_maps_null_handle_arg() { + let outcome = unsafe { parsed_chat_tool_call_arguments_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_NULL_HANDLE_ARG, 0, @@ -2383,41 +2415,42 @@ mod tests { ptr::null_mut(), ) }; - let Err(ParseChatMessageError::FfiContract(contract_0)) = outcome_0 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_NULL_HANDLE_ARG must map to a contract error" - ); - }; assert_eq!( - contract_0, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_tool_call_arguments", - detail: "was given a null handle argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_tool_call_arguments", + detail: "was given a null handle argument", + } + )) ); - let outcome_1 = unsafe { + } + + #[test] + fn parsed_chat_tool_call_arguments_status_to_result_maps_null_out_string_arg() { + let outcome = unsafe { parsed_chat_tool_call_arguments_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_NULL_OUT_STRING_ARG, 0, ptr::null_mut(), ptr::null_mut()) }; - let Err(ParseChatMessageError::FfiContract(contract_1)) = outcome_1 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_NULL_OUT_STRING_ARG must map to a contract error" - ); - }; assert_eq!( - contract_1, - crate::FfiContractError { - operation: "llama_rs_parsed_chat_tool_call_arguments", - detail: "was given a null out_string argument", - } + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_parsed_chat_tool_call_arguments", + detail: "was given a null out_string argument", + } + )) ); - let outcome_2 = unsafe { + } + + #[test] + fn parsed_chat_tool_call_arguments_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { parsed_chat_tool_call_arguments_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_LLAMA_CPP_OUT_OF_MEMORY, 0, ptr::null_mut(), ptr::null_mut()) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = outcome_2 else { - panic!( - "LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_LLAMA_CPP_OUT_OF_MEMORY must map to LlamaCppOutOfMemory" - ); - }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } fn tools_parser_creation_error( diff --git a/llama-cpp-bindings/src/context.rs b/llama-cpp-bindings/src/context.rs index bc89a1ad7..4808461c2 100644 --- a/llama-cpp-bindings/src/context.rs +++ b/llama-cpp-bindings/src/context.rs @@ -1,6 +1,8 @@ +use std::ffi::c_char; use std::ffi::c_void; use std::fmt::{Debug, Formatter}; use std::num::NonZeroI32; +use std::ptr; use std::ptr::NonNull; use std::slice; use std::sync::Arc; @@ -33,10 +35,67 @@ const fn check_lora_adapters_result(err_code: i32) -> Result<(), LlamaLoraAdapte Ok(()) } +fn nextn_hidden_state_probe_status_to_result( + status: llama_cpp_bindings_sys::llama_rs_context_nextn_hidden_state_probe_status, + produces_nextn_hidden_state: bool, + out_error: *mut c_char, +) -> Result { + match status { + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_OK => { + Ok(produces_nextn_hidden_state) + } + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_CTX_ARG => { + Err(crate::FfiContractError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + detail: "was given a null ctx argument", + } + .into()) + } + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_OUT_PRODUCES_NEXTN_HIDDEN_STATE_ARG => { + Err(crate::FfiContractError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + detail: "was given a null out_produces_nextn_hidden_state argument", + } + .into()) + } + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_OUT_ERROR_ARG => { + Err(crate::FfiContractError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + detail: "was given a null out_error argument", + } + .into()) + } + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_FAILED_TO_RESERVE_GRAPH => { + Err(EmbeddingsError::NextnProbeGraphReservationFailed) + } + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_ERROR_STRING_ALLOCATION_FAILED => { + Err(EmbeddingsError::NextnProbeNotEnoughMemory) + } + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_OUT_OF_MEMORY => { + Err(EmbeddingsError::NextnProbeLlamaCppOutOfMemory) + } + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_THREW_CXX_EXCEPTION => { + let message = unsafe { + read_and_free_cpp_string( + out_error, + "llama_rs_context_graph_produces_nextn_hidden_state", + "reported a thrown C++ exception without an error message", + ) + }?; + Err(EmbeddingsError::NextnProbeReported { message }) + } + other => Err(crate::FfiStatusError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + code: i64::from(other), + } + .into()), + } +} + fn new_context_with_model_status_to_result( status: llama_cpp_bindings_sys::llama_rs_new_context_with_model_status, out_ctx: *mut llama_cpp_bindings_sys::llama_context, - out_error: *mut std::os::raw::c_char, + out_error: *mut c_char, ) -> Result, LlamaContextLoadError> { match status { llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_OK => NonNull::new(out_ctx) @@ -98,7 +157,7 @@ fn new_context_with_model_status_to_result( fn decode_status_to_result( status: llama_cpp_bindings_sys::llama_rs_decode_status, out_llama_cpp_return_code: i32, - out_error: *mut std::os::raw::c_char, + out_error: *mut c_char, ) -> Result<(), DecodeError> { match status { llama_cpp_bindings_sys::LLAMA_RS_DECODE_OK => Ok(()), @@ -153,7 +212,7 @@ fn decode_status_to_result( fn encode_status_to_result( status: llama_cpp_bindings_sys::llama_rs_encode_status, out_llama_cpp_return_code: i32, - out_error: *mut std::os::raw::c_char, + out_error: *mut c_char, ) -> Result<(), EncodeError> { match status { llama_cpp_bindings_sys::LLAMA_RS_ENCODE_OK => Ok(()), @@ -262,8 +321,6 @@ pub struct LlamaContext<'model> { pub model: &'model LlamaModel, abort_flag: Option>, initialized_logits: Vec, - embeddings_enabled: bool, - nextn_embeddings_enabled: bool, } impl Debug for LlamaContext<'_> { @@ -279,15 +336,12 @@ impl<'model> LlamaContext<'model> { pub const fn new( llama_model: &'model LlamaModel, llama_context: NonNull, - embeddings_enabled: bool, ) -> Self { Self { context: llama_context, model: llama_model, abort_flag: None, initialized_logits: Vec::new(), - embeddings_enabled, - nextn_embeddings_enabled: false, } } @@ -321,8 +375,8 @@ impl<'model> LlamaContext<'model> { } .validate()?; - let mut out_ctx: *mut llama_cpp_bindings_sys::llama_context = std::ptr::null_mut(); - let mut out_error: *mut std::os::raw::c_char = std::ptr::null_mut(); + let mut out_ctx: *mut llama_cpp_bindings_sys::llama_context = ptr::null_mut(); + let mut out_error: *mut c_char = ptr::null_mut(); let status = unsafe { llama_cpp_bindings_sys::llama_rs_new_context_with_model( model.model.as_ptr(), @@ -333,7 +387,7 @@ impl<'model> LlamaContext<'model> { }; let context = new_context_with_model_status_to_result(status, out_ctx, out_error)?; - Ok(Self::new(model, context, params.embeddings())) + Ok(Self::new(model, context)) } #[must_use] @@ -382,7 +436,7 @@ impl<'model> LlamaContext<'model> { llama_cpp_bindings_sys::llama_set_abort_callback( self.context.as_ptr(), None, - std::ptr::null_mut(), + ptr::null_mut(), ); } } @@ -419,7 +473,7 @@ impl<'model> LlamaContext<'model> { } let mut out_llama_cpp_return_code: i32 = 0; - let mut out_error: *mut std::os::raw::c_char = std::ptr::null_mut(); + let mut out_error: *mut c_char = ptr::null_mut(); let status = unsafe { llama_cpp_bindings_sys::llama_rs_decode( self.context.as_ptr(), @@ -448,7 +502,7 @@ impl<'model> LlamaContext<'model> { } let mut out_llama_cpp_return_code: i32 = 0; - let mut out_error: *mut std::os::raw::c_char = std::ptr::null_mut(); + let mut out_error: *mut c_char = ptr::null_mut(); let status = unsafe { llama_cpp_bindings_sys::llama_rs_encode( self.context.as_ptr(), @@ -465,59 +519,100 @@ impl<'model> LlamaContext<'model> { Ok(()) } + #[must_use] + pub fn pooling_type(&self) -> LlamaPoolingType { + LlamaPoolingType::from(unsafe { + llama_cpp_bindings_sys::llama_pooling_type(self.context.as_ptr()) + }) + } + + pub fn set_embeddings(&mut self, embeddings: bool) { + unsafe { + llama_cpp_bindings_sys::llama_set_embeddings(self.context.as_ptr(), embeddings); + } + } + + fn embeddings_enabled(&self) -> bool { + unsafe { + llama_cpp_bindings_sys::llama_rs_context_embeddings_enabled(self.context.as_ptr()) + } + } + + fn masked_nextn_embeddings_enabled(&self) -> bool { + let nextn_enabled = unsafe { + llama_cpp_bindings_sys::llama_rs_context_embeddings_nextn_enabled(self.context.as_ptr()) + }; + let nextn_masked = unsafe { + llama_cpp_bindings_sys::llama_rs_context_embeddings_nextn_masked(self.context.as_ptr()) + }; + + nextn_enabled && nextn_masked + } + + fn output_embedding_width(&self) -> Result { + usize::try_from(self.model.n_embd_out()).map_err(EmbeddingsError::InvalidEmbeddingDimension) + } + + fn output_embedding_row(&self, embedding: *const f32) -> Result<&[f32], EmbeddingsError> { + self.output_embedding_width() + .map(|output_embedding_width| unsafe { + slice::from_raw_parts(embedding, output_embedding_width) + }) + } + + fn sequence_embedding_width(&self) -> Result { + match self.pooling_type() { + LlamaPoolingType::Rank => usize::try_from(self.model.n_cls_out()) + .map_err(EmbeddingsError::InvalidEmbeddingDimension), + LlamaPoolingType::Mean | LlamaPoolingType::Cls | LlamaPoolingType::Last => { + self.output_embedding_width() + } + LlamaPoolingType::None | LlamaPoolingType::Unspecified => { + Err(EmbeddingsError::NonePoolType) + } + } + } + /// # Errors /// - /// - When the current context was constructed without enabling embeddings. - /// - If the current model had a pooling type of [`llama_cpp_bindings_sys::LLAMA_POOLING_TYPE_NONE`] - /// - If the given sequence index exceeds the max sequence id. - /// + /// - When embeddings are not enabled on this context. + /// - When this context does not pool its outputs. + /// - When the given sequence was not part of the last decoded batch. pub fn embeddings_seq_ith(&self, sequence_index: i32) -> Result<&[f32], EmbeddingsError> { - if !self.embeddings_enabled { + if !self.embeddings_enabled() { return Err(EmbeddingsError::NotEnabled); } - let n_embd = usize::try_from(self.model.n_embd()) - .map_err(EmbeddingsError::InvalidEmbeddingDimension)?; - - unsafe { - let embedding = llama_cpp_bindings_sys::llama_get_embeddings_seq( - self.context.as_ptr(), - sequence_index, - ); + let sequence_embedding_width = self.sequence_embedding_width()?; + let embedding = unsafe { + llama_cpp_bindings_sys::llama_get_embeddings_seq(self.context.as_ptr(), sequence_index) + }; - if embedding.is_null() { - Err(EmbeddingsError::NonePoolType) - } else { - Ok(slice::from_raw_parts(embedding, n_embd)) - } + if embedding.is_null() { + Err(EmbeddingsError::SequenceEmbeddingUnavailable { sequence_index }) + } else { + Ok(unsafe { slice::from_raw_parts(embedding, sequence_embedding_width) }) } } /// # Errors /// - /// - When the current context was constructed without enabling embeddings. + /// - When embeddings are not enabled on this context. /// - When the given token didn't have logits enabled when it was passed. /// - If the given token index exceeds the max token id. - /// pub fn embeddings_ith(&self, token_index: i32) -> Result<&[f32], EmbeddingsError> { - if !self.embeddings_enabled { + if !self.embeddings_enabled() { return Err(EmbeddingsError::NotEnabled); } - let n_embd = usize::try_from(self.model.n_embd()) - .map_err(EmbeddingsError::InvalidEmbeddingDimension)?; - - unsafe { - let embedding = llama_cpp_bindings_sys::llama_get_embeddings_ith( - self.context.as_ptr(), - token_index, - ); + let embedding = unsafe { + llama_cpp_bindings_sys::llama_get_embeddings_ith(self.context.as_ptr(), token_index) + }; - if embedding.is_null() { - Err(EmbeddingsError::LogitsNotEnabled) - } else { - Ok(slice::from_raw_parts(embedding, n_embd)) - } + if embedding.is_null() { + Err(EmbeddingsError::LogitsNotEnabled) + } else { + self.output_embedding_row(embedding) } } @@ -525,26 +620,54 @@ impl<'model> LlamaContext<'model> { /// /// - When the context pools its outputs, because llama.cpp only extracts `NextN` embeddings /// with `LLAMA_POOLING_TYPE_NONE`. + /// - When the model's graph produces no `NextN` hidden state; `NextN` embeddings stay disabled. + /// - When llama.cpp fails while checking the graph; `NextN` embeddings stay disabled. pub fn enable_masked_nextn_embeddings(&mut self) -> Result<(), EmbeddingsError> { - let pooling_type = LlamaPoolingType::from(unsafe { - llama_cpp_bindings_sys::llama_pooling_type(self.context.as_ptr()) - }); + let pooling_type = self.pooling_type(); if pooling_type != LlamaPoolingType::None { return Err(EmbeddingsError::NextnEmbeddingsRequireNonePooling { pooling_type }); } + self.set_masked_nextn_embeddings(true); + + let enabled = + self.graph_produces_nextn_hidden_state() + .and_then(|produces_nextn_hidden_state| { + produces_nextn_hidden_state + .then_some(()) + .ok_or(EmbeddingsError::NextnEmbeddingsUnsupported) + }); + + if enabled.is_err() { + self.set_masked_nextn_embeddings(false); + } + + enabled + } + + fn set_masked_nextn_embeddings(&mut self, enabled: bool) { unsafe { llama_cpp_bindings_sys::llama_rs_set_embeddings_nextn( self.context.as_ptr(), - true, - true, + enabled, + enabled, ); } + } - self.nextn_embeddings_enabled = true; + fn graph_produces_nextn_hidden_state(&self) -> Result { + let mut produces_nextn_hidden_state = false; + let mut out_error: *mut c_char = ptr::null_mut(); + let status = unsafe { + llama_cpp_bindings_sys::llama_rs_context_graph_produces_nextn_hidden_state( + self.context.as_ptr(), + &raw mut produces_nextn_hidden_state, + &raw mut out_error, + ) + }; - Ok(()) + nextn_hidden_state_probe_status_to_result(status, produces_nextn_hidden_state, out_error) } /// # Errors @@ -552,13 +675,10 @@ impl<'model> LlamaContext<'model> { /// - When masked `NextN` embeddings were not enabled on this context. /// - When the given token was not marked as an output of the last decoded batch. pub fn nextn_embeddings_ith(&self, token_index: i32) -> Result<&[f32], EmbeddingsError> { - if !self.nextn_embeddings_enabled { + if !self.masked_nextn_embeddings_enabled() { return Err(EmbeddingsError::NextnEmbeddingsNotEnabled); } - let row_length = unsafe { - llama_cpp_bindings_sys::llama_rs_context_embedding_row_length(self.context.as_ptr()) - }; let embedding = unsafe { llama_cpp_bindings_sys::llama_rs_get_embeddings_nextn_ith( self.context.as_ptr(), @@ -569,7 +689,7 @@ impl<'model> LlamaContext<'model> { if embedding.is_null() { Err(EmbeddingsError::NextnEmbeddingUnavailable { token_index }) } else { - Ok(unsafe { slice::from_raw_parts(embedding, row_length) }) + self.output_embedding_row(embedding) } } @@ -682,10 +802,8 @@ impl<'model> LlamaContext<'model> { .collect::>(); let raw_adapters_ptr = raw_adapters .first_mut() - .map_or(std::ptr::null_mut(), std::ptr::from_mut); - let scales_ptr = scales - .first_mut() - .map_or(std::ptr::null_mut(), std::ptr::from_mut); + .map_or(ptr::null_mut(), ptr::from_mut); + let scales_ptr = scales.first_mut().map_or(ptr::null_mut(), ptr::from_mut); let err_code = unsafe { llama_cpp_bindings_sys::llama_set_adapters_lora( self.context.as_ptr(), @@ -708,18 +826,9 @@ impl Drop for LlamaContext<'_> { } #[cfg(test)] -mod unit_tests { - use crate::DecodeError; - use crate::EncodeError; - use crate::LlamaContextLoadError; +mod check_lora_adapters_result_tests { + use super::check_lora_adapters_result; use crate::LlamaLoraAdaptersError; - use crate::LogitsError; - - use super::{ - check_lora_adapters_result, decode_status_to_result, encode_status_to_result, - logits_slice_from_raw_parts, new_context_with_model_status_to_result, - token_index_within_context, - }; #[test] fn check_lora_adapters_result_ok_for_zero() { @@ -732,13 +841,180 @@ mod unit_tests { assert_eq!(result, Err(LlamaLoraAdaptersError::ErrorResult(-1))); } +} + +#[cfg(test)] +mod nextn_hidden_state_probe_status_to_result_tests { + use std::ptr; + + use llama_cpp_ffi_status::read_and_free_cpp_string; + use llama_cpp_wrapper_error_fixture::wrapper_allocated_error_message_pointer; + + use super::nextn_hidden_state_probe_status_to_result; + use crate::EmbeddingsError; + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_ok_to_whether_the_graph_produces_it() { + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_OK, + true, + ptr::null_mut(), + ), + Ok(true) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_null_ctx_arg() { + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_CTX_ARG, + false, + ptr::null_mut(), + ), + Err(crate::FfiContractError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + detail: "was given a null ctx argument", + } + .into()) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_null_out_produces_nextn_hidden_state_arg() { + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_OUT_PRODUCES_NEXTN_HIDDEN_STATE_ARG, + false, + ptr::null_mut(), + ), + Err(crate::FfiContractError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + detail: "was given a null out_produces_nextn_hidden_state argument", + } + .into()) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_null_out_error_arg() { + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_NULL_OUT_ERROR_ARG, + false, + ptr::null_mut(), + ), + Err(crate::FfiContractError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + detail: "was given a null out_error argument", + } + .into()) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_llama_cpp_failed_to_reserve_graph() { + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_FAILED_TO_RESERVE_GRAPH, + false, + ptr::null_mut(), + ), + Err(EmbeddingsError::NextnProbeGraphReservationFailed) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_error_string_allocation_failed() { + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_ERROR_STRING_ALLOCATION_FAILED, + false, + ptr::null_mut(), + ), + Err(EmbeddingsError::NextnProbeNotEnoughMemory) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_llama_cpp_out_of_memory() { + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_OUT_OF_MEMORY, + false, + ptr::null_mut(), + ), + Err(EmbeddingsError::NextnProbeLlamaCppOutOfMemory) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_a_thrown_exception_with_its_message() { + let expected_message = unsafe { + read_and_free_cpp_string( + wrapper_allocated_error_message_pointer(), + "llama_rs_context_graph_produces_nextn_hidden_state", + "reported a thrown C++ exception without an error message", + ) + }; + + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_THREW_CXX_EXCEPTION, + false, + wrapper_allocated_error_message_pointer(), + ) + .err(), + expected_message + .ok() + .map(|message| EmbeddingsError::NextnProbeReported { message }) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_llama_cpp_threw_cxx_exception_without_a_message() + { + assert_eq!( + nextn_hidden_state_probe_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CONTEXT_NEXTN_HIDDEN_STATE_PROBE_LLAMA_CPP_THREW_CXX_EXCEPTION, + false, + ptr::null_mut(), + ), + Err(crate::FfiContractError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + detail: "reported a thrown C++ exception without an error message", + } + .into()) + ); + } + + #[test] + fn nextn_hidden_state_probe_status_to_result_maps_an_unknown_status() { + assert_eq!( + nextn_hidden_state_probe_status_to_result(255, false, ptr::null_mut()), + Err(crate::FfiStatusError { + operation: "llama_rs_context_graph_produces_nextn_hidden_state", + code: 255, + } + .into()) + ); + } +} + +#[cfg(test)] +mod new_context_with_model_status_to_result_tests { + use std::ptr; + + use super::new_context_with_model_status_to_result; + use crate::LlamaContextLoadError; #[test] fn new_context_success_with_null_context_is_contract_error() { let result = new_context_with_model_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_OK, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -756,8 +1032,8 @@ mod unit_tests { fn new_context_llama_cpp_returned_null_maps_unconstructible() { let result = new_context_with_model_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_RETURNED_NULL, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(LlamaContextLoadError::Unconstructible)); @@ -767,8 +1043,8 @@ mod unit_tests { fn new_context_allocation_failed_maps_not_enough_memory() { let result = new_context_with_model_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(LlamaContextLoadError::NotEnoughMemory)); @@ -778,8 +1054,8 @@ mod unit_tests { fn new_context_cxx_exception_without_a_message_is_a_contract_error() { let result = new_context_with_model_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_THREW_CXX_EXCEPTION, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -794,11 +1070,7 @@ mod unit_tests { #[test] fn new_context_unknown_status_is_preserved() { - let result = new_context_with_model_status_to_result( - 255, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); + let result = new_context_with_model_status_to_result(255, ptr::null_mut(), ptr::null_mut()); assert_eq!( result, @@ -809,12 +1081,90 @@ mod unit_tests { ); } + #[test] + fn new_context_with_model_status_to_result_maps_null_model_arg() { + let outcome = new_context_with_model_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_NULL_MODEL_ARG, + ptr::null_mut(), + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_new_context_with_model", + detail: "was given a null model argument", + } + .into() + ) + ); + } + + #[test] + fn new_context_with_model_status_to_result_maps_null_out_ctx_arg() { + let outcome = new_context_with_model_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_NULL_OUT_CTX_ARG, + ptr::null_mut(), + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_new_context_with_model", + detail: "was given a null out_ctx argument", + } + .into() + ) + ); + } + + #[test] + fn new_context_with_model_status_to_result_maps_null_out_error_arg() { + let outcome = new_context_with_model_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_NULL_OUT_ERROR_ARG, + ptr::null_mut(), + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_new_context_with_model", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + + #[test] + fn new_context_with_model_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = new_context_with_model_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some(LlamaContextLoadError::LlamaCppOutOfMemory) + ); + } +} + +#[cfg(test)] +mod decode_status_to_result_tests { + use std::ptr; + + use super::decode_status_to_result; + use crate::DecodeError; + #[test] fn decode_nonzero_code_maps_from_code() { let result = decode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_DECODE_LLAMA_CPP_RETURNED_NONZERO_CODE, 1, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(DecodeError::NoKvCacheSlot)); @@ -825,7 +1175,7 @@ mod unit_tests { let result = decode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_DECODE_OUT_OF_MEMORY, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(DecodeError::DecodeOutOfMemory)); @@ -836,7 +1186,7 @@ mod unit_tests { let result = decode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_DECODE_COMPUTE_FAILED, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(DecodeError::ComputeFailed)); @@ -847,7 +1197,7 @@ mod unit_tests { let result = decode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_DECODE_ERROR_STRING_ALLOCATION_FAILED, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(DecodeError::NotEnoughMemory)); @@ -858,7 +1208,7 @@ mod unit_tests { let result = decode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_DECODE_LLAMA_CPP_THREW_CXX_EXCEPTION, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -876,7 +1226,7 @@ mod unit_tests { let result = decode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_DECODE_LLAMA_CPP_RETURNED_NONZERO_CODE, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -890,7 +1240,7 @@ mod unit_tests { #[test] fn decode_unknown_status_is_preserved() { - let result = decode_status_to_result(255, 0, std::ptr::null_mut()); + let result = decode_status_to_result(255, 0, ptr::null_mut()); assert_eq!( result, @@ -901,12 +1251,68 @@ mod unit_tests { ); } + #[test] + fn decode_status_to_result_maps_null_ctx_arg() { + let outcome = decode_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_DECODE_NULL_CTX_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_decode", + detail: "was given a null ctx argument", + } + .into() + ) + ); + } + + #[test] + fn decode_status_to_result_maps_null_out_error_arg() { + let outcome = decode_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_DECODE_NULL_OUT_ERROR_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_decode", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + + #[test] + fn decode_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = decode_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_DECODE_LLAMA_CPP_OUT_OF_MEMORY, + 0, + ptr::null_mut(), + ); + assert_eq!(outcome.err(), Some(DecodeError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod encode_status_to_result_tests { + use std::ptr; + + use super::encode_status_to_result; + use crate::EncodeError; + #[test] fn encode_model_has_no_encoder_maps_model_has_no_encoder() { let result = encode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_ENCODE_MODEL_HAS_NO_ENCODER, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(EncodeError::ModelHasNoEncoder)); @@ -917,7 +1323,7 @@ mod unit_tests { let result = encode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_ENCODE_LLAMA_CPP_RETURNED_NONZERO_CODE, 1, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(EncodeError::NoKvCacheSlot)); @@ -928,7 +1334,7 @@ mod unit_tests { let result = encode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_ENCODE_OUT_OF_MEMORY, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(EncodeError::EncodeOutOfMemory)); @@ -939,7 +1345,7 @@ mod unit_tests { let result = encode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_ENCODE_COMPUTE_FAILED, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(EncodeError::ComputeFailed)); @@ -950,7 +1356,7 @@ mod unit_tests { let result = encode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_ENCODE_ERROR_STRING_ALLOCATION_FAILED, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(EncodeError::NotEnoughMemory)); @@ -961,7 +1367,7 @@ mod unit_tests { let result = encode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_ENCODE_LLAMA_CPP_THREW_CXX_EXCEPTION, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -979,7 +1385,7 @@ mod unit_tests { let result = encode_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_ENCODE_LLAMA_CPP_RETURNED_NONZERO_CODE, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -993,7 +1399,7 @@ mod unit_tests { #[test] fn encode_unknown_status_is_preserved() { - let result = encode_status_to_result(255, 0, std::ptr::null_mut()); + let result = encode_status_to_result(255, 0, ptr::null_mut()); assert_eq!( result, @@ -1004,6 +1410,60 @@ mod unit_tests { ); } + #[test] + fn encode_status_to_result_maps_null_ctx_arg() { + let outcome = encode_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_ENCODE_NULL_CTX_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_encode", + detail: "was given a null ctx argument", + } + .into() + ) + ); + } + + #[test] + fn encode_status_to_result_maps_null_model() { + let outcome = encode_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_ENCODE_NULL_MODEL, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_encode", + detail: "was given a null model argument", + } + .into() + ) + ); + } + + #[test] + fn encode_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = encode_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_ENCODE_LLAMA_CPP_OUT_OF_MEMORY, + 0, + ptr::null_mut(), + ); + assert_eq!(outcome.err(), Some(EncodeError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod token_index_within_context_tests { + use super::token_index_within_context; + use crate::LogitsError; + #[test] fn token_index_beyond_context_size_maps_exceeds_context() { let result = token_index_within_context(5, 4); @@ -1026,10 +1486,18 @@ mod unit_tests { fn token_index_negative_skips_context_check() { assert!(token_index_within_context(-1, 4).is_ok()); } +} + +#[cfg(test)] +mod logits_slice_from_raw_parts_tests { + use std::ptr; + + use super::logits_slice_from_raw_parts; + use crate::LogitsError; #[test] fn logits_slice_from_null_data_maps_null_logits() { - let result = unsafe { logits_slice_from_raw_parts(std::ptr::null(), 4) }; + let result = unsafe { logits_slice_from_raw_parts(ptr::null(), 4) }; assert_eq!(result, Err(LogitsError::NullLogits)); } @@ -1047,152 +1515,3 @@ mod unit_tests { ); } } - -#[cfg(test)] -mod ffi_contract_status_tests { - use super::decode_status_to_result; - use super::encode_status_to_result; - use super::new_context_with_model_status_to_result; - use crate::error::decode_error::DecodeError; - use crate::error::encode_error::EncodeError; - use crate::error::llama_context_load_error::LlamaContextLoadError; - use std::ptr; - - #[test] - fn new_context_with_model_status_to_result_maps_every_contract_status() { - let outcome_0 = new_context_with_model_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_NULL_MODEL_ARG, - ptr::null_mut(), - ptr::null_mut(), - ); - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_new_context_with_model", - detail: "was given a null model argument", - } - .into() - ) - ); - let outcome_1 = new_context_with_model_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_NULL_OUT_CTX_ARG, - ptr::null_mut(), - ptr::null_mut(), - ); - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_new_context_with_model", - detail: "was given a null out_ctx argument", - } - .into() - ) - ); - let outcome_2 = new_context_with_model_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_NULL_OUT_ERROR_ARG, - ptr::null_mut(), - ptr::null_mut(), - ); - assert_eq!( - outcome_2.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_new_context_with_model", - detail: "was given a null out_error argument", - } - .into() - ) - ); - let outcome_3 = new_context_with_model_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_NEW_CONTEXT_WITH_MODEL_LLAMA_CPP_OUT_OF_MEMORY, - ptr::null_mut(), - ptr::null_mut(), - ); - assert_eq!( - outcome_3.err(), - Some(LlamaContextLoadError::LlamaCppOutOfMemory) - ); - } - - #[test] - fn decode_status_to_result_maps_every_contract_status() { - let outcome_0 = decode_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_DECODE_NULL_CTX_ARG, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_decode", - detail: "was given a null ctx argument", - } - .into() - ) - ); - let outcome_1 = decode_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_DECODE_NULL_OUT_ERROR_ARG, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_decode", - detail: "was given a null out_error argument", - } - .into() - ) - ); - let outcome_2 = decode_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_DECODE_LLAMA_CPP_OUT_OF_MEMORY, - 0, - ptr::null_mut(), - ); - assert_eq!(outcome_2.err(), Some(DecodeError::LlamaCppOutOfMemory)); - } - - #[test] - fn encode_status_to_result_maps_every_contract_status() { - let outcome_0 = encode_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_ENCODE_NULL_CTX_ARG, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_encode", - detail: "was given a null ctx argument", - } - .into() - ) - ); - let outcome_1 = encode_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_ENCODE_NULL_MODEL, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_encode", - detail: "was given a null model argument", - } - .into() - ) - ); - let outcome_2 = encode_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_ENCODE_LLAMA_CPP_OUT_OF_MEMORY, - 0, - ptr::null_mut(), - ); - assert_eq!(outcome_2.err(), Some(EncodeError::LlamaCppOutOfMemory)); - } -} diff --git a/llama-cpp-bindings/src/context/kv_cache.rs b/llama-cpp-bindings/src/context/kv_cache.rs index 1c4568a91..c346b7437 100644 --- a/llama-cpp-bindings/src/context/kv_cache.rs +++ b/llama-cpp-bindings/src/context/kv_cache.rs @@ -333,13 +333,11 @@ impl LlamaContext<'_> { } #[cfg(test)] -mod tests { +mod kv_cache_seq_add_status_to_result_tests { use std::ptr; use super::kv_cache_seq_add_status_to_result; - use super::kv_cache_seq_div_status_to_result; - use super::kv_cache_seq_pos_max_status_to_result; - use crate::error::{KvCacheSeqAddError, KvCacheSeqDivError, KvCacheSeqPosMaxError}; + use crate::error::KvCacheSeqAddError; #[test] fn add_ok_status_maps_to_ok() { @@ -412,6 +410,41 @@ mod tests { ); } + #[test] + fn kv_cache_seq_add_status_to_result_maps_null_model() { + let outcome = kv_cache_seq_add_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_ADD_NULL_MODEL, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_memory_seq_add", + detail: "was given a null model argument", + } + .into() + ) + ); + } + + #[test] + fn kv_cache_seq_add_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = kv_cache_seq_add_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_ADD_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), + ); + assert_eq!(outcome.err(), Some(KvCacheSeqAddError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod kv_cache_seq_div_status_to_result_tests { + use std::ptr; + + use super::kv_cache_seq_div_status_to_result; + use crate::error::KvCacheSeqDivError; + #[test] fn div_ok_status_maps_to_ok() { let result = kv_cache_seq_div_status_to_result( @@ -483,6 +516,41 @@ mod tests { ); } + #[test] + fn kv_cache_seq_div_status_to_result_maps_null_model() { + let outcome = kv_cache_seq_div_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_DIV_NULL_MODEL, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_memory_seq_div", + detail: "was given a null model argument", + } + .into() + ) + ); + } + + #[test] + fn kv_cache_seq_div_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = kv_cache_seq_div_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_DIV_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), + ); + assert_eq!(outcome.err(), Some(KvCacheSeqDivError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod kv_cache_seq_pos_max_status_to_result_tests { + use std::ptr; + + use super::kv_cache_seq_pos_max_status_to_result; + use crate::error::KvCacheSeqPosMaxError; + #[test] fn seq_pos_max_ok_status_returns_position() { assert_eq!( @@ -616,80 +684,17 @@ mod tests { })) ); } -} - -#[cfg(test)] -mod ffi_contract_status_tests { - use super::kv_cache_seq_add_status_to_result; - use super::kv_cache_seq_div_status_to_result; - use super::kv_cache_seq_pos_max_status_to_result; - use crate::error::kv_cache_seq_add_error::KvCacheSeqAddError; - use crate::error::kv_cache_seq_div_error::KvCacheSeqDivError; - use crate::error::kv_cache_seq_pos_max_error::KvCacheSeqPosMaxError; - use std::ptr; - - #[test] - fn kv_cache_seq_add_status_to_result_maps_every_contract_status() { - let outcome_0 = kv_cache_seq_add_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_ADD_NULL_MODEL, - ptr::null_mut(), - ); - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_memory_seq_add", - detail: "was given a null model argument", - } - .into() - ) - ); - let outcome_1 = kv_cache_seq_add_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_ADD_LLAMA_CPP_OUT_OF_MEMORY, - ptr::null_mut(), - ); - assert_eq!( - outcome_1.err(), - Some(KvCacheSeqAddError::LlamaCppOutOfMemory) - ); - } - - #[test] - fn kv_cache_seq_div_status_to_result_maps_every_contract_status() { - let outcome_0 = kv_cache_seq_div_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_DIV_NULL_MODEL, - ptr::null_mut(), - ); - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_memory_seq_div", - detail: "was given a null model argument", - } - .into() - ) - ); - let outcome_1 = kv_cache_seq_div_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_DIV_LLAMA_CPP_OUT_OF_MEMORY, - ptr::null_mut(), - ); - assert_eq!( - outcome_1.err(), - Some(KvCacheSeqDivError::LlamaCppOutOfMemory) - ); - } #[test] - fn kv_cache_seq_pos_max_status_to_result_maps_every_contract_status() { - let outcome_0 = kv_cache_seq_pos_max_status_to_result( + fn kv_cache_seq_pos_max_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = kv_cache_seq_pos_max_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MEMORY_SEQ_POS_MAX_LLAMA_CPP_OUT_OF_MEMORY, 0, 0, ptr::null_mut(), ); assert_eq!( - outcome_0.err(), + outcome.err(), Some(KvCacheSeqPosMaxError::LlamaCppOutOfMemory) ); } diff --git a/llama-cpp-bindings/src/context/session.rs b/llama-cpp-bindings/src/context/session.rs index 511d2ab23..7e30dc05a 100644 --- a/llama-cpp-bindings/src/context/session.rs +++ b/llama-cpp-bindings/src/context/session.rs @@ -396,13 +396,10 @@ impl LlamaContext<'_> { } #[cfg(test)] -mod unit_tests { - use crate::token::LlamaToken; - - use crate::context::load_seq_state_error::LoadSeqStateError; +mod process_session_load_result_tests { + use super::process_session_load_result; use crate::context::load_session_error::LoadSessionError; - - use super::{process_seq_load_result, process_session_load_result}; + use crate::token::LlamaToken; #[test] fn session_load_success_within_bounds() { @@ -434,6 +431,13 @@ mod unit_tests { }) ); } +} + +#[cfg(test)] +mod process_seq_load_result_tests { + use super::process_seq_load_result; + use crate::context::load_seq_state_error::LoadSeqStateError; + use crate::token::LlamaToken; #[test] fn seq_load_success_within_bounds() { @@ -468,19 +472,20 @@ mod unit_tests { } #[cfg(test)] -mod ffi_contract_status_tests { +mod state_data_status_to_result_tests { + use std::ptr; + use super::state_data_status_to_result; use crate::context::state_data_error::StateDataError; - use std::ptr; #[test] - fn state_data_status_to_result_maps_every_contract_status() { - let outcome_0 = state_data_status_to_result( + fn state_data_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = state_data_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_STATE_DATA_LLAMA_CPP_OUT_OF_MEMORY, 0, ptr::null_mut(), "", ); - assert_eq!(outcome_0.err(), Some(StateDataError::LlamaCppOutOfMemory)); + assert_eq!(outcome.err(), Some(StateDataError::LlamaCppOutOfMemory)); } } diff --git a/llama-cpp-bindings/src/error/embeddings_error.rs b/llama-cpp-bindings/src/error/embeddings_error.rs index 3bf58bc6f..7c45ee7d9 100644 --- a/llama-cpp-bindings/src/error/embeddings_error.rs +++ b/llama-cpp-bindings/src/error/embeddings_error.rs @@ -4,8 +4,14 @@ pub enum EmbeddingsError { NotEnabled, #[error("Logits were not enabled for the given token")] LogitsNotEnabled, - #[error("Can't use sequence embeddings with a model supporting only LLAMA_POOLING_TYPE_NONE")] + #[error( + "Sequence embeddings need a pooling context, but this context uses LLAMA_POOLING_TYPE_NONE" + )] NonePoolType, + #[error( + "No sequence embedding exists for sequence {sequence_index}; it was not part of the last decoded batch" + )] + SequenceEmbeddingUnavailable { sequence_index: i32 }, #[error("Invalid embedding dimension: {0}")] InvalidEmbeddingDimension(#[source] std::num::TryFromIntError), #[error( @@ -16,6 +22,22 @@ pub enum EmbeddingsError { }, #[error("NextN embeddings weren't enabled on this context")] NextnEmbeddingsNotEnabled, + #[error( + "NextN embeddings are unavailable because this model's graph produces no NextN hidden state" + )] + NextnEmbeddingsUnsupported, + #[error("llama.cpp failed to reserve the graph that checks for a NextN hidden state")] + NextnProbeGraphReservationFailed, + #[error("not enough memory to report why the NextN hidden state check failed")] + NextnProbeNotEnoughMemory, + #[error("llama.cpp ran out of memory while checking for a NextN hidden state")] + NextnProbeLlamaCppOutOfMemory, + #[error("llama.cpp failed while checking for a NextN hidden state: {message}")] + NextnProbeReported { message: String }, + #[error(transparent)] + FfiContract(#[from] crate::FfiContractError), + #[error(transparent)] + FfiStatus(#[from] crate::FfiStatusError), #[error( "No NextN embedding exists for token {token_index}; it was not marked as an output of the last decoded batch" )] diff --git a/llama-cpp-bindings/src/json_schema_to_grammar.rs b/llama-cpp-bindings/src/json_schema_to_grammar.rs index 22cdc0536..c4cc7615c 100644 --- a/llama-cpp-bindings/src/json_schema_to_grammar.rs +++ b/llama-cpp-bindings/src/json_schema_to_grammar.rs @@ -105,10 +105,7 @@ pub fn json_schema_to_grammar(schema_json: &str) -> Result c_int { + unsafe { llama_cpp_bindings_sys::llama_model_n_embd_out(self.model.as_ptr()) } + } + + #[must_use] + pub fn n_cls_out(&self) -> u32 { + unsafe { llama_cpp_bindings_sys::llama_model_n_cls_out(self.model.as_ptr()) } + } + #[must_use] pub fn size(&self) -> u64 { unsafe { llama_cpp_bindings_sys::llama_model_size(self.model.as_ptr()) } @@ -1732,116 +1742,67 @@ impl Drop for LlamaModel { } #[cfg(test)] -mod extract_meta_string_tests { - use super::extract_meta_string; - use crate::MetaValError; - - #[test] - fn returns_error_when_null_terminator_missing() { - let result = extract_meta_string( - |buf_ptr, buf_len| { - let buffer = - unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::(), buf_len) }; - buffer[0] = b'a'; - buffer[1] = b'b'; - buffer[2] = b'c'; - 2 - }, - 4, - ); - - assert_eq!(result.unwrap_err(), MetaValError::NegativeReturn(-1)); - } - - #[test] - fn returns_error_for_negative_return_value() { - let result = extract_meta_string(|_buf_ptr, _buf_len| -5, 4); - - assert_eq!(result.unwrap_err(), MetaValError::NegativeReturn(-5)); - } - - #[test] - fn returns_error_for_invalid_utf8_data() { - let result = extract_meta_string( - |buf_ptr, buf_len| { - let buffer = - unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::(), buf_len) }; - buffer[0] = 0xFF; - buffer[1] = 0xFE; - buffer[2] = 0; - 2 - }, - 4, - ); - - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("FromUtf8Error")); - } - - #[test] - fn triggers_buffer_resize_when_returned_len_exceeds_capacity() { - let initial_capacity: usize = 4; - let length_exceeding_initial_capacity = 10; - let written_length = 2; - let call_count = std::cell::Cell::new(0); - let result = extract_meta_string( - |buf_ptr, buf_len| { - let count = call_count.get(); - call_count.set(count + 1); - if count == 0 { - length_exceeding_initial_capacity - } else { - let buffer = - unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::(), buf_len) }; - buffer[0] = b'h'; - buffer[1] = b'i'; - buffer[2] = 0; - written_length - } - }, - initial_capacity, - ); - - assert_eq!(result.unwrap(), "hi"); - } +mod cstring_with_validated_len_tests { + use super::cstring_with_validated_len; #[test] fn cstring_with_validated_len_null_byte_returns_error() { - let result = super::cstring_with_validated_len("null\0byte"); + let result = cstring_with_validated_len("null\0byte"); assert!(result.is_err()); } +} + +#[cfg(test)] +mod validate_string_length_for_tokenizer_tests { + use super::validate_string_length_for_tokenizer; #[test] fn validate_string_length_overflow_returns_error() { - let result = super::validate_string_length_for_tokenizer(usize::MAX); + let result = validate_string_length_for_tokenizer(usize::MAX); assert!(result.is_err()); } +} + +#[cfg(test)] +mod checked_token_buffer_capacity_tests { + use super::checked_token_buffer_capacity; #[test] fn checked_token_buffer_capacity_overflow_returns_error() { - assert!(super::checked_token_buffer_capacity(usize::MAX).is_err()); + assert!(checked_token_buffer_capacity(usize::MAX).is_err()); } #[test] fn checked_token_buffer_capacity_in_range_returns_value() { - assert_eq!(super::checked_token_buffer_capacity(8), Ok(8)); + assert_eq!(checked_token_buffer_capacity(8), Ok(8)); } +} + +#[cfg(test)] +mod checked_token_count_tests { + use super::checked_token_count; #[test] fn checked_token_count_negative_returns_error() { - assert!(super::checked_token_count(-1).is_err()); + assert!(checked_token_count(-1).is_err()); } #[test] fn checked_token_count_non_negative_returns_value() { - assert_eq!(super::checked_token_count(5), Ok(5)); + assert_eq!(checked_token_count(5), Ok(5)); } +} + +#[cfg(test)] +mod tokenize_into_buffer_tests { + + use super::tokenize_into_buffer; #[test] fn tokenize_into_buffer_single_pass_sets_length() { - let buffer = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| Ok(3)).unwrap(); + let buffer = tokenize_into_buffer(8, |_tokens, _n_tokens_max| Ok(3)).unwrap(); assert_eq!(buffer.len(), 3); } @@ -1849,7 +1810,7 @@ mod extract_meta_string_tests { #[test] fn tokenize_into_buffer_grows_buffer_when_first_pass_reports_negative_size() { let call_count = std::cell::Cell::new(0); - let buffer = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| { + let buffer = tokenize_into_buffer(8, |_tokens, _n_tokens_max| { let count = call_count.get(); call_count.set(count + 1); if count == 0 { Ok(-20) } else { Ok(15) } @@ -1862,7 +1823,7 @@ mod extract_meta_string_tests { #[test] fn tokenize_into_buffer_propagates_invocation_error() { - let result = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| { + let result = tokenize_into_buffer(8, |_tokens, _n_tokens_max| { Err(crate::StringToTokenError::NotEnoughMemory) }); @@ -1872,7 +1833,7 @@ mod extract_meta_string_tests { #[test] fn tokenize_into_buffer_propagates_second_invocation_error() { let call_count = std::cell::Cell::new(0); - let result = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| { + let result = tokenize_into_buffer(8, |_tokens, _n_tokens_max| { let count = call_count.get(); call_count.set(count + 1); if count == 0 { @@ -1889,7 +1850,7 @@ mod extract_meta_string_tests { #[test] fn tokenize_into_buffer_negative_final_size_returns_conversion_error() { let call_count = std::cell::Cell::new(0); - let result = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| { + let result = tokenize_into_buffer(8, |_tokens, _n_tokens_max| { let count = call_count.get(); call_count.set(count + 1); if count == 0 { Ok(-20) } else { Ok(-5) } @@ -1900,11 +1861,18 @@ mod extract_meta_string_tests { crate::StringToTokenError::CIntConversionError(usize::try_from(-5i32).unwrap_err()) ); } +} + +#[cfg(test)] +mod read_optional_owned_cstr_tests { + + use super::read_optional_owned_cstr; + use crate::MarkerDetectionError; #[test] fn read_optional_owned_cstr_invalid_utf8_returns_error() { let invalid_utf8_with_terminator: [u8; 3] = [0xFF, 0xFE, 0x00]; - let result = super::read_optional_owned_cstr( + let result = read_optional_owned_cstr( invalid_utf8_with_terminator .as_ptr() .cast::(), @@ -1912,17 +1880,22 @@ mod extract_meta_string_tests { assert_eq!( result.unwrap_err(), - crate::MarkerDetectionError::MarkerUtf8Error( - String::from_utf8(vec![0xFF, 0xFE]).unwrap_err() - ) + MarkerDetectionError::MarkerUtf8Error(String::from_utf8(vec![0xFF, 0xFE]).unwrap_err()) ); } +} + +#[cfg(test)] +mod collect_synthetic_tool_call_renders_tests { + + use super::collect_synthetic_tool_call_renders; + use crate::MarkerDetectionError; #[test] fn collect_synthetic_tool_call_renders_first_invalid_utf8_returns_error() { let invalid_utf8_with_terminator: [u8; 3] = [0xFF, 0xFE, 0x00]; let valid_with_terminator: [u8; 3] = [b'o', b'k', 0x00]; - let result = super::collect_synthetic_tool_call_renders( + let result = collect_synthetic_tool_call_renders( invalid_utf8_with_terminator .as_ptr() .cast::(), @@ -1931,9 +1904,7 @@ mod extract_meta_string_tests { assert_eq!( result.unwrap_err(), - crate::MarkerDetectionError::MarkerUtf8Error( - String::from_utf8(vec![0xFF, 0xFE]).unwrap_err() - ) + MarkerDetectionError::MarkerUtf8Error(String::from_utf8(vec![0xFF, 0xFE]).unwrap_err()) ); } @@ -1941,7 +1912,7 @@ mod extract_meta_string_tests { fn collect_synthetic_tool_call_renders_second_invalid_utf8_returns_error() { let valid_with_terminator: [u8; 3] = [b'o', b'k', 0x00]; let invalid_utf8_with_terminator: [u8; 3] = [0xFF, 0xFE, 0x00]; - let result = super::collect_synthetic_tool_call_renders( + let result = collect_synthetic_tool_call_renders( valid_with_terminator.as_ptr().cast::(), invalid_utf8_with_terminator .as_ptr() @@ -1950,38 +1921,15 @@ mod extract_meta_string_tests { assert_eq!( result.unwrap_err(), - crate::MarkerDetectionError::MarkerUtf8Error( - String::from_utf8(vec![0xFF, 0xFE]).unwrap_err() - ) + MarkerDetectionError::MarkerUtf8Error(String::from_utf8(vec![0xFF, 0xFE]).unwrap_err()) ); } } #[cfg(test)] -mod ffi_status_mapping_tests { - use std::ffi::c_char; - use std::mem::discriminant; - use std::path::Path; - use std::ptr; - - use llama_cpp_bindings_types::ReasoningMarkers; - - use super::chat_parser_create_status_to_result; - use super::chat_parser_free_status_to_result; - use super::compute_tool_call_haystack_status_to_result; +mod cxx_exception_owns_out_error_tests { use super::cxx_exception_owns_out_error; - use super::detect_reasoning_markers_status_to_result; - use super::diagnose_tool_call_synthetic_renders_status_to_result; - use super::initialize_lora_adapter; - use super::load_model_from_file_status_to_result; - use super::reasoning_markers_free_status_to_result; - use super::tokenize_status_to_result; - use super::validate_reasoning_markers; - use crate::LlamaLoraAdapterInitError; - use crate::LlamaModelLoadError; use crate::MarkerDetectionError; - use crate::ParseChatMessageError; - use crate::StringToTokenError; #[test] fn cxx_exception_owns_out_error_classifies_each_failure_variant() { @@ -2002,6 +1950,89 @@ mod ffi_status_mapping_tests { ))); assert!(!cxx_exception_owns_out_error::<()>(&Ok(()))); } +} + +#[cfg(test)] +mod extract_meta_string_tests { + use super::extract_meta_string; + use crate::MetaValError; + + #[test] + fn returns_error_when_null_terminator_missing() { + let result = extract_meta_string( + |buf_ptr, buf_len| { + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::(), buf_len) }; + buffer[0] = b'a'; + buffer[1] = b'b'; + buffer[2] = b'c'; + 2 + }, + 4, + ); + + assert_eq!(result.unwrap_err(), MetaValError::NegativeReturn(-1)); + } + + #[test] + fn returns_error_for_negative_return_value() { + let result = extract_meta_string(|_buf_ptr, _buf_len| -5, 4); + + assert_eq!(result.unwrap_err(), MetaValError::NegativeReturn(-5)); + } + + #[test] + fn returns_error_for_invalid_utf8_data() { + let result = extract_meta_string( + |buf_ptr, buf_len| { + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::(), buf_len) }; + buffer[0] = 0xFF; + buffer[1] = 0xFE; + buffer[2] = 0; + 2 + }, + 4, + ); + + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("FromUtf8Error")); + } + + #[test] + fn triggers_buffer_resize_when_returned_len_exceeds_capacity() { + let initial_capacity: usize = 4; + let length_exceeding_initial_capacity = 10; + let written_length = 2; + let call_count = std::cell::Cell::new(0); + let result = extract_meta_string( + |buf_ptr, buf_len| { + let count = call_count.get(); + call_count.set(count + 1); + if count == 0 { + length_exceeding_initial_capacity + } else { + let buffer = + unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::(), buf_len) }; + buffer[0] = b'h'; + buffer[1] = b'i'; + buffer[2] = 0; + written_length + } + }, + initial_capacity, + ); + + assert_eq!(result.unwrap(), "hi"); + } +} + +#[cfg(test)] +mod initialize_lora_adapter_tests { + use std::ptr; + + use super::initialize_lora_adapter; + use crate::LlamaLoraAdapterInitError; #[test] fn lora_adapter_initialization_maps_a_null_pointer_to_unloadable() { @@ -2020,6 +2051,15 @@ mod ffi_status_mapping_tests { assert_eq!(adapter.as_ptr(), pointer.as_ptr()); } +} + +#[cfg(test)] +mod load_model_from_file_status_to_result_tests { + use std::path::Path; + use std::ptr; + + use super::load_model_from_file_status_to_result; + use crate::LlamaModelLoadError; #[test] fn load_model_success_with_null_model_is_contract_error() { @@ -2116,41 +2156,133 @@ mod ffi_status_mapping_tests { } #[test] - fn chat_parser_create_no_chat_template_maps_to_no_chat_template() { - let mut out_error: *mut c_char = ptr::null_mut(); - let result = unsafe { - chat_parser_create_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_MODEL_HAS_NO_CHAT_TEMPLATE, + fn load_model_from_file_status_to_result_maps_null_path_arg() { + let outcome = unsafe { + load_model_from_file_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_NULL_PATH_ARG, ptr::null_mut(), - &raw mut out_error, + ptr::null_mut(), + Path::new("/missing-for-contract-test.gguf"), ) }; - assert_eq!( - discriminant(&result.unwrap_err()), - discriminant(&ParseChatMessageError::NoChatTemplate) + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_load_model_from_file", + detail: "was given a null path argument", + } + .into() + ) ); } #[test] - fn chat_parser_create_no_vocab_maps_to_no_vocab() { - let mut out_error: *mut c_char = ptr::null_mut(); - let result = unsafe { - chat_parser_create_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_MODEL_HAS_NO_VOCAB, + fn load_model_from_file_status_to_result_maps_null_out_model_arg() { + let outcome = unsafe { + load_model_from_file_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_NULL_OUT_MODEL_ARG, ptr::null_mut(), - &raw mut out_error, + ptr::null_mut(), + Path::new("/missing-for-contract-test.gguf"), ) }; - assert_eq!( - discriminant(&result.unwrap_err()), - discriminant(&ParseChatMessageError::NoVocab) + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_load_model_from_file", + detail: "was given a null out_model argument", + } + .into() + ) ); } #[test] - fn chat_parser_create_allocation_failed_is_not_enough_memory() { + fn load_model_from_file_status_to_result_maps_null_out_error_arg() { + let outcome = unsafe { + load_model_from_file_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_NULL_OUT_ERROR_ARG, + ptr::null_mut(), + ptr::null_mut(), + Path::new("/missing-for-contract-test.gguf"), + ) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_load_model_from_file", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + + #[test] + fn load_model_from_file_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { + load_model_from_file_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), + ptr::null_mut(), + Path::new("/missing-for-contract-test.gguf"), + ) + }; + assert_eq!( + outcome.err(), + Some(LlamaModelLoadError::LlamaCppOutOfMemory) + ); + } +} + +#[cfg(test)] +mod chat_parser_create_status_to_result_tests { + use std::ffi::c_char; + use std::mem::discriminant; + use std::ptr; + + use super::chat_parser_create_status_to_result; + use crate::ParseChatMessageError; + + #[test] + fn chat_parser_create_no_chat_template_maps_to_no_chat_template() { + let mut out_error: *mut c_char = ptr::null_mut(); + let result = unsafe { + chat_parser_create_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_MODEL_HAS_NO_CHAT_TEMPLATE, + ptr::null_mut(), + &raw mut out_error, + ) + }; + + assert_eq!( + discriminant(&result.unwrap_err()), + discriminant(&ParseChatMessageError::NoChatTemplate) + ); + } + + #[test] + fn chat_parser_create_no_vocab_maps_to_no_vocab() { + let mut out_error: *mut c_char = ptr::null_mut(); + let result = unsafe { + chat_parser_create_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_MODEL_HAS_NO_VOCAB, + ptr::null_mut(), + &raw mut out_error, + ) + }; + + assert_eq!( + discriminant(&result.unwrap_err()), + discriminant(&ParseChatMessageError::NoVocab) + ); + } + + #[test] + fn chat_parser_create_allocation_failed_is_not_enough_memory() { let mut out_error: *mut c_char = ptr::null_mut(); let result = unsafe { chat_parser_create_status_to_result( @@ -2179,11 +2311,13 @@ mod ffi_status_mapping_tests { ) }; - let Err(ParseChatMessageError::ParserCreationFailed { message }) = result else { - panic!("the llama.cpp exception status must surface the wrapper message"); - }; + assert_eq!( + result.err(), + Some(ParseChatMessageError::ParserCreationFailed { + message: "the parser could not be built".to_owned() + }) + ); - assert_eq!(message, "the parser could not be built"); assert!( out_error.is_null(), "the reclaimed pointer must be nulled so the caller does not free it twice" @@ -2228,6 +2362,93 @@ mod ffi_status_mapping_tests { ); } + #[test] + fn chat_parser_create_status_to_result_maps_null_model_arg() { + let mut out_error_slot: *mut c_char = ptr::null_mut(); + let outcome = unsafe { + chat_parser_create_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_NULL_MODEL_ARG, + ptr::null_mut(), + &raw mut out_error_slot, + ) + }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_chat_parser_create", + detail: "was given a null model argument", + } + )) + ); + } + + #[test] + fn chat_parser_create_status_to_result_maps_null_out_parser_arg() { + let mut out_error_slot: *mut c_char = ptr::null_mut(); + let outcome = unsafe { + chat_parser_create_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_NULL_OUT_PARSER_ARG, + ptr::null_mut(), + &raw mut out_error_slot, + ) + }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_chat_parser_create", + detail: "was given a null out_parser argument", + } + )) + ); + } + + #[test] + fn chat_parser_create_status_to_result_maps_null_out_error_arg() { + let mut out_error_slot: *mut c_char = ptr::null_mut(); + let outcome = unsafe { + chat_parser_create_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_NULL_OUT_ERROR_ARG, + ptr::null_mut(), + &raw mut out_error_slot, + ) + }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::FfiContract( + crate::FfiContractError { + operation: "llama_rs_chat_parser_create", + detail: "was given a null out_error argument", + } + )) + ); + } + + #[test] + fn chat_parser_create_status_to_result_maps_llama_cpp_out_of_memory() { + let mut out_error_slot: *mut c_char = ptr::null_mut(); + let outcome = unsafe { + chat_parser_create_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), + &raw mut out_error_slot, + ) + }; + assert_eq!( + outcome.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); + } +} + +#[cfg(test)] +mod detect_reasoning_markers_status_to_result_tests { + use std::ptr; + + use super::detect_reasoning_markers_status_to_result; + use crate::MarkerDetectionError; + #[test] fn detect_reasoning_markers_ok_with_null_handle_returns_none() { let result = unsafe { @@ -2345,6 +2566,29 @@ mod ffi_status_mapping_tests { ); } + #[test] + fn detect_reasoning_markers_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { + detect_reasoning_markers_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_DETECT_REASONING_MARKERS_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null(), + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some(MarkerDetectionError::LlamaCppOutOfMemory) + ); + } +} + +#[cfg(test)] +mod validate_reasoning_markers_tests { + use llama_cpp_bindings_types::ReasoningMarkers; + + use super::validate_reasoning_markers; + use crate::MarkerDetectionError; + #[test] fn reasoning_markers_reject_a_missing_opening_marker() { let result = validate_reasoning_markers(None, vec![Some("".to_owned())]); @@ -2427,6 +2671,14 @@ mod ffi_status_mapping_tests { }) ); } +} + +#[cfg(test)] +mod compute_tool_call_haystack_status_to_result_tests { + use std::ptr; + + use super::compute_tool_call_haystack_status_to_result; + use crate::MarkerDetectionError; #[test] fn compute_tool_call_haystack_ok_with_null_pointer_is_none() { @@ -2489,6 +2741,92 @@ mod ffi_status_mapping_tests { ); } + #[test] + fn compute_tool_call_haystack_status_to_result_maps_null_model_arg() { + let outcome = unsafe { + compute_tool_call_haystack_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_NULL_MODEL_ARG, + ptr::null(), + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_compute_tool_call_haystack", + detail: "was given a null model argument", + } + .into() + ) + ); + } + + #[test] + fn compute_tool_call_haystack_status_to_result_maps_null_out_haystack_arg() { + let outcome = unsafe { + compute_tool_call_haystack_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_NULL_OUT_HAYSTACK_ARG, + ptr::null(), + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_compute_tool_call_haystack", + detail: "was given a null out_haystack argument", + } + .into() + ) + ); + } + + #[test] + fn compute_tool_call_haystack_status_to_result_maps_null_out_error_arg() { + let outcome = unsafe { + compute_tool_call_haystack_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_NULL_OUT_ERROR_ARG, + ptr::null(), + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_compute_tool_call_haystack", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + + #[test] + fn compute_tool_call_haystack_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { + compute_tool_call_haystack_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null(), + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some(MarkerDetectionError::LlamaCppOutOfMemory) + ); + } +} + +#[cfg(test)] +mod diagnose_tool_call_synthetic_renders_status_to_result_tests { + use std::ptr; + + use super::diagnose_tool_call_synthetic_renders_status_to_result; + use crate::MarkerDetectionError; + #[test] fn diagnose_tool_call_synthetic_renders_ok_with_null_pointers_is_a_contract_error() { let result = unsafe { @@ -2566,13 +2904,100 @@ mod ffi_status_mapping_tests { } #[test] - fn tokenize_ok_returns_count() { - let result = unsafe { - tokenize_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_OK, - 12, - ptr::null_mut(), - ) + fn diagnose_tool_call_synthetic_renders_status_to_result_maps_null_model_arg() { + let outcome = unsafe { + diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_NULL_MODEL_ARG, ptr::null(), ptr::null(), ptr::null_mut()) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_diagnose_tool_call_synthetic_renders", + detail: "was given a null model argument", + } + .into() + ) + ); + } + + #[test] + fn diagnose_tool_call_synthetic_renders_status_to_result_maps_null_out_no_tools_arg() { + let outcome = unsafe { + diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_NULL_OUT_NO_TOOLS_ARG, ptr::null(), ptr::null(), ptr::null_mut()) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_diagnose_tool_call_synthetic_renders", + detail: "was given a null out_no_tools argument", + } + .into() + ) + ); + } + + #[test] + fn diagnose_tool_call_synthetic_renders_status_to_result_maps_null_out_with_tools_arg() { + let outcome = unsafe { + diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_NULL_OUT_WITH_TOOLS_ARG, ptr::null(), ptr::null(), ptr::null_mut()) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_diagnose_tool_call_synthetic_renders", + detail: "was given a null out_with_tools argument", + } + .into() + ) + ); + } + + #[test] + fn diagnose_tool_call_synthetic_renders_status_to_result_maps_null_out_error_arg() { + let outcome = unsafe { + diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_NULL_OUT_ERROR_ARG, ptr::null(), ptr::null(), ptr::null_mut()) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_diagnose_tool_call_synthetic_renders", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + + #[test] + fn diagnose_tool_call_synthetic_renders_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { + diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_LLAMA_CPP_OUT_OF_MEMORY, ptr::null(), ptr::null(), ptr::null_mut()) + }; + assert_eq!( + outcome.err(), + Some(MarkerDetectionError::LlamaCppOutOfMemory) + ); + } +} + +#[cfg(test)] +mod tokenize_status_to_result_tests { + use std::ptr; + + use super::tokenize_status_to_result; + use crate::StringToTokenError; + + #[test] + fn tokenize_ok_returns_count() { + let result = unsafe { + tokenize_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_OK, + 12, + ptr::null_mut(), + ) }; assert_eq!(result, Ok(12)); @@ -2624,12 +3049,116 @@ mod ffi_status_mapping_tests { ); } + #[test] + fn tokenize_status_to_result_maps_null_vocab_arg() { + let outcome = unsafe { + tokenize_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_NULL_VOCAB_ARG, + 0, + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_tokenize", + detail: "was given a null vocab argument", + } + .into() + ) + ); + } + + #[test] + fn tokenize_status_to_result_maps_null_text_arg() { + let outcome = unsafe { + tokenize_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_NULL_TEXT_ARG, + 0, + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_tokenize", + detail: "was given a null text argument", + } + .into() + ) + ); + } + + #[test] + fn tokenize_status_to_result_maps_null_out_returned_count_arg() { + let outcome = unsafe { + tokenize_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_NULL_OUT_RETURNED_COUNT_ARG, + 0, + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_tokenize", + detail: "was given a null out_returned_count argument", + } + .into() + ) + ); + } + + #[test] + fn tokenize_status_to_result_maps_null_out_error_arg() { + let outcome = unsafe { + tokenize_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_NULL_OUT_ERROR_ARG, + 0, + ptr::null_mut(), + ) + }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_tokenize", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + + #[test] + fn tokenize_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { + tokenize_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY, + 0, + ptr::null_mut(), + ) + }; + assert_eq!(outcome.err(), Some(StringToTokenError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod apply_chat_template_status_to_result_tests { + use std::ptr; + + use super::apply_chat_template_status_to_result; + use crate::error::apply_chat_template_error::ApplyChatTemplateError; + #[test] fn apply_chat_template_ok_returns_rendered_prompt() { let rendered = std::ffi::CString::new("rendered prompt").unwrap(); let out_string = unsafe { llama_cpp_bindings_sys::llama_rs_string_dup(rendered.as_ptr()) }; let result = unsafe { - super::apply_chat_template_status_to_result( + apply_chat_template_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_OK, out_string, ptr::null_mut(), @@ -2642,7 +3171,7 @@ mod ffi_status_mapping_tests { #[test] fn apply_chat_template_ok_with_null_rendered_prompt_is_contract_error() { let result = unsafe { - super::apply_chat_template_status_to_result( + apply_chat_template_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_OK, ptr::null_mut(), ptr::null_mut(), @@ -2651,7 +3180,7 @@ mod ffi_status_mapping_tests { assert_eq!( result, - Err(crate::ApplyChatTemplateError::FfiContract( + Err(ApplyChatTemplateError::FfiContract( crate::FfiContractError { operation: "llama_rs_apply_chat_template", detail: "success status contained a null rendered string", @@ -2663,20 +3192,20 @@ mod ffi_status_mapping_tests { #[test] fn apply_chat_template_no_vocab_maps_to_no_vocab() { let result = unsafe { - super::apply_chat_template_status_to_result( + apply_chat_template_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_MODEL_HAS_NO_VOCAB, ptr::null_mut(), ptr::null_mut(), ) }; - assert_eq!(result, Err(crate::ApplyChatTemplateError::NoVocab)); + assert_eq!(result, Err(ApplyChatTemplateError::NoVocab)); } #[test] fn apply_chat_template_application_failed_maps_to_template_application_failed() { let result = unsafe { - super::apply_chat_template_status_to_result( + apply_chat_template_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_TEMPLATE_APPLICATION_FAILED, ptr::null_mut(), ptr::null_mut(), @@ -2685,21 +3214,21 @@ mod ffi_status_mapping_tests { assert_eq!( result, - Err(crate::ApplyChatTemplateError::TemplateApplicationFailed) + Err(ApplyChatTemplateError::TemplateApplicationFailed) ); } #[test] fn apply_chat_template_allocation_failed_maps_to_not_enough_memory() { let result = unsafe { - super::apply_chat_template_status_to_result( + apply_chat_template_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_ERROR_STRING_ALLOCATION_FAILED, ptr::null_mut(), ptr::null_mut(), ) }; - assert_eq!(result, Err(crate::ApplyChatTemplateError::NotEnoughMemory)); + assert_eq!(result, Err(ApplyChatTemplateError::NotEnoughMemory)); } #[test] @@ -2707,7 +3236,7 @@ mod ffi_status_mapping_tests { let message = std::ffi::CString::new("renderer exploded").unwrap(); let out_error = unsafe { llama_cpp_bindings_sys::llama_rs_string_dup(message.as_ptr()) }; let result = unsafe { - super::apply_chat_template_status_to_result( + apply_chat_template_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_LLAMA_CPP_THREW_CXX_EXCEPTION, ptr::null_mut(), out_error, @@ -2716,7 +3245,7 @@ mod ffi_status_mapping_tests { assert_eq!( result, - Err(crate::ApplyChatTemplateError::Reported { + Err(ApplyChatTemplateError::Reported { message: "renderer exploded".to_owned() }) ); @@ -2724,651 +3253,296 @@ mod ffi_status_mapping_tests { #[test] fn apply_chat_template_unknown_status_is_preserved() { - let result = unsafe { - super::apply_chat_template_status_to_result(255, ptr::null_mut(), ptr::null_mut()) - }; + let result = + unsafe { apply_chat_template_status_to_result(255, ptr::null_mut(), ptr::null_mut()) }; assert_eq!( result, - Err(crate::ApplyChatTemplateError::FfiStatus( - crate::FfiStatusError { - operation: "llama_rs_apply_chat_template", - code: 255, - } - )) + Err(ApplyChatTemplateError::FfiStatus(crate::FfiStatusError { + operation: "llama_rs_apply_chat_template", + code: 255, + })) ); } #[test] - fn chat_parser_free_ok_is_success() { - let result = unsafe { - chat_parser_free_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_FREE_OK, + fn apply_chat_template_status_to_result_maps_null_model_arg() { + let outcome = unsafe { + apply_chat_template_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_MODEL_ARG, + ptr::null_mut(), ptr::null_mut(), ) }; - - assert!( - result.is_ok(), - "a clean destructor must not report a failure" + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_apply_chat_template", + detail: "was given a null model argument", + } + .into() + ) ); } #[test] - fn chat_parser_free_allocation_failed_is_not_enough_memory() { - let result = unsafe { - chat_parser_free_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_FREE_ERROR_STRING_ALLOCATION_FAILED, + fn apply_chat_template_status_to_result_maps_null_template_arg() { + let outcome = unsafe { + apply_chat_template_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_TEMPLATE_ARG, + ptr::null_mut(), ptr::null_mut(), ) }; - - let Err(ParseChatMessageError::NotEnoughMemory) = result else { - panic!("an error-string allocation failure must map to NotEnoughMemory"); - }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_apply_chat_template", + detail: "was given a null template argument", + } + .into() + ) + ); } #[test] - fn chat_parser_free_llama_cpp_out_of_memory_is_preserved() { - let result = unsafe { - chat_parser_free_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_FREE_LLAMA_CPP_OUT_OF_MEMORY, + fn apply_chat_template_status_to_result_maps_null_messages_arg() { + let outcome = unsafe { + apply_chat_template_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_MESSAGES_ARG, + ptr::null_mut(), ptr::null_mut(), ) }; - - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = result else { - panic!("a llama.cpp allocation failure must be reported as its own variant"); - }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_apply_chat_template", + detail: "was given a null messages argument", + } + .into() + ) + ); } #[test] - fn chat_parser_free_destructor_threw_surfaces_the_message() { - let out_error = unsafe { - llama_cpp_bindings_sys::llama_rs_string_dup(c"the destructor threw".as_ptr()) - }; - let result = unsafe { - chat_parser_free_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION, - out_error, + fn apply_chat_template_status_to_result_maps_null_out_string_arg() { + let outcome = unsafe { + apply_chat_template_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_OUT_STRING_ARG, + ptr::null_mut(), + ptr::null_mut(), ) }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_apply_chat_template", + detail: "was given a null out_string argument", + } + .into() + ) + ); + } - let Err(ParseChatMessageError::DestructorFailed { message }) = result else { - panic!("a throwing destructor must surface its message"); - }; - - assert_eq!(message, "the destructor threw"); - } - - #[test] - fn chat_parser_free_unknown_status_is_preserved() { - let result = unsafe { chat_parser_free_status_to_result(255, ptr::null_mut()) }; - - let Err(ParseChatMessageError::FfiStatus(status_error)) = result else { - panic!("an unrecognized status must be preserved verbatim"); + #[test] + fn apply_chat_template_status_to_result_maps_null_out_error_arg() { + let outcome = unsafe { + apply_chat_template_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_OUT_ERROR_ARG, + ptr::null_mut(), + ptr::null_mut(), + ) }; + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_apply_chat_template", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + #[test] + fn apply_chat_template_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { + apply_chat_template_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), + ptr::null_mut(), + ) + }; assert_eq!( - status_error, - crate::FfiStatusError { - operation: "llama_rs_chat_parser_free", - code: 255, - } + outcome.err(), + Some(ApplyChatTemplateError::LlamaCppOutOfMemory) ); } +} + +#[cfg(test)] +mod chat_parser_free_status_to_result_tests { + use std::ptr; + + use super::chat_parser_free_status_to_result; + use crate::ParseChatMessageError; #[test] - fn reasoning_markers_free_ok_is_success() { + fn chat_parser_free_ok_is_success() { let result = unsafe { - reasoning_markers_free_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_REASONING_MARKERS_FREE_OK, + chat_parser_free_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_FREE_OK, ptr::null_mut(), ) }; - assert_eq!(result, Ok(())); + assert!( + result.is_ok(), + "a clean destructor must not report a failure" + ); } #[test] - fn reasoning_markers_free_allocation_failed_is_not_enough_memory() { + fn chat_parser_free_allocation_failed_is_not_enough_memory() { let result = unsafe { - reasoning_markers_free_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_REASONING_MARKERS_FREE_ERROR_STRING_ALLOCATION_FAILED, + chat_parser_free_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_FREE_ERROR_STRING_ALLOCATION_FAILED, ptr::null_mut(), ) }; - assert_eq!(result, Err(MarkerDetectionError::NotEnoughMemory)); + assert_eq!(result.err(), Some(ParseChatMessageError::NotEnoughMemory)); } #[test] - fn reasoning_markers_free_llama_cpp_out_of_memory_is_preserved() { + fn chat_parser_free_llama_cpp_out_of_memory_is_preserved() { let result = unsafe { - reasoning_markers_free_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_REASONING_MARKERS_FREE_LLAMA_CPP_OUT_OF_MEMORY, + chat_parser_free_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_FREE_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), ) }; - assert_eq!(result, Err(MarkerDetectionError::LlamaCppOutOfMemory)); + assert_eq!( + result.err(), + Some(ParseChatMessageError::LlamaCppOutOfMemory) + ); } #[test] - fn reasoning_markers_free_destructor_threw_surfaces_the_message() { + fn chat_parser_free_destructor_threw_surfaces_the_message() { let out_error = unsafe { llama_cpp_bindings_sys::llama_rs_string_dup(c"the destructor threw".as_ptr()) }; let result = unsafe { - reasoning_markers_free_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_REASONING_MARKERS_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION, + chat_parser_free_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION, out_error, ) }; assert_eq!( - result, - Err(MarkerDetectionError::ReasoningMarkersFreeFailed { - message: "the destructor threw".to_owned(), + result.err(), + Some(ParseChatMessageError::DestructorFailed { + message: "the destructor threw".to_owned() }) ); } #[test] - fn reasoning_markers_free_unknown_status_is_preserved() { - let result = unsafe { reasoning_markers_free_status_to_result(255, ptr::null_mut()) }; + fn chat_parser_free_unknown_status_is_preserved() { + let result = unsafe { chat_parser_free_status_to_result(255, ptr::null_mut()) }; assert_eq!( - result, - Err(crate::FfiStatusError { - operation: "llama_rs_reasoning_markers_free", + result.err(), + Some(ParseChatMessageError::FfiStatus(crate::FfiStatusError { + operation: "llama_rs_chat_parser_free", code: 255, - } - .into()) + })) ); } } #[cfg(test)] -mod ffi_contract_status_tests { - use super::apply_chat_template_status_to_result; - use super::chat_parser_create_status_to_result; - use super::compute_tool_call_haystack_status_to_result; - use super::detect_reasoning_markers_status_to_result; - use super::diagnose_tool_call_synthetic_renders_status_to_result; - use super::load_model_from_file_status_to_result; - use super::tokenize_status_to_result; - use crate::error::apply_chat_template_error::ApplyChatTemplateError; - use crate::error::llama_model_load_error::LlamaModelLoadError; - use crate::error::marker_detection_error::MarkerDetectionError; - use crate::error::parse_chat_message_error::ParseChatMessageError; - use crate::error::string_to_token_error::StringToTokenError; - use std::ffi::c_char; - use std::path::Path; +mod reasoning_markers_free_status_to_result_tests { use std::ptr; - #[test] - fn load_model_from_file_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { - load_model_from_file_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_NULL_PATH_ARG, - ptr::null_mut(), - ptr::null_mut(), - Path::new("/missing-for-contract-test.gguf"), - ) - }; - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_load_model_from_file", - detail: "was given a null path argument", - } - .into() - ) - ); - let outcome_1 = unsafe { - load_model_from_file_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_NULL_OUT_MODEL_ARG, - ptr::null_mut(), - ptr::null_mut(), - Path::new("/missing-for-contract-test.gguf"), - ) - }; - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_load_model_from_file", - detail: "was given a null out_model argument", - } - .into() - ) - ); - let outcome_2 = unsafe { - load_model_from_file_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_NULL_OUT_ERROR_ARG, - ptr::null_mut(), - ptr::null_mut(), - Path::new("/missing-for-contract-test.gguf"), - ) - }; - assert_eq!( - outcome_2.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_load_model_from_file", - detail: "was given a null out_error argument", - } - .into() - ) - ); - let outcome_3 = unsafe { - load_model_from_file_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY, - ptr::null_mut(), - ptr::null_mut(), - Path::new("/missing-for-contract-test.gguf"), - ) - }; - assert_eq!( - outcome_3.err(), - Some(LlamaModelLoadError::LlamaCppOutOfMemory) - ); - } + use super::reasoning_markers_free_status_to_result; + use crate::MarkerDetectionError; #[test] - fn chat_parser_create_status_to_result_maps_every_contract_status() { - let mut out_error_slot: *mut c_char = ptr::null_mut(); - let outcome_0 = unsafe { - chat_parser_create_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_NULL_MODEL_ARG, - ptr::null_mut(), - &raw mut out_error_slot, - ) - }; - let Err(ParseChatMessageError::FfiContract(contract_0)) = outcome_0 else { - panic!("LLAMA_RS_CHAT_PARSER_CREATE_NULL_MODEL_ARG must map to a contract error"); - }; - assert_eq!( - contract_0, - crate::FfiContractError { - operation: "llama_rs_chat_parser_create", - detail: "was given a null model argument", - } - ); - let outcome_1 = unsafe { - chat_parser_create_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_NULL_OUT_PARSER_ARG, - ptr::null_mut(), - &raw mut out_error_slot, - ) - }; - let Err(ParseChatMessageError::FfiContract(contract_1)) = outcome_1 else { - panic!("LLAMA_RS_CHAT_PARSER_CREATE_NULL_OUT_PARSER_ARG must map to a contract error"); - }; - assert_eq!( - contract_1, - crate::FfiContractError { - operation: "llama_rs_chat_parser_create", - detail: "was given a null out_parser argument", - } - ); - let outcome_2 = unsafe { - chat_parser_create_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_NULL_OUT_ERROR_ARG, - ptr::null_mut(), - &raw mut out_error_slot, - ) - }; - let Err(ParseChatMessageError::FfiContract(contract_2)) = outcome_2 else { - panic!("LLAMA_RS_CHAT_PARSER_CREATE_NULL_OUT_ERROR_ARG must map to a contract error"); - }; - assert_eq!( - contract_2, - crate::FfiContractError { - operation: "llama_rs_chat_parser_create", - detail: "was given a null out_error argument", - } - ); - let outcome_3 = unsafe { - chat_parser_create_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_LLAMA_CPP_OUT_OF_MEMORY, + fn reasoning_markers_free_ok_is_success() { + let result = unsafe { + reasoning_markers_free_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_REASONING_MARKERS_FREE_OK, ptr::null_mut(), - &raw mut out_error_slot, ) }; - let Err(ParseChatMessageError::LlamaCppOutOfMemory) = outcome_3 else { - panic!( - "LLAMA_RS_CHAT_PARSER_CREATE_LLAMA_CPP_OUT_OF_MEMORY must map to LlamaCppOutOfMemory" - ); - }; - } - #[test] - fn apply_chat_template_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { - apply_chat_template_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_MODEL_ARG, - ptr::null_mut(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_apply_chat_template", - detail: "was given a null model argument", - } - .into() - ) - ); - let outcome_1 = unsafe { - apply_chat_template_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_TEMPLATE_ARG, - ptr::null_mut(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_apply_chat_template", - detail: "was given a null template argument", - } - .into() - ) - ); - let outcome_2 = unsafe { - apply_chat_template_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_MESSAGES_ARG, - ptr::null_mut(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_2.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_apply_chat_template", - detail: "was given a null messages argument", - } - .into() - ) - ); - let outcome_3 = unsafe { - apply_chat_template_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_OUT_STRING_ARG, - ptr::null_mut(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_3.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_apply_chat_template", - detail: "was given a null out_string argument", - } - .into() - ) - ); - let outcome_4 = unsafe { - apply_chat_template_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_NULL_OUT_ERROR_ARG, - ptr::null_mut(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_4.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_apply_chat_template", - detail: "was given a null out_error argument", - } - .into() - ) - ); - let outcome_5 = unsafe { - apply_chat_template_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_LLAMA_CPP_OUT_OF_MEMORY, - ptr::null_mut(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_5.err(), - Some(ApplyChatTemplateError::LlamaCppOutOfMemory) - ); + assert_eq!(result, Ok(())); } #[test] - fn detect_reasoning_markers_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { - detect_reasoning_markers_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_DETECT_REASONING_MARKERS_LLAMA_CPP_OUT_OF_MEMORY, - ptr::null(), + fn reasoning_markers_free_allocation_failed_is_not_enough_memory() { + let result = unsafe { + reasoning_markers_free_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_REASONING_MARKERS_FREE_ERROR_STRING_ALLOCATION_FAILED, ptr::null_mut(), ) }; - assert_eq!( - outcome_0.err(), - Some(MarkerDetectionError::LlamaCppOutOfMemory) - ); + + assert_eq!(result, Err(MarkerDetectionError::NotEnoughMemory)); } #[test] - fn compute_tool_call_haystack_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { - compute_tool_call_haystack_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_NULL_MODEL_ARG, - ptr::null(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_compute_tool_call_haystack", - detail: "was given a null model argument", - } - .into() - ) - ); - let outcome_1 = unsafe { - compute_tool_call_haystack_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_NULL_OUT_HAYSTACK_ARG, - ptr::null(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_compute_tool_call_haystack", - detail: "was given a null out_haystack argument", - } - .into() - ) - ); - let outcome_2 = unsafe { - compute_tool_call_haystack_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_NULL_OUT_ERROR_ARG, - ptr::null(), - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_2.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_compute_tool_call_haystack", - detail: "was given a null out_error argument", - } - .into() - ) - ); - let outcome_3 = unsafe { - compute_tool_call_haystack_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_LLAMA_CPP_OUT_OF_MEMORY, - ptr::null(), + fn reasoning_markers_free_llama_cpp_out_of_memory_is_preserved() { + let result = unsafe { + reasoning_markers_free_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_REASONING_MARKERS_FREE_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), ) }; - assert_eq!( - outcome_3.err(), - Some(MarkerDetectionError::LlamaCppOutOfMemory) - ); + + assert_eq!(result, Err(MarkerDetectionError::LlamaCppOutOfMemory)); } #[test] - fn diagnose_tool_call_synthetic_renders_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { - diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_NULL_MODEL_ARG, ptr::null(), ptr::null(), ptr::null_mut()) - }; - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_diagnose_tool_call_synthetic_renders", - detail: "was given a null model argument", - } - .into() - ) - ); - let outcome_1 = unsafe { - diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_NULL_OUT_NO_TOOLS_ARG, ptr::null(), ptr::null(), ptr::null_mut()) - }; - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_diagnose_tool_call_synthetic_renders", - detail: "was given a null out_no_tools argument", - } - .into() - ) - ); - let outcome_2 = unsafe { - diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_NULL_OUT_WITH_TOOLS_ARG, ptr::null(), ptr::null(), ptr::null_mut()) - }; - assert_eq!( - outcome_2.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_diagnose_tool_call_synthetic_renders", - detail: "was given a null out_with_tools argument", - } - .into() - ) - ); - let outcome_3 = unsafe { - diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_NULL_OUT_ERROR_ARG, ptr::null(), ptr::null(), ptr::null_mut()) + fn reasoning_markers_free_destructor_threw_surfaces_the_message() { + let out_error = unsafe { + llama_cpp_bindings_sys::llama_rs_string_dup(c"the destructor threw".as_ptr()) }; - assert_eq!( - outcome_3.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_diagnose_tool_call_synthetic_renders", - detail: "was given a null out_error argument", - } - .into() + let result = unsafe { + reasoning_markers_free_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_REASONING_MARKERS_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION, + out_error, ) - ); - let outcome_4 = unsafe { - diagnose_tool_call_synthetic_renders_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_LLAMA_CPP_OUT_OF_MEMORY, ptr::null(), ptr::null(), ptr::null_mut()) }; + assert_eq!( - outcome_4.err(), - Some(MarkerDetectionError::LlamaCppOutOfMemory) + result, + Err(MarkerDetectionError::ReasoningMarkersFreeFailed { + message: "the destructor threw".to_owned(), + }) ); } #[test] - fn tokenize_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { - tokenize_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_NULL_VOCAB_ARG, - 0, - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_tokenize", - detail: "was given a null vocab argument", - } - .into() - ) - ); - let outcome_1 = unsafe { - tokenize_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_NULL_TEXT_ARG, - 0, - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_tokenize", - detail: "was given a null text argument", - } - .into() - ) - ); - let outcome_2 = unsafe { - tokenize_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_NULL_OUT_RETURNED_COUNT_ARG, - 0, - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_2.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_tokenize", - detail: "was given a null out_returned_count argument", - } - .into() - ) - ); - let outcome_3 = unsafe { - tokenize_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_NULL_OUT_ERROR_ARG, - 0, - ptr::null_mut(), - ) - }; - assert_eq!( - outcome_3.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_tokenize", - detail: "was given a null out_error argument", - } - .into() - ) - ); - let outcome_4 = unsafe { - tokenize_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY, - 0, - ptr::null_mut(), - ) - }; + fn reasoning_markers_free_unknown_status_is_preserved() { + let result = unsafe { reasoning_markers_free_status_to_result(255, ptr::null_mut()) }; + assert_eq!( - outcome_4.err(), - Some(StringToTokenError::LlamaCppOutOfMemory) + result, + Err(crate::FfiStatusError { + operation: "llama_rs_reasoning_markers_free", + code: 255, + } + .into()) ); } } diff --git a/llama-cpp-bindings/src/model/params.rs b/llama-cpp-bindings/src/model/params.rs index 21976bedb..8265a7e96 100644 --- a/llama-cpp-bindings/src/model/params.rs +++ b/llama-cpp-bindings/src/model/params.rs @@ -490,12 +490,30 @@ impl Default for LlamaModelParams { #[cfg(test)] mod tests { + use std::ptr; + + use super::LLAMA_CPP_MAX_DEVICES; + use super::LlamaModelParams; + use super::fit_params_status_to_result; + use crate::error::fit_error::FitError; use crate::model::llama_lazy_mode::LlamaLazyMode; use crate::model::llama_lazy_mode_parse_error::LlamaLazyModeParseError; use crate::model::llama_load_mode::LlamaLoadMode; use crate::model::split_mode::LlamaSplitMode; - use super::{LLAMA_CPP_MAX_DEVICES, LlamaModelParams}; + fn contract_error_reported_for( + status: llama_cpp_bindings_sys::llama_rs_fit_params_status, + ) -> Option { + fit_params_status_to_result(status, 0, ptr::null_mut()).err() + } + + fn contract_error_with(detail: &'static str) -> FitError { + crate::FfiContractError { + operation: "llama_rs_fit_params", + detail, + } + .into() + } #[test] fn device_capacity_is_not_smaller_than_the_upstream_limit() { @@ -883,12 +901,108 @@ mod tests { ); } + #[test] + fn fit_params_status_to_result_maps_null_path_model_arg() { + assert_eq!( + contract_error_reported_for( + llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_PATH_MODEL_ARG + ), + Some(contract_error_with("was given a null path_model argument")) + ); + } + + #[test] + fn fit_params_status_to_result_maps_null_mparams_arg() { + assert_eq!( + contract_error_reported_for( + llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_MPARAMS_ARG + ), + Some(contract_error_with("was given a null mparams argument")) + ); + } + + #[test] + fn fit_params_status_to_result_maps_null_cparams_arg() { + assert_eq!( + contract_error_reported_for( + llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_CPARAMS_ARG + ), + Some(contract_error_with("was given a null cparams argument")) + ); + } + + #[test] + fn fit_params_status_to_result_maps_null_out_unrecognized_status_code_arg() { + assert_eq!( + contract_error_reported_for( + llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_OUT_UNRECOGNIZED_STATUS_CODE_ARG + ), + Some(contract_error_with( + "was given a null out_unrecognized_status_code argument" + )) + ); + } + + #[test] + fn fit_params_status_to_result_maps_null_out_error_arg() { + assert_eq!( + contract_error_reported_for( + llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_OUT_ERROR_ARG + ), + Some(contract_error_with("was given a null out_error argument")) + ); + } + + #[test] + fn fit_params_status_to_result_maps_null_extra_path_model_arg() { + assert_eq!( + contract_error_reported_for( + llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_EXTRA_PATH_MODEL_ARG + ), + Some(contract_error_with( + "was given an extra model with a null path_model argument" + )) + ); + } + + #[test] + fn fit_params_status_to_result_maps_null_extra_mparams_arg() { + assert_eq!( + contract_error_reported_for( + llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_EXTRA_MPARAMS_ARG + ), + Some(contract_error_with( + "was given an extra model with a null mparams argument" + )) + ); + } + + #[test] + fn fit_params_status_to_result_maps_null_extra_cparams_arg() { + assert_eq!( + contract_error_reported_for( + llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_EXTRA_CPARAMS_ARG + ), + Some(contract_error_with( + "was given an extra model with a null cparams argument" + )) + ); + } +} + +#[cfg(test)] +mod fit_params_status_to_result_tests { + use std::ptr; + + use super::fit_params_status_to_result; + use crate::error::fit_error::FitError; + #[test] fn fit_params_status_ok_returns_ok() { - let result = super::fit_params_status_to_result( + let result = fit_params_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_OK, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Ok(())); @@ -896,57 +1010,54 @@ mod tests { #[test] fn fit_params_status_reported_failure_returns_no_fitting_memory_layout() { - let result = super::fit_params_status_to_result( + let result = fit_params_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_LLAMA_CPP_REPORTED_FAILURE, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); - assert_eq!(result, Err(crate::error::FitError::NoFittingMemoryLayout)); + assert_eq!(result, Err(FitError::NoFittingMemoryLayout)); } #[test] fn fit_params_status_reported_error_returns_aborted() { - let result = super::fit_params_status_to_result( + let result = fit_params_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_LLAMA_CPP_REPORTED_ERROR, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); - assert_eq!(result, Err(crate::error::FitError::Aborted)); + assert_eq!(result, Err(FitError::Aborted)); } #[test] fn fit_params_status_unrecognized_code_returns_unknown_status() { - let result = super::fit_params_status_to_result( + let result = fit_params_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_LLAMA_CPP_RETURNED_UNRECOGNIZED_STATUS_CODE, 42, - std::ptr::null_mut(), + ptr::null_mut(), ); - assert_eq!( - result, - Err(crate::error::FitError::UnknownStatus { code: 42 }) - ); + assert_eq!(result, Err(FitError::UnknownStatus { code: 42 })); } #[test] fn fit_params_status_allocation_failed_returns_not_enough_memory() { - let result = super::fit_params_status_to_result( + let result = fit_params_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_ERROR_STRING_ALLOCATION_FAILED, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); - assert_eq!(result, Err(crate::error::FitError::NotEnoughMemory)); + assert_eq!(result, Err(FitError::NotEnoughMemory)); } #[test] fn fit_params_status_cxx_exception_without_a_message_is_a_contract_error_with_unknown_error() { - let result = super::fit_params_status_to_result( + let result = fit_params_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_LLAMA_CPP_THREW_CXX_EXCEPTION, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -961,80 +1072,16 @@ mod tests { #[test] fn fit_params_unknown_wrapper_status_is_preserved() { - let result = super::fit_params_status_to_result(255, 0, std::ptr::null_mut()); + let result = fit_params_status_to_result(255, 0, ptr::null_mut()); assert_eq!( result, - Err(crate::error::FitError::FfiStatus(crate::FfiStatusError { + Err(FitError::FfiStatus(crate::FfiStatusError { operation: "llama_rs_fit_params", code: 255, })) ); } -} - -#[cfg(test)] -mod ffi_contract_status_tests { - use super::fit_params_status_to_result; - use crate::error::fit_error::FitError; - use std::ptr; - - struct ContractStatusCase { - status: llama_cpp_bindings_sys::llama_rs_fit_params_status, - detail: &'static str, - } - - #[test] - fn fit_params_status_to_result_maps_every_contract_status() { - let cases = [ - ContractStatusCase { - status: llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_PATH_MODEL_ARG, - detail: "was given a null path_model argument", - }, - ContractStatusCase { - status: llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_MPARAMS_ARG, - detail: "was given a null mparams argument", - }, - ContractStatusCase { - status: llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_CPARAMS_ARG, - detail: "was given a null cparams argument", - }, - ContractStatusCase { - status: - llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_OUT_UNRECOGNIZED_STATUS_CODE_ARG, - detail: "was given a null out_unrecognized_status_code argument", - }, - ContractStatusCase { - status: llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_OUT_ERROR_ARG, - detail: "was given a null out_error argument", - }, - ContractStatusCase { - status: llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_EXTRA_PATH_MODEL_ARG, - detail: "was given an extra model with a null path_model argument", - }, - ContractStatusCase { - status: llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_EXTRA_MPARAMS_ARG, - detail: "was given an extra model with a null mparams argument", - }, - ContractStatusCase { - status: llama_cpp_bindings_sys::LLAMA_RS_FIT_PARAMS_NULL_EXTRA_CPARAMS_ARG, - detail: "was given an extra model with a null cparams argument", - }, - ]; - - for ContractStatusCase { status, detail } in cases { - assert_eq!( - fit_params_status_to_result(status, 0, ptr::null_mut()).err(), - Some( - crate::FfiContractError { - operation: "llama_rs_fit_params", - detail, - } - .into() - ) - ); - } - } #[test] fn fit_params_status_llama_cpp_out_of_memory_returns_llama_cpp_out_of_memory() { diff --git a/llama-cpp-bindings/src/mtmd.rs b/llama-cpp-bindings/src/mtmd.rs index 2f80205e4..2d7c4212e 100644 --- a/llama-cpp-bindings/src/mtmd.rs +++ b/llama-cpp-bindings/src/mtmd.rs @@ -1,3 +1,5 @@ +mod mtmd_ffi_input_part; + pub mod fitted_mtmd_input_chunk; pub mod fitted_mtmd_input_chunks; pub mod micro_batch_tokens; diff --git a/llama-cpp-bindings/src/mtmd/fitted_mtmd_input_chunk.rs b/llama-cpp-bindings/src/mtmd/fitted_mtmd_input_chunk.rs index f5186da7f..abdd1f4bf 100644 --- a/llama-cpp-bindings/src/mtmd/fitted_mtmd_input_chunk.rs +++ b/llama-cpp-bindings/src/mtmd/fitted_mtmd_input_chunk.rs @@ -178,7 +178,9 @@ impl<'fitted> FittedMtmdInputChunk<'fitted> { } #[cfg(test)] -mod unit_tests { +mod eval_chunk_single_status_to_result_tests { + use std::ptr; + use llama_cpp_ffi_status::read_and_free_cpp_string; use llama_cpp_wrapper_error_fixture::wrapper_allocated_error_message_pointer; @@ -215,7 +217,7 @@ mod unit_tests { llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_OK, 7, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Ok(7)); @@ -227,7 +229,7 @@ mod unit_tests { llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_RETURNED_NONZERO_CODE, 0, -3, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(MtmdEvalError::EvalFailed { code: -3 })); @@ -239,7 +241,7 @@ mod unit_tests { llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_ERROR_STRING_ALLOCATION_FAILED, 0, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(MtmdEvalError::NotEnoughMemory)); @@ -251,7 +253,7 @@ mod unit_tests { llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_THREW_CXX_EXCEPTION, 0, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -266,7 +268,7 @@ mod unit_tests { #[test] fn eval_chunk_single_unknown_status_is_preserved() { - let result = eval_chunk_single_status_to_result(255, 0, 0, std::ptr::null_mut()); + let result = eval_chunk_single_status_to_result(255, 0, 0, ptr::null_mut()); assert_eq!( result, @@ -276,24 +278,17 @@ mod unit_tests { })) ); } -} - -#[cfg(test)] -mod ffi_contract_status_tests { - use super::eval_chunk_single_status_to_result; - use crate::mtmd::mtmd_eval_error::MtmdEvalError; - use std::ptr; #[test] - fn eval_chunk_single_status_to_result_maps_every_contract_status() { - let outcome_0 = eval_chunk_single_status_to_result( + fn eval_chunk_single_status_to_result_maps_null_mtmd_ctx_arg() { + let outcome = eval_chunk_single_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_NULL_MTMD_CTX_ARG, 0, 0, ptr::null_mut(), ); assert_eq!( - outcome_0.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_eval_chunk_single", @@ -302,14 +297,18 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_1 = eval_chunk_single_status_to_result( + } + + #[test] + fn eval_chunk_single_status_to_result_maps_null_llama_ctx_arg() { + let outcome = eval_chunk_single_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_NULL_LLAMA_CTX_ARG, 0, 0, ptr::null_mut(), ); assert_eq!( - outcome_1.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_eval_chunk_single", @@ -318,14 +317,18 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_2 = eval_chunk_single_status_to_result( + } + + #[test] + fn eval_chunk_single_status_to_result_maps_null_chunk_arg() { + let outcome = eval_chunk_single_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_NULL_CHUNK_ARG, 0, 0, ptr::null_mut(), ); assert_eq!( - outcome_2.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_eval_chunk_single", @@ -334,14 +337,18 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_3 = eval_chunk_single_status_to_result( + } + + #[test] + fn eval_chunk_single_status_to_result_maps_null_out_new_n_past_arg() { + let outcome = eval_chunk_single_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_NULL_OUT_NEW_N_PAST_ARG, 0, 0, ptr::null_mut(), ); assert_eq!( - outcome_3.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_eval_chunk_single", @@ -350,12 +357,16 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_4 = eval_chunk_single_status_to_result( + } + + #[test] + fn eval_chunk_single_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = eval_chunk_single_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_EVAL_CHUNK_SINGLE_LLAMA_CPP_OUT_OF_MEMORY, 0, 0, ptr::null_mut(), ); - assert_eq!(outcome_4.err(), Some(MtmdEvalError::LlamaCppOutOfMemory)); + assert_eq!(outcome.err(), Some(MtmdEvalError::LlamaCppOutOfMemory)); } } diff --git a/llama-cpp-bindings/src/mtmd/mtmd_bitmap.rs b/llama-cpp-bindings/src/mtmd/mtmd_bitmap.rs index 01c104be6..98d0d1271 100644 --- a/llama-cpp-bindings/src/mtmd/mtmd_bitmap.rs +++ b/llama-cpp-bindings/src/mtmd/mtmd_bitmap.rs @@ -137,7 +137,14 @@ unsafe fn from_buf_status_to_result( } } -#[derive(Debug, Clone)] +/// Owns the bitmap and frees it on drop, so it cannot be cloned. +/// +/// ```compile_fail,E0277 +/// fn require_clone() {} +/// +/// require_clone::(); +/// ``` +#[derive(Debug)] pub struct MtmdBitmap { pub bitmap: NonNull, } @@ -282,23 +289,8 @@ impl Drop for MtmdBitmap { #[cfg(test)] mod tests { - use std::path::PathBuf; - use super::MtmdBitmap; - use super::MtmdBitmapError; - - #[test] - fn cstr_ptr_to_optional_string_returns_none_for_null() { - assert!(super::cstr_ptr_to_optional_string(std::ptr::null()).is_none()); - } - - #[test] - fn cstr_ptr_to_optional_string_returns_some_for_valid() { - let cstr = std::ffi::CString::new("hello").unwrap(); - let result = super::cstr_ptr_to_optional_string(cstr.as_ptr()); - - assert_eq!(result, Some("hello".to_string())); - } + use crate::mtmd::mtmd_bitmap_error::MtmdBitmapError; #[test] fn from_image_data_creates_valid_bitmap() { @@ -409,14 +401,43 @@ mod tests { assert!(result.is_err()); } +} + +#[cfg(test)] +mod cstr_ptr_to_optional_string_tests { + use std::ptr; + + use super::cstr_ptr_to_optional_string; + + #[test] + fn cstr_ptr_to_optional_string_returns_none_for_null() { + assert!(cstr_ptr_to_optional_string(ptr::null()).is_none()); + } + + #[test] + fn cstr_ptr_to_optional_string_returns_some_for_valid() { + let cstr = std::ffi::CString::new("hello").unwrap(); + let result = cstr_ptr_to_optional_string(cstr.as_ptr()); + + assert_eq!(result, Some("hello".to_string())); + } +} + +#[cfg(test)] +mod from_file_status_to_result_tests { + use std::path::PathBuf; + use std::ptr; + + use super::from_file_status_to_result; + use crate::mtmd::mtmd_bitmap_error::MtmdBitmapError; #[test] fn from_file_success_with_null_bitmap_is_contract_error() { let result = unsafe { - super::from_file_status_to_result( + from_file_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_OK, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "/missing/image.png", ) }; @@ -433,10 +454,10 @@ mod tests { #[test] fn from_file_status_llama_cpp_returned_null_returns_file_unreadable() { let result = unsafe { - super::from_file_status_to_result( + from_file_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_LLAMA_CPP_RETURNED_NULL, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "/missing/image.png", ) }; @@ -452,10 +473,10 @@ mod tests { #[test] fn from_file_status_error_string_allocation_failed_returns_not_enough_memory() { let result = unsafe { - super::from_file_status_to_result( + from_file_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "/missing/image.png", ) }; @@ -466,10 +487,10 @@ mod tests { #[test] fn from_file_status_llama_cpp_threw_cxx_exception_without_a_message_is_a_contract_error() { let result = unsafe { - super::from_file_status_to_result( + from_file_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "/missing/image.png", ) }; @@ -488,10 +509,10 @@ mod tests { fn from_file_null_context_status_is_a_contract_error() { let status = llama_cpp_bindings_sys::LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_NULL_CTX_ARG; let result = unsafe { - super::from_file_status_to_result( + from_file_status_to_result( status, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "/missing/image.png", ) }; @@ -504,17 +525,10 @@ mod tests { }) ); } -} - -#[cfg(test)] -mod ffi_contract_status_tests { - use super::from_file_status_to_result; - use crate::mtmd::mtmd_bitmap_error::MtmdBitmapError; - use std::ptr; #[test] - fn from_file_status_to_result_maps_every_contract_status() { - let outcome_0 = unsafe { + fn from_file_status_to_result_maps_null_fname_arg() { + let outcome = unsafe { from_file_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_NULL_FNAME_ARG, ptr::null_mut(), @@ -523,7 +537,7 @@ mod ffi_contract_status_tests { ) }; assert_eq!( - outcome_0.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_bitmap_init_from_file", @@ -532,7 +546,11 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_1 = unsafe { + } + + #[test] + fn from_file_status_to_result_maps_null_out_bitmap_arg() { + let outcome = unsafe { from_file_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_NULL_OUT_BITMAP_ARG, ptr::null_mut(), @@ -541,7 +559,7 @@ mod ffi_contract_status_tests { ) }; assert_eq!( - outcome_1.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_bitmap_init_from_file", @@ -550,7 +568,11 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_2 = unsafe { + } + + #[test] + fn from_file_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = unsafe { from_file_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_MTMD_BITMAP_INIT_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), @@ -558,7 +580,7 @@ mod ffi_contract_status_tests { "", ) }; - assert_eq!(outcome_2.err(), Some(MtmdBitmapError::LlamaCppOutOfMemory)); + assert_eq!(outcome.err(), Some(MtmdBitmapError::LlamaCppOutOfMemory)); } } diff --git a/llama-cpp-bindings/src/mtmd/mtmd_context.rs b/llama-cpp-bindings/src/mtmd/mtmd_context.rs index 58492eee9..5c85500ff 100644 --- a/llama-cpp-bindings/src/mtmd/mtmd_context.rs +++ b/llama-cpp-bindings/src/mtmd/mtmd_context.rs @@ -1,14 +1,17 @@ use std::ffi::CString; use std::ffi::c_char; +use std::path::PathBuf; use std::ptr; use std::ptr::NonNull; -use crate::model::LlamaModel; use llama_cpp_ffi_status::read_and_free_cpp_string; +use crate::model::LlamaModel; + use super::mtmd_bitmap::MtmdBitmap; use super::mtmd_context_params::MtmdContextParams; use super::mtmd_encode_error::MtmdEncodeError; +use super::mtmd_ffi_input_part::MtmdFfiInputPart; use super::mtmd_init_error::MtmdInitError; use super::mtmd_input_chunk::MtmdInputChunk; use super::mtmd_input_chunks::MtmdInputChunks; @@ -26,8 +29,8 @@ fn map_tokenize_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_BITMAP_COUNT_DOES_NOT_MATCH_MARKER_COUNT => { Err(MtmdTokenizeError::BitmapCountDoesNotMatchMarkerCount) } - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_IMAGE_PREPROCESSING_ERROR => { - Err(MtmdTokenizeError::MediaPreprocessingFailed) + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_PREPROCESSING_OR_TOKENIZATION_FAILURE => { + Err(MtmdTokenizeError::MediaPreprocessingOrTokenizationFailed) } llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_RETURNED_UNDOCUMENTED_NONZERO_CODE => { Err(MtmdTokenizeError::UnknownStatus { @@ -84,12 +87,12 @@ fn map_tokenize_from_parts_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MALFORMED_PART => { Err(crate::FfiContractError { operation: "llama_rs_mtmd_tokenize_from_parts", - detail: "llama.cpp reported a part holding both or neither of text and bitmap", + detail: "llama.cpp reported a malformed part list: a part holding both or neither of text and bitmap, a text part with a null text pointer, or bitmaps that do not line up with their parts", } .into()) } - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MEDIA_PREPROCESSING_ERROR => { - Err(MtmdTokenizeError::MediaPreprocessingFailed) + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_PREPROCESSING_OR_TOKENIZATION_FAILURE => { + Err(MtmdTokenizeError::MediaPreprocessingOrTokenizationFailed) } llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_RETURNED_UNDOCUMENTED_NONZERO_CODE => { Err(MtmdTokenizeError::UnknownTokenizeFromPartsStatus { @@ -133,6 +136,13 @@ fn map_tokenize_from_parts_status( } .into()) } + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_PART_ELEMENT => { + Err(crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize_from_parts", + detail: "was given a null element in the parts argument", + } + .into()) + } other => Err(crate::FfiStatusError { operation: "llama_rs_mtmd_tokenize_from_parts", code: i64::from(other), @@ -209,7 +219,7 @@ fn map_init_from_file_status( } llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_LLAMA_CPP_RETURNED_NULL => { Err(MtmdInitError::Unloadable { - path: std::path::PathBuf::from(mmproj_path), + path: PathBuf::from(mmproj_path), }) } llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED => { @@ -375,8 +385,14 @@ impl MtmdContext { add_special: bool, ) -> Result { let chunks = MtmdInputChunks::new()?; - let ffi_parts: Vec = - parts.iter().map(MtmdInputPart::ffi_part).collect(); + let ffi_input_parts = parts + .iter() + .map(MtmdFfiInputPart::try_from) + .collect::>, _>>()?; + let ffi_parts: Vec = ffi_input_parts + .iter() + .map(MtmdFfiInputPart::ffi_part) + .collect(); let ffi_part_pointers: Vec<*const llama_cpp_bindings_sys::mtmd_input_part> = ffi_parts.iter().map(ptr::from_ref).collect(); @@ -426,16 +442,13 @@ impl Drop for MtmdContext { } #[cfg(test)] -mod unit_tests { +mod map_tokenize_from_parts_status_tests { + use std::ptr; + use llama_cpp_ffi_status::read_and_free_cpp_string; use llama_cpp_wrapper_error_fixture::wrapper_allocated_error_message_pointer; - use super::map_encode_chunk_status; - use super::map_init_from_file_status; use super::map_tokenize_from_parts_status; - use super::map_tokenize_status; - use crate::mtmd::mtmd_encode_error::MtmdEncodeError; - use crate::mtmd::mtmd_init_error::MtmdInitError; use crate::mtmd::mtmd_tokenize_error::MtmdTokenizeError; #[test] @@ -444,21 +457,21 @@ mod unit_tests { map_tokenize_from_parts_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_OK, 0, - std::ptr::null_mut(), + ptr::null_mut(), ), Ok(()) ); } #[test] - fn tokenize_from_parts_status_maps_media_preprocessing_failed() { + fn tokenize_from_parts_status_maps_preprocessing_or_tokenization_failure() { assert_eq!( map_tokenize_from_parts_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MEDIA_PREPROCESSING_ERROR, + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_PREPROCESSING_OR_TOKENIZATION_FAILURE, 0, - std::ptr::null_mut(), + ptr::null_mut(), ), - Err(MtmdTokenizeError::MediaPreprocessingFailed) + Err(MtmdTokenizeError::MediaPreprocessingOrTokenizationFailed) ); } @@ -468,7 +481,7 @@ mod unit_tests { map_tokenize_from_parts_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_RETURNED_UNDOCUMENTED_NONZERO_CODE, 42, - std::ptr::null_mut(), + ptr::null_mut(), ), Err(MtmdTokenizeError::UnknownTokenizeFromPartsStatus { code: 42 }) ); @@ -480,7 +493,7 @@ mod unit_tests { map_tokenize_from_parts_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_ERROR_STRING_ALLOCATION_FAILED, 0, - std::ptr::null_mut(), + ptr::null_mut(), ), Err(MtmdTokenizeError::NotEnoughMemory) ); @@ -492,7 +505,7 @@ mod unit_tests { map_tokenize_from_parts_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_OUT_OF_MEMORY, 0, - std::ptr::null_mut(), + ptr::null_mut(), ), Err(MtmdTokenizeError::LlamaCppOutOfMemory) ); @@ -521,12 +534,141 @@ mod unit_tests { ); } + #[test] + fn map_tokenize_from_parts_status_maps_llama_cpp_reported_malformed_part() { + assert_eq!( + map_tokenize_from_parts_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MALFORMED_PART, + 0, + ptr::null_mut(), + ) + .err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize_from_parts", + detail: "llama.cpp reported a malformed part list: a part holding both or neither of text and bitmap, a text part with a null text pointer, or bitmaps that do not line up with their parts", + } + .into() + ) + ); + } + + #[test] + fn map_tokenize_from_parts_status_maps_null_part_element() { + assert_eq!( + map_tokenize_from_parts_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_PART_ELEMENT, + 0, + ptr::null_mut(), + ) + .err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize_from_parts", + detail: "was given a null element in the parts argument", + } + .into() + ) + ); + } + + #[test] + fn map_tokenize_from_parts_status_maps_llama_cpp_threw_cxx_exception_without_a_message() { + assert_eq!( + map_tokenize_from_parts_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_THREW_CXX_EXCEPTION, + 0, + ptr::null_mut(), + ) + .err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize_from_parts", + detail: "reported a thrown C++ exception without an error message", + } + .into() + ) + ); + } + + #[test] + fn map_tokenize_from_parts_status_maps_null_ctx_arg() { + assert_eq!( + map_tokenize_from_parts_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_CTX_ARG, + 0, + ptr::null_mut(), + ) + .err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize_from_parts", + detail: "was given a null ctx argument", + } + .into() + ) + ); + } + + #[test] + fn map_tokenize_from_parts_status_maps_null_output_arg() { + assert_eq!( + map_tokenize_from_parts_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_OUTPUT_ARG, + 0, + ptr::null_mut(), + ) + .err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize_from_parts", + detail: "was given a null output argument", + } + .into() + ) + ); + } + + #[test] + fn map_tokenize_from_parts_status_maps_null_parts_arg_when_num_parts_nonzero() { + assert_eq!( + map_tokenize_from_parts_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_PARTS_ARG_WHEN_NUM_PARTS_NONZERO, + 0, + ptr::null_mut(), + ) + .err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize_from_parts", + detail: "nonzero part count was observed with a null part array", + } + .into() + ) + ); + assert_eq!( + map_tokenize_from_parts_status(255, 0, ptr::null_mut()).err(), + Some(MtmdTokenizeError::FfiStatus(crate::FfiStatusError { + operation: "llama_rs_mtmd_tokenize_from_parts", + code: 255, + })) + ); + } +} + +#[cfg(test)] +mod map_tokenize_status_tests { + use std::ptr; + + use super::map_tokenize_status; + use crate::mtmd::mtmd_tokenize_error::MtmdTokenizeError; + #[test] fn tokenize_status_maps_bitmap_count_mismatch() { let result = map_tokenize_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_BITMAP_COUNT_DOES_NOT_MATCH_MARKER_COUNT, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -536,14 +678,17 @@ mod unit_tests { } #[test] - fn tokenize_status_maps_media_preprocessing_failed() { + fn tokenize_status_maps_preprocessing_or_tokenization_failure() { let result = map_tokenize_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_IMAGE_PREPROCESSING_ERROR, + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_REPORTED_PREPROCESSING_OR_TOKENIZATION_FAILURE, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); - assert_eq!(result, Err(MtmdTokenizeError::MediaPreprocessingFailed)); + assert_eq!( + result, + Err(MtmdTokenizeError::MediaPreprocessingOrTokenizationFailed) + ); } #[test] @@ -551,7 +696,7 @@ mod unit_tests { let result = map_tokenize_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_RETURNED_UNDOCUMENTED_NONZERO_CODE, 42, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(MtmdTokenizeError::UnknownStatus { code: 42 })); @@ -562,40 +707,18 @@ mod unit_tests { let result = map_tokenize_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_OK, 0, - std::ptr::null_mut(), - ); - - assert_eq!(result, Ok(())); - } - - #[test] - fn encode_chunk_status_maps_ok_to_unit() { - let result = map_encode_chunk_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_OK, - 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Ok(())); } - #[test] - fn encode_chunk_status_maps_encoding_failed_with_code() { - let result = map_encode_chunk_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_RETURNED_NONZERO_CODE, - 5, - std::ptr::null_mut(), - ); - - assert_eq!(result, Err(MtmdEncodeError::EncodingFailed { code: 5 })); - } - #[test] fn tokenize_status_maps_string_allocation_failed_to_not_enough_memory() { let result = map_tokenize_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(MtmdTokenizeError::NotEnoughMemory)); @@ -606,7 +729,7 @@ mod unit_tests { let result = map_tokenize_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_THREW_CXX_EXCEPTION, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -624,7 +747,7 @@ mod unit_tests { let result = map_tokenize_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_NULL_BITMAPS_ARG_WHEN_NUM_BITMAPS_NONZERO, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -638,7 +761,7 @@ mod unit_tests { #[test] fn tokenize_unknown_status_is_preserved() { - let result = map_tokenize_status(255, 0, std::ptr::null_mut()); + let result = map_tokenize_status(255, 0, ptr::null_mut()); assert_eq!( result, @@ -649,12 +772,109 @@ mod unit_tests { ); } + #[test] + fn map_tokenize_status_maps_null_ctx_arg() { + let outcome = map_tokenize_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_NULL_CTX_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize", + detail: "was given a null ctx argument", + } + .into() + ) + ); + } + + #[test] + fn map_tokenize_status_maps_null_output_arg() { + let outcome = map_tokenize_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_NULL_OUTPUT_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize", + detail: "was given a null output argument", + } + .into() + ) + ); + } + + #[test] + fn map_tokenize_status_maps_null_text_arg() { + let outcome = map_tokenize_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_NULL_TEXT_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_tokenize", + detail: "was given a null text argument", + } + .into() + ) + ); + } + + #[test] + fn map_tokenize_status_maps_llama_cpp_out_of_memory() { + let outcome = map_tokenize_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY, + 0, + ptr::null_mut(), + ); + assert_eq!(outcome.err(), Some(MtmdTokenizeError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod map_encode_chunk_status_tests { + use std::ptr; + + use super::map_encode_chunk_status; + use crate::mtmd::mtmd_encode_error::MtmdEncodeError; + + #[test] + fn encode_chunk_status_maps_ok_to_unit() { + let result = map_encode_chunk_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_OK, + 0, + ptr::null_mut(), + ); + + assert_eq!(result, Ok(())); + } + + #[test] + fn encode_chunk_status_maps_encoding_failed_with_code() { + let result = map_encode_chunk_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_RETURNED_NONZERO_CODE, + 5, + ptr::null_mut(), + ); + + assert_eq!(result, Err(MtmdEncodeError::EncodingFailed { code: 5 })); + } + #[test] fn encode_chunk_status_maps_string_allocation_failed_to_not_enough_memory() { let result = map_encode_chunk_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_ERROR_STRING_ALLOCATION_FAILED, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(MtmdEncodeError::NotEnoughMemory)); @@ -665,7 +885,7 @@ mod unit_tests { let result = map_encode_chunk_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_THREW_CXX_EXCEPTION, 0, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -680,7 +900,7 @@ mod unit_tests { #[test] fn encode_chunk_unknown_status_is_preserved() { - let result = map_encode_chunk_status(255, 0, std::ptr::null_mut()); + let result = map_encode_chunk_status(255, 0, ptr::null_mut()); assert_eq!( result, @@ -691,12 +911,68 @@ mod unit_tests { ); } + #[test] + fn map_encode_chunk_status_maps_null_ctx_arg() { + let outcome = map_encode_chunk_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_NULL_CTX_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_encode_chunk", + detail: "was given a null ctx argument", + } + .into() + ) + ); + } + + #[test] + fn map_encode_chunk_status_maps_null_chunk_arg() { + let outcome = map_encode_chunk_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_NULL_CHUNK_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_mtmd_encode_chunk", + detail: "was given a null chunk argument", + } + .into() + ) + ); + } + + #[test] + fn map_encode_chunk_status_maps_llama_cpp_out_of_memory() { + let outcome = map_encode_chunk_status( + llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_OUT_OF_MEMORY, + 0, + ptr::null_mut(), + ); + assert_eq!(outcome.err(), Some(MtmdEncodeError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod map_init_from_file_status_tests { + use std::ptr; + + use super::map_init_from_file_status; + use crate::mtmd::mtmd_init_error::MtmdInitError; + #[test] fn init_from_file_success_with_null_context_is_contract_error() { let result = map_init_from_file_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_OK, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "mmproj.gguf", ); @@ -713,8 +989,8 @@ mod unit_tests { fn init_from_file_status_maps_string_allocation_failed_to_not_enough_memory() { let result = map_init_from_file_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "mmproj.gguf", ); @@ -725,8 +1001,8 @@ mod unit_tests { fn init_from_file_status_maps_cxx_exception_to_without_a_message_is_a_contract_error() { let result = map_init_from_file_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_LLAMA_CPP_THREW_CXX_EXCEPTION, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "mmproj.gguf", ); @@ -742,12 +1018,8 @@ mod unit_tests { #[test] fn init_from_file_unknown_status_is_preserved() { - let result = map_init_from_file_status( - 255, - std::ptr::null_mut(), - std::ptr::null_mut(), - "mmproj.gguf", - ); + let result = + map_init_from_file_status(255, ptr::null_mut(), ptr::null_mut(), "mmproj.gguf"); assert_eq!( result.unwrap_err(), @@ -757,213 +1029,17 @@ mod unit_tests { }) ); } -} - -#[cfg(test)] -mod ffi_contract_status_tests { - use super::map_encode_chunk_status; - use super::map_init_from_file_status; - use super::map_tokenize_from_parts_status; - use super::map_tokenize_status; - use crate::mtmd::mtmd_encode_error::MtmdEncodeError; - use crate::mtmd::mtmd_init_error::MtmdInitError; - use crate::mtmd::mtmd_tokenize_error::MtmdTokenizeError; - use std::ptr; #[test] - fn map_tokenize_from_parts_status_maps_every_contract_status() { - assert_eq!( - map_tokenize_from_parts_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_REPORTED_MALFORMED_PART, - 0, - ptr::null_mut(), - ) - .err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_tokenize_from_parts", - detail: "llama.cpp reported a part holding both or neither of text and bitmap", - } - .into() - ) - ); - assert_eq!( - map_tokenize_from_parts_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_LLAMA_CPP_THREW_CXX_EXCEPTION, - 0, - ptr::null_mut(), - ) - .err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_tokenize_from_parts", - detail: "reported a thrown C++ exception without an error message", - } - .into() - ) - ); - assert_eq!( - map_tokenize_from_parts_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_CTX_ARG, - 0, - ptr::null_mut(), - ) - .err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_tokenize_from_parts", - detail: "was given a null ctx argument", - } - .into() - ) - ); - assert_eq!( - map_tokenize_from_parts_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_OUTPUT_ARG, - 0, - ptr::null_mut(), - ) - .err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_tokenize_from_parts", - detail: "was given a null output argument", - } - .into() - ) - ); - assert_eq!( - map_tokenize_from_parts_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_FROM_PARTS_NULL_PARTS_ARG_WHEN_NUM_PARTS_NONZERO, - 0, - ptr::null_mut(), - ) - .err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_tokenize_from_parts", - detail: "nonzero part count was observed with a null part array", - } - .into() - ) - ); - assert_eq!( - map_tokenize_from_parts_status(255, 0, ptr::null_mut()).err(), - Some(MtmdTokenizeError::FfiStatus(crate::FfiStatusError { - operation: "llama_rs_mtmd_tokenize_from_parts", - code: 255, - })) - ); - } - - #[test] - fn map_tokenize_status_maps_every_contract_status() { - let outcome_0 = map_tokenize_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_NULL_CTX_ARG, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_tokenize", - detail: "was given a null ctx argument", - } - .into() - ) - ); - let outcome_1 = map_tokenize_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_NULL_OUTPUT_ARG, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_tokenize", - detail: "was given a null output argument", - } - .into() - ) - ); - let outcome_2 = map_tokenize_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_NULL_TEXT_ARG, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_2.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_tokenize", - detail: "was given a null text argument", - } - .into() - ) - ); - let outcome_3 = map_tokenize_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_TOKENIZE_LLAMA_CPP_OUT_OF_MEMORY, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_3.err(), - Some(MtmdTokenizeError::LlamaCppOutOfMemory) - ); - } - - #[test] - fn map_encode_chunk_status_maps_every_contract_status() { - let outcome_0 = map_encode_chunk_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_NULL_CTX_ARG, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_encode_chunk", - detail: "was given a null ctx argument", - } - .into() - ) - ); - let outcome_1 = map_encode_chunk_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_NULL_CHUNK_ARG, - 0, - ptr::null_mut(), - ); - assert_eq!( - outcome_1.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_mtmd_encode_chunk", - detail: "was given a null chunk argument", - } - .into() - ) - ); - let outcome_2 = map_encode_chunk_status( - llama_cpp_bindings_sys::LLAMA_RS_MTMD_ENCODE_CHUNK_LLAMA_CPP_OUT_OF_MEMORY, - 0, - ptr::null_mut(), - ); - assert_eq!(outcome_2.err(), Some(MtmdEncodeError::LlamaCppOutOfMemory)); - } - - #[test] - fn map_init_from_file_status_maps_every_contract_status() { - let outcome_0 = map_init_from_file_status( + fn map_init_from_file_status_maps_null_mmproj_path_arg() { + let outcome = map_init_from_file_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_NULL_MMPROJ_PATH_ARG, ptr::null_mut(), ptr::null_mut(), "", ); assert_eq!( - outcome_0.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_init_from_file", @@ -972,14 +1048,18 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_1 = map_init_from_file_status( + } + + #[test] + fn map_init_from_file_status_maps_null_text_model_arg() { + let outcome = map_init_from_file_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_NULL_TEXT_MODEL_ARG, ptr::null_mut(), ptr::null_mut(), "", ); assert_eq!( - outcome_1.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_init_from_file", @@ -988,14 +1068,18 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_2 = map_init_from_file_status( + } + + #[test] + fn map_init_from_file_status_maps_null_out_ctx_arg() { + let outcome = map_init_from_file_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_NULL_OUT_CTX_ARG, ptr::null_mut(), ptr::null_mut(), "", ); assert_eq!( - outcome_2.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_mtmd_init_from_file", @@ -1004,12 +1088,16 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_3 = map_init_from_file_status( + } + + #[test] + fn map_init_from_file_status_maps_llama_cpp_out_of_memory() { + let outcome = map_init_from_file_status( llama_cpp_bindings_sys::LLAMA_RS_MTMD_INIT_FROM_FILE_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), ptr::null_mut(), "", ); - assert_eq!(outcome_3.err(), Some(MtmdInitError::LlamaCppOutOfMemory)); + assert_eq!(outcome.err(), Some(MtmdInitError::LlamaCppOutOfMemory)); } } diff --git a/llama-cpp-bindings/src/mtmd/mtmd_ffi_input_part.rs b/llama-cpp-bindings/src/mtmd/mtmd_ffi_input_part.rs new file mode 100644 index 000000000..531a7e402 --- /dev/null +++ b/llama-cpp-bindings/src/mtmd/mtmd_ffi_input_part.rs @@ -0,0 +1,56 @@ +use std::ffi::CString; +use std::ffi::NulError; +use std::ptr; + +use super::mtmd_bitmap::MtmdBitmap; +use super::mtmd_input_part::MtmdInputPart; + +pub enum MtmdFfiInputPart<'bitmap> { + Bitmap(&'bitmap MtmdBitmap), + Text(llama_cpp_bindings_sys::mtmd_input_text), +} + +impl MtmdFfiInputPart<'_> { + #[must_use] + pub const fn ffi_part(&self) -> llama_cpp_bindings_sys::mtmd_input_part { + match self { + Self::Bitmap(bitmap) => llama_cpp_bindings_sys::mtmd_input_part { + text: ptr::null(), + bitmap: bitmap.bitmap.as_ptr().cast_const(), + }, + Self::Text(input_text) => llama_cpp_bindings_sys::mtmd_input_part { + text: ptr::from_ref(input_text), + bitmap: ptr::null(), + }, + } + } +} + +impl<'bitmap> TryFrom<&MtmdInputPart<'bitmap>> for MtmdFfiInputPart<'bitmap> { + type Error = NulError; + + fn try_from(input_part: &MtmdInputPart<'bitmap>) -> Result { + match input_part { + MtmdInputPart::Bitmap(bitmap) => Ok(Self::Bitmap(bitmap)), + MtmdInputPart::Text(text_part) => { + let text = CString::new(text_part.text.as_str())?; + let text_len = text.as_bytes().len(); + + Ok(Self::Text(llama_cpp_bindings_sys::mtmd_input_text { + text: text.into_raw(), + text_len, + add_special: false, + parse_special: text_part.parse_special, + })) + } + } + } +} + +impl Drop for MtmdFfiInputPart<'_> { + fn drop(&mut self) { + if let Self::Text(input_text) = self { + drop(unsafe { CString::from_raw(input_text.text.cast_mut()) }); + } + } +} diff --git a/llama-cpp-bindings/src/mtmd/mtmd_input_part.rs b/llama-cpp-bindings/src/mtmd/mtmd_input_part.rs index 03d19b08f..178d23ed7 100644 --- a/llama-cpp-bindings/src/mtmd/mtmd_input_part.rs +++ b/llama-cpp-bindings/src/mtmd/mtmd_input_part.rs @@ -1,25 +1,26 @@ -use std::ptr; - use super::mtmd_bitmap::MtmdBitmap; use super::mtmd_input_text_part::MtmdInputTextPart; +#[derive(Clone, Debug)] pub enum MtmdInputPart<'bitmap> { Bitmap(&'bitmap MtmdBitmap), Text(MtmdInputTextPart), } -impl MtmdInputPart<'_> { - #[must_use] - pub const fn ffi_part(&self) -> llama_cpp_bindings_sys::mtmd_input_part { - match self { - Self::Bitmap(bitmap) => llama_cpp_bindings_sys::mtmd_input_part { - text: ptr::null(), - bitmap: bitmap.bitmap.as_ptr().cast_const(), - }, - Self::Text(text_part) => llama_cpp_bindings_sys::mtmd_input_part { - text: ptr::from_ref(text_part.input_text()), - bitmap: ptr::null(), - }, - } +#[cfg(test)] +mod tests { + use std::thread; + + use super::MtmdInputPart; + use crate::mtmd::mtmd_input_text_part::MtmdInputTextPart; + + #[test] + fn text_parts_move_to_another_thread() { + let parts = [MtmdInputPart::Text(MtmdInputTextPart { + text: "hello".to_owned(), + parse_special: true, + })]; + + assert_eq!(thread::spawn(move || parts.len()).join().ok(), Some(1)); } } diff --git a/llama-cpp-bindings/src/mtmd/mtmd_input_text_part.rs b/llama-cpp-bindings/src/mtmd/mtmd_input_text_part.rs index 2ae10bf6e..cf6e83aa7 100644 --- a/llama-cpp-bindings/src/mtmd/mtmd_input_text_part.rs +++ b/llama-cpp-bindings/src/mtmd/mtmd_input_text_part.rs @@ -1,51 +1,5 @@ -use std::ffi::CString; -use std::ffi::NulError; - +#[derive(Clone, Debug)] pub struct MtmdInputTextPart { - input_text: llama_cpp_bindings_sys::mtmd_input_text, -} - -impl MtmdInputTextPart { - /// # Errors - /// - /// Returns [`NulError`] when `text` contains a NUL byte. - pub fn new(text: &str, parse_special: bool) -> Result { - let text = CString::new(text)?; - let text_len = text.as_bytes().len(); - - Ok(Self { - input_text: llama_cpp_bindings_sys::mtmd_input_text { - text: text.into_raw(), - text_len, - add_special: false, - parse_special, - }, - }) - } - - #[must_use] - pub const fn input_text(&self) -> &llama_cpp_bindings_sys::mtmd_input_text { - &self.input_text - } -} - -impl Drop for MtmdInputTextPart { - fn drop(&mut self) { - drop(unsafe { CString::from_raw(self.input_text.text.cast_mut()) }); - } -} - -#[cfg(test)] -mod tests { - use super::MtmdInputTextPart; - - #[test] - fn a_text_part_containing_a_nul_byte_is_rejected() { - assert_eq!( - MtmdInputTextPart::new("before\0after", true) - .err() - .map(|nul_error| nul_error.nul_position()), - Some(6) - ); - } + pub text: String, + pub parse_special: bool, } diff --git a/llama-cpp-bindings/src/mtmd/mtmd_tokenize_error.rs b/llama-cpp-bindings/src/mtmd/mtmd_tokenize_error.rs index 2a66babf6..29c36b004 100644 --- a/llama-cpp-bindings/src/mtmd/mtmd_tokenize_error.rs +++ b/llama-cpp-bindings/src/mtmd/mtmd_tokenize_error.rs @@ -12,8 +12,10 @@ pub enum MtmdTokenizeError { InputChunksError(#[from] MtmdInputChunksError), #[error("number of bitmaps does not match number of markers in the text")] BitmapCountDoesNotMatchMarkerCount, - #[error("media preprocessing failed (image or audio)")] - MediaPreprocessingFailed, + #[error( + "llama.cpp returned 2: media preprocessing failed, or its tokenizer threw (text too large, missing vocab, media marker and bitmap count mismatch, or allocation failure)" + )] + MediaPreprocessingOrTokenizationFailed, #[error("mtmd_tokenize returned an unknown status code: {code}")] UnknownStatus { code: i32 }, #[error("mtmd_tokenize_from_parts returned an unknown status code: {code}")] diff --git a/llama-cpp-bindings/src/sampling.rs b/llama-cpp-bindings/src/sampling.rs index 7c68f1a99..a950cc6f6 100644 --- a/llama-cpp-bindings/src/sampling.rs +++ b/llama-cpp-bindings/src/sampling.rs @@ -917,27 +917,16 @@ impl Drop for LlamaSampler { #[cfg(test)] mod tests { - use crate::sanitized_grammar::SanitizedGrammar; use std::ffi::CString; + use std::ptr; use llama_cpp_error_recorder::RecordedError; use llama_cpp_error_recorder::record; use super::LlamaSampler; - use super::grammar_callback_error_to_accept_result; - use super::grammar_callback_error_to_result; use crate::GrammarError; use crate::SampleError; - use crate::SamplerAcceptError; - - fn greedy_sampler() -> LlamaSampler { - LlamaSampler::greedy().expect("greedy sampler must initialize") - } - - fn penalties_sampler() -> LlamaSampler { - LlamaSampler::penalties(32_000, 64, 1.1, 0.0, 0.0) - .expect("penalties sampler must initialize") - } + use crate::sanitized_grammar::SanitizedGrammar; unsafe extern "C" fn record_a_failure_and_exclude_every_candidate( _sampler: *mut llama_cpp_bindings_sys::llama_sampler, @@ -953,6 +942,15 @@ mod tests { } } + fn greedy_sampler() -> LlamaSampler { + LlamaSampler::greedy().expect("greedy sampler must initialize") + } + + fn penalties_sampler() -> LlamaSampler { + LlamaSampler::penalties(32_000, 64, 1.1, 0.0, 0.0) + .expect("penalties sampler must initialize") + } + static mut FAILING_EXCLUDING_SAMPLER_I: llama_cpp_bindings_sys::llama_sampler_i = llama_cpp_bindings_sys::llama_sampler_i { name: None, @@ -973,7 +971,7 @@ mod tests { let sampler = unsafe { llama_cpp_bindings_sys::llama_sampler_init( &raw mut FAILING_EXCLUDING_SAMPLER_I, - std::ptr::null_mut(), + ptr::null_mut(), ) }; @@ -1005,7 +1003,7 @@ mod tests { #[test] fn null_native_sampler_is_initialization_error() { - let result = LlamaSampler::from_raw(std::ptr::null_mut(), "test"); + let result = LlamaSampler::from_raw(ptr::null_mut(), "test"); assert_eq!( result.unwrap_err(), @@ -1013,43 +1011,6 @@ mod tests { ); } - #[test] - fn grammar_callback_error_to_result_maps_recorded_error() { - let result = - grammar_callback_error_to_result(Some(RecordedError::new("mask failed".to_string()))); - - assert_eq!( - result.unwrap_err(), - SampleError::GrammarCallbackFailed { - message: "mask failed".to_string() - } - ); - } - - #[test] - fn grammar_callback_error_to_result_maps_absence_to_ok() { - assert!(grammar_callback_error_to_result(None).is_ok()); - } - - #[test] - fn grammar_callback_error_to_accept_result_maps_recorded_error() { - let result = grammar_callback_error_to_accept_result(Some(RecordedError::new( - "consume failed".to_string(), - ))); - - assert_eq!( - result, - Err(SamplerAcceptError::GrammarCallbackFailed { - message: "consume failed".to_string() - }) - ); - } - - #[test] - fn grammar_callback_error_to_accept_result_maps_absence_to_ok() { - assert!(grammar_callback_error_to_accept_result(None).is_ok()); - } - #[test] fn sanitize_grammar_strings_valid() { assert_eq!( @@ -1257,18 +1218,84 @@ mod tests { let debug_output = format!("{sampler:?}"); assert!(debug_output.contains("LlamaSampler")); } +} + +#[cfg(test)] +mod checked_usize_as_i32_sampling_tests { + use super::checked_usize_as_i32_sampling; #[test] fn checked_usize_as_i32_sampling_overflow() { - let result = super::checked_usize_as_i32_sampling(usize::MAX); + let result = checked_usize_as_i32_sampling(usize::MAX); assert!(result.is_err()); } +} + +#[cfg(test)] +mod grammar_callback_error_to_result_tests { + use llama_cpp_error_recorder::RecordedError; + + use super::grammar_callback_error_to_result; + use crate::SampleError; + + #[test] + fn grammar_callback_error_to_result_maps_recorded_error() { + let result = + grammar_callback_error_to_result(Some(RecordedError::new("mask failed".to_string()))); + + assert_eq!( + result.unwrap_err(), + SampleError::GrammarCallbackFailed { + message: "mask failed".to_string() + } + ); + } + + #[test] + fn grammar_callback_error_to_result_maps_absence_to_ok() { + assert!(grammar_callback_error_to_result(None).is_ok()); + } +} + +#[cfg(test)] +mod grammar_callback_error_to_accept_result_tests { + use llama_cpp_error_recorder::RecordedError; + + use super::grammar_callback_error_to_accept_result; + use crate::SamplerAcceptError; + + #[test] + fn grammar_callback_error_to_accept_result_maps_recorded_error() { + let result = grammar_callback_error_to_accept_result(Some(RecordedError::new( + "consume failed".to_string(), + ))); + + assert_eq!( + result, + Err(SamplerAcceptError::GrammarCallbackFailed { + message: "consume failed".to_string() + }) + ); + } + + #[test] + fn grammar_callback_error_to_accept_result_maps_absence_to_ok() { + assert!(grammar_callback_error_to_accept_result(None).is_ok()); + } +} + +#[cfg(test)] +mod check_sampler_accept_status_tests { + use std::ptr; + + use super::check_sampler_accept_status; + use crate::SamplerAcceptError; #[test] fn check_sampler_accept_status_ok() { - let result = super::check_sampler_accept_status( + let result = check_sampler_accept_status( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_ACCEPT_OK, - std::ptr::null_mut(), + ptr::null_mut(), ); assert!(result.is_ok()); @@ -1281,7 +1308,7 @@ mod tests { }; assert_eq!( - super::check_sampler_accept_status( + check_sampler_accept_status( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_ACCEPT_LLAMA_CPP_THREW_CXX_EXCEPTION, out_error, ), @@ -1293,9 +1320,9 @@ mod tests { #[test] fn check_sampler_accept_status_allocation_failure_maps_to_not_enough_memory() { - let result = super::check_sampler_accept_status( + let result = check_sampler_accept_status( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_ACCEPT_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result, Err(SamplerAcceptError::NotEnoughMemory)); @@ -1304,7 +1331,7 @@ mod tests { #[test] fn sampler_accept_null_sampler_status_is_a_contract_error() { let status = llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_ACCEPT_NULL_SAMPLER_ARG; - let result = super::check_sampler_accept_status(status, std::ptr::null_mut()); + let result = check_sampler_accept_status(status, ptr::null_mut()); assert_eq!( result, @@ -1315,12 +1342,47 @@ mod tests { ); } + #[test] + fn check_sampler_accept_status_maps_null_out_error_arg() { + let outcome = check_sampler_accept_status( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_ACCEPT_NULL_OUT_ERROR_ARG, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_sampler_accept", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + + #[test] + fn check_sampler_accept_status_maps_llama_cpp_out_of_memory() { + let outcome = check_sampler_accept_status( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_ACCEPT_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), + ); + assert_eq!(outcome.err(), Some(SamplerAcceptError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod sampler_sample_status_to_result_tests { + use std::ptr; + + use super::sampler_sample_status_to_result; + use crate::SampleError; + #[test] fn sampler_sample_status_allocation_failure_maps_to_not_enough_memory() { - let result = super::sampler_sample_status_to_result( + let result = sampler_sample_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_ERROR_STRING_ALLOCATION_FAILED, -1, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!(result.unwrap_err(), SampleError::NotEnoughMemory); @@ -1328,10 +1390,10 @@ mod tests { #[test] fn sampler_sample_status_exception_without_a_message_is_a_contract_error() { - let result = super::sampler_sample_status_to_result( + let result = sampler_sample_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_LLAMA_CPP_THREW_CXX_EXCEPTION, -1, - std::ptr::null_mut(), + ptr::null_mut(), ); assert_eq!( @@ -1347,7 +1409,7 @@ mod tests { #[test] fn sampler_sample_null_context_status_is_a_contract_error() { let status = llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_NULL_CTX_ARG; - let result = super::sampler_sample_status_to_result(status, -1, std::ptr::null_mut()); + let result = sampler_sample_status_to_result(status, -1, ptr::null_mut()); assert_eq!( result, @@ -1358,13 +1420,87 @@ mod tests { ); } + #[test] + fn sampler_sample_status_to_result_maps_null_sampler_arg() { + let outcome = sampler_sample_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_NULL_SAMPLER_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_sampler_sample", + detail: "was given a null sampler argument", + } + .into() + ) + ); + } + + #[test] + fn sampler_sample_status_to_result_maps_null_out_token_arg() { + let outcome = sampler_sample_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_NULL_OUT_TOKEN_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_sampler_sample", + detail: "was given a null out_token argument", + } + .into() + ) + ); + } + + #[test] + fn sampler_sample_status_to_result_maps_null_out_error_arg() { + let outcome = sampler_sample_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_NULL_OUT_ERROR_ARG, + 0, + ptr::null_mut(), + ); + assert_eq!( + outcome.err(), + Some( + crate::FfiContractError { + operation: "llama_rs_sampler_sample", + detail: "was given a null out_error argument", + } + .into() + ) + ); + } + + #[test] + fn sampler_sample_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = sampler_sample_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_LLAMA_CPP_OUT_OF_MEMORY, + 0, + ptr::null_mut(), + ); + assert_eq!(outcome.err(), Some(SampleError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod sampler_init_status_to_result_tests { + use std::ptr; + + use super::sampler_init_status_to_result; + #[test] fn sampler_init_status_null_maps_to_sampler_unavailable() { assert_eq!( - super::sampler_init_status_to_result( + sampler_init_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_RETURNED_NULL, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "penalties", ) .err(), @@ -1377,10 +1513,10 @@ mod tests { #[test] fn sampler_init_status_allocation_failure_maps_to_not_enough_memory() { assert_eq!( - super::sampler_init_status_to_result( + sampler_init_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "penalties", ) .err(), @@ -1391,10 +1527,10 @@ mod tests { #[test] fn sampler_init_status_out_of_memory_names_the_sampler() { assert_eq!( - super::sampler_init_status_to_result( + sampler_init_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_OUT_OF_MEMORY, - std::ptr::null_mut(), - std::ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), "penalties", ) .err(), @@ -1410,9 +1546,9 @@ mod tests { unsafe { llama_cpp_bindings_sys::llama_rs_string_dup(c"vector too long".as_ptr()) }; assert_eq!( - super::sampler_init_status_to_result( + sampler_init_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_THREW_CXX_EXCEPTION, - std::ptr::null_mut(), + ptr::null_mut(), out_error, "penalties", ) @@ -1425,192 +1561,27 @@ mod tests { } #[test] - fn sampler_chain_add_status_allocation_failure_maps_to_not_enough_memory() { + fn sampler_init_status_to_result_maps_null_out_sampler_arg() { assert_eq!( - super::sampler_chain_add_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), - ), - Err(crate::SamplingError::NotEnoughMemory) + sampler_init_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_NULL_OUT_SAMPLER_ARG, + ptr::null_mut(), + ptr::null_mut(), + "penalties", + ) + .err(), + Some( + crate::FfiContractError { + operation: "llama_rs_sampler_init", + detail: "was given a null out_sampler argument", + } + .into() + ) ); } #[test] - fn sampler_chain_add_status_out_of_memory_names_the_chain() { - assert_eq!( - super::sampler_chain_add_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_OUT_OF_MEMORY, - std::ptr::null_mut(), - ), - Err(crate::SamplingError::LlamaCppOutOfMemory { sampler: "chain" }) - ); - } - - #[test] - fn sampler_chain_add_status_exception_carries_its_message() { - let out_error = - unsafe { llama_cpp_bindings_sys::llama_rs_string_dup(c"vector too long".as_ptr()) }; - - assert_eq!( - super::sampler_chain_add_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION, - out_error, - ), - Err(crate::SamplingError::Reported { - sampler: "chain", - message: "vector too long".to_owned(), - }) - ); - } - - #[test] - fn sampler_init_grammar_status_null_maps_to_grammar_malformed() { - let result = super::sampler_init_grammar_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_RETURNED_NULL, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - assert_eq!(result.unwrap_err(), GrammarError::GrammarMalformed); - } - - #[test] - fn sampler_init_grammar_status_allocation_failure_maps_to_not_enough_memory() { - let result = super::sampler_init_grammar_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - assert_eq!(result.unwrap_err(), GrammarError::NotEnoughMemory); - } - - #[test] - fn sampler_init_grammar_status_exception_without_a_message_is_a_contract_error() { - let result = super::sampler_init_grammar_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_THREW_CXX_EXCEPTION, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - assert_eq!( - result.unwrap_err(), - crate::FfiContractError { - operation: "llama_rs_sampler_init_grammar", - detail: "reported a thrown C++ exception without an error message", - } - .into() - ); - } - - #[test] - fn grammar_null_output_argument_status_is_a_contract_error() { - let status = llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_NULL_OUT_SAMPLER_ARG; - let result = super::sampler_init_grammar_status_to_result( - status, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - assert_eq!( - result.unwrap_err(), - GrammarError::FfiContract(crate::FfiContractError { - operation: "llama_rs_sampler_init_grammar", - detail: "was given a null out_sampler argument", - }) - ); - } - - #[test] - fn sampler_init_grammar_lazy_patterns_status_null_maps_to_lazy_patterns_grammar_malformed() { - let result = super::sampler_init_grammar_lazy_patterns_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_RETURNED_NULL, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - assert_eq!(result.unwrap_err(), GrammarError::LazyGrammarMalformed); - } - - #[test] - fn sampler_init_grammar_lazy_patterns_status_allocation_failure_maps_to_not_enough_memory() { - let result = super::sampler_init_grammar_lazy_patterns_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - assert_eq!(result.unwrap_err(), GrammarError::NotEnoughMemory); - } - - #[test] - fn sampler_init_grammar_lazy_patterns_status_exception_without_a_message_is_a_contract_error() { - let result = super::sampler_init_grammar_lazy_patterns_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_THREW_CXX_EXCEPTION, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - assert_eq!( - result.unwrap_err(), - crate::FfiContractError { - operation: "llama_rs_sampler_init_grammar_lazy_patterns", - detail: "reported a thrown C++ exception without an error message", - } - .into() - ); - } - - #[test] - fn lazy_grammar_null_output_argument_status_is_a_contract_error() { - let status = llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_NULL_OUT_SAMPLER_ARG; - let result = super::sampler_init_grammar_lazy_patterns_status_to_result( - status, - std::ptr::null_mut(), - std::ptr::null_mut(), - ); - - assert_eq!( - result.unwrap_err(), - GrammarError::FfiContract(crate::FfiContractError { - operation: "llama_rs_sampler_init_grammar_lazy_patterns", - detail: "was given a null out_sampler argument", - }) - ); - } -} - -#[cfg(test)] -mod ffi_contract_status_tests { - use super::check_sampler_accept_status; - use super::sampler_chain_add_status_to_result; - use super::sampler_init_grammar_lazy_patterns_status_to_result; - use super::sampler_init_grammar_status_to_result; - use super::sampler_init_status_to_result; - use super::sampler_sample_status_to_result; - use crate::error::grammar_error::GrammarError; - use crate::error::sample_error::SampleError; - use crate::error::sampler_accept_error::SamplerAcceptError; - use std::ptr; - - #[test] - fn sampler_init_status_to_result_maps_every_contract_status() { - assert_eq!( - sampler_init_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_NULL_OUT_SAMPLER_ARG, - ptr::null_mut(), - ptr::null_mut(), - "penalties", - ) - .err(), - Some( - crate::FfiContractError { - operation: "llama_rs_sampler_init", - detail: "was given a null out_sampler argument", - } - .into() - ) - ); + fn sampler_init_status_to_result_maps_null_out_error_arg() { assert_eq!( sampler_init_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_NULL_OUT_ERROR_ARG, @@ -1627,6 +1598,10 @@ mod ffi_contract_status_tests { .into() ) ); + } + + #[test] + fn sampler_init_status_to_result_maps_llama_cpp_threw_cxx_exception_without_a_message() { assert_eq!( sampler_init_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_THREW_CXX_EXCEPTION, @@ -1643,6 +1618,10 @@ mod ffi_contract_status_tests { .into() ) ); + } + + #[test] + fn sampler_init_status_to_result_maps_an_unknown_status() { assert_eq!( sampler_init_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_LLAMA_CPP_THREW_CXX_EXCEPTION + 1, @@ -1663,9 +1642,55 @@ mod ffi_contract_status_tests { ) ); } +} + +#[cfg(test)] +mod sampler_chain_add_status_to_result_tests { + use std::ptr; + + use super::sampler_chain_add_status_to_result; + + #[test] + fn sampler_chain_add_status_allocation_failure_maps_to_not_enough_memory() { + assert_eq!( + sampler_chain_add_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_ERROR_STRING_ALLOCATION_FAILED, + ptr::null_mut(), + ), + Err(crate::SamplingError::NotEnoughMemory) + ); + } + + #[test] + fn sampler_chain_add_status_out_of_memory_names_the_chain() { + assert_eq!( + sampler_chain_add_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), + ), + Err(crate::SamplingError::LlamaCppOutOfMemory { sampler: "chain" }) + ); + } + + #[test] + fn sampler_chain_add_status_exception_carries_its_message() { + let out_error = + unsafe { llama_cpp_bindings_sys::llama_rs_string_dup(c"vector too long".as_ptr()) }; + + assert_eq!( + sampler_chain_add_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION, + out_error, + ), + Err(crate::SamplingError::Reported { + sampler: "chain", + message: "vector too long".to_owned(), + }) + ); + } #[test] - fn sampler_chain_add_status_to_result_maps_every_contract_status() { + fn sampler_chain_add_status_to_result_maps_null_chain_arg() { assert_eq!( sampler_chain_add_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_NULL_CHAIN_ARG, @@ -1677,6 +1702,10 @@ mod ffi_contract_status_tests { } .into()) ); + } + + #[test] + fn sampler_chain_add_status_to_result_maps_null_sampler_arg() { assert_eq!( sampler_chain_add_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_NULL_SAMPLER_ARG, @@ -1688,6 +1717,10 @@ mod ffi_contract_status_tests { } .into()) ); + } + + #[test] + fn sampler_chain_add_status_to_result_maps_null_out_error_arg() { assert_eq!( sampler_chain_add_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_NULL_OUT_ERROR_ARG, @@ -1699,6 +1732,10 @@ mod ffi_contract_status_tests { } .into()) ); + } + + #[test] + fn sampler_chain_add_status_to_result_maps_llama_cpp_threw_cxx_exception_without_a_message() { assert_eq!( sampler_chain_add_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION, @@ -1710,6 +1747,10 @@ mod ffi_contract_status_tests { } .into()) ); + } + + #[test] + fn sampler_chain_add_status_to_result_maps_an_unknown_status() { assert_eq!( sampler_chain_add_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_CHAIN_ADD_LLAMA_CPP_THREW_CXX_EXCEPTION @@ -1726,122 +1767,174 @@ mod ffi_contract_status_tests { .into()) ); } +} + +#[cfg(test)] +mod sampler_init_grammar_status_to_result_tests { + use std::ptr; + + use super::sampler_init_grammar_status_to_result; + use crate::GrammarError; #[test] - fn check_sampler_accept_status_maps_every_contract_status() { - let outcome_0 = check_sampler_accept_status( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_ACCEPT_NULL_OUT_ERROR_ARG, + fn sampler_init_grammar_status_null_maps_to_grammar_malformed() { + let result = sampler_init_grammar_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_RETURNED_NULL, + ptr::null_mut(), ptr::null_mut(), ); - assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_sampler_accept", - detail: "was given a null out_error argument", - } - .into() - ) + + assert_eq!(result.unwrap_err(), GrammarError::GrammarMalformed); + } + + #[test] + fn sampler_init_grammar_status_allocation_failure_maps_to_not_enough_memory() { + let result = sampler_init_grammar_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_ERROR_STRING_ALLOCATION_FAILED, + ptr::null_mut(), + ptr::null_mut(), ); - let outcome_1 = check_sampler_accept_status( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_ACCEPT_LLAMA_CPP_OUT_OF_MEMORY, + + assert_eq!(result.unwrap_err(), GrammarError::NotEnoughMemory); + } + + #[test] + fn sampler_init_grammar_status_exception_without_a_message_is_a_contract_error() { + let result = sampler_init_grammar_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_THREW_CXX_EXCEPTION, + ptr::null_mut(), ptr::null_mut(), ); + assert_eq!( - outcome_1.err(), - Some(SamplerAcceptError::LlamaCppOutOfMemory) + result.unwrap_err(), + crate::FfiContractError { + operation: "llama_rs_sampler_init_grammar", + detail: "reported a thrown C++ exception without an error message", + } + .into() ); } #[test] - fn sampler_sample_status_to_result_maps_every_contract_status() { - let outcome_0 = sampler_sample_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_NULL_SAMPLER_ARG, - 0, - ptr::null_mut(), - ); + fn grammar_null_output_argument_status_is_a_contract_error() { + let status = llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_NULL_OUT_SAMPLER_ARG; + let result = + sampler_init_grammar_status_to_result(status, ptr::null_mut(), ptr::null_mut()); + assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_sampler_sample", - detail: "was given a null sampler argument", - } - .into() - ) + result.unwrap_err(), + GrammarError::FfiContract(crate::FfiContractError { + operation: "llama_rs_sampler_init_grammar", + detail: "was given a null out_sampler argument", + }) ); - let outcome_1 = sampler_sample_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_NULL_OUT_TOKEN_ARG, - 0, + } + + #[test] + fn sampler_init_grammar_status_to_result_maps_null_out_error_arg() { + let outcome = sampler_init_grammar_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_NULL_OUT_ERROR_ARG, + ptr::null_mut(), ptr::null_mut(), ); assert_eq!( - outcome_1.err(), + outcome.err(), Some( crate::FfiContractError { - operation: "llama_rs_sampler_sample", - detail: "was given a null out_token argument", + operation: "llama_rs_sampler_init_grammar", + detail: "was given a null out_error argument", } .into() ) ); - let outcome_2 = sampler_sample_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_NULL_OUT_ERROR_ARG, - 0, + } + + #[test] + fn sampler_init_grammar_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = sampler_init_grammar_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_OUT_OF_MEMORY, + ptr::null_mut(), ptr::null_mut(), ); - assert_eq!( - outcome_2.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_sampler_sample", - detail: "was given a null out_error argument", - } - .into() - ) + assert_eq!(outcome.err(), Some(GrammarError::LlamaCppOutOfMemory)); + } +} + +#[cfg(test)] +mod sampler_init_grammar_lazy_patterns_status_to_result_tests { + use std::ptr; + + use super::sampler_init_grammar_lazy_patterns_status_to_result; + use crate::GrammarError; + + #[test] + fn sampler_init_grammar_lazy_patterns_status_null_maps_to_lazy_patterns_grammar_malformed() { + let result = sampler_init_grammar_lazy_patterns_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_RETURNED_NULL, + ptr::null_mut(), + ptr::null_mut(), ); - let outcome_3 = sampler_sample_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_SAMPLE_LLAMA_CPP_OUT_OF_MEMORY, - 0, + + assert_eq!(result.unwrap_err(), GrammarError::LazyGrammarMalformed); + } + + #[test] + fn sampler_init_grammar_lazy_patterns_status_allocation_failure_maps_to_not_enough_memory() { + let result = sampler_init_grammar_lazy_patterns_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_ERROR_STRING_ALLOCATION_FAILED, + ptr::null_mut(), ptr::null_mut(), ); - assert_eq!(outcome_3.err(), Some(SampleError::LlamaCppOutOfMemory)); + + assert_eq!(result.unwrap_err(), GrammarError::NotEnoughMemory); } #[test] - fn sampler_init_grammar_status_to_result_maps_every_contract_status() { - let outcome_0 = sampler_init_grammar_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_NULL_OUT_ERROR_ARG, + fn sampler_init_grammar_lazy_patterns_status_exception_without_a_message_is_a_contract_error() { + let result = sampler_init_grammar_lazy_patterns_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_THREW_CXX_EXCEPTION, ptr::null_mut(), ptr::null_mut(), ); + assert_eq!( - outcome_0.err(), - Some( - crate::FfiContractError { - operation: "llama_rs_sampler_init_grammar", - detail: "was given a null out_error argument", - } - .into() - ) + result.unwrap_err(), + crate::FfiContractError { + operation: "llama_rs_sampler_init_grammar_lazy_patterns", + detail: "reported a thrown C++ exception without an error message", + } + .into() ); - let outcome_1 = sampler_init_grammar_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LLAMA_CPP_OUT_OF_MEMORY, + } + + #[test] + fn lazy_grammar_null_output_argument_status_is_a_contract_error() { + let status = llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_NULL_OUT_SAMPLER_ARG; + let result = sampler_init_grammar_lazy_patterns_status_to_result( + status, ptr::null_mut(), ptr::null_mut(), ); - assert_eq!(outcome_1.err(), Some(GrammarError::LlamaCppOutOfMemory)); + + assert_eq!( + result.unwrap_err(), + GrammarError::FfiContract(crate::FfiContractError { + operation: "llama_rs_sampler_init_grammar_lazy_patterns", + detail: "was given a null out_sampler argument", + }) + ); } #[test] - fn sampler_init_grammar_lazy_patterns_status_to_result_maps_every_contract_status() { - let outcome_0 = sampler_init_grammar_lazy_patterns_status_to_result( + fn sampler_init_grammar_lazy_patterns_status_to_result_maps_null_out_error_arg() { + let outcome = sampler_init_grammar_lazy_patterns_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_NULL_OUT_ERROR_ARG, ptr::null_mut(), ptr::null_mut(), ); assert_eq!( - outcome_0.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_sampler_init_grammar_lazy_patterns", @@ -1850,7 +1943,11 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_1 = sampler_init_grammar_lazy_patterns_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), ptr::null_mut()); - assert_eq!(outcome_1.err(), Some(GrammarError::LlamaCppOutOfMemory)); + } + + #[test] + fn sampler_init_grammar_lazy_patterns_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = sampler_init_grammar_lazy_patterns_status_to_result(llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_INIT_GRAMMAR_LAZY_PATTERNS_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), ptr::null_mut()); + assert_eq!(outcome.err(), Some(GrammarError::LlamaCppOutOfMemory)); } } diff --git a/llama-cpp-bindings/src/token/data_array.rs b/llama-cpp-bindings/src/token/data_array.rs index 2187c9a04..497121898 100644 --- a/llama-cpp-bindings/src/token/data_array.rs +++ b/llama-cpp-bindings/src/token/data_array.rs @@ -220,52 +220,11 @@ impl LlamaTokenDataArray { #[cfg(test)] mod tests { + use super::LlamaTokenDataArray; use crate::error::SamplerApplyError; use crate::token::LlamaToken; use crate::token::data::LlamaTokenData; - use super::LlamaTokenDataArray; - use super::sampler_apply_status_to_result; - - #[test] - fn sampler_apply_status_allocation_failed_returns_not_enough_memory() { - assert_eq!( - sampler_apply_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_APPLY_ERROR_STRING_ALLOCATION_FAILED, - std::ptr::null_mut(), - ), - Err(SamplerApplyError::NotEnoughMemory), - ); - } - - #[test] - fn sampler_apply_status_cxx_exception_without_a_message_is_a_contract_error() { - assert_eq!( - sampler_apply_status_to_result( - llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_APPLY_LLAMA_CPP_THREW_CXX_EXCEPTION, - std::ptr::null_mut(), - ), - Err(crate::FfiContractError { - operation: "llama_rs_sampler_apply", - detail: "reported a thrown C++ exception without an error message", - } - .into()), - ); - } - - #[test] - fn sampler_apply_unknown_status_is_preserved() { - let result = sampler_apply_status_to_result(255, std::ptr::null_mut()); - - assert_eq!( - result, - Err(SamplerApplyError::FfiStatus(crate::FfiStatusError { - operation: "llama_rs_sampler_apply", - code: 255, - })) - ); - } - #[test] fn a_sampler_excluding_every_candidate_leaves_nothing_to_select() { use crate::sampling::LlamaSampler; @@ -526,24 +485,68 @@ mod tests { } #[cfg(test)] -mod ffi_contract_status_tests { - use super::sampler_apply_status_to_result; - use crate::error::sampler_apply_error::SamplerApplyError; +mod sampler_apply_status_to_result_tests { use std::ptr; + use super::sampler_apply_status_to_result; + use crate::error::SamplerApplyError; + #[test] - fn sampler_apply_status_to_result_maps_every_contract_status() { - let outcome_0 = sampler_apply_status_to_result( + fn sampler_apply_status_allocation_failed_returns_not_enough_memory() { + assert_eq!( + sampler_apply_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_APPLY_ERROR_STRING_ALLOCATION_FAILED, + ptr::null_mut(), + ), + Err(SamplerApplyError::NotEnoughMemory), + ); + } + + #[test] + fn sampler_apply_status_cxx_exception_without_a_message_is_a_contract_error() { + assert_eq!( + sampler_apply_status_to_result( + llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_APPLY_LLAMA_CPP_THREW_CXX_EXCEPTION, + ptr::null_mut(), + ), + Err(crate::FfiContractError { + operation: "llama_rs_sampler_apply", + detail: "reported a thrown C++ exception without an error message", + } + .into()), + ); + } + + #[test] + fn sampler_apply_unknown_status_is_preserved() { + let result = sampler_apply_status_to_result(255, ptr::null_mut()); + + assert_eq!( + result, + Err(SamplerApplyError::FfiStatus(crate::FfiStatusError { + operation: "llama_rs_sampler_apply", + code: 255, + })) + ); + } + + #[test] + fn sampler_apply_status_to_result_maps_null_sampler_arg() { + let outcome = sampler_apply_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_APPLY_NULL_SAMPLER_ARG, ptr::null_mut(), ); - assert_eq!(outcome_0.err(), Some(SamplerApplyError::NullSampler)); - let outcome_data_array = sampler_apply_status_to_result( + assert_eq!(outcome.err(), Some(SamplerApplyError::NullSampler)); + } + + #[test] + fn sampler_apply_status_to_result_maps_null_data_array_arg() { + let outcome = sampler_apply_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_APPLY_NULL_DATA_ARRAY_ARG, ptr::null_mut(), ); assert_eq!( - outcome_data_array.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_sampler_apply", @@ -552,12 +555,16 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_1 = sampler_apply_status_to_result( + } + + #[test] + fn sampler_apply_status_to_result_maps_null_out_error_arg() { + let outcome = sampler_apply_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_APPLY_NULL_OUT_ERROR_ARG, ptr::null_mut(), ); assert_eq!( - outcome_1.err(), + outcome.err(), Some( crate::FfiContractError { operation: "llama_rs_sampler_apply", @@ -566,13 +573,14 @@ mod ffi_contract_status_tests { .into() ) ); - let outcome_2 = sampler_apply_status_to_result( + } + + #[test] + fn sampler_apply_status_to_result_maps_llama_cpp_out_of_memory() { + let outcome = sampler_apply_status_to_result( llama_cpp_bindings_sys::LLAMA_RS_SAMPLER_APPLY_LLAMA_CPP_OUT_OF_MEMORY, ptr::null_mut(), ); - assert_eq!( - outcome_2.err(), - Some(SamplerApplyError::LlamaCppOutOfMemory) - ); + assert_eq!(outcome.err(), Some(SamplerApplyError::LlamaCppOutOfMemory)); } } diff --git a/llama-cpp-test-harness/src/download_model.rs b/llama-cpp-test-harness/src/download_model.rs index e7cf1aa2b..7a194fa90 100644 --- a/llama-cpp-test-harness/src/download_model.rs +++ b/llama-cpp-test-harness/src/download_model.rs @@ -5,8 +5,7 @@ use anyhow::Result; /// # Errors /// /// Returns an error if the HF client cannot be built or the file cannot be downloaded -/// (e.g., the repo or file does not exist, or network access fails). `hf-hub`'s error type -/// already carries the repo and file in its messages, so no extra context is added here. +/// (e.g., the repo or file does not exist, or network access fails). pub fn download_model(repo: &str, file: &str) -> Result { let api = hf_hub::api::sync::ApiBuilder::from_env() .with_progress(true) @@ -16,12 +15,16 @@ pub fn download_model(repo: &str, file: &str) -> Result { #[cfg(test)] mod tests { + use hf_hub::api::sync::ApiError; + use super::download_model; #[test] fn missing_file_in_real_repo_returns_error() { - let result = download_model("unsloth/Qwen3.5-0.8B-GGUF", "this-file-does-not-exist.gguf"); + let download_failure = + download_model("unsloth/Qwen3.5-0.8B-GGUF", "this-file-does-not-exist.gguf") + .expect_err("a file missing from an existing repository must fail to download"); - assert!(result.is_err()); + assert!(download_failure.is::()); } } diff --git a/llama-cpp-test-harness/src/execution_phase.rs b/llama-cpp-test-harness/src/execution_phase.rs index eb29819d8..ed81fa6f6 100644 --- a/llama-cpp-test-harness/src/execution_phase.rs +++ b/llama-cpp-test-harness/src/execution_phase.rs @@ -1,26 +1,5 @@ -use std::sync::Arc; -use std::sync::OnceLock; - -use libtest_mimic::Arguments; -use libtest_mimic::Conclusion; -use libtest_mimic::Failed; -use libtest_mimic::Trial; -use llama_cpp_bindings::llama_backend::LlamaBackend; - -use crate::GgufSource; -use crate::llama_fixture::LlamaFixture; use crate::llama_test_registration::LlamaTestRegistration; use crate::load_key::LoadKey; -use crate::phase_state::PhaseState; - -type LazyPhaseState = Arc>>; - -fn source_label(source: GgufSource) -> String { - match source { - GgufSource::HuggingFace { repo, file } => format!("{repo} / {file}"), - GgufSource::LocalPath(path) => format!("local:{path}"), - } -} pub struct ExecutionPhase { pub key: LoadKey, @@ -31,10 +10,10 @@ impl ExecutionPhase { #[must_use] pub fn header_line(&self, index: usize, total: usize) -> String { format!( - "--- phase {phase_number}/{total_phases}: {source_label} (n_gpu_layers={n_gpu_layers}) ({trial_count} tests) ---", + "--- phase {phase_number}/{total_phases}: {model_source} (n_gpu_layers={n_gpu_layers}) ({trial_count} tests) ---", phase_number = index + 1, total_phases = total, - source_label = source_label(self.key.model_source), + model_source = self.key.model_source, n_gpu_layers = self.key.model_load_params.n_gpu_layers, trial_count = self.registrations.len(), ) @@ -43,46 +22,6 @@ impl ExecutionPhase { pub fn print_header(&self, index: usize, total: usize) { eprintln!("{}", self.header_line(index, total)); } - - pub fn run(&self, backend: &Arc, arguments: &Arguments) -> Conclusion { - let phase_state: LazyPhaseState = Arc::new(OnceLock::new()); - - libtest_mimic::run( - arguments, - self.registrations - .iter() - .map(|registration| self.trial(registration, backend, &phase_state)) - .collect(), - ) - } - - fn trial( - &self, - registration: &'static LlamaTestRegistration, - backend: &Arc, - phase_state: &LazyPhaseState, - ) -> Trial { - let key = self.key; - let backend = Arc::clone(backend); - let phase_state = Arc::clone(phase_state); - - Trial::test(registration.name, move || { - match phase_state.get_or_init(|| { - key.load_phase_state(&backend) - .map_err(|error| format!("phase setup failed: {error:#}")) - }) { - Ok(state) => (registration.func)(&LlamaFixture { - model: &state.model, - backend: &state.backend, - context_params: ®istration.context_params, - mtmd_context: state.mtmd_context.as_ref(), - model_path: &state.model_path, - }) - .map_err(|error| Failed::from(format!("{error:#}"))), - Err(setup_failure) => Err(Failed::from(setup_failure)), - } - }) - } } #[cfg(test)] diff --git a/llama-cpp-test-harness/src/execution_plan.rs b/llama-cpp-test-harness/src/execution_plan.rs index 87c4658ef..e1c203a54 100644 --- a/llama-cpp-test-harness/src/execution_plan.rs +++ b/llama-cpp-test-harness/src/execution_plan.rs @@ -7,6 +7,7 @@ use llama_cpp_bindings::llama_backend::LlamaBackend; use crate::execution_phase::ExecutionPhase; use crate::llama_test_registration::LlamaTestRegistration; +use crate::scheduled_phase::ScheduledPhase; fn collect_inventory_registrations() -> Vec<&'static LlamaTestRegistration> { inventory::iter:: @@ -52,12 +53,21 @@ impl ExecutionPlan { #[must_use] pub fn run(&self, backend: &Arc, arguments: &Arguments) -> Vec { let total = self.phases.len(); - let mut conclusions = Vec::with_capacity(total); - for (index, phase) in self.phases.iter().enumerate() { - phase.print_header(index, total); - conclusions.push(phase.run(backend, arguments)); + let scheduled_phases: Vec> = self + .phases + .iter() + .map(|phase| ScheduledPhase::new(phase, backend)) + .collect(); + + for scheduled_phase in &scheduled_phases { + scheduled_phase.download_model_files(arguments); } - conclusions + + scheduled_phases + .into_iter() + .enumerate() + .map(|(index, scheduled_phase)| scheduled_phase.run(index, total, arguments)) + .collect() } } diff --git a/llama-cpp-test-harness/src/gguf_source.rs b/llama-cpp-test-harness/src/gguf_source.rs index ae219c42e..71d12cd58 100644 --- a/llama-cpp-test-harness/src/gguf_source.rs +++ b/llama-cpp-test-harness/src/gguf_source.rs @@ -1,5 +1,9 @@ +use std::fmt; +use std::fmt::Display; +use std::fmt::Formatter; use std::path::PathBuf; +use anyhow::Context; use anyhow::Result; use crate::download_model::download_model; @@ -13,14 +17,23 @@ pub enum GgufSource { LocalPath(&'static str), } +impl Display for GgufSource { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::HuggingFace { repo, file } => write!(formatter, "{repo} / {file}"), + Self::LocalPath(path) => write!(formatter, "local:{path}"), + } + } +} + impl GgufSource { /// # Errors /// - /// Returns an error if the HF download fails. `LocalPath` is infallible here — file - /// existence is checked when the file is loaded. + /// Returns an error if the HF download fails, with this source attached as its context. + /// `LocalPath` is infallible here — file existence is checked when the file is loaded. pub fn resolve_path(self) -> Result { match self { - Self::HuggingFace { repo, file } => download_model(repo, file), + Self::HuggingFace { repo, file } => download_model(repo, file).context(self), Self::LocalPath(path) => Ok(PathBuf::from(path)), } } @@ -42,4 +55,18 @@ mod tests { assert_eq!(resolved, PathBuf::from("/abs/example.gguf")); } + + #[test] + fn resolve_path_failure_names_the_huggingface_source() { + let source = GgufSource::HuggingFace { + repo: "intentee-test-harness/does-not-exist", + file: "no-such-file.gguf", + }; + + let download_failure = source + .resolve_path() + .expect_err("a file from a repository that does not exist must fail to download"); + + assert_eq!(download_failure.downcast_ref::(), Some(&source)); + } } diff --git a/llama-cpp-test-harness/src/lib.rs b/llama-cpp-test-harness/src/lib.rs index a6794af94..7b59d0bbe 100644 --- a/llama-cpp-test-harness/src/lib.rs +++ b/llama-cpp-test-harness/src/lib.rs @@ -18,9 +18,12 @@ pub mod load_key; pub mod model_load_params; pub mod no_op; pub mod parse_harness_arguments; +pub mod phase_model_files; +pub mod phase_setup; pub mod phase_state; pub mod run; pub mod run_to_conclusions; +pub mod scheduled_phase; #[cfg(test)] mod test_backend_gate; @@ -34,9 +37,12 @@ pub use crate::llama_test_registration::LlamaTestRegistration; pub use crate::load_key::LoadKey; pub use crate::model_load_params::ModelLoadParams; pub use crate::no_op::no_op; +pub use crate::phase_model_files::PhaseModelFiles; +pub use crate::phase_setup::PhaseSetup; pub use crate::phase_state::PhaseState; pub use crate::run::run; pub use crate::run_to_conclusions::run_to_conclusions; +pub use crate::scheduled_phase::ScheduledPhase; pub use llama_cpp_bindings::model::llama_load_mode::LlamaLoadMode; pub use llama_cpp_test_harness_macros::llama_test; diff --git a/llama-cpp-test-harness/src/load_key.rs b/llama-cpp-test-harness/src/load_key.rs index d84d3d7e2..2f889f49e 100644 --- a/llama-cpp-test-harness/src/load_key.rs +++ b/llama-cpp-test-harness/src/load_key.rs @@ -1,14 +1,8 @@ -use std::sync::Arc; - use anyhow::Result; -use llama_cpp_bindings::llama_backend::LlamaBackend; -use llama_cpp_bindings::model::LlamaModel; -use llama_cpp_bindings::mtmd::MtmdContext; -use llama_cpp_bindings::mtmd::MtmdContextParams; use crate::gguf_source::GgufSource; use crate::model_load_params::ModelLoadParams; -use crate::phase_state::PhaseState; +use crate::phase_model_files::PhaseModelFiles; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct LoadKey { @@ -20,38 +14,22 @@ pub struct LoadKey { impl LoadKey { /// # Errors /// - /// Returns an error if any of: source resolution fails, loading the model into llama.cpp - /// fails, or initializing the `MtmdContext` fails. - pub fn load_phase_state(&self, backend: &Arc) -> Result { - let model_path = self.model_source.resolve_path()?; - let model_params = self.model_load_params.into_llama_model_params(); - let model = LlamaModel::load_from_file(backend, &model_path, &model_params)?; - - let mtmd_context = match self.mmproj_source { - Some(mmproj_source) => { - let mmproj_path = mmproj_source.resolve_path()?; - let mmproj_path_str = mmproj_path.to_string_lossy(); - let params = MtmdContextParams::default(); - Some(MtmdContext::init_from_file( - mmproj_path_str.as_ref(), - &model, - ¶ms, - )?) - } - None => None, - }; - - Ok(PhaseState { - mtmd_context, - model, - backend: Arc::clone(backend), - model_path, + /// Returns an error if downloading the model file or the mmproj file fails. + pub fn download_model_files(&self) -> Result { + Ok(PhaseModelFiles { + model_path: self.model_source.resolve_path()?, + mmproj_path: self + .mmproj_source + .map(GgufSource::resolve_path) + .transpose()?, }) } } #[cfg(test)] mod tests { + use std::path::PathBuf; + use llama_cpp_bindings::model::LlamaLoadMode; use crate::gguf_source::GgufSource; @@ -116,84 +94,40 @@ mod tests { assert_ne!(baseline(), other); } - use std::sync::Arc; - - use llama_cpp_bindings::llama_backend::LlamaBackend; - - use crate::test_backend_gate::BACKEND_INIT_GATE; - - const NON_GGUF_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"); - #[test] - fn load_phase_state_propagates_model_load_failure() { - let _gate = BACKEND_INIT_GATE - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let backend = Arc::new(LlamaBackend::init().expect("backend init must succeed")); - let key = LoadKey { - model_source: GgufSource::LocalPath(NON_GGUF_PATH), - mmproj_source: None, - model_load_params: ModelLoadParams { - n_gpu_layers: 0, - load_mode: LlamaLoadMode::Mmap, - }, - }; - - let result = key.load_phase_state(&backend); - - assert!( - result.is_err(), - "LoadKey pointing at a non-GGUF file must fail to load" + fn download_model_files_resolves_local_sources_to_their_paths() { + let mut key = baseline(); + key.model_source = GgufSource::LocalPath("/abs/model.gguf"); + key.mmproj_source = Some(GgufSource::LocalPath("/abs/mmproj.gguf")); + + let model_files = key + .download_model_files() + .expect("local sources resolve without downloading anything"); + + assert_eq!(model_files.model_path, PathBuf::from("/abs/model.gguf")); + assert_eq!( + model_files.mmproj_path, + Some(PathBuf::from("/abs/mmproj.gguf")) ); } #[test] - fn load_phase_state_propagates_mmproj_download_failure() { - let _gate = BACKEND_INIT_GATE - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let backend = Arc::new(LlamaBackend::init().expect("backend init must succeed")); - let key = LoadKey { - model_source: GgufSource::LocalPath(NON_GGUF_PATH), - mmproj_source: Some(GgufSource::HuggingFace { - repo: "intentee-test-harness/does-not-exist", - file: "no-such-mmproj.gguf", - }), - model_load_params: ModelLoadParams { - n_gpu_layers: 0, - load_mode: LlamaLoadMode::Mmap, - }, - }; - - let result = key.load_phase_state(&backend); - - assert!( - result.is_err(), - "LoadKey with bogus mmproj HF repo must fail; the error must surface either at model \ - load (the non-GGUF model fails first) or at mmproj download" - ); - } - - #[test] - fn load_phase_state_propagates_mmproj_local_load_failure() { - let _gate = BACKEND_INIT_GATE - .lock() - .unwrap_or_else(std::sync::PoisonError::into_inner); - let backend = Arc::new(LlamaBackend::init().expect("backend init must succeed")); - let key = LoadKey { - model_source: GgufSource::LocalPath(NON_GGUF_PATH), - mmproj_source: Some(GgufSource::LocalPath(NON_GGUF_PATH)), - model_load_params: ModelLoadParams { - n_gpu_layers: 0, - load_mode: LlamaLoadMode::Mmap, - }, + fn download_model_files_propagates_mmproj_download_failure() { + let missing_mmproj_source = GgufSource::HuggingFace { + repo: "intentee-test-harness/does-not-exist", + file: "no-such-mmproj.gguf", }; + let mut key = baseline(); + key.model_source = GgufSource::LocalPath("/abs/model.gguf"); + key.mmproj_source = Some(missing_mmproj_source); - let result = key.load_phase_state(&backend); + let download_failure = key + .download_model_files() + .expect_err("an mmproj from a repository that does not exist must fail to download"); - assert!( - result.is_err(), - "LoadKey pointing at a non-mmproj LocalPath must fail at MtmdContext init" + assert_eq!( + download_failure.downcast_ref::(), + Some(&missing_mmproj_source) ); } } diff --git a/llama-cpp-test-harness/src/phase_model_files.rs b/llama-cpp-test-harness/src/phase_model_files.rs new file mode 100644 index 000000000..6a467d47a --- /dev/null +++ b/llama-cpp-test-harness/src/phase_model_files.rs @@ -0,0 +1,101 @@ +use std::path::PathBuf; +use std::sync::Arc; + +use anyhow::Result; +use llama_cpp_bindings::llama_backend::LlamaBackend; +use llama_cpp_bindings::model::LlamaModel; +use llama_cpp_bindings::mtmd::MtmdContext; +use llama_cpp_bindings::mtmd::MtmdContextParams; + +use crate::model_load_params::ModelLoadParams; +use crate::phase_state::PhaseState; + +#[derive(Debug)] +pub struct PhaseModelFiles { + pub model_path: PathBuf, + pub mmproj_path: Option, +} + +impl PhaseModelFiles { + /// # Errors + /// + /// Returns an error if loading the model into llama.cpp fails, or initializing the + /// `MtmdContext` from the mmproj file fails. + pub fn load_phase_state( + &self, + backend: &Arc, + model_load_params: ModelLoadParams, + ) -> Result { + let Self { + model_path, + mmproj_path, + } = self; + let model_params = model_load_params.into_llama_model_params(); + let model = LlamaModel::load_from_file(backend, model_path, &model_params)?; + + let mtmd_context = match mmproj_path { + Some(mmproj_path) => { + let mmproj_path_str = mmproj_path.to_string_lossy(); + let params = MtmdContextParams::default(); + Some(MtmdContext::init_from_file( + mmproj_path_str.as_ref(), + &model, + ¶ms, + )?) + } + None => None, + }; + + Ok(PhaseState { + mtmd_context, + model, + backend: Arc::clone(backend), + model_path: model_path.clone(), + }) + } +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + use std::sync::Arc; + use std::sync::PoisonError; + + use llama_cpp_bindings::LlamaModelLoadError; + use llama_cpp_bindings::llama_backend::LlamaBackend; + use llama_cpp_bindings::model::LlamaLoadMode; + + use crate::model_load_params::ModelLoadParams; + use crate::test_backend_gate::BACKEND_INIT_GATE; + + use super::PhaseModelFiles; + + const NON_GGUF_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"); + + #[test] + fn load_phase_state_propagates_model_load_failure() { + let _gate = BACKEND_INIT_GATE + .lock() + .unwrap_or_else(PoisonError::into_inner); + let backend = Arc::new(LlamaBackend::init().expect("backend init must succeed")); + let model_files = PhaseModelFiles { + model_path: PathBuf::from(NON_GGUF_PATH), + mmproj_path: None, + }; + + let load_failure = model_files + .load_phase_state( + &backend, + ModelLoadParams { + n_gpu_layers: 0, + load_mode: LlamaLoadMode::Mmap, + }, + ) + .expect_err("a model file that is not GGUF must fail to load"); + + assert_eq!( + load_failure.downcast_ref::(), + Some(&LlamaModelLoadError::Unloadable) + ); + } +} diff --git a/llama-cpp-test-harness/src/phase_setup.rs b/llama-cpp-test-harness/src/phase_setup.rs new file mode 100644 index 000000000..9b70eade2 --- /dev/null +++ b/llama-cpp-test-harness/src/phase_setup.rs @@ -0,0 +1,123 @@ +use std::sync::Arc; +use std::sync::OnceLock; + +use anyhow::Error; +use anyhow::Result; +use llama_cpp_bindings::llama_backend::LlamaBackend; + +use crate::load_key::LoadKey; +use crate::phase_model_files::PhaseModelFiles; +use crate::phase_state::PhaseState; + +pub struct PhaseSetup { + key: LoadKey, + backend: Arc, + model_files: OnceLock>, + phase_state: OnceLock>, +} + +impl PhaseSetup { + #[must_use] + pub const fn new(key: LoadKey, backend: Arc) -> Self { + Self { + key, + backend, + model_files: OnceLock::new(), + phase_state: OnceLock::new(), + } + } + + /// # Errors + /// + /// Returns the failure of downloading the model or the mmproj file. The outcome is cached, so + /// the files are downloaded at most once per phase. + pub fn download_model_files(&self) -> Result<&PhaseModelFiles, &Error> { + self.model_files + .get_or_init(|| self.key.download_model_files()) + .as_ref() + } + + /// # Errors + /// + /// Returns the cached download failure, or the failure of loading the downloaded files into + /// llama.cpp. The outcome is cached, so the model is loaded at most once per phase. + pub fn load_phase_state(&self) -> Result<&PhaseState, &Error> { + let model_files = self.download_model_files()?; + + self.phase_state + .get_or_init(|| model_files.load_phase_state(&self.backend, self.key.model_load_params)) + .as_ref() + } +} + +#[cfg(test)] +mod tests { + use std::ptr; + use std::sync::Arc; + use std::sync::PoisonError; + + use llama_cpp_bindings::llama_backend::LlamaBackend; + use llama_cpp_bindings::model::LlamaLoadMode; + + use crate::gguf_source::GgufSource; + use crate::load_key::LoadKey; + use crate::model_load_params::ModelLoadParams; + use crate::test_backend_gate::BACKEND_INIT_GATE; + + use super::PhaseSetup; + + const NON_GGUF_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"); + + fn key_for_model(model_source: GgufSource) -> LoadKey { + LoadKey { + model_source, + mmproj_source: None, + model_load_params: ModelLoadParams { + n_gpu_layers: 0, + load_mode: LlamaLoadMode::Mmap, + }, + } + } + + #[test] + fn load_phase_state_returns_the_cached_download_failure() { + let _gate = BACKEND_INIT_GATE + .lock() + .unwrap_or_else(PoisonError::into_inner); + let backend = Arc::new(LlamaBackend::init().expect("backend init must succeed")); + let setup = PhaseSetup::new( + key_for_model(GgufSource::HuggingFace { + repo: "intentee-test-harness/does-not-exist", + file: "no-such-file.gguf", + }), + backend, + ); + + let download_failure = setup + .download_model_files() + .expect_err("a model from a repository that does not exist must fail to download"); + let setup_failure = setup + .load_phase_state() + .expect_err("a phase whose model failed to download must fail to set up"); + + assert!(ptr::eq(download_failure, setup_failure)); + } + + #[test] + fn load_phase_state_loads_once_and_caches_the_outcome() { + let _gate = BACKEND_INIT_GATE + .lock() + .unwrap_or_else(PoisonError::into_inner); + let backend = Arc::new(LlamaBackend::init().expect("backend init must succeed")); + let setup = PhaseSetup::new(key_for_model(GgufSource::LocalPath(NON_GGUF_PATH)), backend); + + let first_failure = setup + .load_phase_state() + .expect_err("a model file that is not GGUF must fail to load"); + let second_failure = setup + .load_phase_state() + .expect_err("a cached load failure must be reported again"); + + assert!(ptr::eq(first_failure, second_failure)); + } +} diff --git a/llama-cpp-test-harness/src/phase_state.rs b/llama-cpp-test-harness/src/phase_state.rs index 1993d6a01..53e4560f3 100644 --- a/llama-cpp-test-harness/src/phase_state.rs +++ b/llama-cpp-test-harness/src/phase_state.rs @@ -5,6 +5,7 @@ use llama_cpp_bindings::llama_backend::LlamaBackend; use llama_cpp_bindings::model::LlamaModel; use llama_cpp_bindings::mtmd::MtmdContext; +#[derive(Debug)] pub struct PhaseState { pub mtmd_context: Option, pub model: LlamaModel, diff --git a/llama-cpp-test-harness/src/scheduled_phase.rs b/llama-cpp-test-harness/src/scheduled_phase.rs new file mode 100644 index 000000000..5c4dfeef1 --- /dev/null +++ b/llama-cpp-test-harness/src/scheduled_phase.rs @@ -0,0 +1,157 @@ +use std::sync::Arc; + +use libtest_mimic::Arguments; +use libtest_mimic::Conclusion; +use libtest_mimic::Failed; +use libtest_mimic::Trial; +use llama_cpp_bindings::llama_backend::LlamaBackend; + +use crate::execution_phase::ExecutionPhase; +use crate::llama_fixture::LlamaFixture; +use crate::llama_test_registration::LlamaTestRegistration; +use crate::phase_setup::PhaseSetup; + +fn trial(registration: &'static LlamaTestRegistration, setup: &Arc) -> Trial { + let setup = Arc::clone(setup); + + Trial::test(registration.name, move || match setup.load_phase_state() { + Ok(state) => (registration.func)(&LlamaFixture { + model: &state.model, + backend: &state.backend, + context_params: ®istration.context_params, + mtmd_context: state.mtmd_context.as_ref(), + model_path: &state.model_path, + }) + .map_err(|error| Failed::from(format!("{error:#}"))), + Err(setup_failure) => Err(Failed::from(format!( + "phase setup failed: {setup_failure:#}" + ))), + }) +} + +pub struct ScheduledPhase<'plan> { + pub phase: &'plan ExecutionPhase, + pub setup: Arc, + pub trials: Vec, +} + +impl<'plan> ScheduledPhase<'plan> { + #[must_use] + pub fn new(phase: &'plan ExecutionPhase, backend: &Arc) -> Self { + let setup = Arc::new(PhaseSetup::new(phase.key, Arc::clone(backend))); + let trials = phase + .registrations + .iter() + .map(|registration| trial(registration, &setup)) + .collect(); + + Self { + phase, + setup, + trials, + } + } + + #[must_use] + pub fn selects_any_trial(&self, arguments: &Arguments) -> bool { + !arguments.list + && self + .trials + .iter() + .any(|trial| !arguments.is_filtered_out(trial) && !arguments.is_ignored(trial)) + } + + pub fn download_model_files(&self, arguments: &Arguments) { + if self.selects_any_trial(arguments) + && let Err(download_failure) = self.setup.download_model_files() + { + eprintln!("model files download failed: {download_failure:#}"); + } + } + + pub fn run(self, index: usize, total: usize, arguments: &Arguments) -> Conclusion { + self.phase.print_header(index, total); + + if self.selects_any_trial(arguments) + && let Err(setup_failure) = self.setup.load_phase_state() + { + eprintln!("phase setup failed: {setup_failure:#}"); + } + + libtest_mimic::run(arguments, self.trials) + } +} + +#[cfg(test)] +mod tests { + use std::iter; + use std::sync::Arc; + use std::sync::PoisonError; + + use libtest_mimic::Arguments; + use llama_cpp_bindings::llama_backend::LlamaBackend; + use llama_cpp_bindings::model::LlamaLoadMode; + + use crate::context_params::ContextParams; + use crate::execution_phase::ExecutionPhase; + use crate::gguf_source::GgufSource; + use crate::llama_test_registration::LlamaTestRegistration; + use crate::load_key::LoadKey; + use crate::model_load_params::ModelLoadParams; + use crate::no_op::no_op; + use crate::test_backend_gate::BACKEND_INIT_GATE; + + use super::ScheduledPhase; + + static REGISTRATION: LlamaTestRegistration = LlamaTestRegistration { + name: "scheduled_trial", + key: LoadKey { + model_source: GgufSource::LocalPath("/nonexistent/llama-cpp-test-harness/model.gguf"), + mmproj_source: None, + model_load_params: ModelLoadParams { + n_gpu_layers: 0, + load_mode: LlamaLoadMode::Mmap, + }, + }, + context_params: ContextParams { + n_ctx: 1, + n_batch: 1, + n_ubatch: 1, + n_seq_max: 1, + n_threads_batch: None, + embeddings: false, + }, + void_logs: false, + func: no_op, + }; + + fn selects_any_trial_with(harness_arguments: &[&str]) -> bool { + let _gate = BACKEND_INIT_GATE + .lock() + .unwrap_or_else(PoisonError::into_inner); + let backend = Arc::new(LlamaBackend::init().expect("backend init must succeed")); + let phase = ExecutionPhase { + key: REGISTRATION.key, + registrations: vec![®ISTRATION], + }; + let arguments = + Arguments::from_iter(iter::once("harness").chain(harness_arguments.iter().copied())); + + ScheduledPhase::new(&phase, &backend).selects_any_trial(&arguments) + } + + #[test] + fn selects_any_trial_when_a_trial_matches_the_filter() { + assert!(selects_any_trial_with(&["scheduled"])); + } + + #[test] + fn selects_no_trial_when_every_trial_is_filtered_out() { + assert!(!selects_any_trial_with(&["--exact", "another_trial"])); + } + + #[test] + fn selects_no_trial_when_listing() { + assert!(!selects_any_trial_with(&["--list"])); + } +} diff --git a/llama-cpp-test-harness/tests/harness_self_test.rs b/llama-cpp-test-harness/tests/harness_self_test.rs index bf962fb51..42e4f7b8c 100644 --- a/llama-cpp-test-harness/tests/harness_self_test.rs +++ b/llama-cpp-test-harness/tests/harness_self_test.rs @@ -111,6 +111,14 @@ fn phase_b_second_passing_trial(fixture: &LlamaFixture<'_>) -> Result<()> { n_ubatch = 64, mmproj_source = LocalPath("/nonexistent/llama-cpp-test-harness/no-such-mmproj.gguf"), )] +#[llama_test( + model_source = LocalPath("Cargo.toml"), + n_gpu_layers = 0, + load_mode = Mmap, + n_ctx = 1, + n_batch = 1, + n_ubatch = 1 +)] fn shared_setup_failure_and_phase_a_trial(fixture: &LlamaFixture<'_>) -> Result<()> { assert!(fixture.model_path.exists()); Ok(()) @@ -133,9 +141,9 @@ fn phase_d_mmproj_trial(fixture: &LlamaFixture<'_>) -> Result<()> { Ok(()) } -const EXPECTED_PHASES: usize = 6; +const EXPECTED_PHASES: usize = 7; const EXPECTED_PASSED: u64 = 6; -const EXPECTED_FAILED: u64 = 4; +const EXPECTED_FAILED: u64 = 5; fn main() -> ExitCode { let conclusions = match run_to_conclusions() { diff --git a/llama-cpp-wrapper-sources/src/wrapper_headers.rs b/llama-cpp-wrapper-sources/src/wrapper_headers.rs index a91184902..a366ebd1f 100644 --- a/llama-cpp-wrapper-sources/src/wrapper_headers.rs +++ b/llama-cpp-wrapper-sources/src/wrapper_headers.rs @@ -8,6 +8,7 @@ pub const WRAPPER_HEADERS: &[&str] = &[ "wrapper_gbnf.h", "wrapper_mtmd.h", "wrapper_reasoning.h", + "wrapper_report_current_exception.h", "wrapper_sampler_init.h", "wrapper_state.h", "wrapper_token_text.h",