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-09-24 22:56:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langchain-openai (Old)
and /work/SRC/openSUSE:Factory/.python-langchain-openai.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-langchain-openai"
Thu Sep 24 22:56:36 2026 rev:14 rq:1380234 version:1.6.6
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-langchain-openai/python-langchain-openai.changes
2026-09-22 21:58:35.431507076 +0200
+++
/work/SRC/openSUSE:Factory/.python-langchain-openai.new.383539/python-langchain-openai.changes
2026-09-24 22:59:22.366028656 +0200
@@ -1,0 +2,6 @@
+Wed Sep 23 18:43:25 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to version 1.6.6 (includes 1.6.4; patch
+ releases, no upstream changelogs)
+
+-------------------------------------------------------------------
Old:
----
langchain_openai-1.6.3.tar.gz
New:
----
langchain_openai-1.6.6.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-langchain-openai.spec ++++++
--- /var/tmp/diff_new_pack.T5L6Jz/_old 2026-09-24 22:59:23.066057929 +0200
+++ /var/tmp/diff_new_pack.T5L6Jz/_new 2026-09-24 22:59:23.067057971 +0200
@@ -17,7 +17,7 @@
Name: python-langchain-openai
-Version: 1.6.3
+Version: 1.6.6
Release: 0
Summary: An integration package connecting OpenAI and LangChain
License: MIT
++++++ langchain_openai-1.6.3.tar.gz -> langchain_openai-1.6.6.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_openai-1.6.3/PKG-INFO
new/langchain_openai-1.6.6/PKG-INFO
--- old/langchain_openai-1.6.3/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.6.6/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.5
Name: langchain-openai
-Version: 1.6.3
+Version: 1.6.6
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/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_openai-1.6.3/langchain_openai/_version.py
new/langchain_openai-1.6.6/langchain_openai/_version.py
--- old/langchain_openai-1.6.3/langchain_openai/_version.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_openai-1.6.6/langchain_openai/_version.py 2020-02-02
01:00:00.000000000 +0100
@@ -1,3 +1,3 @@
"""Version information for `langchain-openai`."""
-__version__ = "1.6.3"
+__version__ = "1.6.6"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.6.3/langchain_openai/chat_models/_compat.py
new/langchain_openai-1.6.6/langchain_openai/chat_models/_compat.py
--- old/langchain_openai-1.6.3/langchain_openai/chat_models/_compat.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.6.6/langchain_openai/chat_models/_compat.py
2020-02-02 01:00:00.000000000 +0100
@@ -74,6 +74,17 @@
from langchain_core.messages import AIMessage, is_data_content_block
from langchain_core.messages import content as types
+
+def _unwrap_non_standard(block: dict) -> dict:
+ """Unwrap a provider-native dictionary from a standard content block."""
+ if block.get("type") == "non_standard" and isinstance(
+ value := block.get("value"),
+ dict,
+ ):
+ return value
+ return block
+
+
_FUNCTION_CALL_IDS_MAP_KEY = "__openai_function_call_ids__"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.6.3/langchain_openai/chat_models/base.py
new/langchain_openai-1.6.6/langchain_openai/chat_models/base.py
--- old/langchain_openai-1.6.3/langchain_openai/chat_models/base.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.6.6/langchain_openai/chat_models/base.py
2020-02-02 01:00:00.000000000 +0100
@@ -163,6 +163,7 @@
_convert_from_v1_to_chat_completions,
_convert_from_v1_to_responses,
_convert_to_v03_ai_message,
+ _unwrap_non_standard,
)
from langchain_openai.data._profiles import _PROFILES
@@ -326,12 +327,79 @@
return content
+_ADDITIONAL_TOOLS_BLOCK_TYPE = "additional_tools"
+"""Responses API input item that adds tools partway through a conversation."""
+
+
+def _is_ai_role(role: str | None) -> bool:
+ """Return whether a message's role is the assistant's.
+
+ Assistant content is replayed model output, not a caller's instruction, so
it is
+ exempt from the placement checks a caller's own blocks are held to.
+ """
+ return str(role).lower().startswith("ai")
+
+
+def _is_system_role(role: str | None) -> bool:
+ """Return whether a message's role carries provider instructions.
+
+ `SystemMessage` reports `"system"` whether or not it is later emitted with
+ OpenAI's `developer` role, so one check covers both spellings.
+ """
+ return role in ("system", "developer")
+
+
+def _raise_if_additional_tools(content: Any, reason: str) -> None:
+ """Reject an `additional_tools` block that cannot work where it was placed.
+
+ The block only reaches the wire as a Responses top-level input item
carried on
+ a system message. Anywhere else it is this provider's own block type in a
+ position this provider forbids, which the error taxonomy makes loud rather
than
+ silent: nothing routes a request to the Responses API based on message
content,
+ so a silent drop would make the broken case the default outcome.
+
+ Args:
+ content: The message's content.
+ reason: Sentence explaining why this placement cannot work, and how to
fix
+ it. Appended to the error.
+
+ Raises:
+ ValueError: If an `additional_tools` block is present, in either
spelling.
+ """
+ if not isinstance(content, list):
+ return
+ for raw_block in content:
+ if (
+ isinstance(raw_block, dict)
+ and _unwrap_non_standard(raw_block).get("type")
+ == _ADDITIONAL_TOOLS_BLOCK_TYPE
+ ):
+ msg = f"`additional_tools` {reason}"
+ raise ValueError(msg)
+
+
def _format_message_content(
content: Any,
api: Literal["chat/completions", "responses"] = "chat/completions",
role: str | None = None,
) -> Any:
"""Format message content."""
+ if _is_ai_role(role):
+ # Replayed assistant output; `additional_tools` is also an output
item, so
+ # an echoed one must survive a round trip rather than abort the
request.
+ pass
+ elif not _is_system_role(role):
+ _raise_if_additional_tools(
+ content,
+ "must be carried on a `SystemMessage`. OpenAI restricts the input
item "
+ 'to `role: "developer"`, so it cannot be sent on any other
message.',
+ )
+ elif api == "chat/completions":
+ _raise_if_additional_tools(
+ content,
+ "requires the Responses API and cannot be sent via Chat
Completions. "
+ "Set `use_responses_api=True`.",
+ )
if content and isinstance(content, list):
formatted_content = []
for block in content:
@@ -3135,6 +3203,39 @@
See `bind_tools` for more.
+ ??? info "Mid-conversation tool additions"
+
+ ```python
+ from langchain_core.messages import HumanMessage, SystemMessage
+ from langchain_openai import ChatOpenAI
+
+ model = ChatOpenAI(model="gpt-6-astra", use_responses_api=True)
+ model.invoke(
+ [
+ HumanMessage("What time is it?"),
+ SystemMessage(
+ [
+ {
+ "type": "additional_tools",
+ "role": "developer",
+ "tools": [
+ {
+ "type": "function",
+ "name": "get_time",
+ "description": "Get the current time.",
+ "parameters": {
+ "type": "object",
+ "properties": {},
+ },
+ }
+ ],
+ }
+ ]
+ ),
+ ]
+ )
+ ```
+
??? info "Built-in (server-side) tools"
You can access [built-in
tools](https://platform.openai.com/docs/guides/tools?api-mode=responses)
@@ -4993,6 +5094,21 @@
new_blocks.append(block)
elif block["type"] in non_message_item_types:
input_.append(block)
+ elif block["type"] == _ADDITIONAL_TOOLS_BLOCK_TYPE:
+ if isinstance(lc_msg, SystemMessage):
+ input_.append(block)
+ elif _is_system_role(msg["role"]):
+ # System content is a closed set here, so an
unrecognized
+ # block is a mistake rather than something to forward.
+ # User content keeps its long-standing silent drop,
where
+ # the set is open and warning would be noise.
+ warnings.warn(
+ f"Content block {block['type']!r} was dropped from
a "
+ "system message: the Responses API has no input
item "
+ "of that type, so it cannot be placed in the
request.",
+ UserWarning,
+ stacklevel=2,
+ )
else:
pass
msg["content"] = new_blocks
@@ -5338,6 +5454,13 @@
response = _coerce_chunk_response(chunk.response)
id = response.id
response_metadata["id"] = response.id # Backwards compatibility
+ elif chunk.type == "response.failed":
+ response = _coerce_chunk_response(chunk.response)
+ error_msg = str(response.error or f"Response {response.id} failed.")
+ raise ValueError(error_msg)
+ elif chunk.type == "error":
+ error_msg = f"{chunk.code}: {chunk.message}" if chunk.code else
chunk.message
+ raise ValueError(error_msg)
elif chunk.type in ("response.completed", "response.incomplete"):
response = _coerce_chunk_response(chunk.response)
msg = cast(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.6.3/langchain_openai/data/_profiles.py
new/langchain_openai-1.6.6/langchain_openai/data/_profiles.py
--- old/langchain_openai-1.6.3/langchain_openai/data/_profiles.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.6.6/langchain_openai/data/_profiles.py
2020-02-02 01:00:00.000000000 +0100
@@ -1204,6 +1204,78 @@
"max",
],
},
+ "gpt-6-luna": {
+ "name": "GPT-6 Luna",
+ "release_date": "2026-09-22",
+ "last_updated": "2026-09-22",
+ "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,
+ "reasoning_effort_levels": [
+ "none",
+ "low",
+ "medium",
+ "high",
+ "xhigh",
+ "max",
+ ],
+ "reasoning_effort_default": "medium",
+ },
+ "gpt-6-sol": {
+ "name": "GPT-6 Sol",
+ "release_date": "2026-09-22",
+ "last_updated": "2026-09-22",
+ "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,
+ "reasoning_effort_levels": [
+ "none",
+ "low",
+ "medium",
+ "high",
+ "xhigh",
+ "max",
+ ],
+ "reasoning_effort_default": "medium",
+ },
"gpt-image-1": {
"name": "gpt-image-1",
"status": "deprecated",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.6.3/langchain_openai/data/profile_augmentations.toml
new/langchain_openai-1.6.6/langchain_openai/data/profile_augmentations.toml
--- old/langchain_openai-1.6.3/langchain_openai/data/profile_augmentations.toml
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.6.6/langchain_openai/data/profile_augmentations.toml
2020-02-02 01:00:00.000000000 +0100
@@ -117,3 +117,11 @@
[overrides."gpt-6-astra"]
reasoning_effort_levels = ["low", "medium", "high", "xhigh", "max"]
+
+[overrides."gpt-6-sol"]
+reasoning_effort_levels = ["none", "low", "medium", "high", "xhigh", "max"]
+reasoning_effort_default = "medium"
+
+[overrides."gpt-6-luna"]
+reasoning_effort_levels = ["none", "low", "medium", "high", "xhigh", "max"]
+reasoning_effort_default = "medium"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_openai-1.6.3/pyproject.toml
new/langchain_openai-1.6.6/pyproject.toml
--- old/langchain_openai-1.6.3/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
+++ new/langchain_openai-1.6.6/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
@@ -20,7 +20,7 @@
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
-version = "1.6.3"
+version = "1.6.6"
requires-python = ">=3.10.0,<4.0.0"
dependencies = [
"langchain-core>=1.6.4,<2.0.0",
Binary files
old/langchain_openai-1.6.3/tests/cassettes/test_system_additional_tools.yaml.gz
and
new/langchain_openai-1.6.6/tests/cassettes/test_system_additional_tools.yaml.gz
differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.6.3/tests/integration_tests/chat_models/test_responses_api.py
new/langchain_openai-1.6.6/tests/integration_tests/chat_models/test_responses_api.py
---
old/langchain_openai-1.6.3/tests/integration_tests/chat_models/test_responses_api.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_openai-1.6.6/tests/integration_tests/chat_models/test_responses_api.py
2020-02-02 01:00:00.000000000 +0100
@@ -21,6 +21,7 @@
BaseMessageChunk,
HumanMessage,
MessageLikeRepresentation,
+ SystemMessage,
ToolMessage,
)
from langchain_core.tools import tool
@@ -1960,3 +1961,34 @@
# v2 bridge's default `"stop"` synthesis; provider metadata now
# passes through unchanged.)
assert v1.response_metadata == v2.response_metadata
+
+
[email protected]
+def test_system_additional_tools() -> None:
+ model = ChatOpenAI(model="gpt-6-astra", use_responses_api=True)
+ response = model.invoke(
+ [
+ HumanMessage("What time is it?"),
+ SystemMessage(
+ [
+ {
+ "type": "additional_tools",
+ "role": "developer",
+ "tools": [
+ {
+ "type": "function",
+ "name": "get_time",
+ "description": "Get the current time.",
+ "parameters": {
+ "type": "object",
+ "properties": {},
+ },
+ }
+ ],
+ }
+ ]
+ ),
+ ]
+ )
+ assert isinstance(response, AIMessage)
+ assert response.tool_calls[0]["name"] == "get_time"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.6.3/tests/unit_tests/chat_models/test_base.py
new/langchain_openai-1.6.6/tests/unit_tests/chat_models/test_base.py
--- old/langchain_openai-1.6.3/tests/unit_tests/chat_models/test_base.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.6.6/tests/unit_tests/chat_models/test_base.py
2020-02-02 01:00:00.000000000 +0100
@@ -2,6 +2,7 @@
from __future__ import annotations
+import copy
import json
import warnings
from functools import partial
@@ -5507,6 +5508,223 @@
assert llm.openai_api_key.get_secret_value() == "provider-key"
+_ADDITIONAL_TOOLS_BLOCK = {
+ "type": "additional_tools",
+ "role": "developer",
+ "tools": [
+ {
+ "type": "function",
+ "name": "get_customer",
+ "description": "Look up a customer by ID.",
+ "parameters": {
+ "type": "object",
+ "properties": {"customer_id": {"type": "string"}},
+ "required": ["customer_id"],
+ "additionalProperties": False,
+ },
+ }
+ ],
+}
+_FOREIGN_TOOL_CHANGE_BLOCK = {
+ "type": "tool_removal",
+ "tool": {"type": "tool_reference", "name": "get_weather"},
+}
+
+
[email protected]("spelling", ["bare", "non_standard"])
+def test_additional_tools_block_becomes_input_item(spelling: str) -> None:
+ """An `additional_tools` block is hoisted to a top-level Responses input
item."""
+ block: dict = (
+ _ADDITIONAL_TOOLS_BLOCK
+ if spelling == "bare"
+ else {"type": "non_standard", "value": _ADDITIONAL_TOOLS_BLOCK}
+ )
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL, use_responses_api=True)
+ payload = llm._get_request_payload(
+ [
+ HumanMessage("Earlier question"),
+ AIMessage("Earlier answer", response_metadata={"id": "resp_123"}),
+ SystemMessage([{"type": "text", "text": "Be concise."}, block]),
+ HumanMessage("Next question"),
+ ]
+ )
+
+ # The item precedes the message it was carried on, matching how the
Responses
+ # API converter hoists every other non-message input item.
+ assert payload["input"][2] == _ADDITIONAL_TOOLS_BLOCK
+ assert payload["input"][3] == {
+ "role": "system",
+ "content": [{"type": "input_text", "text": "Be concise."}],
+ "type": "message",
+ }
+ assert payload["input"][4]["role"] == "user"
+
+
+def test_additional_tools_block_empties_message() -> None:
+ """A system message carrying only the block leaves no message behind."""
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL, use_responses_api=True)
+ payload = llm._get_request_payload(
+ [
+ HumanMessage("Earlier question"),
+ SystemMessage([_ADDITIONAL_TOOLS_BLOCK]),
+ ]
+ )
+
+ assert payload["input"] == [
+ {"role": "user", "content": "Earlier question", "type": "message"},
+ _ADDITIONAL_TOOLS_BLOCK,
+ ]
+
+
+def test_additional_tools_block_does_not_mutate_input_content() -> None:
+ """Hoisting the item must leave the caller's own message content
untouched."""
+ content: list[str | dict] = [
+ {"type": "text", "text": "Be concise."},
+ _ADDITIONAL_TOOLS_BLOCK,
+ ]
+ before = copy.deepcopy(content)
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL, use_responses_api=True)
+ llm._get_request_payload([HumanMessage("Earlier question"),
SystemMessage(content)])
+ assert content == before
+
+
[email protected]("spelling", ["bare", "non_standard"])
+def test_additional_tools_block_on_chat_completions_raises(spelling: str) ->
None:
+ """`additional_tools` requires the Responses API."""
+ block: dict = (
+ _ADDITIONAL_TOOLS_BLOCK
+ if spelling == "bare"
+ else {"type": "non_standard", "value": _ADDITIONAL_TOOLS_BLOCK}
+ )
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL)
+ with pytest.raises(ValueError, match="use_responses_api=True"):
+ llm._get_request_payload(
+ [HumanMessage("Earlier question"), SystemMessage([block])]
+ )
+
+
[email protected]("spelling", ["bare", "non_standard"])
[email protected]("use_responses_api", [True, False])
[email protected]("message_type", ["human", "tool"])
+def test_additional_tools_block_off_system_message_raises(
+ message_type: str,
+ use_responses_api: bool,
+ spelling: str,
+) -> None:
+ """OpenAI restricts the input item to `role: "developer"`.
+
+ Anywhere but a `SystemMessage` it is this provider's own block in a
position
+ this provider forbids, so it is raised rather than dropped. Guards against
+ client-supplied content blocks reaching the top-level input list.
+ """
+ block: dict = (
+ _ADDITIONAL_TOOLS_BLOCK
+ if spelling == "bare"
+ else {"type": "non_standard", "value": _ADDITIONAL_TOOLS_BLOCK}
+ )
+ message: BaseMessage = (
+ HumanMessage([block])
+ if message_type == "human"
+ else ToolMessage([block], tool_call_id="call_1")
+ )
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL,
use_responses_api=use_responses_api)
+ with pytest.raises(ValueError, match="SystemMessage"):
+ llm._get_request_payload([message])
+
+
+def test_additional_tools_block_on_ai_message_not_rejected() -> None:
+ """`additional_tools` is also a Responses *output* item.
+
+ Replaying an assistant turn that echoes one must not raise; handling the
output
+ form is out of scope, and out of scope should mean untouched, not fatal.
+ """
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL, use_responses_api=True)
+ llm._get_request_payload(
+ [
+ HumanMessage("Earlier question"),
+ AIMessage(
+ [{"type": "text", "text": "Sure."}, _ADDITIONAL_TOOLS_BLOCK],
+ response_metadata={"id": "resp_123"},
+ ),
+ ]
+ )
+
+
[email protected]("spelling", ["bare", "non_standard"])
+def test_unrecognized_system_block_dropped_with_warning(spelling: str) -> None:
+ """Responses system content is a closed set, so an unknown block is
reported."""
+ block: dict = (
+ _FOREIGN_TOOL_CHANGE_BLOCK
+ if spelling == "bare"
+ else {"type": "non_standard", "value": _FOREIGN_TOOL_CHANGE_BLOCK}
+ )
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL, use_responses_api=True)
+ with pytest.warns(UserWarning, match="tool_removal"):
+ payload = llm._get_request_payload(
+ [
+ HumanMessage("Earlier question"),
+ SystemMessage([{"type": "text", "text": "Be concise."},
block]),
+ ]
+ )
+
+ assert payload["input"][-1] == {
+ "role": "system",
+ "content": [{"type": "input_text", "text": "Be concise."}],
+ "type": "message",
+ }
+
+
+def test_unrecognized_user_block_dropped_silently() -> None:
+ """User content is an open set, so dropping an unknown block stays
quiet."""
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL, use_responses_api=True)
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ payload = llm._get_request_payload(
+ [
+ HumanMessage(
+ [
+ {"type": "text", "text": "Hello"},
+ {"type": "made_up_block", "foo": "bar"},
+ ]
+ )
+ ]
+ )
+
+ assert payload["input"] == [
+ {
+ "role": "user",
+ "content": [{"type": "input_text", "text": "Hello"}],
+ "type": "message",
+ },
+ ]
+
+
[email protected]("role", ["system", "human"])
+def test_unrecognized_block_forwarded_on_chat_completions(role: str) -> None:
+ """Chat Completions keeps its long-standing passthrough for unknown blocks.
+
+ `additional_tools` is the one system block it rejects, as that's a common
mistake
+ (needs responses api). Every other unknown block is passed through as the
caller
+ wrote it.
+ """
+ message = (
+ SystemMessage([_FOREIGN_TOOL_CHANGE_BLOCK])
+ if role == "system"
+ else HumanMessage([_FOREIGN_TOOL_CHANGE_BLOCK])
+ )
+ llm = ChatOpenAI(model=OPENAI_TEST_MODEL)
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ payload = llm._get_request_payload([message])
+
+ assert payload["messages"] == [
+ {
+ "role": role if role == "system" else "user",
+ "content": [_FOREIGN_TOOL_CHANGE_BLOCK],
+ }
+ ]
+
+
def test_configuration_update_block_becomes_input_item() -> None:
"""A `configuration_update` block is hoisted out of the message content.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_openai-1.6.3/tests/unit_tests/chat_models/test_responses_stream.py
new/langchain_openai-1.6.6/tests/unit_tests/chat_models/test_responses_stream.py
---
old/langchain_openai-1.6.3/tests/unit_tests/chat_models/test_responses_stream.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_openai-1.6.6/tests/unit_tests/chat_models/test_responses_stream.py
2020-02-02 01:00:00.000000000 +0100
@@ -13,6 +13,8 @@
ResponseContentPartAddedEvent,
ResponseContentPartDoneEvent,
ResponseCreatedEvent,
+ ResponseErrorEvent,
+ ResponseFailedEvent,
ResponseFunctionCallArgumentsDeltaEvent,
ResponseFunctionCallArgumentsDoneEvent,
ResponseFunctionToolCallItem,
@@ -30,6 +32,7 @@
ResponseTextDoneEvent,
)
from openai.types.responses.response import Response
+from openai.types.responses.response_error import ResponseError
from openai.types.responses.response_output_text import ResponseOutputText
from openai.types.responses.response_reasoning_item import Summary
from openai.types.responses.response_reasoning_summary_part_added_event import
(
@@ -50,7 +53,10 @@
from langchain_openai.chat_models.base import (
_convert_responses_chunk_to_generation_chunk,
)
-from tests.unit_tests.chat_models.test_base import MockSyncContextManager
+from tests.unit_tests.chat_models.test_base import (
+ MockAsyncContextManager,
+ MockSyncContextManager,
+)
MODEL = "gpt-5.4"
@@ -1231,3 +1237,68 @@
full = chunk if full is None else full + chunk
assert isinstance(full, AIMessageChunk)
assert full.id == "resp_123"
+
+
+def _failed_event(error: ResponseError | None) -> ResponseFailedEvent:
+ created = responses_stream[0]
+ assert isinstance(created, ResponseCreatedEvent)
+ response = created.response.model_copy(update={"status": "failed",
"error": error})
+ return ResponseFailedEvent(
+ response=response, sequence_number=1, type="response.failed"
+ )
+
+
+_FAILURE_CASES = [
+ (
+ _failed_event(ResponseError(code="server_error", message="Model
failed.")),
+ "server_error",
+ ),
+ (_failed_event(None), "Response resp_123 failed."),
+ (
+ ResponseErrorEvent(
+ type="error",
+ code="rate_limit_exceeded",
+ message="Rate limit reached.",
+ param=None,
+ sequence_number=1,
+ ),
+ "rate_limit_exceeded: Rate limit reached.",
+ ),
+]
+
+
[email protected](("failure_event", "match"), _FAILURE_CASES)
+def test_responses_stream_raises_on_failure(failure_event: Any, match: str) ->
None:
+ llm = ChatOpenAI(model=MODEL, use_responses_api=True)
+ mock_client = MagicMock()
+
+ def mock_create(*args: Any, **kwargs: Any) -> MockSyncContextManager:
+ return MockSyncContextManager([responses_stream[0], failure_event])
+
+ mock_client.responses.create = mock_create
+
+ with (
+ patch.object(llm, "root_client", mock_client),
+ pytest.raises(ValueError, match=match),
+ ):
+ list(llm.stream("test"))
+
+
[email protected](("failure_event", "match"), _FAILURE_CASES)
+async def test_responses_astream_raises_on_failure(
+ failure_event: Any, match: str
+) -> None:
+ llm = ChatOpenAI(model=MODEL, use_responses_api=True)
+ mock_client = MagicMock()
+
+ async def mock_create(*args: Any, **kwargs: Any) ->
MockAsyncContextManager:
+ return MockAsyncContextManager([responses_stream[0], failure_event])
+
+ mock_client.responses.create = mock_create
+
+ with (
+ patch.object(llm, "root_async_client", mock_client),
+ pytest.raises(ValueError, match=match),
+ ):
+ async for _ in llm.astream("test"):
+ pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_openai-1.6.3/uv.lock
new/langchain_openai-1.6.6/uv.lock
--- old/langchain_openai-1.6.3/uv.lock 2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_openai-1.6.6/uv.lock 2020-02-02 01:00:00.000000000 +0100
@@ -766,7 +766,7 @@
[[package]]
name = "langchain-openai"
-version = "1.6.3"
+version = "1.6.6"
source = { editable = "." }
dependencies = [
{ name = "certifi" },