metrale_bench/benchmarks/
media_integrity_responses.rs1use super::*;
10
11pub fn responses_image_request(model: &str, mime: &str, bytes: &[u8], prompt: &str) -> Value {
13 use base64::Engine;
14 let mut uri = format!("data:{mime};base64,");
15 base64::engine::general_purpose::STANDARD.encode_string(bytes, &mut uri);
16 json!({
17 "model": model,
18 "stream": false,
19 "temperature": 0.0,
20 "max_output_tokens": 600,
26 "reasoning": {"effort": "low"},
27 "input": [{"role": "user", "content": [
28 {"type": "input_image", "image_url": {"url": uri}},
29 {"type": "input_text", "text": prompt},
30 ]}],
31 })
32}
33
34pub async fn responses_parity(
54 handle: &PluginHandle,
55 smaller: Value,
56 larger: Value,
57 expect_delta: usize,
58 want: &(dyn Fn(&str) -> bool + Sync),
59 timeout: Duration,
60) -> Cell {
61 const ID: &str = "responses-api-parity";
62 let a = match http::responses_blocking(handle.target(), &smaller, timeout).await {
63 Ok(o) => o,
64 Err(e) => {
65 let msg = crate::benchmarks::one_line(format!("{e:#}"));
66 return if msg.contains("404") {
67 Cell::Skipped { id: ID, why: msg }
68 } else {
69 Cell::Error { id: ID, msg }
70 };
71 }
72 };
73 let b = match http::responses_blocking(handle.target(), &larger, timeout).await {
74 Ok(o) => o,
75 Err(e) => {
76 return Cell::Error {
77 id: ID,
78 msg: crate::benchmarks::one_line(format!("{e:#}")),
79 };
80 }
81 };
82 let delta = b.prompt_tokens.abs_diff(a.prompt_tokens);
83 if delta != expect_delta {
84 return Cell::Fail {
85 id: ID,
86 detail: format!(
87 "via /v1/responses the two images differ by {delta} prompt tokens, expected \
88 {expect_delta} ({} and {}) — the image is sized differently on this surface",
89 a.prompt_tokens, b.prompt_tokens
90 ),
91 };
92 }
93 let a_text = a.choices.first().cloned().unwrap_or_default();
94 let b_text = b.choices.first().cloned().unwrap_or_default();
95 if !want(a_text.trim()) || !want(b_text.trim()) {
96 return Cell::Fail {
97 id: ID,
98 detail: format!(
99 "sizing is right but the surface did not answer about the image: \"{}\"",
100 crate::benchmarks::one_line(a_text.chars().take(70).collect::<String>())
101 ),
102 };
103 }
104 Cell::Pass {
105 id: ID,
106 detail: format!(
107 "both images answered, and the image contributes exactly {expect_delta} tokens \
108 ({} vs {})",
109 a.prompt_tokens, b.prompt_tokens
110 ),
111 }
112}
113
114pub fn thinking_image_request(model: &str, mime: &str, bytes: &[u8], prompt: &str) -> Value {
116 let mut v = image_request(model, mime, bytes, prompt, 600);
117 v["chat_template_kwargs"] = json!({"enable_thinking": true});
118 v["max_tokens"] = json!(600);
121 v
122}
123
124pub async fn thinking_parity(
141 handle: &PluginHandle,
142 smaller: Value,
143 larger: Value,
144 expect_delta: usize,
145 want: &(dyn Fn(&str) -> bool + Sync),
146 timeout: Duration,
147) -> Cell {
148 const ID: &str = "thinking-on-vision";
149 let a = match http::chat_stream(handle.target(), &smaller, timeout).await {
150 Ok(o) => o,
151 Err(e) => {
152 return Cell::Error {
153 id: ID,
154 msg: crate::benchmarks::one_line(format!("{e:#}")),
155 };
156 }
157 };
158 let b = match http::chat_stream(handle.target(), &larger, timeout).await {
159 Ok(o) => o,
160 Err(e) => {
161 return Cell::Error {
162 id: ID,
163 msg: crate::benchmarks::one_line(format!("{e:#}")),
164 };
165 }
166 };
167 let delta = b.prompt_tokens.abs_diff(a.prompt_tokens);
168 if delta != expect_delta {
169 return Cell::Fail {
170 id: ID,
171 detail: format!(
172 "with thinking ON the two images differ by {delta} prompt tokens, expected \
173 {expect_delta} ({} and {} total) — the image's contribution changes when \
174 thinking is enabled",
175 a.prompt_tokens, b.prompt_tokens
176 ),
177 };
178 }
179 let answered = want(a.text.trim()) && want(b.text.trim());
182 if !answered {
183 let empty = a.text.trim().is_empty() || b.text.trim().is_empty();
184 return Cell::Fail {
185 id: ID,
186 detail: if empty {
187 "geometry is right but a reply came back EMPTY with thinking on — the \
188 reasoning block consumed the whole token budget"
189 .to_string()
190 } else {
191 format!(
192 "geometry is right but the answer is wrong with thinking on: \"{}\"",
193 crate::benchmarks::one_line(a.text.chars().take(60).collect::<String>())
194 )
195 },
196 };
197 }
198 Cell::Pass {
199 id: ID,
200 detail: format!(
201 "thinking on: both answered, and the image still contributes exactly \
202 {expect_delta} tokens ({} vs {})",
203 a.prompt_tokens, b.prompt_tokens
204 ),
205 }
206}