langchain_rust/chain/router/
chain.rs1use std::collections::HashMap;
2
3use async_trait::async_trait;
4use futures::StreamExt;
5use serde_json::{json, Value};
6
7use crate::{
8 chain::{Chain, ChainError, ChainEventStream, DEFAULT_OUTPUT_KEY, DEFAULT_RESULT_KEY},
9 language_models::{llm::LLM, GenerateResult, LLMEvent, TokenUsage},
10 observability::traced,
11 prompt::PromptArgs,
12 schemas::{messages::value_to_text, Message},
13};
14
15pub const ROUTER_DESTINATION_KEY: &str = "destination";
18pub const DEFAULT_DESTINATION: &str = "DEFAULT";
20const ROUTER_DEFAULT_INPUT_KEY: &str = "input";
21
22const ROUTER_PROMPT: &str = "Given a raw text input, select the destination best suited for it. \
23You will be given the names of the available destinations and a description of what each one \
24is good for.
25
26<< DESTINATIONS >>
27{destinations}
28
29Answer with a JSON object and nothing else:
30{\"destination\": \"<one of the destination names above, or DEFAULT if none of them fits>\"}";
31
32struct Destination {
33 name: String,
34 description: String,
35 chain: Box<dyn Chain>,
36}
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct Route {
41 pub destination: Option<String>,
43 pub tokens: Option<TokenUsage>,
45}
46
47pub struct RouterChain {
64 llm: Box<dyn LLM>,
65 destinations: Vec<Destination>,
66 default_chain: Option<Box<dyn Chain>>,
67 input_key: String,
68 output_key: String,
69}
70
71pub struct RouterChainBuilder {
72 llm: Option<Box<dyn LLM>>,
73 destinations: Vec<Destination>,
74 default_chain: Option<Box<dyn Chain>>,
75 input_key: String,
76 output_key: String,
77}
78
79impl Default for RouterChainBuilder {
80 fn default() -> Self {
81 Self::new()
82 }
83}
84
85impl RouterChainBuilder {
86 pub fn new() -> Self {
87 Self {
88 llm: None,
89 destinations: Vec::new(),
90 default_chain: None,
91 input_key: ROUTER_DEFAULT_INPUT_KEY.to_string(),
92 output_key: DEFAULT_OUTPUT_KEY.to_string(),
93 }
94 }
95
96 pub fn llm<L: Into<Box<dyn LLM>>>(mut self, llm: L) -> Self {
98 self.llm = Some(llm.into());
99 self
100 }
101
102 pub fn destination<C: Into<Box<dyn Chain>>>(
105 mut self,
106 name: &str,
107 description: &str,
108 chain: C,
109 ) -> Self {
110 self.destinations.push(Destination {
111 name: name.trim().to_string(),
112 description: description.trim().to_string(),
113 chain: chain.into(),
114 });
115 self
116 }
117
118 pub fn default_chain<C: Into<Box<dyn Chain>>>(mut self, chain: C) -> Self {
120 self.default_chain = Some(chain.into());
121 self
122 }
123
124 pub fn input_key(mut self, key: &str) -> Self {
127 self.input_key = key.to_string();
128 self
129 }
130
131 pub fn output_key(mut self, key: &str) -> Self {
133 self.output_key = key.to_string();
134 self
135 }
136
137 pub fn build(self) -> Result<RouterChain, ChainError> {
138 let llm = self
139 .llm
140 .ok_or_else(|| ChainError::MissingObject("llm".into()))?;
141 if self.destinations.is_empty() {
142 return Err(ChainError::MissingObject("destination".into()));
143 }
144 for (i, dest) in self.destinations.iter().enumerate() {
145 if dest.name.is_empty() || dest.name.eq_ignore_ascii_case(DEFAULT_DESTINATION) {
146 return Err(ChainError::OtherError(format!(
147 "invalid router destination name {:?}",
148 dest.name
149 )));
150 }
151 if self.destinations[..i]
152 .iter()
153 .any(|d| d.name.eq_ignore_ascii_case(&dest.name))
154 {
155 return Err(ChainError::OtherError(format!(
156 "duplicate router destination {:?}",
157 dest.name
158 )));
159 }
160 }
161 Ok(RouterChain {
162 llm,
163 destinations: self.destinations,
164 default_chain: self.default_chain,
165 input_key: self.input_key,
166 output_key: self.output_key,
167 })
168 }
169}
170
171pub(super) fn parse_destination(answer: &str) -> String {
174 if let (Some(start), Some(end)) = (answer.find('{'), answer.rfind('}')) {
175 if start < end {
176 if let Ok(Value::Object(obj)) = serde_json::from_str::<Value>(&answer[start..=end]) {
177 if let Some(Value::String(name)) = obj.get("destination") {
178 return name.trim().to_string();
179 }
180 }
181 }
182 }
183 answer
184 .trim()
185 .trim_matches(|c: char| c == '"' || c == '\'' || c == '`' || c == '.')
186 .trim()
187 .to_string()
188}
189
190fn add_tokens(a: Option<TokenUsage>, b: Option<&TokenUsage>) -> Option<TokenUsage> {
191 match (a, b) {
192 (Some(a), Some(b)) => Some(a.sum(b)),
193 (a, b) => a.or_else(|| b.cloned()),
194 }
195}
196
197impl RouterChain {
198 pub(super) fn prompt(&self, input: &str) -> Vec<Message> {
199 let destinations = self
200 .destinations
201 .iter()
202 .map(|d| format!("{}: {}", d.name, d.description))
203 .collect::<Vec<_>>()
204 .join("\n");
205 vec![
206 Message::new_system_message(ROUTER_PROMPT.replace("{destinations}", &destinations)),
207 Message::new_human_message(input),
208 ]
209 }
210
211 pub async fn route(&self, input_variables: &PromptArgs) -> Result<Route, ChainError> {
213 let input = input_variables
214 .get(&self.input_key)
215 .map(value_to_text)
216 .ok_or_else(|| ChainError::MissingInputVariable(self.input_key.clone()))?;
217 let result = self.llm.generate(&self.prompt(&input)).await?;
218 let name = parse_destination(&result.generation);
219 let destination = self
220 .destinations
221 .iter()
222 .find(|d| d.name.eq_ignore_ascii_case(&name))
223 .map(|d| d.name.clone());
224 if destination.is_none() && !name.eq_ignore_ascii_case(DEFAULT_DESTINATION) {
225 tracing::warn!(answer = %result.generation, "router answered an unknown destination");
226 if self.default_chain.is_none() {
227 return Err(ChainError::OtherError(format!(
228 "router picked unknown destination {name:?} and there is no default chain; \
229 expected one of {:?}",
230 self.destinations
231 .iter()
232 .map(|d| &d.name)
233 .collect::<Vec<_>>()
234 )));
235 }
236 }
237 Ok(Route {
238 destination,
239 tokens: result.tokens,
240 })
241 }
242
243 fn chain_for(&self, route: &Route) -> Result<&dyn Chain, ChainError> {
244 match &route.destination {
245 Some(name) => Ok(self
246 .destinations
247 .iter()
248 .find(|d| &d.name == name)
249 .map(|d| d.chain.as_ref())
250 .expect("route() only returns known destinations")),
251 None => self.default_chain.as_deref().ok_or_else(|| {
252 ChainError::OtherError("no destination fits and there is no default chain".into())
253 }),
254 }
255 }
256
257 fn span(&self) -> tracing::Span {
258 tracing::info_span!(
259 "chain.call",
260 chain = "router",
261 destination = tracing::field::Empty,
262 error = tracing::field::Empty
263 )
264 }
265}
266
267fn destination_label(route: &Route) -> &str {
268 route.destination.as_deref().unwrap_or(DEFAULT_DESTINATION)
269}
270
271#[async_trait]
272impl Chain for RouterChain {
273 async fn call(&self, input_variables: PromptArgs) -> Result<GenerateResult, ChainError> {
274 let span = self.span();
275 traced(
276 span.clone(),
277 async {
278 let route = self.route(&input_variables).await?;
279 span.record("destination", destination_label(&route));
280 let mut result = self.chain_for(&route)?.call(input_variables).await?;
281 result.tokens = add_tokens(result.tokens, route.tokens.as_ref());
282 Ok(result)
283 },
284 |_, _| {},
285 )
286 .await
287 }
288
289 async fn execute(
292 &self,
293 input_variables: PromptArgs,
294 ) -> Result<HashMap<String, Value>, ChainError> {
295 let span = self.span();
296 traced(
297 span.clone(),
298 async {
299 let route = self.route(&input_variables).await?;
300 span.record("destination", destination_label(&route));
301 let chain = self.chain_for(&route)?;
302 let mut output = chain.execute(input_variables).await?;
303 let mut result: GenerateResult = match output.get(DEFAULT_RESULT_KEY) {
304 Some(result) => serde_json::from_value(result.clone())?,
305 None => {
306 let key = chain
307 .get_output_keys()
308 .first()
309 .cloned()
310 .unwrap_or_else(|| DEFAULT_OUTPUT_KEY.to_string());
311 GenerateResult {
312 generation: output.get(&key).map(value_to_text).unwrap_or_default(),
313 ..Default::default()
314 }
315 }
316 };
317 result.tokens = add_tokens(result.tokens, route.tokens.as_ref());
318 output.insert(self.output_key.clone(), json!(result.generation));
319 output.insert(DEFAULT_RESULT_KEY.to_string(), json!(result));
320 output.insert(
321 ROUTER_DESTINATION_KEY.to_string(),
322 json!(destination_label(&route)),
323 );
324 Ok(output)
325 },
326 |_, _| {},
327 )
328 .await
329 }
330
331 fn call_stream<'a>(&'a self, input_variables: PromptArgs) -> ChainEventStream<'a> {
334 Box::pin(
335 futures::stream::once(async move {
336 let routed = async {
337 let route = self.route(&input_variables).await?;
338 let chain = self.chain_for(&route)?;
339 Ok::<_, ChainError>((route, chain))
340 };
341 match routed.await {
342 Ok((route, chain)) => {
343 let tokens = route.tokens;
344 Box::pin(chain.call_stream(input_variables).map(move |event| {
345 event.map(|event| match event {
346 LLMEvent::Done(mut result) => {
347 result.tokens = add_tokens(result.tokens, tokens.as_ref());
348 LLMEvent::Done(result)
349 }
350 other => other,
351 })
352 })) as ChainEventStream<'a>
353 }
354 Err(e) => Box::pin(futures::stream::iter([Err(e)])) as ChainEventStream<'a>,
355 }
356 })
357 .flatten(),
358 )
359 }
360
361 fn get_input_keys(&self) -> Vec<String> {
362 vec![self.input_key.clone()]
363 }
364
365 fn get_output_keys(&self) -> Vec<String> {
366 vec![
367 self.output_key.clone(),
368 DEFAULT_RESULT_KEY.to_string(),
369 ROUTER_DESTINATION_KEY.to_string(),
370 ]
371 }
372}