Skip to main content

bamboo_agent/agent/llm/
models.rs

1//! LLM API request and response models.
2//!
3//! This module defines the data structures used to communicate with
4//! various LLM providers (OpenAI, Anthropic, etc.) following OpenAI's API format.
5//!
6//! # Key Types
7//!
8//! ## Request Types
9//! - [`ChatCompletionRequest`] - Main request structure
10//! - [`ChatMessage`] - Message in conversation
11//!
12//! ## Content Types
13//! - [`Role`] - Message role (system, user, assistant, tool)
14//! - [`Content`] - Message content (text or parts)
15//! - [`ContentPart`] - Content part (text or image)
16//!
17//! ## Tool Types
18//! - [`Tool`] - Tool definition
19//! - [`ToolChoice`] - Tool selection strategy
20//! - [`ToolCall`] - Tool invocation
21//!
22//! # Example
23//!
24//! ```rust,ignore
25//! use bamboo_agent::agent::llm::models::*;
26//!
27//! let request = ChatCompletionRequest {
28//!     model: "gpt-4o-mini".to_string(),
29//!     messages: vec![
30//!         ChatMessage {
31//!             role: Role::User,
32//!             content: Content::Text("Hello".to_string()),
33//!             tool_calls: None,
34//!             tool_call_id: None,
35//!         }
36//!     ],
37//!     tools: None,
38//!     tool_choice: None,
39//!     stream: Some(true),
40//!     stream_options: Some(StreamOptions { include_usage: true }),
41//!     parameters: HashMap::new(),
42//! };
43//! ```
44
45use serde::{Deserialize, Serialize};
46use std::collections::HashMap;
47
48// ========== Core Request Body ==========
49
50/// Chat completion request to LLM API.
51///
52/// Main request structure sent to LLM providers to generate
53/// chat completions with optional tool calling support.
54///
55/// # Fields
56///
57/// * `model` - Model identifier (e.g., "gpt-4o-mini", "claude-3-opus")
58/// * `messages` - Conversation history
59/// * `tools` - Available tools for the model
60/// * `tool_choice` - Tool selection strategy
61/// * `stream` - Whether to stream the response
62/// * `stream_options` - Streaming options
63/// * `parameters` - Additional model parameters (temperature, etc.)
64///
65/// # Example
66///
67/// ```rust,ignore
68/// let request = ChatCompletionRequest {
69///     model: "gpt-4o-mini".to_string(),
70///     messages: vec![
71///         ChatMessage::user("What is Rust?"),
72///     ],
73///     stream: Some(true),
74///     ..Default::default()
75/// };
76/// ```
77#[derive(Debug, Serialize, Deserialize, Clone, Default)]
78pub struct ChatCompletionRequest {
79    /// The model to use for the completion.
80    pub model: String,
81    /// A list of messages comprising the conversation so far.
82    pub messages: Vec<ChatMessage>,
83    /// A list of tools the model may call.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub tools: Option<Vec<Tool>>,
86    /// Controls which function is called by the model.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub tool_choice: Option<ToolChoice>,
89    /// Whether to stream the response.
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub stream: Option<bool>,
92    /// Options for streaming response. Set `include_usage: true` to receive usage information.
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub stream_options: Option<StreamOptions>,
95    /// Additional parameters like temperature, top_p, etc.
96    #[serde(flatten)]
97    pub parameters: HashMap<String, serde_json::Value>,
98}
99
100/// Options for streaming responses.
101///
102/// # Fields
103///
104/// * `include_usage` - Include token usage in final chunk
105#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
106pub struct StreamOptions {
107    /// If set to true, the streaming response will include a `usage` field in the final chunk.
108    pub include_usage: bool,
109}
110
111// ========== Message and Content Structures ==========
112
113/// A message in the conversation.
114///
115/// Represents one turn in the conversation with role and content.
116///
117/// # Fields
118///
119/// * `role` - Message author role
120/// * `content` - Message contents
121/// * `tool_calls` - Tool calls (for assistant messages)
122/// * `tool_call_id` - Tool call ID (for tool result messages)
123///
124/// # Example
125///
126/// ```rust,ignore
127/// let user_msg = ChatMessage {
128///     role: Role::User,
129///     content: Content::Text("Hello".to_string()),
130///     tool_calls: None,
131///     tool_call_id: None,
132/// };
133///
134/// let tool_result = ChatMessage {
135///     role: Role::Tool,
136///     content: Content::Text("Result".to_string()),
137///     tool_calls: None,
138///     tool_call_id: Some("call-123".to_string()),
139/// };
140/// ```
141#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
142pub struct ChatMessage {
143    /// The role of the message author.
144    pub role: Role,
145    /// The contents of the message.
146    pub content: Content,
147    /// The tool calls generated by the model, if any.
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub tool_calls: Option<Vec<ToolCall>>,
150    /// The ID of the tool call this message is a response to.
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub tool_call_id: Option<String>,
153}
154
155/// Role of a message author.
156///
157/// # Variants
158///
159/// * `System` - System instructions
160/// * `User` - User input
161/// * `Assistant` - AI response
162/// * `Tool` - Tool execution result
163#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
164#[serde(rename_all = "lowercase")]
165pub enum Role {
166    /// System instructions or prompts
167    System,
168    /// User input message
169    User,
170    /// AI assistant response
171    Assistant,
172    /// Tool execution result
173    Tool,
174}
175
176/// Message content.
177///
178/// Can be either plain text or a list of content parts
179/// (for multimodal messages with text and images).
180///
181/// # Variants
182///
183/// * `Text(String)` - Simple text content
184/// * `Parts(Vec<ContentPart>)` - Multiple content parts
185///
186/// # Example
187///
188/// ```rust,ignore
189/// // Simple text
190/// let text = Content::Text("Hello".to_string());
191///
192/// // Multimodal
193/// let parts = Content::Parts(vec![
194///     ContentPart::Text { text: "What's in this image?".to_string() },
195///     ContentPart::ImageUrl { image_url: ImageUrl { url: "...".to_string(), detail: None } },
196/// ]);
197/// ```
198#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
199#[serde(untagged)]
200pub enum Content {
201    /// A single string of text content.
202    Text(String),
203    /// A list of content parts, for complex messages (e.g., with images).
204    Parts(Vec<ContentPart>),
205}
206
207/// A part of message content.
208///
209/// For multimodal messages, content can be text or image URLs.
210///
211/// # Variants
212///
213/// * `Text` - Text content
214/// * `ImageUrl` - Image URL reference
215///
216/// # Example
217///
218/// ```rust,ignore
219/// let text_part = ContentPart::Text {
220///     text: "Describe this image".to_string()
221/// };
222///
223/// let image_part = ContentPart::ImageUrl {
224///     image_url: ImageUrl {
225///         url: "https://example.com/image.png".to_string(),
226///         detail: Some("high".to_string()),
227///     }
228/// };
229/// ```
230#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
231#[serde(tag = "type", rename_all = "snake_case")]
232pub enum ContentPart {
233    /// Text content part
234    Text { text: String },
235    /// Image URL content part
236    ImageUrl { image_url: ImageUrl },
237}
238
239/// Image URL reference.
240///
241/// # Fields
242///
243/// * `url` - Image URL or base64 data URI
244/// * `detail` - Detail level ("low", "high", "auto")
245#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
246pub struct ImageUrl {
247    /// The URL of the image.
248    pub url: String,
249    /// The level of detail to use for the image.
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub detail: Option<String>,
252}
253
254// ========== Tool-Related Structures ==========
255
256/// Tool definition for LLM function calling.
257///
258/// Defines a tool that the model can call during generation.
259///
260/// # Fields
261///
262/// * `tool_type` - Tool type (always "function")
263/// * `function` - Function definition
264///
265/// # Example
266///
267/// ```rust,ignore
268/// let tool = Tool {
269///     tool_type: "function".to_string(),
270///     function: FunctionDefinition {
271///         name: "read_file".to_string(),
272///         description: Some("Read file contents".to_string()),
273///         parameters: json!({
274///             "type": "object",
275///             "properties": {
276///                 "path": {"type": "string"}
277///             }
278///         }),
279///     },
280/// };
281/// ```
282#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
283pub struct Tool {
284    /// Tool type (always "function")
285    #[serde(rename = "type")]
286    pub tool_type: String,
287    /// Function definition
288    pub function: FunctionDefinition,
289}
290
291/// Function definition for tool schema.
292///
293/// # Fields
294///
295/// * `name` - Function name
296/// * `description` - Function description
297/// * `parameters` - JSON Schema for parameters
298#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
299pub struct FunctionDefinition {
300    /// Function name
301    pub name: String,
302    /// Function description for the model
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub description: Option<String>,
305    /// JSON Schema for function parameters
306    pub parameters: serde_json::Value, // JSON Schema
307}
308
309/// Tool selection strategy.
310///
311/// Controls which tool (if any) the model should call.
312///
313/// # Variants
314///
315/// * `String(String)` - "none", "auto", or "required"
316/// * `Object` - Specific function to call
317///
318/// # Example
319///
320/// ```rust,ignore
321/// // No tools
322/// let none = ToolChoice::String("none".to_string());
323///
324/// // Automatic selection
325/// let auto = ToolChoice::String("auto".to_string());
326///
327/// // Force specific tool
328/// let specific = ToolChoice::Object {
329///     tool_type: "function".to_string(),
330///     function: FunctionChoice {
331///         name: "read_file".to_string(),
332///     },
333/// };
334/// ```
335#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
336#[serde(untagged)]
337pub enum ToolChoice {
338    /// Tool selection mode: "none", "auto", or "required"
339    String(String),
340    /// Force specific function call
341    Object {
342        /// Tool type (always "function")
343        #[serde(rename = "type")]
344        tool_type: String,
345        /// Function to call
346        function: FunctionChoice,
347    },
348}
349
350/// Specific function choice for tool_choice.
351#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
352pub struct FunctionChoice {
353    /// Function name to call
354    pub name: String,
355}
356
357/// Tool call from the model.
358///
359/// Represents a tool invocation requested by the LLM.
360///
361/// # Fields
362///
363/// * `id` - Unique call identifier
364/// * `tool_type` - Tool type (always "function")
365/// * `function` - Function call details
366#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
367pub struct ToolCall {
368    /// Unique tool call identifier
369    pub id: String,
370    /// Tool type (always "function")
371    #[serde(rename = "type")]
372    pub tool_type: String,
373    /// Function call details
374    pub function: FunctionCall,
375}
376
377/// Function call details.
378///
379/// # Fields
380///
381/// * `name` - Function name
382/// * `arguments` - JSON-encoded arguments
383#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
384pub struct FunctionCall {
385    /// Function name to invoke
386    pub name: String,
387    /// JSON-encoded function arguments
388    pub arguments: String, // JSON string
389}
390
391// ========== Response Structures ==========
392
393/// Chat completion response from LLM API.
394///
395/// # Fields
396///
397/// * `id` - Response ID
398/// * `object` - Object type
399/// * `created` - Creation timestamp
400/// * `model` - Model used
401/// * `choices` - Completion choices
402/// * `usage` - Token usage statistics
403/// * `system_fingerprint` - System fingerprint
404#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
405pub struct ChatCompletionResponse {
406    /// Response identifier
407    pub id: String,
408    /// Object type (e.g., "chat.completion")
409    #[serde(default)]
410    pub object: Option<String>,
411    /// Unix timestamp when response was created
412    #[serde(default)]
413    pub created: Option<u64>,
414    /// Model name used for generation
415    #[serde(default)]
416    pub model: Option<String>,
417    #[serde(default)]
418    pub choices: Vec<ResponseChoice>,
419    #[serde(default)]
420    pub usage: Option<Usage>,
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub system_fingerprint: Option<String>,
423}
424
425/// A completion choice in the response.
426///
427/// # Fields
428///
429/// * `index` - Choice index
430/// * `message` - Completion message
431/// * `finish_reason` - Reason for finishing
432#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
433pub struct ResponseChoice {
434    #[serde(default)]
435    pub index: u32,
436    pub message: ChatMessage,
437    #[serde(default)]
438    pub finish_reason: Option<String>,
439}
440
441/// Token usage statistics.
442///
443/// # Fields
444///
445/// * `prompt_tokens` - Tokens in prompt
446/// * `completion_tokens` - Tokens in completion
447/// * `total_tokens` - Total tokens used
448#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
449pub struct Usage {
450    /// Tokens in the prompt
451    #[serde(default)]
452    pub prompt_tokens: u32,
453    /// Tokens in the completion
454    #[serde(default)]
455    pub completion_tokens: u32,
456    /// Total tokens used
457    #[serde(default)]
458    pub total_tokens: u32,
459}
460
461// For Stream Responses
462
463/// Streaming chat completion chunk.
464///
465/// Each chunk contains incremental updates during streaming.
466///
467/// # Fields
468///
469/// * `id` - Response ID
470/// * `object` - Object type
471/// * `created` - Creation timestamp
472/// * `model` - Model used
473/// * `choices` - Stream choices with deltas
474/// * `usage` - Usage statistics (final chunk only)
475#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
476pub struct ChatCompletionStreamChunk {
477    pub id: String,
478    #[serde(default)]
479    pub object: Option<String>,
480    pub created: u64,
481    #[serde(default)]
482    pub model: Option<String>,
483    pub choices: Vec<StreamChoice>,
484    /// Usage statistics for the request. Only present if `stream_options.include_usage` was set to true.
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub usage: Option<Usage>,
487}
488
489/// A choice in a streaming chunk.
490///
491/// # Fields
492///
493/// * `index` - Choice index
494/// * `delta` - Content delta
495/// * `finish_reason` - Reason for finishing (if complete)
496#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
497pub struct StreamChoice {
498    /// Choice index
499    pub index: u32,
500    /// Content delta
501    pub delta: StreamDelta,
502    /// Reason for finishing
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub finish_reason: Option<String>,
505}
506
507// Streaming-specific tool call structures
508// These allow partial data since the API sends tool calls incrementally across multiple chunks
509
510/// Streaming tool call fragment.
511///
512/// During streaming, tool calls are sent incrementally across multiple chunks.
513/// The `index` field identifies which tool call each fragment belongs to.
514///
515/// # Fields
516///
517/// * `index` - Tool call index for reassembly
518/// * `id` - Tool call ID (first chunk only)
519/// * `tool_type` - Tool type (first chunk only)
520/// * `function` - Function call fragment
521#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
522pub struct StreamToolCall {
523    /// Index to identify which tool call this fragment belongs to
524    pub index: u32,
525    /// Tool call ID (only present in the first chunk)
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub id: Option<String>,
528    /// Tool type (only present in the first chunk)
529    #[serde(rename = "type")]
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub tool_type: Option<String>,
532    /// Function call data (may be partial)
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub function: Option<StreamFunctionCall>,
535}
536
537/// Streaming function call fragment.
538///
539/// # Fields
540///
541/// * `name` - Function name (first chunk only)
542/// * `arguments` - Arguments fragment (sent incrementally)
543#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
544pub struct StreamFunctionCall {
545    /// Function name (only present in the first chunk)
546    #[serde(skip_serializing_if = "Option::is_none")]
547    pub name: Option<String>,
548    /// Arguments (sent incrementally across chunks)
549    #[serde(skip_serializing_if = "Option::is_none")]
550    pub arguments: Option<String>,
551}
552
553/// Content delta in a streaming chunk.
554///
555/// Contains incremental content updates during streaming.
556///
557/// # Fields
558///
559/// * `role` - Message role (first chunk only)
560/// * `content` - Text content delta
561/// * `tool_calls` - Tool call fragments
562#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
563pub struct StreamDelta {
564    /// Message role (only present in first chunk)
565    #[serde(skip_serializing_if = "Option::is_none")]
566    pub role: Option<Role>,
567    /// Text content delta
568    #[serde(skip_serializing_if = "Option::is_none")]
569    pub content: Option<String>,
570    /// Tool call fragments
571    #[serde(skip_serializing_if = "Option::is_none")]
572    pub tool_calls: Option<Vec<StreamToolCall>>,
573}