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