#[cfg(test)]
mod tests;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct TrieNode {
children: HashMap<u32, Self>,
is_terminal: bool,
boost: f32,
text: Option<String>,
depth: usize,
}
impl TrieNode {
#[must_use]
pub fn new(depth: usize) -> Self {
Self {
children: HashMap::new(),
is_terminal: false,
boost: 0.0,
text: None,
depth,
}
}
#[must_use]
pub fn is_terminal(&self) -> bool {
self.is_terminal
}
#[must_use]
pub fn boost(&self) -> f32 {
self.boost
}
#[must_use]
pub fn text(&self) -> Option<&str> {
self.text.as_deref()
}
#[must_use]
pub fn depth(&self) -> usize {
self.depth
}
#[must_use]
pub fn has_children(&self) -> bool {
!self.children.is_empty()
}
#[must_use]
pub fn child_count(&self) -> usize {
self.children.len()
}
#[must_use]
pub fn get_child(&self, token: u32) -> Option<&Self> {
self.children.get(&token)
}
pub fn get_child_mut(&mut self, token: u32) -> Option<&mut Self> {
self.children.get_mut(&token)
}
#[must_use]
pub fn child_tokens(&self) -> Vec<u32> {
self.children.keys().copied().collect()
}
pub fn get_or_create_child(&mut self, token: u32) -> &mut Self {
let next_depth = self.depth + 1;
self.children
.entry(token)
.or_insert_with(|| Self::new(next_depth))
}
pub fn set_terminal(&mut self, text: String, boost: f32) {
self.is_terminal = true;
self.text = Some(text);
self.boost = boost;
}
}
impl Default for TrieNode {
fn default() -> Self {
Self::new(0)
}
}
#[derive(Debug, Clone)]
pub struct TrieSearchResult {
pub continuations: Vec<(u32, f32)>,
pub is_complete: bool,
pub text: Option<String>,
pub depth: usize,
pub matching_entries: usize,
}
impl TrieSearchResult {
#[must_use]
pub fn empty() -> Self {
Self {
continuations: Vec::new(),
is_complete: false,
text: None,
depth: 0,
matching_entries: 0,
}
}
#[must_use]
pub fn has_matches(&self) -> bool {
!self.continuations.is_empty() || self.is_complete
}
}
#[derive(Debug, Clone)]
pub struct VocabularyTrie {
root: TrieNode,
entry_count: usize,
default_boost: f32,
prefix_boost_factor: f32,
}
impl VocabularyTrie {
#[must_use]
pub fn new() -> Self {
Self {
root: TrieNode::new(0),
entry_count: 0,
default_boost: 0.5,
prefix_boost_factor: 0.8,
}
}
#[must_use]
pub fn with_default_boost(mut self, boost: f32) -> Self {
self.default_boost = boost;
self
}
#[must_use]
pub fn with_prefix_boost_factor(mut self, factor: f32) -> Self {
self.prefix_boost_factor = factor;
self
}
#[must_use]
pub fn len(&self) -> usize {
self.entry_count
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entry_count == 0
}
pub fn insert(&mut self, tokens: &[u32], text: &str, boost: f32) {
if tokens.is_empty() {
return;
}
let mut node = &mut self.root;
for &token in tokens {
node = node.get_or_create_child(token);
}
if !node.is_terminal() {
self.entry_count += 1;
}
node.set_terminal(text.to_string(), boost);
}
pub fn insert_default(&mut self, tokens: &[u32], text: &str) {
self.insert(tokens, text, self.default_boost);
}
#[must_use]
pub fn contains(&self, tokens: &[u32]) -> bool {
self.get_node(tokens).is_some_and(|n| n.is_terminal())
}
#[must_use]
pub fn has_prefix(&self, prefix: &[u32]) -> bool {
self.get_node(prefix).is_some()
}
fn get_node(&self, tokens: &[u32]) -> Option<&TrieNode> {
let mut node = &self.root;
for &token in tokens {
node = node.get_child(token)?;
}
Some(node)
}
#[must_use]
#[allow(clippy::option_if_let_else)]
pub fn search(&self, prefix: &[u32]) -> TrieSearchResult {
match self.get_node(prefix) {
Some(node) => {
let continuations: Vec<(u32, f32)> = node
.children
.iter()
.map(|(&token, child)| {
let boost = if child.is_terminal() {
child.boost()
} else {
self.default_boost * self.prefix_boost_factor
};
(token, boost)
})
.collect();
let matching_entries = Self::count_entries_under(node);
TrieSearchResult {
continuations,
is_complete: node.is_terminal(),
text: node.text().map(String::from),
depth: node.depth(),
matching_entries,
}
}
None => TrieSearchResult::empty(),
}
}
fn count_entries_under(node: &TrieNode) -> usize {
let mut count = usize::from(node.is_terminal());
for child in node.children.values() {
count += Self::count_entries_under(child);
}
count
}
#[must_use]
pub fn get_continuations(&self, prefix: &[u32]) -> Vec<(u32, f32)> {
self.search(prefix).continuations
}
pub fn apply_prefix_boost(&self, logits: &mut [f32], context: &[u32]) {
if self.is_empty() {
return;
}
let result = self.search(context);
for (token, boost) in result.continuations {
if (token as usize) < logits.len() {
logits[token as usize] += boost;
}
}
}
#[must_use]
pub fn all_entries(&self) -> Vec<(Vec<u32>, String, f32)> {
let mut entries = Vec::new();
Self::collect_entries(&self.root, &[], &mut entries);
entries
}
fn collect_entries(node: &TrieNode, path: &[u32], entries: &mut Vec<(Vec<u32>, String, f32)>) {
if node.is_terminal() {
if let Some(text) = node.text() {
entries.push((path.to_vec(), text.to_string(), node.boost()));
}
}
for (&token, child) in &node.children {
let mut new_path = path.to_vec();
new_path.push(token);
Self::collect_entries(child, &new_path, entries);
}
}
pub fn clear(&mut self) {
self.root = TrieNode::new(0);
self.entry_count = 0;
}
}
impl Default for VocabularyTrie {
fn default() -> Self {
Self::new()
}
}