Skip to main content

crabtalk_core/model/
response.rs

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