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