Skip to main content

llmsdk_provider/language_model/
finish_reason.rs

1//! Unified finish reason returned by a language model.
2//!
3//! Mirrors `language-model-v4-finish-reason.ts`. We pair the unified enum
4//! with the provider-raw string so callers can branch on either.
5// Rust guideline compliant 2026-02-21
6
7use serde::{Deserialize, Serialize};
8
9/// Why the model stopped generating.
10///
11/// `unified` is normalized across providers; `raw` preserves the original
12/// string for telemetry.
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct FinishReason {
15    /// Normalized reason.
16    pub unified: FinishReasonKind,
17    /// Provider-reported raw value. `None` when not provided.
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub raw: Option<String>,
20}
21
22/// Normalized reason variants.
23#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
24#[serde(rename_all = "kebab-case")]
25pub enum FinishReasonKind {
26    /// Model generated a stop sequence.
27    Stop,
28    /// Model reached max-tokens.
29    Length,
30    /// Content filter triggered.
31    ContentFilter,
32    /// Model emitted tool calls.
33    ToolCalls,
34    /// Model stopped because of an error.
35    Error,
36    /// Anything else.
37    Other,
38}
39
40impl FinishReason {
41    /// Build a normalized reason without raw provider data.
42    #[must_use]
43    pub const fn new(kind: FinishReasonKind) -> Self {
44        Self {
45            unified: kind,
46            raw: None,
47        }
48    }
49
50    /// Build a normalized reason while preserving the provider string.
51    pub fn with_raw(kind: FinishReasonKind, raw: impl Into<String>) -> Self {
52        Self {
53            unified: kind,
54            raw: Some(raw.into()),
55        }
56    }
57}