Skip to main content

lellm_graph/
error.rs

1//! Graph 错误类型。
2//!
3//! 错误三分法:
4//! - `Terminal` — 终止执行,stream 关闭
5//! - `Recoverable` — 内部重试 / fallback,stream 继续
6//! - `Observed` — 仅事件,不影响 control flow
7
8use std::fmt;
9
10// ─── BuildError ──────────────────────────────────────────────
11
12/// 构建时结构校验错误。
13///
14/// 仅验证图的结构性正确性,不检测循环、业务逻辑漏洞、运行时 unreachable。
15#[derive(Debug, Clone)]
16pub enum BuildError {
17    /// 节点 ID 重复
18    DuplicateNode { id: String },
19    /// 边引用了不存在的节点
20    MissingNode { from: String, to: String },
21    /// 未指定入口节点
22    MissingEntryPoint,
23    /// 未指定出口节点
24    MissingExitPoint,
25    /// 边定义无效
26    InvalidEdgeDefinition { from: String, to: String, reason: String },
27}
28
29impl fmt::Display for BuildError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::DuplicateNode { id } => write!(f, "duplicate node id: '{}'", id),
33            Self::MissingNode { from, to } => {
34                write!(f, "edge references non-existent node: '{}' (in {}→{})", to, from, to)
35            }
36            Self::MissingEntryPoint => write!(f, "entry point not set"),
37            Self::MissingExitPoint => write!(f, "exit point not set"),
38            Self::InvalidEdgeDefinition { from, to, reason } => {
39                write!(f, "invalid edge {}→{}: {}", from, to, reason)
40            }
41        }
42    }
43}
44
45impl std::error::Error for BuildError {}
46
47// ─── GraphError ──────────────────────────────────────────────
48
49/// Graph 运行时错误 — 三分法。
50#[derive(Debug)]
51pub enum GraphError {
52    /// 终止执行 — stream 关闭,不可恢复
53    Terminal(TerminalError),
54    /// 可恢复 — 内部重试 / fallback 触发,stream 继续
55    Recoverable(RecoverableError),
56    /// 仅事件 — 不影响 control flow,stream 继续
57    Observed(ObservedError),
58}
59
60/// 终止错误 — Graph 执行不可恢复地停止。
61#[derive(Debug)]
62pub enum TerminalError {
63    /// 图结构无效(构建时校验遗漏的运行时问题)
64    InvalidGraph(String),
65    /// 节点不存在
66    NodeNotFound(String),
67    /// Goto 目标缺少对应的边
68    MissingEdge { from: String, to: String },
69    /// 节点执行失败(不可恢复)
70    NodeExecutionFailed { node: String, source: Box<dyn std::error::Error + Send + Sync> },
71    /// 全局步数超限(运行时熔断)
72    StepsExceeded { limit: usize },
73    /// 循环超限
74    LoopLimitExceeded { limit: usize },
75    /// 边级 policy 超限
76    EdgePolicyExceeded { edge: String, limit: usize },
77    /// Barrier 超时
78    BarrierTimeout { node: String, timeout: std::time::Duration },
79    /// Barrier 被取消
80    BarrierCancelled { node: String },
81    /// 无匹配边 — 没有任何 outgoing edge 满足条件,且无 fallback
82    Unrouted {
83        /// 当前节点
84        node: String,
85        /// 尝试的条件及其结果
86        attempted_conditions: Vec<ConditionEval>,
87    },
88    /// State 操作错误
89    StateError(String),
90}
91
92/// 可恢复错误 — 内部重试或 fallback 后继续。
93#[derive(Debug)]
94pub enum RecoverableError {
95    /// 节点执行失败但配置了重试
96    Retryable {
97        node: String,
98        attempt: usize,
99        max_attempts: usize,
100        reason: String,
101    },
102    /// 边 fallback 被触发
103    FallbackTriggered {
104        from: String,
105        to: String,
106        reason: String,
107    },
108}
109
110/// 观测错误 — 仅作为事件发出,不影响执行流。
111#[derive(Debug, Clone)]
112pub enum ObservedError {
113    /// 警告
114    Warning { node: String, message: String },
115    /// 降级执行
116    Degraded { node: String, message: String },
117    /// 部分失败
118    PartialFailure { node: String, succeeded: usize, failed: usize, message: String },
119}
120
121/// 条件评估结果 — 用于 Unrouted 错误报告。
122#[derive(Debug, Clone)]
123pub struct ConditionEval {
124    /// 边描述
125    pub edge: String,
126    /// 条件描述(None = default edge)
127    pub condition: Option<String>,
128    /// 评估结果
129    pub matched: bool,
130}
131
132// ─── Display ─────────────────────────────────────────────────
133
134impl fmt::Display for GraphError {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match self {
137            Self::Terminal(e) => write!(f, "[terminal] {}", e),
138            Self::Recoverable(e) => write!(f, "[recoverable] {}", e),
139            Self::Observed(e) => write!(f, "[observed] {}", e),
140        }
141    }
142}
143
144impl fmt::Display for TerminalError {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        match self {
147            Self::InvalidGraph(msg) => write!(f, "invalid graph: {msg}"),
148            Self::NodeNotFound(name) => write!(f, "node not found: {name}"),
149            Self::MissingEdge { from, to } => {
150                write!(f, "goto '{}' from '{}' failed: no edge {}→{} exists", to, from, from, to)
151            }
152            Self::NodeExecutionFailed { node, source } => {
153                write!(f, "node '{node}' execution failed: {source}")
154            }
155            Self::StepsExceeded { limit } => {
156                write!(f, "step limit {limit} exceeded (potential infinite loop)")
157            }
158            Self::LoopLimitExceeded { limit } => write!(f, "loop limit exceeded: {limit}"),
159            Self::EdgePolicyExceeded { edge, limit } => {
160                write!(f, "edge '{edge}' policy limit {limit} exceeded (cycle protection)")
161            }
162            Self::BarrierTimeout { node, timeout } => {
163                write!(f, "barrier '{node}' timed out after {timeout:?}")
164            }
165            Self::BarrierCancelled { node } => {
166                write!(f, "barrier '{node}' cancelled: consumer dropped the signal channel")
167            }
168            Self::Unrouted { node, attempted_conditions } => {
169                write!(f, "node '{}' has no matching outgoing edge", node)?;
170                if !attempted_conditions.is_empty() {
171                    write!(f, ". evaluated: [")?;
172                    for (i, ce) in attempted_conditions.iter().enumerate() {
173                        if i > 0 { write!(f, ", ")?; }
174                        write!(f, "{}={}", ce.edge, ce.matched)?;
175                    }
176                    write!(f, "]")?;
177                }
178                Ok(())
179            }
180            Self::StateError(msg) => write!(f, "state error: {msg}"),
181        }
182    }
183}
184
185impl fmt::Display for RecoverableError {
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        match self {
188            Self::Retryable { node, attempt, max_attempts, reason } => {
189                write!(f, "node '{node}' retry {}/{}, reason: {}", attempt, max_attempts, reason)
190            }
191            Self::FallbackTriggered { from, to, reason } => {
192                write!(f, "fallback edge {}→{} triggered: {}", from, to, reason)
193            }
194        }
195    }
196}
197
198impl fmt::Display for ObservedError {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        match self {
201            Self::Warning { node, message } => write!(f, "node '{}': {}", node, message),
202            Self::Degraded { node, message } => write!(f, "node '{}' degraded: {}", node, message),
203            Self::PartialFailure { node, succeeded, failed, message } => {
204                write!(f, "node '{}' partial: {}/{} ok, {}", node, succeeded, succeeded + failed, message)
205            }
206        }
207    }
208}
209
210impl std::error::Error for GraphError {
211    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
212        match self {
213            Self::Terminal(TerminalError::NodeExecutionFailed { source, .. }) => {
214                Some(source.as_ref())
215            }
216            _ => None,
217        }
218    }
219}
220
221impl std::error::Error for TerminalError {
222    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
223        match self {
224            Self::NodeExecutionFailed { source, .. } => Some(source.as_ref()),
225            _ => None,
226        }
227    }
228}
229
230impl std::error::Error for RecoverableError {}
231impl std::error::Error for ObservedError {}