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