diff --git a/docs/openai-models.md b/docs/openai-models.md index d77f0936a..7cf9d5b32 100644 --- a/docs/openai-models.md +++ b/docs/openai-models.md @@ -52,31 +52,31 @@ OpenAI Chat: gpt-4-turbo-2024-04-09 OpenAI Chat: gpt-4-turbo (aliases: gpt-4-turbo-preview, 4-turbo, 4t) OpenAI Chat: gpt-4.5-preview-2025-02-27 OpenAI Chat: gpt-4.5-preview (aliases: gpt-4.5) -OpenAI Chat: o1 -OpenAI Chat: o1-2024-12-17 +OpenAI Responses: o1 +OpenAI Responses: o1-2024-12-17 OpenAI Chat: o1-preview OpenAI Chat: o1-mini -OpenAI Chat: o3-mini -OpenAI Chat: o3 -OpenAI Chat: o4-mini -OpenAI Chat: gpt-5 -OpenAI Chat: gpt-5-mini -OpenAI Chat: gpt-5-nano -OpenAI Chat: gpt-5-2025-08-07 -OpenAI Chat: gpt-5-mini-2025-08-07 -OpenAI Chat: gpt-5-nano-2025-08-07 -OpenAI Chat: gpt-5.1 -OpenAI Chat: gpt-5.1-chat-latest -OpenAI Chat: gpt-5.2 -OpenAI Chat: gpt-5.2-chat-latest -OpenAI Chat: gpt-5.4 -OpenAI Chat: gpt-5.4-2026-03-05 -OpenAI Chat: gpt-5.4-mini -OpenAI Chat: gpt-5.4-mini-2026-03-17 -OpenAI Chat: gpt-5.4-nano -OpenAI Chat: gpt-5.4-nano-2026-03-17 -OpenAI Chat: gpt-5.5 -OpenAI Chat: gpt-5.5-2026-04-23 +OpenAI Responses: o3-mini +OpenAI Responses: o3 +OpenAI Responses: o4-mini +OpenAI Responses: gpt-5 +OpenAI Responses: gpt-5-mini +OpenAI Responses: gpt-5-nano +OpenAI Responses: gpt-5-2025-08-07 +OpenAI Responses: gpt-5-mini-2025-08-07 +OpenAI Responses: gpt-5-nano-2025-08-07 +OpenAI Responses: gpt-5.1 +OpenAI Responses: gpt-5.1-chat-latest +OpenAI Responses: gpt-5.2 +OpenAI Responses: gpt-5.2-chat-latest +OpenAI Responses: gpt-5.4 +OpenAI Responses: gpt-5.4-2026-03-05 +OpenAI Responses: gpt-5.4-mini +OpenAI Responses: gpt-5.4-mini-2026-03-17 +OpenAI Responses: gpt-5.4-nano +OpenAI Responses: gpt-5.4-nano-2026-03-17 +OpenAI Responses: gpt-5.5 +OpenAI Responses: gpt-5.5-2026-04-23 OpenAI Completion: gpt-3.5-turbo-instruct (aliases: 3.5-instruct, chatgpt-instruct) ``` diff --git a/docs/usage.md b/docs/usage.md index e6f78a968..003418c8d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -958,19 +958,49 @@ OpenAI Chat: gpt-4.5-preview (aliases: gpt-4.5) Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: o1 +OpenAI Responses: o1 Options: temperature: float + What sampling temperature to use, between 0 and 2. Higher values like + 0.8 will make the output more random, while lower values like 0.2 will + make it more focused and deterministic. max_tokens: int + Maximum number of tokens to generate. top_p: float + An alternative to sampling with temperature, called nucleus sampling, + where the model considers the results of the tokens with top_p + probability mass. So 0.1 means only the tokens comprising the top 10% + probability mass are considered. Recommended to use top_p or + temperature but not both. frequency_penalty: float + Number between -2.0 and 2.0. Positive values penalize new tokens based + on their existing frequency in the text so far, decreasing the model's + likelihood to repeat the same line verbatim. presence_penalty: float + Number between -2.0 and 2.0. Positive values penalize new tokens based + on whether they appear in the text so far, increasing the model's + likelihood to talk about new topics. stop: str + A string where the API will stop generating further tokens. logit_bias: dict, str + Modify the likelihood of specified tokens appearing in the completion. + Pass a JSON string like '{"1712":-100, "892":-100, "1489":-100}' seed: int + Integer seed to attempt to sample deterministically json_object: boolean + Output a valid JSON object {...}. Prompt must mention JSON. + chat_completions: boolean + Force the use of the older /v1/chat/completions endpoint instead of + /v1/responses. Most callers should leave this off; set to true to fall + back to the Chat Completions code path for compatibility. image_detail: str + Controls the detail level for image attachments. Supported values are + low, high, and auto. reasoning_effort: str + Constraints effort on reasoning for reasoning models. Currently + supported values are low, medium, and high. Reducing reasoning effort + can result in faster responses and fewer tokens used on reasoning in a + response. Attachment types: application/pdf, image/gif, image/jpeg, image/png, image/webp Features: @@ -980,7 +1010,7 @@ OpenAI Chat: o1 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: o1-2024-12-17 +OpenAI Responses: o1-2024-12-17 Options: temperature: float max_tokens: int @@ -991,6 +1021,7 @@ OpenAI Chat: o1-2024-12-17 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str Attachment types: @@ -1038,7 +1069,7 @@ OpenAI Chat: o1-mini Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: o3-mini +OpenAI Responses: o3-mini Options: temperature: float max_tokens: int @@ -1049,6 +1080,7 @@ OpenAI Chat: o3-mini logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str Features: @@ -1059,7 +1091,7 @@ OpenAI Chat: o3-mini Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: o3 +OpenAI Responses: o3 Options: temperature: float max_tokens: int @@ -1070,6 +1102,7 @@ OpenAI Chat: o3 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str Attachment types: @@ -1082,7 +1115,7 @@ OpenAI Chat: o3 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: o4-mini +OpenAI Responses: o4-mini Options: temperature: float max_tokens: int @@ -1093,6 +1126,7 @@ OpenAI Chat: o4-mini logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str Attachment types: @@ -1105,7 +1139,7 @@ OpenAI Chat: o4-mini Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5 +OpenAI Responses: gpt-5 Options: temperature: float max_tokens: int @@ -1116,6 +1150,7 @@ OpenAI Chat: gpt-5 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1129,7 +1164,7 @@ OpenAI Chat: gpt-5 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5-mini +OpenAI Responses: gpt-5-mini Options: temperature: float max_tokens: int @@ -1140,6 +1175,7 @@ OpenAI Chat: gpt-5-mini logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1153,7 +1189,7 @@ OpenAI Chat: gpt-5-mini Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5-nano +OpenAI Responses: gpt-5-nano Options: temperature: float max_tokens: int @@ -1164,6 +1200,7 @@ OpenAI Chat: gpt-5-nano logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1177,7 +1214,7 @@ OpenAI Chat: gpt-5-nano Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5-2025-08-07 +OpenAI Responses: gpt-5-2025-08-07 Options: temperature: float max_tokens: int @@ -1188,6 +1225,7 @@ OpenAI Chat: gpt-5-2025-08-07 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1201,7 +1239,7 @@ OpenAI Chat: gpt-5-2025-08-07 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5-mini-2025-08-07 +OpenAI Responses: gpt-5-mini-2025-08-07 Options: temperature: float max_tokens: int @@ -1212,6 +1250,7 @@ OpenAI Chat: gpt-5-mini-2025-08-07 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1225,7 +1264,7 @@ OpenAI Chat: gpt-5-mini-2025-08-07 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5-nano-2025-08-07 +OpenAI Responses: gpt-5-nano-2025-08-07 Options: temperature: float max_tokens: int @@ -1236,6 +1275,7 @@ OpenAI Chat: gpt-5-nano-2025-08-07 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1249,7 +1289,7 @@ OpenAI Chat: gpt-5-nano-2025-08-07 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.1 +OpenAI Responses: gpt-5.1 Options: temperature: float max_tokens: int @@ -1260,6 +1300,7 @@ OpenAI Chat: gpt-5.1 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1273,7 +1314,7 @@ OpenAI Chat: gpt-5.1 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.1-chat-latest +OpenAI Responses: gpt-5.1-chat-latest Options: temperature: float max_tokens: int @@ -1284,6 +1325,7 @@ OpenAI Chat: gpt-5.1-chat-latest logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1297,7 +1339,7 @@ OpenAI Chat: gpt-5.1-chat-latest Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.2 +OpenAI Responses: gpt-5.2 Options: temperature: float max_tokens: int @@ -1308,6 +1350,7 @@ OpenAI Chat: gpt-5.2 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1321,7 +1364,7 @@ OpenAI Chat: gpt-5.2 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.2-chat-latest +OpenAI Responses: gpt-5.2-chat-latest Options: temperature: float max_tokens: int @@ -1332,6 +1375,7 @@ OpenAI Chat: gpt-5.2-chat-latest logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1345,7 +1389,7 @@ OpenAI Chat: gpt-5.2-chat-latest Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.4 +OpenAI Responses: gpt-5.4 Options: temperature: float max_tokens: int @@ -1356,6 +1400,7 @@ OpenAI Chat: gpt-5.4 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1369,7 +1414,7 @@ OpenAI Chat: gpt-5.4 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.4-2026-03-05 +OpenAI Responses: gpt-5.4-2026-03-05 Options: temperature: float max_tokens: int @@ -1380,6 +1425,7 @@ OpenAI Chat: gpt-5.4-2026-03-05 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1393,7 +1439,7 @@ OpenAI Chat: gpt-5.4-2026-03-05 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.4-mini +OpenAI Responses: gpt-5.4-mini Options: temperature: float max_tokens: int @@ -1404,6 +1450,7 @@ OpenAI Chat: gpt-5.4-mini logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1417,7 +1464,7 @@ OpenAI Chat: gpt-5.4-mini Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.4-mini-2026-03-17 +OpenAI Responses: gpt-5.4-mini-2026-03-17 Options: temperature: float max_tokens: int @@ -1428,6 +1475,7 @@ OpenAI Chat: gpt-5.4-mini-2026-03-17 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1441,7 +1489,7 @@ OpenAI Chat: gpt-5.4-mini-2026-03-17 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.4-nano +OpenAI Responses: gpt-5.4-nano Options: temperature: float max_tokens: int @@ -1452,6 +1500,7 @@ OpenAI Chat: gpt-5.4-nano logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1465,7 +1514,7 @@ OpenAI Chat: gpt-5.4-nano Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.4-nano-2026-03-17 +OpenAI Responses: gpt-5.4-nano-2026-03-17 Options: temperature: float max_tokens: int @@ -1476,6 +1525,7 @@ OpenAI Chat: gpt-5.4-nano-2026-03-17 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1489,7 +1539,7 @@ OpenAI Chat: gpt-5.4-nano-2026-03-17 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.5 +OpenAI Responses: gpt-5.5 Options: temperature: float max_tokens: int @@ -1500,6 +1550,7 @@ OpenAI Chat: gpt-5.5 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str @@ -1513,7 +1564,7 @@ OpenAI Chat: gpt-5.5 Keys: key: openai env_var: OPENAI_API_KEY -OpenAI Chat: gpt-5.5-2026-04-23 +OpenAI Responses: gpt-5.5-2026-04-23 Options: temperature: float max_tokens: int @@ -1524,6 +1575,7 @@ OpenAI Chat: gpt-5.5-2026-04-23 logit_bias: dict, str seed: int json_object: boolean + chat_completions: boolean image_detail: str reasoning_effort: str verbosity: str diff --git a/llm/default_plugins/openai_models.py b/llm/default_plugins/openai_models.py index f229635f1..09367c0de 100644 --- a/llm/default_plugins/openai_models.py +++ b/llm/default_plugins/openai_models.py @@ -124,7 +124,7 @@ def register_models(register): # o1 for model_id in ("o1", "o1-2024-12-17"): register( - Chat( + Responses( model_id, vision=True, can_stream=False, @@ -132,7 +132,7 @@ def register_models(register): supports_schema=True, supports_tools=True, ), - AsyncChat( + AsyncResponses( model_id, vision=True, can_stream=False, @@ -151,26 +151,28 @@ def register_models(register): AsyncChat("o1-mini", allows_system_prompt=False), ) register( - Chat("o3-mini", reasoning=True, supports_schema=True, supports_tools=True), - AsyncChat("o3-mini", reasoning=True, supports_schema=True, supports_tools=True), + Responses("o3-mini", reasoning=True, supports_schema=True, supports_tools=True), + AsyncResponses( + "o3-mini", reasoning=True, supports_schema=True, supports_tools=True + ), ) register( - Chat( + Responses( "o3", vision=True, reasoning=True, supports_schema=True, supports_tools=True ), - AsyncChat( + AsyncResponses( "o3", vision=True, reasoning=True, supports_schema=True, supports_tools=True ), ) register( - Chat( + Responses( "o4-mini", vision=True, reasoning=True, supports_schema=True, supports_tools=True, ), - AsyncChat( + AsyncResponses( "o4-mini", vision=True, reasoning=True, @@ -188,7 +190,7 @@ def register_models(register): "gpt-5-nano-2025-08-07", ): register( - Chat( + Responses( model_id, vision=True, reasoning=True, @@ -196,7 +198,7 @@ def register_models(register): supports_schema=True, supports_tools=True, ), - AsyncChat( + AsyncResponses( model_id, vision=True, reasoning=True, @@ -211,7 +213,7 @@ def register_models(register): "gpt-5.1-chat-latest", ): register( - Chat( + Responses( model_id, vision=True, reasoning=True, @@ -219,7 +221,7 @@ def register_models(register): supports_schema=True, supports_tools=True, ), - AsyncChat( + AsyncResponses( model_id, vision=True, reasoning=True, @@ -231,7 +233,7 @@ def register_models(register): # GPT-5.2 for model_id in ("gpt-5.2", "gpt-5.2-chat-latest"): register( - Chat( + Responses( model_id, vision=True, reasoning=True, @@ -239,7 +241,7 @@ def register_models(register): supports_schema=True, supports_tools=True, ), - AsyncChat( + AsyncResponses( model_id, vision=True, reasoning=True, @@ -260,7 +262,7 @@ def register_models(register): "gpt-5.4-nano-2026-03-17", ): register( - Chat( + Responses( model_id, vision=True, reasoning=True, @@ -269,7 +271,7 @@ def register_models(register): supports_schema=True, supports_tools=True, ), - AsyncChat( + AsyncResponses( model_id, vision=True, reasoning=True, @@ -279,13 +281,14 @@ def register_models(register): supports_tools=True, ), ) - # GPT-5.5 + # GPT-5.5 — routes through the Responses API by default; pass + # ``-o chat_completions 1`` to fall back to /v1/chat/completions. for model_id in ( "gpt-5.5", "gpt-5.5-2026-04-23", ): register( - Chat( + Responses( model_id, vision=True, reasoning=True, @@ -294,7 +297,7 @@ def register_models(register): supports_schema=True, supports_tools=True, ), - AsyncChat( + AsyncResponses( model_id, vision=True, reasoning=True, @@ -341,6 +344,9 @@ def register_models(register): if extra_model.get("completion"): klass = Completion async_klass = None + elif extra_model.get("responses"): + klass = Responses + async_klass = AsyncResponses else: klass = Chat async_klass = AsyncChat @@ -599,7 +605,11 @@ def enum_values_sentence(enum_class): def build_options_class( - *, reasoning=False, verbosity=False, image_detail_original=False + *, + reasoning=False, + verbosity=False, + image_detail_original=False, + chat_completions=False, ): fields = { "json_object": ( @@ -610,6 +620,19 @@ def build_options_class( ), ) } + if chat_completions: + fields["chat_completions"] = ( + Optional[bool], + Field( + description=( + "Force the use of the older /v1/chat/completions endpoint " + "instead of /v1/responses. Most callers should leave this " + "off; set to true to fall back to the Chat Completions code " + "path for compatibility." + ), + default=None, + ), + ) image_detail_enum = ( ImageDetailWithOriginalEnum if image_detail_original else ImageDetailEnum ) @@ -882,6 +905,7 @@ def build_kwargs(self, prompt, stream): kwargs = dict(not_nulls(prompt.options)) json_object = kwargs.pop("json_object", None) kwargs.pop("image_detail", None) + kwargs.pop("chat_completions", None) if "max_tokens" not in kwargs and self.default_max_tokens is not None: kwargs["max_tokens"] = self.default_max_tokens if json_object: @@ -1142,6 +1166,745 @@ async def execute( response._prompt_json = redact_data({"messages": messages}) +def _responses_attachment(attachment, image_detail=None): + """Translate an llm Attachment into a Responses-API content part.""" + url = attachment.url + base64_content = "" + if not url or attachment.resolve_type().startswith("audio/"): + base64_content = attachment.base64_content() + url = f"data:{attachment.resolve_type()};base64,{base64_content}" + if attachment.resolve_type() == "application/pdf": + if not base64_content: + base64_content = attachment.base64_content() + return { + "type": "input_file", + "filename": f"{attachment.id()}.pdf", + "file_data": f"data:application/pdf;base64,{base64_content}", + } + if attachment.resolve_type().startswith("image/"): + item = {"type": "input_image", "image_url": url} + if image_detail: + item["detail"] = image_detail + return item + # Audio is not yet supported on the Responses input shape we use; fall + # back to image_url for unknown types so we don't silently drop content. + return {"type": "input_image", "image_url": url} + + +class _SharedResponses(_Shared): + """Mixin that translates llm.Prompt into Responses API parameters.""" + + def __str__(self) -> str: + return "OpenAI Responses: {}".format(self.model_id) + + def _delegate_chat_kwargs(self): + """Return constructor kwargs that mirror this Responses model so we + can build a sibling Chat / AsyncChat instance for the + ``-o chat_completions 1`` opt-out path.""" + return dict( + model_id=self.model_id, + key=self.key, + model_name=self.model_name, + api_base=self.api_base, + api_type=self.api_type, + api_version=self.api_version, + api_engine=self.api_engine, + headers=self.headers, + can_stream=self.can_stream, + vision=self.vision, + reasoning=self._reasoning, + verbosity=self._verbosity, + image_detail_original=self._image_detail_original, + supports_schema=self.supports_schema, + supports_tools=self.supports_tools, + allows_system_prompt=self.allows_system_prompt, + ) + + def _build_responses_input(self, prompt, image_detail=None): + """Translate prompt.messages into a (input_items, instructions) tuple + for the Responses API. + + The most recent system Message is hoisted into ``instructions``; + earlier system messages are dropped (mirroring the way the Chat + path collapses repeated identical system prompts). + """ + from llm.parts import ( + AttachmentPart, + ReasoningPart, + TextPart, + ToolCallPart, + ToolResultPart, + ) + + items: List[Dict[str, Any]] = [] + instructions: Optional[str] = None + + for msg in prompt.messages: + if msg.role == "system": + text = "".join(p.text for p in msg.parts if isinstance(p, TextPart)) + if text: + instructions = text + continue + + text_bits: List[str] = [] + attachment_items: List[Dict[str, Any]] = [] + tool_call_items: List[Dict[str, Any]] = [] + tool_result_items: List[Dict[str, Any]] = [] + reasoning_items: List[Dict[str, Any]] = [] + + for part in msg.parts: + if isinstance(part, TextPart): + text_bits.append(part.text) + elif isinstance(part, AttachmentPart) and part.attachment: + attachment_items.append( + _responses_attachment( + part.attachment, image_detail=image_detail + ) + ) + elif isinstance(part, ToolCallPart): + tool_call_items.append( + { + "type": "function_call", + "call_id": part.tool_call_id, + "name": part.name, + "arguments": json.dumps(part.arguments), + } + ) + elif isinstance(part, ToolResultPart): + tool_result_items.append( + { + "type": "function_call_output", + "call_id": part.tool_call_id, + "output": part.output, + } + ) + elif isinstance(part, ReasoningPart): + pm = (part.provider_metadata or {}).get("openai") or {} + enc = pm.get("encrypted_content") + rid = pm.get("id") + if enc or rid: + # Round-trip a previous reasoning item so the model + # can pick up where it left off mid-tool-call. + item: Dict[str, Any] = {"type": "reasoning"} + if rid: + item["id"] = rid + if enc: + item["encrypted_content"] = enc + if pm.get("summary"): + item["summary"] = pm["summary"] + else: + item["summary"] = [] + reasoning_items.append(item) + + # Reasoning items must precede the assistant message / function + # call they belonged to. + items.extend(reasoning_items) + + if msg.role == "tool": + items.extend(tool_result_items) + continue + + if msg.role == "user": + if attachment_items: + content: List[Dict[str, Any]] = [] + if text_bits: + content.append( + {"type": "input_text", "text": "".join(text_bits)} + ) + content.extend(attachment_items) + items.append({"role": "user", "content": content}) + elif text_bits: + items.append({"role": "user", "content": "".join(text_bits)}) + elif msg.role == "assistant": + if text_bits: + items.append({"role": "assistant", "content": "".join(text_bits)}) + items.extend(tool_call_items) + + return items, instructions + + def _build_responses_kwargs(self, prompt, stream): + """Build the keyword arguments for client.responses.create().""" + opts = dict(not_nulls(prompt.options)) + # Strip options that are either internal to llm or not accepted by + # the Responses API. + opts.pop("json_object", None) + opts.pop("chat_completions", None) + opts.pop("image_detail", None) + max_tokens = opts.pop("max_tokens", None) + reasoning_effort = opts.pop("reasoning_effort", None) + verbosity = opts.pop("verbosity", None) + temperature = opts.pop("temperature", None) + top_p = opts.pop("top_p", None) + seed = opts.pop("seed", None) + + kwargs: Dict[str, Any] = {} + if max_tokens is None and self.default_max_tokens is not None: + max_tokens = self.default_max_tokens + if max_tokens is not None: + kwargs["max_output_tokens"] = max_tokens + if temperature is not None: + kwargs["temperature"] = temperature + if top_p is not None: + kwargs["top_p"] = top_p + if seed is not None: + kwargs["seed"] = seed + if self._reasoning: + reasoning = {"summary": "auto"} + if reasoning_effort: + reasoning["effort"] = reasoning_effort + kwargs["reasoning"] = reasoning + + text: Dict[str, Any] = {} + if verbosity: + text["verbosity"] = verbosity + if prompt.options.json_object: + text["format"] = {"type": "json_object"} + if prompt.schema: + # ``strict: False`` mirrors the looser behaviour of the + # /v1/chat/completions json_schema response_format - required + # because the Responses API otherwise insists on + # ``additionalProperties: false`` everywhere. + text["format"] = { + "type": "json_schema", + "name": "output", + "schema": prompt.schema, + "strict": False, + } + if text: + kwargs["text"] = text + + if prompt.tools: + kwargs["tools"] = [ + { + "type": "function", + "name": tool.name, + "description": tool.description or None, + "parameters": tool.input_schema, + } + for tool in prompt.tools + ] + + # Pass anything we did not consume through verbatim - this lets + # extras like ``parallel_tool_calls`` flow into the API. + kwargs.update(opts) + return kwargs + + def _set_usage_responses(self, response, usage): + if not usage: + return + input_tokens = usage.get("input_tokens", 0) or 0 + output_tokens = usage.get("output_tokens", 0) or 0 + details = {} + for key in ("input_tokens_details", "output_tokens_details"): + value = usage.get(key) + if value: + details[key] = value + response.set_usage( + input=input_tokens, output=output_tokens, details=details or None + ) + + def _reasoning_text_from_item(self, item): + bits = [] + for attr in ("summary", "content"): + for part in getattr(item, attr, None) or []: + if isinstance(part, dict): + text = part.get("text") + else: + text = getattr(part, "text", None) + if text: + bits.append(text) + return "".join(bits) + + def _reasoning_event(self, item, *, include_text=True): + """Build a redacted-reasoning StreamEvent that carries the opaque + ``id`` and ``encrypted_content`` from a Responses-API reasoning + item. Echoing this metadata back on the next request via + ``_build_responses_input`` lets the model pick up its prior chain + of thought - critical for tool-using reasoning models, since + without it the model loses ~3% on SWE-bench (per OpenAI).""" + rid = getattr(item, "id", None) + enc = getattr(item, "encrypted_content", None) + summary = getattr(item, "summary", None) + text = self._reasoning_text_from_item(item) if include_text else "" + meta: Dict[str, Any] = {} + if rid: + meta["id"] = rid + if enc: + meta["encrypted_content"] = enc + if summary: + # ``summary`` is a list of {type:"summary_text", text:"..."} + # objects when reasoning summaries are enabled. + try: + meta["summary"] = [ + s.model_dump() if hasattr(s, "model_dump") else dict(s) + for s in summary + ] + except Exception: + meta["summary"] = list(summary) + return StreamEvent( + type="reasoning", + chunk=text, + redacted=include_text and not text, + provider_metadata={"openai": meta} if meta else None, + ) + + +class Responses(_SharedResponses, KeyModel): + needs_key = "openai" + key_env_var = "OPENAI_API_KEY" + default_max_tokens = None + + def __init__( + self, + model_id, + key=None, + model_name=None, + api_base=None, + api_type=None, + api_version=None, + api_engine=None, + headers=None, + can_stream=True, + vision=False, + audio=False, + reasoning=False, + verbosity=False, + image_detail_original=False, + supports_schema=False, + supports_tools=False, + allows_system_prompt=True, + ): + super().__init__( + model_id, + key=key, + model_name=model_name, + api_base=api_base, + api_type=api_type, + api_version=api_version, + api_engine=api_engine, + headers=headers, + can_stream=can_stream, + vision=vision, + audio=audio, + reasoning=reasoning, + verbosity=verbosity, + image_detail_original=image_detail_original, + supports_schema=supports_schema, + supports_tools=supports_tools, + allows_system_prompt=allows_system_prompt, + ) + self._reasoning = reasoning + self._verbosity = verbosity + self._image_detail_original = image_detail_original + # Override the Options class so that ``-o chat_completions 1`` is + # always available on Responses-routed models. + self.Options = build_options_class( + reasoning=reasoning, + verbosity=verbosity, + image_detail_original=image_detail_original, + chat_completions=True, + ) + + def execute( + self, + prompt: Prompt, + stream: bool, + response: Response, + conversation: Optional[Conversation] = None, + key: Optional[str] = None, + ) -> Iterator[Union[str, StreamEvent]]: + if getattr(prompt.options, "chat_completions", None): + chat = Chat(**self._delegate_chat_kwargs()) + yield from chat.execute(prompt, stream, response, conversation, key) + return + + if prompt.system and not self.allows_system_prompt: + raise NotImplementedError("Model does not support system prompts") + + image_detail = getattr(prompt.options, "image_detail", None) + if image_detail is not None: + image_detail = image_detail.value + input_items, instructions = self._build_responses_input( + prompt, image_detail=image_detail + ) + kwargs = self._build_responses_kwargs(prompt, stream) + if instructions is not None: + kwargs["instructions"] = instructions + kwargs["store"] = False + if self._reasoning: + kwargs["include"] = ["reasoning.encrypted_content"] + + client = self.get_client(key) + usage = None + had_reasoning = False + if stream: + stream_obj = client.responses.create( + model=self.model_name or self.model_id, + input=input_items, + stream=True, + **kwargs, + ) + tool_call_meta: Dict[str, Dict[str, str]] = {} + final_response_dict: Optional[Dict[str, Any]] = None + reasoning_items_with_streamed_text = set() + for event in stream_obj: + etype = getattr(event, "type", None) + if etype == "response.output_item.added": + item = event.item + if item.type == "function_call": + tool_call_meta[item.id] = { + "id": item.id, + "call_id": item.call_id, + "name": item.name, + } + yield StreamEvent( + type="tool_call_name", + chunk=item.name or "", + tool_call_id=item.call_id, + ) + elif etype == "response.output_text.delta": + yield StreamEvent(type="text", chunk=event.delta or "") + elif etype == "response.function_call_arguments.delta": + item_id = getattr(event, "item_id", None) + meta = tool_call_meta.get(item_id) if item_id else None + call_id = meta["call_id"] if meta else None + yield StreamEvent( + type="tool_call_args", + chunk=event.delta or "", + tool_call_id=call_id, + ) + elif etype in ( + "response.reasoning_summary_text.delta", + "response.reasoning_text.delta", + ): + item_id = getattr(event, "item_id", None) + if item_id: + reasoning_items_with_streamed_text.add(item_id) + yield StreamEvent(type="reasoning", chunk=event.delta or "") + elif etype in ( + "response.reasoning_summary_text.done", + "response.reasoning_text.done", + ): + item_id = getattr(event, "item_id", None) + if item_id not in reasoning_items_with_streamed_text: + text = getattr(event, "text", None) or "" + if text: + if item_id: + reasoning_items_with_streamed_text.add(item_id) + yield StreamEvent(type="reasoning", chunk=text) + elif etype == "response.output_item.done": + item = event.item + if item.type == "reasoning": + had_reasoning = True + item_id = getattr(item, "id", None) + yield self._reasoning_event( + item, + include_text=( + item_id not in reasoning_items_with_streamed_text + ), + ) + elif item.type == "function_call": + try: + args = json.loads(item.arguments) if item.arguments else {} + except json.JSONDecodeError: + args = {"_raw": item.arguments} + response.add_tool_call( + llm.ToolCall( + tool_call_id=item.call_id, + name=item.name, + arguments=args, + ) + ) + elif etype == "response.completed": + final_response_dict = event.response.model_dump() + if final_response_dict.get("usage"): + usage = final_response_dict["usage"] + if final_response_dict is not None: + response.response_json = remove_dict_none_values(final_response_dict) + else: + completion = client.responses.create( + model=self.model_name or self.model_id, + input=input_items, + stream=False, + **kwargs, + ) + dumped = completion.model_dump() + response.response_json = remove_dict_none_values(dumped) + usage = dumped.get("usage") + for item in completion.output: + if item.type == "reasoning": + had_reasoning = True + yield self._reasoning_event(item) + elif item.type == "function_call": + try: + args = json.loads(item.arguments) if item.arguments else {} + except json.JSONDecodeError: + args = {"_raw": item.arguments} + response.add_tool_call( + llm.ToolCall( + tool_call_id=item.call_id, + name=item.name, + arguments=args, + ) + ) + yield StreamEvent( + type="tool_call_name", + chunk=item.name or "", + tool_call_id=item.call_id, + ) + yield StreamEvent( + type="tool_call_args", + chunk=item.arguments or "", + tool_call_id=item.call_id, + ) + elif item.type == "message": + for content in item.content or []: + ctype = getattr(content, "type", None) + if ctype == "output_text" and content.text: + yield StreamEvent(type="text", chunk=content.text) + + self._set_usage_responses(response, usage) + # Fallback: usage said reasoning happened but the API gave us no + # reasoning items to harvest encrypted_content from. Emit the + # opaque "reasoning happened" marker for UI / token accounting. + if ( + not had_reasoning + and usage + and ((usage.get("output_tokens_details") or {}).get("reasoning_tokens")) + ): + yield StreamEvent(type="reasoning", chunk="", redacted=True) + response._prompt_json = redact_data( + {"input": input_items, "instructions": instructions} + ) + + +class AsyncResponses(_SharedResponses, AsyncKeyModel): + needs_key = "openai" + key_env_var = "OPENAI_API_KEY" + default_max_tokens = None + + def __init__( + self, + model_id, + key=None, + model_name=None, + api_base=None, + api_type=None, + api_version=None, + api_engine=None, + headers=None, + can_stream=True, + vision=False, + audio=False, + reasoning=False, + verbosity=False, + image_detail_original=False, + supports_schema=False, + supports_tools=False, + allows_system_prompt=True, + ): + super().__init__( + model_id, + key=key, + model_name=model_name, + api_base=api_base, + api_type=api_type, + api_version=api_version, + api_engine=api_engine, + headers=headers, + can_stream=can_stream, + vision=vision, + audio=audio, + reasoning=reasoning, + verbosity=verbosity, + image_detail_original=image_detail_original, + supports_schema=supports_schema, + supports_tools=supports_tools, + allows_system_prompt=allows_system_prompt, + ) + self._reasoning = reasoning + self._verbosity = verbosity + self._image_detail_original = image_detail_original + self.Options = build_options_class( + reasoning=reasoning, + verbosity=verbosity, + image_detail_original=image_detail_original, + chat_completions=True, + ) + + async def execute( + self, + prompt: Prompt, + stream: bool, + response: AsyncResponse, + conversation: Optional[AsyncConversation] = None, + key: Optional[str] = None, + ) -> AsyncGenerator[Union[str, StreamEvent], None]: + if getattr(prompt.options, "chat_completions", None): + chat = AsyncChat(**self._delegate_chat_kwargs()) + async for event in chat.execute( + prompt, stream, response, conversation, key + ): + yield event + return + + if prompt.system and not self.allows_system_prompt: + raise NotImplementedError("Model does not support system prompts") + + image_detail = getattr(prompt.options, "image_detail", None) + if image_detail is not None: + image_detail = image_detail.value + input_items, instructions = self._build_responses_input( + prompt, image_detail=image_detail + ) + kwargs = self._build_responses_kwargs(prompt, stream) + if instructions is not None: + kwargs["instructions"] = instructions + kwargs["store"] = False + if self._reasoning: + kwargs["include"] = ["reasoning.encrypted_content"] + + client = self.get_client(key, async_=True) + usage = None + had_reasoning = False + if stream: + stream_obj = await client.responses.create( + model=self.model_name or self.model_id, + input=input_items, + stream=True, + **kwargs, + ) + tool_call_meta: Dict[str, Dict[str, str]] = {} + final_response_dict: Optional[Dict[str, Any]] = None + reasoning_items_with_streamed_text = set() + async for event in stream_obj: + etype = getattr(event, "type", None) + if etype == "response.output_item.added": + item = event.item + if item.type == "function_call": + tool_call_meta[item.id] = { + "id": item.id, + "call_id": item.call_id, + "name": item.name, + } + yield StreamEvent( + type="tool_call_name", + chunk=item.name or "", + tool_call_id=item.call_id, + ) + elif etype == "response.output_text.delta": + yield StreamEvent(type="text", chunk=event.delta or "") + elif etype == "response.function_call_arguments.delta": + item_id = getattr(event, "item_id", None) + meta = tool_call_meta.get(item_id) if item_id else None + call_id = meta["call_id"] if meta else None + yield StreamEvent( + type="tool_call_args", + chunk=event.delta or "", + tool_call_id=call_id, + ) + elif etype in ( + "response.reasoning_summary_text.delta", + "response.reasoning_text.delta", + ): + item_id = getattr(event, "item_id", None) + if item_id: + reasoning_items_with_streamed_text.add(item_id) + yield StreamEvent(type="reasoning", chunk=event.delta or "") + elif etype in ( + "response.reasoning_summary_text.done", + "response.reasoning_text.done", + ): + item_id = getattr(event, "item_id", None) + if item_id not in reasoning_items_with_streamed_text: + text = getattr(event, "text", None) or "" + if text: + if item_id: + reasoning_items_with_streamed_text.add(item_id) + yield StreamEvent(type="reasoning", chunk=text) + elif etype == "response.output_item.done": + item = event.item + if item.type == "reasoning": + had_reasoning = True + item_id = getattr(item, "id", None) + yield self._reasoning_event( + item, + include_text=( + item_id not in reasoning_items_with_streamed_text + ), + ) + elif item.type == "function_call": + try: + args = json.loads(item.arguments) if item.arguments else {} + except json.JSONDecodeError: + args = {"_raw": item.arguments} + response.add_tool_call( + llm.ToolCall( + tool_call_id=item.call_id, + name=item.name, + arguments=args, + ) + ) + elif etype == "response.completed": + final_response_dict = event.response.model_dump() + if final_response_dict.get("usage"): + usage = final_response_dict["usage"] + if final_response_dict is not None: + response.response_json = remove_dict_none_values(final_response_dict) + else: + completion = await client.responses.create( + model=self.model_name or self.model_id, + input=input_items, + stream=False, + **kwargs, + ) + dumped = completion.model_dump() + response.response_json = remove_dict_none_values(dumped) + usage = dumped.get("usage") + for item in completion.output: + if item.type == "reasoning": + had_reasoning = True + yield self._reasoning_event(item) + elif item.type == "function_call": + try: + args = json.loads(item.arguments) if item.arguments else {} + except json.JSONDecodeError: + args = {"_raw": item.arguments} + response.add_tool_call( + llm.ToolCall( + tool_call_id=item.call_id, + name=item.name, + arguments=args, + ) + ) + yield StreamEvent( + type="tool_call_name", + chunk=item.name or "", + tool_call_id=item.call_id, + ) + yield StreamEvent( + type="tool_call_args", + chunk=item.arguments or "", + tool_call_id=item.call_id, + ) + elif item.type == "message": + for content in item.content or []: + ctype = getattr(content, "type", None) + if ctype == "output_text" and content.text: + yield StreamEvent(type="text", chunk=content.text) + + self._set_usage_responses(response, usage) + if ( + not had_reasoning + and usage + and ((usage.get("output_tokens_details") or {}).get("reasoning_tokens")) + ): + yield StreamEvent(type="reasoning", chunk="", redacted=True) + response._prompt_json = redact_data( + {"input": input_items, "instructions": instructions} + ) + + class Completion(Chat): class Options(SharedOptions): logprobs: Optional[int] = Field( diff --git a/pyproject.toml b/pyproject.toml index 9ca5d3c22..948302833 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,7 @@ dev = [ "pytest-asyncio", "cogapp", "mypy>=1.10.0", - "black>=25.1.0", + "black>=26.3.1", "pytest-recording", "ruff", "syrupy", diff --git a/tests/cassettes/test_openai_responses/test_responses_basic_non_streaming.yaml b/tests/cassettes/test_openai_responses/test_responses_basic_non_streaming.yaml new file mode 100644 index 000000000..1faaee3df --- /dev/null +++ b/tests/cassettes/test_openai_responses/test_responses_basic_non_streaming.yaml @@ -0,0 +1,109 @@ +interactions: +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Reply + with exactly: pong"}],"model":"gpt-5.5","reasoning":{"effort":"low"},"store":false,"stream":false}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '182' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + H4sIAAAAAAAAA3RU247jIAx971dEPE9HSS9J2l8ZrZADTsuWAAumM9Wo/74KadJmpn1LfPDB9vHh + e5FlTEm2z5jH4HheS9mut0W1LcpNJcu8KHctNEVTQwWiLiCvi2aHq02eY121Tblmbz2Fbf6ioJHG + moBDXHgEQsmhx4qqqvN1VVRlwgIBxdDnCNs5jYRySGpAnA7eRtPX1YIOOISV1soc2D77XmRZljEH + F/R9vsQzauvQs0WWXYeLR8ofV1cJRe9tn2mi1inQevwX0YgLd2hA04Xts/w9T5gyIxmXSKB0eMxU + JpCPgpQ1s3gHX9xGcpE42RP+BslazQXoOV1nJeq+p4Oj5fZ9u1zlq3KZb5ar26T7Ax766x7ThpvY + PvtIoxkGNEnbhcNrZXf1qk7KNtiWuw0IWcntusjLdF9ioYvDxIMhwAHvwCsJEyisITT3oh4Lm9GO + Y8IvmrLTATDGEoyj/fgzA7U9OG+bJ0gi2mfMWXNgE3C9fU1nmTtCSAW0yoDmYMIn+nv93uqEQggq + EBgauHqexMEceNAa9VxK8nHYV+cxoBH4ZKWcx7OyMfDRLTwJNcnpvO0ccQHiiPyEl5eYx37EwzKw + 1eY47IhHCNbMvIJta32airaftx5ZiF0HfmSfvBOgRbpwJXvqVuHMKQH9WQnkpEbvtRD1IBwLZD3O + TEvYuX5fY4oXt/5vCt1qa63v4P7/sBnp3DD1W8ln9I0NKk2TdShV7O6mH3Q4WiUG4SJZNgH3RWFk + HX9Yn3wKuqTRrh4CPhoxOo1JFaDR4xMVkw+mDpSZ+bwo3n7HHx6Pqc8korwn5rNefz4f22fxZ7ST + /q+YyRLoh3rLaYQxzOXukEACQU9/XVz/AwAA//8DAEAnS6QwBgAA + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74ce61c87211fd-ORD + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 03:12:57 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '1036' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=UYuMLYCou_tA0.A8bkTTDs0INBmHlG2j7U9W9gdY8rQ-1778037177-1.0.1.1-5g.mxYNPGVDfBz2paukjljQbRq5PH1x6hIXpFFHvfnbkZxklDSJMF7lUnwNKNv48F1tAMpQxDGkwbCVoTbZJsbFM324N18q7IanrQ5hcaB8; + path=/; expires=Wed, 06-May-26 03:42:57 GMT; domain=.api.openai.com; HttpOnly; + Secure; SameSite=None + - _cfuvid=j9wkRiR3FXF4b0PnFv7DnktylnlM._0WSw8ZUpHPQmQ-1778037177704-0.0.1.1-604800000; + path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_c6713bbdac8d4639a5ea09cb6c5eb5a9 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_openai_responses/test_responses_basic_streaming.yaml b/tests/cassettes/test_openai_responses/test_responses_basic_streaming.yaml new file mode 100644 index 000000000..2f7e820ea --- /dev/null +++ b/tests/cassettes/test_openai_responses/test_responses_basic_streaming.yaml @@ -0,0 +1,136 @@ +interactions: +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Reply + with exactly: pong"}],"model":"gpt-5.5","reasoning":{"effort":"low"},"store":false,"stream":true}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '181' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: 'event: response.created + + data: {"type":"response.created","response":{"id":"resp_00592e63e61b66660169fab1b9f8e481a2b321356198d7ac1b","object":"response","created_at":1778037177,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.5-2026-04-23","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"effort":"low","summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + + event: response.in_progress + + data: {"type":"response.in_progress","response":{"id":"resp_00592e63e61b66660169fab1b9f8e481a2b321356198d7ac1b","object":"response","created_at":1778037177,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.5-2026-04-23","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"effort":"low","summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","item":{"id":"msg_00592e63e61b66660169fab1bada8481a28ef92eb62c168ead","type":"message","status":"in_progress","content":[],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":2} + + + event: response.content_part.added + + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_00592e63e61b66660169fab1bada8481a28ef92eb62c168ead","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":3} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"pong","item_id":"msg_00592e63e61b66660169fab1bada8481a28ef92eb62c168ead","logprobs":[],"obfuscation":"mZhSFjZXJr0X","output_index":0,"sequence_number":4} + + + event: response.output_text.done + + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_00592e63e61b66660169fab1bada8481a28ef92eb62c168ead","logprobs":[],"output_index":0,"sequence_number":5,"text":"pong"} + + + event: response.content_part.done + + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_00592e63e61b66660169fab1bada8481a28ef92eb62c168ead","output_index":0,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"pong"},"sequence_number":6} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","item":{"id":"msg_00592e63e61b66660169fab1bada8481a28ef92eb62c168ead","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"pong"}],"phase":"final_answer","role":"assistant"},"output_index":0,"sequence_number":7} + + + event: response.completed + + data: {"type":"response.completed","response":{"id":"resp_00592e63e61b66660169fab1b9f8e481a2b321356198d7ac1b","object":"response","created_at":1778037177,"status":"completed","background":false,"completed_at":1778037178,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.5-2026-04-23","moderation":null,"output":[{"id":"msg_00592e63e61b66660169fab1bada8481a28ef92eb62c168ead","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"pong"}],"phase":"final_answer","role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"effort":"low","summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":{"input_tokens":11,"input_tokens_details":{"cached_tokens":0},"output_tokens":5,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":16},"user":null,"metadata":{}},"sequence_number":8} + + + ' + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74ce69fcf0cb75-DFW + content-type: + - text/event-stream; charset=utf-8 + date: + - Wed, 06 May 2026 03:12:58 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '512' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=Xlm1i2d4hNcY9FYDrgbksx5Ak.InLICBWuWA7k.o5eU-1778037177.9137826-1.0.1.1-GD76qNB2IKnNrhlsjKWgXNF6BpybgPQmNoOTYCMh4ejIhEDWMMph.l_WZvek4hrh5qeLqHfJ28EH5H2c10KBdqGhET8jLthIUOa15fXX7oxYNG_MZ.CHZ9Fuj4bT9GU.; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 03:42:58 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '39999826' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_445d87d531af499daeb09f7826886b8c + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_openai_responses/test_responses_interleaved_reasoning_between_tool_calls.yaml b/tests/cassettes/test_openai_responses/test_responses_interleaved_reasoning_between_tool_calls.yaml new file mode 100644 index 000000000..ec692703c --- /dev/null +++ b/tests/cassettes/test_openai_responses/test_responses_interleaved_reasoning_between_tool_calls.yaml @@ -0,0 +1,540 @@ +interactions: +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Solve + this puzzle: call db_lookup(''start''), then follow each instruction step by + step. Each lookup tells you the next key to use. Compute each step in your head. + State only the final integer."}],"model":"gpt-5.5","reasoning":{"effort":"high"},"store":false,"stream":false,"tools":[{"type":"function","name":"db_lookup","description":"Look + up a value by key in the puzzle database.","parameters":{"properties":{"key":{"type":"string"}},"required":["key"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '551' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + H4sIAAAAAAAA/31WWbOaShB+z6+wfA4pQNb7prKouCKuNylqgGGR1ZnBLXX++wXMUc9Jcn2woD+6 + p6e7Z77v55dWqx157X9abQRxYdMcK7uM7/q86/igw9OMIPvA8SHf4UVXYmSxehYAgILvcLzvO3L7 + ax0idw7QJe9h8gzDu91FEBDo2aDGGFGUaI6WaL7BMAGkxLWPm6dFAqvv7k4OcOMA5WVW5+WDBMO7 + OUqSKAsq28/qtTIU4ApR7e/BE0zyonqpgLf7wu8hPy0tNihEKK89szJJGoOP4LGEmXu1C5iBhFwr + kP5GN1iUvQezPUhAlOBXzyjDBJUuiapNv9pTcLHzkhQlsUkew99BkueJ7YLkY7g092BS7ykoCMV/ + 4ymWZgWK5ii20358gEC93KvbfaXK8m9TmnuBnq3Ff2+swAK5aazMcIIoQiADlmM5QDfLNUHItYD3 + 1gKcZ3ULHlBVMnQt6jK7eUZg1oxA0K1/vYJCdqaommIXY0+YqLd81telmQHjI6WepFPmzK14GUHO + zE99E2+Zsot2OKWvhnHtdtVdKBHztl8bKx8anjCwdupRR+cVHMpTTzQYrtQQve5LdlfVZlveNtPo + Fo6G2cGfjHv8BbHCMhg6y7i3K9Sxvrvl3IDKku5ofc5sK8giygD8aXoY7XHIUWf+vNouOlNgcAfa + 2Y52ypkYWrLgk/FtDjgjuMWbUSzQg4shceKe369jcYMH8tJQlPlyrGKXpNvLjj+Wo1jerpb6lI9N + 2pobOnaHF2tLbsp84JZeP+oePFM/0YFtDUfJcLUQcl/ZbhaJ5HjFLj8uho567RjhMezE2MiXushk + 9AqvzpTg0UPYmyxSmlu7etRfhcoAGKCjhtol5CfTIC3X6kBZFlzveobrsyHqnJ5FhidNTzEotas5 + W7Cz9JgdbGFigI3rDsFinfK7s7xZ0fZ8ONeUmXHFUHRTzJLkqhfaBSqBFSy7Jr6MwV7dT4YbN9pb + jAJuXXmMkMJ0QF/h4Hyw6Zsjp3BiZSBqk47FWshiBEqn435U2LNtKmv7/WV06gar7cTJ1ipmlmWw + 6fXZzOr7OzQ2z9vD9baPOzyMbydhPLyK+ygIaULZpjXboF48vXQp8XAK+nN7p/GMqzNZzoWWpg7C + 0C3Pu2U6WU4m3djdRLQSUtrWuYqxTA4rfWJYB8yYU3k6lIjLJstz2lW9cO/F9qhQs3XiF4qKD6Zm + DlXB9INQo5ZmxgZad+Wb7g0OjCJJyWZCQ8WIgCFsjYE1XwCC6fTG9BgBJOZopkLNOU/miy4zud5E + QAxH2vNDe4gSvRcyg1x2SrCzTc3vFzpM5+5MhibS8724GJ+Xi4FQnJkJUkq8twvCnYxdv3NUyg5S + 97YwG5y1XWbO1wd7UXIOkX0eIGNuGd2CDVMV74y+cgIb2CvYJFBnjiDgU4c7DoQjpvK8353pIkrJ + YTnur0tJo02GmUaaG51PaZKq/dnZkj0BpoFxyGSLs9l+D6zSXEaeGk39I+Wm4UQXASscdHDDw0Qe + 3qgClRcxXEuSyES7ZKqEmT1BkhDH/TTOjDHfIbpDsfLWNaiDtcqSsY4GaZj1Elro+7RPuaqILUFh + yQG4CgzluRCQkoZZz5875Wgg8nmX5ehBZKzEwOx5VLzLzkJ4CvsTYxUfHElaHXhmps0uPfqkjDON + s4WrhPD5usnnawhLc7IZjEhuQ4TNZVRustv6MOrmnGfIk7HfM8xwSEJho/RKwYRjlhf5hX9b7VTN + 0repPt92OrP1mabSIH1edbhMU4BqYvj3R2N7+/qnu9Z3/37XirTHSPVdK/kCBB4nsjIPKkZlf79r + /TJrOKVhiJck/sKZDQhQUKbVHdzgP7+3Y3j93v7ne+2EyPf22/PLOqh9z7d5HAoinXZvptDd4ZuX + H8FQFo4BHT89MpA2eXmOneR5XBbtew2q/x8NAxUAVZFg8pHXKmq8k3dR6YKKLOAf+LWCTlFeYvtd + Otwze3BbgaqtkiqkG0K72tNfMQRrBrozY5vlwjthPlnrIRyg7+eooaqwumN+bfKlwXX4h5LAwIfk + WuVUx/Yj+EE3YIhOUbUrEr0rER+UCWn/Ejg5gh8kDIFpUbN32diZXwUg8EKeyVWppeD5/jITzXev + o9c+QeTkOGrK2U6hF5XpUwLdGxHmVX41DEqStx8A/l0rfB69Z/M9iF0UFe+lHVcD0CqLFmidQFLC + lnNtVW1pRVmLhLBVlLdbAlseIMABGH77vxl6QPX0pFX7EH7Z+L2/VcGq6n60V8h9EF5NL1uoJFnd + 8Bfw7fH89vUZvlZ9EYLeoxgvwR/vP14c3hf4pXdfEOB5UV0gkMxfc256/+XTyk1+jVyuz8eng0Ty + oipPUO3cqQPQD2PRnBlZuhtQ1aV3Gdj2Igyc5F0/lxgE8DlQUfZBhErS19/tL8r25/OSqA6V93Sk + P4zeZ20r8H8C/hT3cSCfzhz/ITbJCUieKMN3HkNd4o8HsBoaUI9avcDbl7f/ADPaWJDQDAAA + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f7526f32c3c0044-ORD + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 04:13:27 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '2091' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=N7TAdwEKwrRihZ4HPzOd8tAdwT5JnZWp1Mj2.UfCC3g-1778040804.348554-1.0.1.1-NFuNpo0J7tlz8j4YDeoThOIY2CDX.4CkvnAcV4AwMaERfiFLSTPjDY8gcSPYiwIXP3TeplBxIS2vJK_VrxoTlp4TAVqwN8bgJ6CMnQFDfKuS7S0zRVqTDUsqYdgW6ccN; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 04:43:27 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_c48ea45fc57a407bb6b100db8c59af68 + status: + code: 200 + message: OK +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Solve + this puzzle: call db_lookup(''start''), then follow each instruction step by + step. Each lookup tells you the next key to use. Compute each step in your head. + State only the final integer."},{"type":"reasoning","id":"rs_0429c1fcf5cbfa350169fabfe62a9c8197914677ea9a2424a0","encrypted_content":"gAAAAABp-r_nDEFD_pLd6MEzoOCG8OKekq-Ev8vnbPTkSie4RovCRsX1uArYsm0yKKyAAEYh8tRzZVKUfeKd6HTYEqGrwUeI9Nd7K14uFr0VC8_AEFOX5_RmizhJInjfMLB5xr26SgIbSkBYpELGYzo4H-nlAJVwn_Tgni-Ka5vNjJZsh4-w5wUXQ3NaK4j0bXJYDwtKFlQ5lLzPa4KgzkWJk60HxK847Z5ZVk7WsH9SKDDPSLEsctmXxY5quJk9XUSGN5kR0TPKGscIxTXtzDPHcudCiAjdRGv0g_TIJlIUQ6ofDXWQl8bdpYoqQIbEy3Khqh3ksKoSG71n0UsUw-6d0IeBMQm04VcGiCUhDHaKa3EhFxh5MNgmuVEHDSp4ByweVwK7G4GniKd8NvkauFyROQ2Omqnj_6MKaWccIaQVm5Yw9WU0_PIPFDOKyse7cms2tlyGpFxeDgTgSARsxLaZEZMIWciZT1DazA9LrrD13aCD4ePHWCRJbpbkDH7FM3T2TrT16-G0kCip_OXm9FZZxJvAgUXMbnVEs1SugWBC2nTCfYrLRwXjyzZk35ekzv6LIy7Zigh0t-_RTOWrBkNxA-7jvgCP_YF51cG1no4hTFEHhhcuwYSmMSMMAkcWi0Dh-FXby7k9tjUGMKTjs1RN9NI8tc2lSwmAEdhZdk_JpEnVlfpDEsjRFRIE6RfghF-SRn2gFAUfRczeHKplmtWM0eDKiaK6XKHTPQats0mz1B16alRJOEeFbwMPQA1Myz7atKb8Z5I_IrlGBh1Ho9buaY_RFfCpGemPcO9eRrGoZ7QLwSQH6pw1MrDusZ_pt4vKYC3qDu3rEZ_6OHwFYnRPVj_Qu4bt9f5arKPTKAp2hmEsYKCDvaWeBp2lgEOb66sv34qH6qs-ooCAOG7rmtjSLCVu8F0R11NiFciwvmlmECOwT9d6emgKjn9T4_2CBaUmo9rdEiNfq-cmhMG7a26jGazsIl9Iz-prux7hV8871iYlNDhn_Mr86kkCmknKL53tGb-29XcK-jTUnlLGrHmhnBl06Cf0f-cE7sT6D2tjacDeh9P6gtu0enBfPbuJH75oA240HiKU7gRBd-kYnw6hvhCMKUkjb88Uj51OFOxB0vDLnF4_6y8rswyWoPVeeuRMWHJto_ersRSiuWnzVjJAo4dK9MLfBKRhIth6WDBu6ReL2575QfzUYEFTGXmGPX33OVw0-mgm","summary":[]},{"type":"function_call","call_id":"call_I670mAzR6AYszdoqaI96qg0k","name":"db_lookup","arguments":"{\"key\": + \"start\"}"},{"type":"function_call_output","call_id":"call_I670mAzR6AYszdoqaI96qg0k","output":"Begin + with the value 7."}],"model":"gpt-5.5","reasoning":{"effort":"high"},"store":false,"stream":false,"tools":[{"type":"function","name":"db_lookup","description":"Look + up a value by key in the puzzle database.","parameters":{"properties":{"key":{"type":"string"}},"required":["key"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2127' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + H4sIAAAAAAAA/31XWbOiyBJ+719xwudmApR1IuZBERBRERG36QmigGKTtQABO85/v6B91NPL5cGg + 8qvMyq3Mj+9f3t4GgTP4+22AYJGZODnkbMK1Xcq2XDCicILmXGC5kOFYhmUJjuFI3GHhELcIAto4 + 6Qy+9iZSK4R2+WEmTQp4l9sIghI6JugxgmFYnMRZnLlhRQnKquh17DTOItjtuytZwD57KK2S3i8X + RAW8i4MoChKvk33vlp0gAy1Evb4DLzBKs27RAe/3gz9Mfj6awG8oRCjtNZMqim4CF8G8gondmhlM + QFS2HYj/dd8cJB/GTAeWIIiKV80gKUpU2WXQBf0qj0FjplWZVaVZpmf4K1imaWTaIPpsLk4dGPUx + eVmJUX9R2BAf0hhOYsPR4LEBgf64V7X7SZ3k31tq7gl6lrb4c2HZEcneCsv2DwDAtYbAItx7LW5G + yjaD99KCIk36EjygLmWozfo022lSwuTWAt64fyYZhswcKDNWwsY4yyrr2Bwr5517viLBrLlQCpxl + vvBH7iEFNSdb5VjnprVvmCqf+F4ocuV0pNB0E6Md6e0u0wMVu2FYiOSYRYgrnOO6XNbENrbrZVJO + dsplXpyVMAtPkC4op6iTK3+tjGsl0YaqZJssPWVJzMuwpvQYO6XNEV+H5eEYMubRuZ6vh9hil0Ri + iW0be3KUgilAMDoihhCxui1acyI367ErVpHN8HBrhtKRzrchdgAHIVzBSzY+bxqtGOVTsXCJy3w+ + 15cbo6IuM1HFzrFmXHiCZ5KVflawsdaIpOc3Q9m0pflKn1VFEko5JYMRx8xMnJ+oWrh1iNlcrkbG + XmK9aqmHMy4ewmixFI3MFZqLtSEDBYswWPP2PpNbd984iz2ajuR9HmuU7LCJIM9FhWHjkNzFi3XN + ztX1eYwSW7e9Q75V6KqLWzgMZ2WxEvhG3FA1AXcnnWiDRqBVvVOjrth0GjVNg2/TwBoTcy63C0pT + NhhuLBpzoZhrz+XGUGHN01DMZ6tE9KlE2e2dVj4emm1WoUwkciFRJrzO10BjDldjHs2xhDBE71Ti + xka0Cis/jg6Fz5MVQpe8qNc5sV5Mcl+zT83aKg75UrmKVb2l5zQvREMJGSkfzqzdNJd9IqQjm5pM + jwED1PExYSyh1mSz9N1YswJGQGB3WsaeJSSX47m5XOGaYcc6Tx6DY+3JV6kyNOVa5xtFsZoM0inc + CQwn8vDgC5ayHZ7wsz056UYaN67OB4tyteTxVcDtRddQK46MNuV27axnbLarMG0iwmvASm6jHmar + fFWJ+k42d1p3M+R0TQaAgMfGkod+Vo+j86hiHUPnL/NWdxaLmnPW+5XW2ClFXKcgbrWdAhMrEayQ + POWnjRJhw9JMoISP7QOVRwGfT8dknMcLhmwDhOJjSXNnWdRoXeLXGEuf4xGIvbGcqIU62emagmEz + oF4mhru1MX6/UNVV7oZDcDCCJa9c9OUl5TBarDySL+SDY3tNrmpHybUpNbMhG28NB+lh7FvEPliq + dHTGFzqa5bhsFLLPThPuQFl+V8WEdXiqjpgjYCap23pDYSJtU1K3mQBL3OHEFQ/MOd/GqrGRABmc + IOuvsiptS283jaywjVx81RqYJOBeXVmiF1v0NE8Yw5lDRzADcqrIdlllQZZBO64zT+V2RBJE2+iY + ro4hklAbbjcneiyEbArgZrgZSSRRG9lydqEroz1uZSUioqE9dmhbwj1qzAq8x1maKxv2eGxrq5Hg + uKmcqgdVyPklFvNCGK65WrMb8lquPNxMze2W5vOTt1am1+uepYzOda/cS75B0qS+Foz02MwRn+2D + JtdMdzteOSJZJOuRv5FIpgWVDBPRUUfEtQKuOkd2LkxbW3Gj6ewypo6GGihEoKZThQSW5p8mcB1n + +1YVXTaq3MJYSCks82Ahkbsl5TtBKOdnWd0HTqDok2VzYA8MZY3hlqr0UcLndKLj4WaxVebMYsHl + MxqPoYNxkntdNsohWc4CvIW1t4s11asDRKKCTYdVGRa+hVQ4vZLYCSMzvjjXp9wR8iOu/fPPcwQU + VRwD1A/Mf/+7yd6//m4GufafZxAYWjbZzyBAUCTrOC5OugRLctSvM8itktusvU3OFyf+wCVuIEBe + FXez6YZ//zY4w/bb4O9vA+bb4P25qzdo3n29vRohhdOBoIST0x7N1vmQH0nT0kNPjQTEN58cy4zS + 9Fxlg3v83e9/t6mcAdRZgtHnWd/RhTuhyTqu1A1Q+BvO0UGXIK0K84NO3T17zPsMdWGWnUnbh2YX + zx8xBPupfGcLgyHp30nEc5I/yBR03RTdxrcfeP6PIF+K25t/sKsCuLBsO596224AP3GpAqJL0EVV + Bh/szAVVVA5+kL4UwU+0roRx1jOa6iYnfiSghE35dK5zLQbP9Us/3Pa9tt3gApGVFsEtnYOu0YMq + ftLCeyH8tPOvh0FVpoMHUPzKn35uu2fxHVjYKMg+UrvoGuCtyt7A2wVEFXyz2reuLG9B8lb68C2r + rtcIvjmgBBYo4F//r4ceUN89cVc+VLwEfq9vl7Auu5/lHXJvhFfRSwgdTe0L/gK+P97fvz7N90w4 + QNB5JOPF+GP934vCxwE/vgFeEOA4QZ8gEK1ffb7V/stPJ9/8u31C9Pfjp4tUplmXHq+L3OoN4A9h + drszHHsXoK5KH9R44AQFsKKPb4qqAB58NlSQfCLmBEN8/RV4ofvfn/8S3a1ynpr4p977mfATBPs7 + 5HeGH1fyqc2xn4yXaQmiJzpkuUdbV8XnK9i1DeibrT/g/cv7/wBdVsXR5g0AAA== + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f7527072b8a0044-ORD + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 04:13:30 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '2766' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=DRZquxDLKhKw.kobZpdPpYdyAORTxb8d3bk6OV6L_3g-1778040807.5506942-1.0.1.1-C63sdNCp_e3AcsWa9mPoKJLTMlDCSogmc69aY3uzn3tTS0hpqMcaswl4Vlx9jLN79I4aGoL9PKji0tX10HxiuZxf4oEWmMjN_QopXLRhPxlCyZuOq26Bg0WQvU0arTlt; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 04:43:30 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_af2b96dc465344fbaecb33d287edd1c1 + status: + code: 200 + message: OK +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Solve + this puzzle: call db_lookup(''start''), then follow each instruction step by + step. Each lookup tells you the next key to use. Compute each step in your head. + State only the final integer."},{"type":"reasoning","id":"rs_0429c1fcf5cbfa350169fabfe62a9c8197914677ea9a2424a0","encrypted_content":"gAAAAABp-r_nDEFD_pLd6MEzoOCG8OKekq-Ev8vnbPTkSie4RovCRsX1uArYsm0yKKyAAEYh8tRzZVKUfeKd6HTYEqGrwUeI9Nd7K14uFr0VC8_AEFOX5_RmizhJInjfMLB5xr26SgIbSkBYpELGYzo4H-nlAJVwn_Tgni-Ka5vNjJZsh4-w5wUXQ3NaK4j0bXJYDwtKFlQ5lLzPa4KgzkWJk60HxK847Z5ZVk7WsH9SKDDPSLEsctmXxY5quJk9XUSGN5kR0TPKGscIxTXtzDPHcudCiAjdRGv0g_TIJlIUQ6ofDXWQl8bdpYoqQIbEy3Khqh3ksKoSG71n0UsUw-6d0IeBMQm04VcGiCUhDHaKa3EhFxh5MNgmuVEHDSp4ByweVwK7G4GniKd8NvkauFyROQ2Omqnj_6MKaWccIaQVm5Yw9WU0_PIPFDOKyse7cms2tlyGpFxeDgTgSARsxLaZEZMIWciZT1DazA9LrrD13aCD4ePHWCRJbpbkDH7FM3T2TrT16-G0kCip_OXm9FZZxJvAgUXMbnVEs1SugWBC2nTCfYrLRwXjyzZk35ekzv6LIy7Zigh0t-_RTOWrBkNxA-7jvgCP_YF51cG1no4hTFEHhhcuwYSmMSMMAkcWi0Dh-FXby7k9tjUGMKTjs1RN9NI8tc2lSwmAEdhZdk_JpEnVlfpDEsjRFRIE6RfghF-SRn2gFAUfRczeHKplmtWM0eDKiaK6XKHTPQats0mz1B16alRJOEeFbwMPQA1Myz7atKb8Z5I_IrlGBh1Ho9buaY_RFfCpGemPcO9eRrGoZ7QLwSQH6pw1MrDusZ_pt4vKYC3qDu3rEZ_6OHwFYnRPVj_Qu4bt9f5arKPTKAp2hmEsYKCDvaWeBp2lgEOb66sv34qH6qs-ooCAOG7rmtjSLCVu8F0R11NiFciwvmlmECOwT9d6emgKjn9T4_2CBaUmo9rdEiNfq-cmhMG7a26jGazsIl9Iz-prux7hV8871iYlNDhn_Mr86kkCmknKL53tGb-29XcK-jTUnlLGrHmhnBl06Cf0f-cE7sT6D2tjacDeh9P6gtu0enBfPbuJH75oA240HiKU7gRBd-kYnw6hvhCMKUkjb88Uj51OFOxB0vDLnF4_6y8rswyWoPVeeuRMWHJto_ersRSiuWnzVjJAo4dK9MLfBKRhIth6WDBu6ReL2575QfzUYEFTGXmGPX33OVw0-mgm","summary":[]},{"type":"function_call","call_id":"call_I670mAzR6AYszdoqaI96qg0k","name":"db_lookup","arguments":"{\"key\": + \"start\"}"},{"type":"function_call_output","call_id":"call_I670mAzR6AYszdoqaI96qg0k","output":"Begin + with the value 7."},{"type":"reasoning","id":"rs_0429c1fcf5cbfa350169fabfe83488819788888aaafb2ab1fd","encrypted_content":"gAAAAABp-r_qaKH8G-A088KPm_AKkVfkzrE_w9jGidMqLh3fXoaw9IbtAS9DwhU_OCnhgjF9tD3K66xmrV4gVvDX5mfjjsF4A8rr9sdYPtMw1TmcwMntBVKvJskKjpjZe6s5dswnzCzuUzuG6UOKpRpoZpnmCIew5Sm-ZoxY0PjtXYj7_YdzkzXmb8M1nbFyymgIloaDarelYr71F-wysy_BIxPAfFulc7CeT_jGY6qTj-XaXEjNevpAkRxQs3qDFsf1vJJJSMRUu5vHFO-kmQUvC1C7nNSkK-AQxF4ghx2I_cGJNSHusnjGq5Ia397H_0CBOQjTd1HJIu3UWG8guMSjH9m2elLMFUpfExvbR4iK-l-ewCcWpIyfWxdLWrD3IWqmQ5Id8nEIJFK78mj4VmLPw8JOPkArncScgXqTK6uzkzEX2HtsNECxFR5w1eVZS1yixE6OSj4V5z-DDlxxx0ToibA1J9qcs5QKR-0ULx_LK_Pgf9AeK8_Z2FqHNnFh5nKVWdyIYXxTpurpF1qEnKBCSCwaQ7XzUJlJ-n1UFgZt0URFbsbqY3XshC4urrvqswPq1PLBqhQcZxPbsXqMKzFuwT6J6CEl2GrUoCjHbVDqIh1j6lc5BDYi7aOAYn7bEwQI_thfmQbi7EraVZMmgbEnvYkxvzeP78ASC4YiYwgIzGuUQKzwqRKKbxpe6oeVE79FCeXhEbKT2Z0kcBZSUomxfSCiLtNMC0Ni9WFfUOu94lRtTPdPH8pVu-QBFezi8GfxOXHNqNuFSVI_VQtenIoP4ia1eYxbI2hpwAlk3u8dUSCvJySdLLw9dPWNQxco51zDamyQVKenbnEbj4ZqZRKl-2t_neG0AcX5qliCqDA4mqmL74yirrmYt69kIFQ6SGCP-86km3amgAInOsOBVSQK--HaOvBUfTc-CWLOONqfj2aXUiMCKvSMvo9-6Fug4CsIXdcgxqOQYGfc5Opce8mTUdrSjmhb1WiMO6lk0LSrHq0IUsIh8Dn9X5bhXshn8dC5wl7Ya7Bofyg2EBGTo4Sc7i-nf2BfFX7kqTmOURGa4iZe8hNpuoytgVDlbjylf0NyU-GE0gwubFgmb6Dqn7UdJedE_i4DKIctupippecmwpgO9V1nilTlYoNYjrGryjTRZ6AEj8oaeR2R3G41wUpMHv6uUyYTIKl1l2cAd6cG0g5A8ECg9bQfIUcAAcQN3EdfoIoOXOEqCM-mCEjjP9wQcx4ztNg0_o_TT6CqZgPKDzzW85UgVDgtWGhU464SPEUoYxJrCpWixqQ_fTANdF4snP3hRG47yauIenFdO31zuafOJrcqEDycKflDHvA5YUOiK1iOoDK4abQhZBePmpWyOFf8lufsULGoetqiLG4VM5hdijIqkIOWidiKSBMxX8X75bAeT5uS3nCq6nS0jRLTKJ7LL9qH60med-9GfzMxKXnMHi0yewgVmQOgwir4rs8o2utjshbrOeDz4-Z-4pCskwZqdEqY0Q==","summary":[]},{"type":"function_call","call_id":"call_Uj506iEKjBZWrHPq2C3GDtgr","name":"db_lookup","arguments":"{\"key\": + \"7\"}"},{"type":"function_call_output","call_id":"call_Uj506iEKjBZWrHPq2C3GDtgr","output":"unknown + key"}],"model":"gpt-5.5","reasoning":{"effort":"high"},"store":false,"stream":false,"tools":[{"type":"function","name":"db_lookup","description":"Look + up a value by key in the puzzle database.","parameters":{"properties":{"key":{"type":"string"}},"required":["key"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '3967' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + 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 + AAA= + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f752719988393b1-ORD + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 04:13:35 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '4711' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=ADJyoHaKUvEeC3nK2_sSJ7dr9CEJ2NNMqc9USLXclT4-1778040810.495933-1.0.1.1-uG8A.s4fFg.igFwTcj1WtTLUqjC4PfZvnuYaIcoE7CBZKb_G.0rMV93DAq7s2AeJruAKwYTEjGzqEdIZMlC3NnwS6wHlmWG9sXbbAroBBCjQg2JoSGoYK1dMCFoUIdsl; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 04:43:35 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_c6ffb3ca01ef49e7b02c5b09f6dacbd1 + status: + code: 200 + message: OK +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Solve + this puzzle: call db_lookup(''start''), then follow each instruction step by + step. Each lookup tells you the next key to use. Compute each step in your head. + State only the final integer."},{"type":"reasoning","id":"rs_0429c1fcf5cbfa350169fabfe62a9c8197914677ea9a2424a0","encrypted_content":"gAAAAABp-r_nDEFD_pLd6MEzoOCG8OKekq-Ev8vnbPTkSie4RovCRsX1uArYsm0yKKyAAEYh8tRzZVKUfeKd6HTYEqGrwUeI9Nd7K14uFr0VC8_AEFOX5_RmizhJInjfMLB5xr26SgIbSkBYpELGYzo4H-nlAJVwn_Tgni-Ka5vNjJZsh4-w5wUXQ3NaK4j0bXJYDwtKFlQ5lLzPa4KgzkWJk60HxK847Z5ZVk7WsH9SKDDPSLEsctmXxY5quJk9XUSGN5kR0TPKGscIxTXtzDPHcudCiAjdRGv0g_TIJlIUQ6ofDXWQl8bdpYoqQIbEy3Khqh3ksKoSG71n0UsUw-6d0IeBMQm04VcGiCUhDHaKa3EhFxh5MNgmuVEHDSp4ByweVwK7G4GniKd8NvkauFyROQ2Omqnj_6MKaWccIaQVm5Yw9WU0_PIPFDOKyse7cms2tlyGpFxeDgTgSARsxLaZEZMIWciZT1DazA9LrrD13aCD4ePHWCRJbpbkDH7FM3T2TrT16-G0kCip_OXm9FZZxJvAgUXMbnVEs1SugWBC2nTCfYrLRwXjyzZk35ekzv6LIy7Zigh0t-_RTOWrBkNxA-7jvgCP_YF51cG1no4hTFEHhhcuwYSmMSMMAkcWi0Dh-FXby7k9tjUGMKTjs1RN9NI8tc2lSwmAEdhZdk_JpEnVlfpDEsjRFRIE6RfghF-SRn2gFAUfRczeHKplmtWM0eDKiaK6XKHTPQats0mz1B16alRJOEeFbwMPQA1Myz7atKb8Z5I_IrlGBh1Ho9buaY_RFfCpGemPcO9eRrGoZ7QLwSQH6pw1MrDusZ_pt4vKYC3qDu3rEZ_6OHwFYnRPVj_Qu4bt9f5arKPTKAp2hmEsYKCDvaWeBp2lgEOb66sv34qH6qs-ooCAOG7rmtjSLCVu8F0R11NiFciwvmlmECOwT9d6emgKjn9T4_2CBaUmo9rdEiNfq-cmhMG7a26jGazsIl9Iz-prux7hV8871iYlNDhn_Mr86kkCmknKL53tGb-29XcK-jTUnlLGrHmhnBl06Cf0f-cE7sT6D2tjacDeh9P6gtu0enBfPbuJH75oA240HiKU7gRBd-kYnw6hvhCMKUkjb88Uj51OFOxB0vDLnF4_6y8rswyWoPVeeuRMWHJto_ersRSiuWnzVjJAo4dK9MLfBKRhIth6WDBu6ReL2575QfzUYEFTGXmGPX33OVw0-mgm","summary":[]},{"type":"function_call","call_id":"call_I670mAzR6AYszdoqaI96qg0k","name":"db_lookup","arguments":"{\"key\": + \"start\"}"},{"type":"function_call_output","call_id":"call_I670mAzR6AYszdoqaI96qg0k","output":"Begin + with the value 7."},{"type":"reasoning","id":"rs_0429c1fcf5cbfa350169fabfe83488819788888aaafb2ab1fd","encrypted_content":"gAAAAABp-r_qaKH8G-A088KPm_AKkVfkzrE_w9jGidMqLh3fXoaw9IbtAS9DwhU_OCnhgjF9tD3K66xmrV4gVvDX5mfjjsF4A8rr9sdYPtMw1TmcwMntBVKvJskKjpjZe6s5dswnzCzuUzuG6UOKpRpoZpnmCIew5Sm-ZoxY0PjtXYj7_YdzkzXmb8M1nbFyymgIloaDarelYr71F-wysy_BIxPAfFulc7CeT_jGY6qTj-XaXEjNevpAkRxQs3qDFsf1vJJJSMRUu5vHFO-kmQUvC1C7nNSkK-AQxF4ghx2I_cGJNSHusnjGq5Ia397H_0CBOQjTd1HJIu3UWG8guMSjH9m2elLMFUpfExvbR4iK-l-ewCcWpIyfWxdLWrD3IWqmQ5Id8nEIJFK78mj4VmLPw8JOPkArncScgXqTK6uzkzEX2HtsNECxFR5w1eVZS1yixE6OSj4V5z-DDlxxx0ToibA1J9qcs5QKR-0ULx_LK_Pgf9AeK8_Z2FqHNnFh5nKVWdyIYXxTpurpF1qEnKBCSCwaQ7XzUJlJ-n1UFgZt0URFbsbqY3XshC4urrvqswPq1PLBqhQcZxPbsXqMKzFuwT6J6CEl2GrUoCjHbVDqIh1j6lc5BDYi7aOAYn7bEwQI_thfmQbi7EraVZMmgbEnvYkxvzeP78ASC4YiYwgIzGuUQKzwqRKKbxpe6oeVE79FCeXhEbKT2Z0kcBZSUomxfSCiLtNMC0Ni9WFfUOu94lRtTPdPH8pVu-QBFezi8GfxOXHNqNuFSVI_VQtenIoP4ia1eYxbI2hpwAlk3u8dUSCvJySdLLw9dPWNQxco51zDamyQVKenbnEbj4ZqZRKl-2t_neG0AcX5qliCqDA4mqmL74yirrmYt69kIFQ6SGCP-86km3amgAInOsOBVSQK--HaOvBUfTc-CWLOONqfj2aXUiMCKvSMvo9-6Fug4CsIXdcgxqOQYGfc5Opce8mTUdrSjmhb1WiMO6lk0LSrHq0IUsIh8Dn9X5bhXshn8dC5wl7Ya7Bofyg2EBGTo4Sc7i-nf2BfFX7kqTmOURGa4iZe8hNpuoytgVDlbjylf0NyU-GE0gwubFgmb6Dqn7UdJedE_i4DKIctupippecmwpgO9V1nilTlYoNYjrGryjTRZ6AEj8oaeR2R3G41wUpMHv6uUyYTIKl1l2cAd6cG0g5A8ECg9bQfIUcAAcQN3EdfoIoOXOEqCM-mCEjjP9wQcx4ztNg0_o_TT6CqZgPKDzzW85UgVDgtWGhU464SPEUoYxJrCpWixqQ_fTANdF4snP3hRG47yauIenFdO31zuafOJrcqEDycKflDHvA5YUOiK1iOoDK4abQhZBePmpWyOFf8lufsULGoetqiLG4VM5hdijIqkIOWidiKSBMxX8X75bAeT5uS3nCq6nS0jRLTKJ7LL9qH60med-9GfzMxKXnMHi0yewgVmQOgwir4rs8o2utjshbrOeDz4-Z-4pCskwZqdEqY0Q==","summary":[]},{"type":"function_call","call_id":"call_Uj506iEKjBZWrHPq2C3GDtgr","name":"db_lookup","arguments":"{\"key\": + \"7\"}"},{"type":"function_call_output","call_id":"call_Uj506iEKjBZWrHPq2C3GDtgr","output":"unknown + key"},{"type":"reasoning","id":"rs_0429c1fcf5cbfa350169fabfeb569481978703af8553a073b9","encrypted_content":"gAAAAABp-r_vFGcSWQD083z5Jsq67CmK99_ZqqnwC8n6j4kVfWhNH5J9pJw5e7uV1QurddjHY1w7BdbV4HrfTkz4SUHio6aFeHMUrbQObDiRwP8kHX2JSY3m3wXaXABn5_qJi85Lj7CD6X73jzqdQpEeGQ-5zL3JsxO0k5UuZb01t5EuzuGzw_0LR1Tli0UJw43rKgCEqarHTus3l63d18pXuK5RMBR-3Z_xI93rKewxrkTaH1DBgxtlxtD5Z4f-d0-RbfG14Svq-QS3wsfg5Xrn85KJHwf8yJ55M752AxBArv_iqsV1YuKZAwyFxT0HiLrDOwI_G_QyYfD-0U-HsVGeESCS-g4jwDPDXst2aJPTOyhtTu0DbpXyU_Ukbrtlws4OOZurl-W7LvvXlWJNG0WWj6mpCcHrWMzubGkQSH87c-GXESHoBdxxReXGomDs4gqnbB7dYpgNRNveYzLnJRQdDYkdYHTJI7gBEVNFBw5wY0zXRhYtivh_G4NuL2FYcTPr8zFsxqIUXyLWJ2-95N0jvS_ap4R0H_l5YY8twKvT4stYA39T2PKt067Cl906VXoatJWjW9yfLjn1fIe2m3Wr4VXhkWUm7v7A5x5Q7UObihtAn-5NhoU9BK1FcMEY_0UmTclUAM-SFVrL3Dtf9GtoYoLH8PUb4Sdph9y0D6baVSNnBpydV18l9XtgruSH5p0t9cexYA3zzTAmMb9ujFxYAb_qWZ_BFSFnJo77lYCoiKWeNeEMl3YoW4tXqmosdpqlSFpHKrax-YGeMD1nagNqQFInSLegcESqI8l-Gyqws4X3VGxW_jPGJgjh_wZAWQTKMiKctcA-AP5JOpJxaPzZghRMGpdI73nOOTV_jQ9OVkKKWtjFeMuduFhWLtiichsWWSq8XwJDUKTRyY_LYBxzPFFxIhxkqQ27b_ndJPLvyDT9ATvN856mxSJYelK0uJjxqj13O5ZWICp0pdUGiELg00Q15UIBqllDQv71_Ltnk_oSEprXRlbifFswF8TNVFbvHh5ZF_ajeXNbSgMfs_qSt9HuZD2tybsSsYHqmitpar0udcgNKvyiPKPXab3kwiPYIj_7nvclcqCZUUzddMy5dA18jszEcY5N46eUV_NtZGO7hQ1Z5hSWrmPSeTUHFKg82QfmW8_ejb0y9qfPoxQcTf8XzVd7gaNDkjSoMC-5Fvbz__geDpkRbNqSPs9eFE5RK7TqkcB7PN2qNN-m7jnxgTaMf5zucA0Xj2SJjoTcDCNS22d_qikzo-jzOuiJD7SZzfST4ugJlf5bMUGIiOItD4e_jszixhZHWE8GHHdjFnplasuI2TUwjUSGvgWr66ELWmpwQaDya6bU2t_hrHQWhkiatXJYzizyI3lUGzf-6N1_ieunEEquLrpVad4LDLnYGI5obFQUJabrD-eQI-JfKRKGSlRBtgXCXtiTJg744kJv5VJsgcKyk4xjoeyAwWAL1mw0NKt0XOeJLdWGIlo7_WrVpFNtSngYnkAD7yXyWsoPbcpBpk_j32YT5aZ6NTeyT5jH_XDcu-pq6kF9sIdP6Ry0qSG0XEyz0zXpmUbL0vowzyNWPoKXdsWYVLXAc4IF-E_Ar_mzsMJkB_3Qalb2sT2dmW7UkaXO_Ej2PpA9aNW-qcV4q5st81qbQnW_JHvai0WzKEFimpEf70oa88Z7ARexPicizAGu-1xGTCZM8GqdtwurLiGOn8Wn9ojASn7zUEF_xTw9UcjB94jQwc_PKHY6BPBleKKPYTZWxAJwzLt937JwezczGHKfEDdM_DMVDI_vJ6H-mJ5nu8hAJK6UemD-GE4VSeSP_NtGZXXinevdm2k8HN7-KH6-zhwP8yMzw4NBnwCCdxvuxkTdhtp-69lY88c-_m6xyvkrpDkUDtsQDk6OjBYqe6b94tKnQh9I9FoVOP1-p4wNJNDxK2AjCTsEZIzD-g1ccSL2ZDtN9CmsXAfecl4RKzdyJgvHXbfFz1BRFaudrlk79C5khqva-WLrFfxkl6y9UE-12CFPt779pulpoeSXbR6PW0Y_mZXpFvtPH6N4Sryi3iYObDtxzgdhd6CNdrXovQstWULzuBS76EYyFPQ=","summary":[]},{"type":"function_call","call_id":"call_Vik9mkNli8KsnSbHOShapdmG","name":"db_lookup","arguments":"{\"key\": + \"step1\"}"},{"type":"function_call_output","call_id":"call_Vik9mkNli8KsnSbHOShapdmG","output":"unknown + key"}],"model":"gpt-5.5","reasoning":{"effort":"high"},"store":false,"stream":false,"tools":[{"type":"function","name":"db_lookup","description":"Look + up a value by key in the puzzle database.","parameters":{"properties":{"key":{"type":"string"}},"required":["key"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '6407' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + 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 + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f752737fd3c0044-ORD + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 04:13:37 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '2162' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=_EXrqe7IjNWn6uSESYNRV6be_iYv5vfdiSH8V3vdNgs-1778040815.3529754-1.0.1.1-qv4Kk81buc06G3o0AUYL254nHY_tKoM.r4ToKt1atzCBnU7vDOrMSgZt87H9a__SsX0twv_Dqbwnv35sedBqkSVJ5tcTI3rPQI4rOXZ2o7bFUr43uDG7zWB_uR4Rv0xw; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 04:43:37 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_83b594dde5454daa8a186b3a26e8df84 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_openai_responses/test_responses_round_trips_encrypted_reasoning.yaml b/tests/cassettes/test_openai_responses/test_responses_round_trips_encrypted_reasoning.yaml new file mode 100644 index 000000000..023c034f1 --- /dev/null +++ b/tests/cassettes/test_openai_responses/test_responses_round_trips_encrypted_reasoning.yaml @@ -0,0 +1,356 @@ +interactions: +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Pick + a clever country name, look up its population, then check whether it can have + dragons. Be brief."}],"model":"gpt-5.5","reasoning":{"effort":"high"},"store":false,"stream":false,"tools":[{"type":"function","name":"lookup_population","description":"Returns + the current population of the specified fictional country.","parameters":{"properties":{"country":{"type":"string"}},"required":["country"],"type":"object"}},{"type":"function","name":"can_have_dragons","description":"Returns + True if the specified population can have dragons.","parameters":{"properties":{"population":{"type":"integer"}},"required":["population"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '722' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + 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 + v/3TYzEPAAA= + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74d0b45806549d-ORD + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 03:14:34 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '2715' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=JaAjlN.SsTHssMfEA38GX9HitAbY0eJ2RFPOYXHE.dM-1778037271.7367537-1.0.1.1-ITxNEO9g5Dnpe0xysdsQx6Pb4UfPCLUH47.8gv2jcppI9YSzDXqxNV1wVH1S0DOjPhkjvEoameT18PZ3jubWSiQeRsWVcFTBf8rKD.riIQ5F_txQnQ76olVhtIu0ZumI; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 03:44:34 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_83a358e390da43f0af03fb3186d6e121 + status: + code: 200 + message: OK +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Pick + a clever country name, look up its population, then check whether it can have + dragons. Be brief."},{"type":"reasoning","id":"rs_0f4809b27460351d0169fab21897dc8196960e8680fb72a88d","encrypted_content":"gAAAAABp-rIaNe6WknTMnPCTW6JIYUOsrQbNUvTkE_mV0UapEUw4P056qF39VaxpCjTGFpDbnqZh4ucbsTpbEIYxdv---5gRmkzgej83d5Ey3QtkLFY-y6107PuJvuLDDsQAuSLYgTi3JClqsopNkz4t8JCSyCP8gT6Ecr9zkSsEMNHoJyKZ82RHTJL7NyStuqffIiSOFJi-V9WFbQsHxpbR0bEoUeGjJSed8669_PWFmL8TygAhFNux0rvRXEmN0o9UQca_r8iq-oTtcBY0MPb7ymqt1ZuHfY71RNsuP-hb9MO3_I3IrWr9JTN5sRQIL-5wi6YyEJ2MpTzvuaWUVu3aOGkGpez5fkkVdR26ktjxIfRtV0vJzFV8298ZAmVPm2oRBfsrg_bpY4R9Nc6LVKp-CuW_RxTX29B3V-HKLoDKmvinWRe4ZD9XidVg_fQmYevsNr8LQ3HNuXILDpQoX8nccIBVSuD_cMDQSyb4s81PQEpdnldcKS1uaNuNVFYBkBxPKu6s1GN50oFjwpPbg_k01nCPXdwXM0qJRN7evsJTD9LbnU5Mve0hNPOJ_C0kW72q1IZgjnKinlkSdY0JDS0YAJAzO19jHLOXdtzk2tBV5OB6G_g2kTBam76mRNVCMDzCXS-x8cEVe5KmDAlNIQz8lWzBIdFXk00yB2McTVmr272IyJkRNLq2auuFFOlkUXn2UM5s3BpiOsYEerX8uKwHDAiIYI_egxzAcWO6n5fzEFopZbkdOQKZbxQfewBZahHD4kUj48H6emooHyjt495WLW_-uAtf-KJ9MXA5AVulBzV7TQ1F6yXwSEaubFSpzpKatPmA9g3BqnnqTsH4e90B5JI2Lwsr75UDmMJpHwuEKOJ8vzn9ie9aOc9dIhTjKhfHEogs-biBbqKTJnRzCuNhZhqdZu9W9frCKhz04NzvpmZid99Es78j6HOodv8CPnMkyn5_nZ-ADHFvZeia5soFVL-48YwUKtz4kq6K-FIGpw3Rw0DmA3zlaEdG6hROrabkPPygFBjM-OIi90QmjuxltVZAIpszWjUrqjJfon5--riTuOm9qj0PuwDq0dLqTtiEVn7-d4aJr8_K9QrdGpLXujLzPLkprGeYFGaFht561NHRy6U6ln8nfh3vi80Zp7cgX4-2B3DEnHINE2Z5mvrL9kkucwkNwbeJ-qOIPCYlmP7zXlD9gm6_VMxl1epK5NUjyCcm9MGGM2L2J1efcKMwQBfNN6pphu1K2Sgwfg0FpPmqPgX-lBv12qMdRCMKkOTY0ALOIIj5NP9XzZuf5W6DOjoy6LxQ8SBIC3qDwUa8Iu5PK8EkkgNkWueTJPzpTYhjaRquY_B8","summary":[]},{"type":"function_call","call_id":"call_uy7tfNVokIN7NjFF6k7OtLyl","name":"lookup_population","arguments":"{\"country\": + \"Pundora\"}"},{"type":"function_call_output","call_id":"call_uy7tfNVokIN7NjFF6k7OtLyl","output":"123124"}],"model":"gpt-5.5","reasoning":{"effort":"high"},"store":false,"stream":false,"tools":[{"type":"function","name":"lookup_population","description":"Returns + the current population of the specified fictional country.","parameters":{"properties":{"country":{"type":"string"}},"required":["country"],"type":"object"}},{"type":"function","name":"can_have_dragons","description":"Returns + True if the specified population can have dragons.","parameters":{"properties":{"population":{"type":"integer"}},"required":["population"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2423' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + H4sIAAAAAAAA/71VS4/jNgy+z68wfJ4Z2M7Te+t5L9ttgW7bXRiyRTuayJKqR7rBIP+9lBS/MhOg + 2AK9BDE/8SPFj6ReH5IkZTT9kKQajKqydr3PyrrYrbfZapPTLN+WLamLnLTbItvn5bZe53S/o7SE + ksKmLdNHTyHrF2jsQCOFgWhvNBALtCIey3e7fbbaIXnAjCXWGe/TyF5xwHPRqSbNsdPSCZ9XS7iB + aGacM9Gh7RU/0aDIGbT3p3ACLhV+IHCJgQfKm9CbgILW0nsKx3kwtBr+ciCac6VAEG7PCGbPWcCY + GMgqCpYwbuaeTBirXWMZXnpu78n3SjqrnK2sPMJb0ErJq4bwJV0vKXB/p07Zp83z5qnIiu1Ttn4q + Vul4QBMfbu4WI6Hlz1CaWKBR2ra5L2zdwH4fhM3qVVm3BdB8vVuvorCBxJ4VBBonwj1D1hN8T8cA + Et25HoQN+OvXVEnleEj/a/ohL1Z5sb5Mpz1xFXMOf1/+/n1/5Py3n4yQv3yxL1/++Hnz8fOWTR6C + 9BCPi+pATiiRJp2XIhy44O+3UCBFNBICX5YdlYu9pbBtUX54R36ETkw6Uw2dHRMcS6803toiZXOA + 6gjnu5jGyoircGmxPkQ9cUCMFIu+hraVOgzTgXWH611T4/qe6IF+bHRDWrBnzMlztwwWbW1Anxje + yrJhUFriuE2v8yc1LCbMQq98c7lgz68FsPDdTslhaj2ZvmftEc7Fsl9TPoGupWGhnGkPlLl+mtAo + xEFifh4mzsp0BMzbVr7twqkHKJhGMzWU9jPgBYRJ7AGSxmmNhUmmtktkGxCjoPH1oknLAh/hSYM7 + x+rz89v24lIenaommumI76wepdVmVpSoPRYTK7+0h90UwtyYZ1fEjeIbYgZexv+XxymEX1pMAx2L + dRNgtH2bOQ1Brit7hhBKWazEp3nuoT8ebqKHHMPG9zM0V/3HBfsVmRJ2K89MOhzyxA95ch3y53+x + B/6DTjO170rFhIUuPjw/ptUsyP8s17gbrVQVlx0WovYE2WhUYQ2W+2jQqONQjZQyQ2o+vNjOkA6m + HcHE4tnLy+LxLTB7TF+n/Y+Lkk6e2WKd3D6nRf4e8B7vuGTvUVtpCZ8zr8Y95cxyp2IPEUos8fyX + h8s/3J7RqkIJAAA= + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74d0c81c4d2b4b-ORD + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 03:14:36 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '1077' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=Ff7ZRSpcLAJPDQ.asF9RF4bqWtg97VpC0E51yPz81.U-1778037274.8993971-1.0.1.1-lFBHWH1sK41g3Of_MJ292u9sow8m9djH6vBQSla3pNCvWDtdVwVDdW.ko4QJQaLNA6RhV21md8copjjSWmsVnZJzyKsQgS.IPimoDzj3d9Lt6eQuV2eXHZenOh25fKxo; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 03:44:36 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_477366fddd32402daba00618a4c16efd + status: + code: 200 + message: OK +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"Pick + a clever country name, look up its population, then check whether it can have + dragons. Be brief."},{"type":"reasoning","id":"rs_0f4809b27460351d0169fab21897dc8196960e8680fb72a88d","encrypted_content":"gAAAAABp-rIaNe6WknTMnPCTW6JIYUOsrQbNUvTkE_mV0UapEUw4P056qF39VaxpCjTGFpDbnqZh4ucbsTpbEIYxdv---5gRmkzgej83d5Ey3QtkLFY-y6107PuJvuLDDsQAuSLYgTi3JClqsopNkz4t8JCSyCP8gT6Ecr9zkSsEMNHoJyKZ82RHTJL7NyStuqffIiSOFJi-V9WFbQsHxpbR0bEoUeGjJSed8669_PWFmL8TygAhFNux0rvRXEmN0o9UQca_r8iq-oTtcBY0MPb7ymqt1ZuHfY71RNsuP-hb9MO3_I3IrWr9JTN5sRQIL-5wi6YyEJ2MpTzvuaWUVu3aOGkGpez5fkkVdR26ktjxIfRtV0vJzFV8298ZAmVPm2oRBfsrg_bpY4R9Nc6LVKp-CuW_RxTX29B3V-HKLoDKmvinWRe4ZD9XidVg_fQmYevsNr8LQ3HNuXILDpQoX8nccIBVSuD_cMDQSyb4s81PQEpdnldcKS1uaNuNVFYBkBxPKu6s1GN50oFjwpPbg_k01nCPXdwXM0qJRN7evsJTD9LbnU5Mve0hNPOJ_C0kW72q1IZgjnKinlkSdY0JDS0YAJAzO19jHLOXdtzk2tBV5OB6G_g2kTBam76mRNVCMDzCXS-x8cEVe5KmDAlNIQz8lWzBIdFXk00yB2McTVmr272IyJkRNLq2auuFFOlkUXn2UM5s3BpiOsYEerX8uKwHDAiIYI_egxzAcWO6n5fzEFopZbkdOQKZbxQfewBZahHD4kUj48H6emooHyjt495WLW_-uAtf-KJ9MXA5AVulBzV7TQ1F6yXwSEaubFSpzpKatPmA9g3BqnnqTsH4e90B5JI2Lwsr75UDmMJpHwuEKOJ8vzn9ie9aOc9dIhTjKhfHEogs-biBbqKTJnRzCuNhZhqdZu9W9frCKhz04NzvpmZid99Es78j6HOodv8CPnMkyn5_nZ-ADHFvZeia5soFVL-48YwUKtz4kq6K-FIGpw3Rw0DmA3zlaEdG6hROrabkPPygFBjM-OIi90QmjuxltVZAIpszWjUrqjJfon5--riTuOm9qj0PuwDq0dLqTtiEVn7-d4aJr8_K9QrdGpLXujLzPLkprGeYFGaFht561NHRy6U6ln8nfh3vi80Zp7cgX4-2B3DEnHINE2Z5mvrL9kkucwkNwbeJ-qOIPCYlmP7zXlD9gm6_VMxl1epK5NUjyCcm9MGGM2L2J1efcKMwQBfNN6pphu1K2Sgwfg0FpPmqPgX-lBv12qMdRCMKkOTY0ALOIIj5NP9XzZuf5W6DOjoy6LxQ8SBIC3qDwUa8Iu5PK8EkkgNkWueTJPzpTYhjaRquY_B8","summary":[]},{"type":"function_call","call_id":"call_uy7tfNVokIN7NjFF6k7OtLyl","name":"lookup_population","arguments":"{\"country\": + \"Pundora\"}"},{"type":"function_call_output","call_id":"call_uy7tfNVokIN7NjFF6k7OtLyl","output":"123124"},{"type":"function_call","call_id":"call_jwY8kllWAsnoSXtjXZQ5KR6i","name":"can_have_dragons","arguments":"{\"population\": + 123124}"},{"type":"function_call_output","call_id":"call_jwY8kllWAsnoSXtjXZQ5KR6i","output":"true"}],"model":"gpt-5.5","reasoning":{"effort":"high"},"store":false,"stream":false,"tools":[{"type":"function","name":"lookup_population","description":"Returns + the current population of the specified fictional country.","parameters":{"properties":{"country":{"type":"string"}},"required":["country"],"type":"object"}},{"type":"function","name":"can_have_dragons","description":"Returns + True if the specified population can have dragons.","parameters":{"properties":{"population":{"type":"integer"}},"required":["population"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '2645' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + H4sIAAAAAAAA/71Wy47bOBC8z1cQOs8MJFm27P2KINhbNhAosWVzhyIZPpwYgf89TcqSKHtmDwmw + N6uLrCa7qpv++URIxln2F8kMWN3kfbXPD21ZV7t8sy1YXuwOPW3LoisZq/bFYXfYtvuS9mULXdHW + fZ49BwrV/gudm2iUtDDGOwPUAWtowIq63uebuqx3EbOOOm/Dnk4NWgCuGze1tHs7GuVlOFdPhYUx + zIXg8oixn/iJAU0vYMJ+BmcQSuMHAtcx8UR5l7qOKBijwk7phYiB3sA3D7K7NBokFe6CYP6aR4zL + iaxh4CgXNt3JpXXGd47jpdP4QH80yjvtXePUGzyCTinRdFSs6QbFQIQ7HbV72b5uX8q83L3k1Uu5 + yeYFhoZ06bYxE0a+xNKMBZqlHezxP5Rl9SYPyrZ1Ve9hAzsGHc1bGvNFFnfREHnAWnqEBfhIwgh2 + SjqQy6HSg61opzLBDzfvjguolMrRqbRfvq5AoY7aqPYdJBIh7yd0kDKUnKgllGilvYhkRPWkKDfP + RVmRf3yZFxWhkpEL2GfCHemoxC1nIMzQI6Z+zWby6+3XnC/TSB4v0XP0TUOl/Y42nGGjRESptRyL + hdV4mngiB3rYoAVArO2Ajho9r7Gd0Jbwji0ROnPlbTN1XBPFni2BxRm0Q8ruBM0bXD7EDASZRkNl + ZXUafYaNa5Vc9Rv0vTKxsid+PN0umVk/DNRM9HMDWtqDu+CZAnfPYdVuFsyZ460cnxq4p16M6qOp + lIFV5zsYdDC9j/HiVoCbzLfD4dEGunwn9orrxrLfjnwG0yrLYznR1Iz7YZkcoxAnheeLynmnshmw + jy02pem9jFNgEZ+B7QzXU2k/A15AWuJOQDpvDBbmzpQBsRq6UC9Geh75qCAdzkJnLq8LtaRDTCqU + evO6WWiWJcFZA0prbFKUUXssJlZ+HY8NG9PchZMr4qQLhkjA69IaSwtmYZhyA2zV+kmCOZb07TIN + xqckQShjfKzEp/Ts0R9Pd9njGeNLFHooVf33BfsbmQi/lyeR7nFiPAiFS5qwpLkt+ROdErU/lIrj + 7D2OD+LvaZUk+Z/lmmejU7pJpnw+B3Ucg4f9GDCo41SNjHFLWzH9k/DxuZpnBJer57gs6+dHIHnk + Z8vEQcmWnflqnNw/82X1HvAe7zxkP6J2+P6JhHlbzHPK2/VMRQ9RRh0N/Nen6y8ML4eS2gkAAA== + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74d0cfcf8b2b4b-ORD + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 03:14:37 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '1306' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=QSHgROUcoFXh6zyyvGCnOLGXX2c4b7S8nJowB4RJhtI-1778037276.1270735-1.0.1.1-m3I6gMTwPPLlpeHgduEBRKmVIQGDDZcoItTTaVkwIUQXMOTcCG5_fl8MQIwjyawnDJPGxUthuGFfjTpsPX1OSBOXGmAUziyOhJ04qWwyUzM7fMtNEWtTmOgvh4au55D0; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 03:44:37 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_e20848e56750417e9dd0940f7c4fc82e + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_openai_responses/test_responses_tool_use.yaml b/tests/cassettes/test_openai_responses/test_responses_tool_use.yaml new file mode 100644 index 000000000..4cd3af59b --- /dev/null +++ b/tests/cassettes/test_openai_responses/test_responses_tool_use.yaml @@ -0,0 +1,216 @@ +interactions: +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"What + is 1231 * 2331? Use the multiply tool."}],"model":"gpt-5.5","reasoning":{"effort":"low"},"store":false,"stream":false,"tools":[{"type":"function","name":"multiply","description":"Multiply + two numbers.","parameters":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '400' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + H4sIAAAAAAAA/5VVS0/bQBC+8yuQz4BsB8cO115aaKVKPaGCrLF3HBbW3u0+gAjlv3d2Hb8CVGoO + UXa+nW/n8c3k7eT0NOIsujqNNBpVxlkdJyxjNVaX66TO4mS9aaBKqirDOi6SzbrKcVU0RRrnAJgy + Fp15Clk9Ym0HGtkZ7O21RrDISvBYkudFvMqTfBMwY8E6431q2SqBdK93qqB+2mrpOh9XA8Jgb+ZC + 8G5Ltjc6kkHBDrX3Z/iMQio6ELDvHx4ol08XcUBRa+k9OydEMDQa/zjs6l2psANhdwTGF/1l3g1k + JUMLXJi5J++M1a62nJKe21t4LaWzytnSyid8D1opRVmDWNK1kqHwOW2VPc8usvM0Ttfn8eV5uorG + Cxr8c3O3/iWy/A6l6Qs0trapP29svYY0NBaKJt9keZauWYLNZhOeCyR2pzDQuC7kGaKe4M/6GEDQ + W9diZwP+dhfBXXSVpKvk7C6q6Ge6WiX76bYnLvuYw8/uuv1yk/+6xhU8sttbJb5VX+Ob7ffJo4M2 + xNY6YbkSuygAe/q+D4VRoIkIxbLc1LFeU4rkSm3HD9pO0DOXzpSDovvAxpIrTdlaoqwfsHzC3aeY + pop0h4ZF6eVD30caDCO7hZ6xaaQOQyTkyyHFyLi2BT2wj/o20KDdUUieuuG4ULNB/cwpKcuH+WiA + 6hMdxk5qXAyWxVZ5TblgTw75W3y1U2wUWQvTeaaKcK+v+iHkZ9SVNDxUM2qRcddOg9n34UFSfB4G + Z2U0Aua9go/FN7Weoak1V0NlfxwUcGpfJJWirVCbi38IZUS8RFrqkTaz9PomUlmohku7F/WRYRYm + 7yxu+0U0fPZnc9/q/3xPPmCJ/LLiGtlYrTGuo6fG0/3Me3jusLNnCDDGfTlB/JznHpRychQG6Ujz + sPL9MB1NnZWqFHJLFaw8QTwaVRiwTdEbNPV0WGUR4wYqMfwHOANbnOTHu8UizYqz9/bZdn6bFgpN + IJsc44VQj/dzuvoI+Ih3nN7PqK20ICawSMYBcGY5rCQ9YGCDpvYn+79G2nD3kgcAAA== + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74ce7299e4cb75-DFW + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 03:13:00 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '1326' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=zXi8aTY7cDmdwne_.I.3Hf.KrkQjzxMBHFbO2WWZqFg-1778037179.2921715-1.0.1.1-l3RZkU_etG9t6aSFPSTnlBiIypr5vSKjMdMV9iEuWrDk59KcVyvnaNk5TDFILQjTF1bFbvNJ5NmInvZIg_tp2BdSmbNPLyyqdw3RXJ9W9MUEfl9QCXSFs4E6vlaRqupr; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 03:43:00 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_e6f3256822534dd38be1cf139234e596 + status: + code: 200 + message: OK +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"What + is 1231 * 2331? Use the multiply tool."},{"type":"function_call","call_id":"call_nJmCK7SJe3ajdYYplIbH0KgL","name":"multiply","arguments":"{\"a\": + 1231, \"b\": 2331}"},{"type":"function_call_output","call_id":"call_nJmCK7SJe3ajdYYplIbH0KgL","output":"2869461"}],"model":"gpt-5.5","reasoning":{"effort":"low"},"store":false,"stream":false,"tools":[{"type":"function","name":"multiply","description":"Multiply + two numbers.","parameters":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '619' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: !!binary | + H4sIAAAAAAAA/5VVTY/aMBC98ytQjhWskgAhVOpPqNT7qoomyQTcdWzXdthFK/57x3YSEhYOPUFm + PM/z8d74c7FcRqyOvi8jjUYV8b7aplBleZlWh+0miZPs0ECZlBVmeZwnsMkPmMEO4sMW8jrLMFo5 + CFn+wcoOMFKY3l5pBIt1Ac6X7Pd5vNkneex9xoLtjIupZKs40rkQVEL1dtSyEy6vBrjBYGacM3Ek + 2yd9kkHBBbWLr/GMXCr6IMc1XDxA3l2deC9qLV2k6Dj3hkbj3w5FdSkUCuD2Qs74JeTJxABW1GiB + cTONZMJY3VWWUdFTewsfheys6mxh5Rt+dVopeVEBn8O1skbuajoqu9697NZpnGbreLtON9F4QIO7 + bhoWbiLLq29NaNA42tYcn0+2dhY3WdjRSHebJq92+30FYYIexV4Uehw0Bo4Tx7MRemclhUVxS2qa + 2Ax2aBN+2DHaHwAhpIWhta+/Z04uj0rL8oHHAxFukm6S5bdluqGfH8t0lWeH1TZLovHstf83hkfq + BMbn1DCiQQHCvBOrRreW3HvBGEa1U3GLAcdjECU1TRT5fLpEkEBhReogluEDlpHrzGRnikFAhZ/d + OGGqtVWWIKsTFm94eerT6Loe+BGl21OgDenQSDGTDzaN1L5RXL73NUama1vQA/ooJwMN2gul5KAb + hjPxGNRnRkVZNsixgY6HWRJFpMaZji22ylG48/akr78fWp8bZdbC7XtCFn8udL1P+Yy6lIb5bhJF + a9a1tz0Q5nCSlJ8fXGdlNDrMV8EM1zSd8Jq+zb5GU2mmhs7+pAqZ4pelfZfUirZEbV5upwW0QTL9 + sZvHUaSlGWkzKS8MkdpCPZzbnQzuDJM0GUnsGPbeSOqZFsr/i108QIncbmQa65mSfV53V41fEz3e + VB6eiIkH6pq5dgL/Na3dM2VxlwbxSDP/wjgx3anOSlVM1kE8GpUX2CEPBk0zHTZnVDMDJR+enM7v + tZF+TMz29mG7+mqfPAYje7wC61tgPCPq/XOQ7B85HuGO6n0GbWlP8glykowK6MxcrcQ9qMF6Ul0X + 1381ZAUOAggAAA== + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74ce7c3b71cb75-DFW + content-encoding: + - gzip + content-type: + - application/json + date: + - Wed, 06 May 2026 03:13:02 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '1106' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=yqi2SzRPEQIj_22_ctCcbWfMKVpSPEKe83ZGD3EBaLY-1778037180.8329482-1.0.1.1-enl4RmONGIjXc08ZO75dW7K7g.J5p7W_GNXjJSYbhPlgeJK0.2h4kJfDALyQFDJUpAFXFOhfg.OC3VA.I7QfOELuUPnL1HR2iM7vf7Giaqal.KxFdQJDyFBy5H6GRQcu; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 03:43:02 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '40000000' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_3a3a8ea3224a44ef8a1ccfa281e21c3a + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_openai_responses/test_responses_tool_use_streaming.yaml b/tests/cassettes/test_openai_responses/test_responses_tool_use_streaming.yaml new file mode 100644 index 000000000..c20fa5ef7 --- /dev/null +++ b/tests/cassettes/test_openai_responses/test_responses_tool_use_streaming.yaml @@ -0,0 +1,393 @@ +interactions: +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"What + is 1231 * 2331? Use the multiply tool."}],"model":"gpt-5.5","reasoning":{"effort":"low"},"store":false,"stream":true,"tools":[{"type":"function","name":"multiply","description":"Multiply + two numbers.","parameters":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '399' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: 'event: response.created + + data: {"type":"response.created","response":{"id":"resp_00d64fa806f333310169fab1be69d081a08f8285661855594c","object":"response","created_at":1778037182,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.5-2026-04-23","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"effort":"low","summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Multiply + two numbers.","name":"multiply","parameters":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object","additionalProperties":false},"strict":true}],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + + event: response.in_progress + + data: {"type":"response.in_progress","response":{"id":"resp_00d64fa806f333310169fab1be69d081a08f8285661855594c","object":"response","created_at":1778037182,"status":"in_progress","background":false,"completed_at":null,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.5-2026-04-23","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"effort":"low","summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Multiply + two numbers.","name":"multiply","parameters":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object","additionalProperties":false},"strict":true}],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","item":{"id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","type":"function_call","status":"in_progress","arguments":"","call_id":"call_sVidsfFJ6zlzRpelrPkTPlpd","name":"multiply"},"output_index":0,"sequence_number":2} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"{\"","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"ReLAoBL6oNRsXA","output_index":0,"sequence_number":3} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"a","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"oZA1PK3x65X0w51","output_index":0,"sequence_number":4} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"\":","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"WFzXP9CAvnsSdE","output_index":0,"sequence_number":5} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"123","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"3bk9yszS1TEHx","output_index":0,"sequence_number":6} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"1","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"ckqcfb2NtXm34XT","output_index":0,"sequence_number":7} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":",\"","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"cspustnrzHoiSV","output_index":0,"sequence_number":8} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"b","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"HhO4w36skjtIxaU","output_index":0,"sequence_number":9} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"\":","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"SnDoVlFiXfYxtl","output_index":0,"sequence_number":10} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"233","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"arziGMMxMM5qo","output_index":0,"sequence_number":11} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"1","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"obhmaTA5ZilaAQc","output_index":0,"sequence_number":12} + + + event: response.function_call_arguments.delta + + data: {"type":"response.function_call_arguments.delta","delta":"}","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","obfuscation":"Mezf0KdDroRLDUI","output_index":0,"sequence_number":13} + + + event: response.function_call_arguments.done + + data: {"type":"response.function_call_arguments.done","arguments":"{\"a\":1231,\"b\":2331}","item_id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","output_index":0,"sequence_number":14} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","item":{"id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","type":"function_call","status":"completed","arguments":"{\"a\":1231,\"b\":2331}","call_id":"call_sVidsfFJ6zlzRpelrPkTPlpd","name":"multiply"},"output_index":0,"sequence_number":15} + + + event: response.completed + + data: {"type":"response.completed","response":{"id":"resp_00d64fa806f333310169fab1be69d081a08f8285661855594c","object":"response","created_at":1778037182,"status":"completed","background":false,"completed_at":1778037183,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-5.5-2026-04-23","moderation":null,"output":[{"id":"fc_00d64fa806f333310169fab1bf324481a0852cda3180856290","type":"function_call","status":"completed","arguments":"{\"a\":1231,\"b\":2331}","call_id":"call_sVidsfFJ6zlzRpelrPkTPlpd","name":"multiply"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"24h","reasoning":{"effort":"low","summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tools":[{"type":"function","description":"Multiply + two numbers.","name":"multiply","parameters":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object","additionalProperties":false},"strict":true}],"top_logprobs":0,"top_p":0.98,"truncation":"disabled","usage":{"input_tokens":58,"input_tokens_details":{"cached_tokens":0},"output_tokens":23,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":81},"user":null,"metadata":{}},"sequence_number":16} + + + ' + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74ce85bdf0cb75-DFW + content-type: + - text/event-stream; charset=utf-8 + date: + - Wed, 06 May 2026 03:13:02 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '266' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=dOVwLeU7r7aShRQaxTKiYcu2Y1q8ZJiD8OFt516IQkk-1778037182.353376-1.0.1.1-kDgOFNW1kiTQunazntKDBwpmZh4ai7MGVZI2ZIDLzRXP4MRBfrPevoYSr41Ul_ozGR_VudKgENypl8pRCVaMoqfP66IEVu.Oz7Jz1Le7WzsMCL78NWXm.AbJCZy_KKVD; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 03:43:02 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '39999493' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_996657ef97514aeb93f449d57303514d + status: + code: 200 + message: OK +- request: + body: '{"include":["reasoning.encrypted_content"],"input":[{"role":"user","content":"What + is 1231 * 2331? Use the multiply tool."},{"type":"function_call","call_id":"call_sVidsfFJ6zlzRpelrPkTPlpd","name":"multiply","arguments":"{\"a\": + 1231, \"b\": 2331}"},{"type":"function_call_output","call_id":"call_sVidsfFJ6zlzRpelrPkTPlpd","output":"2869461"}],"model":"gpt-5.5","reasoning":{"effort":"low"},"store":false,"stream":true,"tools":[{"type":"function","name":"multiply","description":"Multiply + two numbers.","parameters":{"properties":{"a":{"type":"integer"},"b":{"type":"integer"}},"required":["a","b"],"type":"object"}}]}' + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '618' + Content-Type: + - application/json + Host: + - api.openai.com + User-Agent: + - OpenAI/Python 2.34.0 + X-Stainless-Arch: + - x64 + X-Stainless-Async: + - 'false' + X-Stainless-Lang: + - python + X-Stainless-OS: + - Linux + X-Stainless-Package-Version: + - 2.34.0 + X-Stainless-Runtime: + - CPython + X-Stainless-Runtime-Version: + - 3.11.15 + x-stainless-read-timeout: + - '600' + x-stainless-retry-count: + - '0' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: "event: response.created\ndata: {\"type\":\"response.created\",\"response\"\ + :{\"id\":\"resp_0dacb603de1c9e6b0169fab1c2314081a3b1df3cc5c09e0c60\",\"object\"\ + :\"response\",\"created_at\":1778037186,\"status\":\"in_progress\",\"background\"\ + :false,\"completed_at\":null,\"error\":null,\"frequency_penalty\":0.0,\"incomplete_details\"\ + :null,\"instructions\":null,\"max_output_tokens\":null,\"max_tool_calls\"\ + :null,\"model\":\"gpt-5.5-2026-04-23\",\"moderation\":null,\"output\":[],\"\ + parallel_tool_calls\":true,\"presence_penalty\":0.0,\"previous_response_id\"\ + :null,\"prompt_cache_key\":null,\"prompt_cache_retention\":\"24h\",\"reasoning\"\ + :{\"effort\":\"low\",\"summary\":null},\"safety_identifier\":null,\"service_tier\"\ + :\"auto\",\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\"\ + :\"text\"},\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"\ + type\":\"function\",\"description\":\"Multiply two numbers.\",\"name\":\"\ + multiply\",\"parameters\":{\"properties\":{\"a\":{\"type\":\"integer\"},\"\ + b\":{\"type\":\"integer\"}},\"required\":[\"a\",\"b\"],\"type\":\"object\"\ + ,\"additionalProperties\":false},\"strict\":true}],\"top_logprobs\":0,\"top_p\"\ + :0.98,\"truncation\":\"disabled\",\"usage\":null,\"user\":null,\"metadata\"\ + :{}},\"sequence_number\":0}\n\nevent: response.in_progress\ndata: {\"type\"\ + :\"response.in_progress\",\"response\":{\"id\":\"resp_0dacb603de1c9e6b0169fab1c2314081a3b1df3cc5c09e0c60\"\ + ,\"object\":\"response\",\"created_at\":1778037186,\"status\":\"in_progress\"\ + ,\"background\":false,\"completed_at\":null,\"error\":null,\"frequency_penalty\"\ + :0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\"\ + :null,\"max_tool_calls\":null,\"model\":\"gpt-5.5-2026-04-23\",\"moderation\"\ + :null,\"output\":[],\"parallel_tool_calls\":true,\"presence_penalty\":0.0,\"\ + previous_response_id\":null,\"prompt_cache_key\":null,\"prompt_cache_retention\"\ + :\"24h\",\"reasoning\":{\"effort\":\"low\",\"summary\":null},\"safety_identifier\"\ + :null,\"service_tier\":\"auto\",\"store\":false,\"temperature\":1.0,\"text\"\ + :{\"format\":{\"type\":\"text\"},\"verbosity\":\"medium\"},\"tool_choice\"\ + :\"auto\",\"tools\":[{\"type\":\"function\",\"description\":\"Multiply two\ + \ numbers.\",\"name\":\"multiply\",\"parameters\":{\"properties\":{\"a\":{\"\ + type\":\"integer\"},\"b\":{\"type\":\"integer\"}},\"required\":[\"a\",\"b\"\ + ],\"type\":\"object\",\"additionalProperties\":false},\"strict\":true}],\"\ + top_logprobs\":0,\"top_p\":0.98,\"truncation\":\"disabled\",\"usage\":null,\"\ + user\":null,\"metadata\":{}},\"sequence_number\":1}\n\nevent: response.output_item.added\n\ + data: {\"type\":\"response.output_item.added\",\"item\":{\"id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"type\":\"message\",\"status\":\"in_progress\",\"content\":[],\"phase\"\ + :\"final_answer\",\"role\":\"assistant\"},\"output_index\":0,\"sequence_number\"\ + :2}\n\nevent: response.content_part.added\ndata: {\"type\":\"response.content_part.added\"\ + ,\"content_index\":0,\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"output_index\":0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"\ + logprobs\":[],\"text\":\"\"},\"sequence_number\":3}\n\nevent: response.output_text.delta\n\ + data: {\"type\":\"response.output_text.delta\",\"content_index\":0,\"delta\"\ + :\"123\",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"yJXGKTBmb2UOz\",\"output_index\":0,\"sequence_number\"\ + :4}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\"\ + ,\"content_index\":0,\"delta\":\"1\",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"0bPnrPoF6d7HddV\",\"output_index\":0,\"\ + sequence_number\":5}\n\nevent: response.output_text.delta\ndata: {\"type\"\ + :\"response.output_text.delta\",\"content_index\":0,\"delta\":\" \xD7\",\"\ + item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\",\"logprobs\"\ + :[],\"obfuscation\":\"ft7PFRXyUN31Dr\",\"output_index\":0,\"sequence_number\"\ + :6}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\"\ + ,\"content_index\":0,\"delta\":\" \",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"HWsKMbb1JWpQBGo\",\"output_index\":0,\"\ + sequence_number\":7}\n\nevent: response.output_text.delta\ndata: {\"type\"\ + :\"response.output_text.delta\",\"content_index\":0,\"delta\":\"233\",\"item_id\"\ + :\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\",\"logprobs\":[],\"\ + obfuscation\":\"OyardqgvtiISP\",\"output_index\":0,\"sequence_number\":8}\n\ + \nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\"\ + ,\"content_index\":0,\"delta\":\"1\",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"M5u2aOAAbIF9HaL\",\"output_index\":0,\"\ + sequence_number\":9}\n\nevent: response.output_text.delta\ndata: {\"type\"\ + :\"response.output_text.delta\",\"content_index\":0,\"delta\":\" =\",\"item_id\"\ + :\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\",\"logprobs\":[],\"\ + obfuscation\":\"DblLLi7wbRu7Yj\",\"output_index\":0,\"sequence_number\":10}\n\ + \nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\"\ + ,\"content_index\":0,\"delta\":\" **\",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"iiojCebBdYGkO\",\"output_index\":0,\"sequence_number\"\ + :11}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\"\ + ,\"content_index\":0,\"delta\":\"2\",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"eJPGA9ScIqJTAvj\",\"output_index\":0,\"\ + sequence_number\":12}\n\nevent: response.output_text.delta\ndata: {\"type\"\ + :\"response.output_text.delta\",\"content_index\":0,\"delta\":\",\",\"item_id\"\ + :\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\",\"logprobs\":[],\"\ + obfuscation\":\"pkBo5DcWhNmQVlb\",\"output_index\":0,\"sequence_number\":13}\n\ + \nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\"\ + ,\"content_index\":0,\"delta\":\"869\",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"6Z8oUB1RMn3DD\",\"output_index\":0,\"sequence_number\"\ + :14}\n\nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\"\ + ,\"content_index\":0,\"delta\":\",\",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"Bf7uSiaON5ThxbH\",\"output_index\":0,\"\ + sequence_number\":15}\n\nevent: response.output_text.delta\ndata: {\"type\"\ + :\"response.output_text.delta\",\"content_index\":0,\"delta\":\"461\",\"item_id\"\ + :\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\",\"logprobs\":[],\"\ + obfuscation\":\"qqwxt4JuKvQU6\",\"output_index\":0,\"sequence_number\":16}\n\ + \nevent: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\"\ + ,\"content_index\":0,\"delta\":\"**\",\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"obfuscation\":\"XM0rK6J7j52uLE\",\"output_index\":0,\"\ + sequence_number\":17}\n\nevent: response.output_text.done\ndata: {\"type\"\ + :\"response.output_text.done\",\"content_index\":0,\"item_id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"logprobs\":[],\"output_index\":0,\"sequence_number\":18,\"text\":\"1231\ + \ \xD7 2331 = **2,869,461**\"}\n\nevent: response.content_part.done\ndata:\ + \ {\"type\":\"response.content_part.done\",\"content_index\":0,\"item_id\"\ + :\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\",\"output_index\"\ + :0,\"part\":{\"type\":\"output_text\",\"annotations\":[],\"logprobs\":[],\"\ + text\":\"1231 \xD7 2331 = **2,869,461**\"},\"sequence_number\":19}\n\nevent:\ + \ response.output_item.done\ndata: {\"type\":\"response.output_item.done\"\ + ,\"item\":{\"id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\"\ + ,\"annotations\":[],\"logprobs\":[],\"text\":\"1231 \xD7 2331 = **2,869,461**\"\ + }],\"phase\":\"final_answer\",\"role\":\"assistant\"},\"output_index\":0,\"\ + sequence_number\":20}\n\nevent: response.completed\ndata: {\"type\":\"response.completed\"\ + ,\"response\":{\"id\":\"resp_0dacb603de1c9e6b0169fab1c2314081a3b1df3cc5c09e0c60\"\ + ,\"object\":\"response\",\"created_at\":1778037186,\"status\":\"completed\"\ + ,\"background\":false,\"completed_at\":1778037187,\"error\":null,\"frequency_penalty\"\ + :0.0,\"incomplete_details\":null,\"instructions\":null,\"max_output_tokens\"\ + :null,\"max_tool_calls\":null,\"model\":\"gpt-5.5-2026-04-23\",\"moderation\"\ + :null,\"output\":[{\"id\":\"msg_0dacb603de1c9e6b0169fab1c2e40081a3857ad788023cb7ea\"\ + ,\"type\":\"message\",\"status\":\"completed\",\"content\":[{\"type\":\"output_text\"\ + ,\"annotations\":[],\"logprobs\":[],\"text\":\"1231 \xD7 2331 = **2,869,461**\"\ + }],\"phase\":\"final_answer\",\"role\":\"assistant\"}],\"parallel_tool_calls\"\ + :true,\"presence_penalty\":0.0,\"previous_response_id\":null,\"prompt_cache_key\"\ + :null,\"prompt_cache_retention\":\"24h\",\"reasoning\":{\"effort\":\"low\"\ + ,\"summary\":null},\"safety_identifier\":null,\"service_tier\":\"default\"\ + ,\"store\":false,\"temperature\":1.0,\"text\":{\"format\":{\"type\":\"text\"\ + },\"verbosity\":\"medium\"},\"tool_choice\":\"auto\",\"tools\":[{\"type\"\ + :\"function\",\"description\":\"Multiply two numbers.\",\"name\":\"multiply\"\ + ,\"parameters\":{\"properties\":{\"a\":{\"type\":\"integer\"},\"b\":{\"type\"\ + :\"integer\"}},\"required\":[\"a\",\"b\"],\"type\":\"object\",\"additionalProperties\"\ + :false},\"strict\":true}],\"top_logprobs\":0,\"top_p\":0.98,\"truncation\"\ + :\"disabled\",\"usage\":{\"input_tokens\":94,\"input_tokens_details\":{\"\ + cached_tokens\":0},\"output_tokens\":18,\"output_tokens_details\":{\"reasoning_tokens\"\ + :0},\"total_tokens\":112},\"user\":null,\"metadata\":{}},\"sequence_number\"\ + :21}\n\n" + headers: + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + cf-ray: + - 9f74ce9d5cec11fd-ORD + content-type: + - text/event-stream; charset=utf-8 + date: + - Wed, 06 May 2026 03:13:06 GMT + openai-organization: + - user-r3e61fpak04cbaokp5buoae4 + openai-processing-ms: + - '353' + openai-project: + - proj_2Y4H7T2XPShczTQx5fqMXdNp + openai-version: + - '2020-10-01' + server: + - cloudflare + set-cookie: + - __cf_bm=YuSyQqrRg_ripxVBGX9ZnLTFASaREsd8RjcORfPvRSE-1778037186.1373405-1.0.1.1-OTt6GIIb.vqG7KdooMAgro_13UWD_y3xibuJnSKYjofIRRPgkkgl3ivm9KWDf85p3Bq6X.FzVkbHHpoqVRfiGNNvdSjs_gxjjGTIEXa8..L8k11d2g6DXPHMJLp1bXFs; + HttpOnly; Secure; Path=/; Domain=api.openai.com; Expires=Wed, 06 May 2026 + 03:43:06 GMT + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '15000' + x-ratelimit-limit-tokens: + - '40000000' + x-ratelimit-remaining-requests: + - '14999' + x-ratelimit-remaining-tokens: + - '39999457' + x-ratelimit-reset-requests: + - 4ms + x-ratelimit-reset-tokens: + - 0s + x-request-id: + - req_808d2a0863014e248d080b21301d5d58 + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_cli_openai_models.py b/tests/test_cli_openai_models.py index eabc7d84d..ac353eefb 100644 --- a/tests/test_cli_openai_models.py +++ b/tests/test_cli_openai_models.py @@ -101,6 +101,9 @@ def test_gpt5_verbosity_option_is_sent_to_openai_chat_completions(httpx_mock): "-m", "gpt-5", "-o", + "chat_completions", + "1", + "-o", "verbosity", "high", "--no-stream", @@ -116,6 +119,62 @@ def test_gpt5_verbosity_option_is_sent_to_openai_chat_completions(httpx_mock): assert "text" not in request_body +def test_gpt5_verbosity_option_is_sent_to_openai_responses_by_default(httpx_mock): + httpx_mock.add_response( + method="POST", + url="https://api.openai.com/v1/responses", + json={ + "id": "resp_test_1", + "object": "response", + "created_at": 1, + "model": "gpt-5", + "output": [ + { + "type": "message", + "id": "msg_1", + "role": "assistant", + "status": "completed", + "content": [ + { + "type": "output_text", + "text": "Verbose enough", + "annotations": [], + } + ], + } + ], + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "total_tokens": 8, + }, + "status": "completed", + }, + headers={"Content-Type": "application/json"}, + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "-m", + "gpt-5", + "-o", + "verbosity", + "high", + "--no-stream", + "--key", + "x", + "Say hi", + ], + catch_exceptions=False, + ) + assert result.exit_code == 0 + request_body = json.loads(httpx_mock.get_requests()[-1].content) + assert request_body["text"]["verbosity"] == "high" + assert request_body["include"] == ["reasoning.encrypted_content"] + assert "verbosity" not in request_body + + def test_gpt5_verbosity_option_validates_allowed_values(): runner = CliRunner() result = runner.invoke( @@ -209,6 +268,9 @@ def test_openai_image_detail_original_is_sent_for_gpt54(httpx_mock): "-m", "gpt-5.4", "-o", + "chat_completions", + "1", + "-o", "image_detail", "original", "--at", @@ -227,6 +289,71 @@ def test_openai_image_detail_original_is_sent_for_gpt54(httpx_mock): assert image_part["image_url"]["detail"] == "original" +def test_openai_image_detail_original_is_sent_for_gpt54_responses_by_default( + httpx_mock, +): + httpx_mock.add_response( + method="POST", + url="https://api.openai.com/v1/responses", + json={ + "id": "resp_test_1", + "object": "response", + "created_at": 1, + "model": "gpt-5.4", + "output": [ + { + "type": "message", + "id": "msg_1", + "role": "assistant", + "status": "completed", + "content": [ + { + "type": "output_text", + "text": "Original detail", + "annotations": [], + } + ], + } + ], + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "total_tokens": 8, + }, + "status": "completed", + }, + headers={"Content-Type": "application/json"}, + ) + runner = CliRunner() + result = runner.invoke( + cli, + [ + "-m", + "gpt-5.4", + "-o", + "image_detail", + "original", + "--at", + "https://example.com/image.jpg", + "image/jpeg", + "--no-stream", + "--key", + "x", + "Describe this", + ], + catch_exceptions=False, + ) + assert result.exit_code == 0 + request_body = json.loads(httpx_mock.get_requests()[-1].content) + image_part = request_body["input"][0]["content"][1] + assert image_part == { + "type": "input_image", + "image_url": "https://example.com/image.jpg", + "detail": "original", + } + assert "image_detail" not in request_body + + def test_openai_image_detail_original_is_rejected_for_other_models(): runner = CliRunner() result = runner.invoke( diff --git a/tests/test_openai_responses.py b/tests/test_openai_responses.py new file mode 100644 index 000000000..1527e0875 --- /dev/null +++ b/tests/test_openai_responses.py @@ -0,0 +1,604 @@ +"""Tests for the /v1/responses code path in the default OpenAI plugin.""" + +import json +import os + +import llm +import pytest +from pytest_httpx import IteratorStream + +API_KEY = os.environ.get("PYTEST_OPENAI_API_KEY", None) or "badkey" + + +def _responses_sse(event_type, data): + data = {"type": event_type, **data} + return f"event: {event_type}\ndata: {json.dumps(data)}\n\n".encode("utf-8") + + +def _responses_reasoning_summary_stream(): + yield _responses_sse( + "response.reasoning_summary_text.delta", + { + "item_id": "rs_1", + "output_index": 0, + "summary_index": 0, + "delta": "Thinking", + "sequence_number": 1, + }, + ) + yield _responses_sse( + "response.reasoning_summary_text.delta", + { + "item_id": "rs_1", + "output_index": 0, + "summary_index": 0, + "delta": " aloud", + "sequence_number": 2, + }, + ) + yield _responses_sse( + "response.output_item.done", + { + "item": { + "id": "rs_1", + "type": "reasoning", + "summary": [{"type": "summary_text", "text": "Thinking aloud"}], + "encrypted_content": "encrypted", + "status": "completed", + }, + "output_index": 0, + "sequence_number": 3, + }, + ) + yield _responses_sse( + "response.output_text.delta", + { + "item_id": "msg_1", + "output_index": 1, + "content_index": 0, + "delta": "done", + "logprobs": [], + "sequence_number": 4, + }, + ) + + +def test_responses_model_is_registered(): + model = llm.get_model("gpt-5.5") + assert "Responses" in type(model).__name__ + # The chat_completions opt-out option must be exposed. + assert "chat_completions" in model.Options.model_fields + + +def test_chat_completions_opt_out_dispatches_to_chat(httpx_mock): + """When chat_completions=1 is passed, the request must hit + /v1/chat/completions, not /v1/responses.""" + httpx_mock.add_response( + method="POST", + url="https://api.openai.com/v1/chat/completions", + json={ + "id": "chatcmpl-x", + "object": "chat.completion", + "model": "gpt-5.5", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "hi from chat"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 1, "completion_tokens": 2, "total_tokens": 3}, + }, + headers={"Content-Type": "application/json"}, + ) + model = llm.get_model("gpt-5.5") + response = model.prompt("hello", stream=False, chat_completions=True, key="test") + assert response.text() == "hi from chat" + + +def test_default_routes_to_responses_endpoint(httpx_mock): + httpx_mock.add_response( + method="POST", + url="https://api.openai.com/v1/responses", + json={ + "id": "resp_test_1", + "object": "response", + "created_at": 1, + "model": "gpt-5.5", + "output": [ + { + "type": "message", + "id": "msg_1", + "role": "assistant", + "status": "completed", + "content": [ + { + "type": "output_text", + "text": "hi from responses", + "annotations": [], + } + ], + } + ], + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "total_tokens": 8, + }, + "status": "completed", + }, + headers={"Content-Type": "application/json"}, + ) + model = llm.get_model("gpt-5.5") + response = model.prompt("hello", stream=False, key="test") + assert response.text() == "hi from responses" + # Ensure we sent to the right endpoint + requests = [r for r in httpx_mock.get_requests()] + assert any("/v1/responses" in str(r.url) for r in requests) + request_body = json.loads(requests[-1].content) + assert request_body["include"] == ["reasoning.encrypted_content"] + assert request_body["reasoning"] == {"summary": "auto"} + + +def test_non_reasoning_responses_model_omits_encrypted_reasoning_include(httpx_mock): + from llm.default_plugins.openai_models import Responses + + httpx_mock.add_response( + method="POST", + url="https://api.openai.com/v1/responses", + json={ + "id": "resp_test_1", + "object": "response", + "created_at": 1, + "model": "gpt-4.1", + "output": [ + { + "type": "message", + "id": "msg_1", + "role": "assistant", + "status": "completed", + "content": [ + { + "type": "output_text", + "text": "hi from gpt-4.1", + "annotations": [], + } + ], + } + ], + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "total_tokens": 8, + }, + "status": "completed", + }, + headers={"Content-Type": "application/json"}, + ) + + model = Responses("gpt-4.1", vision=True, supports_schema=True, supports_tools=True) + response = model.prompt("hello", stream=False, key="test") + + assert response.text() == "hi from gpt-4.1" + request_body = json.loads(httpx_mock.get_requests()[-1].content) + assert request_body["model"] == "gpt-4.1" + assert "include" not in request_body + assert "reasoning" not in request_body + + +def test_responses_input_translation(): + """Unit-test the message-to-input translator without hitting the API.""" + from llm.parts import ( + Message, + TextPart, + ToolCallPart, + ToolResultPart, + ) + + model = llm.get_model("gpt-5.5") + + class FakePrompt: + messages = [ + Message(role="system", parts=[TextPart(text="be brief")]), + Message(role="user", parts=[TextPart(text="2 + 2?")]), + Message( + role="assistant", + parts=[ + ToolCallPart( + name="add", + arguments={"a": 2, "b": 2}, + tool_call_id="call_abc", + ) + ], + ), + Message( + role="tool", + parts=[ToolResultPart(name="add", output="4", tool_call_id="call_abc")], + ), + ] + + items, instructions = model._build_responses_input(FakePrompt()) + assert instructions == "be brief" + # First user message is a plain string content + assert items[0] == {"role": "user", "content": "2 + 2?"} + # function_call from assistant + assert items[1]["type"] == "function_call" + assert items[1]["call_id"] == "call_abc" + assert items[1]["name"] == "add" + assert json.loads(items[1]["arguments"]) == {"a": 2, "b": 2} + # tool result + assert items[2] == { + "type": "function_call_output", + "call_id": "call_abc", + "output": "4", + } + + +def test_responses_input_translation_assistant_text_uses_easy_input_message(): + """Plain prior assistant text should match OpenAI's EasyInputMessage shape.""" + from llm.parts import Message, TextPart + + model = llm.get_model("gpt-5.5") + + class FakePrompt: + messages = [ + Message(role="user", parts=[TextPart(text="hello")]), + Message(role="assistant", parts=[TextPart(text="first-ok")]), + Message(role="user", parts=[TextPart(text="what next?")]), + ] + + items, instructions = model._build_responses_input(FakePrompt()) + + assert instructions is None + assert items == [ + {"role": "user", "content": "hello"}, + {"role": "assistant", "content": "first-ok"}, + {"role": "user", "content": "what next?"}, + ] + + +def test_responses_reply_sends_prior_assistant_text_as_string(httpx_mock): + """response.reply() should send the same simple history shape a direct + openai-python Responses call would use for a text-only assistant turn.""" + + def response_json(response_id, message_id, text): + return { + "id": response_id, + "object": "response", + "created_at": 1, + "model": "gpt-5.5", + "output": [ + { + "type": "message", + "id": message_id, + "role": "assistant", + "status": "completed", + "content": [ + { + "type": "output_text", + "text": text, + "annotations": [], + } + ], + } + ], + "usage": { + "input_tokens": 5, + "output_tokens": 3, + "total_tokens": 8, + }, + "status": "completed", + } + + httpx_mock.add_response( + method="POST", + url="https://api.openai.com/v1/responses", + json=response_json("resp_1", "msg_1", "first-ok"), + headers={"Content-Type": "application/json"}, + ) + httpx_mock.add_response( + method="POST", + url="https://api.openai.com/v1/responses", + json=response_json("resp_2", "msg_2", "followup-ok"), + headers={"Content-Type": "application/json"}, + ) + + model = llm.get_model("gpt-5.5") + first = model.prompt("Say exactly: first-ok", stream=False, key="test") + second = first.reply("Say exactly: followup-ok", stream=False, key="test") + + assert first.text() == "first-ok" + assert second.text() == "followup-ok" + requests = httpx_mock.get_requests() + second_body = json.loads(requests[-1].content) + assert second_body["input"] == [ + {"role": "user", "content": "Say exactly: first-ok"}, + {"role": "assistant", "content": "first-ok"}, + {"role": "user", "content": "Say exactly: followup-ok"}, + ] + + +def test_responses_kwargs_packs_reasoning_and_verbosity(): + model = llm.get_model("gpt-5.5") + options = model.Options(reasoning_effort="low", verbosity="low") + + class FakePrompt: + pass + + p = FakePrompt() + p.options = options + p.tools = [] + p.schema = None + kwargs = model._build_responses_kwargs(p, stream=False) + assert kwargs["reasoning"] == {"summary": "auto", "effort": "low"} + assert kwargs["text"]["verbosity"] == "low" + + +def test_responses_kwargs_sets_reasoning_summary_without_effort(): + model = llm.get_model("gpt-5.5") + options = model.Options() + + class FakePrompt: + pass + + p = FakePrompt() + p.options = options + p.tools = [] + p.schema = None + kwargs = model._build_responses_kwargs(p, stream=False) + assert kwargs["reasoning"] == {"summary": "auto"} + + +def test_responses_streams_reasoning_summary_text(httpx_mock): + httpx_mock.add_response( + method="POST", + url="https://api.openai.com/v1/responses", + stream=IteratorStream(_responses_reasoning_summary_stream()), + headers={"Content-Type": "text/event-stream"}, + ) + + model = llm.get_model("gpt-5.5") + response = model.prompt("hello", key="test") + events = list(response.stream_events()) + + assert [(e.type, e.chunk) for e in events] == [ + ("reasoning", "Thinking"), + ("reasoning", " aloud"), + ("reasoning", ""), + ("text", "done"), + ] + messages = response.messages() + reasoning_parts = [ + p for m in messages for p in m.parts if isinstance(p, llm.parts.ReasoningPart) + ] + assert reasoning_parts == [ + llm.parts.ReasoningPart( + text="Thinking aloud", + provider_metadata={ + "openai": { + "id": "rs_1", + "encrypted_content": "encrypted", + "summary": [{"type": "summary_text", "text": "Thinking aloud"}], + } + }, + ) + ] + assert response.text() == "done" + + +@pytest.mark.vcr +def test_responses_basic_non_streaming(vcr): + model = llm.get_model("gpt-5.5") + response = model.prompt( + "Reply with exactly: pong", + stream=False, + reasoning_effort="low", + key=API_KEY, + ) + text = response.text() + assert "pong" in text.lower() + # response_json should reflect the Responses API shape + assert response.response_json["object"] == "response" + + +@pytest.mark.vcr +def test_responses_basic_streaming(vcr): + model = llm.get_model("gpt-5.5") + response = model.prompt( + "Reply with exactly: pong", + reasoning_effort="low", + key=API_KEY, + ) + chunks = list(response) + text = "".join(chunks) + assert "pong" in text.lower() + + +@pytest.mark.vcr +def test_responses_tool_use(vcr): + model = llm.get_model("gpt-5.5") + + def multiply(a: int, b: int) -> int: + "Multiply two numbers." + return a * b + + chain = model.chain( + "What is 1231 * 2331? Use the multiply tool.", + tools=[multiply], + stream=False, + options={"reasoning_effort": "low"}, + key=API_KEY, + ) + output = chain.text() + assert "2869461" in output.replace(",", "") + first, second = chain._responses + assert first.tool_calls()[0].name == "multiply" + assert first.tool_calls()[0].arguments == {"a": 1231, "b": 2331} + assert second.prompt.tool_results[0].output == "2869461" + + +@pytest.mark.vcr +def test_responses_tool_use_streaming(vcr): + model = llm.get_model("gpt-5.5") + + def multiply(a: int, b: int) -> int: + "Multiply two numbers." + return a * b + + chain = model.chain( + "What is 1231 * 2331? Use the multiply tool.", + tools=[multiply], + options={"reasoning_effort": "low"}, + key=API_KEY, + ) + output = "".join(chain) + assert "2869461" in output.replace(",", "") + first, second = chain._responses + assert first.tool_calls()[0].arguments == {"a": 1231, "b": 2331} + + +@pytest.mark.vcr +def test_responses_round_trips_encrypted_reasoning(vcr): + """Reasoning items returned by the API in the first turn must be + echoed back verbatim on the second turn so the model can pick up + its hidden chain of thought after the tool result arrives.""" + from llm.parts import ReasoningPart + + model = llm.get_model("gpt-5.5") + + def lookup_population(country: str) -> int: + "Returns the current population of the specified fictional country." + return 123124 + + def can_have_dragons(population: int) -> bool: + "Returns True if the specified population can have dragons." + return population > 10000 + + chain = model.chain( + "Pick a clever country name, look up its population, then check " + "whether it can have dragons. Be brief.", + tools=[lookup_population, can_have_dragons], + stream=False, + options={"reasoning_effort": "high"}, + key=API_KEY, + ) + chain.text() # drain the chain + + first = chain._responses[0] + + # The first response must produce at least one ReasoningPart carrying + # the opaque encrypted_content + id. + reasoning_parts = [ + p for m in first.messages() for p in m.parts if isinstance(p, ReasoningPart) + ] + assert reasoning_parts, "first turn should expose at least one ReasoningPart" + pm = reasoning_parts[0].provider_metadata or {} + assert "openai" in pm + assert pm["openai"].get("encrypted_content"), "encrypted_content must be captured" + assert pm["openai"].get("id"), "reasoning id must be captured" + + # The second turn's outgoing input must echo back that reasoning + # item, otherwise the model loses its chain of thought. + second = chain._responses[1] + second_input = (second._prompt_json or {}).get("input") or [] + reasoning_inputs = [it for it in second_input if it.get("type") == "reasoning"] + assert reasoning_inputs, "second turn must echo a reasoning input item" + assert reasoning_inputs[0]["encrypted_content"] == pm["openai"]["encrypted_content"] + assert reasoning_inputs[0]["id"] == pm["openai"]["id"] + + +@pytest.mark.vcr +def test_responses_interleaved_reasoning_between_tool_calls(vcr): + """Tool calls during reasoning: each turn produces fresh reasoning AND + every prior reasoning block is round-tripped on every subsequent turn + so the model's hidden chain of thought accumulates across the whole + chain. This is the GPT-5-class capability that the Chat Completions + API can't deliver because it discards reasoning between turns.""" + from llm.parts import ReasoningPart + + model = llm.get_model("gpt-5.5") + + # Tool whose results force the model to re-plan between calls: each + # lookup hands the model a NEW key to use next, so the model has to + # think to figure out the next argument. Parallel tool calls would + # short-circuit this, so we need the model to reason in series. + def db_lookup(key: str) -> str: + "Look up a value by key in the puzzle database." + table = { + "start": "Begin with the value 7.", + "step1_7": "Multiply by 13. Now lookup with key step2_.", + "step2_91": "Subtract 11. Now lookup with key step3_.", + "step3_80": ("The answer is the value modulo 9. State only the integer."), + } + return table.get(key, "unknown key") + + conversation = model.conversation(tools=[db_lookup]) + conversation.chain_limit = 4 + chain = conversation.chain( + "Solve this puzzle: call db_lookup('start'), then follow each " + "instruction step by step. Each lookup tells you the next key " + "to use. Compute each step in your head. State only the final " + "integer.", + stream=False, + options={"reasoning_effort": "high"}, + key=API_KEY, + ) + # The chain may exceed the limit - we just want enough turns to + # observe interleaved reasoning, then we stop. + try: + chain.text() + except ValueError as e: + if "Chain limit" not in str(e): + raise + + responses = chain._responses + assert ( + len(responses) >= 3 + ), f"expected at least 3 chained turns, got {len(responses)}" + + # 1) Fresh reasoning happens on more than just the first turn. This is + # the actual interleaved-reasoning capability, not just round-trip. + reasoning_token_counts = [] + for r in responses: + u = r.usage() + details = (u.details if u else None) or {} + reasoning_token_counts.append( + (details.get("output_tokens_details") or {}).get("reasoning_tokens") or 0 + ) + turns_with_fresh_reasoning = sum(1 for n in reasoning_token_counts if n > 0) + assert turns_with_fresh_reasoning >= 2, ( + f"expected >=2 turns to produce fresh reasoning, got " + f"{turns_with_fresh_reasoning} (counts: {reasoning_token_counts})" + ) + + # 2) Every reasoning block produced earlier in the chain is round- + # tripped on every subsequent turn. The Nth turn's outgoing input + # must contain at least N-1 reasoning items. + for i in range(1, len(responses)): + outgoing = (responses[i]._prompt_json or {}).get("input") or [] + reasoning_count = sum(1 for it in outgoing if it.get("type") == "reasoning") + # encrypted_content + id are non-empty on each one + for it in outgoing: + if it.get("type") == "reasoning": + assert it.get("encrypted_content"), "encrypted_content lost" + assert it.get("id"), "reasoning id lost" + assert ( + reasoning_count >= i + ), f"turn {i} must echo >= {i} reasoning items, got {reasoning_count}" + + # 3) The captured ReasoningParts on the assistant messages carry the + # opaque metadata that was actually echoed back on the wire. + for i, r in enumerate(responses[:-1]): + rparts = [ + p for m in r.messages() for p in m.parts if isinstance(p, ReasoningPart) + ] + if reasoning_token_counts[i] > 0: + assert rparts, ( + f"turn {i} produced reasoning_tokens={reasoning_token_counts[i]} " + "but no ReasoningPart was persisted" + ) + for rp in rparts: + pm = (rp.provider_metadata or {}).get("openai") or {} + assert pm.get( + "encrypted_content" + ), "ReasoningPart missing encrypted_content" diff --git a/tests/test_parts.py b/tests/test_parts.py index 1fe359282..f2a03e315 100644 --- a/tests/test_parts.py +++ b/tests/test_parts.py @@ -1249,9 +1249,7 @@ def execute(self, prompt, stream, response, conversation): response.text() parts = response.messages()[0].parts assert llm.parts.TextPart(text="answer") in parts - tool_call_parts = [ - p for p in parts if isinstance(p, llm.parts.ToolCallPart) - ] + tool_call_parts = [p for p in parts if isinstance(p, llm.parts.ToolCallPart)] assert tool_call_parts == [ llm.parts.ToolCallPart( name="search",