use std::str::Utf8Error;
use crate::model::{LlamaModel, Special};
use crate::token::LlamaToken;
use crate::TokenToStringError;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum DetokenizeError {
#[error("failed to convert token to raw bytes: {0}")]
TokenToBytes(#[from] TokenToStringError),
#[error("invalid utf-8 in detokenized output: {0}")]
InvalidUtf8(Utf8Error),
#[error("stream ended with {} incomplete utf-8 byte(s)", .0.len())]
IncompleteUtf8(Vec<u8>),
}
#[derive(Debug)]
pub struct StreamDetokenizer<'a> {
model: &'a LlamaModel,
special: Special,
buffer: Vec<u8>,
}
impl<'a> StreamDetokenizer<'a> {
#[must_use]
pub fn new(model: &'a LlamaModel, special: Special) -> Self {
Self {
model,
special,
buffer: Vec::new(),
}
}
pub fn push(&mut self, token: LlamaToken) -> Result<String, DetokenizeError> {
let raw = self.model.token_to_raw_bytes(token, self.special)?;
self.buffer.extend_from_slice(&raw);
self.drain_complete()
}
pub fn push_all(
&mut self,
tokens: impl IntoIterator<Item = LlamaToken>,
) -> Result<String, DetokenizeError> {
let mut out = String::new();
for token in tokens {
out.push_str(&self.push(token)?);
}
Ok(out)
}
#[must_use]
pub fn pending(&self) -> &[u8] {
&self.buffer
}
pub fn finish(mut self) -> Result<String, DetokenizeError> {
let text = self.drain_complete()?;
if self.buffer.is_empty() {
Ok(text)
} else {
Err(DetokenizeError::IncompleteUtf8(self.buffer))
}
}
fn drain_complete(&mut self) -> Result<String, DetokenizeError> {
take_valid_utf8(&mut self.buffer).map_err(DetokenizeError::InvalidUtf8)
}
}
fn take_valid_utf8(buffer: &mut Vec<u8>) -> Result<String, Utf8Error> {
let valid = match std::str::from_utf8(buffer) {
Ok(_) => buffer.len(),
Err(e) if e.error_len().is_some() => return Err(e),
Err(e) => e.valid_up_to(),
};
let complete: Vec<u8> = buffer.drain(..valid).collect();
Ok(String::from_utf8(complete).expect("prefix up to valid_up_to is valid utf-8"))
}
#[cfg(test)]
mod tests {
use super::{take_valid_utf8, DetokenizeError};
#[test]
fn complete_utf8_is_fully_drained() {
let mut buf = "hello".as_bytes().to_vec();
assert_eq!(take_valid_utf8(&mut buf).unwrap(), "hello");
assert!(buf.is_empty());
}
#[test]
fn incomplete_multibyte_is_retained() {
let mut buf = vec![b'a', 0xE2, 0x82];
assert_eq!(take_valid_utf8(&mut buf).unwrap(), "a");
assert_eq!(buf, vec![0xE2, 0x82]);
buf.push(0xAC);
assert_eq!(take_valid_utf8(&mut buf).unwrap(), "€");
assert!(buf.is_empty());
}
#[test]
fn malformed_bytes_are_surfaced() {
let mut buf = vec![b'a', 0xFF, b'b'];
assert!(take_valid_utf8(&mut buf).is_err());
}
#[test]
fn incomplete_utf8_reports_leftover_bytes() {
let err = DetokenizeError::IncompleteUtf8(vec![0xE2, 0x82]);
assert_eq!(err.to_string(), "stream ended with 2 incomplete utf-8 byte(s)");
}
}