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