switchyard_llm_client/
error.rs1use serde_json::Value;
10
11pub use switchyard_protocol::LlmClientError;
12
13pub type Result<T> = std::result::Result<T, LlmClientError>;
15
16pub(crate) fn is_overflow_body<F>(body: &str, structured_check: F, phrases: &[&str]) -> bool
24where
25 F: Fn(&Value) -> bool,
26{
27 if let Ok(value) = serde_json::from_str::<Value>(body) {
28 if structured_check(&value) {
29 return true;
30 }
31 let error = value.get("error").or_else(|| {
32 (value.get("object").and_then(Value::as_str) == Some("error")).then_some(&value)
33 });
34 return error
35 .and_then(|err| err.get("message"))
36 .and_then(Value::as_str)
37 .is_some_and(|message| contains_any(message, phrases));
38 }
39 contains_any(body, phrases)
42}
43
44fn contains_any(message: &str, phrases: &[&str]) -> bool {
46 let lower = message.to_ascii_lowercase();
47 phrases.iter().any(|phrase| lower.contains(phrase))
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 const PHRASES: &[&str] = &["context window", "too long"];
55
56 fn never(_value: &Value) -> bool {
57 false
58 }
59
60 #[test]
61 fn structured_check_short_circuits() {
62 let body = r#"{"error":{"code":"context_length_exceeded","message":"unrelated"}}"#;
63 let matched = is_overflow_body(
64 body,
65 |value| {
66 value
67 .get("error")
68 .and_then(|err| err.get("code"))
69 .and_then(Value::as_str)
70 == Some("context_length_exceeded")
71 },
72 &[],
73 );
74 assert!(matched);
75 }
76
77 #[test]
78 fn falls_back_to_message_phrase_match() {
79 let body = r#"{"error":{"message":"prompt too long"}}"#;
80 assert!(is_overflow_body(body, never, PHRASES));
81 }
82
83 #[test]
84 fn matches_plain_text_body() {
85 assert!(is_overflow_body(
86 "plain text mentioning context window",
87 never,
88 PHRASES
89 ));
90 }
91
92 #[test]
93 fn non_match_returns_false() {
94 let body = r#"{"error":{"message":"rate limit exceeded"}}"#;
95 assert!(!is_overflow_body(body, never, PHRASES));
96 }
97}