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    pub is_streaming: bool,
35}
36
37impl Default for AiResponse {
38    fn default() -> Self {
39        Self {
40            request_id: Uuid::nil(),
41            content: String::new(),
42            provider: String::new(),
43            model: String::new(),
44            tokens_used: None,
45            input_tokens: None,
46            output_tokens: None,
47            latency_ms: 0,
48            tool_calls: Vec::new(),
49            tool_results: Vec::new(),
50            finish_reason: None,
51            cache_hit: false,
52            cache_read_tokens: None,
53            cache_creation_tokens: None,
54            is_streaming: false,
55        }
56    }
57}
58
59impl AiResponse {
60    pub fn new(request_id: Uuid, content: String, provider: String, model: String) -> Self {
61        Self {
62            request_id,
63            content,
64            provider,
65            model,
66            ..Default::default()
67        }
68    }
69
70    pub const fn with_tokens(mut self, tokens_used: u32) -> Self {
71        self.tokens_used = Some(tokens_used);
72        self
73    }
74
75    pub const fn with_latency(mut self, latency_ms: u64) -> Self {
76        self.latency_ms = latency_ms;
77        self
78    }
79
80    pub const fn with_streaming(mut self, is_streaming: bool) -> Self {
81        self.is_streaming = is_streaming;
82        self
83    }
84
85    pub fn with_tool_calls(mut self, tool_calls: Vec<ToolCall>) -> Self {
86        self.tool_calls = tool_calls;
87        self
88    }
89
90    pub fn with_tool_results(mut self, tool_results: Vec<CallToolResult>) -> Self {
91        self.tool_results = tool_results;
92        self
93    }
94
95    pub const fn has_tool_calls(&self) -> bool {
96        !self.tool_calls.is_empty()
97    }
98
99    pub const fn has_tool_results(&self) -> bool {
100        !self.tool_results.is_empty()
101    }
102}
103
104#[derive(Debug, Clone)]
105pub enum StreamChunk {
106    Text(String),
107    Usage {
108        input_tokens: Option<u32>,
109        output_tokens: Option<u32>,
110        tokens_used: Option<u32>,
111        cache_read_tokens: Option<u32>,
112        cache_creation_tokens: Option<u32>,
113        finish_reason: Option<String>,
114    },
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct WebSource {
119    pub title: String,
120    pub uri: String,
121    pub relevance: f32,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct UrlMetadata {
126    pub retrieved_url: String,
127    pub url_retrieval_status: String,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct SearchGroundedResponse {
132    pub content: String,
133    pub sources: Vec<WebSource>,
134    pub confidence_scores: Vec<f32>,
135    pub web_search_queries: Vec<String>,
136    #[serde(default, skip_serializing_if = "Option::is_none")]
137    pub url_context_metadata: Option<Vec<UrlMetadata>>,
138    #[serde(default, skip_serializing_if = "Option::is_none")]
139    pub tokens_used: Option<u32>,
140    pub latency_ms: u64,
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub finish_reason: Option<String>,
143    #[serde(default, skip_serializing_if = "Option::is_none")]
144    pub safety_ratings: Option<Vec<serde_json::Value>>,
145}