Skip to main content

ferrin_core/generate_text/
result.rs

1//! The result of `generate_text`.
2
3use ferrin_message::Message;
4use ferrin_spec::FinishReason;
5use ferrin_spec::ProviderMetadata;
6use ferrin_spec::Usage;
7use ferrin_spec::Warning;
8use ferrin_spec::language_model::Source;
9
10use super::GeneratedFile;
11use super::ParsedToolCall;
12use super::StepContent;
13use super::StepRequest;
14use super::StepResponse;
15use super::ToolResult;
16
17use super::StepResult;
18
19/// Result of a completed generation loop.
20#[derive(Debug, Clone, PartialEq)]
21pub struct GenerateTextResult<O> {
22    /// All steps in order (at least one).
23    pub steps: Vec<StepResult>,
24    /// Usage summed over all steps.
25    pub total_usage: Usage,
26    /// The structured output (`()` when none was requested).
27    pub output: O,
28}
29
30impl<O> GenerateTextResult<O> {
31    /// The final step.
32    ///
33    /// # Panics
34    ///
35    /// Panics if callers manually construct or mutate a result with no steps.
36    /// Generation loops always record at least one step before returning a result.
37    #[must_use]
38    pub fn last_step(&self) -> &StepResult {
39        #[allow(clippy::expect_used, reason = "the loop records at least one step")]
40        self.steps.last().expect("at least one step")
41    }
42
43    /// Text of the final step.
44    #[must_use]
45    pub fn text(&self) -> String {
46        self.last_step().text()
47    }
48
49    /// Reasoning text of the final step.
50    #[must_use]
51    pub fn reasoning_text(&self) -> Option<String> {
52        self.last_step().reasoning_text()
53    }
54
55    /// Finish reason of the final step.
56    #[must_use]
57    pub fn finish_reason(&self) -> &FinishReason {
58        &self.last_step().finish_reason
59    }
60
61    /// Token usage summed over every step.
62    ///
63    /// Use `last_step().usage` for the final model call alone.
64    #[must_use]
65    pub fn usage(&self) -> &Usage {
66        &self.total_usage
67    }
68
69    /// Provider metadata of the final step.
70    #[must_use]
71    pub fn provider_metadata(&self) -> Option<&ProviderMetadata> {
72        self.last_step().provider_metadata.as_ref()
73    }
74
75    /// Warnings from every step in generation order.
76    #[must_use]
77    pub fn warnings(&self) -> Vec<&Warning> {
78        self.steps.iter().flat_map(|step| &step.warnings).collect()
79    }
80
81    /// Iterates over content from every step in generation order.
82    pub fn content(&self) -> impl Iterator<Item = &StepContent> {
83        self.steps.iter().flat_map(|step| &step.content)
84    }
85
86    /// Iterates over files generated in every step.
87    pub fn files(&self) -> impl Iterator<Item = &GeneratedFile> {
88        self.steps.iter().flat_map(StepResult::files)
89    }
90
91    /// Iterates over citation sources from every step.
92    pub fn sources(&self) -> impl Iterator<Item = &Source> {
93        self.steps.iter().flat_map(StepResult::sources)
94    }
95
96    /// Iterates over parsed tool calls from every step.
97    pub fn tool_calls(&self) -> impl Iterator<Item = &ParsedToolCall> {
98        self.steps.iter().flat_map(StepResult::tool_calls)
99    }
100
101    /// Iterates over static tool calls from every step.
102    pub fn static_tool_calls(&self) -> impl Iterator<Item = &ParsedToolCall> {
103        self.tool_calls().filter(|call| !call.dynamic)
104    }
105
106    /// Iterates over dynamic tool calls from every step.
107    pub fn dynamic_tool_calls(&self) -> impl Iterator<Item = &ParsedToolCall> {
108        self.tool_calls().filter(|call| call.dynamic)
109    }
110
111    /// Iterates over final tool results from every step.
112    pub fn tool_results(&self) -> impl Iterator<Item = &ToolResult> {
113        self.steps.iter().flat_map(StepResult::tool_results)
114    }
115
116    /// Iterates over static tool results from every step.
117    pub fn static_tool_results(&self) -> impl Iterator<Item = &ToolResult> {
118        self.tool_results().filter(|result| !result.dynamic)
119    }
120
121    /// Iterates over dynamic tool results from every step.
122    pub fn dynamic_tool_results(&self) -> impl Iterator<Item = &ToolResult> {
123        self.tool_results().filter(|result| result.dynamic)
124    }
125
126    /// The final step, using the reference SDK's naming.
127    #[must_use]
128    pub fn final_step(&self) -> &StepResult {
129        self.last_step()
130    }
131
132    /// The provider's unnormalized finish reason from the final step.
133    #[must_use]
134    pub fn raw_finish_reason(&self) -> Option<&str> {
135        self.finish_reason().raw.as_deref()
136    }
137
138    /// Request metadata from the final step.
139    #[must_use]
140    pub fn request(&self) -> &StepRequest {
141        &self.last_step().request
142    }
143
144    /// Response metadata and messages from the final step.
145    #[must_use]
146    pub fn response(&self) -> &StepResponse {
147        &self.last_step().response
148    }
149
150    /// Messages of all steps, ready to append to the conversation history.
151    #[must_use]
152    pub fn response_messages(&self) -> Vec<Message> {
153        self.steps
154            .iter()
155            .flat_map(|step| step.response.messages.iter().cloned())
156            .collect()
157    }
158
159    /// Maps the output.
160    pub fn map_output<P>(self, f: impl FnOnce(O) -> P) -> GenerateTextResult<P> {
161        GenerateTextResult {
162            steps: self.steps,
163            total_usage: self.total_usage,
164            output: f(self.output),
165        }
166    }
167}