use axum::{
body::Body,
http::{Request, StatusCode},
};
use serde_json::Value;
use tower::util::ServiceExt;
use super::native_routes_2376::{body_string, post, quantized_state};
use crate::api::create_router;
const CHAT: &str = "/v1/chat/completions";
fn chat_body(extra: &str) -> String {
format!(
r#"{{"model":"default","messages":[{{"role":"user","content":"Hi"}}],"max_tokens":4,"temperature":0,"ignore_eos":true{extra}}}"#
)
}
async fn chat_json(extra: &str) -> Value {
let (status, body) = post(quantized_state(), CHAT, &chat_body(extra)).await;
assert_eq!(status, StatusCode::OK, "{body}");
serde_json::from_str(&body).expect("chat reply is JSON")
}
fn logprob(entry: &Value) -> f64 {
entry["logprob"].as_f64().expect("logprob is a number")
}
#[tokio::test]
async fn logprobs_has_one_entry_per_completion_token_with_its_best_alternatives() {
let reply = chat_json(r#","logprobs":true,"top_logprobs":3"#).await;
let completion_tokens = reply["usage"]["completion_tokens"]
.as_u64()
.expect("usage.completion_tokens");
let content = reply["choices"][0]["logprobs"]["content"]
.as_array()
.expect("choices[0].logprobs.content is an array");
assert!(
completion_tokens > 0,
"the fixture generated nothing: {reply}"
);
assert_eq!(content.len() as u64, completion_tokens, "{reply}");
for entry in content {
assert!(entry["token"].is_string(), "{entry}");
assert!(entry["bytes"].is_array(), "{entry}");
assert!(logprob(entry) <= 0.0, "{entry}");
let tops = entry["top_logprobs"].as_array().expect("top_logprobs");
assert_eq!(tops.len(), 3, "{entry}");
assert!(
tops.windows(2).all(|w| logprob(&w[0]) >= logprob(&w[1])),
"{entry}"
);
assert!(tops.iter().all(|t| logprob(t) <= 0.0), "{entry}");
assert!((logprob(entry) - logprob(&tops[0])).abs() < 1e-4, "{entry}");
}
}
#[tokio::test]
async fn asking_for_logprobs_changes_no_token_and_not_asking_adds_no_key() {
let with = chat_json(r#","logprobs":true,"top_logprobs":2"#).await;
let without = chat_json("").await;
assert_eq!(
with["choices"][0]["message"]["content"],
without["choices"][0]["message"]["content"]
);
assert_eq!(
with["usage"]["completion_tokens"],
without["usage"]["completion_tokens"]
);
let choice = without["choices"][0].as_object().expect("choice object");
assert!(!choice.contains_key("logprobs"), "{without}");
}
#[tokio::test]
async fn logprobs_without_top_logprobs_carries_empty_alternatives() {
let reply = chat_json(r#","logprobs":true"#).await;
let content = reply["choices"][0]["logprobs"]["content"]
.as_array()
.expect("content");
assert!(!content.is_empty(), "{reply}");
assert!(content
.iter()
.all(|e| e["top_logprobs"].as_array().is_some_and(Vec::is_empty)));
}
#[tokio::test]
async fn top_logprobs_out_of_range_or_without_logprobs_is_refused_naming_the_field() {
for extra in [
r#","logprobs":true,"top_logprobs":21"#,
r#","logprobs":true,"top_logprobs":-1"#,
r#","top_logprobs":2"#,
r#","logprobs":false,"top_logprobs":2"#,
] {
let (status, body) = post(quantized_state(), CHAT, &chat_body(extra)).await;
assert!(status.is_client_error(), "{extra}: {status} {body}");
assert!(body.contains("top_logprobs"), "{extra}: {body}");
}
}
fn stream_chunks(body: &str) -> Vec<Value> {
body.lines()
.filter_map(|line| line.strip_prefix("data: "))
.filter(|data| *data != "[DONE]")
.map(|data| serde_json::from_str(data).expect("an SSE chunk is JSON"))
.collect()
}
async fn stream_chunks_of(extra: &str) -> Vec<Value> {
let body = chat_body(&format!(r#","stream":true{extra}"#));
let (status, body) = post(quantized_state(), CHAT, &body).await;
assert_eq!(status, StatusCode::OK, "{body}");
let chunks = stream_chunks(&body);
assert!(!chunks.is_empty(), "no chunks: {body}");
chunks
}
#[tokio::test]
async fn a_stream_carries_on_its_chunks_the_entries_of_the_same_reply_unstreamed() {
let chunks = stream_chunks_of(r#","logprobs":true,"top_logprobs":2"#).await;
let completion_tokens = chunks
.iter()
.find_map(|c| c["usage"]["completion_tokens"].as_u64())
.expect("the terminal chunk carries usage");
let streamed: Vec<&Value> = chunks
.iter()
.filter_map(|c| c["choices"][0]["logprobs"]["content"].as_array())
.flatten()
.collect();
assert!(completion_tokens > 0, "the fixture streamed nothing");
assert_eq!(streamed.len() as u64, completion_tokens, "{chunks:?}");
for entry in &streamed {
let tops = entry["top_logprobs"].as_array().expect("top_logprobs");
assert_eq!(tops.len(), 2, "{entry}");
assert!(logprob(entry) <= 0.0, "{entry}");
assert!((logprob(entry) - logprob(&tops[0])).abs() < 1e-4, "{entry}");
}
let reply = chat_json(r#","logprobs":true,"top_logprobs":2"#).await;
let unstreamed = reply["choices"][0]["logprobs"]["content"]
.as_array()
.expect("content");
assert_eq!(streamed.len(), unstreamed.len(), "{reply}");
for (s, u) in streamed.iter().zip(unstreamed) {
assert_eq!(s["token"], u["token"], "{s} vs {u}");
assert!((logprob(s) - logprob(u)).abs() < 1e-4, "{s} vs {u}");
}
}
#[tokio::test]
async fn a_stream_without_logprobs_has_no_logprobs_key_on_any_chunk() {
for chunk in stream_chunks_of("").await {
let choice = chunk["choices"][0].as_object().expect("choice object");
assert!(!choice.contains_key("logprobs"), "{chunk}");
}
}
#[tokio::test]
async fn a_traced_request_for_logprobs_is_refused_not_served_without_them() {
let request = Request::builder()
.method("POST")
.uri(CHAT)
.header("content-type", "application/json")
.header("X-Trace-Level", "brick")
.body(Body::from(chat_body(r#","logprobs":true"#)))
.expect("build request");
let response = create_router(quantized_state())
.oneshot(request)
.await
.expect("dispatch");
let status = response.status();
let body = body_string(response).await;
assert_eq!(status, StatusCode::NOT_IMPLEMENTED, "{body}");
assert!(body.contains("quantized (traced)"), "{body}");
}