Skip to main content

agent_io/llm/types/
response.rs

1//! Response types for LLM completions
2
3use serde::{Deserialize, Serialize};
4
5use super::tool::ToolCall;
6
7/// Token usage information
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct Usage {
10    pub prompt_tokens: u64,
11    pub completion_tokens: u64,
12    pub total_tokens: u64,
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub prompt_cached_tokens: Option<u64>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub prompt_cache_creation_tokens: Option<u64>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub prompt_image_tokens: Option<u64>,
19}
20
21impl Usage {
22    pub fn new(prompt_tokens: u64, completion_tokens: u64) -> Self {
23        Self {
24            prompt_tokens,
25            completion_tokens,
26            total_tokens: prompt_tokens + completion_tokens,
27            ..Default::default()
28        }
29    }
30}
31
32/// Stop reason for completion
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
34#[serde(rename_all = "snake_case")]
35pub enum StopReason {
36    EndTurn,
37    StopSequence,
38    ToolUse,
39    MaxTokens,
40    #[serde(other)]
41    Unknown,
42}
43
44/// Chat completion response
45#[derive(Debug, Clone)]
46pub struct ChatCompletion {
47    /// Text content (if any)
48    pub content: Option<String>,
49    /// Thinking content (for extended thinking models)
50    pub thinking: Option<String>,
51    /// Redacted thinking content
52    pub redacted_thinking: Option<String>,
53    /// Tool calls (if any)
54    pub tool_calls: Vec<ToolCall>,
55    /// Token usage
56    pub usage: Option<Usage>,
57    /// Why the completion stopped
58    pub stop_reason: Option<StopReason>,
59}
60
61impl ChatCompletion {
62    pub fn text(content: impl Into<String>) -> Self {
63        Self {
64            content: Some(content.into()),
65            thinking: None,
66            redacted_thinking: None,
67            tool_calls: Vec::new(),
68            usage: None,
69            stop_reason: None,
70        }
71    }
72
73    pub fn with_tool_calls(tool_calls: Vec<ToolCall>) -> Self {
74        Self {
75            content: None,
76            thinking: None,
77            redacted_thinking: None,
78            tool_calls,
79            usage: None,
80            stop_reason: Some(StopReason::ToolUse),
81        }
82    }
83
84    pub fn has_tool_calls(&self) -> bool {
85        !self.tool_calls.is_empty()
86    }
87
88    pub fn has_content(&self) -> bool {
89        self.content.is_some() && self.content.as_ref().is_some_and(|c| !c.is_empty())
90    }
91}