llm_token_visualizer/
data.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
4pub struct TokenInfo {
6 pub text: String,
8 pub confidence: f64,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct TokenFlag {
15 pub start: usize,
17 pub end: usize,
19 pub flag: String,
21 pub description: Option<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct TokenAnalysis {
28 pub tokens: Vec<TokenInfo>,
30 pub flags: Vec<TokenFlag>,
32}
33
34#[derive(Debug, Clone)]
35pub struct VisualizationConfig {
37 pub verbose: bool,
39 pub show_confidence_scores: bool,
41 pub show_flags: bool,
43}
44
45impl Default for VisualizationConfig {
46 fn default() -> Self {
47 Self {
48 verbose: false,
49 show_confidence_scores: true,
50 show_flags: true,
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq)]
56pub enum ConfidenceLevel {
58 VeryLow, Low, Medium, High, VeryHigh, }
69
70impl From<f64> for ConfidenceLevel {
71 fn from(confidence: f64) -> Self {
72 match confidence {
73 c if c < 0.3 => ConfidenceLevel::VeryLow,
74 c if c < 0.5 => ConfidenceLevel::Low,
75 c if c < 0.7 => ConfidenceLevel::Medium,
76 c if c < 0.9 => ConfidenceLevel::High,
77 _ => ConfidenceLevel::VeryHigh,
78 }
79 }
80}
81
82#[derive(Debug, Clone, PartialEq)]
83pub enum FlagType {
85 Fact,
87 Uncertain,
89 Overconfident,
91 Hallucination,
93 Other(String),
95}
96
97impl From<&str> for FlagType {
98 fn from(s: &str) -> Self {
99 match s.to_lowercase().as_str() {
100 "fact" => FlagType::Fact,
101 "uncertain" => FlagType::Uncertain,
102 "overconfident" => FlagType::Overconfident,
103 "hallucination" => FlagType::Hallucination,
104 _ => FlagType::Other(s.to_string()),
105 }
106 }
107}
108
109impl TokenAnalysis {
110 pub fn validate(&self) -> Result<(), String> {
112 if self.tokens.is_empty() {
113 return Err("No tokens provided".to_string());
114 }
115
116 for (i, token) in self.tokens.iter().enumerate() {
117 if token.confidence < 0.0 || token.confidence > 1.0 {
118 return Err(format!(
119 "Invalid confidence score for token {}: {}",
120 i, token.confidence
121 ));
122 }
123 }
124
125 for flag in &self.flags {
126 if flag.start >= self.tokens.len() || flag.end > self.tokens.len() {
127 return Err(format!(
128 "Flag span out of bounds: {} to {}",
129 flag.start, flag.end
130 ));
131 }
132 if flag.start >= flag.end {
133 return Err(format!("Invalid flag span: {} to {}", flag.start, flag.end));
134 }
135 }
136
137 Ok(())
138 }
139
140 pub fn get_flags_for_token(&self, token_index: usize) -> Vec<&TokenFlag> {
142 self.flags
143 .iter()
144 .filter(|flag| token_index >= flag.start && token_index < flag.end)
145 .collect()
146 }
147
148 pub fn get_confidence_stats(&self) -> (f64, f64, f64) {
150 let confidences: Vec<f64> = self.tokens.iter().map(|t| t.confidence).collect();
151 if confidences.is_empty() {
152 return (0.0, 0.0, 0.0);
154 }
155 let sum: f64 = confidences.iter().sum();
156 let avg = sum / confidences.len() as f64;
157 let min = confidences.iter().fold(f64::INFINITY, |a, &b| a.min(b));
158 let max = confidences.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
159 (min, max, avg)
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn stats_of_empty_analysis_are_zero_not_nan() {
169 let a = TokenAnalysis { tokens: vec![], flags: vec![] };
170 assert_eq!(a.get_confidence_stats(), (0.0, 0.0, 0.0));
171 }
172}