openai_tools/responses/request.rs
1use crate::{
2 common::{
3 auth::{AuthProvider, OpenAIAuth},
4 client::create_http_client,
5 errors::{OpenAIToolError, Result},
6 message::Message,
7 models::{ChatModel, ParameterRestriction},
8 structured_output::Schema,
9 tool::Tool,
10 },
11 responses::response::{CompactedResponse, DeleteResponseResult, InputItemsListResponse, InputTokensResponse, Response},
12};
13use derive_new::new;
14use request;
15use serde::{ser::SerializeStruct, Serialize};
16use std::collections::HashMap;
17use std::time::Duration;
18use strum::{Display, EnumString};
19
20/// Specifies additional data to include in the response output
21///
22/// This enum defines various types of additional information that can be
23/// included in the API response output, such as web search results, code
24/// interpreter outputs, image URLs, and other metadata.
25///
26/// # API Reference
27///
28/// Corresponds to the `include` parameter in the OpenAI Responses API:
29/// <https://platform.openai.com/docs/api-reference/responses/create>
30#[derive(Debug, Clone, EnumString, Display, Serialize, PartialEq)]
31#[serde(rename_all = "snake_case")]
32#[non_exhaustive]
33pub enum Include {
34 /// Include web search call results in the output
35 ///
36 /// When included, the response will contain information about web search
37 /// results that were used during the response generation process.
38 #[strum(serialize = "web_search_call.results")]
39 #[serde(rename = "web_search_call.results")]
40 WebSearchCall,
41
42 /// Include code interpreter call outputs in the output
43 ///
44 /// When included, the response will contain outputs from any code
45 /// that was executed during the response generation process.
46 #[strum(serialize = "code_interpreter_call.outputs")]
47 #[serde(rename = "code_interpreter_call.outputs")]
48 CodeInterpreterCall,
49
50 /// Include computer call output image URLs in the output
51 ///
52 /// When included, the response will contain image URLs from any
53 /// computer interaction calls that were made.
54 #[strum(serialize = "computer_call_output.output.image_url")]
55 #[serde(rename = "computer_call_output.output.image_url")]
56 ImageUrlInComputerCallOutput,
57
58 /// Include file search call results in the output
59 ///
60 /// When included, the response will contain results from any
61 /// file search operations that were performed.
62 #[strum(serialize = "file_search_call.results")]
63 #[serde(rename = "file_search_call.results")]
64 FileSearchCall,
65
66 /// Include image URLs from input messages in the output
67 ///
68 /// When included, the response will contain image URLs that were
69 /// present in the input messages.
70 #[strum(serialize = "message.input_image.image_url")]
71 #[serde(rename = "message.input_image.image_url")]
72 ImageUrlInInputMessages,
73
74 /// Include log probabilities in the output
75 ///
76 /// When included, the response will contain log probability information
77 /// for the generated text tokens.
78 #[strum(serialize = "message.output_text.logprobs")]
79 #[serde(rename = "message.output_text.logprobs")]
80 LogprobsInOutput,
81
82 /// Include reasoning encrypted content in the output
83 ///
84 /// When included, the response will contain encrypted reasoning
85 /// content that shows the model's internal reasoning process.
86 #[strum(serialize = "reasoning.encrypted_content")]
87 #[serde(rename = "reasoning.encrypted_content")]
88 ReasoningEncryptedContent,
89}
90
91/// Defines the level of reasoning effort the model should apply
92///
93/// This enum controls how much computational effort the model invests
94/// in reasoning through complex problems before generating a response.
95///
96/// # Model Support
97///
98/// | Model | Supported Values |
99/// |-------|-----------------|
100/// | GPT-6 Astra | `low`, `medium`, `high`, `xhigh`, `max` |
101/// | GPT-6 Sol, GPT-6 Luna | `none`, `low`, `medium`, `high`, `xhigh`, `max` |
102/// | GPT-5.2, GPT-5.2-pro | `none`, `low`, `medium`, `high`, `xhigh` |
103/// | GPT-5.1 | `none`, `low`, `medium`, `high` |
104/// | GPT-5-mini | `minimal`, `medium`, `high` |
105/// | o1, o3, o4 series | `low`, `medium`, `high` |
106///
107/// # API Reference
108///
109/// Corresponds to the `reasoning.effort` parameter in the OpenAI Responses API.
110#[derive(Debug, Clone, Serialize, EnumString, Display, PartialEq)]
111#[serde(rename_all = "snake_case")]
112#[non_exhaustive]
113pub enum ReasoningEffort {
114 /// No reasoning tokens - fastest response (GPT-5.1/5.2 default)
115 ///
116 /// Use this when you don't need reasoning capabilities and want
117 /// the fastest possible response time.
118 #[strum(serialize = "none")]
119 #[serde(rename = "none")]
120 None,
121
122 /// Minimal reasoning effort - fastest response time
123 ///
124 /// Use this for simple queries that don't require deep analysis.
125 #[strum(serialize = "minimal")]
126 #[serde(rename = "minimal")]
127 Minimal,
128
129 /// Low reasoning effort - balanced performance
130 ///
131 /// Use this for moderately complex queries that benefit from some reasoning.
132 #[strum(serialize = "low")]
133 #[serde(rename = "low")]
134 Low,
135
136 /// Medium reasoning effort - comprehensive analysis
137 ///
138 /// Use this for complex queries that require thorough consideration.
139 #[strum(serialize = "medium")]
140 #[serde(rename = "medium")]
141 Medium,
142
143 /// High reasoning effort - maximum thoughtfulness
144 ///
145 /// Use this for very complex queries requiring deep, careful analysis.
146 #[strum(serialize = "high")]
147 #[serde(rename = "high")]
148 High,
149
150 /// Extra-high reasoning effort - quality-critical work (GPT-5.2 only)
151 ///
152 /// Use this for the most demanding tasks requiring maximum reasoning
153 /// quality. Only available on GPT-5.2 and GPT-5.2-pro models.
154 #[strum(serialize = "xhigh")]
155 #[serde(rename = "xhigh")]
156 Xhigh,
157
158 /// Maximum reasoning effort for GPT-6 models
159 ///
160 /// Responses API only. Accepted by GPT-6 Astra, GPT-6 Sol, and GPT-6 Luna
161 /// (`gpt-6-astra`, `gpt-6-sol`, and `gpt-6-luna`). Chat Completions rejects
162 /// `max` in its `reasoning_effort` parameter, including for GPT-6 models.
163 #[strum(serialize = "max")]
164 #[serde(rename = "max")]
165 Max,
166}
167
168/// Defines the format of reasoning summary to include in the response
169///
170/// This enum controls how the model's reasoning process is summarized
171/// and presented in the response output.
172///
173/// # API Reference
174///
175/// Corresponds to the `reasoning.summary` parameter in the OpenAI Responses API.
176#[derive(Debug, Clone, Serialize, EnumString, Display, PartialEq)]
177#[serde(rename_all = "snake_case")]
178#[non_exhaustive]
179pub enum ReasoningSummary {
180 /// Automatically determine the appropriate summary format
181 ///
182 /// The model will choose the most suitable summary format based on the query.
183 #[strum(serialize = "auto")]
184 #[serde(rename = "auto")]
185 Auto,
186
187 /// Provide a concise summary of the reasoning process
188 ///
189 /// Use this for shorter, more focused reasoning explanations.
190 #[strum(serialize = "concise")]
191 #[serde(rename = "concise")]
192 Concise,
193
194 /// Provide a detailed summary of the reasoning process
195 ///
196 /// Use this for comprehensive reasoning explanations with full detail.
197 #[strum(serialize = "detailed")]
198 #[serde(rename = "detailed")]
199 Detailed,
200}
201
202/// Configuration for reasoning behavior in responses
203///
204/// This struct allows you to control how the model approaches reasoning
205/// for complex queries, including the effort level and summary format.
206///
207/// # API Reference
208///
209/// Corresponds to the `reasoning` parameter in the OpenAI Responses API.
210#[derive(Debug, Clone, Serialize)]
211pub struct Reasoning {
212 /// The level of reasoning effort to apply
213 pub effort: Option<ReasoningEffort>,
214 /// The format for the reasoning summary
215 pub summary: Option<ReasoningSummary>,
216}
217
218/// Defines the verbosity level for text output
219///
220/// This enum controls how detailed and lengthy the model's text responses
221/// should be. Available on GPT-5.2 and newer models.
222///
223/// # API Reference
224///
225/// Corresponds to the `text.verbosity` parameter in the OpenAI Responses API.
226#[derive(Debug, Clone, Serialize, EnumString, Display, PartialEq)]
227#[serde(rename_all = "snake_case")]
228#[non_exhaustive]
229pub enum TextVerbosity {
230 /// Low verbosity - concise responses
231 ///
232 /// Use this for brief, to-the-point answers.
233 #[strum(serialize = "low")]
234 #[serde(rename = "low")]
235 Low,
236
237 /// Medium verbosity - balanced responses (default)
238 ///
239 /// Use this for standard-length responses with appropriate detail.
240 #[strum(serialize = "medium")]
241 #[serde(rename = "medium")]
242 Medium,
243
244 /// High verbosity - comprehensive responses
245 ///
246 /// Use this for detailed explanations with thorough coverage.
247 #[strum(serialize = "high")]
248 #[serde(rename = "high")]
249 High,
250}
251
252/// Configuration for text output behavior
253///
254/// This struct allows you to control the characteristics of the generated
255/// text output, such as verbosity level.
256///
257/// # API Reference
258///
259/// Corresponds to the `text` parameter in the OpenAI Responses API.
260#[derive(Debug, Clone, Serialize)]
261pub struct TextConfig {
262 /// The verbosity level for text output
263 pub verbosity: Option<TextVerbosity>,
264}
265
266/// Defines how the model should choose and use tools
267///
268/// This enum controls the model's behavior regarding tool usage during
269/// response generation.
270///
271/// # API Reference
272///
273/// Corresponds to the `tool_choice` parameter in the OpenAI Responses API.
274#[derive(Debug, Clone, Serialize, EnumString, Display, PartialEq)]
275#[serde(rename_all = "snake_case")]
276pub enum ToolChoiceMode {
277 /// Disable tool usage completely
278 ///
279 /// The model will not use any tools and will generate responses
280 /// based solely on its training data.
281 #[strum(serialize = "none")]
282 #[serde(rename = "none")]
283 None,
284
285 /// Automatically decide when to use tools
286 ///
287 /// The model will automatically determine when tools are needed
288 /// and which tools to use based on the query context.
289 #[strum(serialize = "auto")]
290 #[serde(rename = "auto")]
291 Auto,
292
293 /// Require the use of tools
294 ///
295 /// The model must use at least one of the provided tools in its response.
296 #[strum(serialize = "required")]
297 #[serde(rename = "required")]
298 Required,
299}
300
301/// Controls truncation behavior for long inputs
302///
303/// This enum defines how the system should handle inputs that exceed
304/// the maximum context length.
305///
306/// # API Reference
307///
308/// Corresponds to the `truncation` parameter in the OpenAI Responses API.
309#[derive(Debug, Clone, Serialize, EnumString, Display, PartialEq)]
310#[serde(rename_all = "snake_case")]
311pub enum Truncation {
312 /// Automatically truncate inputs to fit context length
313 ///
314 /// The system will automatically trim inputs to ensure they fit
315 /// within the model's context window.
316 #[strum(serialize = "auto")]
317 #[serde(rename = "auto")]
318 Auto,
319
320 /// Disable truncation - return error if input is too long
321 ///
322 /// The system will return an error rather than truncating
323 /// inputs that exceed the context length.
324 #[strum(serialize = "disabled")]
325 #[serde(rename = "disabled")]
326 Disabled,
327}
328
329/// Specifies a specific function to force the model to call
330///
331/// When you want the model to call a specific function rather than
332/// choosing which tool to use, use this structure to specify the
333/// function name.
334///
335/// # Example
336///
337/// ```rust
338/// use openai_tools::responses::request::NamedFunctionChoice;
339///
340/// let function_choice = NamedFunctionChoice::new("get_weather");
341/// ```
342#[derive(Debug, Clone, Serialize)]
343pub struct NamedFunctionChoice {
344 /// The type of tool choice, always "function" for named functions
345 #[serde(rename = "type")]
346 pub type_name: String,
347 /// The name of the function to call
348 pub name: String,
349}
350
351impl NamedFunctionChoice {
352 /// Creates a new NamedFunctionChoice for the specified function
353 ///
354 /// # Arguments
355 ///
356 /// * `name` - The name of the function to call
357 ///
358 /// # Returns
359 ///
360 /// A new NamedFunctionChoice instance
361 pub fn new<S: AsRef<str>>(name: S) -> Self {
362 Self { type_name: "function".to_string(), name: name.as_ref().to_string() }
363 }
364}
365
366/// Controls how the model selects tools
367///
368/// This enum allows you to specify whether the model should automatically
369/// choose tools, be forced to use specific tools, or be prevented from
370/// using tools altogether.
371///
372/// # API Reference
373///
374/// Corresponds to the `tool_choice` parameter in the OpenAI Responses API:
375/// <https://platform.openai.com/docs/api-reference/responses/create>
376///
377/// # Examples
378///
379/// ```rust
380/// use openai_tools::responses::request::{ToolChoice, ToolChoiceMode, NamedFunctionChoice};
381///
382/// // Let the model decide
383/// let auto_choice = ToolChoice::Simple(ToolChoiceMode::Auto);
384///
385/// // Force a specific function
386/// let function_choice = ToolChoice::Function(NamedFunctionChoice::new("get_weather"));
387/// ```
388#[derive(Debug, Clone, Serialize)]
389#[serde(untagged)]
390pub enum ToolChoice {
391 /// Simple mode selection (auto, none, required)
392 Simple(ToolChoiceMode),
393 /// Force a specific function to be called
394 Function(NamedFunctionChoice),
395}
396
397/// Reference to a prompt template with variables
398///
399/// Allows you to use pre-defined prompt templates stored in the OpenAI
400/// platform, optionally with variable substitution.
401///
402/// # API Reference
403///
404/// Corresponds to the `prompt` parameter in the OpenAI Responses API:
405/// <https://platform.openai.com/docs/api-reference/responses/create>
406///
407/// # Examples
408///
409/// ```rust
410/// use openai_tools::responses::request::Prompt;
411/// use std::collections::HashMap;
412///
413/// // Simple prompt reference
414/// let prompt = Prompt::new("prompt-abc123");
415///
416/// // Prompt with variables
417/// let mut vars = HashMap::new();
418/// vars.insert("name".to_string(), "Alice".to_string());
419/// let prompt = Prompt::with_variables("prompt-abc123", vars);
420/// ```
421#[derive(Debug, Clone, Serialize)]
422pub struct Prompt {
423 /// The ID of the prompt template
424 pub id: String,
425 /// Optional variables to substitute in the template
426 #[serde(skip_serializing_if = "Option::is_none")]
427 pub variables: Option<HashMap<String, String>>,
428}
429
430impl Prompt {
431 /// Creates a new Prompt reference without variables
432 ///
433 /// # Arguments
434 ///
435 /// * `id` - The ID of the prompt template
436 ///
437 /// # Returns
438 ///
439 /// A new Prompt instance
440 pub fn new<S: AsRef<str>>(id: S) -> Self {
441 Self { id: id.as_ref().to_string(), variables: None }
442 }
443
444 /// Creates a new Prompt reference with variables
445 ///
446 /// # Arguments
447 ///
448 /// * `id` - The ID of the prompt template
449 /// * `variables` - Variables to substitute in the template
450 ///
451 /// # Returns
452 ///
453 /// A new Prompt instance with variables
454 pub fn with_variables<S: AsRef<str>>(id: S, variables: HashMap<String, String>) -> Self {
455 Self { id: id.as_ref().to_string(), variables: Some(variables) }
456 }
457}
458
459/// Options for streaming responses
460///
461/// This struct configures how streaming responses should behave,
462/// including whether to include obfuscated content.
463///
464/// # API Reference
465///
466/// Corresponds to the `stream_options` parameter in the OpenAI Responses API.
467#[derive(Debug, Clone, Serialize)]
468pub struct StreamOptions {
469 /// Whether to include obfuscated content in streaming responses
470 ///
471 /// When enabled, streaming responses may include placeholder or
472 /// obfuscated content that gets replaced as the real content is generated.
473 pub include_obfuscation: bool,
474}
475/// Represents the format configuration for structured output in responses
476///
477/// This struct is used to specify the schema format for structured text output
478/// when making requests to the OpenAI Responses API.
479#[derive(Debug, Clone, Default, Serialize, new)]
480pub struct Format {
481 /// The schema definition that specifies the structure of the expected output
482 pub format: Schema,
483}
484
485/// Represents the body of a request to the OpenAI Responses API
486///
487/// This struct contains all the parameters for making requests to the OpenAI Responses API.
488/// It supports both plain text and structured message input, along with extensive configuration
489/// options for tools, reasoning, output formatting, and response behavior.
490///
491/// # Required Parameters
492///
493/// - `model`: The ID of the model to use
494/// - Either `plain_text_input` OR `messages_input` (mutually exclusive)
495///
496/// # API Reference
497///
498/// Based on the OpenAI Responses API specification:
499/// <https://platform.openai.com/docs/api-reference/responses/create>
500///
501/// # Examples
502///
503/// ## Simple Text Input
504///
505/// ```rust
506/// use openai_tools::responses::request::Body;
507/// use openai_tools::common::models::ChatModel;
508///
509/// let body = Body {
510/// model: ChatModel::Gpt4o,
511/// plain_text_input: Some("What is the weather like?".to_string()),
512/// ..Default::default()
513/// };
514/// ```
515///
516/// ## With Messages and Tools
517///
518/// ```rust
519/// use openai_tools::responses::request::Body;
520/// use openai_tools::common::message::Message;
521/// use openai_tools::common::role::Role;
522/// use openai_tools::common::models::ChatModel;
523///
524/// let messages = vec![
525/// Message::from_string(Role::User, "Help me with coding")
526/// ];
527///
528/// let body = Body {
529/// model: ChatModel::Gpt4o,
530/// messages_input: Some(messages),
531/// instructions: Some("You are a helpful coding assistant".to_string()),
532/// max_output_tokens: Some(1000),
533/// ..Default::default()
534/// };
535/// ```
536#[derive(Debug, Clone, Default, new)]
537#[allow(clippy::too_many_arguments)]
538pub struct Body {
539 /// The model to use for generating responses
540 ///
541 /// Specifies which OpenAI model to use for response generation.
542 ///
543 /// # Required
544 ///
545 /// This field is required for all requests.
546 ///
547 /// # Examples
548 ///
549 /// - `ChatModel::Gpt4o` - Latest GPT-4o model
550 /// - `ChatModel::Gpt4oMini` - Cost-effective option
551 /// - `ChatModel::O3Mini` - Reasoning model
552 pub model: ChatModel,
553
554 /// Optional instructions to guide the model's behavior and response style
555 ///
556 /// Provides system-level instructions that define how the model should
557 /// behave, its personality, response format, or any other behavioral guidance.
558 ///
559 /// # Examples
560 ///
561 /// - `"You are a helpful assistant that provides concise answers"`
562 /// - `"Respond only with JSON formatted data"`
563 /// - `"Act as a professional code reviewer"`
564 pub instructions: Option<String>,
565
566 /// Plain text input for simple text-based requests
567 ///
568 /// Use this for straightforward text input when you don't need the structure
569 /// of messages with roles. This is mutually exclusive with `messages_input`.
570 ///
571 /// # Mutually Exclusive
572 ///
573 /// Cannot be used together with `messages_input`. Choose one based on your needs:
574 /// - Use `plain_text_input` for simple, single-turn interactions
575 /// - Use `messages_input` for conversation history or role-based interactions
576 ///
577 /// # Examples
578 ///
579 /// - `"What is the capital of France?"`
580 /// - `"Summarize this article: [article content]"`
581 /// - `"Write a haiku about programming"`
582 pub plain_text_input: Option<String>,
583
584 /// Structured message input for conversation-style interactions
585 ///
586 /// Use this when you need conversation history, different message roles
587 /// (user, assistant, system), or structured dialogue. This is mutually
588 /// exclusive with `plain_text_input`.
589 ///
590 /// # Mutually Exclusive
591 ///
592 /// Cannot be used together with `plain_text_input`.
593 ///
594 /// # Message Roles
595 ///
596 /// - `System`: Instructions for the model's behavior
597 /// - `User`: User input or questions
598 /// - `Assistant`: Previous model responses (for conversation history)
599 ///
600 /// # Examples
601 ///
602 /// ```rust
603 /// use openai_tools::common::message::Message;
604 /// use openai_tools::common::role::Role;
605 ///
606 /// let messages = vec![
607 /// Message::from_string(Role::System, "You are a helpful assistant"),
608 /// Message::from_string(Role::User, "Hello!"),
609 /// Message::from_string(Role::Assistant, "Hi there! How can I help you?"),
610 /// Message::from_string(Role::User, "What's 2+2?"),
611 /// ];
612 /// ```
613 pub messages_input: Option<Vec<Message>>,
614
615 /// Optional tools that the model can use during response generation
616 ///
617 /// Provides the model with access to external tools like web search,
618 /// code execution, file access, or custom functions. The model will
619 /// automatically decide when and how to use these tools based on the query.
620 ///
621 /// # Tool Types
622 ///
623 /// - Web search tools for finding current information
624 /// - Code interpreter for running and analyzing code
625 /// - File search tools for accessing document collections
626 /// - Custom function tools for specific business logic
627 ///
628 /// # Examples
629 ///
630 /// ```rust
631 /// use openai_tools::common::tool::Tool;
632 /// use openai_tools::common::parameters::ParameterProperty;
633 ///
634 /// let tools = vec![
635 /// Tool::function("search", "Search the web", Vec::<(&str, ParameterProperty)>::new(), false),
636 /// Tool::function("calculate", "Perform calculations", Vec::<(&str, ParameterProperty)>::new(), false),
637 /// ];
638 /// ```
639 pub tools: Option<Vec<Tool>>,
640
641 /// Optional tool choice configuration
642 ///
643 /// Controls how the model selects which tool to use when tools are available.
644 /// Can be set to auto (let model decide), none (no tools), required (must use tools),
645 /// or a specific function name to force calling that function.
646 ///
647 /// # Examples
648 ///
649 /// ```rust
650 /// use openai_tools::responses::request::{ToolChoice, ToolChoiceMode, NamedFunctionChoice};
651 ///
652 /// // Let the model decide
653 /// let auto_choice = ToolChoice::Simple(ToolChoiceMode::Auto);
654 ///
655 /// // Force a specific function
656 /// let function_choice = ToolChoice::Function(NamedFunctionChoice::new("get_weather"));
657 /// ```
658 pub tool_choice: Option<ToolChoice>,
659
660 /// Optional prompt template reference
661 ///
662 /// Allows you to use pre-defined prompt templates stored in the OpenAI
663 /// platform, optionally with variable substitution.
664 ///
665 /// # Examples
666 ///
667 /// ```rust
668 /// use openai_tools::responses::request::Prompt;
669 ///
670 /// let prompt = Prompt::new("prompt-abc123");
671 /// ```
672 pub prompt: Option<Prompt>,
673
674 /// Optional prompt cache key for caching
675 ///
676 /// A unique key to use for prompt caching. When provided, the same
677 /// prompt will be cached and reused for subsequent requests with
678 /// the same cache key.
679 pub prompt_cache_key: Option<String>,
680
681 /// Optional prompt cache retention duration
682 ///
683 /// Controls how long cached prompts should be retained.
684 /// Format: duration string (e.g., "1h", "24h", "7d")
685 pub prompt_cache_retention: Option<String>,
686
687 /// Optional structured output format specification
688 ///
689 /// Defines the structure and format for the model's response output.
690 /// Use this when you need the response in a specific JSON schema format
691 /// or other structured format for programmatic processing.
692 ///
693 /// # Examples
694 ///
695 /// ```rust
696 /// use openai_tools::common::structured_output::Schema;
697 /// use openai_tools::responses::request::Format;
698 ///
699 /// let format = Format::new(Schema::responses_json_schema("response_schema"));
700 /// ```
701 pub structured_output: Option<Format>,
702
703 /// Optional sampling temperature for controlling response randomness
704 ///
705 /// Controls the randomness and creativity of the model's responses.
706 /// Higher values make the output more random and creative, while lower
707 /// values make it more focused, deterministic, and consistent.
708 ///
709 /// # Range
710 ///
711 /// - **Range**: 0.0 to 2.0
712 /// - **Default**: 1.0 (if not specified)
713 /// - **Minimum**: 0.0 (most deterministic, least creative)
714 /// - **Maximum**: 2.0 (most random, most creative)
715 ///
716 /// # Recommended Values
717 ///
718 /// - **0.0 - 0.3**: Highly focused and deterministic
719 /// - Best for: Factual questions, code generation, translations
720 /// - Behavior: Very consistent, predictable responses
721 ///
722 /// - **0.3 - 0.7**: Balanced creativity and consistency
723 /// - Best for: General conversation, explanations, analysis
724 /// - Behavior: Good balance between creativity and reliability
725 ///
726 /// - **0.7 - 1.2**: More creative and varied responses
727 /// - Best for: Creative writing, brainstorming, ideation
728 /// - Behavior: More diverse and interesting outputs
729 ///
730 /// - **1.2 - 2.0**: Highly creative and unpredictable
731 /// - Best for: Experimental creative tasks, humor, unconventional ideas
732 /// - Behavior: Very diverse but potentially less coherent
733 ///
734 /// # Usage Guidelines
735 ///
736 /// - **Start with 0.7** for most applications as a good default
737 /// - **Use 0.0-0.3** when you need consistent, reliable responses
738 /// - **Use 0.8-1.2** for creative tasks that still need coherence
739 /// - **Avoid values above 1.5** unless you specifically want very random outputs
740 ///
741 /// # API Reference
742 ///
743 /// Corresponds to the `temperature` parameter in the OpenAI Responses API:
744 /// <https://platform.openai.com/docs/api-reference/responses/create>
745 ///
746 /// # Examples
747 ///
748 /// ```rust
749 /// use openai_tools::responses::request::Responses;
750 ///
751 /// // Deterministic, factual responses
752 /// let mut client_factual = Responses::new();
753 /// client_factual.temperature(0.2);
754 ///
755 /// // Balanced creativity and consistency
756 /// let mut client_balanced = Responses::new();
757 /// client_balanced.temperature(0.7);
758 ///
759 /// // High creativity for brainstorming
760 /// let mut client_creative = Responses::new();
761 /// client_creative.temperature(1.1);
762 /// ```
763 pub temperature: Option<f64>,
764
765 /// Optional maximum number of tokens to generate in the response
766 ///
767 /// Controls the maximum length of the generated response. The actual response
768 /// may be shorter if the model naturally concludes or hits other stopping conditions.
769 ///
770 /// # Range
771 ///
772 /// - Minimum: 1
773 /// - Maximum: Depends on the model (typically 4096-8192 for most models)
774 ///
775 /// # Default Behavior
776 ///
777 /// If not specified, the model will use its default maximum output length.
778 ///
779 /// # Examples
780 ///
781 /// - `Some(100)` - Short responses, good for summaries or brief answers
782 /// - `Some(1000)` - Medium responses, suitable for detailed explanations
783 /// - `Some(4000)` - Long responses, for comprehensive analysis or long-form content
784 pub max_output_tokens: Option<usize>,
785
786 /// Optional maximum number of tool calls to make
787 ///
788 /// Limits how many tools the model can invoke during response generation.
789 /// This helps control cost and response time when using multiple tools.
790 ///
791 /// # Range
792 ///
793 /// - Minimum: 0 (no tool calls allowed)
794 /// - Maximum: Implementation-dependent
795 ///
796 /// # Use Cases
797 ///
798 /// - Set to `Some(1)` for single tool usage
799 /// - Set to `Some(0)` to disable tool usage entirely
800 /// - Leave as `None` for unlimited tool usage (subject to other constraints)
801 pub max_tool_calls: Option<usize>,
802
803 /// Optional metadata to include with the request
804 ///
805 /// Arbitrary key-value pairs that can be attached to the request for
806 /// tracking, logging, or passing additional context that doesn't affect
807 /// the model's behavior.
808 ///
809 /// # Common Use Cases
810 ///
811 /// - Request tracking: `{"request_id": "req_123", "user_id": "user_456"}`
812 /// - A/B testing: `{"experiment": "variant_a", "test_group": "control"}`
813 /// - Analytics: `{"session_id": "sess_789", "feature": "chat"}`
814 ///
815 /// # Examples
816 ///
817 /// ```rust
818 /// use std::collections::HashMap;
819 /// use serde_json::Value;
820 ///
821 /// let mut metadata = HashMap::new();
822 /// metadata.insert("user_id".to_string(), Value::String("user123".to_string()));
823 /// metadata.insert("session_id".to_string(), Value::String("sess456".to_string()));
824 /// metadata.insert("priority".to_string(), Value::Number(serde_json::Number::from(1)));
825 /// ```
826 pub metadata: Option<HashMap<String, serde_json::Value>>,
827
828 /// Optional flag to enable parallel tool calls
829 ///
830 /// When enabled, the model can make multiple tool calls simultaneously
831 /// rather than sequentially. This can significantly improve response time
832 /// when multiple independent tools need to be used.
833 ///
834 /// # Default
835 ///
836 /// If not specified, defaults to the model's default behavior (usually `true`).
837 ///
838 /// # When to Use
839 ///
840 /// - `Some(true)`: Enable when tools are independent and can run in parallel
841 /// - `Some(false)`: Disable when tools have dependencies or order matters
842 ///
843 /// # Examples
844 ///
845 /// - Weather + Stock prices: Can run in parallel (`true`)
846 /// - File read + File analysis: Should run sequentially (`false`)
847 pub parallel_tool_calls: Option<bool>,
848
849 /// Optional fields to include in the output
850 ///
851 /// Specifies additional metadata and information to include in the response
852 /// beyond the main generated content. This can include tool call details,
853 /// reasoning traces, log probabilities, and more.
854 ///
855 /// # Available Inclusions
856 ///
857 /// - Web search call sources and results
858 /// - Code interpreter execution outputs
859 /// - Image URLs from various sources
860 /// - Log probabilities for generated tokens
861 /// - Reasoning traces and encrypted content
862 ///
863 /// # Examples
864 ///
865 /// ```rust
866 /// use openai_tools::responses::request::Include;
867 ///
868 /// let includes = vec![
869 /// Include::WebSearchCall,
870 /// Include::LogprobsInOutput,
871 /// Include::ReasoningEncryptedContent,
872 /// ];
873 /// ```
874 pub include: Option<Vec<Include>>,
875
876 /// Optional flag to enable background processing
877 ///
878 /// When enabled, allows the request to be processed in the background,
879 /// potentially improving throughput for non-urgent requests.
880 ///
881 /// # Use Cases
882 ///
883 /// - `Some(true)`: Batch processing, non-interactive requests
884 /// - `Some(false)` or `None`: Real-time, interactive requests
885 ///
886 /// # Trade-offs
887 ///
888 /// - Background processing may have lower latency guarantees
889 /// - May be more cost-effective for bulk operations
890 /// - May have different rate limiting behavior
891 pub background: Option<bool>,
892
893 /// Optional conversation ID for tracking
894 ///
895 /// Identifier for grouping related requests as part of the same conversation
896 /// or session. This helps with context management and analytics.
897 ///
898 /// # Format
899 ///
900 /// Typically a UUID or other unique identifier string.
901 ///
902 /// # Examples
903 ///
904 /// - `Some("conv_123e4567-e89b-12d3-a456-426614174000".to_string())`
905 /// - `Some("user123_session456".to_string())`
906 pub conversation: Option<String>,
907
908 /// Optional ID of the previous response for context
909 ///
910 /// References a previous response in the same conversation to maintain
911 /// context and enable features like response chaining or follow-up handling.
912 ///
913 /// # Use Cases
914 ///
915 /// - Multi-turn conversations with context preservation
916 /// - Follow-up questions or clarifications
917 /// - Response refinement or iteration
918 ///
919 /// # Examples
920 ///
921 /// - `Some("resp_abc123def456".to_string())`
922 pub previous_response_id: Option<String>,
923
924 /// Optional reasoning configuration
925 ///
926 /// Controls how the model approaches complex reasoning tasks, including
927 /// the effort level and format of reasoning explanations.
928 ///
929 /// # Use Cases
930 ///
931 /// - Complex problem-solving requiring deep analysis
932 /// - Mathematical or logical reasoning tasks
933 /// - When you need insight into the model's reasoning process
934 ///
935 /// # Examples
936 ///
937 /// ```rust
938 /// use openai_tools::responses::request::{Reasoning, ReasoningEffort, ReasoningSummary};
939 ///
940 /// let reasoning = Reasoning {
941 /// effort: Some(ReasoningEffort::High),
942 /// summary: Some(ReasoningSummary::Detailed),
943 /// };
944 /// ```
945 pub reasoning: Option<Reasoning>,
946
947 /// Optional text output configuration
948 ///
949 /// Controls the characteristics of the generated text output,
950 /// such as verbosity level. Available on GPT-5.2 and newer models.
951 ///
952 /// # Examples
953 ///
954 /// ```rust
955 /// use openai_tools::responses::request::{TextConfig, TextVerbosity};
956 ///
957 /// let text = TextConfig {
958 /// verbosity: Some(TextVerbosity::High),
959 /// };
960 /// ```
961 pub text: Option<TextConfig>,
962
963 /// Optional safety identifier
964 ///
965 /// Identifier for safety and content filtering configurations.
966 /// Used to specify which safety policies should be applied to the request.
967 ///
968 /// # Examples
969 ///
970 /// - `Some("strict".to_string())` - Apply strict content filtering
971 /// - `Some("moderate".to_string())` - Apply moderate content filtering
972 /// - `Some("permissive".to_string())` - Apply permissive content filtering
973 pub safety_identifier: Option<String>,
974
975 /// Optional service tier specification
976 ///
977 /// Specifies the service tier for the request, which may affect
978 /// processing priority, rate limits, and pricing.
979 ///
980 /// # Common Values
981 ///
982 /// - `Some("default".to_string())` - Standard service tier
983 /// - `Some("scale".to_string())` - High-throughput tier
984 /// - `Some("premium".to_string())` - Premium service tier with enhanced features
985 pub service_tier: Option<String>,
986
987 /// Optional flag to store the conversation
988 ///
989 /// When enabled, the conversation may be stored for future reference,
990 /// training, or analytics purposes (subject to privacy policies).
991 ///
992 /// # Privacy Considerations
993 ///
994 /// - `Some(true)`: Allow storage (check privacy policies)
995 /// - `Some(false)`: Explicitly opt-out of storage
996 /// - `None`: Use default storage policy
997 pub store: Option<bool>,
998
999 /// Optional flag to enable streaming responses
1000 ///
1001 /// When enabled, the response will be streamed back in chunks as it's
1002 /// generated, allowing for real-time display of partial results.
1003 ///
1004 /// # Use Cases
1005 ///
1006 /// - `Some(true)`: Real-time chat interfaces, live text generation
1007 /// - `Some(false)`: Batch processing, when you need the complete response
1008 ///
1009 /// # Considerations
1010 ///
1011 /// - Streaming responses require different handling in client code
1012 /// - May affect some response features or formatting options
1013 pub stream: Option<bool>,
1014
1015 /// Optional streaming configuration options
1016 ///
1017 /// Additional options for controlling streaming response behavior,
1018 /// such as whether to include obfuscated placeholder content.
1019 ///
1020 /// # Only Relevant When Streaming
1021 ///
1022 /// This field is only meaningful when `stream` is `Some(true)`.
1023 pub stream_options: Option<StreamOptions>,
1024
1025 /// Optional number of top log probabilities to include
1026 ///
1027 /// Specifies how many of the most likely alternative tokens to include
1028 /// with their log probabilities for each generated token.
1029 ///
1030 /// # Range
1031 ///
1032 /// - Minimum: 0 (no log probabilities)
1033 /// - Maximum: Model-dependent (typically 5-20)
1034 ///
1035 /// # Use Cases
1036 ///
1037 /// - Model analysis and debugging
1038 /// - Confidence estimation
1039 /// - Alternative response exploration
1040 ///
1041 /// # Examples
1042 ///
1043 /// - `Some(1)` - Include the top alternative for each token
1044 /// - `Some(5)` - Include top 5 alternatives for detailed analysis
1045 pub top_logprobs: Option<usize>,
1046
1047 /// Optional nucleus sampling parameter
1048 ///
1049 /// Controls the randomness of the model's responses by limiting the
1050 /// cumulative probability of considered tokens.
1051 ///
1052 /// # Range
1053 ///
1054 /// - 0.0 to 1.0
1055 /// - Lower values (e.g., 0.1) make responses more focused and deterministic
1056 /// - Higher values (e.g., 0.9) make responses more diverse and creative
1057 ///
1058 /// # Default
1059 ///
1060 /// If not specified, uses the model's default value (typically around 1.0).
1061 ///
1062 /// # Examples
1063 ///
1064 /// - `Some(0.1)` - Very focused, deterministic responses
1065 /// - `Some(0.7)` - Balanced creativity and focus
1066 /// - `Some(0.95)` - High creativity and diversity
1067 pub top_p: Option<f64>,
1068
1069 /// Optional truncation behavior configuration
1070 ///
1071 /// Controls how the system handles inputs that exceed the maximum
1072 /// context length supported by the model.
1073 ///
1074 /// # Options
1075 ///
1076 /// - `Some(Truncation::Auto)` - Automatically truncate long inputs
1077 /// - `Some(Truncation::Disabled)` - Return error for long inputs
1078 /// - `None` - Use system default behavior
1079 ///
1080 /// # Use Cases
1081 ///
1082 /// - `Auto`: When you want to handle long documents gracefully
1083 /// - `Disabled`: When you need to ensure complete input processing
1084 pub truncation: Option<Truncation>,
1085}
1086
1087impl Serialize for Body {
1088 /// Custom serialization implementation for the request body
1089 ///
1090 /// This implementation handles the conversion of either plain text input
1091 /// or messages input into the appropriate "input" field format required
1092 /// by the OpenAI API. It also conditionally includes optional fields
1093 /// like tools and text formatting.
1094 ///
1095 /// # Errors
1096 ///
1097 /// Returns a serialization error if neither plain_text_input nor
1098 /// messages_input is set, as one of them is required.
1099 fn serialize<S>(&self, serializer: S) -> anyhow::Result<S::Ok, S::Error>
1100 where
1101 S: serde::Serializer,
1102 {
1103 let mut state = serializer.serialize_struct("ResponsesBody", 4)?;
1104 state.serialize_field("model", &self.model)?;
1105
1106 // Set input
1107 if self.plain_text_input.is_some() {
1108 state.serialize_field("input", &self.plain_text_input.clone().unwrap())?;
1109 } else if self.messages_input.is_some() {
1110 state.serialize_field("input", &self.messages_input.clone().unwrap())?;
1111 } else {
1112 return Err(serde::ser::Error::custom("Either plain_text_input or messages_input must be set."));
1113 };
1114
1115 // Optional fields
1116 if self.temperature.is_some() {
1117 state.serialize_field("temperature", &self.temperature)?;
1118 }
1119 if self.instructions.is_some() {
1120 state.serialize_field("instructions", &self.instructions)?;
1121 }
1122 if self.tools.is_some() {
1123 state.serialize_field("tools", &self.tools)?;
1124 }
1125 if self.tool_choice.is_some() {
1126 state.serialize_field("tool_choice", &self.tool_choice)?;
1127 }
1128 if self.prompt.is_some() {
1129 state.serialize_field("prompt", &self.prompt)?;
1130 }
1131 if self.prompt_cache_key.is_some() {
1132 state.serialize_field("prompt_cache_key", &self.prompt_cache_key)?;
1133 }
1134 if self.prompt_cache_retention.is_some() {
1135 state.serialize_field("prompt_cache_retention", &self.prompt_cache_retention)?;
1136 }
1137 if self.structured_output.is_some() {
1138 state.serialize_field("text", &self.structured_output)?;
1139 }
1140 if self.max_output_tokens.is_some() {
1141 state.serialize_field("max_output_tokens", &self.max_output_tokens)?;
1142 }
1143 if self.max_tool_calls.is_some() {
1144 state.serialize_field("max_tool_calls", &self.max_tool_calls)?;
1145 }
1146 if self.metadata.is_some() {
1147 state.serialize_field("metadata", &self.metadata)?;
1148 }
1149 if self.parallel_tool_calls.is_some() {
1150 state.serialize_field("parallel_tool_calls", &self.parallel_tool_calls)?;
1151 }
1152 if self.include.is_some() {
1153 state.serialize_field("include", &self.include)?;
1154 }
1155 if self.background.is_some() {
1156 state.serialize_field("background", &self.background)?;
1157 }
1158 if self.conversation.is_some() {
1159 state.serialize_field("conversation", &self.conversation)?;
1160 }
1161 if self.previous_response_id.is_some() {
1162 state.serialize_field("previous_response_id", &self.previous_response_id)?;
1163 }
1164 if self.reasoning.is_some() {
1165 state.serialize_field("reasoning", &self.reasoning)?;
1166 }
1167 if self.text.is_some() {
1168 state.serialize_field("text", &self.text)?;
1169 }
1170 if self.safety_identifier.is_some() {
1171 state.serialize_field("safety_identifier", &self.safety_identifier)?;
1172 }
1173 if self.service_tier.is_some() {
1174 state.serialize_field("service_tier", &self.service_tier)?;
1175 }
1176 if self.store.is_some() {
1177 state.serialize_field("store", &self.store)?;
1178 }
1179 if self.stream.is_some() {
1180 state.serialize_field("stream", &self.stream)?;
1181 }
1182 if self.stream_options.is_some() {
1183 state.serialize_field("stream_options", &self.stream_options)?;
1184 }
1185 if self.top_logprobs.is_some() {
1186 state.serialize_field("top_logprobs", &self.top_logprobs)?;
1187 }
1188 if self.top_p.is_some() {
1189 state.serialize_field("top_p", &self.top_p)?;
1190 }
1191 if self.truncation.is_some() {
1192 state.serialize_field("truncation", &self.truncation)?;
1193 }
1194 state.end()
1195 }
1196}
1197
1198/// Default API path for Responses
1199const RESPONSES_PATH: &str = "responses";
1200
1201/// Client for making requests to the OpenAI Responses API
1202///
1203/// This struct provides a convenient interface for building and executing requests
1204/// to the OpenAI Responses API and Azure OpenAI API. It handles authentication,
1205/// request formatting, and response parsing automatically.
1206///
1207/// # Providers
1208///
1209/// The client supports two providers:
1210/// - **OpenAI**: Standard OpenAI API (default)
1211/// - **Azure**: Azure OpenAI Service
1212///
1213/// # Examples
1214///
1215/// ## OpenAI (existing behavior - unchanged)
1216///
1217/// ```rust,no_run
1218/// use openai_tools::responses::request::Responses;
1219///
1220/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1221/// let mut client = Responses::new();
1222/// let response = client
1223/// .model_id("gpt-4")
1224/// .instructions("You are a helpful assistant.")
1225/// .str_message("Hello, how are you?")
1226/// .complete()
1227/// .await?;
1228/// # Ok(())
1229/// # }
1230/// ```
1231///
1232/// ## Azure OpenAI
1233///
1234/// ```rust,no_run
1235/// use openai_tools::responses::request::Responses;
1236///
1237/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1238/// let mut client = Responses::azure()?;
1239/// let response = client
1240/// .str_message("Hello!")
1241/// .complete()
1242/// .await?;
1243/// # Ok(())
1244/// # }
1245/// ```
1246#[derive(Debug, Clone)]
1247pub struct Responses {
1248 /// Authentication provider (OpenAI or Azure)
1249 auth: AuthProvider,
1250 /// The User-Agent string to include in requests
1251 user_agent: String,
1252 /// The request body containing all parameters for the API call
1253 pub request_body: Body,
1254 /// Optional request timeout duration
1255 timeout: Option<Duration>,
1256}
1257
1258impl Default for Responses {
1259 fn default() -> Self {
1260 Self::new()
1261 }
1262}
1263
1264impl Responses {
1265 /// Creates a new instance of the Responses client for OpenAI API
1266 ///
1267 /// This method initializes a new client by loading the OpenAI API key from
1268 /// the `OPENAI_API_KEY` environment variable. Make sure to set this environment
1269 /// variable before calling this method.
1270 ///
1271 /// # Panics
1272 ///
1273 /// Panics if the `OPENAI_API_KEY` environment variable is not set.
1274 pub fn new() -> Self {
1275 let auth = AuthProvider::openai_from_env().map_err(|e| OpenAIToolError::Error(format!("Failed to load OpenAI auth: {}", e))).unwrap();
1276 Self { auth, user_agent: "".into(), request_body: Body::default(), timeout: None }
1277 }
1278
1279 /// Creates a new instance of the Responses client with a custom endpoint
1280 #[deprecated(since = "0.3.0", note = "Use `with_auth()` with custom OpenAIAuth for custom endpoints")]
1281 pub fn from_endpoint<T: AsRef<str>>(endpoint: T) -> Self {
1282 let auth = AuthProvider::openai_from_env().map_err(|e| OpenAIToolError::Error(format!("Failed to load OpenAI auth: {}", e))).unwrap();
1283 // Extract the path from the endpoint and use it
1284 let mut responses = Self { auth, user_agent: "".into(), request_body: Body::default(), timeout: None };
1285 responses.base_url(endpoint.as_ref().trim_end_matches("/responses"));
1286 responses
1287 }
1288
1289 /// Creates a new Responses client with a specified model.
1290 ///
1291 /// This is the recommended constructor as it enables parameter validation
1292 /// at setter time. When you set parameters like `temperature()` or `top_p()`,
1293 /// the model's parameter support is checked and warnings are logged for
1294 /// unsupported values.
1295 ///
1296 /// # Arguments
1297 ///
1298 /// * `model` - The model to use for response generation
1299 ///
1300 /// # Panics
1301 ///
1302 /// Panics if the `OPENAI_API_KEY` environment variable is not set.
1303 ///
1304 /// # Returns
1305 ///
1306 /// A new Responses instance with the specified model
1307 ///
1308 /// # Example
1309 ///
1310 /// ```rust,no_run
1311 /// use openai_tools::responses::request::Responses;
1312 /// use openai_tools::common::models::ChatModel;
1313 ///
1314 /// // Recommended: specify model at creation time
1315 /// let mut responses = Responses::with_model(ChatModel::Gpt4oMini);
1316 ///
1317 /// // For reasoning models, unsupported parameters are validated at setter time
1318 /// let mut reasoning_responses = Responses::with_model(ChatModel::O3Mini);
1319 /// reasoning_responses.temperature(0.5); // Warning logged, value ignored
1320 /// ```
1321 pub fn with_model(model: ChatModel) -> Self {
1322 let auth = AuthProvider::openai_from_env().map_err(|e| OpenAIToolError::Error(format!("Failed to load OpenAI auth: {}", e))).unwrap();
1323 Self { auth, user_agent: "".into(), request_body: Body { model, ..Default::default() }, timeout: None }
1324 }
1325
1326 /// Creates a new Responses client with a custom authentication provider
1327 ///
1328 /// Use this to explicitly configure OpenAI or Azure authentication.
1329 ///
1330 /// # Arguments
1331 ///
1332 /// * `auth` - The authentication provider
1333 ///
1334 /// # Returns
1335 ///
1336 /// A new Responses instance with the specified auth provider
1337 ///
1338 /// # Example
1339 ///
1340 /// ```rust
1341 /// use openai_tools::responses::request::Responses;
1342 /// use openai_tools::common::auth::{AuthProvider, AzureAuth};
1343 ///
1344 /// // Explicit Azure configuration with complete base URL
1345 /// let auth = AuthProvider::Azure(
1346 /// AzureAuth::new(
1347 /// "api-key",
1348 /// "https://my-resource.openai.azure.com/openai/deployments/gpt-4o?api-version=2024-08-01-preview"
1349 /// )
1350 /// );
1351 /// let mut responses = Responses::with_auth(auth);
1352 /// ```
1353 pub fn with_auth(auth: AuthProvider) -> Self {
1354 Self { auth, user_agent: "".into(), request_body: Body::default(), timeout: None }
1355 }
1356
1357 /// Creates a new Responses client for Azure OpenAI API
1358 ///
1359 /// Loads configuration from Azure-specific environment variables.
1360 ///
1361 /// # Returns
1362 ///
1363 /// `Result<Responses>` - Configured for Azure or error if env vars missing
1364 ///
1365 /// # Environment Variables
1366 ///
1367 /// | Variable | Required | Description |
1368 /// |----------|----------|-------------|
1369 /// | `AZURE_OPENAI_API_KEY` | Yes | Azure API key |
1370 /// | `AZURE_OPENAI_BASE_URL` | Yes | Complete endpoint URL including deployment, API path, and api-version |
1371 ///
1372 /// # Example
1373 ///
1374 /// ```rust,no_run
1375 /// use openai_tools::responses::request::Responses;
1376 ///
1377 /// // With environment variables:
1378 /// // AZURE_OPENAI_API_KEY=xxx
1379 /// // AZURE_OPENAI_BASE_URL=https://my-resource.openai.azure.com/openai/deployments/gpt-4o/responses?api-version=2024-08-01-preview
1380 /// let mut responses = Responses::azure()?;
1381 /// # Ok::<(), openai_tools::common::errors::OpenAIToolError>(())
1382 /// ```
1383 pub fn azure() -> Result<Self> {
1384 let auth = AuthProvider::azure_from_env()?;
1385 Ok(Self { auth, user_agent: "".into(), request_body: Body::default(), timeout: None })
1386 }
1387
1388 /// Creates a new Responses client by auto-detecting the provider
1389 ///
1390 /// Tries Azure first (if AZURE_OPENAI_API_KEY is set), then falls back to OpenAI.
1391 ///
1392 /// # Returns
1393 ///
1394 /// `Result<Responses>` - Auto-configured client or error
1395 ///
1396 /// # Example
1397 ///
1398 /// ```rust,no_run
1399 /// use openai_tools::responses::request::Responses;
1400 ///
1401 /// // Uses Azure if AZURE_OPENAI_API_KEY is set, otherwise OpenAI
1402 /// let mut responses = Responses::detect_provider()?;
1403 /// # Ok::<(), openai_tools::common::errors::OpenAIToolError>(())
1404 /// ```
1405 pub fn detect_provider() -> Result<Self> {
1406 let auth = AuthProvider::from_env()?;
1407 Ok(Self { auth, user_agent: "".into(), request_body: Body::default(), timeout: None })
1408 }
1409
1410 /// Creates a new Responses instance with URL-based provider detection
1411 ///
1412 /// Analyzes the URL pattern to determine the provider:
1413 /// - URLs containing `.openai.azure.com` → Azure
1414 /// - All other URLs → OpenAI-compatible
1415 ///
1416 /// # Arguments
1417 ///
1418 /// * `base_url` - The complete base URL for API requests
1419 /// * `api_key` - The API key or token
1420 pub fn with_url<S: Into<String>>(base_url: S, api_key: S) -> Self {
1421 let auth = AuthProvider::from_url_with_key(base_url, api_key);
1422 Self { auth, user_agent: "".into(), request_body: Body::default(), timeout: None }
1423 }
1424
1425 /// Creates a new Responses instance from URL using environment variables
1426 ///
1427 /// Analyzes the URL pattern to determine the provider, then loads
1428 /// credentials from the appropriate environment variables.
1429 pub fn from_url<S: Into<String>>(url: S) -> Result<Self> {
1430 let auth = AuthProvider::from_url(url)?;
1431 Ok(Self { auth, user_agent: "".into(), request_body: Body::default(), timeout: None })
1432 }
1433
1434 /// Returns the authentication provider
1435 ///
1436 /// # Returns
1437 ///
1438 /// Reference to the authentication provider
1439 pub fn auth(&self) -> &AuthProvider {
1440 &self.auth
1441 }
1442
1443 /// Sets a custom API endpoint URL (OpenAI only)
1444 ///
1445 /// Use this to point to alternative OpenAI-compatible APIs (e.g., proxy servers).
1446 /// For Azure, use `azure()` or `with_auth()` instead.
1447 ///
1448 /// # Arguments
1449 ///
1450 /// * `url` - The base URL (e.g., "https://my-proxy.example.com/v1")
1451 ///
1452 /// # Returns
1453 ///
1454 /// A mutable reference to self for method chaining
1455 ///
1456 /// # Note
1457 ///
1458 /// This method only works with OpenAI authentication. For Azure, the endpoint
1459 /// is constructed from resource name and deployment name.
1460 ///
1461 /// # Example
1462 ///
1463 /// ```rust,no_run
1464 /// use openai_tools::responses::request::Responses;
1465 ///
1466 /// let mut responses = Responses::new();
1467 /// responses.base_url("https://my-proxy.example.com/v1");
1468 /// ```
1469 pub fn base_url<T: AsRef<str>>(&mut self, url: T) -> &mut Self {
1470 // Only modify if OpenAI provider
1471 if let AuthProvider::OpenAI(ref openai_auth) = self.auth {
1472 let new_auth = OpenAIAuth::new(openai_auth.api_key()).with_base_url(url.as_ref());
1473 self.auth = AuthProvider::OpenAI(new_auth);
1474 } else {
1475 tracing::warn!("base_url() is only supported for OpenAI provider. Use azure() or with_auth() for Azure.");
1476 }
1477 self
1478 }
1479
1480 /// Sets the model for the request.
1481 ///
1482 /// # Arguments
1483 ///
1484 /// * `model` - The model to use (e.g., `ChatModel::Gpt4oMini`, `ChatModel::Gpt4o`)
1485 ///
1486 /// # Returns
1487 ///
1488 /// A mutable reference to self for method chaining
1489 ///
1490 /// # Example
1491 ///
1492 /// ```rust,no_run
1493 /// use openai_tools::responses::request::Responses;
1494 /// use openai_tools::common::models::ChatModel;
1495 ///
1496 /// let mut responses = Responses::new();
1497 /// responses.model(ChatModel::Gpt4oMini);
1498 /// ```
1499 pub fn model(&mut self, model: ChatModel) -> &mut Self {
1500 self.request_body.model = model;
1501 self
1502 }
1503
1504 /// Sets the model using a string ID (for backward compatibility).
1505 ///
1506 /// Prefer using [`model`] with `ChatModel` enum for type safety.
1507 ///
1508 /// # Arguments
1509 ///
1510 /// * `model_id` - The ID of the model to use (e.g., "gpt-4o-mini")
1511 ///
1512 /// # Returns
1513 ///
1514 /// A mutable reference to self for method chaining
1515 #[deprecated(since = "0.2.0", note = "Use `model(ChatModel)` instead for type safety")]
1516 pub fn model_id<T: AsRef<str>>(&mut self, model_id: T) -> &mut Self {
1517 self.request_body.model = ChatModel::from(model_id.as_ref());
1518 self
1519 }
1520
1521 /// Sets the request timeout duration
1522 ///
1523 /// # Arguments
1524 ///
1525 /// * `timeout` - The maximum time to wait for a response
1526 ///
1527 /// # Returns
1528 ///
1529 /// A mutable reference to self for method chaining
1530 ///
1531 /// # Example
1532 ///
1533 /// ```rust,no_run
1534 /// use std::time::Duration;
1535 /// use openai_tools::responses::request::Responses;
1536 ///
1537 /// let mut responses = Responses::new();
1538 /// responses.model_id("gpt-4o")
1539 /// .timeout(Duration::from_secs(30));
1540 /// ```
1541 pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
1542 self.timeout = Some(timeout);
1543 self
1544 }
1545
1546 /// Sets the User-Agent string for the request
1547 ///
1548 /// # Arguments
1549 ///
1550 /// * `user_agent` - The User-Agent string to include in the request headers
1551 ///
1552 /// # Returns
1553 ///
1554 /// A mutable reference to self for method chaining
1555 pub fn user_agent<T: AsRef<str>>(&mut self, user_agent: T) -> &mut Self {
1556 self.user_agent = user_agent.as_ref().to_string();
1557 self
1558 }
1559
1560 /// Sets instructions to guide the model's behavior
1561 ///
1562 /// # Arguments
1563 ///
1564 /// * `instructions` - Instructions that define how the model should behave
1565 ///
1566 /// # Returns
1567 ///
1568 /// A mutable reference to self for method chaining
1569 pub fn instructions<T: AsRef<str>>(&mut self, instructions: T) -> &mut Self {
1570 self.request_body.instructions = Some(instructions.as_ref().to_string());
1571 self
1572 }
1573
1574 /// Sets plain text input for simple text-based requests
1575 ///
1576 /// This method is mutually exclusive with `messages()`. Use this for simple
1577 /// text-based interactions where you don't need conversation history.
1578 ///
1579 /// # Arguments
1580 ///
1581 /// * `input` - The plain text input to send to the model
1582 ///
1583 /// # Returns
1584 ///
1585 /// A mutable reference to self for method chaining
1586 pub fn str_message<T: AsRef<str>>(&mut self, input: T) -> &mut Self {
1587 self.request_body.plain_text_input = Some(input.as_ref().to_string());
1588 self
1589 }
1590
1591 /// Sets structured message input for conversation-style interactions
1592 ///
1593 /// This method is mutually exclusive with `plain_text_input()`. Use this
1594 /// for complex conversations with message history and different roles.
1595 ///
1596 /// # Arguments
1597 ///
1598 /// * `messages` - A vector of messages representing the conversation history
1599 ///
1600 /// # Returns
1601 ///
1602 /// A mutable reference to self for method chaining
1603 pub fn messages(&mut self, messages: Vec<Message>) -> &mut Self {
1604 self.request_body.messages_input = Some(messages);
1605 self
1606 }
1607
1608 /// Sets tools that the model can use during response generation
1609 ///
1610 /// # Arguments
1611 ///
1612 /// * `tools` - A vector of tools available to the model
1613 ///
1614 /// # Returns
1615 ///
1616 /// A mutable reference to self for method chaining
1617 pub fn tools(&mut self, tools: Vec<Tool>) -> &mut Self {
1618 self.request_body.tools = Some(tools);
1619 self
1620 }
1621
1622 /// Sets the tool choice configuration
1623 ///
1624 /// Controls how the model selects which tool to use when tools are available.
1625 /// Can be set to auto (let model decide), none (no tools), required (must use tools),
1626 /// or a specific function name to force calling that function.
1627 ///
1628 /// # Arguments
1629 ///
1630 /// * `tool_choice` - The tool choice configuration
1631 ///
1632 /// # Returns
1633 ///
1634 /// A mutable reference to self for method chaining
1635 ///
1636 /// # Examples
1637 ///
1638 /// ```rust
1639 /// use openai_tools::responses::request::{Responses, ToolChoice, ToolChoiceMode, NamedFunctionChoice};
1640 ///
1641 /// let mut client = Responses::new();
1642 ///
1643 /// // Let the model decide
1644 /// client.tool_choice(ToolChoice::Simple(ToolChoiceMode::Auto));
1645 ///
1646 /// // Force a specific function
1647 /// client.tool_choice(ToolChoice::Function(NamedFunctionChoice::new("get_weather")));
1648 /// ```
1649 pub fn tool_choice(&mut self, tool_choice: ToolChoice) -> &mut Self {
1650 self.request_body.tool_choice = Some(tool_choice);
1651 self
1652 }
1653
1654 /// Sets a prompt template reference
1655 ///
1656 /// Allows you to use pre-defined prompt templates stored in the OpenAI
1657 /// platform, optionally with variable substitution.
1658 ///
1659 /// # Arguments
1660 ///
1661 /// * `prompt` - The prompt template reference
1662 ///
1663 /// # Returns
1664 ///
1665 /// A mutable reference to self for method chaining
1666 ///
1667 /// # Examples
1668 ///
1669 /// ```rust
1670 /// use openai_tools::responses::request::{Responses, Prompt};
1671 /// use std::collections::HashMap;
1672 ///
1673 /// let mut client = Responses::new();
1674 ///
1675 /// // Simple prompt reference
1676 /// client.prompt(Prompt::new("prompt-abc123"));
1677 ///
1678 /// // Prompt with variables
1679 /// let mut vars = HashMap::new();
1680 /// vars.insert("name".to_string(), "Alice".to_string());
1681 /// client.prompt(Prompt::with_variables("prompt-abc123", vars));
1682 /// ```
1683 pub fn prompt(&mut self, prompt: Prompt) -> &mut Self {
1684 self.request_body.prompt = Some(prompt);
1685 self
1686 }
1687
1688 /// Sets the prompt cache key for caching
1689 ///
1690 /// A unique key to use for prompt caching. When provided, the same
1691 /// prompt will be cached and reused for subsequent requests with
1692 /// the same cache key.
1693 ///
1694 /// # Arguments
1695 ///
1696 /// * `key` - The cache key to use
1697 ///
1698 /// # Returns
1699 ///
1700 /// A mutable reference to self for method chaining
1701 ///
1702 /// # Examples
1703 ///
1704 /// ```rust
1705 /// use openai_tools::responses::request::Responses;
1706 ///
1707 /// let mut client = Responses::new();
1708 /// client.prompt_cache_key("my-cache-key");
1709 /// ```
1710 pub fn prompt_cache_key<T: AsRef<str>>(&mut self, key: T) -> &mut Self {
1711 self.request_body.prompt_cache_key = Some(key.as_ref().to_string());
1712 self
1713 }
1714
1715 /// Sets the prompt cache retention duration
1716 ///
1717 /// Controls how long cached prompts should be retained.
1718 ///
1719 /// # Arguments
1720 ///
1721 /// * `retention` - The retention duration string (e.g., "1h", "24h", "7d")
1722 ///
1723 /// # Returns
1724 ///
1725 /// A mutable reference to self for method chaining
1726 ///
1727 /// # Examples
1728 ///
1729 /// ```rust
1730 /// use openai_tools::responses::request::Responses;
1731 ///
1732 /// let mut client = Responses::new();
1733 /// client.prompt_cache_retention("24h");
1734 /// ```
1735 pub fn prompt_cache_retention<T: AsRef<str>>(&mut self, retention: T) -> &mut Self {
1736 self.request_body.prompt_cache_retention = Some(retention.as_ref().to_string());
1737 self
1738 }
1739
1740 /// Sets structured output format specification
1741 ///
1742 /// This allows you to specify the exact format and structure of the
1743 /// model's response output.
1744 ///
1745 /// # Arguments
1746 ///
1747 /// * `text_format` - The schema defining the expected output structure
1748 ///
1749 /// # Returns
1750 ///
1751 /// A mutable reference to self for method chaining
1752 pub fn structured_output(&mut self, text_format: Schema) -> &mut Self {
1753 self.request_body.structured_output = Option::from(Format::new(text_format));
1754 self
1755 }
1756
1757 /// Sets the sampling temperature for controlling response randomness
1758 ///
1759 /// Controls the randomness and creativity of the model's responses.
1760 /// Higher values make the output more random and creative, while lower
1761 /// values make it more focused and deterministic.
1762 ///
1763 /// # Arguments
1764 ///
1765 /// * `temperature` - The temperature value (0.0 to 2.0)
1766 /// - 0.0: Most deterministic and focused responses
1767 /// - 1.0: Default balanced behavior
1768 /// - 2.0: Most random and creative responses
1769 ///
1770 /// **Note:** Reasoning models (GPT-6, GPT-5, o-series) only support temperature=1.0.
1771 /// For these models, other values will be ignored with a warning.
1772 ///
1773 /// # Panics
1774 ///
1775 /// This method will panic if the temperature value is outside the valid
1776 /// range of 0.0 to 2.0, as this would result in an API error.
1777 ///
1778 /// # Returns
1779 ///
1780 /// A mutable reference to self for method chaining
1781 ///
1782 /// # Examples
1783 ///
1784 /// ```rust
1785 /// use openai_tools::responses::request::Responses;
1786 ///
1787 /// // Deterministic responses for factual queries
1788 /// let mut client = Responses::new();
1789 /// client.temperature(0.2);
1790 ///
1791 /// // Creative responses for brainstorming
1792 /// let mut client = Responses::new();
1793 /// client.temperature(1.1);
1794 /// ```
1795 pub fn temperature(&mut self, temperature: f64) -> &mut Self {
1796 assert!((0.0..=2.0).contains(&temperature), "Temperature must be between 0.0 and 2.0, got {}", temperature);
1797 let support = self.request_body.model.parameter_support();
1798 match support.temperature {
1799 ParameterRestriction::FixedValue(fixed) => {
1800 if (temperature - fixed).abs() > f64::EPSILON {
1801 tracing::warn!("Model '{}' only supports temperature={}. Ignoring temperature={}.", self.request_body.model, fixed, temperature);
1802 return self;
1803 }
1804 }
1805 ParameterRestriction::NotSupported => {
1806 tracing::warn!("Model '{}' does not support temperature parameter. Ignoring.", self.request_body.model);
1807 return self;
1808 }
1809 ParameterRestriction::Any => {}
1810 }
1811 self.request_body.temperature = Some(temperature);
1812 self
1813 }
1814
1815 /// Sets the maximum number of tokens to generate in the response
1816 ///
1817 /// Controls the maximum length of the generated response. The actual response
1818 /// may be shorter if the model naturally concludes or hits other stopping conditions.
1819 ///
1820 /// # Arguments
1821 ///
1822 /// * `max_tokens` - Maximum number of tokens to generate (minimum: 1)
1823 ///
1824 /// # Returns
1825 ///
1826 /// A mutable reference to self for method chaining
1827 ///
1828 /// # Examples
1829 ///
1830 /// ```rust
1831 /// use openai_tools::responses::request::Responses;
1832 ///
1833 /// let mut client = Responses::new();
1834 /// client.max_output_tokens(100); // Limit response to 100 tokens
1835 /// ```
1836 pub fn max_output_tokens(&mut self, max_tokens: usize) -> &mut Self {
1837 self.request_body.max_output_tokens = Some(max_tokens);
1838 self
1839 }
1840
1841 /// Sets the maximum number of tool calls allowed during response generation
1842 ///
1843 /// Limits how many tools the model can invoke during response generation.
1844 /// This helps control cost and response time when using multiple tools.
1845 ///
1846 /// # Arguments
1847 ///
1848 /// * `max_tokens` - Maximum number of tool calls allowed (0 = no tool calls)
1849 ///
1850 /// # Returns
1851 ///
1852 /// A mutable reference to self for method chaining
1853 ///
1854 /// # Examples
1855 ///
1856 /// ```rust
1857 /// use openai_tools::responses::request::Responses;
1858 ///
1859 /// let mut client = Responses::new();
1860 /// client.max_tool_calls(3); // Allow up to 3 tool calls
1861 /// client.max_tool_calls(0); // Disable tool usage
1862 /// ```
1863 pub fn max_tool_calls(&mut self, max_tokens: usize) -> &mut Self {
1864 self.request_body.max_tool_calls = Some(max_tokens);
1865 self
1866 }
1867
1868 /// Adds or updates a metadata key-value pair for the request
1869 ///
1870 /// Metadata provides arbitrary key-value pairs that can be attached to the request
1871 /// for tracking, logging, or passing additional context that doesn't affect
1872 /// the model's behavior.
1873 ///
1874 /// # Arguments
1875 ///
1876 /// * `key` - The metadata key (string identifier)
1877 /// * `value` - The metadata value (can be string, number, boolean, etc.)
1878 ///
1879 /// # Behavior
1880 ///
1881 /// - If the key already exists, the old value is replaced with the new one
1882 /// - If metadata doesn't exist yet, a new metadata map is created
1883 /// - Values are stored as `serde_json::Value` for flexibility
1884 ///
1885 /// # Returns
1886 ///
1887 /// A mutable reference to self for method chaining
1888 ///
1889 /// # Examples
1890 ///
1891 /// ```rust
1892 /// use openai_tools::responses::request::Responses;
1893 /// use serde_json::Value;
1894 ///
1895 /// let mut client = Responses::new();
1896 /// client.metadata("user_id".to_string(), Value::String("user123".to_string()));
1897 /// client.metadata("priority".to_string(), Value::Number(serde_json::Number::from(1)));
1898 /// client.metadata("debug".to_string(), Value::Bool(true));
1899 /// ```
1900 pub fn metadata(&mut self, key: String, value: serde_json::Value) -> &mut Self {
1901 if self.request_body.metadata.is_none() {
1902 self.request_body.metadata = Some(HashMap::new());
1903 }
1904 if self.request_body.metadata.as_ref().unwrap().keys().any(|k| k == &key) {
1905 self.request_body.metadata.as_mut().unwrap().remove(&key);
1906 }
1907 self.request_body.metadata.as_mut().unwrap().insert(key, value);
1908 self
1909 }
1910
1911 /// Enables or disables parallel tool calls
1912 ///
1913 /// When enabled, the model can make multiple tool calls simultaneously
1914 /// rather than sequentially. This can significantly improve response time
1915 /// when multiple independent tools need to be used.
1916 ///
1917 /// # Arguments
1918 ///
1919 /// * `enable` - Whether to enable parallel tool calls
1920 /// - `true`: Tools can be called in parallel (faster for independent tools)
1921 /// - `false`: Tools are called sequentially (better for dependent operations)
1922 ///
1923 /// # Returns
1924 ///
1925 /// A mutable reference to self for method chaining
1926 ///
1927 /// # When to Use
1928 ///
1929 /// - **Enable (true)**: When tools are independent (e.g., weather + stock prices)
1930 /// - **Disable (false)**: When tools have dependencies (e.g., read file → analyze content)
1931 ///
1932 /// # Examples
1933 ///
1934 /// ```rust
1935 /// use openai_tools::responses::request::Responses;
1936 ///
1937 /// let mut client = Responses::new();
1938 /// client.parallel_tool_calls(true); // Enable parallel execution
1939 /// client.parallel_tool_calls(false); // Force sequential execution
1940 /// ```
1941 pub fn parallel_tool_calls(&mut self, enable: bool) -> &mut Self {
1942 self.request_body.parallel_tool_calls = Some(enable);
1943 self
1944 }
1945
1946 /// Specifies additional data to include in the response output
1947 ///
1948 /// Defines various types of additional information that can be included
1949 /// in the API response output, such as web search results, code interpreter
1950 /// outputs, image URLs, log probabilities, and reasoning traces.
1951 ///
1952 /// # Arguments
1953 ///
1954 /// * `includes` - A vector of `Include` enum values specifying what to include
1955 ///
1956 /// # Available Inclusions
1957 ///
1958 /// - `Include::WebSearchCall` - Web search results and sources
1959 /// - `Include::CodeInterpreterCall` - Code execution outputs
1960 /// - `Include::FileSearchCall` - File search operation results
1961 /// - `Include::LogprobsInOutput` - Token log probabilities
1962 /// - `Include::ReasoningEncryptedContent` - Reasoning process traces
1963 /// - `Include::ImageUrlInInputMessages` - Image URLs from input
1964 /// - `Include::ImageUrlInComputerCallOutput` - Computer interaction screenshots
1965 ///
1966 /// # Returns
1967 ///
1968 /// A mutable reference to self for method chaining
1969 ///
1970 /// # Examples
1971 ///
1972 /// ```rust
1973 /// use openai_tools::responses::request::{Responses, Include};
1974 ///
1975 /// let mut client = Responses::new();
1976 /// client.include(vec![
1977 /// Include::WebSearchCall,
1978 /// Include::LogprobsInOutput,
1979 /// Include::ReasoningEncryptedContent,
1980 /// ]);
1981 /// ```
1982 pub fn include(&mut self, includes: Vec<Include>) -> &mut Self {
1983 self.request_body.include = Some(includes);
1984 self
1985 }
1986
1987 /// Enables or disables background processing for the request
1988 ///
1989 /// When enabled, allows the request to be processed in the background,
1990 /// potentially improving throughput for non-urgent requests at the cost
1991 /// of potentially higher latency.
1992 ///
1993 /// # Arguments
1994 ///
1995 /// * `enable` - Whether to enable background processing
1996 /// - `true`: Process in background (lower priority, potentially longer latency)
1997 /// - `false`: Process with standard priority (default behavior)
1998 ///
1999 /// # Trade-offs
2000 ///
2001 /// - **Background processing**: Better for batch operations, non-interactive requests
2002 /// - **Standard processing**: Better for real-time, interactive applications
2003 ///
2004 /// # Returns
2005 ///
2006 /// A mutable reference to self for method chaining
2007 ///
2008 /// # Examples
2009 ///
2010 /// ```rust
2011 /// use openai_tools::responses::request::Responses;
2012 ///
2013 /// let mut client = Responses::new();
2014 /// client.background(true); // Enable background processing
2015 /// client.background(false); // Use standard processing
2016 /// ```
2017 pub fn background(&mut self, enable: bool) -> &mut Self {
2018 self.request_body.background = Some(enable);
2019 self
2020 }
2021
2022 /// Sets the conversation ID for grouping related requests
2023 ///
2024 /// Identifier for grouping related requests as part of the same conversation
2025 /// or session. This helps with context management, analytics, and conversation
2026 /// tracking across multiple API calls.
2027 ///
2028 /// # Arguments
2029 ///
2030 /// * `conversation_id` - The conversation identifier
2031 /// - Must start with "conv-" prefix according to API requirements
2032 /// - Should be a unique identifier (UUID recommended)
2033 ///
2034 /// # Returns
2035 ///
2036 /// A mutable reference to self for method chaining
2037 ///
2038 /// # Format Requirements
2039 ///
2040 /// The conversation ID must follow the format: `conv-{identifier}`
2041 ///
2042 /// # Examples
2043 ///
2044 /// ```rust
2045 /// use openai_tools::responses::request::Responses;
2046 ///
2047 /// let mut client = Responses::new();
2048 /// client.conversation("conv-123e4567-e89b-12d3-a456-426614174000");
2049 /// client.conversation("conv-user123-session456");
2050 /// ```
2051 pub fn conversation<T: AsRef<str>>(&mut self, conversation_id: T) -> &mut Self {
2052 self.request_body.conversation = Some(conversation_id.as_ref().to_string());
2053 self
2054 }
2055
2056 /// Sets the ID of the previous response for context continuation
2057 ///
2058 /// References a previous response in the same conversation to maintain
2059 /// context and enable features like response chaining, follow-up handling,
2060 /// or response refinement.
2061 ///
2062 /// # Arguments
2063 ///
2064 /// * `response_id` - The ID of the previous response to reference
2065 ///
2066 /// # Use Cases
2067 ///
2068 /// - **Multi-turn conversations**: Maintaining context across multiple exchanges
2069 /// - **Follow-up questions**: Building on previous responses
2070 /// - **Response refinement**: Iterating on or clarifying previous answers
2071 /// - **Context chaining**: Creating connected sequences of responses
2072 ///
2073 /// # Returns
2074 ///
2075 /// A mutable reference to self for method chaining
2076 ///
2077 /// # Examples
2078 ///
2079 /// ```rust
2080 /// use openai_tools::responses::request::Responses;
2081 ///
2082 /// let mut client = Responses::new();
2083 /// client.previous_response_id("resp_abc123def456");
2084 /// client.previous_response_id("response-uuid-here");
2085 /// ```
2086 pub fn previous_response_id<T: AsRef<str>>(&mut self, response_id: T) -> &mut Self {
2087 self.request_body.previous_response_id = Some(response_id.as_ref().to_string());
2088 self
2089 }
2090
2091 /// Configures reasoning behavior for complex problem-solving
2092 ///
2093 /// Controls how the model approaches complex reasoning tasks, including
2094 /// the computational effort level and format of reasoning explanations.
2095 /// This is particularly useful for mathematical, logical, or analytical tasks.
2096 ///
2097 /// # Arguments
2098 ///
2099 /// * `effort` - The level of reasoning effort to apply:
2100 /// - `ReasoningEffort::Minimal` - Fastest, for simple queries
2101 /// - `ReasoningEffort::Low` - Balanced, for moderate complexity
2102 /// - `ReasoningEffort::Medium` - Thorough, for complex queries
2103 /// - `ReasoningEffort::High` - Maximum analysis, for very complex problems
2104 ///
2105 /// * `summary` - The format for reasoning explanations:
2106 /// - `ReasoningSummary::Auto` - Let the model choose the format
2107 /// - `ReasoningSummary::Concise` - Brief, focused explanations
2108 /// - `ReasoningSummary::Detailed` - Comprehensive, step-by-step explanations
2109 ///
2110 /// # Returns
2111 ///
2112 /// A mutable reference to self for method chaining
2113 ///
2114 /// # Use Cases
2115 ///
2116 /// - Mathematical problem-solving with step-by-step explanations
2117 /// - Complex logical reasoning tasks
2118 /// - Analysis requiring deep consideration
2119 /// - Tasks where understanding the reasoning process is important
2120 ///
2121 /// # Examples
2122 ///
2123 /// ```rust
2124 /// use openai_tools::responses::request::{Responses, ReasoningEffort, ReasoningSummary};
2125 ///
2126 /// let mut client = Responses::new();
2127 ///
2128 /// // High effort with detailed explanations for complex problems
2129 /// client.reasoning(ReasoningEffort::High, ReasoningSummary::Detailed);
2130 ///
2131 /// // Medium effort with concise explanations for balanced approach
2132 /// client.reasoning(ReasoningEffort::Medium, ReasoningSummary::Concise);
2133 /// ```
2134 pub fn reasoning(&mut self, effort: ReasoningEffort, summary: ReasoningSummary) -> &mut Self {
2135 self.request_body.reasoning = Some(Reasoning { effort: Some(effort), summary: Some(summary) });
2136 self
2137 }
2138
2139 /// Sets the text output verbosity level
2140 ///
2141 /// Controls how detailed and lengthy the model's text responses should be.
2142 /// This is particularly useful for controlling response length and detail level
2143 /// based on your use case requirements.
2144 ///
2145 /// # Arguments
2146 ///
2147 /// * `verbosity` - The verbosity level for text output:
2148 /// - `TextVerbosity::Low` - Concise, brief responses
2149 /// - `TextVerbosity::Medium` - Balanced responses (default)
2150 /// - `TextVerbosity::High` - Comprehensive, detailed responses
2151 ///
2152 /// # Model Support
2153 ///
2154 /// This parameter is available on GPT-5.2 and newer models.
2155 ///
2156 /// # Use Cases
2157 ///
2158 /// - **Low verbosity**: Quick answers, summaries, yes/no questions
2159 /// - **Medium verbosity**: Standard explanations, general queries
2160 /// - **High verbosity**: Detailed tutorials, comprehensive analysis
2161 ///
2162 /// # Examples
2163 ///
2164 /// ```rust
2165 /// use openai_tools::responses::request::{Responses, TextVerbosity};
2166 ///
2167 /// let mut client = Responses::new();
2168 ///
2169 /// // Concise responses for simple queries
2170 /// client.text_verbosity(TextVerbosity::Low);
2171 ///
2172 /// // Detailed responses for complex explanations
2173 /// client.text_verbosity(TextVerbosity::High);
2174 /// ```
2175 pub fn text_verbosity(&mut self, verbosity: TextVerbosity) -> &mut Self {
2176 self.request_body.text = Some(TextConfig { verbosity: Some(verbosity) });
2177 self
2178 }
2179
2180 /// Sets the safety identifier for content filtering configuration
2181 ///
2182 /// Specifies which safety and content filtering policies should be applied
2183 /// to the request. Different safety levels provide varying degrees of content
2184 /// restriction and filtering.
2185 ///
2186 /// # Arguments
2187 ///
2188 /// * `safety_id` - The safety configuration identifier
2189 ///
2190 /// # Common Safety Levels
2191 ///
2192 /// - `"strict"` - Apply strict content filtering (highest safety)
2193 /// - `"moderate"` - Apply moderate content filtering (balanced approach)
2194 /// - `"permissive"` - Apply permissive content filtering (minimal restrictions)
2195 /// - `"default"` - Use system default safety settings
2196 ///
2197 /// # Returns
2198 ///
2199 /// A mutable reference to self for method chaining
2200 ///
2201 /// # Use Cases
2202 ///
2203 /// - Educational content requiring strict filtering
2204 /// - Business applications with moderate restrictions
2205 /// - Research applications needing broader content access
2206 ///
2207 /// # Examples
2208 ///
2209 /// ```rust
2210 /// use openai_tools::responses::request::Responses;
2211 ///
2212 /// let mut client = Responses::new();
2213 /// client.safety_identifier("strict"); // High safety for education
2214 /// client.safety_identifier("moderate"); // Balanced for general use
2215 /// client.safety_identifier("permissive"); // Minimal restrictions
2216 /// ```
2217 pub fn safety_identifier<T: AsRef<str>>(&mut self, safety_id: T) -> &mut Self {
2218 self.request_body.safety_identifier = Some(safety_id.as_ref().to_string());
2219 self
2220 }
2221
2222 /// Sets the service tier for request processing priority and features
2223 ///
2224 /// Specifies the service tier for the request, which affects processing
2225 /// priority, rate limits, pricing, and available features. Different tiers
2226 /// provide different levels of service quality and capabilities.
2227 ///
2228 /// # Arguments
2229 ///
2230 /// * `tier` - The service tier identifier
2231 ///
2232 /// # Common Service Tiers
2233 ///
2234 /// - `"default"` - Standard service tier with regular priority
2235 /// - `"scale"` - High-throughput tier optimized for bulk processing
2236 /// - `"premium"` - Premium service tier with enhanced features and priority
2237 /// - `"enterprise"` - Enterprise tier with dedicated resources
2238 ///
2239 /// # Returns
2240 ///
2241 /// A mutable reference to self for method chaining
2242 ///
2243 /// # Considerations
2244 ///
2245 /// - Higher tiers may have different pricing structures
2246 /// - Some features may only be available in certain tiers
2247 /// - Rate limits and quotas may vary by tier
2248 ///
2249 /// # Examples
2250 ///
2251 /// ```rust
2252 /// use openai_tools::responses::request::Responses;
2253 ///
2254 /// let mut client = Responses::new();
2255 /// client.service_tier("default"); // Standard service
2256 /// client.service_tier("scale"); // High-throughput processing
2257 /// client.service_tier("premium"); // Premium features and priority
2258 /// ```
2259 pub fn service_tier<T: AsRef<str>>(&mut self, tier: T) -> &mut Self {
2260 self.request_body.service_tier = Some(tier.as_ref().to_string());
2261 self
2262 }
2263
2264 /// Enables or disables conversation storage
2265 ///
2266 /// Controls whether the conversation may be stored for future reference,
2267 /// training, or analytics purposes. This setting affects data retention
2268 /// and privacy policies.
2269 ///
2270 /// # Arguments
2271 ///
2272 /// * `enable` - Whether to allow conversation storage
2273 /// - `true`: Allow storage for training, analytics, etc.
2274 /// - `false`: Explicitly opt-out of storage
2275 ///
2276 /// # Privacy Considerations
2277 ///
2278 /// - **Enabled storage**: Conversation may be retained according to service policies
2279 /// - **Disabled storage**: Request explicit deletion after processing
2280 /// - **Default behavior**: Varies by service configuration
2281 ///
2282 /// # Returns
2283 ///
2284 /// A mutable reference to self for method chaining
2285 ///
2286 /// # Use Cases
2287 ///
2288 /// - **Enable**: Contributing to model improvement, analytics
2289 /// - **Disable**: Sensitive data, privacy-critical applications
2290 ///
2291 /// # Examples
2292 ///
2293 /// ```rust
2294 /// use openai_tools::responses::request::Responses;
2295 ///
2296 /// let mut client = Responses::new();
2297 /// client.store(false); // Opt-out of storage for privacy
2298 /// client.store(true); // Allow storage for improvement
2299 /// ```
2300 pub fn store(&mut self, enable: bool) -> &mut Self {
2301 self.request_body.store = Some(enable);
2302 self
2303 }
2304
2305 /// Enables or disables streaming responses
2306 ///
2307 /// When enabled, the response will be streamed back in chunks as it's
2308 /// generated, allowing for real-time display of partial results instead
2309 /// of waiting for the complete response.
2310 ///
2311 /// # Arguments
2312 ///
2313 /// * `enable` - Whether to enable streaming
2314 /// - `true`: Stream response in real-time chunks
2315 /// - `false`: Wait for complete response before returning
2316 ///
2317 /// # Returns
2318 ///
2319 /// A mutable reference to self for method chaining
2320 ///
2321 /// # Use Cases
2322 ///
2323 /// - **Enable streaming**: Real-time chat interfaces, live text generation
2324 /// - **Disable streaming**: Batch processing, when complete response is needed
2325 ///
2326 /// # Implementation Notes
2327 ///
2328 /// - Streaming responses require different handling in client code
2329 /// - May affect some response features or formatting options
2330 /// - Typically used with `stream_options()` for additional configuration
2331 ///
2332 /// # Examples
2333 ///
2334 /// ```rust
2335 /// use openai_tools::responses::request::Responses;
2336 ///
2337 /// let mut client = Responses::new();
2338 /// client.stream(true); // Enable real-time streaming
2339 /// client.stream(false); // Wait for complete response
2340 /// ```
2341 pub fn stream(&mut self, enable: bool) -> &mut Self {
2342 self.request_body.stream = Some(enable);
2343 self
2344 }
2345
2346 /// Configures streaming response options
2347 ///
2348 /// Additional options for controlling streaming response behavior,
2349 /// such as whether to include obfuscated placeholder content during
2350 /// the streaming process.
2351 ///
2352 /// # Arguments
2353 ///
2354 /// * `include_obfuscation` - Whether to include obfuscated content
2355 /// - `true`: Include placeholder/obfuscated content in streams
2356 /// - `false`: Only include final, non-obfuscated content
2357 ///
2358 /// # Returns
2359 ///
2360 /// A mutable reference to self for method chaining
2361 ///
2362 /// # Relevance
2363 ///
2364 /// This setting is only meaningful when `stream(true)` is also set.
2365 /// It has no effect on non-streaming responses.
2366 ///
2367 /// # Use Cases
2368 ///
2369 /// - **Include obfuscation**: Better user experience with placeholder content
2370 /// - **Exclude obfuscation**: Cleaner streams with only final content
2371 ///
2372 /// # Examples
2373 ///
2374 /// ```rust
2375 /// use openai_tools::responses::request::Responses;
2376 ///
2377 /// let mut client = Responses::new();
2378 /// client.stream(true); // Enable streaming
2379 /// client.stream_options(true); // Include placeholder content
2380 /// client.stream_options(false); // Only final content
2381 /// ```
2382 pub fn stream_options(&mut self, include_obfuscation: bool) -> &mut Self {
2383 self.request_body.stream_options = Some(StreamOptions { include_obfuscation });
2384 self
2385 }
2386
2387 /// Sets the number of top log probabilities to include in the response
2388 ///
2389 /// Specifies how many of the most likely alternative tokens to include
2390 /// with their log probabilities for each generated token. This provides
2391 /// insight into the model's confidence and alternative choices.
2392 ///
2393 /// # Arguments
2394 ///
2395 /// * `n` - Number of top alternatives to include (typically 1-20)
2396 /// - `0`: No log probabilities included
2397 /// - `1-5`: Common range for most use cases
2398 /// - `>5`: Detailed analysis scenarios
2399 ///
2400 /// # Returns
2401 ///
2402 /// A mutable reference to self for method chaining
2403 ///
2404 /// # Use Cases
2405 ///
2406 /// - **Model analysis**: Understanding model decision-making
2407 /// - **Confidence estimation**: Measuring response certainty
2408 /// - **Alternative exploration**: Seeing what else the model considered
2409 /// - **Debugging**: Analyzing unexpected model behavior
2410 ///
2411 /// # Performance Note
2412 ///
2413 /// Higher values increase response size and may affect latency.
2414 ///
2415 /// **Note:** Reasoning models (GPT-6, GPT-5, o-series) do not support top_logprobs.
2416 /// For these models, this parameter will be ignored with a warning.
2417 ///
2418 /// # Examples
2419 ///
2420 /// ```rust
2421 /// use openai_tools::responses::request::Responses;
2422 ///
2423 /// let mut client = Responses::new();
2424 /// client.top_logprobs(1); // Include top alternative for each token
2425 /// client.top_logprobs(5); // Include top 5 alternatives (detailed analysis)
2426 /// client.top_logprobs(0); // No log probabilities
2427 /// ```
2428 pub fn top_logprobs(&mut self, n: usize) -> &mut Self {
2429 let support = self.request_body.model.parameter_support();
2430 if !support.top_logprobs {
2431 tracing::warn!("Model '{}' does not support top_logprobs parameter. Ignoring.", self.request_body.model);
2432 return self;
2433 }
2434 self.request_body.top_logprobs = Some(n);
2435 self
2436 }
2437
2438 /// Sets the nucleus sampling parameter for controlling response diversity
2439 ///
2440 /// Controls the randomness of the model's responses by limiting the
2441 /// cumulative probability of considered tokens. This is an alternative
2442 /// to temperature-based sampling that can provide more stable results.
2443 ///
2444 /// # Arguments
2445 ///
2446 /// * `p` - The nucleus sampling parameter (0.0 to 1.0)
2447 /// - `0.1`: Very focused, deterministic responses
2448 /// - `0.7`: Balanced creativity and focus (good default)
2449 /// - `0.9`: More diverse and creative responses
2450 /// - `1.0`: Consider all possible tokens (no truncation)
2451 ///
2452 /// # Returns
2453 ///
2454 /// A mutable reference to self for method chaining
2455 ///
2456 /// # How It Works
2457 ///
2458 /// The model considers only the tokens whose cumulative probability
2459 /// reaches the specified threshold, filtering out unlikely options.
2460 ///
2461 /// # Interaction with Temperature
2462 ///
2463 /// Can be used together with `temperature()` for fine-tuned control:
2464 /// - Low top_p + Low temperature = Very focused responses
2465 /// - High top_p + High temperature = Very creative responses
2466 ///
2467 /// **Note:** Reasoning models (GPT-6, GPT-5, o-series) only support top_p=1.0.
2468 /// For these models, other values will be ignored with a warning.
2469 ///
2470 /// # Examples
2471 ///
2472 /// ```rust
2473 /// use openai_tools::responses::request::Responses;
2474 ///
2475 /// let mut client = Responses::new();
2476 /// client.top_p(0.1); // Very focused responses
2477 /// client.top_p(0.7); // Balanced (recommended default)
2478 /// client.top_p(0.95); // High diversity
2479 /// ```
2480 pub fn top_p(&mut self, p: f64) -> &mut Self {
2481 let support = self.request_body.model.parameter_support();
2482 match support.top_p {
2483 ParameterRestriction::FixedValue(fixed) => {
2484 if (p - fixed).abs() > f64::EPSILON {
2485 tracing::warn!("Model '{}' only supports top_p={}. Ignoring top_p={}.", self.request_body.model, fixed, p);
2486 return self;
2487 }
2488 }
2489 ParameterRestriction::NotSupported => {
2490 tracing::warn!("Model '{}' does not support top_p parameter. Ignoring.", self.request_body.model);
2491 return self;
2492 }
2493 ParameterRestriction::Any => {}
2494 }
2495 self.request_body.top_p = Some(p);
2496 self
2497 }
2498
2499 /// Sets the truncation behavior for handling long inputs
2500 ///
2501 /// Controls how the system handles inputs that exceed the maximum
2502 /// context length supported by the model. This helps manage cases
2503 /// where input content is too large to process entirely.
2504 ///
2505 /// # Arguments
2506 ///
2507 /// * `truncation` - The truncation mode to use:
2508 /// - `Truncation::Auto`: Automatically truncate long inputs to fit
2509 /// - `Truncation::Disabled`: Return error if input exceeds context length
2510 ///
2511 /// # Returns
2512 ///
2513 /// A mutable reference to self for method chaining
2514 ///
2515 /// # Use Cases
2516 ///
2517 /// - **Auto truncation**: When you want to handle long documents gracefully
2518 /// - **Disabled truncation**: When you need to ensure complete input processing
2519 ///
2520 /// # Considerations
2521 ///
2522 /// - Auto truncation may remove important context from long inputs
2523 /// - Disabled truncation ensures complete processing but may cause errors
2524 /// - Consider breaking long inputs into smaller chunks when possible
2525 ///
2526 /// # Examples
2527 ///
2528 /// ```rust
2529 /// use openai_tools::responses::request::{Responses, Truncation};
2530 ///
2531 /// let mut client = Responses::new();
2532 /// client.truncation(Truncation::Auto); // Handle long inputs gracefully
2533 /// client.truncation(Truncation::Disabled); // Ensure complete processing
2534 /// ```
2535 pub fn truncation(&mut self, truncation: Truncation) -> &mut Self {
2536 self.request_body.truncation = Some(truncation);
2537 self
2538 }
2539
2540 /// Checks if the model is a reasoning model that doesn't support custom temperature
2541 ///
2542 /// Reasoning models (o1, o3, o4 series) only support the default temperature value of 1.0.
2543 /// This method checks if the current model is one of these reasoning models.
2544 ///
2545 /// # Returns
2546 ///
2547 /// `true` if the model is a reasoning model, `false` otherwise
2548 ///
2549 /// # Supported Reasoning Models
2550 ///
2551 /// - `o1`, `o1-pro`, and variants
2552 /// - `o3`, `o3-mini`, and variants
2553 /// - `o4-mini` and variants
2554 fn is_reasoning_model(&self) -> bool {
2555 self.request_body.model.is_reasoning_model()
2556 }
2557
2558 /// Executes the request and returns the response
2559 ///
2560 /// This method sends the configured request to the OpenAI Responses API
2561 /// and returns the parsed response. It performs validation of required
2562 /// fields before sending the request.
2563 ///
2564 /// # Returns
2565 ///
2566 /// A `Result` containing the `Response` on success, or an `OpenAIToolError` on failure
2567 ///
2568 /// # Errors
2569 ///
2570 /// Returns an error if:
2571 /// - The API key is not set or is empty
2572 /// - The model ID is not set or is empty
2573 /// - Neither messages nor plain text input is provided
2574 /// - Both messages and plain text input are provided (mutually exclusive)
2575 /// - The HTTP request fails
2576 /// - The response cannot be parsed
2577 ///
2578 /// # Parameter Validation
2579 ///
2580 /// For reasoning models (GPT-6, GPT-5, o-series), certain parameters have restrictions:
2581 /// - `temperature`: only 1.0 supported
2582 /// - `top_p`: only 1.0 supported
2583 /// - `top_logprobs`: not supported
2584 ///
2585 /// **Validation occurs at two points:**
2586 /// 1. At setter time (when using `with_model()` constructor) - immediate warning
2587 /// 2. At API call time (fallback) - for cases where model is changed after setting params
2588 ///
2589 /// Unsupported parameter values are ignored with a warning and the request proceeds.
2590 ///
2591 /// # Examples
2592 ///
2593 /// ```rust,no_run
2594 /// use openai_tools::responses::request::Responses;
2595 ///
2596 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2597 /// let mut client = Responses::new();
2598 /// let response = client
2599 /// .model_id("gpt-4")
2600 /// .str_message("Hello!")
2601 /// .complete()
2602 /// .await?;
2603 /// # Ok(())
2604 /// # }
2605 /// ```
2606 pub async fn complete(&self) -> Result<Response> {
2607 // Validate that either messages or plain text input is set
2608 if self.request_body.messages_input.is_none() && self.request_body.plain_text_input.is_none() {
2609 return Err(OpenAIToolError::Error("Messages are not set.".into()));
2610 } else if self.request_body.plain_text_input.is_none() && self.request_body.messages_input.is_none() {
2611 return Err(OpenAIToolError::Error("Both plain text input and messages are set. Please use one of them.".into()));
2612 }
2613
2614 // Handle reasoning models that don't support certain parameters
2615 // See: https://platform.openai.com/docs/guides/reasoning
2616 let mut request_body = self.request_body.clone();
2617 if self.is_reasoning_model() {
2618 let model = &self.request_body.model;
2619
2620 // Temperature: only default (1.0) is supported
2621 if let Some(temp) = request_body.temperature {
2622 if (temp - 1.0).abs() > f64::EPSILON {
2623 tracing::warn!(
2624 "Reasoning model '{}' does not support custom temperature. \
2625 Ignoring temperature={} and using default (1.0).",
2626 model,
2627 temp
2628 );
2629 request_body.temperature = None;
2630 }
2631 }
2632
2633 // Top P: only default (1.0) is supported
2634 if let Some(top_p) = request_body.top_p {
2635 if (top_p - 1.0).abs() > f64::EPSILON {
2636 tracing::warn!(
2637 "Reasoning model '{}' does not support custom top_p. \
2638 Ignoring top_p={} and using default (1.0).",
2639 model,
2640 top_p
2641 );
2642 request_body.top_p = None;
2643 }
2644 }
2645
2646 // Top logprobs: not supported
2647 if request_body.top_logprobs.is_some() {
2648 tracing::warn!("Reasoning model '{}' does not support top_logprobs. Ignoring top_logprobs parameter.", model);
2649 request_body.top_logprobs = None;
2650 }
2651 }
2652
2653 let body = serde_json::to_string(&request_body)?;
2654
2655 let client = create_http_client(self.timeout)?;
2656
2657 // Set up headers
2658 let mut headers = request::header::HeaderMap::new();
2659 headers.insert("Content-Type", request::header::HeaderValue::from_static("application/json"));
2660 if !self.user_agent.is_empty() {
2661 headers.insert("User-Agent", request::header::HeaderValue::from_str(&self.user_agent).unwrap());
2662 }
2663
2664 // Apply provider-specific authentication headers
2665 self.auth.apply_headers(&mut headers)?;
2666
2667 // Get the endpoint URL from the auth provider
2668 let endpoint = self.auth.endpoint(RESPONSES_PATH);
2669
2670 if cfg!(test) {
2671 tracing::info!("Endpoint: {}", endpoint);
2672 // Replace API key with a placeholder for security
2673 let body_for_debug = serde_json::to_string_pretty(&request_body).unwrap().replace(self.auth.api_key(), "*************");
2674 // Log the request body for debugging purposes
2675 tracing::info!("Request body: {}", body_for_debug);
2676 }
2677
2678 // Send the request and handle the response
2679 match client.post(&endpoint).headers(headers).body(body).send().await.map_err(OpenAIToolError::RequestError) {
2680 Err(e) => {
2681 tracing::error!("Request error: {}", e);
2682 Err(e)
2683 }
2684 Ok(response) if !response.status().is_success() => {
2685 let status = response.status();
2686 let error_text = response.text().await.unwrap_or_else(|_| "Failed to read error response".to_string());
2687 tracing::error!("API error (status: {}): {}", status, error_text);
2688 Err(OpenAIToolError::Error(format!("API request failed with status {}: {}", status, error_text)))
2689 }
2690 Ok(response) => {
2691 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
2692
2693 if cfg!(test) {
2694 tracing::info!("Response content: {}", content);
2695 }
2696
2697 serde_json::from_str::<Response>(&content).map_err(OpenAIToolError::SerdeJsonError)
2698 }
2699 }
2700 }
2701
2702 // ========================================
2703 // CRUD Endpoint Methods
2704 // ========================================
2705
2706 /// Creates the HTTP client and headers for API requests
2707 ///
2708 /// This is a helper method that sets up the common HTTP client and headers
2709 /// needed for all API requests.
2710 ///
2711 /// # Returns
2712 ///
2713 /// A tuple of the HTTP client and headers
2714 fn create_api_client(&self) -> Result<(request::Client, request::header::HeaderMap)> {
2715 let client = create_http_client(self.timeout)?;
2716 let mut headers = request::header::HeaderMap::new();
2717 headers.insert("Content-Type", request::header::HeaderValue::from_static("application/json"));
2718 if !self.user_agent.is_empty() {
2719 headers.insert(
2720 "User-Agent",
2721 request::header::HeaderValue::from_str(&self.user_agent).map_err(|e| OpenAIToolError::Error(format!("Invalid user agent: {}", e)))?,
2722 );
2723 }
2724 self.auth.apply_headers(&mut headers)?;
2725 Ok((client, headers))
2726 }
2727
2728 /// Handles API error responses
2729 ///
2730 /// This is a helper method that formats API error responses into a
2731 /// standardized error type.
2732 ///
2733 /// # Arguments
2734 ///
2735 /// * `status` - The HTTP status code
2736 /// * `content` - The error response content
2737 ///
2738 /// # Returns
2739 ///
2740 /// An OpenAIToolError containing the error details
2741 fn handle_api_error(status: request::StatusCode, content: &str) -> OpenAIToolError {
2742 tracing::error!("API error (status: {}): {}", status, content);
2743 OpenAIToolError::Error(format!("API request failed with status {}: {}", status, content))
2744 }
2745
2746 /// Retrieves a response by its ID
2747 ///
2748 /// Fetches the details of a specific response, including its output,
2749 /// status, and metadata.
2750 ///
2751 /// # Arguments
2752 ///
2753 /// * `response_id` - The ID of the response to retrieve
2754 ///
2755 /// # Returns
2756 ///
2757 /// A `Result` containing the `Response` on success
2758 ///
2759 /// # API Reference
2760 ///
2761 /// <https://platform.openai.com/docs/api-reference/responses/get>
2762 ///
2763 /// # Examples
2764 ///
2765 /// ```rust,no_run
2766 /// use openai_tools::responses::request::Responses;
2767 ///
2768 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2769 /// let client = Responses::new();
2770 /// let response = client.retrieve("resp_abc123").await?;
2771 /// println!("Status: {:?}", response.status);
2772 /// # Ok(())
2773 /// # }
2774 /// ```
2775 pub async fn retrieve(&self, response_id: &str) -> Result<Response> {
2776 let (client, headers) = self.create_api_client()?;
2777 let endpoint = format!("{}/{}", self.auth.endpoint(RESPONSES_PATH), response_id);
2778
2779 match client.get(&endpoint).headers(headers).send().await.map_err(OpenAIToolError::RequestError) {
2780 Err(e) => {
2781 tracing::error!("Request error: {}", e);
2782 Err(e)
2783 }
2784 Ok(response) if !response.status().is_success() => {
2785 let status = response.status();
2786 let error_text = response.text().await.unwrap_or_else(|_| "Failed to read error response".to_string());
2787 Err(Self::handle_api_error(status, &error_text))
2788 }
2789 Ok(response) => {
2790 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
2791 serde_json::from_str::<Response>(&content).map_err(OpenAIToolError::SerdeJsonError)
2792 }
2793 }
2794 }
2795
2796 /// Deletes a response by its ID
2797 ///
2798 /// Permanently removes a response from the OpenAI platform.
2799 ///
2800 /// # Arguments
2801 ///
2802 /// * `response_id` - The ID of the response to delete
2803 ///
2804 /// # Returns
2805 ///
2806 /// A `Result` containing `DeleteResponseResult` on success
2807 ///
2808 /// # API Reference
2809 ///
2810 /// <https://platform.openai.com/docs/api-reference/responses/delete>
2811 ///
2812 /// # Examples
2813 ///
2814 /// ```rust,no_run
2815 /// use openai_tools::responses::request::Responses;
2816 ///
2817 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2818 /// let client = Responses::new();
2819 /// let result = client.delete("resp_abc123").await?;
2820 /// assert!(result.deleted);
2821 /// # Ok(())
2822 /// # }
2823 /// ```
2824 pub async fn delete(&self, response_id: &str) -> Result<DeleteResponseResult> {
2825 let (client, headers) = self.create_api_client()?;
2826 let endpoint = format!("{}/{}", self.auth.endpoint(RESPONSES_PATH), response_id);
2827
2828 match client.delete(&endpoint).headers(headers).send().await.map_err(OpenAIToolError::RequestError) {
2829 Err(e) => {
2830 tracing::error!("Request error: {}", e);
2831 Err(e)
2832 }
2833 Ok(response) if !response.status().is_success() => {
2834 let status = response.status();
2835 let error_text = response.text().await.unwrap_or_else(|_| "Failed to read error response".to_string());
2836 Err(Self::handle_api_error(status, &error_text))
2837 }
2838 Ok(response) => {
2839 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
2840 serde_json::from_str::<DeleteResponseResult>(&content).map_err(OpenAIToolError::SerdeJsonError)
2841 }
2842 }
2843 }
2844
2845 /// Cancels an in-progress response
2846 ///
2847 /// Cancels a response that is currently being generated. This is useful
2848 /// for background responses that are taking too long.
2849 ///
2850 /// # Arguments
2851 ///
2852 /// * `response_id` - The ID of the response to cancel
2853 ///
2854 /// # Returns
2855 ///
2856 /// A `Result` containing the cancelled `Response` on success
2857 ///
2858 /// # API Reference
2859 ///
2860 /// <https://platform.openai.com/docs/api-reference/responses/cancel>
2861 ///
2862 /// # Examples
2863 ///
2864 /// ```rust,no_run
2865 /// use openai_tools::responses::request::Responses;
2866 ///
2867 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2868 /// let client = Responses::new();
2869 /// let response = client.cancel("resp_abc123").await?;
2870 /// println!("Response cancelled: {:?}", response.status);
2871 /// # Ok(())
2872 /// # }
2873 /// ```
2874 pub async fn cancel(&self, response_id: &str) -> Result<Response> {
2875 let (client, headers) = self.create_api_client()?;
2876 let endpoint = format!("{}/{}/cancel", self.auth.endpoint(RESPONSES_PATH), response_id);
2877
2878 match client.post(&endpoint).headers(headers).send().await.map_err(OpenAIToolError::RequestError) {
2879 Err(e) => {
2880 tracing::error!("Request error: {}", e);
2881 Err(e)
2882 }
2883 Ok(response) if !response.status().is_success() => {
2884 let status = response.status();
2885 let error_text = response.text().await.unwrap_or_else(|_| "Failed to read error response".to_string());
2886 Err(Self::handle_api_error(status, &error_text))
2887 }
2888 Ok(response) => {
2889 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
2890 serde_json::from_str::<Response>(&content).map_err(OpenAIToolError::SerdeJsonError)
2891 }
2892 }
2893 }
2894
2895 /// Lists input items for a response
2896 ///
2897 /// Retrieves a paginated list of input items that were part of the request
2898 /// that generated the response.
2899 ///
2900 /// # Arguments
2901 ///
2902 /// * `response_id` - The ID of the response to get input items for
2903 /// * `limit` - Maximum number of items to return (default: 20)
2904 /// * `after` - Cursor for pagination (return items after this ID)
2905 /// * `before` - Cursor for pagination (return items before this ID)
2906 ///
2907 /// # Returns
2908 ///
2909 /// A `Result` containing `InputItemsListResponse` on success
2910 ///
2911 /// # API Reference
2912 ///
2913 /// <https://platform.openai.com/docs/api-reference/responses/list-input-items>
2914 ///
2915 /// # Examples
2916 ///
2917 /// ```rust,no_run
2918 /// use openai_tools::responses::request::Responses;
2919 ///
2920 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2921 /// let client = Responses::new();
2922 /// let items = client.list_input_items("resp_abc123", Some(10), None, None).await?;
2923 /// for item in items.data {
2924 /// println!("Item: {} (type: {})", item.id, item.item_type);
2925 /// }
2926 /// # Ok(())
2927 /// # }
2928 /// ```
2929 pub async fn list_input_items(
2930 &self,
2931 response_id: &str,
2932 limit: Option<u32>,
2933 after: Option<&str>,
2934 before: Option<&str>,
2935 ) -> Result<InputItemsListResponse> {
2936 let (client, headers) = self.create_api_client()?;
2937 let base_endpoint = format!("{}/{}/input_items", self.auth.endpoint(RESPONSES_PATH), response_id);
2938
2939 // Build query parameters
2940 let mut query_params = Vec::new();
2941 if let Some(limit) = limit {
2942 query_params.push(format!("limit={}", limit));
2943 }
2944 if let Some(after) = after {
2945 query_params.push(format!("after={}", after));
2946 }
2947 if let Some(before) = before {
2948 query_params.push(format!("before={}", before));
2949 }
2950
2951 let endpoint = if query_params.is_empty() { base_endpoint } else { format!("{}?{}", base_endpoint, query_params.join("&")) };
2952
2953 match client.get(&endpoint).headers(headers).send().await.map_err(OpenAIToolError::RequestError) {
2954 Err(e) => {
2955 tracing::error!("Request error: {}", e);
2956 Err(e)
2957 }
2958 Ok(response) if !response.status().is_success() => {
2959 let status = response.status();
2960 let error_text = response.text().await.unwrap_or_else(|_| "Failed to read error response".to_string());
2961 Err(Self::handle_api_error(status, &error_text))
2962 }
2963 Ok(response) => {
2964 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
2965 serde_json::from_str::<InputItemsListResponse>(&content).map_err(OpenAIToolError::SerdeJsonError)
2966 }
2967 }
2968 }
2969
2970 /// Compacts a response to reduce its size
2971 ///
2972 /// Creates a compacted version of a response, which can be useful
2973 /// for long-running conversations to reduce token usage.
2974 ///
2975 /// # Arguments
2976 ///
2977 /// * `previous_response_id` - The ID of the response to compact
2978 /// * `model` - Optional model to use for compaction (defaults to original model)
2979 ///
2980 /// # Returns
2981 ///
2982 /// A `Result` containing `CompactedResponse` on success
2983 ///
2984 /// # API Reference
2985 ///
2986 /// <https://platform.openai.com/docs/api-reference/responses/compact>
2987 ///
2988 /// # Examples
2989 ///
2990 /// ```rust,no_run
2991 /// use openai_tools::responses::request::Responses;
2992 ///
2993 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
2994 /// let client = Responses::new();
2995 /// let compacted = client.compact("resp_abc123", None).await?;
2996 /// println!("Compacted response ID: {}", compacted.id);
2997 /// # Ok(())
2998 /// # }
2999 /// ```
3000 pub async fn compact(&self, previous_response_id: &str, model: Option<&str>) -> Result<CompactedResponse> {
3001 let (client, headers) = self.create_api_client()?;
3002 let endpoint = format!("{}/compact", self.auth.endpoint(RESPONSES_PATH));
3003
3004 // Build request body
3005 let mut body = serde_json::json!({
3006 "previous_response_id": previous_response_id
3007 });
3008 if let Some(model) = model {
3009 body["model"] = serde_json::json!(model);
3010 }
3011
3012 match client.post(&endpoint).headers(headers).body(serde_json::to_string(&body)?).send().await.map_err(OpenAIToolError::RequestError) {
3013 Err(e) => {
3014 tracing::error!("Request error: {}", e);
3015 Err(e)
3016 }
3017 Ok(response) if !response.status().is_success() => {
3018 let status = response.status();
3019 let error_text = response.text().await.unwrap_or_else(|_| "Failed to read error response".to_string());
3020 Err(Self::handle_api_error(status, &error_text))
3021 }
3022 Ok(response) => {
3023 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
3024 serde_json::from_str::<CompactedResponse>(&content).map_err(OpenAIToolError::SerdeJsonError)
3025 }
3026 }
3027 }
3028
3029 /// Counts the number of input tokens for a potential request
3030 ///
3031 /// Useful for estimating token usage before sending a request.
3032 ///
3033 /// # Arguments
3034 ///
3035 /// * `model` - The model to use for token counting
3036 /// * `input` - The input to count tokens for (can be a string or messages array)
3037 ///
3038 /// # Returns
3039 ///
3040 /// A `Result` containing `InputTokensResponse` on success
3041 ///
3042 /// # API Reference
3043 ///
3044 /// <https://platform.openai.com/docs/api-reference/responses/input-tokens>
3045 ///
3046 /// # Examples
3047 ///
3048 /// ```rust,no_run
3049 /// use openai_tools::responses::request::Responses;
3050 /// use serde_json::json;
3051 ///
3052 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
3053 /// let client = Responses::new();
3054 /// let tokens = client.get_input_tokens("gpt-4o-mini", json!("Hello, world!")).await?;
3055 /// println!("Input tokens: {}", tokens.input_tokens);
3056 /// # Ok(())
3057 /// # }
3058 /// ```
3059 pub async fn get_input_tokens(&self, model: &str, input: serde_json::Value) -> Result<InputTokensResponse> {
3060 let (client, headers) = self.create_api_client()?;
3061 let endpoint = format!("{}/input_tokens", self.auth.endpoint(RESPONSES_PATH));
3062
3063 let body = serde_json::json!({
3064 "model": model,
3065 "input": input
3066 });
3067
3068 match client.post(&endpoint).headers(headers).body(serde_json::to_string(&body)?).send().await.map_err(OpenAIToolError::RequestError) {
3069 Err(e) => {
3070 tracing::error!("Request error: {}", e);
3071 Err(e)
3072 }
3073 Ok(response) if !response.status().is_success() => {
3074 let status = response.status();
3075 let error_text = response.text().await.unwrap_or_else(|_| "Failed to read error response".to_string());
3076 Err(Self::handle_api_error(status, &error_text))
3077 }
3078 Ok(response) => {
3079 let content = response.text().await.map_err(OpenAIToolError::RequestError)?;
3080 serde_json::from_str::<InputTokensResponse>(&content).map_err(OpenAIToolError::SerdeJsonError)
3081 }
3082 }
3083 }
3084}
3085
3086#[cfg(test)]
3087mod tests {
3088 use super::ReasoningEffort;
3089
3090 #[test]
3091 fn reasoning_effort_max_serializes_as_max() {
3092 assert_eq!(serde_json::to_string(&ReasoningEffort::Max).unwrap(), "\"max\"");
3093 assert_eq!(ReasoningEffort::Max.to_string(), "max");
3094 assert_eq!("max".parse::<ReasoningEffort>().unwrap(), ReasoningEffort::Max);
3095 }
3096}