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