Skip to main content

synapse/routing/
classify.rs

1//! Pure lane detection: standard vs native-Vertex.
2
3use crate::routing::request::ChatRequest;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Lane {
7    Standard,
8    NativeVertex,
9    /// TypeSafe System One (Jev): the request carries a `jev` extension block
10    /// with typed questions to evaluate against the request state.
11    Jev,
12}
13
14/// Native-Vertex triggers on the `vertex` extension block. Also consulted by
15/// the Jev lane, where these triggers describe the native-Vertex fallback
16/// (the `jev` block itself takes lane precedence).
17pub(crate) fn vertex_triggers(req: &ChatRequest) -> bool {
18    let v = match &req.vertex {
19        Some(v) => v,
20        None => return false,
21    };
22    v.cached_content.is_some()
23        || v.response_schema.is_some()
24        || v.thinking_config.is_some()
25        || v.media_uris
26            .as_ref()
27            .is_some_and(|uris| uris.iter().any(|u| u.starts_with("gs://")))
28}
29
30/// Classify by inspecting the request for lane triggers.
31/// Pure and allocation-free; safe to call on the hot path.
32pub fn classify(req: &ChatRequest) -> Lane {
33    if req.jev.as_ref().is_some_and(|j| !j.questions.is_empty()) {
34        return Lane::Jev;
35    }
36    if vertex_triggers(req) {
37        Lane::NativeVertex
38    } else {
39        Lane::Standard
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::routing::request::{ChatRequest, VertexExt};
47
48    fn base() -> ChatRequest {
49        serde_json::from_value(serde_json::json!({
50            "model": "m", "messages": [{"role": "user", "content": "hi"}]
51        }))
52        .unwrap()
53    }
54
55    #[test]
56    fn no_vertex_block_is_standard() {
57        assert_eq!(classify(&base()), Lane::Standard);
58    }
59
60    #[test]
61    fn cached_content_is_native() {
62        let req = ChatRequest {
63            vertex: Some(VertexExt {
64                cached_content: Some("cachedContents/x".into()),
65                ..Default::default()
66            }),
67            ..base()
68        };
69        assert_eq!(classify(&req), Lane::NativeVertex);
70    }
71
72    #[test]
73    fn response_schema_is_native() {
74        let req = ChatRequest {
75            vertex: Some(VertexExt {
76                response_schema: Some(serde_json::json!({"type": "object"})),
77                ..Default::default()
78            }),
79            ..base()
80        };
81        assert_eq!(classify(&req), Lane::NativeVertex);
82    }
83
84    #[test]
85    fn gs_media_uri_is_native_but_https_is_not() {
86        let gs = ChatRequest {
87            vertex: Some(VertexExt {
88                media_uris: Some(vec!["gs://b/v.mp4".into()]),
89                ..Default::default()
90            }),
91            ..base()
92        };
93        assert_eq!(classify(&gs), Lane::NativeVertex);
94        let https = ChatRequest {
95            vertex: Some(VertexExt {
96                media_uris: Some(vec!["https://x/v.mp4".into()]),
97                ..Default::default()
98            }),
99            ..base()
100        };
101        assert_eq!(classify(&https), Lane::Standard);
102    }
103
104    #[test]
105    fn jev_questions_take_lane_precedence() {
106        let req = ChatRequest {
107            jev: Some(crate::routing::request::JevExt {
108                questions: serde_json::json!({"q": {"type": "noul"}})
109                    .as_object()
110                    .unwrap()
111                    .clone(),
112                ..Default::default()
113            }),
114            vertex: Some(crate::routing::request::VertexExt {
115                response_schema: Some(serde_json::json!({"type": "object"})),
116                ..Default::default()
117            }),
118            ..base()
119        };
120        // The vertex block stays in force for the fallback; the lane is Jev.
121        assert_eq!(classify(&req), Lane::Jev);
122        assert!(super::vertex_triggers(&req));
123    }
124
125    #[test]
126    fn empty_jev_questions_do_not_claim_the_lane() {
127        let req = ChatRequest {
128            jev: Some(crate::routing::request::JevExt::default()),
129            vertex: Some(crate::routing::request::VertexExt {
130                response_schema: Some(serde_json::json!({"type": "object"})),
131                ..Default::default()
132            }),
133            ..base()
134        };
135        assert_eq!(classify(&req), Lane::NativeVertex);
136    }
137
138    #[test]
139    fn thinking_config_is_native() {
140        let req = ChatRequest {
141            vertex: Some(VertexExt {
142                thinking_config: Some(serde_json::json!({ "thinkingBudget": 0 })),
143                ..Default::default()
144            }),
145            ..base()
146        };
147        assert_eq!(classify(&req), Lane::NativeVertex);
148    }
149}