Skip to main content

ferrin_spec/language_model/
result.rs

1//! Results of language model calls and shared request/response metadata.
2
3use chrono::DateTime;
4use chrono::Utc;
5use serde::Deserialize;
6use serde::Serialize;
7
8use super::content::Content;
9use super::finish_reason::FinishReason;
10use super::stream_part::StreamPart;
11use super::usage::Usage;
12use crate::dynamic::BoxStream;
13use crate::json::JsonValue;
14use crate::shared::Headers;
15use crate::shared::ModelId;
16use crate::shared::ProviderMetadata;
17use crate::shared::Warning;
18
19/// Metadata about the request sent to the provider.
20#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
21pub struct RequestMetadata {
22    /// The JSON request body, when the adapter sends JSON.
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub body: Option<JsonValue>,
25}
26
27impl RequestMetadata {
28    /// Creates request metadata with a body.
29    #[must_use]
30    pub fn with_body(body: JsonValue) -> Self {
31        Self { body: Some(body) }
32    }
33}
34
35/// Metadata about the provider response.
36///
37/// Every field is optional because it may be unknown at the point where the
38/// metadata is created (for example headers-only metadata at stream start).
39#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
40pub struct ResponseMetadata {
41    /// Provider response id.
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub id: Option<String>,
44    /// Response timestamp.
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub timestamp: Option<DateTime<Utc>>,
47    /// Model that produced the response (may differ from the requested id).
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub model_id: Option<ModelId>,
50    /// Response headers.
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    pub headers: Option<Headers>,
53    /// Raw response body (non-streaming calls).
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub body: Option<JsonValue>,
56}
57
58impl ResponseMetadata {
59    /// Creates metadata that only carries headers.
60    #[must_use]
61    pub fn with_headers(headers: Headers) -> Self {
62        Self {
63            headers: Some(headers),
64            ..Self::default()
65        }
66    }
67
68    /// Creates metadata with timestamp and model id, as required by
69    /// non-text modalities.
70    #[must_use]
71    pub fn new(timestamp: DateTime<Utc>, model_id: impl Into<ModelId>) -> Self {
72        Self {
73            timestamp: Some(timestamp),
74            model_id: Some(model_id.into()),
75            ..Self::default()
76        }
77    }
78
79    /// Overlays fields that are `Some` in `other` onto `self`.
80    pub fn merge(&mut self, other: ResponseMetadata) {
81        if other.id.is_some() {
82            self.id = other.id;
83        }
84        if other.timestamp.is_some() {
85            self.timestamp = other.timestamp;
86        }
87        if other.model_id.is_some() {
88            self.model_id = other.model_id;
89        }
90        if other.headers.is_some() {
91            self.headers = other.headers;
92        }
93        if other.body.is_some() {
94            self.body = other.body;
95        }
96    }
97}
98
99/// Result of `do_generate`.
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101pub struct GenerateResult {
102    /// Generated content parts in order.
103    pub content: Vec<Content>,
104    /// Why generation stopped.
105    pub finish_reason: FinishReason,
106    /// Token usage.
107    #[serde(default)]
108    pub usage: Usage,
109    /// Provider-specific metadata.
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub provider_metadata: Option<ProviderMetadata>,
112    /// Request metadata.
113    #[serde(default)]
114    pub request: RequestMetadata,
115    /// Response metadata.
116    #[serde(default)]
117    pub response: ResponseMetadata,
118    /// Warnings produced while preparing or executing the call.
119    #[serde(default)]
120    pub warnings: Vec<Warning>,
121}
122
123impl GenerateResult {
124    /// Creates a result with content and finish reason; other fields default.
125    #[must_use]
126    pub fn new(content: Vec<Content>, finish_reason: FinishReason) -> Self {
127        Self {
128            content,
129            finish_reason,
130            usage: Usage::default(),
131            provider_metadata: None,
132            request: RequestMetadata::default(),
133            response: ResponseMetadata::default(),
134            warnings: Vec::new(),
135        }
136    }
137
138    /// Concatenates all [`Content::Text`] parts.
139    #[must_use]
140    pub fn text(&self) -> String {
141        self.content.iter().filter_map(Content::as_text).collect()
142    }
143}
144
145/// Result of `do_stream`.
146pub struct StreamResult {
147    /// The stream of parts.
148    pub stream: BoxStream<'static, StreamPart>,
149    /// Request metadata.
150    pub request: RequestMetadata,
151    /// Response metadata known at stream start (typically headers only).
152    pub response: ResponseMetadata,
153}
154
155impl StreamResult {
156    /// Creates a stream result with default request and response metadata.
157    #[must_use]
158    pub fn new(stream: BoxStream<'static, StreamPart>) -> Self {
159        Self {
160            stream,
161            request: RequestMetadata::default(),
162            response: ResponseMetadata::default(),
163        }
164    }
165}
166
167impl std::fmt::Debug for StreamResult {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        f.debug_struct("StreamResult")
170            .field("stream", &"<stream>")
171            .field("request", &self.request)
172            .field("response", &self.response)
173            .finish()
174    }
175}