1pub mod add_bos;
2pub mod llama_chat_message;
3pub mod llama_chat_template;
4pub mod llama_lora_adapter;
5pub mod llama_split_mode_parse_error;
6pub mod params;
7pub mod rope_type;
8pub mod split_mode;
9pub mod vocab_type;
10pub mod vocab_type_from_int_error;
11
12use std::ffi::{CStr, CString, c_char};
13use std::num::NonZeroU16;
14use std::os::raw::c_int;
15use std::path::Path;
16use std::ptr;
17use std::ptr::NonNull;
18use std::sync::Arc;
19use std::sync::OnceLock;
20
21use toktrie::ApproximateTokEnv;
22use toktrie::TokRxInfo;
23use toktrie::TokTrie;
24
25use llama_cpp_bindings_types::ParsedChatMessage;
26use llama_cpp_bindings_types::ParsedToolCall;
27use llama_cpp_bindings_types::ReasoningMarkers;
28use llama_cpp_bindings_types::ToolCallArguments;
29use llama_cpp_bindings_types::ToolCallMarkers;
30
31use crate::chat_message_parse_outcome::ChatMessageParseOutcome;
32use crate::llama_backend::LlamaBackend;
33use crate::llama_token_attrs::LlamaTokenAttrs;
34use crate::llama_token_attrs_from_int_error::LlamaTokenAttrsFromIntError;
35use crate::raw_chat_message::RawChatMessage;
36use crate::resolved_tool_call_markers::ResolvedToolCallMarkers;
37use crate::sampled_token::SampledToken;
38use crate::sampled_token_classifier::SampledTokenClassifier;
39use crate::streaming_markers::StreamingMarkers;
40use crate::token::LlamaToken;
41use crate::tool_call_format;
42use crate::tool_call_format::ToolCallFormatOutcome;
43use crate::tool_call_template_overrides;
44use crate::{
45 ApplyChatTemplateError, ChatTemplateError, LlamaLoraAdapterInitError, LlamaModelLoadError,
46 MarkerDetectionError, MetaValError, ParseChatMessageError, StringToTokenError,
47 TokenToStringError,
48};
49
50pub use add_bos::AddBos;
51pub use llama_chat_message::LlamaChatMessage;
52pub use llama_chat_template::LlamaChatTemplate;
53pub use llama_lora_adapter::LlamaLoraAdapter;
54pub use rope_type::RopeType;
55pub use vocab_type::VocabType;
56pub use vocab_type_from_int_error::VocabTypeFromIntError;
57
58use params::LlamaModelParams;
59
60fn validate_string_length_for_tokenizer(length: usize) -> Result<c_int, StringToTokenError> {
61 Ok(c_int::try_from(length)?)
62}
63
64fn cstring_with_validated_len(str: &str) -> Result<(CString, c_int), StringToTokenError> {
65 let c_string = CString::new(str)?;
66 let len = validate_string_length_for_tokenizer(c_string.as_bytes().len())?;
67 Ok((c_string, len))
68}
69
70pub struct LlamaModel {
71 pub model: NonNull<llama_cpp_bindings_sys::llama_model>,
72 tok_env: OnceLock<Arc<ApproximateTokEnv>>,
73 chat_parser: OnceLock<ChatParserHandle>,
74}
75
76#[derive(Debug)]
77struct ChatParserHandle {
78 parser: NonNull<llama_cpp_bindings_sys::llama_rs_chat_parser>,
79}
80
81unsafe impl Send for ChatParserHandle {}
86
87unsafe impl Sync for ChatParserHandle {}
88
89impl Drop for ChatParserHandle {
90 fn drop(&mut self) {
91 let mut out_error: *mut c_char = ptr::null_mut();
92 unsafe {
93 llama_cpp_bindings_sys::llama_rs_chat_parser_free(
94 self.parser.as_ptr(),
95 &raw mut out_error,
96 );
97 }
98 if !out_error.is_null() {
99 let _ = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
100 }
101 }
102}
103
104impl std::fmt::Debug for LlamaModel {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.debug_struct("LlamaModel")
107 .field("model", &self.model)
108 .finish_non_exhaustive()
109 }
110}
111
112unsafe impl Send for LlamaModel {}
113
114unsafe impl Sync for LlamaModel {}
115
116unsafe fn load_model_from_file_status_to_result(
120 status: llama_cpp_bindings_sys::llama_rs_load_model_from_file_status,
121 out_model: *mut llama_cpp_bindings_sys::llama_model,
122 out_error: *mut c_char,
123 path: &Path,
124) -> Result<LlamaModel, LlamaModelLoadError> {
125 match status {
126 llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_OK => {
127 let model = NonNull::new(out_model).ok_or(LlamaModelLoadError::Unloadable)?;
128 Ok(LlamaModel {
129 model,
130 tok_env: OnceLock::new(),
131 chat_parser: OnceLock::new(),
132 })
133 }
134 llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_VENDORED_RETURNED_NULL => {
135 if path.exists() {
136 Err(LlamaModelLoadError::Unloadable)
137 } else {
138 Err(LlamaModelLoadError::FileNotFound(path.to_path_buf()))
139 }
140 }
141 llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED => {
142 Err(LlamaModelLoadError::NotEnoughMemory)
143 }
144 llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_VENDORED_THREW_CXX_EXCEPTION => {
145 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
146 Err(LlamaModelLoadError::Reported { message })
147 }
148 other => {
149 unreachable!("llama_rs_load_model_from_file returned unrecognized status {other}")
150 }
151 }
152}
153
154unsafe fn parse_chat_message_status_to_result(
159 status: llama_cpp_bindings_sys::llama_rs_parse_chat_message_status,
160 handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat,
161 out_error: *mut *mut c_char,
162) -> Result<ParsedChatMessage, ParseChatMessageError> {
163 match status {
164 llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_OK => {
165 collect_parsed_chat_message(handle)
166 }
167 llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_ERROR_STRING_ALLOCATION_FAILED => {
168 Err(ParseChatMessageError::NotEnoughMemory)
169 }
170 llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_VENDORED_THREW_CXX_EXCEPTION => {
171 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(*out_error) };
172 unsafe { *out_error = ptr::null_mut() };
173 Err(ParseChatMessageError::ParseFailed { message })
174 }
175 other => {
176 unreachable!("llama_rs_parse_chat_message returned unrecognized status {other}")
177 }
178 }
179}
180
181unsafe fn chat_parser_create_status_to_result(
185 status: llama_cpp_bindings_sys::llama_rs_chat_parser_create_status,
186 parser: *mut llama_cpp_bindings_sys::llama_rs_chat_parser,
187 out_error: *mut *mut c_char,
188) -> Result<ChatParserHandle, ParseChatMessageError> {
189 match status {
190 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_OK => NonNull::new(parser).map_or_else(
191 || unreachable!("llama_rs_chat_parser_create returned OK with a null parser handle"),
192 |parser| Ok(ChatParserHandle { parser }),
193 ),
194 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_MODEL_HAS_NO_CHAT_TEMPLATE => {
195 Err(ParseChatMessageError::NoChatTemplate)
196 }
197 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_MODEL_HAS_NO_VOCAB => {
198 Err(ParseChatMessageError::NoVocab)
199 }
200 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_ERROR_STRING_ALLOCATION_FAILED => {
201 Err(ParseChatMessageError::NotEnoughMemory)
202 }
203 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_VENDORED_THREW_CXX_EXCEPTION => {
204 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(*out_error) };
205 unsafe { *out_error = ptr::null_mut() };
206 Err(ParseChatMessageError::ParseFailed { message })
207 }
208 other => {
209 unreachable!("llama_rs_chat_parser_create returned unrecognized status {other}")
210 }
211 }
212}
213
214unsafe fn parsed_chat_free_status_to_result(
218 parsed: Result<ParsedChatMessage, ParseChatMessageError>,
219 free_status: llama_cpp_bindings_sys::llama_rs_parsed_chat_free_status,
220 out_error: *mut c_char,
221 free_error: *mut c_char,
222) -> Result<ParsedChatMessage, ParseChatMessageError> {
223 match (parsed, free_status) {
224 (Ok(value), llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_FREE_OK) => {
225 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
226 Ok(value)
227 }
228 (
229 Ok(_),
230 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION,
231 ) => {
232 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
233 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(free_error) };
234 Err(ParseChatMessageError::DestructorFailed { message })
235 }
236 (
237 Ok(_),
238 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_FREE_ERROR_STRING_ALLOCATION_FAILED,
239 ) => {
240 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
241 Err(ParseChatMessageError::NotEnoughMemory)
242 }
243 (Ok(_), other) => {
244 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
245 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(free_error) };
246 unreachable!("llama_rs_parsed_chat_free returned unrecognized status {other}")
247 }
248 (Err(parse_err), _) => {
249 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
250 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(free_error) };
251 Err(parse_err)
252 }
253 }
254}
255
256fn reasoning_markers_from_marker_pair(
257 open: Option<String>,
258 close: Option<String>,
259) -> Option<ReasoningMarkers> {
260 match (open, close) {
261 (Some(open), Some(close)) if !open.is_empty() && !close.is_empty() => {
262 Some(ReasoningMarkers { open, close })
263 }
264 _ => None,
265 }
266}
267
268fn outcome_from_via_ffi_result(
269 via_ffi_result: Result<ParsedChatMessage, ParseChatMessageError>,
270 tools_json: &str,
271 input: &str,
272 is_partial: bool,
273) -> Result<ChatMessageParseOutcome, ParseChatMessageError> {
274 match via_ffi_result {
275 Ok(mut parsed) => {
276 synthesize_missing_tool_call_ids(&mut parsed.tool_calls);
277 Ok(ChatMessageParseOutcome::Recognized(parsed))
278 }
279 Err(ParseChatMessageError::ParseFailed { message }) => {
280 Ok(ChatMessageParseOutcome::Unrecognized(RawChatMessage {
281 tools_json: tools_json.to_owned(),
282 text: input.to_owned(),
283 is_partial,
284 ffi_error_message: message,
285 }))
286 }
287 Err(other) => Err(other),
288 }
289}
290
291unsafe fn apply_chat_template_status_to_result(
295 status: llama_cpp_bindings_sys::llama_rs_apply_chat_template_status,
296 out_string: *mut c_char,
297 out_error: *mut c_char,
298) -> Result<String, ApplyChatTemplateError> {
299 match status {
300 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_OK => {
301 Ok(unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_string) })
302 }
303 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_MODEL_HAS_NO_VOCAB => {
304 Err(ApplyChatTemplateError::NoVocab)
305 }
306 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_TEMPLATE_APPLICATION_FAILED => {
307 Err(ApplyChatTemplateError::TemplateApplicationFailed)
308 }
309 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_ERROR_STRING_ALLOCATION_FAILED => {
310 Err(ApplyChatTemplateError::NotEnoughMemory)
311 }
312 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_VENDORED_THREW_CXX_EXCEPTION => {
313 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
314 Err(ApplyChatTemplateError::Reported { message })
315 }
316 other => {
317 unreachable!("llama_rs_apply_chat_template returned unrecognized status {other}")
318 }
319 }
320}
321
322impl LlamaModel {
323 #[must_use]
324 pub fn vocab_ptr(&self) -> *const llama_cpp_bindings_sys::llama_vocab {
325 unsafe { llama_cpp_bindings_sys::llama_model_get_vocab(self.model.as_ptr()) }
326 }
327
328 pub fn n_ctx_train(&self) -> Result<u32, std::num::TryFromIntError> {
332 let n_ctx_train = unsafe { llama_cpp_bindings_sys::llama_n_ctx_train(self.model.as_ptr()) };
333
334 u32::try_from(n_ctx_train)
335 }
336
337 pub fn tokens(
338 &self,
339 decode_special: bool,
340 ) -> impl Iterator<Item = (LlamaToken, Result<String, TokenToStringError>)> + '_ {
341 (0..self.n_vocab())
342 .map(LlamaToken::new)
343 .map(move |llama_token| {
344 let mut decoder = encoding_rs::UTF_8.new_decoder();
345 (
346 llama_token,
347 self.token_to_piece(
348 &SampledToken::Content(llama_token),
349 &mut decoder,
350 decode_special,
351 None,
352 ),
353 )
354 })
355 }
356
357 #[must_use]
358 pub fn token_bos(&self) -> LlamaToken {
359 let token = unsafe { llama_cpp_bindings_sys::llama_token_bos(self.vocab_ptr()) };
360 LlamaToken(token)
361 }
362
363 #[must_use]
364 pub fn token_eos(&self) -> LlamaToken {
365 let token = unsafe { llama_cpp_bindings_sys::llama_token_eos(self.vocab_ptr()) };
366 LlamaToken(token)
367 }
368
369 #[must_use]
370 pub fn token_nl(&self) -> LlamaToken {
371 let token = unsafe { llama_cpp_bindings_sys::llama_token_nl(self.vocab_ptr()) };
372 LlamaToken(token)
373 }
374
375 #[must_use]
376 pub fn is_eog_token(&self, token: &SampledToken) -> bool {
377 let (SampledToken::Content(LlamaToken(id))
378 | SampledToken::Reasoning(LlamaToken(id))
379 | SampledToken::ToolCall(LlamaToken(id))
380 | SampledToken::Undeterminable(LlamaToken(id))) = *token;
381
382 unsafe { llama_cpp_bindings_sys::llama_token_is_eog(self.vocab_ptr(), id) }
383 }
384
385 #[must_use]
386 pub fn decode_start_token(&self) -> LlamaToken {
387 let token =
388 unsafe { llama_cpp_bindings_sys::llama_model_decoder_start_token(self.model.as_ptr()) };
389 LlamaToken(token)
390 }
391
392 #[must_use]
393 pub fn token_sep(&self) -> LlamaToken {
394 let token = unsafe { llama_cpp_bindings_sys::llama_vocab_sep(self.vocab_ptr()) };
395 LlamaToken(token)
396 }
397
398 pub fn str_to_token(
408 &self,
409 str: &str,
410 add_bos: AddBos,
411 ) -> Result<Vec<LlamaToken>, StringToTokenError> {
412 let add_bos = match add_bos {
413 AddBos::Always => true,
414 AddBos::Never => false,
415 };
416
417 let tokens_estimation = std::cmp::max(8, (str.len() / 2) + usize::from(add_bos));
418 let (c_string, c_string_len) = cstring_with_validated_len(str)?;
419 let vocab = self.vocab_ptr();
420
421 tokenize_into_buffer(tokens_estimation, |tokens, n_tokens_max| {
422 invoke_rs_tokenize(
423 vocab,
424 c_string.as_ptr(),
425 c_string_len,
426 tokens,
427 n_tokens_max,
428 add_bos,
429 )
430 })
431 }
432
433 pub fn token_attr(
437 &self,
438 LlamaToken(id): LlamaToken,
439 ) -> Result<LlamaTokenAttrs, LlamaTokenAttrsFromIntError> {
440 let token_type =
441 unsafe { llama_cpp_bindings_sys::llama_token_get_attr(self.vocab_ptr(), id) };
442
443 LlamaTokenAttrs::try_from(token_type)
444 }
445
446 pub fn token_to_piece(
452 &self,
453 token: &SampledToken,
454 decoder: &mut encoding_rs::Decoder,
455 special: bool,
456 lstrip: Option<NonZeroU16>,
457 ) -> Result<String, TokenToStringError> {
458 let (SampledToken::Content(inner)
459 | SampledToken::Reasoning(inner)
460 | SampledToken::ToolCall(inner)
461 | SampledToken::Undeterminable(inner)) = *token;
462 let bytes = match self.token_to_piece_bytes(inner, 8, special, lstrip) {
463 Err(TokenToStringError::InsufficientBufferSpace(required_size)) => {
464 let buffer_size: usize = (-required_size).try_into()?;
465
466 self.token_to_piece_bytes(inner, buffer_size, special, lstrip)
467 }
468 other => other,
469 }?;
470
471 let mut output_piece = String::with_capacity(bytes.len());
472 let (_result, _decoded_size, _had_replacements) =
473 decoder.decode_to_string(&bytes, &mut output_piece, false);
474
475 Ok(output_piece)
476 }
477
478 pub fn token_to_piece_bytes(
484 &self,
485 token: LlamaToken,
486 buffer_size: usize,
487 special: bool,
488 lstrip: Option<NonZeroU16>,
489 ) -> Result<Vec<u8>, TokenToStringError> {
490 let mut buffer: Vec<u8> = vec![0u8; buffer_size];
491 let buffer_len = c_int::try_from(buffer.len())?;
492 let lstrip = lstrip.map_or(0, |strip_count| i32::from(strip_count.get()));
493 let size = unsafe {
494 llama_cpp_bindings_sys::llama_token_to_piece(
495 self.vocab_ptr(),
496 token.0,
497 buffer.as_mut_ptr().cast::<c_char>(),
498 buffer_len,
499 lstrip,
500 special,
501 )
502 };
503
504 match size {
505 0 => Err(TokenToStringError::UnknownTokenType),
506 error_code if error_code.is_negative() => {
507 Err(TokenToStringError::InsufficientBufferSpace(error_code))
508 }
509 size => {
510 let written = usize::try_from(size)?;
511 buffer.truncate(written);
512
513 Ok(buffer)
514 }
515 }
516 }
517
518 #[must_use]
519 pub fn n_vocab(&self) -> i32 {
520 unsafe { llama_cpp_bindings_sys::llama_n_vocab(self.vocab_ptr()) }
521 }
522
523 pub fn vocab_type(&self) -> Result<VocabType, VocabTypeFromIntError> {
527 let vocab_type = unsafe { llama_cpp_bindings_sys::llama_vocab_type(self.vocab_ptr()) };
528
529 VocabType::try_from(vocab_type)
530 }
531
532 #[must_use]
533 pub fn n_embd(&self) -> c_int {
534 unsafe { llama_cpp_bindings_sys::llama_n_embd(self.model.as_ptr()) }
535 }
536
537 #[must_use]
538 pub fn size(&self) -> u64 {
539 unsafe { llama_cpp_bindings_sys::llama_model_size(self.model.as_ptr()) }
540 }
541
542 #[must_use]
543 pub fn n_params(&self) -> u64 {
544 unsafe { llama_cpp_bindings_sys::llama_model_n_params(self.model.as_ptr()) }
545 }
546
547 #[must_use]
548 pub fn is_recurrent(&self) -> bool {
549 unsafe { llama_cpp_bindings_sys::llama_model_is_recurrent(self.model.as_ptr()) }
550 }
551
552 pub fn n_layer(&self) -> Result<u32, std::num::TryFromIntError> {
556 u32::try_from(unsafe { llama_cpp_bindings_sys::llama_model_n_layer(self.model.as_ptr()) })
557 }
558
559 pub fn n_head(&self) -> Result<u32, std::num::TryFromIntError> {
563 u32::try_from(unsafe { llama_cpp_bindings_sys::llama_model_n_head(self.model.as_ptr()) })
564 }
565
566 pub fn n_head_kv(&self) -> Result<u32, std::num::TryFromIntError> {
570 u32::try_from(unsafe { llama_cpp_bindings_sys::llama_model_n_head_kv(self.model.as_ptr()) })
571 }
572
573 #[must_use]
574 pub fn is_hybrid(&self) -> bool {
575 unsafe { llama_cpp_bindings_sys::llama_model_is_hybrid(self.model.as_ptr()) }
576 }
577
578 pub fn meta_val_str(&self, key: &str) -> Result<String, MetaValError> {
581 let key_cstring = CString::new(key)?;
582 let key_ptr = key_cstring.as_ptr();
583
584 extract_meta_string(
585 |buf_ptr, buf_len| unsafe {
586 llama_cpp_bindings_sys::llama_model_meta_val_str(
587 self.model.as_ptr(),
588 key_ptr,
589 buf_ptr,
590 buf_len,
591 )
592 },
593 256,
594 )
595 }
596
597 #[must_use]
598 pub fn meta_count(&self) -> i32 {
599 unsafe { llama_cpp_bindings_sys::llama_model_meta_count(self.model.as_ptr()) }
600 }
601
602 pub fn meta_key_by_index(&self, index: i32) -> Result<String, MetaValError> {
605 extract_meta_string(
606 |buf_ptr, buf_len| unsafe {
607 llama_cpp_bindings_sys::llama_model_meta_key_by_index(
608 self.model.as_ptr(),
609 index,
610 buf_ptr,
611 buf_len,
612 )
613 },
614 256,
615 )
616 }
617
618 pub fn meta_val_str_by_index(&self, index: i32) -> Result<String, MetaValError> {
621 extract_meta_string(
622 |buf_ptr, buf_len| unsafe {
623 llama_cpp_bindings_sys::llama_model_meta_val_str_by_index(
624 self.model.as_ptr(),
625 index,
626 buf_ptr,
627 buf_len,
628 )
629 },
630 256,
631 )
632 }
633
634 #[must_use]
635 pub fn rope_type(&self) -> Option<RopeType> {
636 let raw = unsafe { llama_cpp_bindings_sys::llama_model_rope_type(self.model.as_ptr()) };
637
638 rope_type::rope_type_from_raw(raw)
639 }
640
641 pub fn chat_template(
650 &self,
651 name: Option<&str>,
652 ) -> Result<LlamaChatTemplate, ChatTemplateError> {
653 let name_cstr = name.map(CString::new);
654 let name_ptr = match name_cstr {
655 Some(Ok(name)) => name.as_ptr(),
656 _ => ptr::null(),
657 };
658 let result = unsafe {
659 llama_cpp_bindings_sys::llama_model_chat_template(self.model.as_ptr(), name_ptr)
660 };
661
662 if result.is_null() {
663 Err(ChatTemplateError::MissingTemplate)
664 } else {
665 let chat_template_cstr = unsafe { CStr::from_ptr(result) };
666
667 Ok(LlamaChatTemplate(chat_template_cstr.to_owned()))
668 }
669 }
670
671 pub fn load_from_file(
679 _: &LlamaBackend,
680 path: impl AsRef<Path>,
681 params: &LlamaModelParams,
682 ) -> Result<Self, LlamaModelLoadError> {
683 let path = path.as_ref();
684
685 let path_str = path
686 .to_str()
687 .ok_or_else(|| LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
688
689 if !path.exists() {
690 return Err(LlamaModelLoadError::FileNotFound(path.to_path_buf()));
691 }
692
693 let cstr = CString::new(path_str)?;
694 let mut out_model: *mut llama_cpp_bindings_sys::llama_model = ptr::null_mut();
695 let mut out_error: *mut c_char = ptr::null_mut();
696 let status = unsafe {
697 llama_cpp_bindings_sys::llama_rs_load_model_from_file(
698 cstr.as_ptr(),
699 params.params,
700 &raw mut out_model,
701 &raw mut out_error,
702 )
703 };
704 unsafe { load_model_from_file_status_to_result(status, out_model, out_error, path) }
705 }
706
707 pub fn lora_adapter_init(
711 &self,
712 path: impl AsRef<Path>,
713 ) -> Result<LlamaLoraAdapter, LlamaLoraAdapterInitError> {
714 let path = path.as_ref();
715
716 let path_str = path
717 .to_str()
718 .ok_or_else(|| LlamaLoraAdapterInitError::PathToStrError(path.to_path_buf()))?;
719
720 if !path.exists() {
721 return Err(LlamaLoraAdapterInitError::FileNotFound(path.to_path_buf()));
722 }
723
724 let cstr = CString::new(path_str)?;
725 let raw_adapter = unsafe {
726 llama_cpp_bindings_sys::llama_adapter_lora_init(self.model.as_ptr(), cstr.as_ptr())
727 };
728
729 let Some(adapter) = NonNull::new(raw_adapter) else {
730 return Err(LlamaLoraAdapterInitError::Unloadable);
731 };
732
733 Ok(LlamaLoraAdapter {
734 lora_adapter: adapter,
735 })
736 }
737
738 pub fn apply_chat_template(
742 &self,
743 tmpl: &LlamaChatTemplate,
744 chat: &[LlamaChatMessage],
745 add_ass: bool,
746 enable_thinking: bool,
747 ) -> Result<String, ApplyChatTemplateError> {
748 let roles: Vec<*const c_char> = chat
749 .iter()
750 .map(|chat_message| chat_message.role.as_ptr())
751 .collect();
752 let contents: Vec<*const c_char> = chat
753 .iter()
754 .map(|chat_message| chat_message.content.as_ptr())
755 .collect();
756
757 let mut out_string: *mut c_char = ptr::null_mut();
758 let mut out_error: *mut c_char = ptr::null_mut();
759
760 let status = unsafe {
761 llama_cpp_bindings_sys::llama_rs_apply_chat_template(
762 self.model.as_ptr(),
763 tmpl.0.as_ptr(),
764 roles.as_ptr(),
765 contents.as_ptr(),
766 chat.len(),
767 i32::from(add_ass),
768 i32::from(enable_thinking),
769 &raw mut out_string,
770 &raw mut out_error,
771 )
772 };
773
774 unsafe { apply_chat_template_status_to_result(status, out_string, out_error) }
775 }
776
777 pub fn sampled_token_classifier(
782 &self,
783 ) -> Result<SampledTokenClassifier<'_>, MarkerDetectionError> {
784 let markers = self.streaming_markers()?;
785
786 Ok(SampledTokenClassifier::new(self, markers))
787 }
788
789 pub fn streaming_markers(&self) -> Result<StreamingMarkers, MarkerDetectionError> {
792 let (reasoning_open_str, reasoning_close_str) =
793 invoke_detect_reasoning_markers(self.model.as_ptr())?;
794
795 let tool_call_haystack = invoke_compute_tool_call_haystack(self.model.as_ptr())?;
796
797 let autoparser_pair = tool_call_haystack.as_deref().and_then(
798 crate::extract_tool_call_markers_from_haystack::extract_tool_call_markers_from_haystack,
799 );
800
801 let (autoparser_open, autoparser_close) = match autoparser_pair {
802 Some(crate::tool_call_marker_pair::ToolCallMarkerPair { open, close }) => {
803 (Some(open), Some(close))
804 }
805 None => (None, None),
806 };
807
808 let resolved_tool_call_markers =
809 self.resolve_tool_call_marker_strings(autoparser_open, autoparser_close)?;
810
811 Ok(StreamingMarkers {
812 reasoning_open: self.tokenize_marker(reasoning_open_str.as_deref())?,
813 reasoning_close: self.tokenize_marker(reasoning_close_str.as_deref())?,
814 tool_call_open: self.tokenize_marker(resolved_tool_call_markers.open.as_deref())?,
815 tool_call_close: self.tokenize_marker(resolved_tool_call_markers.close.as_deref())?,
816 })
817 }
818
819 fn resolve_tool_call_marker_strings(
820 &self,
821 autoparser_open: Option<String>,
822 autoparser_close: Option<String>,
823 ) -> Result<ResolvedToolCallMarkers, MarkerDetectionError> {
824 if autoparser_open
825 .as_deref()
826 .is_some_and(|raw| !raw.trim().is_empty())
827 {
828 return Ok(ResolvedToolCallMarkers {
829 open: autoparser_open,
830 close: autoparser_close,
831 });
832 }
833 let Some(markers) = self.tool_call_markers()? else {
834 return Ok(ResolvedToolCallMarkers {
835 open: autoparser_open,
836 close: autoparser_close,
837 });
838 };
839 let close = if markers.close.is_empty() {
840 None
841 } else {
842 Some(markers.close)
843 };
844 Ok(ResolvedToolCallMarkers {
845 open: Some(markers.open),
846 close,
847 })
848 }
849
850 pub fn reasoning_markers(&self) -> Result<Option<ReasoningMarkers>, MarkerDetectionError> {
853 let (open, close) = invoke_detect_reasoning_markers(self.model.as_ptr())?;
854
855 if let Some(markers) = reasoning_markers_from_marker_pair(open, close) {
856 return Ok(Some(markers));
857 }
858
859 detect_reasoning_markers_via_template_probe(self.model.as_ptr())
860 }
861
862 pub fn tool_call_markers(&self) -> Result<Option<ToolCallMarkers>, MarkerDetectionError> {
867 let template = match self.chat_template(None) {
868 Ok(template) => template,
869 Err(ChatTemplateError::MissingTemplate) => return Ok(None),
870 Err(other) => return Err(MarkerDetectionError::ChatTemplateUnavailable(other)),
871 };
872 let template_str = template.to_str()?;
873
874 Ok(tool_call_template_overrides::detect(template_str))
875 }
876
877 fn tokenize_marker(
881 &self,
882 marker: Option<&str>,
883 ) -> Result<Option<Vec<LlamaToken>>, StringToTokenError> {
884 let Some(marker) = marker else {
885 return Ok(None);
886 };
887 let marker = marker.trim();
888 if marker.is_empty() {
889 return Ok(None);
890 }
891 let tokens = self.str_to_token(marker, AddBos::Never)?;
892 if tokens.is_empty() {
893 Ok(None)
894 } else {
895 Ok(Some(tokens))
896 }
897 }
898
899 pub fn parse_chat_message(
905 &self,
906 tools_json: &str,
907 input: &str,
908 is_partial: bool,
909 ) -> Result<ChatMessageParseOutcome, ParseChatMessageError> {
910 let tools_value: serde_json::Value =
911 serde_json::from_str(tools_json).map_err(ParseChatMessageError::ToolsJsonInvalid)?;
912 if !tools_value.is_array() {
913 return Err(ParseChatMessageError::ToolsJsonNotArray);
914 }
915
916 let reasoning_markers = self.reasoning_markers()?;
917
918 for candidate in tool_call_template_overrides::known_marker_candidates() {
919 if let ToolCallFormatOutcome::Parsed(calls) =
920 tool_call_format::try_parse(input, &candidate)
921 {
922 let split =
923 split_reasoning_prefix(input, reasoning_markers.as_ref(), &candidate.open);
924 let mut parsed = ParsedChatMessage::new(split.content, split.reasoning, calls);
925 synthesize_missing_tool_call_ids(&mut parsed.tool_calls);
926 return Ok(ChatMessageParseOutcome::Recognized(parsed));
927 }
928 }
929
930 let via_ffi_result = self.parse_chat_message_via_ffi(tools_json, input, is_partial);
931
932 outcome_from_via_ffi_result(via_ffi_result, tools_json, input, is_partial)
933 }
934
935 fn parse_chat_message_via_ffi(
936 &self,
937 tools_json: &str,
938 input: &str,
939 is_partial: bool,
940 ) -> Result<ParsedChatMessage, ParseChatMessageError> {
941 let parser = self.chat_parser()?;
942
943 let tools_cstring = CString::new(tools_json)
944 .map_err(|err| ParseChatMessageError::ToolsSerialization(err.to_string()))?;
945 let input_cstring = CString::new(input)
946 .map_err(|err| ParseChatMessageError::ToolsSerialization(err.to_string()))?;
947
948 let mut handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat = ptr::null_mut();
949 let mut out_error: *mut c_char = ptr::null_mut();
950
951 let status = unsafe {
952 llama_cpp_bindings_sys::llama_rs_parse_chat_message(
953 parser.parser.as_ptr(),
954 tools_cstring.as_ptr(),
955 input_cstring.as_ptr(),
956 i32::from(is_partial),
957 &raw mut handle,
958 &raw mut out_error,
959 )
960 };
961
962 let parsed =
963 unsafe { parse_chat_message_status_to_result(status, handle, &raw mut out_error) };
964
965 let mut free_error: *mut c_char = ptr::null_mut();
966 let free_status = unsafe {
967 llama_cpp_bindings_sys::llama_rs_parsed_chat_free(handle, &raw mut free_error)
968 };
969 unsafe { parsed_chat_free_status_to_result(parsed, free_status, out_error, free_error) }
970 }
971
972 fn chat_parser(&self) -> Result<&ChatParserHandle, ParseChatMessageError> {
973 if let Some(parser) = self.chat_parser.get() {
974 return Ok(parser);
975 }
976 let parser = self.create_chat_parser()?;
977 Ok(self.chat_parser.get_or_init(|| parser))
978 }
979
980 fn create_chat_parser(&self) -> Result<ChatParserHandle, ParseChatMessageError> {
981 let probe_markers = detect_reasoning_markers_via_template_probe(self.model.as_ptr())?;
982
983 let reasoning_open = probe_markers.as_ref().map(|markers| unsafe {
986 CString::from_vec_unchecked(markers.open.as_bytes().to_vec())
987 });
988 let reasoning_close = probe_markers.as_ref().map(|markers| unsafe {
989 CString::from_vec_unchecked(markers.close.as_bytes().to_vec())
990 });
991 let reasoning_open_ptr = reasoning_open
992 .as_ref()
993 .map_or(ptr::null(), |value| value.as_ptr());
994 let reasoning_close_ptr = reasoning_close
995 .as_ref()
996 .map_or(ptr::null(), |value| value.as_ptr());
997
998 let mut out_parser: *mut llama_cpp_bindings_sys::llama_rs_chat_parser = ptr::null_mut();
999 let mut out_error: *mut c_char = ptr::null_mut();
1000
1001 let status = unsafe {
1002 llama_cpp_bindings_sys::llama_rs_chat_parser_create(
1003 self.model.as_ptr(),
1004 reasoning_open_ptr,
1005 reasoning_close_ptr,
1006 &raw mut out_parser,
1007 &raw mut out_error,
1008 )
1009 };
1010
1011 unsafe { chat_parser_create_status_to_result(status, out_parser, &raw mut out_error) }
1012 }
1013
1014 pub fn diagnose_tool_call_synthetic_renders(
1019 &self,
1020 ) -> Result<(String, String), MarkerDetectionError> {
1021 let (no_tools, with_tools) =
1022 invoke_diagnose_tool_call_synthetic_renders(self.model.as_ptr())?;
1023
1024 Ok((no_tools.unwrap_or_default(), with_tools.unwrap_or_default()))
1025 }
1026}
1027
1028impl LlamaModel {
1029 pub fn approximate_tok_env(&self) -> Result<Arc<ApproximateTokEnv>, TokenToStringError> {
1035 if let Some(env) = self.tok_env.get() {
1036 return Ok(Arc::clone(env));
1037 }
1038 let env = build_approximate_tok_env(self)?;
1039 Ok(Arc::clone(self.tok_env.get_or_init(|| env)))
1040 }
1041}
1042
1043const TOK_ENV_PIECE_PROBE_SIZE: usize = 32;
1044
1045fn token_piece_bytes_for_tok_env(
1046 model: &LlamaModel,
1047 token: LlamaToken,
1048 special: bool,
1049) -> Result<Vec<u8>, TokenToStringError> {
1050 match model.token_to_piece_bytes(token, TOK_ENV_PIECE_PROBE_SIZE, special, None) {
1051 Ok(bytes) => Ok(bytes),
1052 Err(TokenToStringError::UnknownTokenType) => Ok(Vec::new()),
1053 Err(TokenToStringError::InsufficientBufferSpace(required)) => {
1054 model.token_to_piece_bytes(token, required.unsigned_abs() as usize, special, None)
1055 }
1056 Err(other) => Err(other),
1057 }
1058}
1059
1060fn build_approximate_tok_env(
1061 model: &LlamaModel,
1062) -> Result<Arc<ApproximateTokEnv>, TokenToStringError> {
1063 let n_vocab = model.n_vocab().cast_unsigned();
1064 let tok_eos = {
1065 let eot = unsafe { llama_cpp_bindings_sys::llama_vocab_eot(model.vocab_ptr()) };
1066 if eot == -1 {
1067 model.token_eos().0.cast_unsigned()
1068 } else {
1069 eot.cast_unsigned()
1070 }
1071 };
1072 let info = TokRxInfo::new(n_vocab, tok_eos);
1073
1074 let mut words = Vec::with_capacity(n_vocab as usize);
1075
1076 for token_id in 0..n_vocab.cast_signed() {
1077 let token = LlamaToken(token_id);
1078 let bytes = token_piece_bytes_for_tok_env(model, token, false)?;
1079 if bytes.is_empty() {
1080 let special_bytes = token_piece_bytes_for_tok_env(model, token, true)?;
1081 if special_bytes.is_empty() {
1082 words.push(vec![]);
1083 } else {
1084 let mut marked = Vec::with_capacity(special_bytes.len() + 1);
1085 marked.push(0xFF);
1086 marked.extend(special_bytes);
1087 words.push(marked);
1088 }
1089 } else {
1090 words.push(bytes);
1091 }
1092 }
1093
1094 let trie = TokTrie::from(&info, &words);
1095 Ok(Arc::new(ApproximateTokEnv::new(trie)))
1096}
1097
1098fn collect_parsed_chat_message(
1099 handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat,
1100) -> Result<ParsedChatMessage, ParseChatMessageError> {
1101 if handle.is_null() {
1102 return Ok(ParsedChatMessage::default());
1103 }
1104
1105 let content = read_parsed_chat_content(handle)?;
1106 let reasoning_content = read_parsed_chat_reasoning_content(handle)?;
1107 let count = read_parsed_chat_tool_call_count(handle)?;
1108
1109 let mut tool_calls = Vec::with_capacity(count);
1110 for index in 0..count {
1111 let id = read_parsed_chat_tool_call_id(handle, index)?;
1112 let name = read_parsed_chat_tool_call_name(handle, index)?;
1113 let arguments_json = read_parsed_chat_tool_call_arguments(handle, index)?;
1114
1115 let arguments = ToolCallArguments::from_string(arguments_json);
1116 tool_calls.push(ParsedToolCall::new(id, name, arguments));
1117 }
1118
1119 Ok(ParsedChatMessage::new(
1120 content,
1121 reasoning_content,
1122 tool_calls,
1123 ))
1124}
1125
1126unsafe fn parsed_chat_content_status_to_result(
1130 status: llama_cpp_bindings_sys::llama_rs_parsed_chat_content_status,
1131 out_string: *mut c_char,
1132 out_error: *mut c_char,
1133) -> Result<String, ParseChatMessageError> {
1134 match status {
1135 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_OK => {
1136 consume_accessor_string(out_string)
1137 }
1138 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_ERROR_STRING_ALLOCATION_FAILED => {
1139 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1140 Err(ParseChatMessageError::NotEnoughMemory)
1141 }
1142 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_VENDORED_THREW_CXX_EXCEPTION => {
1143 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1144 Err(ParseChatMessageError::Reported { message })
1145 }
1146 other => unreachable!("llama_rs_parsed_chat_content returned unrecognized status {other}"),
1147 }
1148}
1149
1150fn read_parsed_chat_content(
1151 handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat,
1152) -> Result<String, ParseChatMessageError> {
1153 let mut out_string: *mut c_char = ptr::null_mut();
1154 let mut out_error: *mut c_char = ptr::null_mut();
1155 let status = unsafe {
1156 llama_cpp_bindings_sys::llama_rs_parsed_chat_content(
1157 handle,
1158 &raw mut out_string,
1159 &raw mut out_error,
1160 )
1161 };
1162 unsafe { parsed_chat_content_status_to_result(status, out_string, out_error) }
1163}
1164
1165unsafe fn parsed_chat_reasoning_content_status_to_result(
1169 status: llama_cpp_bindings_sys::llama_rs_parsed_chat_reasoning_content_status,
1170 out_string: *mut c_char,
1171 out_error: *mut c_char,
1172) -> Result<String, ParseChatMessageError> {
1173 match status {
1174 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_OK => {
1175 consume_accessor_string(out_string)
1176 }
1177 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_ERROR_STRING_ALLOCATION_FAILED => {
1178 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1179 Err(ParseChatMessageError::NotEnoughMemory)
1180 }
1181 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_VENDORED_THREW_CXX_EXCEPTION => {
1182 let message =
1183 unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1184 Err(ParseChatMessageError::Reported { message })
1185 }
1186 other => unreachable!(
1187 "llama_rs_parsed_chat_reasoning_content returned unrecognized status {other}"
1188 ),
1189 }
1190}
1191
1192fn read_parsed_chat_reasoning_content(
1193 handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat,
1194) -> Result<String, ParseChatMessageError> {
1195 let mut out_string: *mut c_char = ptr::null_mut();
1196 let mut out_error: *mut c_char = ptr::null_mut();
1197 let status = unsafe {
1198 llama_cpp_bindings_sys::llama_rs_parsed_chat_reasoning_content(
1199 handle,
1200 &raw mut out_string,
1201 &raw mut out_error,
1202 )
1203 };
1204 unsafe { parsed_chat_reasoning_content_status_to_result(status, out_string, out_error) }
1205}
1206
1207unsafe fn parsed_chat_tool_call_count_status_to_result(
1211 status: llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_count_status,
1212 out_count: usize,
1213 out_error: *mut c_char,
1214) -> Result<usize, ParseChatMessageError> {
1215 match status {
1216 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_OK => Ok(out_count),
1217 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_ERROR_STRING_ALLOCATION_FAILED => {
1218 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1219 Err(ParseChatMessageError::NotEnoughMemory)
1220 }
1221 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_VENDORED_THREW_CXX_EXCEPTION => {
1222 let message =
1223 unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1224 Err(ParseChatMessageError::Reported { message })
1225 }
1226 other => unreachable!(
1227 "llama_rs_parsed_chat_tool_call_count returned unrecognized status {other}"
1228 ),
1229 }
1230}
1231
1232fn read_parsed_chat_tool_call_count(
1233 handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat,
1234) -> Result<usize, ParseChatMessageError> {
1235 let mut out_count: usize = 0;
1236 let mut out_error: *mut c_char = ptr::null_mut();
1237 let status = unsafe {
1238 llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_count(
1239 handle,
1240 &raw mut out_count,
1241 &raw mut out_error,
1242 )
1243 };
1244 unsafe { parsed_chat_tool_call_count_status_to_result(status, out_count, out_error) }
1245}
1246
1247unsafe fn parsed_chat_tool_call_id_status_to_result(
1251 status: llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_id_status,
1252 index: usize,
1253 out_string: *mut c_char,
1254 out_error: *mut c_char,
1255) -> Result<String, ParseChatMessageError> {
1256 match status {
1257 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_OK => {
1258 consume_accessor_string(out_string)
1259 }
1260 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_INDEX_OUT_OF_BOUNDS => {
1261 Err(ParseChatMessageError::ToolCallIdIndexOutOfBounds { index })
1262 }
1263 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_ERROR_STRING_ALLOCATION_FAILED => {
1264 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1265 Err(ParseChatMessageError::NotEnoughMemory)
1266 }
1267 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_VENDORED_THREW_CXX_EXCEPTION => {
1268 let message =
1269 unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1270 Err(ParseChatMessageError::Reported { message })
1271 }
1272 other => unreachable!(
1273 "llama_rs_parsed_chat_tool_call_id returned unrecognized status {other}"
1274 ),
1275 }
1276}
1277
1278fn read_parsed_chat_tool_call_id(
1279 handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat,
1280 index: usize,
1281) -> Result<String, ParseChatMessageError> {
1282 let mut out_string: *mut c_char = ptr::null_mut();
1283 let mut out_error: *mut c_char = ptr::null_mut();
1284 let status = unsafe {
1285 llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_id(
1286 handle,
1287 index,
1288 &raw mut out_string,
1289 &raw mut out_error,
1290 )
1291 };
1292 unsafe { parsed_chat_tool_call_id_status_to_result(status, index, out_string, out_error) }
1293}
1294
1295unsafe fn parsed_chat_tool_call_name_status_to_result(
1299 status: llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_name_status,
1300 index: usize,
1301 out_string: *mut c_char,
1302 out_error: *mut c_char,
1303) -> Result<String, ParseChatMessageError> {
1304 match status {
1305 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_OK => {
1306 consume_accessor_string(out_string)
1307 }
1308 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_INDEX_OUT_OF_BOUNDS => {
1309 Err(ParseChatMessageError::ToolCallNameIndexOutOfBounds { index })
1310 }
1311 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_ERROR_STRING_ALLOCATION_FAILED => {
1312 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1313 Err(ParseChatMessageError::NotEnoughMemory)
1314 }
1315 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_VENDORED_THREW_CXX_EXCEPTION => {
1316 let message =
1317 unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1318 Err(ParseChatMessageError::Reported { message })
1319 }
1320 other => unreachable!(
1321 "llama_rs_parsed_chat_tool_call_name returned unrecognized status {other}"
1322 ),
1323 }
1324}
1325
1326fn read_parsed_chat_tool_call_name(
1327 handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat,
1328 index: usize,
1329) -> Result<String, ParseChatMessageError> {
1330 let mut out_string: *mut c_char = ptr::null_mut();
1331 let mut out_error: *mut c_char = ptr::null_mut();
1332 let status = unsafe {
1333 llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_name(
1334 handle,
1335 index,
1336 &raw mut out_string,
1337 &raw mut out_error,
1338 )
1339 };
1340 unsafe { parsed_chat_tool_call_name_status_to_result(status, index, out_string, out_error) }
1341}
1342
1343unsafe fn parsed_chat_tool_call_arguments_status_to_result(
1347 status: llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_arguments_status,
1348 index: usize,
1349 out_string: *mut c_char,
1350 out_error: *mut c_char,
1351) -> Result<String, ParseChatMessageError> {
1352 match status {
1353 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_OK => {
1354 consume_accessor_string(out_string)
1355 }
1356 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_INDEX_OUT_OF_BOUNDS => {
1357 Err(ParseChatMessageError::ToolCallArgumentsIndexOutOfBounds { index })
1358 }
1359 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_ERROR_STRING_ALLOCATION_FAILED => {
1360 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1361 Err(ParseChatMessageError::NotEnoughMemory)
1362 }
1363 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_VENDORED_THREW_CXX_EXCEPTION => {
1364 let message =
1365 unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1366 Err(ParseChatMessageError::Reported { message })
1367 }
1368 other => unreachable!(
1369 "llama_rs_parsed_chat_tool_call_arguments returned unrecognized status {other}"
1370 ),
1371 }
1372}
1373
1374fn read_parsed_chat_tool_call_arguments(
1375 handle: *mut llama_cpp_bindings_sys::llama_rs_parsed_chat,
1376 index: usize,
1377) -> Result<String, ParseChatMessageError> {
1378 let mut out_string: *mut c_char = ptr::null_mut();
1379 let mut out_error: *mut c_char = ptr::null_mut();
1380 let status = unsafe {
1381 llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_arguments(
1382 handle,
1383 index,
1384 &raw mut out_string,
1385 &raw mut out_error,
1386 )
1387 };
1388 unsafe {
1389 parsed_chat_tool_call_arguments_status_to_result(status, index, out_string, out_error)
1390 }
1391}
1392
1393fn consume_accessor_string(ptr: *mut c_char) -> Result<String, ParseChatMessageError> {
1394 if ptr.is_null() {
1395 return Ok(String::new());
1396 }
1397 let bytes = unsafe { CStr::from_ptr(ptr) }.to_bytes().to_vec();
1398 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(ptr) };
1399 Ok(String::from_utf8(bytes)?)
1400}
1401
1402struct ReasoningSplit {
1403 reasoning: String,
1404 content: String,
1405}
1406
1407fn split_reasoning_prefix(
1408 input: &str,
1409 reasoning_markers: Option<&ReasoningMarkers>,
1410 tool_call_open: &str,
1411) -> ReasoningSplit {
1412 let content_only = || ReasoningSplit {
1413 reasoning: String::new(),
1414 content: prefix_before(input, tool_call_open),
1415 };
1416
1417 let Some(reasoning_markers) = reasoning_markers else {
1418 return content_only();
1419 };
1420 let Some(open_pos) = input.find(&reasoning_markers.open) else {
1421 return content_only();
1422 };
1423
1424 let after_open = &input[open_pos + reasoning_markers.open.len()..];
1425 let Some(close_offset) = after_open.find(&reasoning_markers.close) else {
1426 return content_only();
1427 };
1428
1429 let reasoning = after_open[..close_offset].to_owned();
1430 let after_close = &after_open[close_offset + reasoning_markers.close.len()..];
1431
1432 ReasoningSplit {
1433 reasoning,
1434 content: prefix_before(after_close, tool_call_open),
1435 }
1436}
1437
1438fn prefix_before(text: &str, marker: &str) -> String {
1439 text.find(marker)
1440 .map_or_else(|| text.to_owned(), |pos| text[..pos].to_owned())
1441}
1442
1443fn synthesize_missing_tool_call_ids(tool_calls: &mut [ParsedToolCall]) {
1444 for (index, call) in tool_calls.iter_mut().enumerate() {
1445 if call.id.is_empty() {
1446 call.id = format!("call_{index}");
1447 }
1448 }
1449}
1450
1451unsafe fn detect_reasoning_markers_status_to_result(
1456 status: llama_cpp_bindings_sys::llama_rs_detect_reasoning_markers_status,
1457 out_open: *const c_char,
1458 out_close: *const c_char,
1459 out_error: *mut c_char,
1460) -> Result<(Option<String>, Option<String>), MarkerDetectionError> {
1461 match status {
1462 llama_cpp_bindings_sys::LLAMA_RS_DETECT_REASONING_MARKERS_OK => {
1463 collect_optional_cstr_pair(out_open, out_close)
1464 }
1465 llama_cpp_bindings_sys::LLAMA_RS_DETECT_REASONING_MARKERS_ERROR_STRING_ALLOCATION_FAILED => {
1466 Err(MarkerDetectionError::NotEnoughMemory)
1467 }
1468 llama_cpp_bindings_sys::LLAMA_RS_DETECT_REASONING_MARKERS_VENDORED_THREW_CXX_EXCEPTION => {
1469 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1470 Err(MarkerDetectionError::ReasoningMarkerDetectionFailed { message })
1471 }
1472 other => unreachable!(
1473 "llama_rs_detect_reasoning_markers returned unrecognized status {other}"
1474 ),
1475 }
1476}
1477
1478const fn cxx_exception_owns_out_error<TValue>(
1479 parsed: &Result<TValue, MarkerDetectionError>,
1480) -> bool {
1481 matches!(
1482 parsed,
1483 Err(MarkerDetectionError::ReasoningMarkerDetectionFailed { .. }
1484 | MarkerDetectionError::ToolCallHaystackComputationFailed { .. }
1485 | MarkerDetectionError::ToolCallSyntheticRenderDiagnosisFailed { .. })
1486 )
1487}
1488
1489fn invoke_detect_reasoning_markers(
1490 model: *const llama_cpp_bindings_sys::llama_model,
1491) -> Result<(Option<String>, Option<String>), MarkerDetectionError> {
1492 let mut out_open: *mut c_char = ptr::null_mut();
1493 let mut out_close: *mut c_char = ptr::null_mut();
1494 let mut out_error: *mut c_char = ptr::null_mut();
1495
1496 let status = unsafe {
1497 llama_cpp_bindings_sys::llama_rs_detect_reasoning_markers(
1498 model,
1499 &raw mut out_open,
1500 &raw mut out_close,
1501 &raw mut out_error,
1502 )
1503 };
1504
1505 let parsed = unsafe {
1506 detect_reasoning_markers_status_to_result(status, out_open, out_close, out_error)
1507 };
1508
1509 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_open) };
1510 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_close) };
1511 if !cxx_exception_owns_out_error(&parsed) {
1512 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1513 }
1514
1515 parsed
1516}
1517
1518unsafe fn render_chat_template_status_to_result(
1523 status: llama_cpp_bindings_sys::llama_rs_render_chat_template_status,
1524 out_rendered: *const c_char,
1525 out_error: *mut c_char,
1526) -> Result<Option<String>, MarkerDetectionError> {
1527 match status {
1528 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_OK => {
1529 read_optional_owned_cstr(out_rendered)
1530 }
1531 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_MODEL_HAS_NO_CHAT_TEMPLATE
1532 | llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_MODEL_HAS_NO_VOCAB => Ok(None),
1533 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_ERROR_STRING_ALLOCATION_FAILED => {
1534 Err(MarkerDetectionError::NotEnoughMemory)
1535 }
1536 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_VENDORED_THREW_CXX_EXCEPTION => {
1537 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1538 Err(MarkerDetectionError::ReasoningMarkerDetectionFailed { message })
1539 }
1540 other => {
1541 unreachable!("llama_rs_render_chat_template returned unrecognized status {other}")
1542 }
1543 }
1544}
1545
1546fn render_chat_template(
1547 model: *const llama_cpp_bindings_sys::llama_model,
1548 messages_json: &str,
1549) -> Result<Option<String>, MarkerDetectionError> {
1550 let messages = unsafe { CString::from_vec_unchecked(messages_json.as_bytes().to_vec()) };
1553 let mut out_rendered: *mut c_char = ptr::null_mut();
1554 let mut out_error: *mut c_char = ptr::null_mut();
1555
1556 let status = unsafe {
1557 llama_cpp_bindings_sys::llama_rs_render_chat_template(
1558 model,
1559 messages.as_ptr(),
1560 0,
1561 1,
1562 &raw mut out_rendered,
1563 &raw mut out_error,
1564 )
1565 };
1566
1567 let parsed = unsafe { render_chat_template_status_to_result(status, out_rendered, out_error) };
1568
1569 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_rendered) };
1570 if !cxx_exception_owns_out_error(&parsed) {
1571 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1572 }
1573
1574 parsed
1575}
1576
1577fn render_probe_messages(
1583 model: *const llama_cpp_bindings_sys::llama_model,
1584 messages_json: &str,
1585) -> Result<Option<String>, MarkerDetectionError> {
1586 match render_chat_template(model, messages_json) {
1587 Ok(rendered) => Ok(rendered),
1588 Err(MarkerDetectionError::ReasoningMarkerDetectionFailed { .. }) => Ok(None),
1589 Err(other) => Err(other),
1590 }
1591}
1592
1593fn detect_reasoning_markers_via_template_probe(
1594 model: *const llama_cpp_bindings_sys::llama_model,
1595) -> Result<Option<ReasoningMarkers>, MarkerDetectionError> {
1596 use crate::extract_reasoning_markers_from_probe_renders::chunked_probe_messages_json;
1597 use crate::extract_reasoning_markers_from_probe_renders::extract_reasoning_markers_from_probe_renders;
1598 use crate::extract_reasoning_markers_from_probe_renders::plain_probe_messages_json;
1599
1600 let Some(plain_render) = render_probe_messages(model, &plain_probe_messages_json())? else {
1601 return Ok(None);
1602 };
1603 let Some(chunked_render) = render_probe_messages(model, &chunked_probe_messages_json())? else {
1604 return Ok(None);
1605 };
1606
1607 Ok(extract_reasoning_markers_from_probe_renders(
1608 &plain_render,
1609 &chunked_render,
1610 ))
1611}
1612
1613unsafe fn compute_tool_call_haystack_status_to_result(
1618 status: llama_cpp_bindings_sys::llama_rs_compute_tool_call_haystack_status,
1619 out_haystack: *const c_char,
1620 out_error: *mut c_char,
1621) -> Result<Option<String>, MarkerDetectionError> {
1622 match status {
1623 llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_OK => {
1624 read_optional_owned_cstr(out_haystack)
1625 }
1626 llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_ERROR_STRING_ALLOCATION_FAILED => {
1627 Err(MarkerDetectionError::NotEnoughMemory)
1628 }
1629 llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_VENDORED_THREW_CXX_EXCEPTION => {
1630 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1631 Err(MarkerDetectionError::ToolCallHaystackComputationFailed { message })
1632 }
1633 other => unreachable!(
1634 "llama_rs_compute_tool_call_haystack returned unrecognized status {other}"
1635 ),
1636 }
1637}
1638
1639fn invoke_compute_tool_call_haystack(
1640 model: *const llama_cpp_bindings_sys::llama_model,
1641) -> Result<Option<String>, MarkerDetectionError> {
1642 let mut out_haystack: *mut c_char = ptr::null_mut();
1643 let mut out_error: *mut c_char = ptr::null_mut();
1644
1645 let status = unsafe {
1646 llama_cpp_bindings_sys::llama_rs_compute_tool_call_haystack(
1647 model,
1648 &raw mut out_haystack,
1649 &raw mut out_error,
1650 )
1651 };
1652
1653 let parsed =
1654 unsafe { compute_tool_call_haystack_status_to_result(status, out_haystack, out_error) };
1655
1656 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_haystack) };
1657 if !cxx_exception_owns_out_error(&parsed) {
1658 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1659 }
1660
1661 parsed
1662}
1663
1664unsafe fn diagnose_tool_call_synthetic_renders_status_to_result(
1669 status: llama_cpp_bindings_sys::llama_rs_diagnose_tool_call_synthetic_renders_status,
1670 out_no_tools: *const c_char,
1671 out_with_tools: *const c_char,
1672 out_error: *mut c_char,
1673) -> Result<(Option<String>, Option<String>), MarkerDetectionError> {
1674 match status {
1675 llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_OK => {
1676 collect_optional_cstr_pair(out_no_tools, out_with_tools)
1677 }
1678 llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_ERROR_STRING_ALLOCATION_FAILED => {
1679 Err(MarkerDetectionError::NotEnoughMemory)
1680 }
1681 llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_VENDORED_THREW_CXX_EXCEPTION => {
1682 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1683 Err(MarkerDetectionError::ToolCallSyntheticRenderDiagnosisFailed { message })
1684 }
1685 other => unreachable!(
1686 "llama_rs_diagnose_tool_call_synthetic_renders returned unrecognized status {other}"
1687 ),
1688 }
1689}
1690
1691fn invoke_diagnose_tool_call_synthetic_renders(
1692 model: *const llama_cpp_bindings_sys::llama_model,
1693) -> Result<(Option<String>, Option<String>), MarkerDetectionError> {
1694 let mut out_no_tools: *mut c_char = ptr::null_mut();
1695 let mut out_with_tools: *mut c_char = ptr::null_mut();
1696 let mut out_error: *mut c_char = ptr::null_mut();
1697
1698 let status = unsafe {
1699 llama_cpp_bindings_sys::llama_rs_diagnose_tool_call_synthetic_renders(
1700 model,
1701 &raw mut out_no_tools,
1702 &raw mut out_with_tools,
1703 &raw mut out_error,
1704 )
1705 };
1706
1707 let parsed = unsafe {
1708 diagnose_tool_call_synthetic_renders_status_to_result(
1709 status,
1710 out_no_tools,
1711 out_with_tools,
1712 out_error,
1713 )
1714 };
1715
1716 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_no_tools) };
1717 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_with_tools) };
1718 if !cxx_exception_owns_out_error(&parsed) {
1719 unsafe { llama_cpp_bindings_sys::llama_rs_string_free(out_error) };
1720 }
1721
1722 parsed
1723}
1724
1725fn read_optional_owned_cstr(ptr: *const c_char) -> Result<Option<String>, MarkerDetectionError> {
1726 if ptr.is_null() {
1727 return Ok(None);
1728 }
1729
1730 let bytes = unsafe { CStr::from_ptr(ptr) }.to_bytes().to_vec();
1731
1732 Ok(Some(String::from_utf8(bytes)?))
1733}
1734
1735unsafe fn tokenize_status_to_result(
1739 status: llama_cpp_bindings_sys::llama_rs_tokenize_status,
1740 out_count: c_int,
1741 out_error: *mut c_char,
1742) -> Result<c_int, StringToTokenError> {
1743 match status {
1744 llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_OK => Ok(out_count),
1745 llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED => {
1746 Err(StringToTokenError::NotEnoughMemory)
1747 }
1748 llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_VENDORED_THREW_CXX_EXCEPTION => {
1749 let message = unsafe { crate::ffi_error_reader::read_and_free_cpp_error(out_error) };
1750 Err(StringToTokenError::Reported { message })
1751 }
1752 other => unreachable!("llama_rs_tokenize returned unrecognized status {other}"),
1753 }
1754}
1755
1756fn invoke_rs_tokenize(
1757 vocab: *const llama_cpp_bindings_sys::llama_vocab,
1758 text: *const c_char,
1759 text_len: c_int,
1760 tokens: *mut llama_cpp_bindings_sys::llama_token,
1761 n_tokens_max: c_int,
1762 add_bos: bool,
1763) -> Result<c_int, StringToTokenError> {
1764 let mut out_count: i32 = 0;
1765 let mut out_error: *mut c_char = ptr::null_mut();
1766 let status = unsafe {
1767 llama_cpp_bindings_sys::llama_rs_tokenize(
1768 vocab,
1769 text,
1770 text_len,
1771 tokens,
1772 n_tokens_max,
1773 add_bos,
1774 true,
1775 &raw mut out_count,
1776 &raw mut out_error,
1777 )
1778 };
1779 unsafe { tokenize_status_to_result(status, out_count, out_error) }
1780}
1781
1782fn checked_token_buffer_capacity(capacity: usize) -> Result<c_int, StringToTokenError> {
1783 Ok(c_int::try_from(capacity)?)
1784}
1785
1786fn checked_token_count(size: i32) -> Result<usize, StringToTokenError> {
1787 Ok(usize::try_from(size)?)
1788}
1789
1790fn tokenize_into_buffer(
1791 estimated_capacity: usize,
1792 invoke: impl Fn(
1793 *mut llama_cpp_bindings_sys::llama_token,
1794 c_int,
1795 ) -> Result<c_int, StringToTokenError>,
1796) -> Result<Vec<LlamaToken>, StringToTokenError> {
1797 let mut buffer: Vec<LlamaToken> = Vec::with_capacity(estimated_capacity);
1798 let buffer_capacity = checked_token_buffer_capacity(buffer.capacity())?;
1799
1800 let size = invoke(
1801 buffer
1802 .as_mut_ptr()
1803 .cast::<llama_cpp_bindings_sys::llama_token>(),
1804 buffer_capacity,
1805 )?;
1806
1807 let size = if size.is_negative() {
1808 buffer.reserve_exact(checked_token_count(-size)?);
1809 invoke(
1810 buffer
1811 .as_mut_ptr()
1812 .cast::<llama_cpp_bindings_sys::llama_token>(),
1813 -size,
1814 )?
1815 } else {
1816 size
1817 };
1818
1819 let size = checked_token_count(size)?;
1820
1821 unsafe { buffer.set_len(size) }
1823
1824 Ok(buffer)
1825}
1826
1827fn collect_optional_cstr_pair(
1828 first_ptr: *const c_char,
1829 second_ptr: *const c_char,
1830) -> Result<(Option<String>, Option<String>), MarkerDetectionError> {
1831 let first = read_optional_owned_cstr(first_ptr)?;
1832 let second = read_optional_owned_cstr(second_ptr)?;
1833 Ok((first, second))
1834}
1835
1836fn extract_meta_string<TCFunction>(
1837 c_function: TCFunction,
1838 capacity: usize,
1839) -> Result<String, MetaValError>
1840where
1841 TCFunction: Fn(*mut c_char, usize) -> i32,
1842{
1843 let mut buffer = vec![0u8; capacity];
1844 let result = c_function(buffer.as_mut_ptr().cast::<c_char>(), buffer.len());
1845
1846 if result < 0 {
1847 return Err(MetaValError::NegativeReturn(result));
1848 }
1849
1850 let returned_len = result.cast_unsigned() as usize;
1851
1852 if returned_len >= capacity {
1853 return extract_meta_string(c_function, returned_len + 1);
1854 }
1855
1856 if buffer.get(returned_len) != Some(&0) {
1857 return Err(MetaValError::NegativeReturn(-1));
1858 }
1859
1860 buffer.truncate(returned_len);
1861
1862 Ok(String::from_utf8(buffer)?)
1863}
1864
1865impl Drop for LlamaModel {
1866 fn drop(&mut self) {
1867 unsafe { llama_cpp_bindings_sys::llama_free_model(self.model.as_ptr()) }
1868 }
1869}
1870
1871#[cfg(test)]
1872mod extract_meta_string_tests {
1873 use super::extract_meta_string;
1874 use crate::MetaValError;
1875
1876 #[test]
1877 fn returns_error_when_null_terminator_missing() {
1878 let result = extract_meta_string(
1879 |buf_ptr, buf_len| {
1880 let buffer =
1881 unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::<u8>(), buf_len) };
1882 buffer[0] = b'a';
1883 buffer[1] = b'b';
1884 buffer[2] = b'c';
1885 2
1886 },
1887 4,
1888 );
1889
1890 assert_eq!(result.unwrap_err(), MetaValError::NegativeReturn(-1));
1891 }
1892
1893 #[test]
1894 fn returns_error_for_negative_return_value() {
1895 let result = extract_meta_string(|_buf_ptr, _buf_len| -5, 4);
1896
1897 assert_eq!(result.unwrap_err(), MetaValError::NegativeReturn(-5));
1898 }
1899
1900 #[test]
1901 fn returns_error_for_invalid_utf8_data() {
1902 let result = extract_meta_string(
1903 |buf_ptr, buf_len| {
1904 let buffer =
1905 unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::<u8>(), buf_len) };
1906 buffer[0] = 0xFF;
1907 buffer[1] = 0xFE;
1908 buffer[2] = 0;
1909 2
1910 },
1911 4,
1912 );
1913
1914 assert!(result.is_err());
1915 assert!(result.unwrap_err().to_string().contains("FromUtf8Error"));
1916 }
1917
1918 #[test]
1919 fn triggers_buffer_resize_when_returned_len_exceeds_capacity() {
1920 let initial_capacity: usize = 4;
1921 let length_exceeding_initial_capacity = 10;
1922 let written_length = 2;
1923 let call_count = std::cell::Cell::new(0);
1924 let result = extract_meta_string(
1925 |buf_ptr, buf_len| {
1926 let count = call_count.get();
1927 call_count.set(count + 1);
1928 if count == 0 {
1929 length_exceeding_initial_capacity
1930 } else {
1931 let buffer =
1932 unsafe { std::slice::from_raw_parts_mut(buf_ptr.cast::<u8>(), buf_len) };
1933 buffer[0] = b'h';
1934 buffer[1] = b'i';
1935 buffer[2] = 0;
1936 written_length
1937 }
1938 },
1939 initial_capacity,
1940 );
1941
1942 assert_eq!(result.unwrap(), "hi");
1943 }
1944
1945 #[test]
1946 fn cstring_with_validated_len_null_byte_returns_error() {
1947 let result = super::cstring_with_validated_len("null\0byte");
1948
1949 assert!(result.is_err());
1950 }
1951
1952 #[test]
1953 fn validate_string_length_overflow_returns_error() {
1954 let result = super::validate_string_length_for_tokenizer(usize::MAX);
1955
1956 assert!(result.is_err());
1957 }
1958
1959 #[test]
1960 fn checked_token_buffer_capacity_overflow_returns_error() {
1961 assert!(super::checked_token_buffer_capacity(usize::MAX).is_err());
1962 }
1963
1964 #[test]
1965 fn checked_token_buffer_capacity_in_range_returns_value() {
1966 assert_eq!(super::checked_token_buffer_capacity(8), Ok(8));
1967 }
1968
1969 #[test]
1970 fn checked_token_count_negative_returns_error() {
1971 assert!(super::checked_token_count(-1).is_err());
1972 }
1973
1974 #[test]
1975 fn checked_token_count_non_negative_returns_value() {
1976 assert_eq!(super::checked_token_count(5), Ok(5));
1977 }
1978
1979 #[test]
1980 fn tokenize_into_buffer_single_pass_sets_length() {
1981 let buffer = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| Ok(3)).unwrap();
1982
1983 assert_eq!(buffer.len(), 3);
1984 }
1985
1986 #[test]
1987 fn tokenize_into_buffer_grows_buffer_when_first_pass_reports_negative_size() {
1988 let call_count = std::cell::Cell::new(0);
1989 let buffer = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| {
1990 let count = call_count.get();
1991 call_count.set(count + 1);
1992 if count == 0 { Ok(-20) } else { Ok(15) }
1993 })
1994 .unwrap();
1995
1996 assert_eq!(buffer.len(), 15);
1997 assert_eq!(call_count.get(), 2);
1998 }
1999
2000 #[test]
2001 fn tokenize_into_buffer_propagates_invocation_error() {
2002 let result = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| {
2003 Err(crate::StringToTokenError::NotEnoughMemory)
2004 });
2005
2006 assert_eq!(result, Err(crate::StringToTokenError::NotEnoughMemory));
2007 }
2008
2009 #[test]
2010 fn tokenize_into_buffer_propagates_second_invocation_error() {
2011 let call_count = std::cell::Cell::new(0);
2012 let result = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| {
2013 let count = call_count.get();
2014 call_count.set(count + 1);
2015 if count == 0 {
2016 Ok(-20)
2017 } else {
2018 Err(crate::StringToTokenError::NotEnoughMemory)
2019 }
2020 });
2021
2022 assert_eq!(result, Err(crate::StringToTokenError::NotEnoughMemory));
2023 assert_eq!(call_count.get(), 2);
2024 }
2025
2026 #[test]
2027 fn tokenize_into_buffer_negative_final_size_returns_conversion_error() {
2028 let call_count = std::cell::Cell::new(0);
2029 let result = super::tokenize_into_buffer(8, |_tokens, _n_tokens_max| {
2030 let count = call_count.get();
2031 call_count.set(count + 1);
2032 if count == 0 { Ok(-20) } else { Ok(-5) }
2033 });
2034
2035 assert_eq!(
2036 result.unwrap_err(),
2037 crate::StringToTokenError::CIntConversionError(usize::try_from(-5i32).unwrap_err())
2038 );
2039 }
2040
2041 #[test]
2042 fn read_optional_owned_cstr_invalid_utf8_returns_error() {
2043 let invalid_utf8_with_terminator: [u8; 3] = [0xFF, 0xFE, 0x00];
2044 let result = super::read_optional_owned_cstr(
2045 invalid_utf8_with_terminator
2046 .as_ptr()
2047 .cast::<std::ffi::c_char>(),
2048 );
2049
2050 assert_eq!(
2051 result.unwrap_err(),
2052 crate::MarkerDetectionError::MarkerUtf8Error(
2053 String::from_utf8(vec![0xFF, 0xFE]).unwrap_err()
2054 )
2055 );
2056 }
2057
2058 #[test]
2059 fn collect_optional_cstr_pair_first_invalid_utf8_returns_error() {
2060 let invalid_utf8_with_terminator: [u8; 3] = [0xFF, 0xFE, 0x00];
2061 let valid_with_terminator: [u8; 3] = [b'o', b'k', 0x00];
2062 let result = super::collect_optional_cstr_pair(
2063 invalid_utf8_with_terminator
2064 .as_ptr()
2065 .cast::<std::ffi::c_char>(),
2066 valid_with_terminator.as_ptr().cast::<std::ffi::c_char>(),
2067 );
2068
2069 assert_eq!(
2070 result.unwrap_err(),
2071 crate::MarkerDetectionError::MarkerUtf8Error(
2072 String::from_utf8(vec![0xFF, 0xFE]).unwrap_err()
2073 )
2074 );
2075 }
2076
2077 #[test]
2078 fn collect_optional_cstr_pair_second_invalid_utf8_returns_error() {
2079 let valid_with_terminator: [u8; 3] = [b'o', b'k', 0x00];
2080 let invalid_utf8_with_terminator: [u8; 3] = [0xFF, 0xFE, 0x00];
2081 let result = super::collect_optional_cstr_pair(
2082 valid_with_terminator.as_ptr().cast::<std::ffi::c_char>(),
2083 invalid_utf8_with_terminator
2084 .as_ptr()
2085 .cast::<std::ffi::c_char>(),
2086 );
2087
2088 assert_eq!(
2089 result.unwrap_err(),
2090 crate::MarkerDetectionError::MarkerUtf8Error(
2091 String::from_utf8(vec![0xFF, 0xFE]).unwrap_err()
2092 )
2093 );
2094 }
2095}
2096
2097#[cfg(test)]
2098mod ffi_status_mapping_tests {
2099 use std::ffi::c_char;
2100 use std::mem::discriminant;
2101 use std::path::Path;
2102 use std::ptr;
2103
2104 use llama_cpp_bindings_types::ParsedChatMessage;
2105 use llama_cpp_bindings_types::ParsedToolCall;
2106 use llama_cpp_bindings_types::ReasoningMarkers;
2107 use llama_cpp_bindings_types::ToolCallArguments;
2108
2109 use super::ReasoningSplit;
2110 use super::chat_parser_create_status_to_result;
2111 use super::compute_tool_call_haystack_status_to_result;
2112 use super::cxx_exception_owns_out_error;
2113 use super::detect_reasoning_markers_status_to_result;
2114 use super::diagnose_tool_call_synthetic_renders_status_to_result;
2115 use super::load_model_from_file_status_to_result;
2116 use super::outcome_from_via_ffi_result;
2117 use super::parse_chat_message_status_to_result;
2118 use super::parsed_chat_content_status_to_result;
2119 use super::parsed_chat_free_status_to_result;
2120 use super::parsed_chat_reasoning_content_status_to_result;
2121 use super::parsed_chat_tool_call_arguments_status_to_result;
2122 use super::parsed_chat_tool_call_count_status_to_result;
2123 use super::parsed_chat_tool_call_id_status_to_result;
2124 use super::parsed_chat_tool_call_name_status_to_result;
2125 use super::reasoning_markers_from_marker_pair;
2126 use super::render_chat_template_status_to_result;
2127 use super::split_reasoning_prefix;
2128 use super::tokenize_status_to_result;
2129 use crate::ChatMessageParseOutcome;
2130 use crate::LlamaModelLoadError;
2131 use crate::MarkerDetectionError;
2132 use crate::ParseChatMessageError;
2133 use crate::RawChatMessage;
2134 use crate::StringToTokenError;
2135
2136 #[test]
2137 fn cxx_exception_owns_out_error_classifies_each_failure_variant() {
2138 assert!(cxx_exception_owns_out_error::<()>(&Err(
2139 MarkerDetectionError::ReasoningMarkerDetectionFailed {
2140 message: String::new()
2141 }
2142 )));
2143 assert!(cxx_exception_owns_out_error::<()>(&Err(
2144 MarkerDetectionError::ToolCallHaystackComputationFailed {
2145 message: String::new()
2146 }
2147 )));
2148 assert!(cxx_exception_owns_out_error::<()>(&Err(
2149 MarkerDetectionError::ToolCallSyntheticRenderDiagnosisFailed {
2150 message: String::new()
2151 }
2152 )));
2153 assert!(!cxx_exception_owns_out_error::<()>(&Ok(())));
2154 }
2155
2156 #[test]
2157 fn load_model_from_file_ok_with_null_model_is_unloadable() {
2158 let result = unsafe {
2159 load_model_from_file_status_to_result(
2160 llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_OK,
2161 ptr::null_mut(),
2162 ptr::null_mut(),
2163 Path::new("/some/path"),
2164 )
2165 };
2166
2167 assert_eq!(result.unwrap_err(), LlamaModelLoadError::Unloadable);
2168 }
2169
2170 #[test]
2171 fn load_model_from_file_vendored_returned_null_for_missing_path_is_file_not_found() {
2172 let result = unsafe {
2173 load_model_from_file_status_to_result(
2174 llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_VENDORED_RETURNED_NULL,
2175 ptr::null_mut(),
2176 ptr::null_mut(),
2177 Path::new("/definitely/missing/model.gguf"),
2178 )
2179 };
2180
2181 assert_eq!(
2182 result.unwrap_err(),
2183 LlamaModelLoadError::FileNotFound(
2184 Path::new("/definitely/missing/model.gguf").to_path_buf()
2185 )
2186 );
2187 }
2188
2189 #[test]
2190 fn load_model_from_file_allocation_failed_is_not_enough_memory() {
2191 let result = unsafe {
2192 load_model_from_file_status_to_result(
2193 llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_ERROR_STRING_ALLOCATION_FAILED,
2194 ptr::null_mut(),
2195 ptr::null_mut(),
2196 Path::new("/some/path"),
2197 )
2198 };
2199
2200 assert_eq!(result.unwrap_err(), LlamaModelLoadError::NotEnoughMemory);
2201 }
2202
2203 #[test]
2204 fn load_model_from_file_cxx_exception_is_reported() {
2205 let result = unsafe {
2206 load_model_from_file_status_to_result(
2207 llama_cpp_bindings_sys::LLAMA_RS_LOAD_MODEL_FROM_FILE_VENDORED_THREW_CXX_EXCEPTION,
2208 ptr::null_mut(),
2209 ptr::null_mut(),
2210 Path::new("/some/path"),
2211 )
2212 };
2213
2214 assert_eq!(
2215 result.unwrap_err(),
2216 LlamaModelLoadError::Reported {
2217 message: "unknown error".to_owned()
2218 }
2219 );
2220 }
2221
2222 #[test]
2223 #[should_panic(expected = "llama_rs_load_model_from_file returned unrecognized status")]
2224 fn load_model_from_file_unrecognized_status_panics() {
2225 let _ = unsafe {
2226 load_model_from_file_status_to_result(
2227 llama_cpp_bindings_sys::llama_rs_load_model_from_file_status::MAX,
2228 ptr::null_mut(),
2229 ptr::null_mut(),
2230 Path::new("/some/path"),
2231 )
2232 };
2233 }
2234
2235 #[test]
2236 fn parse_chat_message_ok_with_null_handle_is_default_message() {
2237 let mut out_error: *mut c_char = ptr::null_mut();
2238 let result = unsafe {
2239 parse_chat_message_status_to_result(
2240 llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_OK,
2241 ptr::null_mut(),
2242 &raw mut out_error,
2243 )
2244 };
2245
2246 assert_eq!(result.unwrap(), ParsedChatMessage::default());
2247 }
2248
2249 #[test]
2250 fn chat_parser_create_no_chat_template_maps_to_no_chat_template() {
2251 let mut out_error: *mut c_char = ptr::null_mut();
2252 let result = unsafe {
2253 chat_parser_create_status_to_result(
2254 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_MODEL_HAS_NO_CHAT_TEMPLATE,
2255 ptr::null_mut(),
2256 &raw mut out_error,
2257 )
2258 };
2259
2260 assert_eq!(
2261 discriminant(&result.unwrap_err()),
2262 discriminant(&ParseChatMessageError::NoChatTemplate)
2263 );
2264 }
2265
2266 #[test]
2267 fn chat_parser_create_no_vocab_maps_to_no_vocab() {
2268 let mut out_error: *mut c_char = ptr::null_mut();
2269 let result = unsafe {
2270 chat_parser_create_status_to_result(
2271 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_MODEL_HAS_NO_VOCAB,
2272 ptr::null_mut(),
2273 &raw mut out_error,
2274 )
2275 };
2276
2277 assert_eq!(
2278 discriminant(&result.unwrap_err()),
2279 discriminant(&ParseChatMessageError::NoVocab)
2280 );
2281 }
2282
2283 #[test]
2284 fn chat_parser_create_allocation_failed_is_not_enough_memory() {
2285 let mut out_error: *mut c_char = ptr::null_mut();
2286 let result = unsafe {
2287 chat_parser_create_status_to_result(
2288 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_ERROR_STRING_ALLOCATION_FAILED,
2289 ptr::null_mut(),
2290 &raw mut out_error,
2291 )
2292 };
2293
2294 assert_eq!(
2295 discriminant(&result.unwrap_err()),
2296 discriminant(&ParseChatMessageError::NotEnoughMemory)
2297 );
2298 }
2299
2300 #[test]
2301 fn chat_parser_create_cxx_exception_is_parse_failed_and_nulls_error() {
2302 let mut out_error: *mut c_char = ptr::null_mut();
2303 let result = unsafe {
2304 chat_parser_create_status_to_result(
2305 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_VENDORED_THREW_CXX_EXCEPTION,
2306 ptr::null_mut(),
2307 &raw mut out_error,
2308 )
2309 };
2310
2311 assert_eq!(
2312 discriminant(&result.unwrap_err()),
2313 discriminant(&ParseChatMessageError::ParseFailed {
2314 message: String::new()
2315 })
2316 );
2317 assert!(out_error.is_null());
2318 }
2319
2320 #[test]
2321 #[should_panic(expected = "llama_rs_chat_parser_create returned OK with a null parser handle")]
2322 fn chat_parser_create_ok_with_null_parser_panics() {
2323 let mut out_error: *mut c_char = ptr::null_mut();
2324 let _ = unsafe {
2325 chat_parser_create_status_to_result(
2326 llama_cpp_bindings_sys::LLAMA_RS_CHAT_PARSER_CREATE_OK,
2327 ptr::null_mut(),
2328 &raw mut out_error,
2329 )
2330 };
2331 }
2332
2333 #[test]
2334 #[should_panic(expected = "llama_rs_chat_parser_create returned unrecognized status")]
2335 fn chat_parser_create_unrecognized_status_panics() {
2336 let mut out_error: *mut c_char = ptr::null_mut();
2337 let _ = unsafe {
2338 chat_parser_create_status_to_result(
2339 llama_cpp_bindings_sys::llama_rs_chat_parser_create_status::MAX,
2340 ptr::null_mut(),
2341 &raw mut out_error,
2342 )
2343 };
2344 }
2345
2346 #[test]
2347 fn parse_chat_message_allocation_failed_is_not_enough_memory() {
2348 let mut out_error: *mut c_char = ptr::null_mut();
2349 let result = unsafe {
2350 parse_chat_message_status_to_result(
2351 llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_ERROR_STRING_ALLOCATION_FAILED,
2352 ptr::null_mut(),
2353 &raw mut out_error,
2354 )
2355 };
2356
2357 assert_eq!(
2358 discriminant(&result.unwrap_err()),
2359 discriminant(&ParseChatMessageError::NotEnoughMemory)
2360 );
2361 }
2362
2363 #[test]
2364 fn parse_chat_message_cxx_exception_is_parse_failed_and_nulls_error() {
2365 let mut out_error: *mut c_char = ptr::null_mut();
2366 let result = unsafe {
2367 parse_chat_message_status_to_result(
2368 llama_cpp_bindings_sys::LLAMA_RS_PARSE_CHAT_MESSAGE_VENDORED_THREW_CXX_EXCEPTION,
2369 ptr::null_mut(),
2370 &raw mut out_error,
2371 )
2372 };
2373
2374 assert_eq!(
2375 discriminant(&result.unwrap_err()),
2376 discriminant(&ParseChatMessageError::ParseFailed {
2377 message: String::new()
2378 })
2379 );
2380 assert!(out_error.is_null());
2381 }
2382
2383 #[test]
2384 #[should_panic(expected = "llama_rs_parse_chat_message returned unrecognized status")]
2385 fn parse_chat_message_unrecognized_status_panics() {
2386 let mut out_error: *mut c_char = ptr::null_mut();
2387 let _ = unsafe {
2388 parse_chat_message_status_to_result(
2389 llama_cpp_bindings_sys::llama_rs_parse_chat_message_status::MAX,
2390 ptr::null_mut(),
2391 &raw mut out_error,
2392 )
2393 };
2394 }
2395
2396 #[test]
2397 fn parsed_chat_free_ok_returns_parsed_value() {
2398 let parsed = Ok(ParsedChatMessage::default());
2399 let result = unsafe {
2400 parsed_chat_free_status_to_result(
2401 parsed,
2402 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_FREE_OK,
2403 ptr::null_mut(),
2404 ptr::null_mut(),
2405 )
2406 };
2407
2408 assert_eq!(result.unwrap(), ParsedChatMessage::default());
2409 }
2410
2411 #[test]
2412 fn parsed_chat_free_destructor_threw_is_destructor_failed() {
2413 let parsed = Ok(ParsedChatMessage::default());
2414 let result = unsafe {
2415 parsed_chat_free_status_to_result(
2416 parsed,
2417 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_FREE_DESTRUCTOR_THREW_CXX_EXCEPTION,
2418 ptr::null_mut(),
2419 ptr::null_mut(),
2420 )
2421 };
2422
2423 assert_eq!(
2424 discriminant(&result.unwrap_err()),
2425 discriminant(&ParseChatMessageError::DestructorFailed {
2426 message: String::new()
2427 })
2428 );
2429 }
2430
2431 #[test]
2432 fn parsed_chat_free_allocation_failed_is_not_enough_memory() {
2433 let parsed = Ok(ParsedChatMessage::default());
2434 let result = unsafe {
2435 parsed_chat_free_status_to_result(
2436 parsed,
2437 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_FREE_ERROR_STRING_ALLOCATION_FAILED,
2438 ptr::null_mut(),
2439 ptr::null_mut(),
2440 )
2441 };
2442
2443 assert_eq!(
2444 discriminant(&result.unwrap_err()),
2445 discriminant(&ParseChatMessageError::NotEnoughMemory)
2446 );
2447 }
2448
2449 #[test]
2450 fn parsed_chat_free_propagates_existing_parse_error() {
2451 let parsed = Err(ParseChatMessageError::NoVocab);
2452 let result = unsafe {
2453 parsed_chat_free_status_to_result(
2454 parsed,
2455 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_FREE_OK,
2456 ptr::null_mut(),
2457 ptr::null_mut(),
2458 )
2459 };
2460
2461 assert_eq!(
2462 discriminant(&result.unwrap_err()),
2463 discriminant(&ParseChatMessageError::NoVocab)
2464 );
2465 }
2466
2467 #[test]
2468 #[should_panic(expected = "llama_rs_parsed_chat_free returned unrecognized status")]
2469 fn parsed_chat_free_unrecognized_status_panics() {
2470 let parsed = Ok(ParsedChatMessage::default());
2471 let _ = unsafe {
2472 parsed_chat_free_status_to_result(
2473 parsed,
2474 llama_cpp_bindings_sys::llama_rs_parsed_chat_free_status::MAX,
2475 ptr::null_mut(),
2476 ptr::null_mut(),
2477 )
2478 };
2479 }
2480
2481 #[test]
2482 fn parsed_chat_content_ok_with_null_string_is_empty() {
2483 let result = unsafe {
2484 parsed_chat_content_status_to_result(
2485 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_OK,
2486 ptr::null_mut(),
2487 ptr::null_mut(),
2488 )
2489 };
2490
2491 assert_eq!(result.unwrap(), "");
2492 }
2493
2494 #[test]
2495 fn parsed_chat_content_allocation_failed_is_not_enough_memory() {
2496 let result = unsafe {
2497 parsed_chat_content_status_to_result(
2498 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_ERROR_STRING_ALLOCATION_FAILED,
2499 ptr::null_mut(),
2500 ptr::null_mut(),
2501 )
2502 };
2503
2504 assert_eq!(
2505 discriminant(&result.unwrap_err()),
2506 discriminant(&ParseChatMessageError::NotEnoughMemory)
2507 );
2508 }
2509
2510 #[test]
2511 fn parsed_chat_content_cxx_exception_is_reported() {
2512 let result = unsafe {
2513 parsed_chat_content_status_to_result(
2514 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_CONTENT_VENDORED_THREW_CXX_EXCEPTION,
2515 ptr::null_mut(),
2516 ptr::null_mut(),
2517 )
2518 };
2519
2520 assert_eq!(
2521 discriminant(&result.unwrap_err()),
2522 discriminant(&ParseChatMessageError::Reported {
2523 message: String::new()
2524 })
2525 );
2526 }
2527
2528 #[test]
2529 #[should_panic(expected = "llama_rs_parsed_chat_content returned unrecognized status")]
2530 fn parsed_chat_content_unrecognized_status_panics() {
2531 let _ = unsafe {
2532 parsed_chat_content_status_to_result(
2533 llama_cpp_bindings_sys::llama_rs_parsed_chat_content_status::MAX,
2534 ptr::null_mut(),
2535 ptr::null_mut(),
2536 )
2537 };
2538 }
2539
2540 #[test]
2541 fn parsed_chat_reasoning_content_ok_with_null_string_is_empty() {
2542 let result = unsafe {
2543 parsed_chat_reasoning_content_status_to_result(
2544 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_OK,
2545 ptr::null_mut(),
2546 ptr::null_mut(),
2547 )
2548 };
2549
2550 assert_eq!(result.unwrap(), "");
2551 }
2552
2553 #[test]
2554 fn parsed_chat_reasoning_content_allocation_failed_is_not_enough_memory() {
2555 let result = unsafe {
2556 parsed_chat_reasoning_content_status_to_result(
2557 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_ERROR_STRING_ALLOCATION_FAILED,
2558 ptr::null_mut(),
2559 ptr::null_mut(),
2560 )
2561 };
2562
2563 assert_eq!(
2564 discriminant(&result.unwrap_err()),
2565 discriminant(&ParseChatMessageError::NotEnoughMemory)
2566 );
2567 }
2568
2569 #[test]
2570 fn parsed_chat_reasoning_content_cxx_exception_is_reported() {
2571 let result = unsafe {
2572 parsed_chat_reasoning_content_status_to_result(
2573 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_REASONING_CONTENT_VENDORED_THREW_CXX_EXCEPTION,
2574 ptr::null_mut(),
2575 ptr::null_mut(),
2576 )
2577 };
2578
2579 assert_eq!(
2580 discriminant(&result.unwrap_err()),
2581 discriminant(&ParseChatMessageError::Reported {
2582 message: String::new()
2583 })
2584 );
2585 }
2586
2587 #[test]
2588 #[should_panic(
2589 expected = "llama_rs_parsed_chat_reasoning_content returned unrecognized status"
2590 )]
2591 fn parsed_chat_reasoning_content_unrecognized_status_panics() {
2592 let _ = unsafe {
2593 parsed_chat_reasoning_content_status_to_result(
2594 llama_cpp_bindings_sys::llama_rs_parsed_chat_reasoning_content_status::MAX,
2595 ptr::null_mut(),
2596 ptr::null_mut(),
2597 )
2598 };
2599 }
2600
2601 #[test]
2602 fn parsed_chat_tool_call_count_ok_returns_count() {
2603 let result = unsafe {
2604 parsed_chat_tool_call_count_status_to_result(
2605 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_OK,
2606 7,
2607 ptr::null_mut(),
2608 )
2609 };
2610
2611 assert_eq!(result.unwrap(), 7);
2612 }
2613
2614 #[test]
2615 fn parsed_chat_tool_call_count_allocation_failed_is_not_enough_memory() {
2616 let result = unsafe {
2617 parsed_chat_tool_call_count_status_to_result(
2618 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_ERROR_STRING_ALLOCATION_FAILED,
2619 0,
2620 ptr::null_mut(),
2621 )
2622 };
2623
2624 assert_eq!(
2625 discriminant(&result.unwrap_err()),
2626 discriminant(&ParseChatMessageError::NotEnoughMemory)
2627 );
2628 }
2629
2630 #[test]
2631 fn parsed_chat_tool_call_count_cxx_exception_is_reported() {
2632 let result = unsafe {
2633 parsed_chat_tool_call_count_status_to_result(
2634 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_COUNT_VENDORED_THREW_CXX_EXCEPTION,
2635 0,
2636 ptr::null_mut(),
2637 )
2638 };
2639
2640 assert_eq!(
2641 discriminant(&result.unwrap_err()),
2642 discriminant(&ParseChatMessageError::Reported {
2643 message: String::new()
2644 })
2645 );
2646 }
2647
2648 #[test]
2649 #[should_panic(expected = "llama_rs_parsed_chat_tool_call_count returned unrecognized status")]
2650 fn parsed_chat_tool_call_count_unrecognized_status_panics() {
2651 let _ = unsafe {
2652 parsed_chat_tool_call_count_status_to_result(
2653 llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_count_status::MAX,
2654 0,
2655 ptr::null_mut(),
2656 )
2657 };
2658 }
2659
2660 #[test]
2661 fn parsed_chat_tool_call_id_ok_with_null_string_is_empty() {
2662 let result = unsafe {
2663 parsed_chat_tool_call_id_status_to_result(
2664 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_OK,
2665 0,
2666 ptr::null_mut(),
2667 ptr::null_mut(),
2668 )
2669 };
2670
2671 assert_eq!(result.unwrap(), "");
2672 }
2673
2674 #[test]
2675 fn parsed_chat_tool_call_id_out_of_bounds_carries_index() {
2676 let result = unsafe {
2677 parsed_chat_tool_call_id_status_to_result(
2678 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_INDEX_OUT_OF_BOUNDS,
2679 4,
2680 ptr::null_mut(),
2681 ptr::null_mut(),
2682 )
2683 };
2684
2685 let Err(ParseChatMessageError::ToolCallIdIndexOutOfBounds { index }) = result else {
2686 panic!("expected ToolCallIdIndexOutOfBounds, got {result:?}");
2687 };
2688 assert_eq!(index, 4);
2689 }
2690
2691 #[test]
2692 fn parsed_chat_tool_call_id_allocation_failed_is_not_enough_memory() {
2693 let result = unsafe {
2694 parsed_chat_tool_call_id_status_to_result(
2695 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_ERROR_STRING_ALLOCATION_FAILED,
2696 0,
2697 ptr::null_mut(),
2698 ptr::null_mut(),
2699 )
2700 };
2701
2702 assert_eq!(
2703 discriminant(&result.unwrap_err()),
2704 discriminant(&ParseChatMessageError::NotEnoughMemory)
2705 );
2706 }
2707
2708 #[test]
2709 fn parsed_chat_tool_call_id_cxx_exception_is_reported() {
2710 let result = unsafe {
2711 parsed_chat_tool_call_id_status_to_result(
2712 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ID_VENDORED_THREW_CXX_EXCEPTION,
2713 0,
2714 ptr::null_mut(),
2715 ptr::null_mut(),
2716 )
2717 };
2718
2719 assert_eq!(
2720 discriminant(&result.unwrap_err()),
2721 discriminant(&ParseChatMessageError::Reported {
2722 message: String::new()
2723 })
2724 );
2725 }
2726
2727 #[test]
2728 #[should_panic(expected = "llama_rs_parsed_chat_tool_call_id returned unrecognized status")]
2729 fn parsed_chat_tool_call_id_unrecognized_status_panics() {
2730 let _ = unsafe {
2731 parsed_chat_tool_call_id_status_to_result(
2732 llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_id_status::MAX,
2733 0,
2734 ptr::null_mut(),
2735 ptr::null_mut(),
2736 )
2737 };
2738 }
2739
2740 #[test]
2741 fn parsed_chat_tool_call_name_ok_with_null_string_is_empty() {
2742 let result = unsafe {
2743 parsed_chat_tool_call_name_status_to_result(
2744 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_OK,
2745 0,
2746 ptr::null_mut(),
2747 ptr::null_mut(),
2748 )
2749 };
2750
2751 assert_eq!(result.unwrap(), "");
2752 }
2753
2754 #[test]
2755 fn parsed_chat_tool_call_name_out_of_bounds_carries_index() {
2756 let result = unsafe {
2757 parsed_chat_tool_call_name_status_to_result(
2758 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_INDEX_OUT_OF_BOUNDS,
2759 2,
2760 ptr::null_mut(),
2761 ptr::null_mut(),
2762 )
2763 };
2764
2765 let Err(ParseChatMessageError::ToolCallNameIndexOutOfBounds { index }) = result else {
2766 panic!("expected ToolCallNameIndexOutOfBounds, got {result:?}");
2767 };
2768 assert_eq!(index, 2);
2769 }
2770
2771 #[test]
2772 fn parsed_chat_tool_call_name_allocation_failed_is_not_enough_memory() {
2773 let result = unsafe {
2774 parsed_chat_tool_call_name_status_to_result(
2775 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_ERROR_STRING_ALLOCATION_FAILED,
2776 0,
2777 ptr::null_mut(),
2778 ptr::null_mut(),
2779 )
2780 };
2781
2782 assert_eq!(
2783 discriminant(&result.unwrap_err()),
2784 discriminant(&ParseChatMessageError::NotEnoughMemory)
2785 );
2786 }
2787
2788 #[test]
2789 fn parsed_chat_tool_call_name_cxx_exception_is_reported() {
2790 let result = unsafe {
2791 parsed_chat_tool_call_name_status_to_result(
2792 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_NAME_VENDORED_THREW_CXX_EXCEPTION,
2793 0,
2794 ptr::null_mut(),
2795 ptr::null_mut(),
2796 )
2797 };
2798
2799 assert_eq!(
2800 discriminant(&result.unwrap_err()),
2801 discriminant(&ParseChatMessageError::Reported {
2802 message: String::new()
2803 })
2804 );
2805 }
2806
2807 #[test]
2808 #[should_panic(expected = "llama_rs_parsed_chat_tool_call_name returned unrecognized status")]
2809 fn parsed_chat_tool_call_name_unrecognized_status_panics() {
2810 let _ = unsafe {
2811 parsed_chat_tool_call_name_status_to_result(
2812 llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_name_status::MAX,
2813 0,
2814 ptr::null_mut(),
2815 ptr::null_mut(),
2816 )
2817 };
2818 }
2819
2820 #[test]
2821 fn parsed_chat_tool_call_arguments_ok_with_null_string_is_empty() {
2822 let result = unsafe {
2823 parsed_chat_tool_call_arguments_status_to_result(
2824 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_OK,
2825 0,
2826 ptr::null_mut(),
2827 ptr::null_mut(),
2828 )
2829 };
2830
2831 assert_eq!(result.unwrap(), "");
2832 }
2833
2834 #[test]
2835 fn parsed_chat_tool_call_arguments_out_of_bounds_carries_index() {
2836 let result = unsafe {
2837 parsed_chat_tool_call_arguments_status_to_result(
2838 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_INDEX_OUT_OF_BOUNDS,
2839 9,
2840 ptr::null_mut(),
2841 ptr::null_mut(),
2842 )
2843 };
2844
2845 let Err(ParseChatMessageError::ToolCallArgumentsIndexOutOfBounds { index }) = result else {
2846 panic!("expected ToolCallArgumentsIndexOutOfBounds, got {result:?}");
2847 };
2848 assert_eq!(index, 9);
2849 }
2850
2851 #[test]
2852 fn parsed_chat_tool_call_arguments_allocation_failed_is_not_enough_memory() {
2853 let result = unsafe {
2854 parsed_chat_tool_call_arguments_status_to_result(
2855 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_ERROR_STRING_ALLOCATION_FAILED,
2856 0,
2857 ptr::null_mut(),
2858 ptr::null_mut(),
2859 )
2860 };
2861
2862 assert_eq!(
2863 discriminant(&result.unwrap_err()),
2864 discriminant(&ParseChatMessageError::NotEnoughMemory)
2865 );
2866 }
2867
2868 #[test]
2869 fn parsed_chat_tool_call_arguments_cxx_exception_is_reported() {
2870 let result = unsafe {
2871 parsed_chat_tool_call_arguments_status_to_result(
2872 llama_cpp_bindings_sys::LLAMA_RS_PARSED_CHAT_TOOL_CALL_ARGUMENTS_VENDORED_THREW_CXX_EXCEPTION,
2873 0,
2874 ptr::null_mut(),
2875 ptr::null_mut(),
2876 )
2877 };
2878
2879 assert_eq!(
2880 discriminant(&result.unwrap_err()),
2881 discriminant(&ParseChatMessageError::Reported {
2882 message: String::new()
2883 })
2884 );
2885 }
2886
2887 #[test]
2888 #[should_panic(
2889 expected = "llama_rs_parsed_chat_tool_call_arguments returned unrecognized status"
2890 )]
2891 fn parsed_chat_tool_call_arguments_unrecognized_status_panics() {
2892 let _ = unsafe {
2893 parsed_chat_tool_call_arguments_status_to_result(
2894 llama_cpp_bindings_sys::llama_rs_parsed_chat_tool_call_arguments_status::MAX,
2895 0,
2896 ptr::null_mut(),
2897 ptr::null_mut(),
2898 )
2899 };
2900 }
2901
2902 #[test]
2903 fn render_chat_template_status_ok_reads_rendered() {
2904 let rendered = std::ffi::CString::new("hi").expect("test render string");
2905 let result = unsafe {
2906 render_chat_template_status_to_result(
2907 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_OK,
2908 rendered.as_ptr(),
2909 ptr::null_mut(),
2910 )
2911 };
2912
2913 assert_eq!(result.expect("ok render"), Some("hi".to_owned()));
2914 }
2915
2916 #[test]
2917 fn render_chat_template_status_no_chat_template_is_none() {
2918 let result = unsafe {
2919 render_chat_template_status_to_result(
2920 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_MODEL_HAS_NO_CHAT_TEMPLATE,
2921 ptr::null(),
2922 ptr::null_mut(),
2923 )
2924 };
2925
2926 assert_eq!(result.expect("none"), None);
2927 }
2928
2929 #[test]
2930 fn render_chat_template_status_no_vocab_is_none() {
2931 let result = unsafe {
2932 render_chat_template_status_to_result(
2933 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_MODEL_HAS_NO_VOCAB,
2934 ptr::null(),
2935 ptr::null_mut(),
2936 )
2937 };
2938
2939 assert_eq!(result.expect("none"), None);
2940 }
2941
2942 #[test]
2943 fn render_chat_template_status_allocation_failed_is_not_enough_memory() {
2944 let result = unsafe {
2945 render_chat_template_status_to_result(
2946 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_ERROR_STRING_ALLOCATION_FAILED,
2947 ptr::null(),
2948 ptr::null_mut(),
2949 )
2950 };
2951
2952 assert_eq!(
2953 discriminant(&result.unwrap_err()),
2954 discriminant(&MarkerDetectionError::NotEnoughMemory)
2955 );
2956 }
2957
2958 #[test]
2959 fn render_chat_template_status_cxx_exception_is_reported() {
2960 let result = unsafe {
2961 render_chat_template_status_to_result(
2962 llama_cpp_bindings_sys::LLAMA_RS_RENDER_CHAT_TEMPLATE_VENDORED_THREW_CXX_EXCEPTION,
2963 ptr::null(),
2964 ptr::null_mut(),
2965 )
2966 };
2967
2968 assert_eq!(
2969 discriminant(&result.unwrap_err()),
2970 discriminant(&MarkerDetectionError::ReasoningMarkerDetectionFailed {
2971 message: String::new()
2972 })
2973 );
2974 }
2975
2976 #[test]
2977 #[should_panic(expected = "llama_rs_render_chat_template returned unrecognized status")]
2978 fn render_chat_template_status_unrecognized_panics() {
2979 let _ = unsafe {
2980 render_chat_template_status_to_result(
2981 llama_cpp_bindings_sys::llama_rs_render_chat_template_status::MAX,
2982 ptr::null(),
2983 ptr::null_mut(),
2984 )
2985 };
2986 }
2987
2988 #[test]
2989 fn detect_reasoning_markers_ok_with_null_pointers_is_none_pair() {
2990 let result = unsafe {
2991 detect_reasoning_markers_status_to_result(
2992 llama_cpp_bindings_sys::LLAMA_RS_DETECT_REASONING_MARKERS_OK,
2993 ptr::null(),
2994 ptr::null(),
2995 ptr::null_mut(),
2996 )
2997 };
2998
2999 assert_eq!(result, Ok((None, None)));
3000 }
3001
3002 #[test]
3003 fn detect_reasoning_markers_allocation_failed_is_not_enough_memory() {
3004 let result = unsafe {
3005 detect_reasoning_markers_status_to_result(
3006 llama_cpp_bindings_sys::LLAMA_RS_DETECT_REASONING_MARKERS_ERROR_STRING_ALLOCATION_FAILED,
3007 ptr::null(),
3008 ptr::null(),
3009 ptr::null_mut(),
3010 )
3011 };
3012
3013 assert_eq!(result, Err(MarkerDetectionError::NotEnoughMemory));
3014 }
3015
3016 #[test]
3017 fn detect_reasoning_markers_cxx_exception_is_detection_failed() {
3018 let result = unsafe {
3019 detect_reasoning_markers_status_to_result(
3020 llama_cpp_bindings_sys::LLAMA_RS_DETECT_REASONING_MARKERS_VENDORED_THREW_CXX_EXCEPTION,
3021 ptr::null(),
3022 ptr::null(),
3023 ptr::null_mut(),
3024 )
3025 };
3026
3027 assert_eq!(
3028 result,
3029 Err(MarkerDetectionError::ReasoningMarkerDetectionFailed {
3030 message: "unknown error".to_owned()
3031 })
3032 );
3033 }
3034
3035 #[test]
3036 #[should_panic(expected = "llama_rs_detect_reasoning_markers returned unrecognized status")]
3037 fn detect_reasoning_markers_unrecognized_status_panics() {
3038 let _ = unsafe {
3039 detect_reasoning_markers_status_to_result(
3040 llama_cpp_bindings_sys::llama_rs_detect_reasoning_markers_status::MAX,
3041 ptr::null(),
3042 ptr::null(),
3043 ptr::null_mut(),
3044 )
3045 };
3046 }
3047
3048 #[test]
3049 fn compute_tool_call_haystack_ok_with_null_pointer_is_none() {
3050 let result = unsafe {
3051 compute_tool_call_haystack_status_to_result(
3052 llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_OK,
3053 ptr::null(),
3054 ptr::null_mut(),
3055 )
3056 };
3057
3058 assert_eq!(result, Ok(None));
3059 }
3060
3061 #[test]
3062 fn compute_tool_call_haystack_allocation_failed_is_not_enough_memory() {
3063 let result = unsafe {
3064 compute_tool_call_haystack_status_to_result(
3065 llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_ERROR_STRING_ALLOCATION_FAILED,
3066 ptr::null(),
3067 ptr::null_mut(),
3068 )
3069 };
3070
3071 assert_eq!(result, Err(MarkerDetectionError::NotEnoughMemory));
3072 }
3073
3074 #[test]
3075 fn compute_tool_call_haystack_cxx_exception_is_computation_failed() {
3076 let result = unsafe {
3077 compute_tool_call_haystack_status_to_result(
3078 llama_cpp_bindings_sys::LLAMA_RS_COMPUTE_TOOL_CALL_HAYSTACK_VENDORED_THREW_CXX_EXCEPTION,
3079 ptr::null(),
3080 ptr::null_mut(),
3081 )
3082 };
3083
3084 assert_eq!(
3085 result,
3086 Err(MarkerDetectionError::ToolCallHaystackComputationFailed {
3087 message: "unknown error".to_owned()
3088 })
3089 );
3090 }
3091
3092 #[test]
3093 #[should_panic(expected = "llama_rs_compute_tool_call_haystack returned unrecognized status")]
3094 fn compute_tool_call_haystack_unrecognized_status_panics() {
3095 let _ = unsafe {
3096 compute_tool_call_haystack_status_to_result(
3097 llama_cpp_bindings_sys::llama_rs_compute_tool_call_haystack_status::MAX,
3098 ptr::null(),
3099 ptr::null_mut(),
3100 )
3101 };
3102 }
3103
3104 #[test]
3105 fn diagnose_tool_call_synthetic_renders_ok_with_null_pointers_is_none_pair() {
3106 let result = unsafe {
3107 diagnose_tool_call_synthetic_renders_status_to_result(
3108 llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_OK,
3109 ptr::null(),
3110 ptr::null(),
3111 ptr::null_mut(),
3112 )
3113 };
3114
3115 assert_eq!(result, Ok((None, None)));
3116 }
3117
3118 #[test]
3119 fn diagnose_tool_call_synthetic_renders_allocation_failed_is_not_enough_memory() {
3120 let result = unsafe {
3121 diagnose_tool_call_synthetic_renders_status_to_result(
3122 llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_ERROR_STRING_ALLOCATION_FAILED,
3123 ptr::null(),
3124 ptr::null(),
3125 ptr::null_mut(),
3126 )
3127 };
3128
3129 assert_eq!(result, Err(MarkerDetectionError::NotEnoughMemory));
3130 }
3131
3132 #[test]
3133 fn diagnose_tool_call_synthetic_renders_cxx_exception_is_diagnosis_failed() {
3134 let result = unsafe {
3135 diagnose_tool_call_synthetic_renders_status_to_result(
3136 llama_cpp_bindings_sys::LLAMA_RS_DIAGNOSE_TOOL_CALL_SYNTHETIC_RENDERS_VENDORED_THREW_CXX_EXCEPTION,
3137 ptr::null(),
3138 ptr::null(),
3139 ptr::null_mut(),
3140 )
3141 };
3142
3143 assert_eq!(
3144 result,
3145 Err(
3146 MarkerDetectionError::ToolCallSyntheticRenderDiagnosisFailed {
3147 message: "unknown error".to_owned()
3148 }
3149 )
3150 );
3151 }
3152
3153 #[test]
3154 #[should_panic(
3155 expected = "llama_rs_diagnose_tool_call_synthetic_renders returned unrecognized status"
3156 )]
3157 fn diagnose_tool_call_synthetic_renders_unrecognized_status_panics() {
3158 let _ = unsafe {
3159 diagnose_tool_call_synthetic_renders_status_to_result(
3160 llama_cpp_bindings_sys::llama_rs_diagnose_tool_call_synthetic_renders_status::MAX,
3161 ptr::null(),
3162 ptr::null(),
3163 ptr::null_mut(),
3164 )
3165 };
3166 }
3167
3168 #[test]
3169 fn tokenize_ok_returns_count() {
3170 let result = unsafe {
3171 tokenize_status_to_result(
3172 llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_OK,
3173 12,
3174 ptr::null_mut(),
3175 )
3176 };
3177
3178 assert_eq!(result, Ok(12));
3179 }
3180
3181 #[test]
3182 fn tokenize_allocation_failed_is_not_enough_memory() {
3183 let result = unsafe {
3184 tokenize_status_to_result(
3185 llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_ERROR_STRING_ALLOCATION_FAILED,
3186 0,
3187 ptr::null_mut(),
3188 )
3189 };
3190
3191 assert_eq!(result, Err(StringToTokenError::NotEnoughMemory));
3192 }
3193
3194 #[test]
3195 fn tokenize_cxx_exception_is_reported() {
3196 let result = unsafe {
3197 tokenize_status_to_result(
3198 llama_cpp_bindings_sys::LLAMA_RS_TOKENIZE_VENDORED_THREW_CXX_EXCEPTION,
3199 0,
3200 ptr::null_mut(),
3201 )
3202 };
3203
3204 assert_eq!(
3205 result,
3206 Err(StringToTokenError::Reported {
3207 message: "unknown error".to_owned()
3208 })
3209 );
3210 }
3211
3212 #[test]
3213 #[should_panic(expected = "llama_rs_tokenize returned unrecognized status")]
3214 fn tokenize_unrecognized_status_panics() {
3215 let _ = unsafe {
3216 tokenize_status_to_result(
3217 llama_cpp_bindings_sys::llama_rs_tokenize_status::MAX,
3218 0,
3219 ptr::null_mut(),
3220 )
3221 };
3222 }
3223
3224 #[test]
3225 fn apply_chat_template_ok_returns_rendered_prompt() {
3226 unsafe extern "C" {
3227 fn strdup(text: *const c_char) -> *mut c_char;
3228 }
3229 let rendered = std::ffi::CString::new("<bos>rendered prompt").unwrap();
3230 let out_string = unsafe { strdup(rendered.as_ptr()) };
3231 let result = unsafe {
3232 super::apply_chat_template_status_to_result(
3233 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_OK,
3234 out_string,
3235 ptr::null_mut(),
3236 )
3237 };
3238
3239 assert_eq!(result, Ok("<bos>rendered prompt".to_owned()));
3240 }
3241
3242 #[test]
3243 fn apply_chat_template_no_vocab_maps_to_no_vocab() {
3244 let result = unsafe {
3245 super::apply_chat_template_status_to_result(
3246 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_MODEL_HAS_NO_VOCAB,
3247 ptr::null_mut(),
3248 ptr::null_mut(),
3249 )
3250 };
3251
3252 assert_eq!(result, Err(crate::ApplyChatTemplateError::NoVocab));
3253 }
3254
3255 #[test]
3256 fn apply_chat_template_application_failed_maps_to_template_application_failed() {
3257 let result = unsafe {
3258 super::apply_chat_template_status_to_result(
3259 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_TEMPLATE_APPLICATION_FAILED,
3260 ptr::null_mut(),
3261 ptr::null_mut(),
3262 )
3263 };
3264
3265 assert_eq!(
3266 result,
3267 Err(crate::ApplyChatTemplateError::TemplateApplicationFailed)
3268 );
3269 }
3270
3271 #[test]
3272 fn apply_chat_template_allocation_failed_maps_to_not_enough_memory() {
3273 let result = unsafe {
3274 super::apply_chat_template_status_to_result(
3275 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_ERROR_STRING_ALLOCATION_FAILED,
3276 ptr::null_mut(),
3277 ptr::null_mut(),
3278 )
3279 };
3280
3281 assert_eq!(result, Err(crate::ApplyChatTemplateError::NotEnoughMemory));
3282 }
3283
3284 #[test]
3285 fn apply_chat_template_cxx_exception_is_reported() {
3286 unsafe extern "C" {
3287 fn strdup(text: *const c_char) -> *mut c_char;
3288 }
3289 let message = std::ffi::CString::new("renderer exploded").unwrap();
3290 let out_error = unsafe { strdup(message.as_ptr()) };
3291 let result = unsafe {
3292 super::apply_chat_template_status_to_result(
3293 llama_cpp_bindings_sys::LLAMA_RS_APPLY_CHAT_TEMPLATE_VENDORED_THREW_CXX_EXCEPTION,
3294 ptr::null_mut(),
3295 out_error,
3296 )
3297 };
3298
3299 assert_eq!(
3300 result,
3301 Err(crate::ApplyChatTemplateError::Reported {
3302 message: "renderer exploded".to_owned()
3303 })
3304 );
3305 }
3306
3307 #[test]
3308 #[should_panic(expected = "llama_rs_apply_chat_template returned unrecognized status")]
3309 fn apply_chat_template_unrecognized_status_panics() {
3310 let _ = unsafe {
3311 super::apply_chat_template_status_to_result(
3312 llama_cpp_bindings_sys::llama_rs_apply_chat_template_status::MAX,
3313 ptr::null_mut(),
3314 ptr::null_mut(),
3315 )
3316 };
3317 }
3318
3319 #[test]
3320 fn split_reasoning_prefix_without_markers_returns_content_up_to_tool_call_open() {
3321 let ReasoningSplit { reasoning, content } =
3322 split_reasoning_prefix("answer<tool>rest", None, "<tool>");
3323
3324 assert!(reasoning.is_empty());
3325 assert_eq!(content, "answer");
3326 }
3327
3328 #[test]
3329 fn split_reasoning_prefix_with_missing_open_marker_returns_content_only() {
3330 let markers = ReasoningMarkers {
3331 open: "<think>".to_owned(),
3332 close: "</think>".to_owned(),
3333 };
3334 let ReasoningSplit { reasoning, content } =
3335 split_reasoning_prefix("plain answer", Some(&markers), "<tool>");
3336
3337 assert!(reasoning.is_empty());
3338 assert_eq!(content, "plain answer");
3339 }
3340
3341 #[test]
3342 fn split_reasoning_prefix_with_missing_close_marker_returns_content_only() {
3343 let markers = ReasoningMarkers {
3344 open: "<think>".to_owned(),
3345 close: "</think>".to_owned(),
3346 };
3347 let ReasoningSplit { reasoning, content } =
3348 split_reasoning_prefix("<think>unterminated", Some(&markers), "<tool>");
3349
3350 assert!(reasoning.is_empty());
3351 assert_eq!(content, "<think>unterminated");
3352 }
3353
3354 #[test]
3355 fn split_reasoning_prefix_extracts_reasoning_and_trailing_content() {
3356 let markers = ReasoningMarkers {
3357 open: "<think>".to_owned(),
3358 close: "</think>".to_owned(),
3359 };
3360 let ReasoningSplit { reasoning, content } = split_reasoning_prefix(
3361 "<think>deduce</think>answer<tool>tail",
3362 Some(&markers),
3363 "<tool>",
3364 );
3365
3366 assert_eq!(reasoning, "deduce");
3367 assert_eq!(content, "answer");
3368 }
3369
3370 #[test]
3371 fn reasoning_markers_from_marker_pair_with_both_present_builds_markers() {
3372 let markers = reasoning_markers_from_marker_pair(
3373 Some("<think>".to_owned()),
3374 Some("</think>".to_owned()),
3375 );
3376
3377 assert_eq!(
3378 markers,
3379 Some(ReasoningMarkers {
3380 open: "<think>".to_owned(),
3381 close: "</think>".to_owned()
3382 })
3383 );
3384 }
3385
3386 #[test]
3387 fn reasoning_markers_from_marker_pair_with_empty_marker_is_none() {
3388 let markers =
3389 reasoning_markers_from_marker_pair(Some(String::new()), Some("</think>".to_owned()));
3390
3391 assert!(markers.is_none());
3392 }
3393
3394 #[test]
3395 fn reasoning_markers_from_marker_pair_with_missing_marker_is_none() {
3396 let markers = reasoning_markers_from_marker_pair(None, Some("</think>".to_owned()));
3397
3398 assert!(markers.is_none());
3399 }
3400
3401 #[test]
3402 fn outcome_from_via_ffi_result_recognized_synthesizes_tool_call_ids() {
3403 let parsed = ParsedChatMessage::new(
3404 "answer".to_owned(),
3405 String::new(),
3406 vec![ParsedToolCall::new(
3407 String::new(),
3408 "tool".to_owned(),
3409 ToolCallArguments::default(),
3410 )],
3411 );
3412
3413 let outcome = outcome_from_via_ffi_result(Ok(parsed), "[]", "answer", false);
3414
3415 assert_eq!(
3416 outcome.unwrap(),
3417 ChatMessageParseOutcome::Recognized(ParsedChatMessage::new(
3418 "answer".to_owned(),
3419 String::new(),
3420 vec![ParsedToolCall::new(
3421 "call_0".to_owned(),
3422 "tool".to_owned(),
3423 ToolCallArguments::default(),
3424 )],
3425 ))
3426 );
3427 }
3428
3429 #[test]
3430 fn outcome_from_via_ffi_result_parse_failed_is_unrecognized_with_raw_message() {
3431 let outcome = outcome_from_via_ffi_result(
3432 Err(ParseChatMessageError::ParseFailed {
3433 message: "boom".to_owned(),
3434 }),
3435 "[]",
3436 "garbled",
3437 true,
3438 );
3439
3440 assert_eq!(
3441 outcome.unwrap(),
3442 ChatMessageParseOutcome::Unrecognized(RawChatMessage {
3443 tools_json: "[]".to_owned(),
3444 text: "garbled".to_owned(),
3445 is_partial: true,
3446 ffi_error_message: "boom".to_owned(),
3447 })
3448 );
3449 }
3450
3451 #[test]
3452 fn outcome_from_via_ffi_result_other_error_propagates() {
3453 let outcome =
3454 outcome_from_via_ffi_result(Err(ParseChatMessageError::NoVocab), "[]", "x", false);
3455
3456 assert_eq!(
3457 discriminant(&outcome.unwrap_err()),
3458 discriminant(&ParseChatMessageError::NoVocab)
3459 );
3460 }
3461}