supercode_interchange/
fidelity.rs1use std::collections::BTreeSet;
8
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::{ChatMessage, Role};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "snake_case")]
24pub enum Fidelity {
25 ByteLossless,
27 ValueLossless,
29 Semantic,
31}
32
33impl Fidelity {
34 pub fn tolerates_residue(self) -> bool {
40 matches!(self, Self::Semantic)
41 }
42}
43
44pub fn messages_equal(a: &ChatMessage, b: &ChatMessage) -> bool {
46 if a.role != b.role || a.content != b.content || a.tool_call_id != b.tool_call_id {
47 return false;
48 }
49 let (a_calls, b_calls) = (a.tool_calls(), b.tool_calls());
50 a_calls.len() == b_calls.len()
51 && a_calls.iter().zip(b_calls).all(|(a_call, b_call)| {
52 a_call.id == b_call.id
53 && a_call.function.name == b_call.function.name
54 && a_call.function.parsed_arguments().ok()
55 == b_call.function.parsed_arguments().ok()
56 })
57}
58
59pub fn messages_equal_multimodal(a: &ChatMessage, b: &ChatMessage) -> bool {
61 if !messages_equal(a, b) || a.name != b.name {
62 return false;
63 }
64 let empty = Vec::new();
65 let a_parts = a.content_parts.as_ref().unwrap_or(&empty);
66 let b_parts = b.content_parts.as_ref().unwrap_or(&empty);
67 a_parts.len() == b_parts.len()
68 && a_parts
69 .iter()
70 .zip(b_parts)
71 .all(|(a_part, b_part)| normalize_part(a_part) == normalize_part(b_part))
72}
73
74fn normalize_part(part: &Value) -> (String, Option<String>, Option<Vec<u8>>) {
75 let kind = part
76 .get("type")
77 .and_then(Value::as_str)
78 .unwrap_or("")
79 .to_owned();
80 let url = part
81 .get("image_url")
82 .and_then(|value| value.get("url"))
83 .and_then(Value::as_str);
84 match url {
85 Some(url) if url.starts_with("data:") => {
86 let rest = &url["data:".len()..];
87 let (metadata, data) = rest.split_once(',').unwrap_or((rest, ""));
88 let mime = metadata
89 .strip_suffix(";base64")
90 .unwrap_or(metadata)
91 .to_owned();
92 (kind, Some(mime), decode_base64(data))
93 }
94 Some(url) => (kind, Some(url.to_owned()), None),
95 None => (kind, None, None),
96 }
97}
98
99fn decode_base64(input: &str) -> Option<Vec<u8>> {
100 fn digit(byte: u8) -> Option<u8> {
101 match byte {
102 b'A'..=b'Z' => Some(byte - b'A'),
103 b'a'..=b'z' => Some(byte - b'a' + 26),
104 b'0'..=b'9' => Some(byte - b'0' + 52),
105 b'+' => Some(62),
106 b'/' => Some(63),
107 _ => None,
108 }
109 }
110 let bytes = input
111 .bytes()
112 .filter(|byte| *byte != b'\n' && *byte != b'\r')
113 .collect::<Vec<_>>();
114 let mut output = Vec::with_capacity(bytes.len() / 4 * 3 + 3);
115 let mut chunk = [0_u8; 4];
116 let mut chunk_len = 0;
117 let mut padding = 0;
118 for byte in bytes {
119 if byte == b'=' {
120 padding += 1;
121 chunk[chunk_len] = 0;
122 } else {
123 chunk[chunk_len] = digit(byte)?;
124 }
125 chunk_len += 1;
126 if chunk_len == 4 {
127 let value = ((chunk[0] as u32) << 18)
128 | ((chunk[1] as u32) << 12)
129 | ((chunk[2] as u32) << 6)
130 | chunk[3] as u32;
131 output.push((value >> 16) as u8);
132 if padding < 2 {
133 output.push((value >> 8) as u8);
134 }
135 if padding < 1 {
136 output.push(value as u8);
137 }
138 chunk_len = 0;
139 padding = 0;
140 }
141 }
142 Some(output)
143}
144
145pub fn core_messages(messages: &[ChatMessage]) -> Vec<ChatMessage> {
147 messages
148 .iter()
149 .filter(|message| message.role != Role::System)
150 .cloned()
151 .collect()
152}
153
154pub fn replay_excluded(message: &ChatMessage) -> bool {
156 message.metadata.get("compacted_out").map(String::as_str) == Some("true")
157 || message
158 .metadata
159 .get("pi_exclude_from_context")
160 .map(String::as_str)
161 == Some("true")
162}
163
164pub fn replay_eligible(messages: &[ChatMessage]) -> Vec<ChatMessage> {
166 messages
167 .iter()
168 .filter(|message| !replay_excluded(message))
169 .cloned()
170 .collect()
171}
172
173#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
175pub struct FidelityResidue {
176 pub compacted_out_excluded: usize,
178 pub other_dropped_messages: usize,
180 pub dropped_metadata_keys: BTreeSet<String>,
186}
187
188impl FidelityResidue {
189 pub fn is_semantically_lossless(&self) -> bool {
191 self.other_dropped_messages == 0 && self.dropped_metadata_keys.is_empty()
192 }
193
194 pub fn count(&self) -> usize {
196 self.compacted_out_excluded + self.other_dropped_messages + self.dropped_metadata_keys.len()
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct FidelityMetric {
203 pub matched: usize,
205 pub total: usize,
207 pub residue: FidelityResidue,
209}
210
211impl FidelityMetric {
212 pub fn percent(&self) -> f64 {
214 if self.total == 0 {
215 100.0
216 } else {
217 self.matched as f64 / self.total as f64 * 100.0
218 }
219 }
220
221 pub fn pct(&self) -> f64 {
223 self.percent()
224 }
225
226 pub fn is_semantically_lossless(&self) -> bool {
228 self.matched + self.residue.compacted_out_excluded == self.total
229 && self.residue.is_semantically_lossless()
230 }
231}
232
233pub fn measure_fidelity(source: &[ChatMessage], reloaded: &[ChatMessage]) -> FidelityMetric {
235 let mut residue = FidelityResidue::default();
236 let mut matched = 0;
237 let mut reload_index = 0;
238 for source_message in source {
239 let found = (reload_index..reloaded.len())
240 .find(|index| messages_equal_multimodal(source_message, &reloaded[*index]));
241 match found {
242 Some(index) => {
243 matched += 1;
244 for (key, value) in &source_message.metadata {
245 if reloaded[index].metadata.get(key) != Some(value) {
246 residue.dropped_metadata_keys.insert(key.clone());
247 }
248 }
249 reload_index = index + 1;
250 }
251 None if replay_excluded(source_message) => residue.compacted_out_excluded += 1,
252 None => residue.other_dropped_messages += 1,
253 }
254 }
255 FidelityMetric {
256 matched,
257 total: source.len(),
258 residue,
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn semantic_losslessness_ignores_only_explicit_replay_exclusions() {
268 let mut excluded = ChatMessage::user("old");
269 excluded
270 .metadata
271 .insert("compacted_out".into(), "true".into());
272 let kept = ChatMessage::user("new");
273 let metric = measure_fidelity(&[excluded, kept.clone()], &[kept]);
274 assert_eq!(metric.percent(), 50.0);
275 assert!(metric.is_semantically_lossless());
276 }
277
278 #[test]
279 fn changed_metadata_values_are_semantic_residue() {
280 let mut source = ChatMessage::user("hello");
281 source.metadata.insert("model".into(), "alpha".into());
282 let mut reloaded = source.clone();
283 reloaded.metadata.insert("model".into(), "beta".into());
284
285 let metric = measure_fidelity(&[source], &[reloaded]);
286
287 assert_eq!(metric.matched, 1);
288 assert_eq!(
289 metric.residue.dropped_metadata_keys,
290 BTreeSet::from(["model".into()])
291 );
292 assert!(!metric.is_semantically_lossless());
293 }
294}