Skip to main content

llama_cpp_2/
model.rs

1//! A safe wrapper around `llama_model`.
2use std::ffi::{c_char, CStr, CString};
3use std::os::raw::c_int;
4use std::path::Path;
5use std::str::Utf8Error;
6
7use crate::context::params::LlamaContextParams;
8use crate::context::LlamaContext;
9use crate::llama_backend::LlamaBackend;
10use crate::model::params::LlamaModelParams;
11use crate::ptr::Ptr;
12use crate::sampling::LlamaSampler;
13use crate::token::LlamaToken;
14use crate::vocab::LlamaVocab;
15use crate::{
16    ApplyChatTemplateError, ChatTemplateError, LlamaContextLoadError, LlamaLoraAdapterInitError,
17    LlamaModelLoadError, MetaValError, NewLlamaChatMessageError,
18};
19
20pub mod params;
21// For backwards compat.
22pub use crate::vocab::{LlamaTokenTypeFromIntError, VocabType};
23
24/// A safe wrapper around `llama_model`.
25#[derive(Debug)]
26#[repr(transparent)]
27#[allow(clippy::module_name_repetitions)]
28pub struct LlamaModel {
29    pub(crate) model: Ptr<llama_cpp_sys_2::llama_model>,
30}
31
32// SAFETY: The model is immutable except for methods where it's passed as `&mut`.
33//
34// FIXME(madsmtm): Apart from `llama_new_context_with_model`, which mutates.
35unsafe impl Send for LlamaModel {}
36// SAFETY: Same as above.
37unsafe impl Sync for LlamaModel {}
38
39/// A safe wrapper around `llama_lora_adapter`.
40#[derive(Debug)]
41#[repr(transparent)]
42#[allow(clippy::module_name_repetitions)]
43pub struct LlamaLoraAdapter {
44    pub(crate) lora_adapter: Ptr<llama_cpp_sys_2::llama_adapter_lora>,
45}
46
47// SAFETY: The lora is immutable.
48unsafe impl Send for LlamaLoraAdapter {}
49// SAFETY: Same as above.
50unsafe impl Sync for LlamaLoraAdapter {}
51
52/// A performance-friendly wrapper around [`LlamaModel::chat_template`] which is then
53/// fed into [`LlamaModel::apply_chat_template`] to convert a list of messages into an LLM
54/// prompt. Internally the template is stored as a `CString` to avoid round-trip conversions
55/// within the FFI.
56#[derive(Eq, PartialEq, Clone, PartialOrd, Ord, Hash)]
57pub struct LlamaChatTemplate(CString);
58
59impl LlamaChatTemplate {
60    /// Create a new template from a string. This can either be the name of a llama.cpp [chat template](https://github.com/ggerganov/llama.cpp/blob/8a8c4ceb6050bd9392609114ca56ae6d26f5b8f5/src/llama-chat.cpp#L27-L61)
61    /// like "chatml" or "llama3" or an actual Jinja template for llama.cpp to interpret.
62    pub fn new(template: &str) -> Result<Self, std::ffi::NulError> {
63        Ok(Self(CString::new(template)?))
64    }
65
66    /// Accesses the template as a c string reference.
67    pub fn as_c_str(&self) -> &CStr {
68        &self.0
69    }
70
71    /// Attempts to convert the `CString` into a Rust str reference.
72    pub fn to_str(&self) -> Result<&str, Utf8Error> {
73        self.0.to_str()
74    }
75
76    /// Convenience method to create an owned String.
77    pub fn to_string(&self) -> Result<String, Utf8Error> {
78        self.to_str().map(str::to_string)
79    }
80}
81
82impl std::fmt::Debug for LlamaChatTemplate {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        self.0.fmt(f)
85    }
86}
87
88/// A Safe wrapper around `llama_chat_message`
89#[derive(Debug, Eq, PartialEq, Clone)]
90pub struct LlamaChatMessage {
91    role: CString,
92    content: CString,
93}
94
95impl LlamaChatMessage {
96    /// Create a new `LlamaChatMessage`
97    ///
98    /// # Errors
99    /// If either of ``role`` or ``content`` contain null bytes.
100    pub fn new(role: String, content: String) -> Result<Self, NewLlamaChatMessageError> {
101        Ok(Self {
102            role: CString::new(role)?,
103            content: CString::new(content)?,
104        })
105    }
106}
107
108/// The Rope type that's used within the model.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum RopeType {
111    Norm,
112    NeoX,
113    MRope,
114    Vision,
115}
116
117impl LlamaModel {
118    /// Get the model's vocabulary.
119    #[must_use]
120    pub fn vocab(&self) -> LlamaVocab<'_> {
121        let ptr = unsafe { llama_cpp_sys_2::llama_model_get_vocab(self.model.as_ptr()) };
122        LlamaVocab::new(ptr).expect("model must have vocabulary")
123    }
124
125    /// get the number of tokens the model was trained on
126    ///
127    /// # Panics
128    ///
129    /// If the number of tokens the model was trained on does not fit into an `u32`. This should be impossible on most
130    /// platforms due to llama.cpp returning a `c_int` (i32 on most platforms) which is almost certainly positive.
131    #[must_use]
132    pub fn n_ctx_train(&self) -> u32 {
133        let n_ctx_train = unsafe { llama_cpp_sys_2::llama_n_ctx_train(self.model.as_ptr()) };
134        u32::try_from(n_ctx_train).expect("n_ctx_train fits into an u32")
135    }
136
137    /// Get all tokens in the model.
138    pub fn tokens(&self, decode_special: bool) -> impl Iterator<Item = (LlamaToken, Vec<u8>)> + '_ {
139        let vocab = self.vocab();
140        vocab.tokens().map(move |llama_token| {
141            let bytes = vocab.token_to_piece(llama_token, decode_special, None);
142            (llama_token, bytes)
143        })
144    }
145
146    /// Get the decoder start token.
147    #[must_use]
148    pub fn decode_start_token(&self) -> LlamaToken {
149        let token =
150            unsafe { llama_cpp_sys_2::llama_model_decoder_start_token(self.model.as_ptr()) };
151        LlamaToken(token)
152    }
153
154    /// The number of tokens the model was trained on.
155    ///
156    /// This returns a `c_int` for maximum compatibility. Most of the time it can be cast to an i32
157    /// without issue.
158    #[must_use]
159    pub fn n_vocab(&self) -> i32 {
160        self.vocab().n_tokens()
161    }
162
163    /// This returns a `c_int` for maximum compatibility. Most of the time it can be cast to an i32
164    /// without issue.
165    #[must_use]
166    pub fn n_embd(&self) -> c_int {
167        unsafe { llama_cpp_sys_2::llama_n_embd(self.model.as_ptr()) }
168    }
169
170    /// The model's *output* embedding width (`n_embd_out`). This is the width
171    /// llama.cpp actually extracts embeddings at — `n_embd` and `n_embd_out`
172    /// diverge when `{arch}.embedding_length_out` is present (deepstack models
173    /// like qwen3vl). Returns a `c_int` for maximum compatibility.
174    #[must_use]
175    pub fn n_embd_out(&self) -> c_int {
176        unsafe { llama_cpp_sys_2::llama_model_n_embd_out(self.model.as_ptr()) }
177    }
178
179    /// The model's classification output width (`n_cls_out`, default 1) — the
180    /// width of a RANK-pooled embeddings read (llama.h:1029).
181    #[must_use]
182    pub fn n_cls_out(&self) -> u32 {
183        unsafe { llama_cpp_sys_2::llama_model_n_cls_out(self.model.as_ptr()) }
184    }
185
186    /// Returns the total size of all the tensors in the model in bytes.
187    pub fn size(&self) -> u64 {
188        unsafe { llama_cpp_sys_2::llama_model_size(self.model.as_ptr()) }
189    }
190
191    /// Returns the number of parameters in the model.
192    pub fn n_params(&self) -> u64 {
193        unsafe { llama_cpp_sys_2::llama_model_n_params(self.model.as_ptr()) }
194    }
195
196    /// Returns whether the model is a recurrent network (Mamba, RWKV, etc)
197    pub fn is_recurrent(&self) -> bool {
198        unsafe { llama_cpp_sys_2::llama_model_is_recurrent(self.model.as_ptr()) }
199    }
200
201    /// Returns whether the model is a hybrid network (Jamba, Granite, Qwen3xx, etc)
202    ///
203    /// Hybrid models have both attention layers and recurrent/SSM layers.
204    /// They require special handling for state checkpointing.
205    pub fn is_hybrid(&self) -> bool {
206        unsafe { llama_cpp_sys_2::llama_model_is_hybrid(self.model.as_ptr()) }
207    }
208
209    /// Returns the number of layers within the model.
210    pub fn n_layer(&self) -> u32 {
211        // It's never possible for this to panic because while the API interface is defined as an int32_t,
212        // the field it's accessing is a uint32_t.
213        u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_layer(self.model.as_ptr()) }).unwrap()
214    }
215
216    /// Returns the number of attention heads within the model.
217    pub fn n_head(&self) -> u32 {
218        // It's never possible for this to panic because while the API interface is defined as an int32_t,
219        // the field it's accessing is a uint32_t.
220        u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head(self.model.as_ptr()) }).unwrap()
221    }
222
223    /// Returns the number of KV attention heads.
224    pub fn n_head_kv(&self) -> u32 {
225        // It's never possible for this to panic because while the API interface is defined as an int32_t,
226        // the field it's accessing is a uint32_t.
227        u32::try_from(unsafe { llama_cpp_sys_2::llama_model_n_head_kv(self.model.as_ptr()) })
228            .unwrap()
229    }
230
231    /// Get metadata value as a string by key name
232    pub fn meta_val_str(&self, key: &str) -> Result<String, MetaValError> {
233        let key_cstring = CString::new(key)?;
234        let key_ptr = key_cstring.as_ptr();
235
236        extract_meta_string(
237            |buf_ptr, buf_len| unsafe {
238                llama_cpp_sys_2::llama_model_meta_val_str(
239                    self.model.as_ptr(),
240                    key_ptr,
241                    buf_ptr,
242                    buf_len,
243                )
244            },
245            256,
246        )
247    }
248
249    /// Get the number of metadata key/value pairs
250    pub fn meta_count(&self) -> i32 {
251        unsafe { llama_cpp_sys_2::llama_model_meta_count(self.model.as_ptr()) }
252    }
253
254    /// Get metadata key name by index
255    pub fn meta_key_by_index(&self, index: i32) -> Result<String, MetaValError> {
256        extract_meta_string(
257            |buf_ptr, buf_len| unsafe {
258                llama_cpp_sys_2::llama_model_meta_key_by_index(
259                    self.model.as_ptr(),
260                    index,
261                    buf_ptr,
262                    buf_len,
263                )
264            },
265            256,
266        )
267    }
268
269    /// Get metadata value as a string by index
270    pub fn meta_val_str_by_index(&self, index: i32) -> Result<String, MetaValError> {
271        extract_meta_string(
272            |buf_ptr, buf_len| unsafe {
273                llama_cpp_sys_2::llama_model_meta_val_str_by_index(
274                    self.model.as_ptr(),
275                    index,
276                    buf_ptr,
277                    buf_len,
278                )
279            },
280            256,
281        )
282    }
283
284    /// Returns the rope type of the model.
285    pub fn rope_type(&self) -> Option<RopeType> {
286        match unsafe { llama_cpp_sys_2::llama_model_rope_type(self.model.as_ptr()) } {
287            llama_cpp_sys_2::LLAMA_ROPE_TYPE_NONE => None,
288            llama_cpp_sys_2::LLAMA_ROPE_TYPE_NORM => Some(RopeType::Norm),
289            llama_cpp_sys_2::LLAMA_ROPE_TYPE_NEOX => Some(RopeType::NeoX),
290            llama_cpp_sys_2::LLAMA_ROPE_TYPE_MROPE => Some(RopeType::MRope),
291            llama_cpp_sys_2::LLAMA_ROPE_TYPE_VISION => Some(RopeType::Vision),
292            rope_type => {
293                tracing::error!(rope_type = rope_type, "Unexpected rope type from llama.cpp");
294                None
295            }
296        }
297    }
298
299    /// Get chat template from model by name. If the name parameter is None, the default chat template will be returned.
300    ///
301    /// You supply this into [`Self::apply_chat_template`] to get back a string with the appropriate template
302    /// substitution applied to convert a list of messages into a prompt the LLM can use to complete
303    /// the chat.
304    ///
305    /// You could also use an external jinja parser, like [minijinja](https://github.com/mitsuhiko/minijinja),
306    /// to parse jinja templates not supported by the llama.cpp template engine.
307    ///
308    /// # Errors
309    ///
310    /// * If the model has no chat template by that name
311    /// * If the chat template is not a valid [`CString`].
312    pub fn chat_template(
313        &self,
314        name: Option<&str>,
315    ) -> Result<LlamaChatTemplate, ChatTemplateError> {
316        let name_cstr = name.map(CString::new);
317        let name_ptr = match name_cstr {
318            Some(Ok(name)) => name.as_ptr(),
319            _ => std::ptr::null(),
320        };
321        let result =
322            unsafe { llama_cpp_sys_2::llama_model_chat_template(self.model.as_ptr(), name_ptr) };
323
324        // Convert result to Rust String if not null
325        if result.is_null() {
326            Err(ChatTemplateError::MissingTemplate)
327        } else {
328            let chat_template_cstr = unsafe { CStr::from_ptr(result) };
329            let chat_template = CString::new(chat_template_cstr.to_bytes())?;
330            Ok(LlamaChatTemplate(chat_template))
331        }
332    }
333
334    /// Loads a model from a file.
335    ///
336    /// # Errors
337    ///
338    /// See [`LlamaModelLoadError`] for more information.
339    #[tracing::instrument(skip_all, fields(params))]
340    pub fn load_from_file(
341        _: &LlamaBackend,
342        path: impl AsRef<Path>,
343        params: &LlamaModelParams,
344    ) -> Result<Self, LlamaModelLoadError> {
345        let path = path.as_ref();
346        debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
347        let path = path
348            .to_str()
349            .ok_or(LlamaModelLoadError::PathToStrError(path.to_path_buf()))?;
350
351        let cstr = CString::new(path)?;
352        let llama_model =
353            unsafe { llama_cpp_sys_2::llama_load_model_from_file(cstr.as_ptr(), params.params) };
354
355        let model = Ptr::new(llama_model).ok_or(LlamaModelLoadError::NullResult)?;
356
357        tracing::debug!(?path, "Loaded model");
358        Ok(LlamaModel { model })
359    }
360
361    /// Initializes a lora adapter from a file.
362    ///
363    /// # Errors
364    ///
365    /// See [`LlamaLoraAdapterInitError`] for more information.
366    pub fn lora_adapter_init(
367        &mut self,
368        path: impl AsRef<Path>,
369    ) -> Result<LlamaLoraAdapter, LlamaLoraAdapterInitError> {
370        let path = path.as_ref();
371        debug_assert!(Path::new(path).exists(), "{path:?} does not exist");
372
373        let path = path
374            .to_str()
375            .ok_or(LlamaLoraAdapterInitError::PathToStrError(
376                path.to_path_buf(),
377            ))?;
378
379        let cstr = CString::new(path)?;
380        let adapter = unsafe {
381            llama_cpp_sys_2::llama_adapter_lora_init(self.model.as_mut_ptr(), cstr.as_ptr())
382        };
383
384        let adapter = Ptr::new(adapter).ok_or(LlamaLoraAdapterInitError::NullResult)?;
385
386        tracing::debug!(?path, "Initialized lora adapter");
387        Ok(LlamaLoraAdapter {
388            lora_adapter: adapter,
389        })
390    }
391
392    /// Create a new context from this model.
393    ///
394    /// # Errors
395    ///
396    /// There is many ways this can fail. See [`LlamaContextLoadError`] for more information.
397    // we intentionally do not derive Copy on `LlamaContextParams` to allow llama.cpp to change the type to be non-trivially copyable.
398    #[allow(clippy::needless_pass_by_value)]
399    pub fn new_context<'a>(
400        &'a self,
401        _: &LlamaBackend,
402        params: LlamaContextParams,
403    ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
404        let context_params = params.context_params;
405        // Constructing multiple contexts from a model in parallel is actually
406        // unsound, since `llama_new_context_with_model` mutates the model's
407        // `n_ctx_train` in some cases:
408        // <https://github.com/ggml-org/llama.cpp/blob/f45576aa86c07d60d346b469651a098a15cc4f81/src/llama-context.cpp#L3751>
409        // FIXME(madsmtm): Make this sound!
410        let context = unsafe {
411            llama_cpp_sys_2::llama_new_context_with_model(
412                self.model.as_mut_ptr_unsound(),
413                context_params,
414            )
415        };
416        let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
417
418        Ok(LlamaContext::new(context))
419    }
420
421    /// Create a new context bound to another context via llama.cpp's `ctx_other` field.
422    ///
423    /// This is required for MTP speculative decoding when the target model's
424    /// architecture uses `LLM_ARCH_GEMMA4_ASSISTANT`, which asserts that the draft
425    /// context references the target context so KV state can be shared.
426    ///
427    /// # Errors
428    ///
429    /// See [`LlamaContextLoadError`].
430    #[allow(clippy::needless_pass_by_value)]
431    pub fn new_context_with_ctx_other<'a>(
432        &'a self,
433        _: &LlamaBackend,
434        params: LlamaContextParams,
435        ctx_other: &LlamaContext<'_>,
436    ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
437        let mut context_params = params.context_params;
438        // FIXME(madsmtm): Use `.as_ptr()` after:
439        // https://github.com/ggml-org/llama.cpp/pull/28316
440        context_params.ctx_other = unsafe { ctx_other.context.as_mut_ptr_unsound() };
441        // Unsoundness: See `LlamaMode::new_context`.
442        let context = unsafe {
443            llama_cpp_sys_2::llama_new_context_with_model(
444                self.model.as_mut_ptr_unsound(),
445                context_params,
446            )
447        };
448        let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
449
450        Ok(LlamaContext::new(context))
451    }
452
453    /// Creates a new context with backend samplers attached for specific sequences.
454    ///
455    /// Ownership of the samplers is transferred to the context, ensuring they remain
456    /// alive for the context's lifetime. Only samplers that support backend execution
457    /// (greedy, dist, temp, top_k, top_p, min_p, logit_bias) will run on the backend.
458    ///
459    /// # Arguments
460    ///
461    /// * `params` - Context parameters
462    /// * `samplers` - Iterator of `(seq_id, sampler)` pairs where sampler must be a chain
463    ///
464    /// # Example
465    ///
466    /// ```rust,ignore
467    /// let sampler = LlamaSampler::chain([
468    ///     LlamaSampler::min_p(0.01, 64),
469    ///     LlamaSampler::temp(0.1),
470    ///     LlamaSampler::dist(42),
471    /// ], false);
472    ///
473    /// let ctx = model.new_context_with_samplers(
474    ///     &backend,
475    ///     ctx_params,
476    ///     [(0, sampler)],
477    /// )?;
478    /// ```
479    #[allow(clippy::needless_pass_by_value)]
480    pub fn new_context_with_samplers<'a>(
481        &'a self,
482        _: &LlamaBackend,
483        params: LlamaContextParams,
484        samplers: impl IntoIterator<Item = (i32, LlamaSampler)>,
485    ) -> Result<LlamaContext<'a>, LlamaContextLoadError> {
486        let mut samplers: Vec<_> = samplers.into_iter().collect();
487        let mut context_params = params.context_params;
488
489        let mut sampler_configs: Vec<llama_cpp_sys_2::llama_sampler_seq_config> = samplers
490            .iter_mut()
491            .map(
492                |(seq_id, sampler)| llama_cpp_sys_2::llama_sampler_seq_config {
493                    seq_id: *seq_id,
494                    sampler: sampler.sampler.as_mut_ptr(),
495                },
496            )
497            .collect();
498
499        if !sampler_configs.is_empty() {
500            context_params.samplers = sampler_configs.as_mut_ptr();
501            context_params.n_samplers = sampler_configs.len();
502        }
503
504        // Unsoundness: See `LlamaMode::new_context`.
505        let context = unsafe {
506            llama_cpp_sys_2::llama_new_context_with_model(
507                self.model.as_mut_ptr_unsound(),
508                context_params,
509            )
510        };
511        let context = Ptr::new(context).ok_or(LlamaContextLoadError::NullReturn)?;
512
513        Ok(LlamaContext::with_samplers(context, samplers))
514    }
515
516    /// Apply the models chat template to some messages.
517    /// See <https://github.com/ggerganov/llama.cpp/wiki/Templates-supported-by-llama_chat_apply_template>
518    ///
519    /// Unlike the llama.cpp `apply_chat_template` which just randomly uses the ChatML template when given
520    /// a null pointer for the template, this requires an explicit template to be specified. If you want to
521    /// use "chatml", then just do `LlamaChatTemplate::new("chatml")` or any other model name or template
522    /// string.
523    ///
524    /// Use [`Self::chat_template`] to retrieve the template baked into the model (this is the preferred
525    /// mechanism as using the wrong chat template can result in really unexpected responses from the LLM).
526    ///
527    /// You probably want to set `add_ass` to true so that the generated template string ends with a the
528    /// opening tag of the assistant. If you fail to leave a hanging chat tag, the model will likely generate
529    /// one into the output and the output may also have unexpected output aside from that.
530    ///
531    /// # Errors
532    /// There are many ways this can fail. See [`ApplyChatTemplateError`] for more information.
533    #[tracing::instrument(skip_all)]
534    pub fn apply_chat_template(
535        &self,
536        tmpl: &LlamaChatTemplate,
537        chat: &[LlamaChatMessage],
538        add_ass: bool,
539    ) -> Result<String, ApplyChatTemplateError> {
540        // Buffer is twice the length of messages per their recommendation
541        let message_length = chat.iter().fold(0, |acc, c| {
542            acc + c.role.to_bytes().len() + c.content.to_bytes().len()
543        });
544        let mut buff: Vec<u8> = vec![0; message_length * 2];
545
546        // Build our llama_cpp_sys_2 chat messages
547        let chat: Vec<llama_cpp_sys_2::llama_chat_message> = chat
548            .iter()
549            .map(|c| llama_cpp_sys_2::llama_chat_message {
550                role: c.role.as_ptr(),
551                content: c.content.as_ptr(),
552            })
553            .collect();
554
555        let tmpl_ptr = tmpl.0.as_ptr();
556
557        let res = unsafe {
558            llama_cpp_sys_2::llama_chat_apply_template(
559                tmpl_ptr,
560                chat.as_ptr(),
561                chat.len(),
562                add_ass,
563                buff.as_mut_ptr().cast::<c_char>(),
564                buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
565            )
566        };
567
568        if res < 0 {
569            return Err(ApplyChatTemplateError::FfiError(res));
570        }
571
572        if res > buff.len().try_into().expect("Buffer size exceeds i32::MAX") {
573            buff.resize(res.try_into().expect("res is negative"), 0);
574
575            let res = unsafe {
576                llama_cpp_sys_2::llama_chat_apply_template(
577                    tmpl_ptr,
578                    chat.as_ptr(),
579                    chat.len(),
580                    add_ass,
581                    buff.as_mut_ptr().cast::<c_char>(),
582                    buff.len().try_into().expect("Buffer size exceeds i32::MAX"),
583                )
584            };
585            if res < 0 {
586                return Err(ApplyChatTemplateError::FfiError(res));
587            }
588            assert_eq!(Ok(res), buff.len().try_into());
589        }
590        buff.truncate(res.try_into().expect("res is negative"));
591        Ok(String::from_utf8(buff)?)
592    }
593}
594
595/// Generic helper function for extracting string values from the C API
596/// This are specifically useful for the the metadata functions, where we pass in a buffer
597/// to be populated by a string, not yet knowing if the buffer is large enough.
598/// If the buffer was not large enough, we get the correct length back, which can be used to
599/// construct a buffer of appropriate size.
600fn extract_meta_string<F>(c_function: F, capacity: usize) -> Result<String, MetaValError>
601where
602    F: Fn(*mut c_char, usize) -> i32,
603{
604    let mut buffer = vec![0u8; capacity];
605
606    // call the foreign function
607    let result = c_function(buffer.as_mut_ptr().cast::<c_char>(), buffer.len());
608    if result < 0 {
609        return Err(MetaValError::NegativeReturn(result));
610    }
611
612    // check if the response fit in our buffer
613    let returned_len = result as usize;
614    if returned_len >= capacity {
615        // buffer wasn't large enough, try again with the correct capacity.
616        return extract_meta_string(c_function, returned_len + 1);
617    }
618
619    // verify null termination
620    debug_assert_eq!(
621        buffer.get(returned_len),
622        Some(&0),
623        "should end with null byte"
624    );
625
626    // resize, convert, and return
627    buffer.truncate(returned_len);
628    Ok(String::from_utf8(buffer)?)
629}
630
631impl Drop for LlamaModel {
632    fn drop(&mut self) {
633        unsafe { llama_cpp_sys_2::llama_free_model(self.model.as_mut_ptr()) }
634    }
635}