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() || len > best_match.unwrap().1 {
122 best_match = Some((dest, len));
123 }
124 }
125 }
126 }
127 best_match.map(|(dest, _)| dest)
128 }
129
130 fn select_route(&self, input: &str) -> Result<Option<&RouteDestination>, ChainError> {
132 if let Some(dest) = self.route_by_keywords(input) {
133 return Ok(Some(dest));
134 }
135
136 Ok(None)
137 }
138
139 async fn route_and_invoke(
142 &self,
143 inputs: HashMap<String, Value>,
144 config: Option<RunnableConfig>,
145 ) -> Result<ChainResult, ChainError> {
146 self.validate_inputs(&inputs)?;
147
148 let input = inputs
149 .get(&self.input_key)
150 .and_then(|v| v.as_str())
151 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
152
153 if self.verbose {
154 println!("\n=== RouterChain execution ===");
155 println!("Input: {}", input);
156 println!("Route destination count: {}", self.destinations.len());
157 }
158
159 let route_result = self.select_route(input)?;
160
161 let chain = match route_result {
162 Some(dest) => {
163 if self.verbose {
164 println!("Routed to: {} ({})", dest.name(), dest.description());
165 }
166 dest.chain()
167 }
168 None => {
169 if let Some(default) = &self.default_chain {
170 if self.verbose {
171 println!("No keyword match, using default Chain");
172 }
173 default
174 } else {
175 return Err(ChainError::ExecutionError(
176 "No matching route destination and no default Chain configured".to_string(),
177 ));
178 }
179 }
180 };
181
182 let result = chain.invoke_with_config(inputs, config).await?;
183
184 if self.verbose {
185 println!("=== RouterChain complete ===\n");
186 }
187
188 Ok(result)
189 }
190
191 async fn route_and_stream(
194 &self,
195 inputs: HashMap<String, Value>,
196 config: Option<RunnableConfig>,
197 ) -> Result<ChainStream, ChainError> {
198 self.validate_inputs(&inputs)?;
199
200 let input = inputs
201 .get(&self.input_key)
202 .and_then(|v| v.as_str())
203 .ok_or_else(|| ChainError::MissingInput(self.input_key.clone()))?;
204
205 let route_result = self.select_route(input)?;
206
207 let chain = match route_result {
208 Some(dest) => dest.chain(),
209 None => self.default_chain.as_ref().ok_or_else(|| {
210 ChainError::ExecutionError(
211 "No matching route destination and no default Chain configured".to_string(),
212 )
213 })?,
214 };
215
216 chain.stream_with_config(inputs, config).await
217 }
218}
219
220impl Default for RouterChain {
221 fn default() -> Self {
222 Self::new()
223 }
224}
225
226#[async_trait]
227impl BaseChain for RouterChain {
228 fn input_keys(&self) -> Vec<&str> {
229 vec![&self.input_key]
230 }
231
232 fn output_keys(&self) -> Vec<&str> {
233 let mut seen = std::collections::HashSet::new();
234 let mut result: Vec<&str> = Vec::new();
235
236 for dest in &self.destinations {
237 for key in dest.chain().output_keys() {
238 if seen.insert(key.to_string()) {
239 result.push(key);
240 }
241 }
242 }
243 if let Some(default) = &self.default_chain {
244 for key in default.output_keys() {
245 if seen.insert(key.to_string()) {
246 result.push(key);
247 }
248 }
249 }
250
251 if result.is_empty() {
252 vec!["output"]
253 } else {
254 result
255 }
256 }
257
258 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
259 self.route_and_invoke(inputs, None).await
260 }
261
262 async fn invoke_with_config(
267 &self,
268 inputs: HashMap<String, Value>,
269 config: Option<RunnableConfig>,
270 ) -> Result<ChainResult, ChainError> {
271 run_chain_with_callbacks(self.name(), inputs, config.clone(), |inputs| async move {
272 self.route_and_invoke(inputs, config).await
273 })
274 .await
275 }
276
277 async fn stream(&self, inputs: HashMap<String, Value>) -> Result<ChainStream, ChainError> {
282 self.route_and_stream(inputs, None).await
283 }
284
285 async fn stream_with_config(
287 &self,
288 inputs: HashMap<String, Value>,
289 config: Option<RunnableConfig>,
290 ) -> Result<ChainStream, ChainError> {
291 stream_chain_with_callbacks(self.name(), inputs, config.clone(), |inputs| async move {
292 self.route_and_stream(inputs, config).await
293 })
294 .await
295 }
296
297 fn name(&self) -> &str {
298 &self.name
299 }
300}
301
302#[derive(Debug, Clone, Deserialize)]
309pub(crate) struct RouteDecision {
310 pub destination: String,
312 pub reason: Option<String>,
314}
315
316impl RouteDecision {
317 pub(crate) fn from_text(text: &str) -> Self {
319 let trimmed = text.trim();
320 if let Ok(decision) = serde_json::from_str::<RouteDecision>(trimmed) {
321 return decision;
322 }
323 Self {
324 destination: trimmed.to_string(),
325 reason: None,
326 }
327 }
328}
329
330pub(crate) fn route_tool() -> ToolDefinition {
333 ToolDefinition::new(
334 "route_to_destination",
335 "根据用户输入选择最合适的处理 handler,返回目标名称与理由",
336 )
337 .with_parameters(json!({
338 "type": "object",
339 "properties": {
340 "destination": { "type": "string", "description": "目标 handler 名称" },
341 "reason": { "type": "string", "description": "选择该 handler 的理由" }
342 },
343 "required": ["destination"]
344 }))
345}