switchyard_libsy/algorithms/
passthrough.rs1use std::sync::Arc;
7
8use switchyard_protocol::{Request, Response};
9
10use crate::Result;
11use crate::core::algorithm::{Algorithm, Driver, LlmTarget};
12use switchyard_protocol::{Context, Decision, RoutedLlmClient};
13
14pub struct Passthrough {
16 target: LlmTarget,
17}
18
19impl Passthrough {
20 pub fn new(target: LlmTarget) -> Self {
22 Passthrough { target }
23 }
24}
25
26pub(crate) struct PassthroughDecision {
28 model_id: String,
29}
30
31impl Decision for PassthroughDecision {
32 fn selected_model(&self) -> &str {
33 &self.model_id
34 }
35 fn reasoning(&self) -> Option<&str> {
36 None
38 }
39 fn as_any(&self) -> &dyn std::any::Any {
40 self
41 }
42}
43
44#[async_trait::async_trait]
45impl Algorithm for Passthrough {
46 fn name(&self) -> &str {
47 "passthrough"
48 }
49
50 fn count_tokens_client(&self) -> Option<Arc<dyn RoutedLlmClient>> {
51 self.target
52 .llm_client
53 .as_ref()
54 .filter(|client| client.supports_count_tokens())
55 .cloned()
56 }
57
58 async fn create_run_task(
59 self: Arc<Self>,
60 ctx: Context,
61 driver: Driver,
62 request: Request,
63 ) -> Result<Response> {
64 let decision: Arc<dyn Decision> = Arc::new(PassthroughDecision {
65 model_id: self.target.semantic_name.clone(),
66 });
67 driver.info(ctx.clone(), decision.clone()).await?;
68 driver
69 .call_llm_target(ctx, &self.target, request, decision)
70 .await
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use std::sync::Arc;
77
78 use super::Passthrough;
79 use crate::core::algorithm::{Algorithm, LlmTarget};
80 use crate::text::{completion_text, text_request, text_response};
81 use switchyard_protocol::{Context, Decision, LlmResponse, Request, Response, RoutedLlmClient};
82
83 struct EchoClient;
86
87 #[async_trait::async_trait]
88 impl RoutedLlmClient for EchoClient {
89 async fn call(
90 &self,
91 _ctx: Context,
92 _request: Request,
93 decision: Arc<dyn Decision>,
94 ) -> std::result::Result<Response, switchyard_protocol::LlmClientError> {
95 Ok(Response {
96 llm_response: LlmResponse::Agg(text_response(None, decision.selected_model())),
97 metadata: None,
98 })
99 }
100 }
101
102 #[tokio::test]
103 async fn test_passthrough() -> crate::Result<()> {
104 const MODEL_ID: &str = "testing/passthrough";
105 let request = Request {
106 llm_request: text_request(Some("auto".to_string()), "hi"),
107 raw_request: None,
108 metadata: None,
109 };
110 let algorithm: Arc<dyn Algorithm> = Arc::new(Passthrough::new(LlmTarget {
111 semantic_name: MODEL_ID.to_string(),
112 llm_client: Some(Arc::new(EchoClient)),
113 }));
114 let (trace, response) = algorithm.run(Context::default(), request).await?;
115
116 assert_eq!(
117 response
118 .llm_response
119 .as_agg()
120 .map(completion_text)
121 .unwrap_or_default(),
122 MODEL_ID
123 );
124 assert_eq!(trace.len(), 1);
125 assert_eq!(trace[0].selected_model(), MODEL_ID);
126 Ok(())
127 }
128}