use std::ffi::{CStr, CString, NulError};
use std::os::raw::c_char;
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use ik_llama_cpp_sys as sys;
#[derive(Eq, PartialEq, Clone, PartialOrd, Ord, Hash)]
pub struct LlamaChatTemplate(CString);
impl LlamaChatTemplate {
pub fn new(template: &str) -> Result<Self, NulError> {
Ok(Self(CString::new(template)?))
}
#[must_use]
pub fn as_c_str(&self) -> &CStr {
&self.0
}
pub fn to_str(&self) -> Result<&str, Utf8Error> {
self.0.to_str()
}
pub fn to_string(&self) -> Result<String, Utf8Error> {
self.to_str().map(str::to_string)
}
}
impl std::fmt::Debug for LlamaChatTemplate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct LlamaChatMessage {
role: CString,
content: CString,
}
impl LlamaChatMessage {
pub fn new(role: String, content: String) -> Result<Self, NewLlamaChatMessageError> {
Ok(Self {
role: CString::new(role)?,
content: CString::new(content)?,
})
}
}
#[derive(Debug, thiserror::Error)]
pub enum NewLlamaChatMessageError {
#[error("{0}")]
NulError(#[from] NulError),
}
#[derive(Debug, thiserror::Error)]
pub enum ApplyChatTemplateError {
#[error("{0}")]
NulError(#[from] NulError),
#[error("{0}")]
FromUtf8Error(#[from] FromUtf8Error),
#[error("ffi error {0}")]
FfiError(i32),
}
impl crate::model::LlamaModel {
#[must_use]
pub fn chat_template(&self, name: Option<&str>) -> Option<LlamaChatTemplate> {
let name_cstr = match name {
Some(name) => match CString::new(name) {
Ok(name) => Some(name),
Err(_) => return None,
},
None => None,
};
let name_ptr = name_cstr.as_ref().map_or(std::ptr::null(), |c| c.as_ptr());
let result = unsafe { sys::llama_model_chat_template(self.model.as_ptr(), name_ptr) };
if result.is_null() {
return None;
}
let bytes = unsafe { CStr::from_ptr(result) }.to_bytes();
CString::new(bytes).ok().map(LlamaChatTemplate)
}
pub fn apply_chat_template(
&self,
tmpl: &LlamaChatTemplate,
chat: &[LlamaChatMessage],
add_assistant: bool,
) -> Result<String, ApplyChatTemplateError> {
let c_chat: Vec<sys::llama_chat_message> = chat
.iter()
.map(|c| sys::llama_chat_message {
role: c.role.as_ptr(),
content: c.content.as_ptr(),
})
.collect();
let n_msg = c_chat.len();
let tmpl_ptr = tmpl.0.as_ptr();
let hint = chat
.iter()
.fold(0usize, |acc, c| {
acc + c.role.as_bytes().len() + c.content.as_bytes().len()
})
.saturating_mul(2);
let mut cap = i32::try_from(hint).unwrap_or(i32::MAX);
let mut buf = vec![0u8; cap.max(0) as usize];
let mut n = unsafe {
sys::llama_chat_apply_template(
tmpl_ptr,
c_chat.as_ptr(),
n_msg,
add_assistant,
buf.as_mut_ptr().cast::<c_char>(),
cap,
)
};
if n < 0 {
return Err(ApplyChatTemplateError::FfiError(n));
}
if n > cap {
cap = n;
buf = vec![0u8; cap as usize];
n = unsafe {
sys::llama_chat_apply_template(
tmpl_ptr,
c_chat.as_ptr(),
n_msg,
add_assistant,
buf.as_mut_ptr().cast::<c_char>(),
cap,
)
};
if n < 0 {
return Err(ApplyChatTemplateError::FfiError(n));
}
}
buf.truncate(n as usize);
Ok(String::from_utf8(buf)?)
}
}