use serde::{Deserialize, Serialize};
use crate::models::Content;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LlmResponse {
content: Content,
usage: TokenUsage,
}
impl LlmResponse {
#[must_use]
pub const fn new(content: Content, usage: TokenUsage) -> Self {
Self { content, usage }
}
#[must_use]
pub const fn content(&self) -> &Content {
&self.content
}
#[must_use]
pub const fn usage(&self) -> &TokenUsage {
&self.usage
}
#[must_use]
pub fn into_content(self) -> Content {
self.content
}
#[must_use]
pub fn into_parts(self) -> (Content, TokenUsage) {
(self.content, self.usage)
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenUsage {
input: Option<u32>,
output: Option<u32>,
total: Option<u32>,
}
impl TokenUsage {
#[must_use]
pub const fn new(input_tokens: u32, output_tokens: u32, total_tokens: u32) -> Self {
Self {
input: Some(input_tokens),
output: Some(output_tokens),
total: Some(total_tokens),
}
}
#[must_use]
pub fn empty() -> Self {
Self::default()
}
#[must_use]
pub const fn partial(
input_tokens: Option<u32>,
output_tokens: Option<u32>,
total_tokens: Option<u32>,
) -> Self {
Self {
input: input_tokens,
output: output_tokens,
total: total_tokens,
}
}
#[must_use]
pub fn input_tokens(&self) -> u32 {
self.input.unwrap_or(0)
}
#[must_use]
pub fn output_tokens(&self) -> u32 {
self.output.unwrap_or(0)
}
#[must_use]
pub fn total_tokens(&self) -> u32 {
self.total.unwrap_or(0)
}
#[must_use]
pub const fn input_tokens_opt(&self) -> Option<u32> {
self.input
}
#[must_use]
pub const fn output_tokens_opt(&self) -> Option<u32> {
self.output
}
#[must_use]
pub const fn total_tokens_opt(&self) -> Option<u32> {
self.total
}
#[must_use]
pub const fn with_input_tokens(mut self, tokens: u32) -> Self {
self.input = Some(tokens);
self
}
#[must_use]
pub const fn with_output_tokens(mut self, tokens: u32) -> Self {
self.output = Some(tokens);
self
}
#[must_use]
pub const fn with_total_tokens(mut self, tokens: u32) -> Self {
self.total = Some(tokens);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::ContentPart;
#[test]
fn llm_response_accessors_work() {
let content = Content::from_parts(vec![ContentPart::Text("Hello".into())]);
let usage = TokenUsage::new(1, 2, 3);
let response = LlmResponse::new(content, usage);
assert_eq!(response.content().first_text(), Some("Hello"));
assert_eq!(response.usage().total_tokens(), 3);
let (content_parts, metrics) = response.into_parts();
assert_eq!(content_parts.first_text(), Some("Hello"));
assert_eq!(metrics.total_tokens(), 3);
}
#[test]
fn token_usage_option_helpers() {
let usage = TokenUsage::empty()
.with_input_tokens(5)
.with_output_tokens(7)
.with_total_tokens(12);
assert_eq!(usage.input_tokens_opt(), Some(5));
assert_eq!(usage.output_tokens(), 7);
assert_eq!(usage.total_tokens(), 12);
}
}