Skip to main content

systemprompt_models/ai/
response.rs

1//! `AiResponse`: the canonical inference response shape.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use super::tools::{CallToolResult, ToolCall};
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AiResponse {
12    pub request_id: Uuid,
13    pub content: String,
14    pub provider: String,
15    pub model: String,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub tokens_used: Option<u32>,
18    #[serde(default, skip_serializing_if = "Option::is_none")]
19    pub input_tokens: Option<u32>,
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub output_tokens: Option<u32>,
22    pub latency_ms: u64,
23    #[serde(default, skip_serializing_if = "Vec::is_empty")]
24    pub tool_calls: Vec<ToolCall>,
25    #[serde(default, skip_serializing_if = "Vec::is_empty")]
26    pub tool_results: Vec<CallToolResult>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub finish_reason: Option<String>,
29    pub cache_hit: bool,
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub cache_read_tokens: Option<u32>,
32    #[serde(default, skip_serializing_if = "Option::is_none")]
33    pub cache_creation_tokens: Option<u32>,
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub reasoning_tokens: Option<u32>,
36    pub is_streaming: bool,
37}
38
39impl Default for AiResponse {
40    fn default() -> Self {
41        Self {
42            request_id: Uuid::nil(),
43            content: String::new(),
44            provider: String::new(),
45            model: String::new(),
46            tokens_used: None,
47            input_tokens: None,
48            output_tokens: None,
49            latency_ms: 0,
50            tool_calls: Vec::new(),
51            tool_results: Vec::new(),
52            finish_reason: None,
53            cache_hit: false,
54            cache_read_tokens: None,
55            cache_creation_tokens: None,
56            reasoning_tokens: None,
57            is_streaming: false,
58        }
59    }
60}
61
62impl AiResponse {
63    pub fn new(request_id: Uuid, content: String, provider: String, model: String) -> Self {
64        Self {
65            request_id,
66            content,
67            provider,
68            model,
69            ..Default::default()
70        }
71    }
72
73    pub const fn with_tokens(mut self, tokens_used: u32) -> Self {
74        self.tokens_used = Some(tokens_used);
75        self
76    }
77
78    pub const fn with_latency(mut self, latency_ms: u64) -> Self {
79        self.latency_ms = latency_ms;
80        self
81    }
82
83    pub const fn with_streaming(mut self, is_streaming: bool) -> Self {
84        self.is_streaming = is_streaming;
85        self
86    }
87
88    pub fn with_tool_calls(mut self, tool_calls: Vec<ToolCall>) -> Self {
89        self.tool_calls = tool_calls;
90        self
91    }
92
93    pub fn with_tool_results(mut self, tool_results: Vec<CallToolResult>) -> Self {
94        self.tool_results = tool_results;
95        self
96    }
97
98    pub const fn has_tool_calls(&self) -> bool {
99        !self.tool_calls.is_empty()
100    }
101
102    pub const fn has_tool_results(&self) -> bool {
103        !self.tool_results.is_empty()
104    }
105}
106
107#[derive(Debug, Clone)]
108pub enum StreamChunk {
109    Text(String),
110    Usage {
111        input_tokens: Option<u32>,
112        output_tokens: Option<u32>,
113        tokens_used: Option<u32>,
114        cache_read_tokens: Option<u32>,
115        cache_creation_tokens: Option<u32>,
116        reasoning_tokens: Option<u32>,
117        finish_reason: Option<String>,
118    },
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct WebSource {
123    pub title: String,
124    pub uri: String,
125    pub relevance: f32,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct UrlMetadata {
130    pub retrieved_url: String,
131    pub url_retrieval_status: String,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct SearchGroundedResponse {
136    pub content: String,
137    pub sources: Vec<WebSource>,
138    pub confidence_scores: Vec<f32>,
139    pub web_search_queries: Vec<String>,
140    #[serde(default, skip_serializing_if = "Option::is_none")]
141    pub url_context_metadata: Option<Vec<UrlMetadata>>,
142    #[serde(default, skip_serializing_if = "Option::is_none")]
143    pub tokens_used: Option<u32>,
144    pub latency_ms: u64,
145    #[serde(default, skip_serializing_if = "Option::is_none")]
146    pub finish_reason: Option<String>,
147    #[serde(default, skip_serializing_if = "Option::is_none")]
148    pub safety_ratings: Option<Vec<serde_json::Value>>,
149}