1use std::collections::HashMap;
5use std::sync::Arc;
6
7use async_trait::async_trait;
8use lc_core::runnables::RunnableConfig;
9use lc_core::tools::ToolDefinition;
10use serde::Deserialize;
11use serde_json::json;
12use serde_json::Value;
13
14use crate::base::{
15 run_chain_with_callbacks, stream_chain_with_callbacks, BaseChain, ChainError, ChainResult,
16 ChainStream,
17};
18
19use super::destination::RouteDestination;
20
21pub struct RouterChain {
25 destinations: Vec<RouteDestination>,
27
28 default_chain: Option<Arc<dyn BaseChain>>,
30
31 input_key: String,
33
34 name: String,
36
37 verbose: bool,
39}
40
41impl RouterChain {
42 pub fn new() -> Self {
44 Self {
45 destinations: Vec::new(),
46 default_chain: None,
47 input_key: "input".to_string(),
48 name: "router_chain".to_string(),
49 verbose: false,
50 }
51 }
52
53 pub fn add_route(
55 mut self,
56 name: impl Into<String>,
57 description: impl Into<String>,
58 chain: Arc<dyn BaseChain>,
59 ) -> Self {
60 self.destinations
61 .push(RouteDestination::new(name, description, chain));
62 self
63 }
64
65 pub fn add_route_with_keywords(
67 mut self,
68 name: impl Into<String>,
69 description: impl Into<String>,
70 chain: Arc<dyn BaseChain>,
71 keywords: Vec<&str>,
72 ) -> Self {
73 self.destinations
74 .push(RouteDestination::new(name, description, chain).with_keywords(keywords));
75 self
76 }
77
78 pub fn with_default(mut self, chain: Arc<dyn BaseChain>) -> Self {
80 self.default_chain = Some(chain);
81 self
82 }
83
84 pub fn with_input_key(mut self, key: impl Into<String>) -> Self {
86 self.input_key = key.into();
87 self
88 }
89
90 pub fn with_name(mut self, name: impl Into<String>) -> Self {
92 self.name = name.into();
93 self
94 }
95
96 pub fn with_verbose(mut self, verbose: bool) -> Self {
98 self.verbose = verbose;
99 self
100 }
101
102 pub fn destinations(&self) -> &[RouteDestination] {
104 &self.destinations
105 }
106
107 pub fn default_chain(&self) -> Option<&Arc<dyn BaseChain>> {
109 self.default_chain.as_ref()
110 }
111
112 fn route_by_keywords(&self, input: &str) -> Option<&RouteDestination> {
116 let mut best_match: Option<(&RouteDestination, usize)> = None;
117 for dest in &self.destinations {
118 for keyword in dest.keywords() {
119 if input.contains(keyword) {
120 let len = keyword.len();
121 if best_match.is_none_or(|(_, best_len)| len > best_len) {
123 best_match = Some((dest, len));
124 }
125 }
126 }
127 }
128 best_match.map(|(dest, _)| dest)
129 }
130
131 fn select_route(&self, input: &str) -> Result<Option<&RouteDestination>, ChainError> {
133 if let Some(dest) = self.route_by_keywords(input) {
134 return Ok(Some(dest));
135 }
136
137 Ok(None)
138 }
139
140 async fn route_and_invoke(
143 &self,
144 inputs: HashMap<String, Value>,
145 config: Option<RunnableConfig>,
146 ) -> Result<ChainResult, ChainError> {
147 self.validate_inputs(&inputs)?;
148
149 let input = inputs
150 .get(&self.input_key)
151 .and_then(|v| v.as_str())
152 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
153
154 if self.verbose {
155 println!("\n=== RouterChain execution ===");
156 println!("Input: {}", input);
157 println!("Route destination count: {}", self.destinations.len());
158 }
159
160 let route_result = self.select_route(input)?;
161
162 let chain = match route_result {
163 Some(dest) => {
164 if self.verbose {
165 println!("Routed to: {} ({})", dest.name(), dest.description());
166 }
167 dest.chain()
168 }
169 None => {
170 if let Some(default) = &self.default_chain {
171 if self.verbose {
172 println!("No keyword match, using default Chain");
173 }
174 default
175 } else {
176 return Err(ChainError::ExecutionError(
177 "No matching route destination and no default Chain configured".to_string(),
178 ));
179 }
180 }
181 };
182
183 let result = chain.invoke_with_config(inputs, config).await?;
184
185 if self.verbose {
186 println!("=== RouterChain complete ===\n");
187 }
188
189 Ok(result)
190 }
191
192 async fn route_and_stream(
195 &self,
196 inputs: HashMap<String, Value>,
197 config: Option<RunnableConfig>,
198 ) -> Result<ChainStream, ChainError> {
199 self.validate_inputs(&inputs)?;
200
201 let input = inputs
202 .get(&self.input_key)
203 .and_then(|v| v.as_str())
204 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
205
206 let route_result = self.select_route(input)?;
207
208 let chain = match route_result {
209 Some(dest) => dest.chain(),
210 None => self.default_chain.as_ref().ok_or_else(|| {
211 ChainError::ExecutionError(
212 "No matching route destination and no default Chain configured".to_string(),
213 )
214 })?,
215 };
216
217 chain.stream_with_config(inputs, config).await
218 }
219}
220
221impl Default for RouterChain {
222 fn default() -> Self {
223 Self::new()
224 }
225}
226
227#[async_trait]
228impl BaseChain for RouterChain {
229 fn input_keys(&self) -> Vec<&str> {
230 vec![&self.input_key]
231 }
232
233 fn output_keys(&self) -> Vec<&str> {
234 let mut seen = std::collections::HashSet::new();
235 let mut result: Vec<&str> = Vec::new();
236
237 for dest in &self.destinations {
238 for key in dest.chain().output_keys() {
239 if seen.insert(key.to_string()) {
240 result.push(key);
241 }
242 }
243 }
244 if let Some(default) = &self.default_chain {
245 for key in default.output_keys() {
246 if seen.insert(key.to_string()) {
247 result.push(key);
248 }
249 }
250 }
251
252 if result.is_empty() {
253 vec!["output"]
254 } else {
255 result
256 }
257 }
258
259 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
260 self.route_and_invoke(inputs, None).await
261 }
262
263 async fn invoke_with_config(
268 &self,
269 inputs: HashMap<String, Value>,
270 config: Option<RunnableConfig>,
271 ) -> Result<ChainResult, ChainError> {
272 run_chain_with_callbacks(self.name(), inputs, config.clone(), |inputs| async move {
273 self.route_and_invoke(inputs, config).await
274 })
275 .await
276 }
277
278 async fn stream(&self, inputs: HashMap<String, Value>) -> Result<ChainStream, ChainError> {
283 self.route_and_stream(inputs, None).await
284 }
285
286 async fn stream_with_config(
288 &self,
289 inputs: HashMap<String, Value>,
290 config: Option<RunnableConfig>,
291 ) -> Result<ChainStream, ChainError> {
292 let output_key = self.output_keys().first().map(|k| (*k).to_string());
293 stream_chain_with_callbacks(
294 self.name(),
295 inputs,
296 config.clone(),
297 output_key,
298 |inputs| async move { self.route_and_stream(inputs, config).await },
299 )
300 .await
301 }
302
303 fn name(&self) -> &str {
304 &self.name
305 }
306}
307
308#[derive(Debug, Clone, Deserialize)]
315pub(crate) struct RouteDecision {
316 pub destination: String,
318 pub reason: Option<String>,
320}
321
322impl RouteDecision {
323 pub(crate) fn from_text(text: &str) -> Self {
325 let trimmed = text.trim();
326 if let Ok(decision) = serde_json::from_str::<RouteDecision>(trimmed) {
327 return decision;
328 }
329 Self {
330 destination: trimmed.to_string(),
331 reason: None,
332 }
333 }
334}
335
336pub(crate) fn route_tool() -> ToolDefinition {
339 ToolDefinition::new(
340 "route_to_destination",
341 "根据用户输入选择最合适的处理 handler,返回目标名称与理由",
342 )
343 .with_parameters(json!({
344 "type": "object",
345 "properties": {
346 "destination": { "type": "string", "description": "目标 handler 名称" },
347 "reason": { "type": "string", "description": "选择该 handler 的理由" }
348 },
349 "required": ["destination"]
350 }))
351}