Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-langchain-openai for
openSUSE:Factory checked in at 2026-07-12 19:02:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langchain-openai (Old)
and /work/SRC/openSUSE:Factory/.python-langchain-openai.new.1991 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-langchain-openai"
Sun Jul 12 19:02:58 2026 rev:3 rq:1364983 version:1.3.5
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-langchain-openai/python-langchain-openai.changes
2026-07-09 22:21:24.006254837 +0200
+++
/work/SRC/openSUSE:Factory/.python-langchain-openai.new.1991/python-langchain-openai.changes
2026-07-12 19:03:00.022692493 +0200
@@ -1,0 +2,15 @@
+Sat Jul 11 05:37:48 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 1.3.5:
+ * Add prompt cache breakpoint support: content blocks can carry
+ a prompt_cache_breakpoint and ChatOpenAI accepts a new
+ prompt_cache_options parameter for explicit/implicit caching
+ * Expose cache-write token counts as cache_creation in the
+ input token usage metadata (chat completions and responses)
+ * Add GPT-5.6 model profiles (gpt-5.6, -luna, -sol, -terra)
+ * Drop the legacy prompt_cache_retention in-memory value
+ normalization now that the newer openai SDK is required
+- Raise langchain-core requirement to >= 1.4.9
+- Raise openai requirement to >= 2.45.0
+
+-------------------------------------------------------------------
Old:
----
langchain_openai-1.3.4.tar.gz
New:
----
langchain_openai-1.3.5.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-langchain-openai.spec ++++++
--- /var/tmp/diff_new_pack.r38UQ3/_old 2026-07-12 19:03:00.762717236 +0200
+++ /var/tmp/diff_new_pack.r38UQ3/_new 2026-07-12 19:03:00.762717236 +0200
@@ -17,7 +17,7 @@
Name: python-langchain-openai
-Version: 1.3.4
+Version: 1.3.5
Release: 0
Summary: An integration package connecting OpenAI and LangChain
License: MIT
@@ -27,14 +27,14 @@
BuildRequires: %{python_module pip}
BuildRequires: fdupes
BuildRequires: python-rpm-macros
-Requires: python-langchain-core >= 1.4.8
-Requires: python-openai >= 2.26.0
+Requires: python-langchain-core >= 1.4.9
+Requires: python-openai >= 2.45.0
Requires: python-tiktoken >= 0.7.0
BuildArch: noarch
# SECTION test requirements
BuildRequires: %{python_module httpx}
-BuildRequires: %{python_module langchain-core >= 1.4.8}
-BuildRequires: %{python_module openai >= 2.26.0}
+BuildRequires: %{python_module langchain-core >= 1.4.9}
+BuildRequires: %{python_module openai >= 2.45.0}
BuildRequires: %{python_module pytest-asyncio}
BuildRequires: %{python_module tiktoken >= 0.7.0}
# /SECTION
++++++ langchain_openai-1.3.4.tar.gz -> langchain_openai-1.3.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_openai-1.3.4/PKG-INFO
new/langchain_openai-1.3.5/PKG-INFO
--- old/langchain_openai-1.3.4/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.3.5/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: langchain-openai
-Version: 1.3.4
+Version: 1.3.5
Summary: An integration package connecting OpenAI and LangChain
Project-URL: Homepage,
https://docs.langchain.com/oss/python/integrations/providers/openai
Project-URL: Documentation,
https://reference.langchain.com/python/integrations/langchain_openai/
@@ -24,7 +24,7 @@
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: <4.0.0,>=3.10.0
Requires-Dist: langchain-core<2.0.0,>=1.4.9
-Requires-Dist: openai<3.0.0,>=2.26.0
+Requires-Dist: openai<3.0.0,>=2.45.0
Requires-Dist: tiktoken<1.0.0,>=0.7.0
Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_openai-1.3.4/langchain_openai/_version.py
new/langchain_openai-1.3.5/langchain_openai/_version.py
--- old/langchain_openai-1.3.4/langchain_openai/_version.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_openai-1.3.5/langchain_openai/_version.py 2020-02-02
01:00:00.000000000 +0100
@@ -1,3 +1,3 @@
"""Version information for `langchain-openai`."""
-__version__ = "1.3.4"
+__version__ = "1.3.5"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.3.4/langchain_openai/chat_models/base.py
new/langchain_openai-1.3.5/langchain_openai/chat_models/base.py
--- old/langchain_openai-1.3.4/langchain_openai/chat_models/base.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.3.5/langchain_openai/chat_models/base.py
2020-02-02 01:00:00.000000000 +0100
@@ -264,10 +264,30 @@
return ChatMessage(content=_dict.get("content", ""), role=role, id=id_) #
type: ignore[arg-type]
+def _apply_prompt_cache_breakpoint(
+ source_block: dict[str, Any], formatted_block: dict[str, Any]
+) -> dict[str, Any]:
+ """Apply an OpenAI prompt cache breakpoint to a formatted content block.
+
+ A breakpoint set directly on the block takes precedence over one nested in
+ `extras`. Membership (not truthiness) decides whether to copy it, so a
+ present-but-falsy value (e.g. `None`) is still preserved.
+ """
+ if "prompt_cache_breakpoint" in source_block:
+ formatted_block["prompt_cache_breakpoint"] = source_block[
+ "prompt_cache_breakpoint"
+ ]
+ elif isinstance(extras := source_block.get("extras"), dict) and (
+ "prompt_cache_breakpoint" in extras
+ ):
+ formatted_block["prompt_cache_breakpoint"] =
extras["prompt_cache_breakpoint"]
+ return formatted_block
+
+
def _sanitize_chat_completions_content(content: str | list[dict]) -> str |
list[dict]:
"""Sanitize content for chat/completions API.
- For list content, filters text blocks to only keep 'type' and 'text' keys.
+ For list content, filters text blocks to only keep supported keys.
"""
if isinstance(content, list):
sanitized = []
@@ -277,7 +297,12 @@
and block.get("type") == "text"
and "text" in block
):
- sanitized.append({"type": "text", "text": block["text"]})
+ sanitized_block = {"type": "text", "text": block["text"]}
+ if "prompt_cache_breakpoint" in block:
+ sanitized_block["prompt_cache_breakpoint"] = block[
+ "prompt_cache_breakpoint"
+ ]
+ sanitized.append(sanitized_block)
else:
sanitized.append(block)
return sanitized
@@ -313,7 +338,21 @@
# image generation calls)
and not (api == "responses" and
str(role).lower().startswith("ai"))
):
- formatted_content.append(convert_to_openai_data_block(block,
api=api))
+ formatted_block = convert_to_openai_data_block(block, api=api)
+ formatted_content.append(
+ _apply_prompt_cache_breakpoint(block, formatted_block)
+ )
+ elif (
+ isinstance(block, dict)
+ and block.get("type") == "text"
+ and "text" in block
+ and isinstance(extras := block.get("extras"), dict)
+ and "prompt_cache_breakpoint" in extras
+ ):
+ formatted_block = {"type": "text", "text": block["text"]}
+ formatted_content.append(
+ _apply_prompt_cache_breakpoint(block, formatted_block)
+ )
# Anthropic image blocks
elif (
isinstance(block, dict)
@@ -984,6 +1023,12 @@
!!! version-added "Added in `langchain-openai` 0.3.24"
"""
+ prompt_cache_options: dict[str, Any] | None = None
+ """Options controlling OpenAI prompt cache behavior.
+
+ !!! version-added "Added in `langchain-openai` 1.3.5"
+ """
+
service_tier: str | None = None
"""Latency tier for request.
@@ -1309,6 +1354,7 @@
"verbosity": self.verbosity,
"context_management": self.context_management,
"include": self.include,
+ "prompt_cache_options": self.prompt_cache_options,
"service_tier": self.service_tier,
"truncation": self.truncation,
"store": self.store,
@@ -3297,8 +3343,9 @@
??? info "Prompt caching optimization"
- For high-volume applications with repetitive prompts, use
`prompt_cache_key`
- per-invocation to improve cache hit rates and reduce costs:
+ OpenAI prompt caching is automatic for eligible prompts. For
high-volume
+ applications with repetitive prompts, use `prompt_cache_key`
+ per invocation to improve cache hit rates and reduce costs:
```python
model = ChatOpenAI(model="...")
@@ -3316,9 +3363,69 @@
response = model.invoke(messages, prompt_cache_key=cache_key)
```
- Cache keys help ensure requests with the same prompt prefix are routed
to
- machines with existing cache, providing cost reduction and latency
improvement on
- cached tokens.
+ The default `"implicit"` mode keeps OpenAI's automatic breakpoint and
+ also uses explicit breakpoints. The `"explicit"` mode uses only the
+ breakpoints you provide.
+
+ For models that support explicit cache breakpoints, pass
+ request-level cache options and mark supported content blocks
+ with `prompt_cache_breakpoint`:
+
+ ```python
+ model = ChatOpenAI(model="gpt-5.6-sol")
+ response = model.invoke(
+ [
+ {
+ "role": "system",
+ "content": [
+ {
+ "type": "text",
+ "text": "Stable instructions and examples...",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ }
+ ],
+ },
+ {"role": "user", "content": "Current request"},
+ ],
+ prompt_cache_key="tenant:acme:support-v1",
+ prompt_cache_options={"mode": "explicit", "ttl": "30m"},
+ )
+ ```
+
+ Set `prompt_cache_options` per invocation, as above, or persist it on
+ the model:
+
+ ```python
+ model = ChatOpenAI(
+ model="gpt-5.6-sol",
+ prompt_cache_options={"mode": "explicit", "ttl": "30m"},
+ )
+ ```
+
+ `prompt_cache_options["mode"]` can be `"implicit"` or `"explicit"`.
+ OpenAI limits how many breakpoints can write to the cache in a single
+ request. In `"implicit"` mode, the implicit breakpoint on the latest
+ message uses one write slot, so up to three explicit breakpoints can
+ write. In `"explicit"` mode, up to four explicit breakpoints can write.
+ For reads, OpenAI considers up to the latest 50 breakpoints.
+
+ For models before the GPT-5.6 family that support legacy prompt cache
+ retention, pass `prompt_cache_retention`. See OpenAI's
+ [prompt caching
docs](https://platform.openai.com/docs/guides/prompt-caching)
+ for the current model support list and retention semantics.
+
+ ```python
+ response = model.invoke(messages, prompt_cache_retention="24h")
+ ```
+
+ Cache keys help ensure requests with the same prompt prefix are routed
+ to machines with existing cache, providing cost reduction and
+ latency improvement on cached tokens. Cache reads are available as
+ `response.usage_metadata["input_token_details"]["cache_read"]`; cache
+ writes are available as `"cache_creation"` when the OpenAI response
+ includes `cache_write_tokens`. On the `"priority"` and `"flex"`
+ service tiers these keys are prefixed with the tier name
+ (e.g. `"priority_cache_read"`).
""" # noqa: E501
max_tokens: int | None = Field(default=None, alias="max_completion_tokens")
@@ -4037,13 +4144,13 @@
if service_tier not in {"priority", "flex"}:
service_tier = None
service_tier_prefix = f"{service_tier}_" if service_tier else ""
+ prompt_tokens_details = oai_token_usage.get("prompt_tokens_details") or {}
input_token_details: dict = {
- "audio": (oai_token_usage.get("prompt_tokens_details") or {}).get(
- "audio_tokens"
+ "audio": prompt_tokens_details.get("audio_tokens"),
+ f"{service_tier_prefix}cache_read":
prompt_tokens_details.get("cached_tokens"),
+ f"{service_tier_prefix}cache_creation": prompt_tokens_details.get(
+ "cache_write_tokens"
),
- f"{service_tier_prefix}cache_read": (
- oai_token_usage.get("prompt_tokens_details") or {}
- ).get("cached_tokens"),
}
output_token_details: dict = {
"audio": (oai_token_usage.get("completion_tokens_details") or {}).get(
@@ -4054,13 +4161,13 @@
).get("reasoning_tokens"),
}
if service_tier is not None:
- # Avoid counting cache and reasoning tokens towards the service tier
token
- # counts, since service tier tokens are already priced differently
- input_token_details[service_tier] = input_tokens -
input_token_details.get(
- f"{service_tier_prefix}cache_read", 0
+ # Avoid counting cache-read and reasoning tokens towards the service
tier
+ # token counts, since service tier tokens are already priced
differently
+ input_token_details[service_tier] = input_tokens - (
+ input_token_details.get(f"{service_tier_prefix}cache_read", 0) or 0
)
- output_token_details[service_tier] = output_tokens -
output_token_details.get(
- f"{service_tier_prefix}reasoning", 0
+ output_token_details[service_tier] = output_tokens - (
+ output_token_details.get(f"{service_tier_prefix}reasoning", 0) or 0
)
return UsageMetadata(
input_tokens=input_tokens,
@@ -4078,9 +4185,12 @@
def _create_usage_metadata_responses(
oai_token_usage: dict, service_tier: str | None = None
) -> UsageMetadata:
- input_tokens = oai_token_usage.get("input_tokens", 0)
- output_tokens = oai_token_usage.get("output_tokens", 0)
- total_tokens = oai_token_usage.get("total_tokens", input_tokens +
output_tokens)
+ _input = oai_token_usage.get("input_tokens")
+ input_tokens = _input if _input is not None else 0
+ _output = oai_token_usage.get("output_tokens")
+ output_tokens = _output if _output is not None else 0
+ _total = oai_token_usage.get("total_tokens")
+ total_tokens = _total if _total is not None else input_tokens +
output_tokens
if service_tier not in {"priority", "flex"}:
service_tier = None
service_tier_prefix = f"{service_tier}_" if service_tier else ""
@@ -4089,19 +4199,21 @@
oai_token_usage.get("output_tokens_details") or {}
).get("reasoning_tokens")
}
+ input_tokens_details = oai_token_usage.get("input_tokens_details") or {}
input_token_details: dict = {
- f"{service_tier_prefix}cache_read": (
- oai_token_usage.get("input_tokens_details") or {}
- ).get("cached_tokens")
+ f"{service_tier_prefix}cache_read":
input_tokens_details.get("cached_tokens"),
+ f"{service_tier_prefix}cache_creation": input_tokens_details.get(
+ "cache_write_tokens"
+ ),
}
if service_tier is not None:
- # Avoid counting cache and reasoning tokens towards the service tier
token
- # counts, since service tier tokens are already priced differently
- output_token_details[service_tier] = output_tokens -
output_token_details.get(
- f"{service_tier_prefix}reasoning", 0
+ # Avoid counting cache-read and reasoning tokens towards the service
tier
+ # token counts, since service tier tokens are already priced
differently
+ output_token_details[service_tier] = output_tokens - (
+ output_token_details.get(f"{service_tier_prefix}reasoning", 0) or 0
)
- input_token_details[service_tier] = input_tokens -
input_token_details.get(
- f"{service_tier_prefix}cache_read", 0
+ input_token_details[service_tier] = input_tokens - (
+ input_token_details.get(f"{service_tier_prefix}cache_read", 0) or 0
)
return UsageMetadata(
input_tokens=input_tokens,
@@ -4301,19 +4413,27 @@
if block["type"] == "text":
# chat api: {"type": "text", "text": "..."}
# responses api: {"type": "input_text", "text": "..."}
- return {"type": "input_text", "text": block["text"]}
+ new_block = {"type": "input_text", "text": block["text"]}
+ if "prompt_cache_breakpoint" in block:
+ new_block["prompt_cache_breakpoint"] =
block["prompt_cache_breakpoint"]
+ return new_block
if block["type"] == "image_url":
# chat api: {"type": "image_url", "image_url": {"url": "...",
"detail": "..."}} # noqa: E501
- # responses api: {"type": "image_url", "image_url": "...", "detail":
"...", "file_id": "..."} # noqa: E501
+ # responses api: {"type": "input_image", "image_url": "...", "detail":
"..."} # noqa: E501
new_block = {
"type": "input_image",
"image_url": block["image_url"]["url"],
}
if block["image_url"].get("detail"):
new_block["detail"] = block["image_url"]["detail"]
+ if "prompt_cache_breakpoint" in block:
+ new_block["prompt_cache_breakpoint"] =
block["prompt_cache_breakpoint"]
return new_block
if block["type"] == "file":
- return {"type": "input_file", **block["file"]}
+ new_block = {"type": "input_file", **block["file"]}
+ if "prompt_cache_breakpoint" in block:
+ new_block["prompt_cache_breakpoint"] =
block["prompt_cache_breakpoint"]
+ return new_block
return block
@@ -4842,13 +4962,6 @@
if isinstance(resp, dict):
from openai.types.responses import Response
- # Known mismatch: API emits `prompt_cache_retention="in_memory"` while
- # older `openai` packages declare only `"in-memory"` in the Literal
- # (openai-python#2883). Pre-normalize so validation succeeds on
- # currently-released SDK versions.
- if resp.get("prompt_cache_retention") == "in_memory":
- resp = {**resp, "prompt_cache_retention": "in-memory"}
-
try:
return Response.model_validate(resp)
except ValidationError as e:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.3.4/langchain_openai/data/_profiles.py
new/langchain_openai-1.3.5/langchain_openai/data/_profiles.py
--- old/langchain_openai-1.3.4/langchain_openai/data/_profiles.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.3.5/langchain_openai/data/_profiles.py
2020-02-02 01:00:00.000000000 +0100
@@ -987,6 +987,114 @@
"tool_choice": True,
"tool_call_streaming": True,
},
+ "gpt-5.6": {
+ "name": "GPT-5.6",
+ "release_date": "2026-07-09",
+ "last_updated": "2026-07-09",
+ "open_weights": False,
+ "max_input_tokens": 1050000,
+ "max_output_tokens": 128000,
+ "text_inputs": True,
+ "image_inputs": True,
+ "audio_inputs": False,
+ "pdf_inputs": True,
+ "video_inputs": False,
+ "text_outputs": True,
+ "image_outputs": False,
+ "audio_outputs": False,
+ "video_outputs": False,
+ "reasoning_output": True,
+ "tool_calling": True,
+ "structured_output": True,
+ "attachment": True,
+ "temperature": False,
+ "image_url_inputs": True,
+ "pdf_tool_message": True,
+ "image_tool_message": True,
+ "tool_choice": True,
+ "tool_call_streaming": True,
+ },
+ "gpt-5.6-luna": {
+ "name": "GPT-5.6 Luna",
+ "release_date": "2026-07-09",
+ "last_updated": "2026-07-09",
+ "open_weights": False,
+ "max_input_tokens": 1050000,
+ "max_output_tokens": 128000,
+ "text_inputs": True,
+ "image_inputs": True,
+ "audio_inputs": False,
+ "pdf_inputs": True,
+ "video_inputs": False,
+ "text_outputs": True,
+ "image_outputs": False,
+ "audio_outputs": False,
+ "video_outputs": False,
+ "reasoning_output": True,
+ "tool_calling": True,
+ "structured_output": True,
+ "attachment": True,
+ "temperature": False,
+ "image_url_inputs": True,
+ "pdf_tool_message": True,
+ "image_tool_message": True,
+ "tool_choice": True,
+ "tool_call_streaming": True,
+ },
+ "gpt-5.6-sol": {
+ "name": "GPT-5.6 Sol",
+ "release_date": "2026-07-09",
+ "last_updated": "2026-07-09",
+ "open_weights": False,
+ "max_input_tokens": 1050000,
+ "max_output_tokens": 128000,
+ "text_inputs": True,
+ "image_inputs": True,
+ "audio_inputs": False,
+ "pdf_inputs": True,
+ "video_inputs": False,
+ "text_outputs": True,
+ "image_outputs": False,
+ "audio_outputs": False,
+ "video_outputs": False,
+ "reasoning_output": True,
+ "tool_calling": True,
+ "structured_output": True,
+ "attachment": True,
+ "temperature": False,
+ "image_url_inputs": True,
+ "pdf_tool_message": True,
+ "image_tool_message": True,
+ "tool_choice": True,
+ "tool_call_streaming": True,
+ },
+ "gpt-5.6-terra": {
+ "name": "GPT-5.6 Terra",
+ "release_date": "2026-07-09",
+ "last_updated": "2026-07-09",
+ "open_weights": False,
+ "max_input_tokens": 1050000,
+ "max_output_tokens": 128000,
+ "text_inputs": True,
+ "image_inputs": True,
+ "audio_inputs": False,
+ "pdf_inputs": True,
+ "video_inputs": False,
+ "text_outputs": True,
+ "image_outputs": False,
+ "audio_outputs": False,
+ "video_outputs": False,
+ "reasoning_output": True,
+ "tool_calling": True,
+ "structured_output": True,
+ "attachment": True,
+ "temperature": False,
+ "image_url_inputs": True,
+ "pdf_tool_message": True,
+ "image_tool_message": True,
+ "tool_choice": True,
+ "tool_call_streaming": True,
+ },
"gpt-image-1": {
"name": "gpt-image-1",
"release_date": "2025-04-24",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_openai-1.3.4/pyproject.toml
new/langchain_openai-1.3.5/pyproject.toml
--- old/langchain_openai-1.3.4/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
+++ new/langchain_openai-1.3.5/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
@@ -20,11 +20,11 @@
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
-version = "1.3.4"
+version = "1.3.5"
requires-python = ">=3.10.0,<4.0.0"
dependencies = [
"langchain-core>=1.4.9,<2.0.0",
- "openai>=2.26.0,<3.0.0",
+ "openai>=2.45.0,<3.0.0",
"tiktoken>=0.7.0,<1.0.0",
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.3.4/tests/integration_tests/chat_models/test_base.py
new/langchain_openai-1.3.5/tests/integration_tests/chat_models/test_base.py
--- old/langchain_openai-1.3.4/tests/integration_tests/chat_models/test_base.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.3.5/tests/integration_tests/chat_models/test_base.py
2020-02-02 01:00:00.000000000 +0100
@@ -1200,6 +1200,45 @@
assert isinstance(response_model_level.content, str)
[email protected]
[email protected](retries=3, delay=1)
+def test_explicit_prompt_cache_breakpoint_invoke() -> None:
+ """Explicit cache breakpoints produce a cache read on a repeated prefix.
+
+ Uses a long, stable prefix marked with a `prompt_cache_breakpoint` and
+ `prompt_cache_options={"mode": "explicit"}`. The first invocation writes
the
+ cache and the second should read it back.
+ """
+ chat = ChatOpenAI(
+ model="gpt-5.6-sol",
+ max_completion_tokens=10,
+ prompt_cache_options={"mode": "explicit"},
+ )
+ # A prefix long enough to exceed OpenAI's minimum cacheable prompt length.
+ stable_prefix = "Stable, cacheable instructions and reference material. "
* 400
+ messages = [
+ HumanMessage(
+ content=[
+ {
+ "type": "text",
+ "text": stable_prefix,
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {"type": "text", "text": "Say hello."},
+ ]
+ )
+ ]
+
+ first = chat.invoke(messages)
+ assert isinstance(first, AIMessage)
+
+ second = chat.invoke(messages)
+ assert isinstance(second, AIMessage)
+ assert second.usage_metadata is not None
+ cache_read =
second.usage_metadata["input_token_details"].get("cache_read", 0)
+ assert cache_read > 0
+
+
class BadModel(BaseModel):
response: str
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.3.4/tests/unit_tests/chat_models/test_base.py
new/langchain_openai-1.3.5/tests/unit_tests/chat_models/test_base.py
--- old/langchain_openai-1.3.4/tests/unit_tests/chat_models/test_base.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.3.5/tests/unit_tests/chat_models/test_base.py
2020-02-02 01:00:00.000000000 +0100
@@ -1157,6 +1157,90 @@
assert result["total_tokens"] == 0
+def test__create_usage_metadata_cache_write_tokens() -> None:
+ """`cache_write_tokens` is surfaced under the standard `cache_creation`
key."""
+ usage_metadata = {
+ "completion_tokens": 15,
+ # OpenAI's `cache_write_tokens` maps to core's `cache_creation`
+ "prompt_tokens_details": {"cached_tokens": 50, "cache_write_tokens":
25},
+ "completion_tokens_details": None,
+ "prompt_tokens": 100,
+ "total_tokens": 115,
+ }
+ result = _create_usage_metadata(usage_metadata)
+ assert result["input_token_details"] == {
+ "cache_read": 50,
+ "cache_creation": 25,
+ }
+
+
+def test__create_usage_metadata_cache_read_only() -> None:
+ """Responses without `cache_write_tokens` emit no `cache_creation` key."""
+ usage_metadata = {
+ "completion_tokens": 15,
+ "prompt_tokens_details": {"cached_tokens": 50},
+ "completion_tokens_details": None,
+ "prompt_tokens": 100,
+ "total_tokens": 115,
+ }
+ result = _create_usage_metadata(usage_metadata)
+ assert result["input_token_details"] == {"cache_read": 50}
+
+
+def test__create_usage_metadata_cache_tokens_zero_retained() -> None:
+ """Explicit zero cache counts are retained (filtered on `None`, not
falsiness)."""
+ usage_metadata = {
+ "completion_tokens": 15,
+ "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 0},
+ "completion_tokens_details": None,
+ "prompt_tokens": 100,
+ "total_tokens": 115,
+ }
+ result = _create_usage_metadata(usage_metadata)
+ assert result["input_token_details"] == {
+ "cache_read": 0,
+ "cache_creation": 0,
+ }
+
+
+def test__create_usage_metadata_service_tier_excludes_cache_read_tokens() ->
None:
+ """Tier counts exclude cache reads but not overlapping cache writes."""
+ usage_metadata = {
+ "completion_tokens": 50,
+ "prompt_tokens_details": {
+ "cached_tokens": 256,
+ "cache_write_tokens": 3072,
+ },
+ "completion_tokens_details": {"reasoning_tokens": 10},
+ "prompt_tokens": 2304,
+ "total_tokens": 2354,
+ }
+ result = _create_usage_metadata(usage_metadata, service_tier="priority")
+ assert result["input_token_details"] == {
+ "priority_cache_read": 256,
+ "priority_cache_creation": 3072,
+ "priority": 2048,
+ }
+ assert result["output_token_details"] == {
+ "priority_reasoning": 10,
+ "priority": 40, # 50 - 10 (reasoning)
+ }
+
+
+def test__create_usage_metadata_service_tier_without_detail_fields() -> None:
+ """Tier arithmetic tolerates missing cache/reasoning fields (no
TypeError)."""
+ usage_metadata = {
+ "completion_tokens": 50,
+ "prompt_tokens_details": None,
+ "completion_tokens_details": None,
+ "prompt_tokens": 100,
+ "total_tokens": 150,
+ }
+ result = _create_usage_metadata(usage_metadata, service_tier="flex")
+ assert result["input_token_details"] == {"flex": 100}
+ assert result["output_token_details"] == {"flex": 50}
+
+
def test__create_usage_metadata_responses() -> None:
response_usage_metadata = {
"input_tokens": 100,
@@ -1176,6 +1260,68 @@
)
+def test__create_usage_metadata_responses_cache_write_tokens() -> None:
+ """Responses usage maps `cache_write_tokens` to the `cache_creation`
key."""
+ response_usage_metadata = {
+ "input_tokens": 100,
+ "input_tokens_details": {"cached_tokens": 50, "cache_write_tokens":
25},
+ "output_tokens": 50,
+ "output_tokens_details": {"reasoning_tokens": 10},
+ "total_tokens": 150,
+ }
+ result = _create_usage_metadata_responses(response_usage_metadata)
+
+ assert result == UsageMetadata(
+ output_tokens=50,
+ input_tokens=100,
+ total_tokens=150,
+ input_token_details={"cache_read": 50, "cache_creation": 25},
+ output_token_details={"reasoning": 10},
+ )
+
+
+def test__create_usage_metadata_responses_service_tier_cache_write_overlap()
-> None:
+ """Tier counts exclude cache reads but not overlapping cache writes."""
+ response_usage_metadata = {
+ "input_tokens": 2304,
+ "input_tokens_details": {
+ "cached_tokens": 256,
+ "cache_write_tokens": 3072,
+ },
+ "output_tokens": 50,
+ "output_tokens_details": {"reasoning_tokens": 10},
+ "total_tokens": 2354,
+ }
+ result = _create_usage_metadata_responses(
+ response_usage_metadata, service_tier="flex"
+ )
+ assert result["input_token_details"] == {
+ "flex_cache_read": 256,
+ "flex_cache_creation": 3072,
+ "flex": 2048,
+ }
+ assert result["output_token_details"] == {
+ "flex_reasoning": 10,
+ "flex": 40, # 50 - 10 (reasoning)
+ }
+
+
+def test__create_usage_metadata_responses_service_tier_without_detail_fields()
-> None:
+ """Tier arithmetic tolerates missing cache/reasoning fields (no
TypeError)."""
+ response_usage_metadata = {
+ "input_tokens": 100,
+ "input_tokens_details": None,
+ "output_tokens": 50,
+ "output_tokens_details": None,
+ "total_tokens": 150,
+ }
+ result = _create_usage_metadata_responses(
+ response_usage_metadata, service_tier="priority"
+ )
+ assert result["input_token_details"] == {"priority": 100}
+ assert result["output_token_details"] == {"priority": 50}
+
+
def test__resize_caps_dimensions_preserving_ratio() -> None:
"""Larger side capped at 2048 then smaller at 768 keeping aspect ratio."""
assert _resize(2048, 4096) == (768, 1536)
@@ -1889,7 +2035,9 @@
input_tokens=10,
output_tokens=3,
total_tokens=13,
- input_tokens_details=InputTokensDetails(cached_tokens=0),
+ input_tokens_details=InputTokensDetails(
+ cache_write_tokens=0, cached_tokens=0
+ ),
output_tokens_details=OutputTokensDetails(reasoning_tokens=0),
),
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.3.4/tests/unit_tests/chat_models/test_prompt_cache_key.py
new/langchain_openai-1.3.5/tests/unit_tests/chat_models/test_prompt_cache_key.py
---
old/langchain_openai-1.3.4/tests/unit_tests/chat_models/test_prompt_cache_key.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_openai-1.3.5/tests/unit_tests/chat_models/test_prompt_cache_key.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,9 +1,12 @@
"""Unit tests for prompt_cache_key parameter."""
-from langchain_core.messages import HumanMessage
+from langchain_core.messages import HumanMessage, ToolMessage
+from langchain_core.messages.content import create_text_block
from langchain_openai import ChatOpenAI
+MODEL_NAME = "gpt-5.5"
+
def test_prompt_cache_key_parameter_inclusion() -> None:
"""Test that prompt_cache_key parameter is properly included in request
payload."""
@@ -85,3 +88,332 @@
# prompt_cache_key should be present regardless of API type
assert "prompt_cache_key" in payload
assert payload["prompt_cache_key"] == "responses-api-cache-v1"
+
+
+def test_prompt_cache_options_and_retention_request_payload() -> None:
+ """Per-invocation cache options/retention flow into the request payload."""
+ chat = ChatOpenAI(model=MODEL_NAME, max_completion_tokens=10)
+ messages = [HumanMessage("Hello")]
+
+ payload = chat._get_request_payload(
+ messages,
+ prompt_cache_options={"mode": "explicit", "ttl": "30m"},
+ prompt_cache_retention="24h",
+ )
+
+ assert payload["prompt_cache_options"] == {"mode": "explicit", "ttl":
"30m"}
+ assert payload["prompt_cache_retention"] == "24h"
+
+
+def test_prompt_cache_options_and_retention_responses_api_payload() -> None:
+ """Cache options/retention survive Responses API payload construction."""
+ chat = ChatOpenAI(
+ model=MODEL_NAME,
+ use_responses_api=True,
+ output_version="responses/v1",
+ max_completion_tokens=10,
+ )
+ messages = [HumanMessage("Hello")]
+
+ payload = chat._get_request_payload(
+ messages,
+ prompt_cache_options={"mode": "explicit", "ttl": "30m"},
+ prompt_cache_retention="24h",
+ )
+
+ assert payload["prompt_cache_options"] == {"mode": "explicit", "ttl":
"30m"}
+ assert payload["prompt_cache_retention"] == "24h"
+
+
+def test_prompt_cache_options_init_param() -> None:
+ """Model-level cache options flow into the payload and can be
overridden."""
+ chat = ChatOpenAI(
+ model=MODEL_NAME,
+ max_completion_tokens=10,
+ prompt_cache_options={"mode": "explicit", "ttl": "30m"},
+ model_kwargs={"prompt_cache_retention": "24h"},
+ )
+ messages = [HumanMessage("Hello")]
+
+ payload = chat._get_request_payload(messages)
+ override_payload = chat._get_request_payload(
+ messages, prompt_cache_options={"mode": "implicit"}
+ )
+
+ assert payload["prompt_cache_options"] == {"mode": "explicit", "ttl":
"30m"}
+ assert payload["prompt_cache_retention"] == "24h"
+ assert override_payload["prompt_cache_options"] == {"mode": "implicit"}
+
+
+def test_prompt_cache_breakpoint_chat_completions_text_block() -> None:
+ """A `prompt_cache_breakpoint` on a text block is preserved for Chat
Completions."""
+ chat = ChatOpenAI(model=MODEL_NAME, max_completion_tokens=10)
+ messages = [
+ HumanMessage(
+ content=[
+ {
+ "type": "text",
+ "text": "Stable prefix",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ }
+ ]
+ )
+ ]
+
+ payload = chat._get_request_payload(messages)
+
+ assert payload["messages"][0]["content"][0]["prompt_cache_breakpoint"] == {
+ "mode": "explicit"
+ }
+
+
+def test_prompt_cache_breakpoint_chat_completions_tool_message() -> None:
+ """A cache breakpoint on a tool result text block is preserved."""
+ chat = ChatOpenAI(model=MODEL_NAME, max_completion_tokens=10)
+ messages = [
+ ToolMessage(
+ tool_call_id="call_123",
+ content_blocks=[
+ {
+ "type": "text",
+ "text": "Stable tool result",
+ "extras": {"prompt_cache_breakpoint": {"mode":
"explicit"}},
+ }
+ ],
+ )
+ ]
+
+ payload = chat._get_request_payload(messages)
+
+ assert payload["messages"][0]["content"] == [
+ {
+ "type": "text",
+ "text": "Stable tool result",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ }
+ ]
+
+
+def test_prompt_cache_breakpoint_responses_api_converted_blocks() -> None:
+ """`prompt_cache_breakpoint` survives Responses API conversion for each
block."""
+ chat = ChatOpenAI(
+ model=MODEL_NAME,
+ use_responses_api=True,
+ output_version="responses/v1",
+ max_completion_tokens=10,
+ )
+ messages = [
+ HumanMessage(
+ content=[
+ {
+ "type": "text",
+ "text": "Stable text prefix",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "image_url",
+ "image_url": {"url": "https://example.com/image.png"},
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "file",
+ "file": {"file_id": "file_123"},
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ ]
+ )
+ ]
+
+ payload = chat._get_request_payload(messages)
+ content = payload["input"][0]["content"]
+
+ assert content == [
+ {
+ "type": "input_text",
+ "text": "Stable text prefix",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "input_image",
+ "image_url": "https://example.com/image.png",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "input_file",
+ "file_id": "file_123",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ ]
+
+
+def test_prompt_cache_breakpoint_chat_completions_content_blocks() -> None:
+ """Standard content block cache breakpoints reach Chat Completions."""
+ chat = ChatOpenAI(model=MODEL_NAME, output_version="v1",
max_completion_tokens=10)
+ message = HumanMessage(
+ content_blocks=[
+ create_text_block(
+ "Stable text prefix",
+ prompt_cache_breakpoint={"mode": "explicit"},
+ ),
+ {
+ "type": "image",
+ "url": "https://example.com/image.png",
+ "extras": {"prompt_cache_breakpoint": {"mode": "explicit"}},
+ },
+ {
+ "type": "file",
+ "file_id": "file_123",
+ "extras": {"prompt_cache_breakpoint": {"mode": "explicit"}},
+ },
+ ]
+ )
+
+ payload = chat._get_request_payload([message])
+
+ assert payload["messages"][0]["content"] == [
+ {
+ "type": "text",
+ "text": "Stable text prefix",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "image_url",
+ "image_url": {"url": "https://example.com/image.png"},
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "file",
+ "file": {"file_id": "file_123"},
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ ]
+
+
+def test_prompt_cache_breakpoint_responses_api_content_blocks() -> None:
+ """Standard content block cache breakpoints reach the Responses API."""
+ chat = ChatOpenAI(
+ model=MODEL_NAME,
+ use_responses_api=True,
+ output_version="v1",
+ max_completion_tokens=10,
+ )
+ message = HumanMessage(
+ content_blocks=[
+ {
+ "type": "text",
+ "text": "Stable text prefix",
+ "extras": {"prompt_cache_breakpoint": {"mode": "explicit"}},
+ },
+ {
+ "type": "image",
+ "url": "https://example.com/image.png",
+ "extras": {"prompt_cache_breakpoint": {"mode": "explicit"}},
+ },
+ {
+ "type": "file",
+ "file_id": "file_123",
+ "extras": {"prompt_cache_breakpoint": {"mode": "explicit"}},
+ },
+ ]
+ )
+
+ payload = chat._get_request_payload([message])
+
+ assert payload["input"][0]["content"] == [
+ {
+ "type": "input_text",
+ "text": "Stable text prefix",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "input_image",
+ "image_url": "https://example.com/image.png",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "input_file",
+ "file_id": "file_123",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ ]
+
+
+def test_prompt_cache_breakpoint_top_level_on_data_blocks() -> None:
+ """A top-level `prompt_cache_breakpoint` on a data block is preserved."""
+ chat = ChatOpenAI(model=MODEL_NAME, max_completion_tokens=10)
+ message = HumanMessage(
+ content=[
+ {
+ "type": "image",
+ "url": "https://example.com/image.png",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "file",
+ "file_id": "file_123",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ ]
+ )
+
+ payload = chat._get_request_payload([message])
+
+ assert payload["messages"][0]["content"] == [
+ {
+ "type": "image_url",
+ "image_url": {"url": "https://example.com/image.png"},
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ {
+ "type": "file",
+ "file": {"file_id": "file_123"},
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ },
+ ]
+
+
+def test_prompt_cache_breakpoint_preserves_falsy_value() -> None:
+ """A present-but-falsy breakpoint value is copied (membership, not
truthiness)."""
+ chat = ChatOpenAI(model=MODEL_NAME, max_completion_tokens=10)
+ message = HumanMessage(
+ content=[
+ {
+ "type": "text",
+ "text": "Stable prefix",
+ "extras": {"prompt_cache_breakpoint": None},
+ }
+ ]
+ )
+
+ payload = chat._get_request_payload([message])
+
+ block = payload["messages"][0]["content"][0]
+ assert "prompt_cache_breakpoint" in block
+ assert block["prompt_cache_breakpoint"] is None
+
+
+def test_prompt_cache_breakpoint_text_block_drops_other_extras() -> None:
+ """Promoting a breakpoint from `extras` drops the block's other
`extras`."""
+ chat = ChatOpenAI(model=MODEL_NAME, max_completion_tokens=10)
+ message = HumanMessage(
+ content=[
+ {
+ "type": "text",
+ "text": "Stable prefix",
+ "extras": {
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ "unsupported": "value",
+ },
+ }
+ ]
+ )
+
+ payload = chat._get_request_payload([message])
+
+ assert payload["messages"][0]["content"] == [
+ {
+ "type": "text",
+ "text": "Stable prefix",
+ "prompt_cache_breakpoint": {"mode": "explicit"},
+ }
+ ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.3.4/tests/unit_tests/chat_models/test_responses_stream.py
new/langchain_openai-1.3.5/tests/unit_tests/chat_models/test_responses_stream.py
---
old/langchain_openai-1.3.4/tests/unit_tests/chat_models/test_responses_stream.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_openai-1.3.5/tests/unit_tests/chat_models/test_responses_stream.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,7 @@
from __future__ import annotations
import copy
+import logging
from typing import Any, cast
from unittest.mock import MagicMock, patch
@@ -608,7 +609,9 @@
truncation="disabled",
usage=ResponseUsage(
input_tokens=13,
- input_tokens_details=InputTokensDetails(cached_tokens=0),
+ input_tokens_details=InputTokensDetails(
+ cache_write_tokens=0, cached_tokens=0
+ ),
output_tokens=71,
output_tokens_details=OutputTokensDetails(reasoning_tokens=64),
total_tokens=84,
@@ -1004,7 +1007,9 @@
truncation="disabled",
usage=ResponseUsage(
input_tokens=10,
- input_tokens_details=InputTokensDetails(cached_tokens=0),
+ input_tokens_details=InputTokensDetails(
+ cache_write_tokens=0, cached_tokens=0
+ ),
output_tokens=20,
output_tokens_details=OutputTokensDetails(reasoning_tokens=0),
total_tokens=30,
@@ -1078,15 +1083,15 @@
("event_index", "event_type"),
[(0, ResponseCreatedEvent), (46, ResponseCompletedEvent)],
)
-def test_responses_stream_normalizes_in_memory_prompt_cache_retention(
- event_index: int, event_type: type
+def test_responses_stream_validates_in_memory_prompt_cache_retention(
+ event_index: int, event_type: type, caplog: pytest.LogCaptureFixture
) -> None:
"""`prompt_cache_retention="in_memory"` from the API must not abort
streams.
- The API emits the underscore form while older `openai` packages declare
only
- `"in-memory"` in the Literal (openai-python#2883). `_coerce_chunk_response`
- should normalize so both the `response.created` and `response.completed`
- handlers can validate successfully.
+ The OpenAI SDK accepts the underscore form, so both the `response.created`
+ and `response.completed` handlers should validate it via the strict
+ `Response.model_validate` path -- not the non-validating `model_construct`
+ fallback (which would also complete the stream, masking a regression).
"""
stream = copy.deepcopy(responses_stream)
target = stream[event_index]
@@ -1105,12 +1110,19 @@
mock_client.responses.create = mock_create
full: BaseMessageChunk | None = None
- with patch.object(llm, "root_client", mock_client):
+ with (
+ caplog.at_level(logging.WARNING),
+ patch.object(llm, "root_client", mock_client),
+ ):
for chunk in llm.stream("test"):
assert isinstance(chunk, AIMessageChunk)
full = chunk if full is None else full + chunk
assert isinstance(full, AIMessageChunk)
assert full.id == "resp_123"
+ # `in_memory` must validate cleanly: no fallback to the non-validating
+ # construct. Otherwise this test would pass even if the SDK rejected the
+ # value, giving false confidence in the removed normalization workaround.
+ assert "falling back to non-validating construct" not in caplog.text
# The completed event drives usage/metadata aggregation, so assert it
# survived coercion when that branch is exercised.
if event_type is ResponseCompletedEvent:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_openai-1.3.4/uv.lock
new/langchain_openai-1.3.5/uv.lock
--- old/langchain_openai-1.3.4/uv.lock 2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.3.5/uv.lock 2020-02-02 01:00:00.000000000 +0100
@@ -727,7 +727,7 @@
[[package]]
name = "langchain-openai"
-version = "1.3.4"
+version = "1.3.5"
source = { editable = "." }
dependencies = [
{ name = "langchain-core" },
@@ -770,7 +770,7 @@
[package.metadata]
requires-dist = [
{ name = "langchain-core", editable = "../../core" },
- { name = "openai", specifier = ">=2.26.0,<3.0.0" },
+ { name = "openai", specifier = ">=2.45.0,<3.0.0" },
{ name = "tiktoken", specifier = ">=0.7.0,<1.0.0" },
]
@@ -1272,7 +1272,7 @@
[[package]]
name = "openai"
-version = "2.32.0"
+version = "2.45.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -1284,9 +1284,9 @@
{ name = "tqdm" },
{ name = "typing-extensions" },
]
-sdist = { url =
"https://files.pythonhosted.org/packages/ed/59/bdcc6b759b8c42dd73afaf5bf8f902c04b37987a5514dbc1c64dba390fef/openai-2.32.0.tar.gz",
hash =
"sha256:c54b27a9e4cb8d51f0dd94972ffd1a04437efeb259a9e60d8922b8bd26fe55e0", size
= 693286, upload-time = "2026-04-15T22:28:19.434Z" }
+sdist = { url =
"https://files.pythonhosted.org/packages/78/60/d4219875289b11d2c2f7da93c36283da224a2e55865ed865ab64e0ce9217/openai-2.45.0.tar.gz",
hash =
"sha256:10d34ca9c5643bce775852fddbfc172505cb1d4de1ccd101696c3ecff358765d", size
= 1109653, upload-time = "2026-07-09T18:02:44.091Z" }
wheels = [
- { url =
"https://files.pythonhosted.org/packages/1e/c1/d6e64ccd0536bf616556f0cad2b6d94a8125f508d25cfd814b1d2db4e2f1/openai-2.32.0-py3-none-any.whl",
hash =
"sha256:4dcc9badeb4bf54ad0d187453742f290226d30150890b7890711bda4f32f192f", size
= 1162570, upload-time = "2026-04-15T22:28:17.714Z" },
+ { url =
"https://files.pythonhosted.org/packages/f1/b0/2291689e3ec4723fbf5bbf3b54afcd7b160f9ddc98ca7aedfd0132af5677/openai-2.45.0-py3-none-any.whl",
hash =
"sha256:5df105f5f8c9b711fcb9d06d2d3888cebc82506db216484c14a4e53cdf651777", size
= 1629470, upload-time = "2026-07-09T18:02:42.21Z" },
]
[[package]]