synapse/routing/
classify.rs1use crate::routing::request::ChatRequest;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Lane {
7 Standard,
8 NativeVertex,
9 Jev,
12}
13
14pub(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
30pub 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 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}