Skip to main content

walrus_core/model/
response.rs

1//! Chat response abstractions for the unified LLM Interfaces
2
3use crate::model::{Message, Role, tool::ToolCall};
4use compact_str::CompactString;
5pub use crabtalk_core::{CompletionTokensDetails, FinishReason, Usage};
6use serde::{Deserialize, Serialize};
7
8/// Common metadata shared between streaming and non-streaming completions
9#[derive(Debug, Clone, Deserialize, Default)]
10pub struct CompletionMeta {
11    /// A unique identifier for the chat completion
12    pub id: CompactString,
13
14    /// The object type
15    pub object: CompactString,
16
17    /// Unix timestamp (in seconds) of when the response was created
18    pub created: u64,
19
20    /// The model used for the completion
21    pub model: CompactString,
22
23    /// Backend configuration identifier
24    pub system_fingerprint: Option<CompactString>,
25}
26
27/// Message content in a completion response
28///
29/// Used for both streaming deltas and non-streaming response messages.
30#[derive(Debug, Clone, Deserialize, Default)]
31pub struct Delta {
32    /// The role of the message author
33    pub role: Option<Role>,
34
35    /// The content of the message
36    pub content: Option<String>,
37
38    /// The reasoning content (for deepseek-reasoner model)
39    pub reasoning_content: Option<String>,
40
41    /// Tool calls made by the model
42    pub tool_calls: Option<Vec<ToolCall>>,
43}
44
45/// A chat completion response from the LLM
46#[derive(Debug, Clone, Deserialize)]
47pub struct Response {
48    /// Completion metadata
49    #[serde(flatten)]
50    pub meta: CompletionMeta,
51
52    /// The list of completion choices
53    pub choices: Vec<Choice>,
54
55    /// Token usage statistics
56    pub usage: Usage,
57}
58
59impl Response {
60    pub fn message(&self) -> Option<Message> {
61        let choice = self.choices.first()?;
62        Some(Message::assistant(
63            choice.delta.content.clone().unwrap_or_default(),
64            choice.delta.reasoning_content.clone(),
65            choice.delta.tool_calls.as_deref(),
66        ))
67    }
68
69    /// Get the first message from the response
70    pub fn content(&self) -> Option<&String> {
71        self.choices
72            .first()
73            .and_then(|choice| choice.delta.content.as_ref())
74    }
75
76    /// Get the first message from the response
77    pub fn reasoning(&self) -> Option<&String> {
78        self.choices
79            .first()
80            .and_then(|choice| choice.delta.reasoning_content.as_ref())
81    }
82
83    /// Get the tool calls from the response
84    pub fn tool_calls(&self) -> Option<&[ToolCall]> {
85        self.choices
86            .first()
87            .and_then(|choice| choice.delta.tool_calls.as_deref())
88    }
89
90    /// Get the reason the model stopped generating
91    pub fn reason(&self) -> Option<&FinishReason> {
92        self.choices
93            .first()
94            .and_then(|choice| choice.finish_reason.as_ref())
95    }
96}
97
98/// A completion choice (used for both streaming and non-streaming responses).
99#[derive(Debug, Clone, Deserialize, Default)]
100pub struct Choice {
101    /// The index of this choice in the list
102    pub index: u32,
103
104    /// The message content (streaming: `delta`, non-streaming: `message`)
105    #[serde(alias = "message")]
106    pub delta: Delta,
107
108    /// The reason the model stopped generating
109    pub finish_reason: Option<FinishReason>,
110
111    /// Log probability information
112    pub logprobs: Option<LogProbs>,
113}
114
115/// Log probability information
116#[derive(Debug, Clone, Deserialize)]
117pub struct LogProbs {
118    /// Log probabilities for each token
119    pub content: Option<Vec<LogProb>>,
120}
121
122/// Log probability for a single token
123#[derive(Debug, Clone, Deserialize, Serialize)]
124pub struct LogProb {
125    /// The token string
126    pub token: String,
127
128    /// The log probability of this token
129    pub logprob: f64,
130
131    /// Byte representation of the token
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub bytes: Option<Vec<u8>>,
134
135    /// Top log probabilities for this position
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub top_logprobs: Option<Vec<TopLogProb>>,
138}
139
140/// Top log probability entry
141#[derive(Debug, Clone, Deserialize, Serialize)]
142pub struct TopLogProb {
143    /// The token string
144    pub token: String,
145
146    /// The log probability
147    pub logprob: f64,
148
149    /// Byte representation of the token
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub bytes: Option<Vec<u8>>,
152}