diff --git a/chatlas/__init__.py b/chatlas/__init__.py index 60fe8658..f9fc6d54 100644 --- a/chatlas/__init__.py +++ b/chatlas/__init__.py @@ -38,7 +38,7 @@ from ._stream_controller import StreamController from ._tokens import token_usage from ._tools import Tool, ToolBuiltIn, ToolRejectError -from ._tools_builtin import tool_web_fetch, tool_web_search +from ._tools_builtin import tool_code_execution, tool_web_fetch, tool_web_search from ._turn import AssistantTurn, SystemTurn, Turn, UserTurn try: @@ -93,6 +93,7 @@ "Tool", "ToolBuiltIn", "ToolRejectError", + "tool_code_execution", "tool_web_fetch", "tool_web_search", "Turn", diff --git a/chatlas/_content.py b/chatlas/_content.py index 2243343e..6deba7cd 100644 --- a/chatlas/_content.py +++ b/chatlas/_content.py @@ -146,6 +146,8 @@ def from_tool(cls, tool: "Tool | ToolBuiltIn") -> "ToolInfo": "web_search_results", "web_fetch_request", "web_fetch_results", + "code_execution_request", + "code_execution_result", ] """ A discriminated union of all content types. @@ -839,6 +841,74 @@ def __str__(self): return f"[web fetch result]: {self.url}" +class ContentToolRequestCodeExecution(Content): + """ + A code execution request from the model. + + This content type represents the model's request to run code in a + sandboxed environment. It's automatically generated when a built-in code + execution tool is used. + + Parameters + ---------- + code + The code the model wants to run. + language + The programming language of `code`, if the provider reports it. + extra + The raw provider-specific response data. + """ + + code: str + language: Optional[str] = None + extra: Optional[dict[str, Any]] = None + + content_type: ContentTypeEnum = "code_execution_request" + + def __str__(self): + return f"[code execution request]:\n```\n{self.code}\n```" + + +class ContentToolResponseCodeExecution(Content): + """ + Code execution results from the model. + + This content type represents the result of running code in a sandboxed + environment. It's automatically generated when a built-in code execution + tool returns results. + + Only text output (stdout/stderr or each provider's equivalent) is + surfaced here. Files the code produced (e.g. plots, CSVs) aren't + downloaded or decoded -- their raw provider references are still + available via `extra`. + + Parameters + ---------- + output + The text output of the code (e.g. stdout). + error + The error output of the code (e.g. stderr), if any. + container_id + The id of the sandbox/container the code ran in, if the provider + exposes one. Used by `chatlas.Chat` to reuse the same sandbox across + turns for providers that support it (OpenAI, Anthropic). + extra + The raw provider-specific response data. + """ + + output: Optional[str] = None + error: Optional[str] = None + container_id: Optional[str] = None + extra: Optional[dict[str, Any]] = None + + content_type: ContentTypeEnum = "code_execution_result" + + def __str__(self): + if self.error: + return f"[code execution error]:\n```\n{self.error}\n```" + return f"[code execution result]:\n```\n{self.output or ''}\n```" + + ContentUnion = Union[ ContentText, ContentImageRemote, @@ -852,6 +922,8 @@ def __str__(self): ContentToolResponseSearch, ContentToolRequestFetch, ContentToolResponseFetch, + ContentToolRequestCodeExecution, + ContentToolResponseCodeExecution, ] @@ -917,6 +989,10 @@ def create_content(data: dict[str, Any]) -> ContentUnion: return ContentToolRequestFetch.model_validate(data) elif ct == "web_fetch_results": return ContentToolResponseFetch.model_validate(data) + elif ct == "code_execution_request": + return ContentToolRequestCodeExecution.model_validate(data) + elif ct == "code_execution_result": + return ContentToolResponseCodeExecution.model_validate(data) else: raise ValueError(f"Unknown content type: {ct}") diff --git a/chatlas/_provider_anthropic.py b/chatlas/_provider_anthropic.py index 1892c230..3fdf9c43 100644 --- a/chatlas/_provider_anthropic.py +++ b/chatlas/_provider_anthropic.py @@ -28,8 +28,10 @@ ContentThinking, ContentThinkingDelta, ContentToolRequest, + ContentToolRequestCodeExecution, ContentToolRequestFetch, ContentToolRequestSearch, + ContentToolResponseCodeExecution, ContentToolResponseFetch, ContentToolResponseSearch, ContentToolResult, @@ -44,7 +46,12 @@ ) from ._tokens import get_price_info from ._tools import Tool, ToolBuiltIn, basemodel_to_param_schema -from ._tools_builtin import ToolWebFetch, ToolWebSearch +from ._tools_builtin import ( + ToolCodeExecution, + ToolWebFetch, + ToolWebSearch, + last_code_execution_container_id, +) from ._turn import AssistantTurn, FinishReason, SystemTurn, Turn, UserTurn, user_turn from ._utils import split_http_client_kwargs @@ -492,6 +499,13 @@ def _chat_perform_args( **(kwargs or {}), } + if "container" not in kwargs_full and any( + isinstance(tool, ToolCodeExecution) for tool in tools.values() + ): + container_id = last_code_execution_container_id(turns) + if container_id is not None: + kwargs_full["container"] = container_id + if data_model is not None and use_native: kwargs_full["output_config"] = output_config # type: ignore[reportPossiblyUnbound] elif data_model is not None: @@ -591,6 +605,10 @@ def stream_merge_chunks(self, completion, chunk): completion.stop_reason = chunk.delta.stop_reason completion.stop_sequence = chunk.delta.stop_sequence completion.usage.output_tokens = chunk.usage.output_tokens + # Anthropic only sends `container` (e.g. from the code execution + # tool) on the message_delta event, not message_start. + if chunk.delta.container is not None: + completion.container = chunk.delta.container return completion @@ -716,9 +734,13 @@ def _as_message_params(self, turns: Sequence[Turn]) -> list["MessageParam"]: # https://docs.claude.com/en/docs/build-with-claude/prompt-caching#how-automatic-prefix-checking-works is_last_turn = i == len(turns) - 1 if self._cache_control() and is_last_turn and len(content) > 0: - # Note: ThinkingBlockParam (i.e., type: "thinking") doesn't support cache_control - if content[-1].get("type") != "thinking": - content[-1]["cache_control"] = self._cache_control() # type: ignore + last_block = content[-1] + # Note: ThinkingBlockParam (i.e., type: "thinking") doesn't support + # cache_control. `last_block` can also be None for round-tripped + # server tool content missing `extra` (e.g. manually constructed + # in tests), in which case there's nothing to attach cache_control to. + if last_block is not None and last_block.get("type") != "thinking": + last_block["cache_control"] = self._cache_control() # type: ignore role = "user" if isinstance(turn, UserTurn) else "assistant" messages.append({"role": role, "content": content}) @@ -788,6 +810,8 @@ def _as_content_block(content: Content) -> "ContentBlockParam": ContentToolResponseSearch, ContentToolRequestFetch, ContentToolResponseFetch, + ContentToolRequestCodeExecution, + ContentToolResponseCodeExecution, ), ): # extra contains the full original content block param @@ -803,6 +827,9 @@ def _anthropic_tool_schema(tool: "Tool | ToolBuiltIn") -> "ToolUnionParam": # N.B. seems the return type here (BetaWebFetchTool20250910Param) is # not a member of ToolUnionParam since it's still in beta? return tool.get_definition("anthropic") # type: ignore + if isinstance(tool, ToolCodeExecution): + # N.B. same beta-type situation as ToolWebFetch above. + return tool.get_definition("anthropic") # type: ignore if isinstance(tool, ToolBuiltIn): return tool.definition # type: ignore @@ -907,6 +934,13 @@ def _as_turn(self, completion: Message, has_data_model=False) -> AssistantTurn: extra=extra, ) ) + elif content.name == "code_execution": + contents.append( + ContentToolRequestCodeExecution( + code=str(input_data.get("code", "")), + extra=extra, + ) + ) else: raise ValueError(f"Unknown server tool: {content.name}") elif content.type == "web_search_tool_result": @@ -954,6 +988,34 @@ def _as_turn(self, completion: Message, has_data_model=False) -> AssistantTurn: extra=extra, ) ) + elif content.type == "code_execution_tool_result": + # https://docs.claude.com/en/docs/agents-and-tools/tool-use/code-execution-tool#result + result = content.content + if result.type == "code_execution_tool_result_error": + output = None + error = result.error_code + elif result.type == "code_execution_result": + output = result.stdout or None + error = result.stderr or None + else: + # encrypted_code_execution_result: stdout isn't available in plaintext + output = None + error = result.stderr or None + extra = { + "type": content.type, + "tool_use_id": content.tool_use_id, + "content": result.model_dump(exclude_none=True), + } + contents.append( + ContentToolResponseCodeExecution( + output=output, + error=error, + container_id=completion.container.id + if completion.container + else None, + extra=extra, + ) + ) return AssistantTurn( contents, diff --git a/chatlas/_provider_google.py b/chatlas/_provider_google.py index bdafdc15..a9af587b 100644 --- a/chatlas/_provider_google.py +++ b/chatlas/_provider_google.py @@ -19,6 +19,8 @@ ContentThinking, ContentThinkingDelta, ContentToolRequest, + ContentToolRequestCodeExecution, + ContentToolResponseCodeExecution, ContentToolResult, ) from ._logging import log_model_default @@ -32,7 +34,7 @@ ) from ._tokens import get_price_info from ._tools import Tool, ToolBuiltIn -from ._tools_builtin import ToolWebFetch, ToolWebSearch +from ._tools_builtin import ToolCodeExecution, ToolWebFetch, ToolWebSearch from ._turn import AssistantTurn, FinishReason, SystemTurn, Turn, UserTurn, user_turn if TYPE_CHECKING: @@ -398,6 +400,9 @@ def _chat_perform_args( elif isinstance(tool, ToolWebFetch): gtool = GoogleTool(url_context=tool.get_definition("google")) google_tools.append(gtool) + elif isinstance(tool, ToolCodeExecution): + gtool = GoogleTool(code_execution=tool.get_definition("google")) + google_tools.append(gtool) elif isinstance(tool, ToolBuiltIn): gtool = GoogleTool.model_validate(tool.definition) google_tools.append(gtool) @@ -618,6 +623,26 @@ def _as_part_type(self, content: Content) -> "Part": response=resp, ) ) + elif isinstance(content, ContentToolRequestCodeExecution): + from google.genai.types import ExecutableCode, Language + + return Part( + executable_code=ExecutableCode( + code=content.code, + language=Language.PYTHON, + ) + ) + elif isinstance(content, ContentToolResponseCodeExecution): + from google.genai.types import CodeExecutionResult, Outcome + + return Part( + code_execution_result=CodeExecutionResult( + outcome=Outcome.OUTCOME_FAILED + if content.error + else Outcome.OUTCOME_OK, + output=content.error or content.output, + ) + ) raise ValueError(f"Unknown content type: {type(content)}") def _as_turn( @@ -695,6 +720,28 @@ def _as_turn( image_content_type=mime_type, # type: ignore ) ) + executable_code = part.get("executable_code") + if executable_code: + code = executable_code.get("code") + if code: + contents.append( + ContentToolRequestCodeExecution( + code=code, + language=executable_code.get("language"), + extra=dict(executable_code), + ) + ) + code_execution_result = part.get("code_execution_result") + if code_execution_result: + outcome = code_execution_result.get("outcome") + output = code_execution_result.get("output") + contents.append( + ContentToolResponseCodeExecution( + output=output if outcome == "OUTCOME_OK" else None, + error=output if outcome != "OUTCOME_OK" else None, + extra=dict(code_execution_result), + ) + ) if isinstance(finish_reason, FinishReason): finish_reason = finish_reason.name diff --git a/chatlas/_provider_openai.py b/chatlas/_provider_openai.py index 28c402cb..605a606c 100644 --- a/chatlas/_provider_openai.py +++ b/chatlas/_provider_openai.py @@ -20,7 +20,9 @@ ContentThinking, ContentThinkingDelta, ContentToolRequest, + ContentToolRequestCodeExecution, ContentToolRequestSearch, + ContentToolResponseCodeExecution, ContentToolResult, ) from ._logging import log_model_default @@ -28,7 +30,12 @@ from ._provider_openai_completions import load_tool_request_args from ._provider_openai_generic import BatchResult, OpenAIAbstractProvider from ._tools import Tool, ToolBuiltIn, basemodel_to_param_schema -from ._tools_builtin import ToolWebFetch, ToolWebSearch +from ._tools_builtin import ( + ToolCodeExecution, + ToolWebFetch, + ToolWebSearch, + last_code_execution_container_id, +) from ._turn import AssistantTurn, FinishReason, Turn if TYPE_CHECKING: @@ -272,6 +279,11 @@ def _chat_perform_args( "Consider using the MCP Fetch server instead via chat.register_mcp_tools_stdio_async(). " "See help(tool_web_fetch) for details." ) + elif isinstance(tool, ToolCodeExecution): + container_id = last_code_execution_container_id(turns) + tool_params.append( + tool.get_definition("openai", container_id=container_id) + ) elif isinstance(tool, ToolBuiltIn): tool_params.append(cast("ToolParam", tool.definition)) else: @@ -465,6 +477,23 @@ def _response_as_turn(completion: Response, has_data_model: bool) -> AssistantTu ) ) + elif output.type == "code_interpreter_call": + # https://platform.openai.com/docs/guides/tools-code-interpreter#understanding-code-interpreter-output + contents.append( + ContentToolRequestCodeExecution( + code=output.code or "", + extra=output.model_dump(), + ) + ) + logs = [o.logs for o in (output.outputs or []) if o.type == "logs"] + contents.append( + ContentToolResponseCodeExecution( + output="\n".join(logs) or None, + container_id=output.container_id, + extra=output.model_dump(), + ) + ) + else: raise ValueError(f"Unknown output type: {output.type}") @@ -487,7 +516,11 @@ def _response_as_turn(completion: Response, has_data_model: bool) -> AssistantTu def _turns_as_inputs(self, turns: list[Turn]) -> "list[ResponseInputItemParam]": res: "list[ResponseInputItemParam]" = [] for turn in turns: - res.extend([as_input_param(x, turn.role) for x in turn.contents]) + res.extend( + x + for x in (as_input_param(c, turn.role) for c in turn.contents) + if x is not None + ) return res def translate_model_params(self, params: StandardModelParams) -> "SubmitInputArgs": @@ -525,7 +558,9 @@ def _batch_endpoint(): return "/v1/responses" -def as_input_param(content: Content, role: Role) -> "ResponseInputItemParam": +def as_input_param( + content: Content, role: Role +) -> "Optional[ResponseInputItemParam]": if isinstance(content, ContentText): if role == "assistant": # OpenAI's type for this value (ResponseOutputMessageParam) currently has a bunch @@ -598,6 +633,13 @@ def as_input_param(content: Content, role: Role) -> "ResponseInputItemParam": } elif isinstance(content, ContentToolRequestSearch): return cast("ResponseInputItemParam", content.extra) + elif isinstance(content, ContentToolRequestCodeExecution): + return cast("ResponseInputItemParam", content.extra) + elif isinstance(content, ContentToolResponseCodeExecution): + # OpenAI bundles code + result into a single code_interpreter_call + # item, already captured by the paired + # ContentToolRequestCodeExecution's extra above. + return None else: raise ValueError(f"Unsupported content type: {type(content)}") diff --git a/chatlas/_tools_builtin.py b/chatlas/_tools_builtin.py index 356a35bb..852a4baa 100644 --- a/chatlas/_tools_builtin.py +++ b/chatlas/_tools_builtin.py @@ -1,22 +1,26 @@ """ -Built-in provider tools for web search and fetch. +Built-in provider tools for web search, web fetch, and code execution. These classes provide a provider-agnostic way to configure built-in tools -like web search and URL fetching. Each provider translates these configurations -into their specific API format. +like web search, URL fetching, and code execution. Each provider translates +these configurations into their specific API format. """ from __future__ import annotations import warnings -from typing import TYPE_CHECKING, Literal, Optional, overload +from typing import TYPE_CHECKING, Literal, Optional, Sequence, overload -from ._content import ToolAnnotations +from ._content import ContentToolResponseCodeExecution, ToolAnnotations from ._tools import ToolBuiltIn +from ._turn import AssistantTurn, Turn if TYPE_CHECKING: from anthropic.types import WebSearchTool20250305Param - from anthropic.types.beta import BetaWebFetchTool20250910Param + from anthropic.types.beta import ( + BetaCodeExecutionTool20250522Param, + BetaWebFetchTool20250910Param, + ) from anthropic.types.beta.beta_citations_config_param import ( BetaCitationsConfigParam, ) @@ -29,7 +33,9 @@ PhishBlockThreshold, UrlContext, ) + from google.genai.types import ToolCodeExecution as GoogleToolCodeExecution from openai.types.responses import WebSearchToolParam + from openai.types.responses.tool_param import CodeInterpreter from ._typing_extensions import TypedDict @@ -49,8 +55,10 @@ class UserLocation(TypedDict, total=False): __all__ = ( "tool_web_search", "tool_web_fetch", + "tool_code_execution", "ToolWebSearch", "ToolWebFetch", + "ToolCodeExecution", ) @@ -643,3 +651,189 @@ async def main(): blocked_domains=blocked_domains, max_uses=max_uses, ) + + +class ToolCodeExecution(ToolBuiltIn): + """ + A provider-agnostic code execution tool configuration. + + This class stores configuration for server-side code execution + functionality. Each provider translates this configuration into their + specific API format. + """ + + def __init__(self): + _annotations: ToolAnnotations = { + "title": "Code execution", + "readOnlyHint": False, + "openWorldHint": False, + } + super().__init__( + name="code_execution", + definition={}, + description="Execute code in a sandboxed environment.", + annotations=_annotations, + ) + + @overload + def get_definition( + self, + provider_name: Literal["openai"], + *, + container_id: Optional[str] = None, + ) -> "CodeInterpreter": ... + + @overload + def get_definition( + self, + provider_name: Literal["anthropic"], + ) -> "BetaCodeExecutionTool20250522Param": ... + + @overload + def get_definition( + self, + provider_name: Literal["google"], + ) -> "GoogleToolCodeExecution": ... + + def get_definition( + self, + provider_name: Literal["openai", "anthropic", "google"], + *, + # OpenAI-specific + container_id: Optional[str] = None, + ) -> ( + "CodeInterpreter | BetaCodeExecutionTool20250522Param | GoogleToolCodeExecution" + ): + """ + Get the provider-specific tool definition. + + Parameters + ---------- + provider_name + The name of the provider ('openai', 'anthropic', or 'google'). + container_id + OpenAI only. Reuse an existing sandbox container instead of + starting a fresh one. `chatlas.Chat` sets this automatically + from prior turns; you shouldn't need to pass it yourself. + + Returns + ------- + : + The provider-specific tool definition. + """ + if provider_name == "openai": + return self._openai_definition(container_id=container_id) + elif provider_name == "anthropic": + return self._anthropic_definition() + elif provider_name == "google": + return self._google_definition() + else: + raise ValueError( + f"Code execution is not supported for provider '{provider_name}'. " + "Supported providers: openai, anthropic, google." + ) + + @staticmethod + def _openai_definition(*, container_id: Optional[str] = None) -> "CodeInterpreter": + """Generate OpenAI code interpreter tool definition.""" + # https://platform.openai.com/docs/guides/tools-code-interpreter + container = container_id or {"type": "auto"} + # dict[str, str] doesn't structurally satisfy CodeInterpreter's container union + return {"type": "code_interpreter", "container": container} # type: ignore + + @staticmethod + def _anthropic_definition() -> "BetaCodeExecutionTool20250522Param": + """Generate Anthropic/Claude code execution tool definition.""" + # https://docs.claude.com/en/docs/agents-and-tools/tool-use/code-execution-tool + return { + "name": "code_execution", + "type": "code_execution_20250522", + } + + @staticmethod + def _google_definition() -> "GoogleToolCodeExecution": + """Generate Google/Gemini code execution tool definition.""" + # https://ai.google.dev/gemini-api/docs/code-execution + from google.genai.types import ToolCodeExecution as GoogleToolCodeExecution + + return GoogleToolCodeExecution() + + +def tool_code_execution() -> ToolCodeExecution: + """ + Create a code execution tool for use with chat models. + + This function creates a provider-agnostic code execution tool that can be + registered with any supported chat provider. The tool allows the model to + write and run code in a sandboxed environment and see the result -- e.g. + for math, data analysis, or verifying its own logic. + + Supported providers: OpenAI, Claude (Anthropic), Google (Gemini) + + Prerequisites + ------------- + - **OpenAI**: Code execution is available by default with the Responses API. + - **Claude**: The code execution tool requires the beta header + `anthropic-beta: code-execution-2025-05-22`. Pass this via the `kwargs` + parameter's `default_headers` option (see examples below). + - **Google**: Code execution is available by default with Gemini. + + Returns + ------- + ToolCodeExecution + A code execution tool that can be registered with `chat.register_tool()`. + + Examples + -------- + ```python + from chatlas import ChatOpenAI, tool_code_execution + + chat = ChatOpenAI() + chat.register_tool(tool_code_execution()) + chat.chat("What's the 20th Fibonacci number?") + ``` + + ```python + from chatlas import ChatAnthropic, tool_code_execution + + chat = ChatAnthropic( + kwargs={"default_headers": {"anthropic-beta": "code-execution-2025-05-22"}} + ) + chat.register_tool(tool_code_execution()) + chat.chat("What's the 20th Fibonacci number?") + ``` + + Note + ---- + Only text output (stdout/stderr, or each provider's equivalent) is + surfaced as structured content. Files the code produces (e.g. plots, + CSVs) aren't downloaded or decoded -- their raw provider references are + still available via each content's `extra` attribute. + + OpenAI reuses the same sandbox across turns in a conversation + automatically, so a variable defined in one turn is still available in + the next. Claude also reuses the same container across turns, but its + code execution tool does not persist Python interpreter state (e.g. + variables) between separate executions -- each execution starts fresh. + Google starts a fresh sandbox on every turn. + """ + return ToolCodeExecution() + + +def last_code_execution_container_id(turns: Sequence["Turn"]) -> Optional[str]: + """ + Find the most recent code execution container/sandbox id in turn history. + + Providers that support session reuse (OpenAI, Anthropic) key off this to + reuse the same sandbox across turns instead of starting a fresh one. + """ + for turn in reversed(turns): + if not isinstance(turn, AssistantTurn): + continue + for content in reversed(turn.contents): + if ( + isinstance(content, ContentToolResponseCodeExecution) + and content.container_id + ): + return content.container_id + return None diff --git a/chatlas/types/__init__.py b/chatlas/types/__init__.py index a6c5e580..408a0724 100644 --- a/chatlas/types/__init__.py +++ b/chatlas/types/__init__.py @@ -14,8 +14,10 @@ ContentThinking, ContentThinkingDelta, ContentToolRequest, + ContentToolRequestCodeExecution, ContentToolRequestFetch, ContentToolRequestSearch, + ContentToolResponseCodeExecution, ContentToolResponseFetch, ContentToolResponseSearch, ContentToolResult, @@ -46,6 +48,8 @@ "ContentToolRequestSearch", "ContentToolResponseSearch", "FinishReason", + "ContentToolRequestCodeExecution", + "ContentToolResponseCodeExecution", "StructuredChatResult", "ChatResponse", "ChatResponseAsync", diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 61b43e3a..d45e8cb0 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -184,10 +184,11 @@ quartodoc: - Tool - ToolRejectError - title: Built-in tools - desc: Provider-agnostic access to built-in web search and fetch capabilities. + desc: Provider-agnostic access to built-in web search, fetch, and code execution capabilities. contents: - tool_web_search - tool_web_fetch + - tool_code_execution - title: Parallel and batch chat desc: Submit multiple chats in parallel (fast) or one batch (cheap) contents: diff --git a/docs/get-started/tools.qmd b/docs/get-started/tools.qmd index a4e2f3ee..73114436 100644 --- a/docs/get-started/tools.qmd +++ b/docs/get-started/tools.qmd @@ -110,6 +110,31 @@ See the [MCP tools guide](../misc/mcp-tools.qmd) for more information. ::: +### Code execution + +Some providers can run code for you in a server-side sandbox -- useful for +math, data analysis, or letting the model verify its own logic. Register it +the same way as web search or fetch: + +```python +from chatlas import ChatAnthropic, tool_code_execution + +chat = ChatAnthropic( + kwargs={"default_headers": {"anthropic-beta": "code-execution-2025-05-22"}} +) +chat.register_tool(tool_code_execution()) +chat.chat("What's the 20th Fibonacci number?") +``` + +Supported providers: OpenAI, Claude (Anthropic), Google (Gemini). Only text +output (stdout/stderr) is surfaced -- files the code produces (e.g. plots) +aren't downloaded automatically. OpenAI reuses the same sandbox across turns +in a conversation, so state (like a variable) persists. Claude also reuses +the same container across turns, but doesn't persist Python interpreter +state between executions -- each execution starts fresh. Google starts a +fresh sandbox every turn too. + + ### Tool errors When a tool function is called, it may fail for various reasons, such as network issues, invalid input, or unexpected exceptions. diff --git a/tests/_vcr/test_provider_anthropic/test_anthropic_code_execution.yaml b/tests/_vcr/test_provider_anthropic/test_anthropic_code_execution.yaml new file mode 100644 index 00000000..e2fe24cf --- /dev/null +++ b/tests/_vcr/test_provider_anthropic/test_anthropic_code_execution.yaml @@ -0,0 +1,175 @@ +interactions: +- request: + body: '{"max_tokens": 4096, "messages": [{"role": "user", "content": [{"text": + "Use code execution to compute the 20th Fibonacci number (0-indexed, so fib(0) + = 0, fib(1) = 1). Just give me the number.", "type": "text", "cache_control": + {"type": "ephemeral", "ttl": "5m"}}]}], "model": "claude-sonnet-4-6", "stream": + true, "tools": [{"name": "code_execution", "type": "code_execution_20250522"}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + anthropic-beta: + - code-execution-2025-05-22 + anthropic-version: + - '2023-06-01' + connection: + - keep-alive + content-length: + - '365' + content-type: + - application/json + host: + - api.anthropic.com + x-stainless-async: + - 'false' + x-stainless-read-timeout: + - '600' + x-stainless-timeout: + - NOT_GIVEN + method: POST + uri: https://api.anthropic.com/v1/messages + response: + body: + string: 'event: message_start + + data: {"type":"message_start","message":{"model":"claude-sonnet-4-6","id":"msg_011CdFxG4dasFq1CME8FgDso","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"stop_details":null,"usage":{"input_tokens":970,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":65,"service_tier":"standard","inference_geo":"global"}} } + + + event: content_block_start + + data: {"type":"content_block_start","index":0,"content_block":{"type":"server_tool_use","id":"srvtoolu_018dAxW3UzAfH8W1pSrwuhbD","name":"code_execution","input":{}} } + + + event: ping + + data: {"type": "ping"} + + + event: content_block_delta + + data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""} } + + + event: content_block_delta + + data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\"code\": + \"\\ndef fib(n):\\n a, b = 0, 1\\n for _ in range(n):\\n a, b + = b"} } + + + event: content_block_delta + + data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":", + a + b\\n return a\\n\\nprint(fib(20))\\n"} } + + + event: content_block_delta + + data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"\"}"} } + + + event: content_block_stop + + data: {"type":"content_block_stop","index":0 } + + + event: content_block_start + + data: {"type":"content_block_start","index":1,"content_block":{"type":"code_execution_tool_result","tool_use_id":"srvtoolu_018dAxW3UzAfH8W1pSrwuhbD","content":{"type":"code_execution_result","stdout":"6765","stderr":"","return_code":0,"content":[],"abort_reason":null}} } + + + event: content_block_stop + + data: {"type":"content_block_stop","index":1 } + + + event: content_block_start + + data: {"type":"content_block_start","index":2,"content_block":{"type":"text","text":""}} + + + event: content_block_delta + + data: {"type":"content_block_delta","index":2,"delta":{"type":"text_delta","text":"The + 20th Fibonacci number is"} } + + + event: content_block_delta + + data: {"type":"content_block_delta","index":2,"delta":{"type":"text_delta","text":" + **6765**."} } + + + event: content_block_stop + + data: {"type":"content_block_stop","index":2 } + + + event: message_delta + + data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"container":{"id":"container_01BRzMk5ucXoE1VG3XWHRPfK","expires_at":"2026-07-21T22:59:36.338608Z"}},"usage":{"input_tokens":971,"cache_creation_input_tokens":1091,"cache_read_input_tokens":0,"output_tokens":119,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0}} } + + + event: message_stop + + data: {"type":"message_stop" } + + + ' + headers: + anthropic-ratelimit-input-tokens-limit: + - '10000000' + anthropic-ratelimit-input-tokens-remaining: + - '9999000' + anthropic-ratelimit-input-tokens-reset: + - '2026-07-21T21:59:28Z' + anthropic-ratelimit-output-tokens-limit: + - '2000000' + anthropic-ratelimit-output-tokens-remaining: + - '2000000' + anthropic-ratelimit-output-tokens-reset: + - '2026-07-21T21:59:28Z' + anthropic-ratelimit-requests-limit: + - '20000' + anthropic-ratelimit-requests-remaining: + - '19999' + anthropic-ratelimit-requests-reset: + - '2026-07-21T21:59:28Z' + anthropic-ratelimit-tokens-limit: + - '12000000' + anthropic-ratelimit-tokens-remaining: + - '11999000' + anthropic-ratelimit-tokens-reset: + - '2026-07-21T21:59:28Z' + cache-control: + - no-cache + cf-cache-status: + - DYNAMIC + connection: + - keep-alive + content-length: + - '2824' + content-security-policy: + - default-src 'none'; frame-ancestors 'none' + content-type: + - text/event-stream; charset=utf-8 + date: + - Tue, 21 Jul 2026 21:59:29 GMT + server: + - cloudflare + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + traceresponse: + - 00-aa7c3fc4448e6f4cf3c9e8f1208d2cfb-49d9bcbc3347ab44-01 + transfer-encoding: + - chunked + vary: + - Accept-Encoding + x-robots-tag: + - none + status: + code: 200 + message: OK +version: 1 diff --git a/tests/_vcr/test_provider_google/test_google_code_execution.yaml b/tests/_vcr/test_provider_google/test_google_code_execution.yaml new file mode 100644 index 00000000..556c1e83 --- /dev/null +++ b/tests/_vcr/test_provider_google/test_google_code_execution.yaml @@ -0,0 +1,80 @@ +interactions: +- request: + body: '{"contents": [{"parts": [{"text": "Use code execution to compute the 20th + Fibonacci number (0-indexed, so fib(0) = 0, fib(1) = 1). Just give me the number."}], + "role": "user"}], "tools": [{"codeExecution": {}}], "generationConfig": {"temperature": + 0}}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '251' + content-type: + - application/json + host: + - generativelanguage.googleapis.com + x-goog-api-client: + - google-genai-sdk/2.13.0 gl-python/3.12.13 + method: POST + uri: https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-flash:streamGenerateContent?alt=sse + response: + body: + string: "data: {\"candidates\": [{\"content\": {\"parts\": [{\"executableCode\": + {\"language\": \"PYTHON\",\"code\": \"def fib(n):\\n a, b = 0, 1\\n for + _ in range(n):\\n a, b = b, a + b\\n return a\\n\\nprint(fib(20))\\n\",\"id\": + \"2ydsz5r8\"},\"thoughtSignature\": \"EtEDCs4DARFNMg9yXq63pfAU+5EIHV1gUziVmbRPItM89YwjikWVR+GszBzKz0NBHKG6dfDNmNYOlcsGgMACcXqhbj/t6pLQ6eAEe1a8BzO0AavIIP37l6lsap9ihFyDLtgnPx/sHCcSG/5efbQZigZZc+bGZVcymO3JSO2Y4KUx8ZUdVVcpY8wFJs4U1qa8v4UVReoTvdfnyBQVGqnHakAfrh6soDHsX0jEsm+LGU+n6mdFlS54KT+jUgwtbAvxy1p1p9Oc83LshluQqZSTIdF6930VrAJMA+a50nXx3g68LrIQ2glKl/rDdzupfNC/U0nUryFUAHYPlE9wwVDptLBZEMmm4iBBzu3cPxxtsOQ35neqB7tZrhZ2n/gNwx9BgUTmiWtpnsMK8Xv80N571ikBeXruGxoaUWiMJctm8BdP6DRxo9b/o9M5WzKS7Fw+j+nlGbH35lDG7wXxNMLpPDUPW6aZqSxLNxS6FoylUn+J1L+n66CDJFSRM+YQHKjja7XexCBsH0uqyVTwhsn2xeDRihhBTBabGAdOJs+pr7HFTtLCtbKp4YJHLZj34xZ2Ch1lkWC6VJ3Kfjoi85vudBypP7pzuvxf57GxqdEz8UrmFojP\"}],\"role\": + \"model\"},\"index\": 0}],\"usageMetadata\": {\"promptTokenCount\": 41,\"candidatesTokenCount\": + 62,\"totalTokenCount\": 219,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 41}],\"thoughtsTokenCount\": 116,\"serviceTier\": \"standard\"},\"modelVersion\": + \"gemini-3.5-flash\",\"responseId\": \"F-1fapjUPPzN-8YPr4a9mQ8\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"codeExecutionResult\": {\"outcome\": + \"OUTCOME_OK\",\"output\": \"6765\\n\",\"id\": \"2ydsz5r8\"}}],\"role\": \"model\"}}],\"usageMetadata\": + {\"promptTokenCount\": 41,\"totalTokenCount\": 41,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 41}],\"serviceTier\": \"standard\"},\"modelVersion\": + \"gemini-3.5-flash\",\"responseId\": \"F-1fapjUPPzN-8YPr4a9mQ8\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"6765\"}],\"role\": + \"model\"},\"index\": 0}],\"usageMetadata\": {\"promptTokenCount\": 41,\"candidatesTokenCount\": + 4,\"totalTokenCount\": 82,\"promptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 41}],\"thoughtsTokenCount\": 37,\"serviceTier\": \"standard\"},\"modelVersion\": + \"gemini-3.5-flash\",\"responseId\": \"F-1fapjUPPzN-8YPr4a9mQ8\"}\r\n\r\ndata: + {\"candidates\": [{\"content\": {\"parts\": [{\"text\": \"\",\"thoughtSignature\": + \"Er8BCrwBARFNMg+sMxy77lRN7eSUv8M2cz7iRPN8mgg/5TNPkYX5gp+mG0Y04XPSfSOs3v6p/0W6wmvbCq8ZEg/f0Q/57f5P5bPIhDoIOo9sIdTZSzBf55lu6gQLsPbF5AJQBGdD4Q+NgR+SAt9Zrb/3hTBjkhFDAU3qlwLlygeS84fYD+8ESgKch32f3Pu4tkkfBK067o/faYQSOoxV45AC+qiRqcjHxPXOkcQqwBnL0rk+rG3M55v4WnuZbdRkGmI=\"}],\"role\": + \"model\"},\"finishReason\": \"STOP\",\"index\": 0}],\"usageMetadata\": {\"promptTokenCount\": + 157,\"candidatesTokenCount\": 66,\"totalTokenCount\": 598,\"promptTokensDetails\": + [{\"modality\": \"TEXT\",\"tokenCount\": 157}],\"toolUsePromptTokenCount\": + 222,\"toolUsePromptTokensDetails\": [{\"modality\": \"TEXT\",\"tokenCount\": + 222}],\"thoughtsTokenCount\": 153,\"serviceTier\": \"standard\"},\"modelVersion\": + \"gemini-3.5-flash\",\"responseId\": \"F-1fapjUPPzN-8YPr4a9mQ8\"}\r\n\r\n" + headers: + alt-svc: + - h3=":443"; ma=2592000,h3-29=":443"; ma=2592000 + content-disposition: + - attachment + content-type: + - text/event-stream + date: + - Tue, 21 Jul 2026 22:05:13 GMT + server: + - scaffolding on HTTPServer2 + server-timing: + - gfet4t7; dur=1306 + transfer-encoding: + - chunked + vary: + - Origin + - X-Origin + - Referer + x-content-type-options: + - nosniff + x-frame-options: + - SAMEORIGIN + x-xss-protection: + - '0' + status: + code: 200 + message: OK +version: 1 diff --git a/tests/_vcr/test_provider_openai/test_openai_code_execution.yaml b/tests/_vcr/test_provider_openai/test_openai_code_execution.yaml new file mode 100644 index 00000000..7bffe5df --- /dev/null +++ b/tests/_vcr/test_provider_openai/test_openai_code_execution.yaml @@ -0,0 +1,472 @@ +interactions: +- request: + body: '{"input": [{"role": "user", "content": [{"type": "input_text", "text": + "Use code execution to compute the 20th Fibonacci number (0-indexed, so fib(0) + = 0, fib(1) = 1). Just give me the number."}]}], "model": "gpt-4.1", "store": + false, "stream": true, "tools": [{"type": "code_interpreter", "container": {"type": + "auto"}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '303' + content-type: + - application/json + host: + - api.openai.com + x-stainless-async: + - 'false' + x-stainless-read-timeout: + - '600' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: 'event: response.created + + data: {"type":"response.created","response":{"id":"resp_050aaa8793a4eddc016a5fea29a7988194b29400d594354572","object":"response","created_at":1784670761,"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-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":{"type":"auto"}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + + event: response.in_progress + + data: {"type":"response.in_progress","response":{"id":"resp_050aaa8793a4eddc016a5fea29a7988194b29400d594354572","object":"response","created_at":1784670761,"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-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":{"type":"auto"}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","item":{"id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","type":"code_interpreter_call","status":"in_progress","code":"","container_id":"cntr_6a5fea2aff5c8193a1065b8c97a102160cefcd69266ffa1c","outputs":null},"output_index":0,"sequence_number":2} + + + event: response.code_interpreter_call.in_progress + + data: {"type":"response.code_interpreter_call.in_progress","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","output_index":0,"sequence_number":3} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"def","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"kRM4LyFV6qeUv","output_index":0,"sequence_number":4} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" fib","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"fe8sWC7fDW45","output_index":0,"sequence_number":5} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"(n","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"2wca7B7uZEItSl","output_index":0,"sequence_number":6} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"):\r\n","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"ZDx4UP4pchSQ","output_index":0,"sequence_number":7} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"VSWqjSvEe6PMM","output_index":0,"sequence_number":8} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" a","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"vP4OnNncePl94A","output_index":0,"sequence_number":9} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":",","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"hfFxh7XJF1pOVXz","output_index":0,"sequence_number":10} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" b","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"o2dDxqMydCip9M","output_index":0,"sequence_number":11} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" =","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"9SA7XKY4BRuCN7","output_index":0,"sequence_number":12} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"epeRdmvuvwDbcLl","output_index":0,"sequence_number":13} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"0","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"gNawHfa2gKzhsnY","output_index":0,"sequence_number":14} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":",","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"6USMYJFqXToDDno","output_index":0,"sequence_number":15} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"ipMSRiKrpaHPwl0","output_index":0,"sequence_number":16} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"1","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"HX2ZHSh3jO1rVlm","output_index":0,"sequence_number":17} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"\r\n","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"iDObfsgVnNY967","output_index":0,"sequence_number":18} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"bnVFGjrBhvXUe","output_index":0,"sequence_number":19} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" for","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"AmB4tU3FaEwz","output_index":0,"sequence_number":20} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" _","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"yrtwYE7YtkZqx4","output_index":0,"sequence_number":21} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" in","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"4pBUv5NHRQfcm","output_index":0,"sequence_number":22} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" range","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"iFywOIcNQK","output_index":0,"sequence_number":23} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"(n","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"J2RqWrfGTzRn33","output_index":0,"sequence_number":24} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"):\r\n","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"4ylQrSA8j80v","output_index":0,"sequence_number":25} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"bjqbciwZC","output_index":0,"sequence_number":26} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" a","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"nwBGIEHvy86fN6","output_index":0,"sequence_number":27} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":",","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"CChxTJw4svAkYDV","output_index":0,"sequence_number":28} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" b","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"0s4Mn31khjq9UF","output_index":0,"sequence_number":29} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" =","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"fiwcTEhvPmtd97","output_index":0,"sequence_number":30} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" b","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"vq8Wdv3Qe8Fuve","output_index":0,"sequence_number":31} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":",","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"MQof3HLPYGM2Wwz","output_index":0,"sequence_number":32} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" a","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"zpHV8y2ncbMum0","output_index":0,"sequence_number":33} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" +","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"tYP1qoxChCq40P","output_index":0,"sequence_number":34} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" b","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"svO0zOmGtozLtS","output_index":0,"sequence_number":35} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"\r\n","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"Vj0FMOCXiXTlhv","output_index":0,"sequence_number":36} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"4qTAqBQnzA0GX","output_index":0,"sequence_number":37} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" return","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"Q0drPtMPo","output_index":0,"sequence_number":38} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" a","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"wGxeVkSFmyyKAd","output_index":0,"sequence_number":39} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"\r\n\r\n","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"LcUz4FUCUT3x","output_index":0,"sequence_number":40} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"fib","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"qIUXL9NOSdZnH","output_index":0,"sequence_number":41} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"_","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"EPScMkbXAStT6xD","output_index":0,"sequence_number":42} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"20","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"OvHnNPApV2Gxwk","output_index":0,"sequence_number":43} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" =","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"5yGOcA2IH2xZNv","output_index":0,"sequence_number":44} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" fib","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"uf7faDr2sXk7","output_index":0,"sequence_number":45} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"(","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"jb5T433BNb3se3J","output_index":0,"sequence_number":46} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"20","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"xQGiWd02oSaRxy","output_index":0,"sequence_number":47} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":")\r\n","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"XMR9S384Jdnjw","output_index":0,"sequence_number":48} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"fib","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"a9wymtHSPQ2NC","output_index":0,"sequence_number":49} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"_","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"28gv1aaselBGhZv","output_index":0,"sequence_number":50} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"20","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","obfuscation":"WstJbkyJvcZ9q4","output_index":0,"sequence_number":51} + + + event: response.code_interpreter_call_code.done + + data: {"type":"response.code_interpreter_call_code.done","code":"def fib(n):\r\n a, + b = 0, 1\r\n for _ in range(n):\r\n a, b = b, a + b\r\n return + a\r\n\r\nfib_20 = fib(20)\r\nfib_20","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","output_index":0,"sequence_number":52} + + + event: response.code_interpreter_call.interpreting + + data: {"type":"response.code_interpreter_call.interpreting","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","output_index":0,"sequence_number":53} + + + event: response.code_interpreter_call.completed + + data: {"type":"response.code_interpreter_call.completed","item_id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","output_index":0,"sequence_number":54} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","item":{"id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","type":"code_interpreter_call","status":"completed","code":"def + fib(n):\r\n a, b = 0, 1\r\n for _ in range(n):\r\n a, b = b, + a + b\r\n return a\r\n\r\nfib_20 = fib(20)\r\nfib_20","container_id":"cntr_6a5fea2aff5c8193a1065b8c97a102160cefcd69266ffa1c","outputs":null},"output_index":0,"sequence_number":55} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","item":{"id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":1,"sequence_number":56} + + + event: response.content_part.added + + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":57} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"B31oPGxZma4BA","output_index":1,"sequence_number":58} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"Ej7MjFW0jzcysH5","output_index":1,"sequence_number":59} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"20","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"G8mLU9zXiPgkMP","output_index":1,"sequence_number":60} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"th","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"qMwCiACkYlAKmF","output_index":1,"sequence_number":61} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" Fibonacci","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"XJc5FN","output_index":1,"sequence_number":62} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" number","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"7pjY71ZXK","output_index":1,"sequence_number":63} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" (","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"VLCfOyJjxHu6D3","output_index":1,"sequence_number":64} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"0","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"zpHkvQvWBKELgkk","output_index":1,"sequence_number":65} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"-index","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"HcAhqsIBL9","output_index":1,"sequence_number":66} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"ed","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"vtE9neD9rdvAkO","output_index":1,"sequence_number":67} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":")","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"TCufQBYnYU5nyrz","output_index":1,"sequence_number":68} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" is","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"5xBB7vmssQsym","output_index":1,"sequence_number":69} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"8GAXleuAAG4kbsn","output_index":1,"sequence_number":70} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"676","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"FWjUWtduHTRUl","output_index":1,"sequence_number":71} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"5","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"JpyTnUyXeFNref8","output_index":1,"sequence_number":72} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"obfuscation":"cwkKBPhQ8XMlsfb","output_index":1,"sequence_number":73} + + + event: response.output_text.done + + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","logprobs":[],"output_index":1,"sequence_number":74,"text":"The + 20th Fibonacci number (0-indexed) is 6765."} + + + event: response.content_part.done + + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"The + 20th Fibonacci number (0-indexed) is 6765."},"sequence_number":75} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","item":{"id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The + 20th Fibonacci number (0-indexed) is 6765."}],"role":"assistant"},"output_index":1,"sequence_number":76} + + + event: response.completed + + data: {"type":"response.completed","response":{"id":"resp_050aaa8793a4eddc016a5fea29a7988194b29400d594354572","object":"response","created_at":1784670761,"status":"completed","background":false,"completed_at":1784670765,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"ci_050aaa8793a4eddc016a5fea2b88d8819480b746e8a50a7eec","type":"code_interpreter_call","status":"completed","code":"def + fib(n):\r\n a, b = 0, 1\r\n for _ in range(n):\r\n a, b = b, + a + b\r\n return a\r\n\r\nfib_20 = fib(20)\r\nfib_20","container_id":"cntr_6a5fea2aff5c8193a1065b8c97a102160cefcd69266ffa1c","outputs":null},{"id":"msg_050aaa8793a4eddc016a5fea2d2ee88194b6955808c3796652","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The + 20th Fibonacci number (0-indexed) is 6765."}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":{"type":"auto"}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":266,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":72,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":338},"user":null,"metadata":{}},"sequence_number":77} + + + ' + headers: + access-control-expose-headers: + - X-Request-ID + - CF-Ray + - CF-Ray + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + connection: + - keep-alive + content-type: + - text/event-stream; charset=utf-8 + date: + - Tue, 21 Jul 2026 21:52:43 GMT + openai-processing-ms: + - '1738' + openai-version: + - '2020-10-01' + server: + - cloudflare + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999779' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + status: + code: 200 + message: OK +version: 1 diff --git a/tests/_vcr/test_provider_openai/test_openai_code_execution_persistence.yaml b/tests/_vcr/test_provider_openai/test_openai_code_execution_persistence.yaml new file mode 100644 index 00000000..99b2f16b --- /dev/null +++ b/tests/_vcr/test_provider_openai/test_openai_code_execution_persistence.yaml @@ -0,0 +1,761 @@ +interactions: +- request: + body: '{"input": [{"role": "user", "content": [{"type": "input_text", "text": + "Use code execution to define a variable `x = 42`. Don''t print anything yet."}]}], + "model": "gpt-4.1", "store": false, "stream": true, "tools": [{"type": "code_interpreter", + "container": {"type": "auto"}}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '258' + content-type: + - application/json + host: + - api.openai.com + x-stainless-async: + - 'false' + x-stainless-read-timeout: + - '600' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: 'event: response.created + + data: {"type":"response.created","response":{"id":"resp_0d4ae9317fe2724b016a5fea2e253c8195af712eca877e120b","object":"response","created_at":1784670766,"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-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":{"type":"auto"}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + + event: response.in_progress + + data: {"type":"response.in_progress","response":{"id":"resp_0d4ae9317fe2724b016a5fea2e253c8195af712eca877e120b","object":"response","created_at":1784670766,"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-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":{"type":"auto"}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","item":{"id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","type":"code_interpreter_call","status":"in_progress","code":"","container_id":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272","outputs":null},"output_index":0,"sequence_number":2} + + + event: response.code_interpreter_call.in_progress + + data: {"type":"response.code_interpreter_call.in_progress","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","output_index":0,"sequence_number":3} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"#","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"d4QWvppFEp2cy59","output_index":0,"sequence_number":4} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" Def","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"szoCNXqqycjW","output_index":0,"sequence_number":5} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"ining","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"O5PHfDS3f4e","output_index":0,"sequence_number":6} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" the","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"Ed1UhrjYDmvp","output_index":0,"sequence_number":7} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" variable","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"M8b1lSG","output_index":0,"sequence_number":8} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" as","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"ENxcdF0u0eWLl","output_index":0,"sequence_number":9} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" instructed","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"r0sxv","output_index":0,"sequence_number":10} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"\n","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"7iGFNLiXXb44dDc","output_index":0,"sequence_number":11} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"x","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"GKOcUqm68BHE4dZ","output_index":0,"sequence_number":12} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" =","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"Ie0OfBOotxrXYR","output_index":0,"sequence_number":13} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"bMcKcl8JcFJZGOs","output_index":0,"sequence_number":14} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"42","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","obfuscation":"JWQsndjFwUYGC3","output_index":0,"sequence_number":15} + + + event: response.code_interpreter_call_code.done + + data: {"type":"response.code_interpreter_call_code.done","code":"# Defining + the variable as instructed\nx = 42","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","output_index":0,"sequence_number":16} + + + event: response.code_interpreter_call.interpreting + + data: {"type":"response.code_interpreter_call.interpreting","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","output_index":0,"sequence_number":17} + + + event: response.code_interpreter_call.completed + + data: {"type":"response.code_interpreter_call.completed","item_id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","output_index":0,"sequence_number":18} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","item":{"id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","type":"code_interpreter_call","status":"completed","code":"# + Defining the variable as instructed\nx = 42","container_id":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272","outputs":null},"output_index":0,"sequence_number":19} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","item":{"id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":1,"sequence_number":20} + + + event: response.content_part.added + + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":21} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"PWsdZS1zbFmZQ","output_index":1,"sequence_number":22} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" variable","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"l4bEdt8","output_index":1,"sequence_number":23} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" `","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"i14yOm4qTHwO8o","output_index":1,"sequence_number":24} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"x","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"lo0uUywkpTpR303","output_index":1,"sequence_number":25} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"`","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"lbUipnflWmXscV7","output_index":1,"sequence_number":26} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" has","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"MCiW62a79hBF","output_index":1,"sequence_number":27} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" been","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"Vhw5i3K7AdG","output_index":1,"sequence_number":28} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" defined","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"prLU1f64","output_index":1,"sequence_number":29} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" and","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"u7vU65Ui71LI","output_index":1,"sequence_number":30} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" set","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"nxO8lEEUw2rE","output_index":1,"sequence_number":31} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"snmGymlnDGs6s","output_index":1,"sequence_number":32} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"w3E1aiPMyAQm61z","output_index":1,"sequence_number":33} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"42","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"Zdame3gFBmtwGX","output_index":1,"sequence_number":34} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" as","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"UTEgrAYxuv7rQ","output_index":1,"sequence_number":35} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" requested","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"wALMLX","output_index":1,"sequence_number":36} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"SFfpsZeiVO8rtNa","output_index":1,"sequence_number":37} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" No","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"CDERnkjawdB0U","output_index":1,"sequence_number":38} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" output","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"7fMH40nW2","output_index":1,"sequence_number":39} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" has","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"wniTO0pvrCgF","output_index":1,"sequence_number":40} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" been","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"eAgowXJjC01","output_index":1,"sequence_number":41} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" printed","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"cTyG0CQY","output_index":1,"sequence_number":42} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"6X1iyhkvwoTR5xM","output_index":1,"sequence_number":43} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" If","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"CgSuqJ4LpU1PB","output_index":1,"sequence_number":44} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" you","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"L1YfwpmMnAju","output_index":1,"sequence_number":45} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" need","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"Bj8OKkyo2XY","output_index":1,"sequence_number":46} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" to","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"dz09hh0YVOrcd","output_index":1,"sequence_number":47} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" use","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"7QZAofP77I6w","output_index":1,"sequence_number":48} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" or","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"yeKx2vVQ4KQtL","output_index":1,"sequence_number":49} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" display","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"cD3oeb1R","output_index":1,"sequence_number":50} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" this","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"5zliBMwkWLH","output_index":1,"sequence_number":51} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" variable","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"1dHq2fC","output_index":1,"sequence_number":52} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"OtY2Jjy5sS9m7uR","output_index":1,"sequence_number":53} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" please","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"xrzi72B5U","output_index":1,"sequence_number":54} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" let","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"TXiGSWSQHDqA","output_index":1,"sequence_number":55} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" me","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"gRgonpTGjg8z2","output_index":1,"sequence_number":56} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" know","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"4MMjvwYzBfh","output_index":1,"sequence_number":57} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" your","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"gOcAxSoz7zR","output_index":1,"sequence_number":58} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" next","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"LFB04ZR8Pfc","output_index":1,"sequence_number":59} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" step","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"2IdtLG8pc9X","output_index":1,"sequence_number":60} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"!","item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"obfuscation":"LSeylVARYa1infh","output_index":1,"sequence_number":61} + + + event: response.output_text.done + + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","logprobs":[],"output_index":1,"sequence_number":62,"text":"The + variable `x` has been defined and set to 42 as requested. No output has been + printed. If you need to use or display this variable, please let me know your + next step!"} + + + event: response.content_part.done + + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"The + variable `x` has been defined and set to 42 as requested. No output has been + printed. If you need to use or display this variable, please let me know your + next step!"},"sequence_number":63} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","item":{"id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The + variable `x` has been defined and set to 42 as requested. No output has been + printed. If you need to use or display this variable, please let me know your + next step!"}],"role":"assistant"},"output_index":1,"sequence_number":64} + + + event: response.completed + + data: {"type":"response.completed","response":{"id":"resp_0d4ae9317fe2724b016a5fea2e253c8195af712eca877e120b","object":"response","created_at":1784670766,"status":"completed","background":false,"completed_at":1784670769,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d","type":"code_interpreter_call","status":"completed","code":"# + Defining the variable as instructed\nx = 42","container_id":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272","outputs":null},{"id":"msg_0d4ae9317fe2724b016a5fea3122508195886023a6159ea907","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The + variable `x` has been defined and set to 42 as requested. No output has been + printed. If you need to use or display this variable, please let me know your + next step!"}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":{"type":"auto"}}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":207,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":60,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":267},"user":null,"metadata":{}},"sequence_number":65} + + + ' + headers: + access-control-expose-headers: + - X-Request-ID + - CF-Ray + - CF-Ray + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + connection: + - keep-alive + content-type: + - text/event-stream; charset=utf-8 + date: + - Tue, 21 Jul 2026 21:52:47 GMT + openai-processing-ms: + - '1426' + openai-version: + - '2020-10-01' + server: + - cloudflare + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999800' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + status: + code: 200 + message: OK +- request: + body: '{"input": [{"role": "user", "content": [{"type": "input_text", "text": + "Use code execution to define a variable `x = 42`. Don''t print anything yet."}]}, + {"id": "ci_0d4ae9317fe2724b016a5fea2fcc38819591480e57c25fa43d", "code": "# Defining + the variable as instructed\nx = 42", "container_id": "cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272", + "outputs": null, "status": "completed", "type": "code_interpreter_call"}, {"role": + "assistant", "content": [{"type": "output_text", "text": "The variable `x` has + been defined and set to 42 as requested. No output has been printed. If you + need to use or display this variable, please let me know your next step!", "annotations": + []}], "status": "completed", "type": "message", "id": "msg_missing_id"}, {"role": + "user", "content": [{"type": "input_text", "text": "Now use code execution to + print `x * 2`, reusing the same `x` from before -- don''t redefine it."}]}], + "model": "gpt-4.1", "store": false, "stream": true, "tools": [{"type": "code_interpreter", + "container": "cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272"}]}' + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate, zstd + connection: + - keep-alive + content-length: + - '1021' + content-type: + - application/json + host: + - api.openai.com + x-stainless-async: + - 'false' + x-stainless-read-timeout: + - '600' + method: POST + uri: https://api.openai.com/v1/responses + response: + body: + string: 'event: response.created + + data: {"type":"response.created","response":{"id":"resp_0d4ae9317fe2724b016a5fea31f17c8195b3036834d2f69bbc","object":"response","created_at":1784670770,"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-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272"}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":0} + + + event: response.in_progress + + data: {"type":"response.in_progress","response":{"id":"resp_0d4ae9317fe2724b016a5fea31f17c8195b3036834d2f69bbc","object":"response","created_at":1784670770,"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-4.1-2025-04-14","moderation":null,"output":[],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"auto","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272"}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":null,"user":null,"metadata":{}},"sequence_number":1} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","item":{"id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","type":"code_interpreter_call","status":"in_progress","code":"","container_id":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272","outputs":null},"output_index":0,"sequence_number":2} + + + event: response.code_interpreter_call.in_progress + + data: {"type":"response.code_interpreter_call.in_progress","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","output_index":0,"sequence_number":3} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"#","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"WrNZgsF7HRKYuKp","output_index":0,"sequence_number":4} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" Printing","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"vZ7N3oW","output_index":0,"sequence_number":5} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" x","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"SleZWvqYxtsgNZ","output_index":0,"sequence_number":6} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" *","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"6NCUrDSMBDUK2X","output_index":0,"sequence_number":7} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"hSmTi7th6CKoQ7e","output_index":0,"sequence_number":8} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"2","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"DRjlQl5MCuUWzvl","output_index":0,"sequence_number":9} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" using","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"lXwDD7QiMf","output_index":0,"sequence_number":10} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" the","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"EvLiwZQbJpdO","output_index":0,"sequence_number":11} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" previously","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"HHl8A","output_index":0,"sequence_number":12} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" defined","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"D2EWgMOS","output_index":0,"sequence_number":13} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" x","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"X6hMH4ZpdeI6F4","output_index":0,"sequence_number":14} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"\n","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"LLq8WfqTe7czPsr","output_index":0,"sequence_number":15} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"print","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"TXFh905h5nM","output_index":0,"sequence_number":16} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"(x","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"1XYU3w11DO14Rz","output_index":0,"sequence_number":17} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" *","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"XHGll7wW883wG5","output_index":0,"sequence_number":18} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":" ","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"DfDk3HJDkiKtpA5","output_index":0,"sequence_number":19} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":"2","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"yUWLH1xSSMWmViR","output_index":0,"sequence_number":20} + + + event: response.code_interpreter_call_code.delta + + data: {"type":"response.code_interpreter_call_code.delta","delta":")","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","obfuscation":"ooxuFb1mGvWMJ1A","output_index":0,"sequence_number":21} + + + event: response.code_interpreter_call_code.done + + data: {"type":"response.code_interpreter_call_code.done","code":"# Printing + x * 2 using the previously defined x\nprint(x * 2)","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","output_index":0,"sequence_number":22} + + + event: response.code_interpreter_call.interpreting + + data: {"type":"response.code_interpreter_call.interpreting","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","output_index":0,"sequence_number":23} + + + event: response.code_interpreter_call.completed + + data: {"type":"response.code_interpreter_call.completed","item_id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","output_index":0,"sequence_number":24} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","item":{"id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","type":"code_interpreter_call","status":"completed","code":"# + Printing x * 2 using the previously defined x\nprint(x * 2)","container_id":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272","outputs":null},"output_index":0,"sequence_number":25} + + + event: response.output_item.added + + data: {"type":"response.output_item.added","item":{"id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","type":"message","status":"in_progress","content":[],"role":"assistant"},"output_index":1,"sequence_number":26} + + + event: response.content_part.added + + data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":""},"sequence_number":27} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"The","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"9UIUUv0NHUJhT","output_index":1,"sequence_number":28} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" result","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"OGQTvIbod","output_index":1,"sequence_number":29} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" of","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"3cYQuXS0uLYTl","output_index":1,"sequence_number":30} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" `","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"2iG9cKfdkN1n44","output_index":1,"sequence_number":31} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"x","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"870aIDVrQlsKInh","output_index":1,"sequence_number":32} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" *","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"Gh0PjrKYBoAyaA","output_index":1,"sequence_number":33} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"P6w1iw8BnWPrvgV","output_index":1,"sequence_number":34} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"2","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"KbjqtsBvdxx0cJK","output_index":1,"sequence_number":35} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"`","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"ueV8kCL02hka8Qv","output_index":1,"sequence_number":36} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" using","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"z3t0mnfJ2M","output_index":1,"sequence_number":37} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" the","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"Ajr5ZbAdNbGD","output_index":1,"sequence_number":38} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" previously","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"dJOaz","output_index":1,"sequence_number":39} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" defined","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"hu84Jy3U","output_index":1,"sequence_number":40} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" `","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"l2ClVvh4dGO2Fv","output_index":1,"sequence_number":41} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"x","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"puuGjtqaYkdcTD9","output_index":1,"sequence_number":42} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"`","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"J6h6H0li4TjK3oK","output_index":1,"sequence_number":43} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" is","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"h1fp1AOm7Dp9I","output_index":1,"sequence_number":44} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":" ","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"9KGUXopLXojZHMc","output_index":1,"sequence_number":45} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":"84","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"wKQuk9twJDSJ2k","output_index":1,"sequence_number":46} + + + event: response.output_text.delta + + data: {"type":"response.output_text.delta","content_index":0,"delta":".","item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"obfuscation":"Q8fhCgABVCYeOOm","output_index":1,"sequence_number":47} + + + event: response.output_text.done + + data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","logprobs":[],"output_index":1,"sequence_number":48,"text":"The + result of `x * 2` using the previously defined `x` is 84."} + + + event: response.content_part.done + + data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","output_index":1,"part":{"type":"output_text","annotations":[],"logprobs":[],"text":"The + result of `x * 2` using the previously defined `x` is 84."},"sequence_number":49} + + + event: response.output_item.done + + data: {"type":"response.output_item.done","item":{"id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The + result of `x * 2` using the previously defined `x` is 84."}],"role":"assistant"},"output_index":1,"sequence_number":50} + + + event: response.completed + + data: {"type":"response.completed","response":{"id":"resp_0d4ae9317fe2724b016a5fea31f17c8195b3036834d2f69bbc","object":"response","created_at":1784670770,"status":"completed","background":false,"completed_at":1784670773,"error":null,"frequency_penalty":0.0,"incomplete_details":null,"instructions":null,"max_output_tokens":null,"max_tool_calls":null,"model":"gpt-4.1-2025-04-14","moderation":null,"output":[{"id":"ci_0d4ae9317fe2724b016a5fea33959881958ac3a10224a0921d","type":"code_interpreter_call","status":"completed","code":"# + Printing x * 2 using the previously defined x\nprint(x * 2)","container_id":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272","outputs":null},{"id":"msg_0d4ae9317fe2724b016a5fea35163c8195b20b1772770f8d60","type":"message","status":"completed","content":[{"type":"output_text","annotations":[],"logprobs":[],"text":"The + result of `x * 2` using the previously defined `x` is 84."}],"role":"assistant"}],"parallel_tool_calls":true,"presence_penalty":0.0,"previous_response_id":null,"prompt_cache_key":null,"prompt_cache_retention":"in_memory","reasoning":{"context":null,"effort":null,"summary":null},"safety_identifier":null,"service_tier":"default","store":false,"temperature":1.0,"text":{"format":{"type":"text"},"verbosity":"medium"},"tool_choice":"auto","tool_usage":{"image_gen":{"input_tokens":0,"input_tokens_details":{"image_tokens":0,"text_tokens":0},"output_tokens":0,"output_tokens_details":{"image_tokens":0,"text_tokens":0},"total_tokens":0},"web_search":{"num_requests":0}},"tools":[{"type":"code_interpreter","container":"cntr_6a5fea2f61108193b410afd5e492da560f7799a7edfbd272"}],"top_logprobs":0,"top_p":1.0,"truncation":"disabled","usage":{"input_tokens":313,"input_tokens_details":{"cache_write_tokens":0,"cached_tokens":0},"output_tokens":46,"output_tokens_details":{"reasoning_tokens":0},"total_tokens":359},"user":null,"metadata":{}},"sequence_number":51} + + + ' + headers: + access-control-expose-headers: + - X-Request-ID + - CF-Ray + - CF-Ray + alt-svc: + - h3=":443"; ma=86400 + cf-cache-status: + - DYNAMIC + connection: + - keep-alive + content-type: + - text/event-stream; charset=utf-8 + date: + - Tue, 21 Jul 2026 21:52:51 GMT + openai-processing-ms: + - '1363' + openai-version: + - '2020-10-01' + server: + - cloudflare + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + x-ratelimit-limit-requests: + - '10000' + x-ratelimit-limit-tokens: + - '30000000' + x-ratelimit-remaining-requests: + - '9999' + x-ratelimit-remaining-tokens: + - '29999702' + x-ratelimit-reset-requests: + - 6ms + x-ratelimit-reset-tokens: + - 0s + status: + code: 200 + message: OK +version: 1 diff --git a/tests/conftest.py b/tests/conftest.py index 6bfcacaf..bdf72282 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -417,6 +417,39 @@ def assert_tool_web_search(chat_fun: ChatFun, tool, hint: str = "", stream: bool assert "May" in str(response) +def assert_tool_code_execution(chat_fun: ChatFun, tool, stream: bool = True): + """Test code execution tool functionality.""" + chat = chat_fun() + chat.register_tool(tool) + + response = chat.chat( + "Use code execution to compute the 20th Fibonacci number " + "(0-indexed, so fib(0) = 0, fib(1) = 1). Just give me the number.", + stream=stream, + ) + assert "6765" in str(response) + + +def assert_tool_code_execution_persistence( + chat_fun: ChatFun, tool, stream: bool = True +): + """Test that the sandbox persists a variable across turns.""" + chat = chat_fun() + chat.register_tool(tool) + + chat.chat( + "Use code execution to define a variable `x = 42`. " + "Don't print anything yet.", + stream=stream, + ) + response = chat.chat( + "Now use code execution to print `x * 2`, reusing the same `x` " + "from before -- don't redefine it.", + stream=stream, + ) + assert "84" in str(response) + + retry_api_call = retry( wait=wait_exponential(min=1, max=60), stop=stop_after_attempt(3), diff --git a/tests/test_content_code_execution.py b/tests/test_content_code_execution.py new file mode 100644 index 00000000..c5662bd2 --- /dev/null +++ b/tests/test_content_code_execution.py @@ -0,0 +1,69 @@ +from chatlas._content import ( + ContentToolRequestCodeExecution, + ContentToolResponseCodeExecution, + ContentUnion, + create_content, +) + + +def test_content_tool_request_code_execution_defaults(): + content = ContentToolRequestCodeExecution(code="print(1 + 1)") + assert content.content_type == "code_execution_request" + assert content.code == "print(1 + 1)" + assert content.language is None + assert content.extra is None + + +def test_content_tool_request_code_execution_str(): + content = ContentToolRequestCodeExecution(code="print(1 + 1)") + assert "print(1 + 1)" in str(content) + + +def test_content_tool_response_code_execution_defaults(): + content = ContentToolResponseCodeExecution(output="2") + assert content.content_type == "code_execution_result" + assert content.output == "2" + assert content.error is None + assert content.container_id is None + assert content.extra is None + + +def test_content_tool_response_code_execution_str_with_output(): + content = ContentToolResponseCodeExecution(output="2") + assert "2" in str(content) + + +def test_content_tool_response_code_execution_str_with_error(): + content = ContentToolResponseCodeExecution(error="NameError: x is not defined") + assert "NameError" in str(content) + + +def test_create_content_round_trips_request(): + content = ContentToolRequestCodeExecution( + code="print(1 + 1)", language="PYTHON", extra={"id": "abc"} + ) + data = content.model_dump() + restored = create_content(data) + assert isinstance(restored, ContentToolRequestCodeExecution) + assert restored.code == "print(1 + 1)" + assert restored.language == "PYTHON" + assert restored.extra == {"id": "abc"} + + +def test_create_content_round_trips_response(): + content = ContentToolResponseCodeExecution( + output="2", container_id="cntr_123", extra={"id": "abc"} + ) + data = content.model_dump() + restored = create_content(data) + assert isinstance(restored, ContentToolResponseCodeExecution) + assert restored.output == "2" + assert restored.container_id == "cntr_123" + + +def test_content_union_includes_code_execution_types(): + import typing + + args = typing.get_args(ContentUnion) + assert ContentToolRequestCodeExecution in args + assert ContentToolResponseCodeExecution in args diff --git a/tests/test_provider_anthropic.py b/tests/test_provider_anthropic.py index 208e8508..a54a4ae8 100644 --- a/tests/test_provider_anthropic.py +++ b/tests/test_provider_anthropic.py @@ -7,6 +7,7 @@ ChatAnthropic, UserTurn, content_image_file, + tool_code_execution, tool_web_fetch, tool_web_search, ) @@ -22,6 +23,7 @@ assert_images_remote, assert_list_models, assert_pdf_local, + assert_tool_code_execution, assert_tool_web_fetch, assert_tool_web_search, assert_tools_async, @@ -162,6 +164,26 @@ def test_anthropic_web_search_citations(): assert has_citations, "Expected citations on text blocks from web search" +@pytest.mark.vcr +def test_anthropic_code_execution(): + def chat_fun(**kwargs): + return ChatAnthropic( + kwargs={"default_headers": {"anthropic-beta": "code-execution-2025-05-22"}}, + **kwargs, + ) + + assert_tool_code_execution(chat_fun, tool_code_execution()) + + +# N.B. no code execution *persistence* test for Anthropic: despite chatlas +# reusing the same `container` across turns, Claude's code execution tool +# does not persist Python REPL state (e.g. variables) across separate turns +# -- each execution starts a fresh interpreter. This was verified live and +# contradicts the tool's own SDK docstring ("REPL state persistence"), so +# it's treated the same as Google's documented per-turn-fresh-sandbox +# limitation rather than a bug in chatlas. + + @pytest.mark.vcr def test_data_extraction(): assert_data_extraction(chat_func) @@ -371,3 +393,184 @@ class Person(BaseModel): output_config = args["output_config"] assert output_config["effort"] == "high" assert output_config["format"]["type"] == "json_schema" + + +def test_anthropic_code_execution_parses_request_and_response(): + """server_tool_use(code_execution) + code_execution_tool_result parse correctly.""" + from anthropic.types import Message + from chatlas._content import ( + ContentToolRequestCodeExecution, + ContentToolResponseCodeExecution, + ) + from chatlas._provider_anthropic import AnthropicProvider + + chat = ChatAnthropic() + provider = chat.provider + assert isinstance(provider, AnthropicProvider) + + message = Message.model_validate( + { + "id": "msg_1", + "type": "message", + "role": "assistant", + "model": "claude-opus-4-6", + "content": [ + { + "type": "server_tool_use", + "id": "srvtoolu_1", + "name": "code_execution", + "input": {"code": "print(1 + 1)"}, + }, + { + "type": "code_execution_tool_result", + "tool_use_id": "srvtoolu_1", + "content": { + "type": "code_execution_result", + "stdout": "2\n", + "stderr": "", + "return_code": 0, + "content": [], + }, + }, + ], + "stop_reason": "end_turn", + "usage": {"input_tokens": 10, "output_tokens": 5}, + "container": { + "id": "cntr_xyz", + "expires_at": "2026-07-21T00:00:00Z", + }, + } + ) + + turn = provider._as_turn(message, has_data_model=False) + + assert len(turn.contents) == 2 + request = turn.contents[0] + assert isinstance(request, ContentToolRequestCodeExecution) + assert request.code == "print(1 + 1)" + + response = turn.contents[1] + assert isinstance(response, ContentToolResponseCodeExecution) + assert response.output == "2\n" + assert response.error is None + assert response.container_id == "cntr_xyz" + + +def test_anthropic_code_execution_error_result(): + """An error result maps to `error`, not `output`.""" + from anthropic.types import Message + from chatlas._content import ContentToolResponseCodeExecution + from chatlas._provider_anthropic import AnthropicProvider + + chat = ChatAnthropic() + provider = chat.provider + assert isinstance(provider, AnthropicProvider) + + message = Message.model_validate( + { + "id": "msg_1", + "type": "message", + "role": "assistant", + "model": "claude-opus-4-6", + "content": [ + { + "type": "code_execution_tool_result", + "tool_use_id": "srvtoolu_1", + "content": { + "type": "code_execution_tool_result_error", + "error_code": "execution_time_exceeded", + }, + }, + ], + "stop_reason": "end_turn", + "usage": {"input_tokens": 10, "output_tokens": 5}, + } + ) + + turn = provider._as_turn(message, has_data_model=False) + response = turn.contents[0] + assert isinstance(response, ContentToolResponseCodeExecution) + assert response.output is None + assert response.error == "execution_time_exceeded" + assert response.container_id is None + + +def test_anthropic_code_execution_tool_schema(): + from chatlas._provider_anthropic import AnthropicProvider + + schema = AnthropicProvider._anthropic_tool_schema(tool_code_execution()) + assert schema["name"] == "code_execution" + assert schema["type"] == "code_execution_20250522" + + +def test_anthropic_code_execution_container_reuse(): + from chatlas._content import ContentToolResponseCodeExecution + from chatlas._provider_anthropic import AnthropicProvider + from chatlas._turn import AssistantTurn + + chat = ChatAnthropic() + chat.register_tool(tool_code_execution()) + provider = chat.provider + assert isinstance(provider, AnthropicProvider) + + turns = [ + AssistantTurn( + [ContentToolResponseCodeExecution(output="1", container_id="cntr_xyz")] + ) + ] + kwargs = provider._chat_perform_args( + stream=False, + turns=turns, + tools=chat._tools, # type: ignore[reportPrivateUsage] + data_model=None, + kwargs=None, + ) + assert kwargs["container"] == "cntr_xyz" + + +def test_anthropic_container_captured_from_streaming_message_delta(): + """Anthropic only sends `container` on the message_delta event (not + message_start), so the streaming accumulator must copy it over -- otherwise + cross-turn container reuse silently never works when streaming (the + default `chat.chat()` mode).""" + from anthropic.types import Message, RawMessageDeltaEvent, RawMessageStartEvent + from chatlas._provider_anthropic import AnthropicProvider + + chat = ChatAnthropic() + provider = chat.provider + assert isinstance(provider, AnthropicProvider) + + start_message = Message.model_validate( + { + "id": "msg_1", + "type": "message", + "role": "assistant", + "model": "claude-opus-4-6", + "content": [], + "stop_reason": None, + "usage": {"input_tokens": 10, "output_tokens": 0}, + } + ) + start_chunk = RawMessageStartEvent.model_validate( + {"type": "message_start", "message": start_message.model_dump()} + ) + completion = provider.stream_merge_chunks(None, start_chunk) + assert completion.container is None + + delta_chunk = RawMessageDeltaEvent.model_validate( + { + "type": "message_delta", + "delta": { + "stop_reason": "end_turn", + "stop_sequence": None, + "container": { + "id": "cntr_stream_xyz", + "expires_at": "2026-07-21T00:00:00Z", + }, + }, + "usage": {"output_tokens": 5}, + } + ) + completion = provider.stream_merge_chunks(completion, delta_chunk) + assert completion.container is not None + assert completion.container.id == "cntr_stream_xyz" diff --git a/tests/test_provider_google.py b/tests/test_provider_google.py index 4d12fcab..95a562de 100644 --- a/tests/test_provider_google.py +++ b/tests/test_provider_google.py @@ -1,6 +1,12 @@ import pytest import requests -from chatlas import ChatGoogle, ChatVertex, tool_web_fetch, tool_web_search +from chatlas import ( + ChatGoogle, + ChatVertex, + tool_code_execution, + tool_web_fetch, + tool_web_search, +) from chatlas._provider_google import ( normalize_finish_reason as google_normalize_finish_reason, ) @@ -13,6 +19,7 @@ assert_images_remote_error, assert_list_models, assert_pdf_local, + assert_tool_code_execution, assert_tool_web_fetch, assert_tool_web_search, assert_tools_parallel, @@ -209,6 +216,12 @@ def test_google_web_search(): assert_tool_web_search(chat_func, tool_web_search()) +@pytest.mark.vcr +@retry_gemini_call +def test_google_code_execution(): + assert_tool_code_execution(chat_func, tool_code_execution()) + + @pytest.mark.vcr @retry_gemini_call def test_images_inline(): @@ -602,3 +615,133 @@ def test_google_tool_config_not_set_when_tools_not_mixed(): only_builtin = {"web_search": tool_web_search()} kwargs = provider._chat_perform_args(turns=[user_turn("hi")], tools=only_builtin) assert kwargs["config"].tool_config is None + + +def test_google_code_execution_parses_request_and_response(): + """executable_code + code_execution_result parts parse correctly.""" + from chatlas._content import ( + ContentToolRequestCodeExecution, + ContentToolResponseCodeExecution, + ) + from chatlas._provider_google import GoogleProvider + + provider = GoogleProvider( + model="gemini-2.5-flash-preview-04-17", + api_key="dummy", + kwargs=None, + ) + + message = { + "candidates": [ + { + "content": { + "parts": [ + { + "executable_code": { + "code": "print(1 + 1)", + "language": "PYTHON", + } + }, + { + "code_execution_result": { + "outcome": "OUTCOME_OK", + "output": "2\n", + } + }, + ] + }, + "finish_reason": "STOP", + } + ], + } + + turn = provider._as_turn(message, has_data_model=False) + assert len(turn.contents) == 2 + + request = turn.contents[0] + assert isinstance(request, ContentToolRequestCodeExecution) + assert request.code == "print(1 + 1)" + assert request.language == "PYTHON" + + response = turn.contents[1] + assert isinstance(response, ContentToolResponseCodeExecution) + assert response.output == "2\n" + assert response.error is None + + +def test_google_code_execution_failed_outcome_maps_to_error(): + from chatlas._content import ContentToolResponseCodeExecution + from chatlas._provider_google import GoogleProvider + + provider = GoogleProvider( + model="gemini-2.5-flash-preview-04-17", + api_key="dummy", + kwargs=None, + ) + + message = { + "candidates": [ + { + "content": { + "parts": [ + { + "code_execution_result": { + "outcome": "OUTCOME_FAILED", + "output": "NameError: x is not defined", + } + }, + ] + }, + "finish_reason": "STOP", + } + ], + } + + turn = provider._as_turn(message, has_data_model=False) + response = turn.contents[0] + assert isinstance(response, ContentToolResponseCodeExecution) + assert response.output is None + assert response.error == "NameError: x is not defined" + + +def test_google_code_execution_round_trip(): + from chatlas._content import ( + ContentToolRequestCodeExecution, + ContentToolResponseCodeExecution, + ) + from chatlas._provider_google import GoogleProvider + + provider = GoogleProvider( + model="gemini-2.5-flash-preview-04-17", + api_key="dummy", + kwargs=None, + ) + + request = ContentToolRequestCodeExecution(code="print(1 + 1)", language="PYTHON") + part = provider._as_part_type(request) + assert part.executable_code is not None + assert part.executable_code.code == "print(1 + 1)" + + response = ContentToolResponseCodeExecution(output="2\n") + part = provider._as_part_type(response) + assert part.code_execution_result is not None + assert part.code_execution_result.output == "2\n" + + +def test_google_code_execution_tool_definition_registered(): + from chatlas._provider_google import GoogleProvider + + provider = GoogleProvider( + model="gemini-2.5-flash-preview-04-17", + api_key="dummy", + kwargs=None, + ) + kwargs = provider._chat_perform_args( + turns=[], + tools={"code_execution": tool_code_execution()}, + data_model=None, + kwargs=None, + ) + tools = kwargs["config"].tools + assert tools is not None + assert tools[0].code_execution is not None diff --git a/tests/test_provider_openai.py b/tests/test_provider_openai.py index 544986ba..41d8d3de 100644 --- a/tests/test_provider_openai.py +++ b/tests/test_provider_openai.py @@ -2,7 +2,7 @@ import httpx import pytest -from chatlas import ChatOpenAI, tool_web_search +from chatlas import ChatOpenAI, tool_code_execution, tool_web_search from chatlas._provider_openai import ( normalize_finish_reason as openai_normalize_finish_reason, ) @@ -14,6 +14,8 @@ assert_images_remote, assert_list_models, assert_pdf_local, + assert_tool_code_execution, + assert_tool_code_execution_persistence, assert_tool_web_search, assert_tools_async, assert_tools_parallel, @@ -130,6 +132,22 @@ def chat_fun(**kwargs): ) +@pytest.mark.vcr +def test_openai_code_execution(): + def chat_fun(**kwargs): + return ChatOpenAI(model="gpt-4.1", **kwargs) + + assert_tool_code_execution(chat_fun, tool_code_execution()) + + +@pytest.mark.vcr +def test_openai_code_execution_persistence(): + def chat_fun(**kwargs): + return ChatOpenAI(model="gpt-4.1", **kwargs) + + assert_tool_code_execution_persistence(chat_fun, tool_code_execution()) + + @pytest.mark.vcr def test_openai_images(): chat_fun = ChatOpenAI @@ -336,3 +354,111 @@ def test_openai_custom_base_url_warning(): with warnings.catch_warnings(): warnings.simplefilter("error") check_base_url("https://api.openai.com/v1") + + +def test_openai_code_execution_call_parses_request_and_response(): + """A code_interpreter_call output splits into request + response content.""" + from chatlas._content import ( + ContentToolRequestCodeExecution, + ContentToolResponseCodeExecution, + ) + from chatlas._provider_openai import OpenAIProvider + + chat = ChatOpenAI() + provider = chat.provider + assert isinstance(provider, OpenAIProvider) + + def make_response(outputs: list[dict]): + from openai.types.responses import Response + + return Response.model_validate( + { + "id": "resp_1", + "created_at": 0, + "model": "gpt-4.1", + "object": "response", + "output": [ + { + "id": "ci_1", + "type": "code_interpreter_call", + "status": "completed", + "code": "print(1 + 1)", + "container_id": "cntr_abc123", + "outputs": outputs, + } + ], + "parallel_tool_calls": True, + "tool_choice": "auto", + "tools": [], + } + ) + + resp = make_response([{"type": "logs", "logs": "2"}]) + turn = provider._response_as_turn(resp, has_data_model=False) + + assert len(turn.contents) == 2 + request = turn.contents[0] + assert isinstance(request, ContentToolRequestCodeExecution) + assert request.code == "print(1 + 1)" + + response = turn.contents[1] + assert isinstance(response, ContentToolResponseCodeExecution) + assert response.output == "2" + assert response.container_id == "cntr_abc123" + + +def test_openai_code_execution_response_does_not_duplicate_on_round_trip(): + """The response half shouldn't be resubmitted -- it's bundled into the request's extra.""" + from chatlas._content import ContentToolResponseCodeExecution + from chatlas._provider_openai import as_input_param + + content = ContentToolResponseCodeExecution( + output="2", extra={"type": "code_interpreter_call"} + ) + assert as_input_param(content, "assistant") is None + + +def test_openai_code_execution_tool_uses_auto_container_by_default(): + from chatlas._provider_openai import OpenAIProvider + + chat = ChatOpenAI() + chat.register_tool(tool_code_execution()) + provider = chat.provider + assert isinstance(provider, OpenAIProvider) + + kwargs = provider._chat_perform_args( + stream=False, + turns=[], + tools=chat._tools, # type: ignore[reportPrivateUsage] + data_model=None, + kwargs=None, + ) + tools = kwargs["tools"] + assert tools[0]["type"] == "code_interpreter" + assert tools[0]["container"] == {"type": "auto"} + + +def test_openai_code_execution_tool_reuses_container_from_turn_history(): + from chatlas._content import ContentToolResponseCodeExecution + from chatlas._provider_openai import OpenAIProvider + from chatlas._turn import AssistantTurn + + chat = ChatOpenAI() + chat.register_tool(tool_code_execution()) + provider = chat.provider + assert isinstance(provider, OpenAIProvider) + + turns = [ + AssistantTurn( + [ContentToolResponseCodeExecution(output="1", container_id="cntr_xyz")] + ) + ] + kwargs = provider._chat_perform_args( + stream=False, + turns=turns, + tools=chat._tools, # type: ignore[reportPrivateUsage] + data_model=None, + kwargs=None, + ) + tools = kwargs["tools"] + assert tools[0]["container"] == "cntr_xyz" diff --git a/tests/test_tools_builtin.py b/tests/test_tools_builtin.py index ec20d314..6863237c 100644 --- a/tests/test_tools_builtin.py +++ b/tests/test_tools_builtin.py @@ -1,17 +1,22 @@ """Tests for built-in web search and fetch tools.""" import pytest - from chatlas import ( ChatAnthropic, ChatGoogle, ChatOpenAI, + tool_code_execution, tool_web_fetch, tool_web_search, ) from chatlas._content import ToolAnnotations from chatlas._tools import ToolBuiltIn -from chatlas._tools_builtin import ToolWebFetch, ToolWebSearch +from chatlas._tools_builtin import ( + ToolCodeExecution, + ToolWebFetch, + ToolWebSearch, + last_code_execution_container_id, +) class TestToolBuiltInMetadata: @@ -84,9 +89,7 @@ def test_cannot_use_both_domain_filters(self): with pytest.raises( ValueError, match="Cannot specify both allowed_domains and blocked_domains" ): - tool_web_search( - allowed_domains=["good.com"], blocked_domains=["bad.com"] - ) + tool_web_search(allowed_domains=["good.com"], blocked_domains=["bad.com"]) def test_with_user_location(self): """Test web search tool with user location.""" @@ -127,9 +130,7 @@ def test_cannot_use_both_domain_filters(self): with pytest.raises( ValueError, match="Cannot specify both allowed_domains and blocked_domains" ): - tool_web_fetch( - allowed_domains=["good.com"], blocked_domains=["bad.com"] - ) + tool_web_fetch(allowed_domains=["good.com"], blocked_domains=["bad.com"]) class TestToolWebSearchProviderDefinitions: @@ -205,7 +206,9 @@ def test_unsupported_provider(self): def test_openai_warns_on_unsupported_params(self): """Test that OpenAI warns about unsupported parameters.""" tool = tool_web_search(blocked_domains=["spam.com"], max_uses=5) - with pytest.warns(UserWarning, match="blocked_domains is not supported by OpenAI"): + with pytest.warns( + UserWarning, match="blocked_domains is not supported by OpenAI" + ): tool.get_definition("openai") def test_google_warns_on_unsupported_params(self): @@ -287,7 +290,9 @@ def test_google_warns_on_unsupported_params(self): assert any("allowed_domains" in m for m in messages) assert any("max_uses" in m for m in messages) - with pytest.warns(UserWarning, match="blocked_domains is not supported by Google"): + with pytest.warns( + UserWarning, match="blocked_domains is not supported by Google" + ): tool2.get_definition("google") def test_anthropic_with_extra_params(self): @@ -372,3 +377,116 @@ def add(x: int, y: int) -> int: assert len(tools) == 2 tool_names = {t.name for t in tools} assert tool_names == {"web_search", "add"} + + +class TestToolCodeExecutionConfiguration: + """Test ToolCodeExecution configuration and provider definitions.""" + + def test_has_description_and_annotations(self): + tool = tool_code_execution() + assert tool.description == "Execute code in a sandboxed environment." + assert tool.annotations is not None + assert tool.annotations["title"] == "Code execution" + assert tool.annotations["readOnlyHint"] is False + assert tool.annotations["openWorldHint"] is False + + def test_basic_configuration(self): + tool = tool_code_execution() + assert isinstance(tool, ToolCodeExecution) + assert tool.name == "code_execution" + + def test_openai_definition_defaults_to_auto_container(self): + tool = tool_code_execution() + definition = tool.get_definition("openai") + assert definition["type"] == "code_interpreter" + assert definition["container"] == {"type": "auto"} + + def test_openai_definition_reuses_container_id(self): + tool = tool_code_execution() + definition = tool.get_definition("openai", container_id="cntr_abc123") + assert definition["container"] == "cntr_abc123" + + def test_anthropic_definition(self): + tool = tool_code_execution() + definition = tool.get_definition("anthropic") + assert definition["name"] == "code_execution" + assert definition["type"] == "code_execution_20250522" + + def test_google_definition(self): + from google.genai.types import ToolCodeExecution as GoogleToolCodeExecution + + tool = tool_code_execution() + definition = tool.get_definition("google") + assert isinstance(definition, GoogleToolCodeExecution) + + def test_unsupported_provider(self): + tool = tool_code_execution() + with pytest.raises(ValueError, match="Code execution is not supported"): + tool.get_definition("unsupported_provider") + + +class TestLastCodeExecutionContainerId: + """Test the turn-history scan that powers cross-turn sandbox reuse.""" + + def test_returns_none_with_no_turns(self): + assert last_code_execution_container_id([]) is None + + def test_returns_none_when_no_code_execution_response(self): + from chatlas._content import ContentText + from chatlas._turn import AssistantTurn + + turns = [AssistantTurn([ContentText(text="hi")])] + assert last_code_execution_container_id(turns) is None + + def test_finds_container_id_from_last_assistant_turn(self): + from chatlas._content import ContentToolResponseCodeExecution + from chatlas._turn import AssistantTurn, UserTurn + + turns = [ + UserTurn("define x = 1"), + AssistantTurn( + [ContentToolResponseCodeExecution(output="", container_id="cntr_1")] + ), + UserTurn("now print x"), + AssistantTurn( + [ContentToolResponseCodeExecution(output="1", container_id="cntr_2")] + ), + ] + assert last_code_execution_container_id(turns) == "cntr_2" + + def test_ignores_response_without_container_id(self): + from chatlas._content import ContentToolResponseCodeExecution + from chatlas._turn import AssistantTurn + + turns = [ + AssistantTurn( + [ContentToolResponseCodeExecution(output="1", container_id="cntr_1")] + ), + AssistantTurn([ContentToolResponseCodeExecution(output="2")]), + ] + assert last_code_execution_container_id(turns) == "cntr_1" + + +class TestChatRegistrationCodeExecution: + """Test registering the code execution tool with chat instances.""" + + def test_register_code_execution_openai(self): + chat = ChatOpenAI() + chat.register_tool(tool_code_execution()) + tools = chat.get_tools() + assert len(tools) == 1 + assert tools[0].name == "code_execution" + + def test_register_code_execution_anthropic(self): + chat = ChatAnthropic() + chat.register_tool(tool_code_execution()) + tools = chat.get_tools() + assert len(tools) == 1 + assert tools[0].name == "code_execution" + + def test_register_code_execution_google(self): + chat = ChatGoogle() + chat.register_tool(tool_code_execution()) + tools = chat.get_tools() + assert len(tools) == 1 + assert tools[0].name == "code_execution"