1use async_trait::async_trait;
7use lc_core::language_models::LLMResult;
8use lc_core::{BaseChatModel, Runnable};
9use lc_schema::Message;
10use serde_json::Value;
11use std::collections::HashMap;
12use std::sync::Arc;
13
14use crate::base::{BaseChain, ChainError, ChainResult};
15
16pub struct RouteDestination {
18 name: String,
20 description: String,
22 chain: Arc<dyn BaseChain>,
24 keywords: Vec<String>,
26}
27
28impl RouteDestination {
29 pub fn new(
30 name: impl Into<String>,
31 description: impl Into<String>,
32 chain: Arc<dyn BaseChain>,
33 ) -> Self {
34 Self {
35 name: name.into(),
36 description: description.into(),
37 chain,
38 keywords: Vec::new(),
39 }
40 }
41
42 pub fn with_keywords(mut self, keywords: Vec<&str>) -> Self {
43 self.keywords = keywords.into_iter().map(String::from).collect();
44 self
45 }
46
47 pub fn name(&self) -> &str {
48 &self.name
49 }
50
51 pub fn description(&self) -> &str {
52 &self.description
53 }
54
55 pub fn chain(&self) -> &Arc<dyn BaseChain> {
56 &self.chain
57 }
58
59 pub fn keywords(&self) -> &[String] {
60 &self.keywords
61 }
62}
63
64pub struct RouterChain {
68 destinations: Vec<RouteDestination>,
70
71 default_chain: Option<Arc<dyn BaseChain>>,
73
74 input_key: String,
76
77 name: String,
79
80 verbose: bool,
82}
83
84impl RouterChain {
85 pub fn new() -> Self {
86 Self {
87 destinations: Vec::new(),
88 default_chain: None,
89 input_key: "input".to_string(),
90 name: "router_chain".to_string(),
91 verbose: false,
92 }
93 }
94
95 pub fn add_route(
96 mut self,
97 name: impl Into<String>,
98 description: impl Into<String>,
99 chain: Arc<dyn BaseChain>,
100 ) -> Self {
101 self.destinations
102 .push(RouteDestination::new(name, description, chain));
103 self
104 }
105
106 pub fn add_route_with_keywords(
107 mut self,
108 name: impl Into<String>,
109 description: impl Into<String>,
110 chain: Arc<dyn BaseChain>,
111 keywords: Vec<&str>,
112 ) -> Self {
113 self.destinations
114 .push(RouteDestination::new(name, description, chain).with_keywords(keywords));
115 self
116 }
117
118 pub fn with_default(mut self, chain: Arc<dyn BaseChain>) -> Self {
119 self.default_chain = Some(chain);
120 self
121 }
122
123 pub fn with_input_key(mut self, key: impl Into<String>) -> Self {
124 self.input_key = key.into();
125 self
126 }
127
128 pub fn with_name(mut self, name: impl Into<String>) -> Self {
129 self.name = name.into();
130 self
131 }
132
133 pub fn with_verbose(mut self, verbose: bool) -> Self {
134 self.verbose = verbose;
135 self
136 }
137
138 pub fn destinations(&self) -> &[RouteDestination] {
139 &self.destinations
140 }
141
142 pub fn default_chain(&self) -> Option<&Arc<dyn BaseChain>> {
143 self.default_chain.as_ref()
144 }
145
146 fn route_by_keywords(&self, input: &str) -> Option<&RouteDestination> {
150 let mut best_match: Option<(&RouteDestination, usize)> = None;
151 for dest in &self.destinations {
152 for keyword in &dest.keywords {
153 if input.contains(keyword) {
154 let len = keyword.len();
155 if best_match.is_none() || len > best_match.unwrap().1 {
156 best_match = Some((dest, len));
157 }
158 }
159 }
160 }
161 best_match.map(|(dest, _)| dest)
162 }
163
164 fn select_route(&self, input: &str) -> Result<Option<&RouteDestination>, ChainError> {
166 if let Some(dest) = self.route_by_keywords(input) {
167 return Ok(Some(dest));
168 }
169
170 Ok(None)
171 }
172}
173
174impl Default for RouterChain {
175 fn default() -> Self {
176 Self::new()
177 }
178}
179
180#[async_trait]
181impl BaseChain for RouterChain {
182 fn input_keys(&self) -> Vec<&str> {
183 vec![&self.input_key]
184 }
185
186 fn output_keys(&self) -> Vec<&str> {
187 let mut seen = std::collections::HashSet::new();
188 let mut result: Vec<&str> = Vec::new();
189
190 for dest in &self.destinations {
191 for key in dest.chain().output_keys() {
192 if seen.insert(key.to_string()) {
193 result.push(key);
194 }
195 }
196 }
197 if let Some(default) = &self.default_chain {
198 for key in default.output_keys() {
199 if seen.insert(key.to_string()) {
200 result.push(key);
201 }
202 }
203 }
204
205 if result.is_empty() {
206 vec!["output"]
207 } else {
208 result
209 }
210 }
211
212 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
213 self.validate_inputs(&inputs)?;
214
215 let input = inputs
216 .get(&self.input_key)
217 .and_then(|v| v.as_str())
218 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
219
220 if self.verbose {
221 println!("\n=== RouterChain execution ===");
222 println!("Input: {}", input);
223 println!("Route destination count: {}", self.destinations.len());
224 }
225
226 let route_result = self.select_route(input)?;
227
228 let chain = match route_result {
229 Some(dest) => {
230 if self.verbose {
231 println!("Routed to: {} ({})", dest.name(), dest.description());
232 }
233 dest.chain()
234 }
235 None => {
236 if let Some(default) = &self.default_chain {
237 if self.verbose {
238 println!("No keyword match, using default Chain");
239 }
240 default
241 } else {
242 return Err(ChainError::ExecutionError(
243 "No matching route destination and no default Chain configured".to_string(),
244 ));
245 }
246 }
247 };
248
249 let result = chain.invoke(inputs).await?;
250
251 if self.verbose {
252 println!("=== RouterChain complete ===\n");
253 }
254
255 Ok(result)
256 }
257
258 fn name(&self) -> &str {
259 &self.name
260 }
261}
262
263pub struct LLMRouterChain<M: BaseChatModel> {
267 llm: M,
269
270 destinations: Vec<RouteDestination>,
272
273 default_chain: Option<Arc<dyn BaseChain>>,
275
276 input_key: String,
278
279 name: String,
281
282 verbose: bool,
284}
285
286impl<M: BaseChatModel> LLMRouterChain<M> {
287 pub fn new(llm: M) -> Self {
288 Self {
289 llm,
290 destinations: Vec::new(),
291 default_chain: None,
292 input_key: "input".to_string(),
293 name: "llm_router_chain".to_string(),
294 verbose: false,
295 }
296 }
297
298 pub fn add_route(
299 mut self,
300 name: impl Into<String>,
301 description: impl Into<String>,
302 chain: Arc<dyn BaseChain>,
303 ) -> Self {
304 self.destinations
305 .push(RouteDestination::new(name, description, chain));
306 self
307 }
308
309 pub fn add_route_with_keywords(
310 mut self,
311 name: impl Into<String>,
312 description: impl Into<String>,
313 chain: Arc<dyn BaseChain>,
314 keywords: Vec<&str>,
315 ) -> Self {
316 self.destinations
317 .push(RouteDestination::new(name, description, chain).with_keywords(keywords));
318 self
319 }
320
321 pub fn with_default(mut self, chain: Arc<dyn BaseChain>) -> Self {
322 self.default_chain = Some(chain);
323 self
324 }
325
326 pub fn with_input_key(mut self, key: impl Into<String>) -> Self {
327 self.input_key = key.into();
328 self
329 }
330
331 pub fn with_name(mut self, name: impl Into<String>) -> Self {
332 self.name = name.into();
333 self
334 }
335
336 pub fn with_verbose(mut self, verbose: bool) -> Self {
337 self.verbose = verbose;
338 self
339 }
340
341 pub fn destinations(&self) -> &[RouteDestination] {
342 &self.destinations
343 }
344
345 pub fn default_chain(&self) -> Option<&Arc<dyn BaseChain>> {
346 self.default_chain.as_ref()
347 }
348
349 fn build_router_prompt(&self, input: &str) -> String {
351 let mut prompt =
352 String::from("Based on the user input, select the most appropriate handler.\n\n");
353 prompt.push_str("Available handlers:\n");
354
355 for (i, dest) in self.destinations.iter().enumerate() {
356 prompt.push_str(&format!(
357 "{}. {}: {}\n",
358 i + 1,
359 dest.name(),
360 dest.description()
361 ));
362 }
363
364 prompt.push_str("\nUser input: ");
365 prompt.push_str(input);
366 prompt
367 .push_str("\n\nReturn only the name of the most appropriate handler (no explanation).");
368
369 prompt
370 }
371
372 async fn route_with_llm(&self, input: &str) -> Result<String, ChainError> {
374 let prompt = self.build_router_prompt(input);
375
376 let messages = vec![Message::human(&prompt)];
377
378 let result = self
379 .llm
380 .invoke(messages, None)
381 .await
382 .map_err(|e| ChainError::ExecutionError(format!("LLM call failed: {}", e)))?;
383
384 Ok(result.content.trim().to_string())
385 }
386
387 fn find_destination(&self, name: &str) -> Option<&RouteDestination> {
389 let name_lower = name.to_lowercase();
390 if let Some(dest) = self
392 .destinations
393 .iter()
394 .find(|d| d.name().eq_ignore_ascii_case(name))
395 {
396 return Some(dest);
397 }
398 self.destinations.iter().find(|d| {
400 let d_lower = d.name().to_lowercase();
401 name_lower.starts_with(&d_lower)
402 || name_lower.ends_with(&d_lower)
403 || name_lower
404 .split_whitespace()
405 .any(|word| word.eq_ignore_ascii_case(&d_lower))
406 })
407 }
408
409 async fn select_route(&self, input: &str) -> Result<&RouteDestination, ChainError> {
411 if self.destinations.is_empty() {
412 return Err(ChainError::ExecutionError(
413 "No route destinations configured".to_string(),
414 ));
415 }
416
417 if self.destinations.len() == 1 {
418 return Ok(&self.destinations[0]);
419 }
420
421 let llm_result = self.route_with_llm(input).await;
423 match llm_result {
424 Ok(route_name) => {
425 if let Some(dest) = self.find_destination(&route_name) {
426 return Ok(dest);
427 }
428 }
429 Err(_) => {
430 }
432 }
433
434 let mut best_match: Option<(&RouteDestination, usize)> = None;
436 for dest in &self.destinations {
437 for keyword in dest.keywords() {
438 if input.contains(keyword) {
439 let len = keyword.len();
440 if best_match.is_none() || len > best_match.unwrap().1 {
441 best_match = Some((dest, len));
442 }
443 }
444 }
445 }
446 if let Some((dest, _)) = best_match {
447 return Ok(dest);
448 }
449
450 Err(ChainError::ExecutionError(
451 "No matching route destination found (LLM and keyword matching both failed)"
452 .to_string(),
453 ))
454 }
455}
456
457#[async_trait]
458impl<M: BaseChatModel + Send + Sync + 'static> BaseChain for LLMRouterChain<M>
459where
460 <M as Runnable<Vec<Message>, LLMResult>>::Error: std::fmt::Display,
461{
462 fn input_keys(&self) -> Vec<&str> {
463 vec![&self.input_key]
464 }
465
466 fn output_keys(&self) -> Vec<&str> {
467 let mut seen = std::collections::HashSet::new();
468 let mut result: Vec<&str> = Vec::new();
469
470 for dest in &self.destinations {
471 for key in dest.chain().output_keys() {
472 if seen.insert(key.to_string()) {
473 result.push(key);
474 }
475 }
476 }
477 if let Some(default) = &self.default_chain {
478 for key in default.output_keys() {
479 if seen.insert(key.to_string()) {
480 result.push(key);
481 }
482 }
483 }
484
485 if result.is_empty() {
486 vec!["output"]
487 } else {
488 result
489 }
490 }
491
492 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
493 self.validate_inputs(&inputs)?;
494
495 let input = inputs
496 .get(&self.input_key)
497 .and_then(|v| v.as_str())
498 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
499
500 if self.verbose {
501 println!("\n=== LLMRouterChain execution ===");
502 println!("Input: {}", input);
503 println!("Route destination count: {}", self.destinations.len());
504 }
505
506 let route_result = self.select_route(input).await;
507
508 let chain = match route_result {
509 Ok(dest) => {
510 if self.verbose {
511 println!("Routed to: {} ({})", dest.name(), dest.description());
512 }
513 dest.chain()
514 }
515 Err(e) => {
516 if let Some(default) = &self.default_chain {
517 if self.verbose {
518 println!("Routing failed: {}, using default Chain", e);
519 }
520 default
521 } else {
522 return Err(e);
523 }
524 }
525 };
526
527 let result = chain.invoke(inputs).await?;
528
529 if self.verbose {
530 println!("=== LLMRouterChain complete ===\n");
531 }
532
533 Ok(result)
534 }
535
536 fn name(&self) -> &str {
537 &self.name
538 }
539}