1use af_context::ToolCallId;
9use std::fmt;
10
11use serde::{Deserialize, Deserializer, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum Role {
17 System,
19 User,
21 Assistant,
23 Tool,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
31pub enum AssistantBlock {
32 Text {
34 text: String,
36 },
37 Resource {
39 resource_id: String,
41 media_type: String,
43 },
44 Data {
46 slot: String,
48 value: serde_json::Value,
50 },
51 Citation {
53 resource_id: String,
55 label: String,
57 uri: String,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
61 excerpt: Option<String>,
62 },
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ChatMessage {
70 pub role: Role,
72
73 #[serde(skip_serializing_if = "Option::is_none")]
75 pub content: Option<String>,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub tool_calls: Option<Vec<ToolCall>>,
80
81 #[serde(skip_serializing_if = "Option::is_none")]
83 pub tool_call_id: Option<ToolCallId>,
84
85 #[serde(skip_serializing_if = "Option::is_none")]
87 pub name: Option<String>,
88}
89
90impl ChatMessage {
91 pub fn system(content: impl Into<String>) -> Self {
93 Self::text(Role::System, content)
94 }
95 pub fn user(content: impl Into<String>) -> Self {
97 Self::text(Role::User, content)
98 }
99 pub fn assistant(content: impl Into<String>) -> Self {
101 Self::text(Role::Assistant, content)
102 }
103
104 fn text(role: Role, content: impl Into<String>) -> Self {
105 Self {
106 role,
107 content: Some(content.into()),
108 tool_calls: None,
109 tool_call_id: None,
110 name: None,
111 }
112 }
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct Tool {
118 #[serde(rename = "type")]
120 pub kind: String,
121 pub function: FunctionDef,
123}
124
125impl Tool {
126 pub fn function(
128 name: impl Into<String>,
129 description: impl Into<String>,
130 parameters: serde_json::Value,
131 ) -> Self {
132 Self {
133 kind: "function".to_string(),
134 function: FunctionDef {
135 name: name.into(),
136 description: Some(description.into()),
137 parameters: Some(parameters),
138 },
139 }
140 }
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct FunctionDef {
146 pub name: String,
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub description: Option<String>,
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub parameters: Option<serde_json::Value>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct ToolCall {
159 pub id: ToolCallId,
161 #[serde(rename = "type")]
163 pub kind: String,
164 pub function: FunctionCall,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct FunctionCall {
171 pub name: String,
173 pub arguments: String,
175}
176
177#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
179#[serde(rename_all = "lowercase")]
180pub enum ToolChoice {
181 Auto,
183 None,
185 Required,
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
191#[serde(rename_all = "lowercase")]
192pub enum ReasoningEffort {
193 Low,
195 Medium,
197 High,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct CompletionRequest {
204 pub model: String,
206 pub messages: Vec<ChatMessage>,
208
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub tools: Option<Vec<Tool>>,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub tool_choice: Option<ToolChoice>,
216
217 pub temperature: f32,
219 pub max_tokens: u32,
221
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub reasoning_effort: Option<ReasoningEffort>,
225
226 #[serde(skip)]
228 pub provider_attempt_id: Option<String>,
229
230 #[serde(skip_serializing_if = "std::ops::Not::not")]
233 pub stream: bool,
234
235 #[serde(skip_serializing_if = "Option::is_none")]
238 pub stream_options: Option<StreamOptions>,
239}
240
241#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
243pub struct StreamOptions {
244 pub include_usage: bool,
246}
247
248impl CompletionRequest {
249 pub fn new(model: impl Into<String>, messages: Vec<ChatMessage>) -> Self {
252 Self {
253 model: model.into(),
254 messages,
255 tools: None,
256 tool_choice: None,
257 temperature: 0.3,
258 max_tokens: 4096,
259 reasoning_effort: None,
260 provider_attempt_id: None,
261 stream: false,
262 stream_options: None,
263 }
264 }
265
266 pub fn stream(mut self, enabled: bool) -> Self {
268 self.stream = enabled;
269 self
270 }
271
272 pub fn temperature(mut self, t: f32) -> Self {
274 self.temperature = t;
275 self
276 }
277
278 pub fn max_tokens(mut self, n: u32) -> Self {
280 self.max_tokens = n;
281 self
282 }
283
284 pub fn reasoning_effort(mut self, effort: ReasoningEffort) -> Self {
286 self.reasoning_effort = Some(effort);
287 self
288 }
289
290 pub fn tools(mut self, tools: Vec<Tool>) -> Self {
293 if !tools.is_empty() && self.tool_choice.is_none() {
294 self.tool_choice = Some(ToolChoice::Auto);
295 }
296 self.tools = Some(tools);
297 self
298 }
299
300 pub fn tool_choice(mut self, choice: ToolChoice) -> Self {
302 self.tool_choice = Some(choice);
303 self
304 }
305}
306
307#[derive(Debug, Clone, Deserialize)]
309pub struct CompletionResponse {
310 #[serde(default)]
312 pub id: String,
313 pub choices: Vec<Choice>,
315 #[serde(default)]
317 pub usage: Option<Usage>,
318}
319
320impl CompletionResponse {
321 pub fn first_content(&self) -> Option<&str> {
323 self.choices
324 .first()
325 .and_then(|c| c.message.content.as_deref())
326 }
327
328 pub fn first_tool_calls(&self) -> Option<&[ToolCall]> {
330 self.choices
331 .first()
332 .and_then(|c| c.message.tool_calls.as_deref())
333 }
334
335 pub fn first_finish_reason(&self) -> Option<&FinishReason> {
337 self.choices
338 .first()
339 .and_then(|choice| choice.finish_reason.as_ref())
340 }
341}
342
343#[derive(Debug, Clone, PartialEq, Eq)]
345pub enum FinishReason {
346 Stop,
348 ToolCalls,
350 Length,
352 ContentFilter,
354 Unknown(String),
356}
357
358impl FinishReason {
359 pub fn as_str(&self) -> &str {
361 match self {
362 Self::Stop => "stop",
363 Self::ToolCalls => "tool_calls",
364 Self::Length => "length",
365 Self::ContentFilter => "content_filter",
366 Self::Unknown(reason) => reason,
367 }
368 }
369}
370
371impl fmt::Display for FinishReason {
372 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
373 formatter.write_str(self.as_str())
374 }
375}
376
377impl From<&str> for FinishReason {
378 fn from(reason: &str) -> Self {
379 match reason {
380 "stop" => Self::Stop,
381 "tool_calls" => Self::ToolCalls,
382 "length" => Self::Length,
383 "content_filter" => Self::ContentFilter,
384 unknown => Self::Unknown(unknown.to_string()),
385 }
386 }
387}
388
389impl From<String> for FinishReason {
390 fn from(reason: String) -> Self {
391 Self::from(reason.as_str())
392 }
393}
394
395impl<'de> Deserialize<'de> for FinishReason {
396 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
397 where
398 D: Deserializer<'de>,
399 {
400 String::deserialize(deserializer).map(Into::into)
401 }
402}
403
404#[derive(Debug, Clone, Deserialize)]
406pub struct Choice {
407 #[serde(default)]
409 pub index: u32,
410 pub message: ChatMessage,
412 #[serde(default)]
414 pub finish_reason: Option<FinishReason>,
415 #[serde(default, alias = "content_blocks")]
417 pub output_blocks: Vec<AssistantBlock>,
418}
419
420#[derive(Debug, Clone, Copy, Default, Deserialize)]
422pub struct Usage {
423 #[serde(default)]
425 pub prompt_tokens: u32,
426 #[serde(default)]
428 pub completion_tokens: u32,
429 #[serde(default)]
431 pub total_tokens: u32,
432}