lc_core/runnables/error.rs
1// lc-core/src/runnables/error.rs
2//! Unified error type for LCEL (LangChain Expression Language) pipelines.
3//!
4//! `LcelError` bridges all sub-crate error types into a single enum,
5//! enabling `RunnableSequence` and other LCEL combinators to work with
6//! a uniform error type regardless of which components are in the pipeline.
7//!
8//! # Design Decision
9//!
10//! `LcelError` stores error descriptions as `String` rather than wrapping
11//! concrete sub-error types (e.g. `Chain(ChainError)`). This avoids:
12//! 1. Circular dependencies between `lc-core` and downstream crates
13//! 2. Type-erased pipeline steps where the concrete error type is lost anyway
14//! 3. Bloating the enum with every provider-specific error variant
15//!
16//! The `Display` representation preserves enough information for debugging.
17
18use std::fmt;
19
20/// Unified error type for LCEL pipelines.
21///
22/// All `Runnable` components that participate in LCEL composition
23/// must have an `Error` type that implements `Into<LcelError>`.
24#[derive(Debug, Clone)]
25pub enum LcelError {
26 /// Error from an LLM provider (OpenAI, Anthropic, Gemini, Ollama, etc.).
27 Provider(String),
28
29 /// Error from a chain execution.
30 Chain(String),
31
32 /// Error from an agent execution.
33 Agent(String),
34
35 /// Error from a graph execution.
36 Graph(String),
37
38 /// Error from a tool execution.
39 Tool(String),
40
41 /// Error from an output parser.
42 OutputParser(String),
43
44 /// Error during streaming.
45 Stream(String),
46
47 /// Error in pipeline composition or execution.
48 Pipeline(String),
49
50 /// Type-erasure downcast failure.
51 TypeMismatch(String),
52
53 /// Catch-all for errors that don't fit other variants.
54 Other(String),
55}
56
57impl fmt::Display for LcelError {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 LcelError::Provider(msg) => write!(f, "Provider error: {msg}"),
61 LcelError::Chain(msg) => write!(f, "Chain error: {msg}"),
62 LcelError::Agent(msg) => write!(f, "Agent error: {msg}"),
63 LcelError::Graph(msg) => write!(f, "Graph error: {msg}"),
64 LcelError::Tool(msg) => write!(f, "Tool error: {msg}"),
65 LcelError::OutputParser(msg) => write!(f, "Output parser error: {msg}"),
66 LcelError::Stream(msg) => write!(f, "Stream error: {msg}"),
67 LcelError::Pipeline(msg) => write!(f, "Pipeline error: {msg}"),
68 LcelError::TypeMismatch(msg) => write!(f, "Type mismatch: {msg}"),
69 LcelError::Other(msg) => write!(f, "{msg}"),
70 }
71 }
72}
73
74impl std::error::Error for LcelError {}
75
76// Allow `Infallible` to convert into `LcelError` (never actually happens).
77impl From<std::convert::Infallible> for LcelError {
78 fn from(_: std::convert::Infallible) -> Self {
79 unreachable!()
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn display_formats_correctly() {
89 assert_eq!(
90 LcelError::Provider("openai timeout".to_string()).to_string(),
91 "Provider error: openai timeout"
92 );
93 assert_eq!(
94 LcelError::Chain("missing input".to_string()).to_string(),
95 "Chain error: missing input"
96 );
97 assert_eq!(
98 LcelError::TypeMismatch("expected String got i32".to_string()).to_string(),
99 "Type mismatch: expected String got i32"
100 );
101 }
102
103 #[test]
104 fn is_send_sync() {
105 fn assert_send_sync<T: Send + Sync + 'static>() {}
106 assert_send_sync::<LcelError>();
107 }
108}