Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-langchain-anthropic for
openSUSE:Factory checked in at 2026-11-07 23:33:13
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langchain-anthropic (Old)
and /work/SRC/openSUSE:Factory/.python-langchain-anthropic.new.3759290
(New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-langchain-anthropic"
Sat Nov 7 23:33:13 2026 rev:19 rq:1396003 version:1.7.10
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-langchain-anthropic/python-langchain-anthropic.changes
2026-11-04 19:49:58.090726989 +0100
+++
/work/SRC/openSUSE:Factory/.python-langchain-anthropic.new.3759290/python-langchain-anthropic.changes
2026-11-07 23:33:47.694477123 +0100
@@ -1,0 +2,11 @@
+Sat Nov 7 12:43:49 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 1.7.10:
+ * Require langchain-core >= 1.7.5 for refusal handling
+ * Support standardized refusal content blocks: skip refusal
+ input blocks (the Messages API has none), propagate
+ stop_reason refusal metadata, and raise ModelRefusalError on
+ refused structured output
+ * Fix streamed usage metadata handling when stream_usage is off
+
+-------------------------------------------------------------------
Old:
----
langchain_anthropic-1.7.9.tar.gz
New:
----
langchain_anthropic-1.7.10.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-langchain-anthropic.spec ++++++
--- /var/tmp/diff_new_pack.wYremT/_old 2026-11-07 23:33:48.433507659 +0100
+++ /var/tmp/diff_new_pack.wYremT/_new 2026-11-07 23:33:48.435507741 +0100
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-langchain-anthropic
-Version: 1.7.9
+Version: 1.7.10
Release: 0
Summary: Integration package connecting Claude (Anthropic) APIs and
LangChain
License: MIT
@@ -29,7 +29,7 @@
BuildRequires: fdupes
BuildRequires: python-rpm-macros
Requires: python-anthropic >= 0.120.0
-Requires: python-langchain-core >= 1.7.3
+Requires: python-langchain-core >= 1.7.5
Requires: python-pydantic >= 2.7.4
BuildArch: noarch
# SECTION test requirements
@@ -37,7 +37,7 @@
BuildRequires: %{python_module blockbuster}
BuildRequires: %{python_module defusedxml}
BuildRequires: %{python_module freezegun}
-BuildRequires: %{python_module langchain-core >= 1.7.3}
+BuildRequires: %{python_module langchain-core >= 1.7.5}
BuildRequires: %{python_module pydantic >= 2.7.4}
BuildRequires: %{python_module pytest-asyncio}
BuildRequires: %{python_module pytest-mock}
++++++ langchain_anthropic-1.7.9.tar.gz -> langchain_anthropic-1.7.10.tar.gz
++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_anthropic-1.7.9/PKG-INFO
new/langchain_anthropic-1.7.10/PKG-INFO
--- old/langchain_anthropic-1.7.9/PKG-INFO 2020-02-02 01:00:00.000000000
+0100
+++ new/langchain_anthropic-1.7.10/PKG-INFO 2020-02-02 01:00:00.000000000
+0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.5
Name: langchain-anthropic
-Version: 1.7.9
+Version: 1.7.10
Summary: Integration package connecting Claude (Anthropic) APIs and LangChain
Project-URL: Homepage,
https://docs.langchain.com/oss/python/integrations/providers/anthropic
Project-URL: Documentation,
https://reference.langchain.com/python/integrations/langchain_anthropic/
@@ -24,7 +24,7 @@
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: <4.0.0,>=3.10.0
Requires-Dist: anthropic>=0.120.0
-Requires-Dist: langchain-core<2.0.0,>=1.7.3
+Requires-Dist: langchain-core<2.0.0,>=1.7.5
Requires-Dist: pydantic>=2.7.4
Description-Content-Type: text/markdown
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_anthropic-1.7.9/langchain_anthropic/_compat.py
new/langchain_anthropic-1.7.10/langchain_anthropic/_compat.py
--- old/langchain_anthropic-1.7.9/langchain_anthropic/_compat.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_anthropic-1.7.10/langchain_anthropic/_compat.py
2020-02-02 01:00:00.000000000 +0100
@@ -122,6 +122,10 @@
new_block = {"text": block.get("text", ""), "type": "text"}
new_content.append(new_block)
+ elif block["type"] == "refusal":
+ # The Messages API has no refusal input block.
+ continue
+
elif block["type"] == "tool_call":
tool_use_block = {
"type": "tool_use",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_anthropic-1.7.9/langchain_anthropic/_version.py
new/langchain_anthropic-1.7.10/langchain_anthropic/_version.py
--- old/langchain_anthropic-1.7.9/langchain_anthropic/_version.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_anthropic-1.7.10/langchain_anthropic/_version.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,3 +1,3 @@
"""Version information for `langchain-anthropic`."""
-__version__ = "1.7.9"
+__version__ = "1.7.10"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_anthropic-1.7.9/langchain_anthropic/chat_models.py
new/langchain_anthropic-1.7.10/langchain_anthropic/chat_models.py
--- old/langchain_anthropic-1.7.9/langchain_anthropic/chat_models.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_anthropic-1.7.10/langchain_anthropic/chat_models.py
2020-02-02 01:00:00.000000000 +0100
@@ -786,6 +786,9 @@
!= "anthropic"
):
continue
+ # The Messages API has no refusal input block.
+ if block["type"] == "refusal":
+ continue
if block["type"] == "image_url":
# convert format
source = _format_image(block["image_url"]["url"])
@@ -2488,8 +2491,10 @@
message_chunk = AIMessageChunk(content=[content_block])
# Process final usage metadata and completion info
- elif event.type == "message_delta" and stream_usage:
- usage_metadata = _create_usage_metadata(event.usage)
+ elif event.type == "message_delta":
+ usage_metadata = (
+ _create_usage_metadata(event.usage) if stream_usage else None
+ )
response_metadata = {
"stop_reason": event.delta.stop_reason,
"stop_sequence": event.delta.stop_sequence,
@@ -2560,7 +2565,13 @@
):
# TODO: dump all `data` with `mode="json"`
llm_output["container"]["expires_at"] = expires_at.isoformat()
- response_metadata = {"model_provider": "anthropic"}
+ response_metadata: dict[str, Any] = {"model_provider": "anthropic"}
+ if llm_output.get("stop_reason") == "refusal":
+ # Needed on the message itself so `output_version="v1"` content,
which
+ # is built before `llm_output` is merged, includes the refusal
block.
+ response_metadata["stop_reason"] = "refusal"
+ if stop_details := llm_output.get("stop_details"):
+ response_metadata["stop_details"] = stop_details
if "model" in llm_output and "model_name" not in llm_output:
llm_output["model_name"] = llm_output["model"]
if (
@@ -2909,6 +2920,9 @@
The final output is always a `dict` with keys `'raw'`,
`'parsed'`, and
`'parsing_error'`.
+
+ If the model refuses, `ModelRefusalError` is raised, or
returned as
+ `'parsing_error'` when `include_raw` is `True`.
method: The structured output method to use. Options are:
- `'function_calling'` (default): Use forced tool calling to
get
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_anthropic-1.7.9/pyproject.toml
new/langchain_anthropic-1.7.10/pyproject.toml
--- old/langchain_anthropic-1.7.9/pyproject.toml 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_anthropic-1.7.10/pyproject.toml 2020-02-02
01:00:00.000000000 +0100
@@ -20,11 +20,11 @@
"Topic :: Scientific/Engineering :: Artificial Intelligence",
]
-version = "1.7.9"
+version = "1.7.10"
requires-python = ">=3.10.0,<4.0.0"
dependencies = [
"anthropic>=0.120.0",
- "langchain-core>=1.7.3,<2.0.0",
+ "langchain-core>=1.7.5,<2.0.0",
"pydantic>=2.7.4",
]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_anthropic-1.7.9/tests/unit_tests/test_chat_models.py
new/langchain_anthropic-1.7.10/tests/unit_tests/test_chat_models.py
--- old/langchain_anthropic-1.7.9/tests/unit_tests/test_chat_models.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_anthropic-1.7.10/tests/unit_tests/test_chat_models.py
2020-02-02 01:00:00.000000000 +0100
@@ -25,6 +25,7 @@
ModelNotFoundError,
ModelPermissionDeniedError,
ModelRateLimitError,
+ ModelRefusalError,
ModelTimeoutError,
)
from langchain_core.messages import (
@@ -36,6 +37,7 @@
ToolMessage,
)
from langchain_core.messages.content import create_text_block
+from langchain_core.outputs import ChatGenerationChunk
from langchain_core.runnables import RunnableBinding
from langchain_core.tools import BaseTool, tool
from langchain_core.tracers._streaming import _V2StreamingCallbackHandler
@@ -6426,3 +6428,181 @@
)
payload = llm._get_request_payload([HumanMessage("Hello, world!")])
assert "betas" not in payload
+
+
+class _RefusalSchema(BaseModel):
+ answer: str
+
+
+def _refusal_message() -> Message:
+ return Message.model_validate(
+ {
+ "id": "msg_refusal",
+ "content": [],
+ "model": MODEL_NAME,
+ "role": "assistant",
+ "stop_reason": "refusal",
+ "stop_sequence": None,
+ "stop_details": {
+ "type": "refusal",
+ "category": "cyber",
+ "explanation": "This request may enable malware development.",
+ },
+ "usage": {"input_tokens": 10, "output_tokens": 0},
+ "type": "message",
+ }
+ )
+
+
+def _patch_refusal_response() -> Any:
+ raw_response = MagicMock()
+ raw_response.parse.return_value = _refusal_message()
+ return patch.object(ChatAnthropic, "_create", return_value=raw_response)
+
+
+def _patch_refusal_response_async() -> Any:
+ raw_response = MagicMock()
+ raw_response.parse.return_value = _refusal_message()
+
+ async def _acreate(*_: Any, **__: Any) -> Any:
+ return raw_response
+
+ return patch.object(ChatAnthropic, "_acreate", side_effect=_acreate)
+
+
[email protected]("method", ["function_calling", "json_schema"])
[email protected]("schema", [_RefusalSchema,
_RefusalSchema.model_json_schema()])
+def test_with_structured_output_raises_model_refusal_error(
+ method: Literal["function_calling", "json_schema"], schema: Any
+) -> None:
+ llm = ChatAnthropic(model=MODEL_NAME)
+ structured = llm.with_structured_output(schema, method=method)
+
+ with _patch_refusal_response(), pytest.raises(ModelRefusalError) as
exc_info:
+ structured.invoke("Write a keylogger")
+
+ err = exc_info.value
+ assert str(err) == (
+ "Model refused to produce the requested output (category='cyber'): "
+ "This request may enable malware development."
+ )
+ assert isinstance(err.raw_message, AIMessage)
+ assert err.is_retryable is False
+
+
[email protected]("method", ["function_calling", "json_schema"])
+def test_with_structured_output_include_raw_reports_refusal(
+ method: Literal["function_calling", "json_schema"],
+) -> None:
+ llm = ChatAnthropic(model=MODEL_NAME)
+ structured = llm.with_structured_output(
+ _RefusalSchema, method=method, include_raw=True
+ )
+
+ with _patch_refusal_response():
+ result = structured.invoke("Write a keylogger")
+
+ assert isinstance(result, dict)
+ assert result["parsed"] is None
+ assert isinstance(result["parsing_error"], ModelRefusalError)
+ assert "category='cyber'" in str(result["parsing_error"])
+ assert result["raw"].response_metadata["stop_reason"] == "refusal"
+
+
+async def test_with_structured_output_raises_model_refusal_error_async() ->
None:
+ llm = ChatAnthropic(model=MODEL_NAME)
+ structured = llm.with_structured_output(_RefusalSchema,
method="json_schema")
+
+ with _patch_refusal_response_async(), pytest.raises(ModelRefusalError):
+ await structured.ainvoke("Write a keylogger")
+
+
+def test_invoke_does_not_raise_on_refusal() -> None:
+ llm = ChatAnthropic(model=MODEL_NAME)
+
+ with _patch_refusal_response():
+ result = llm.invoke("Write a keylogger")
+
+ assert result.response_metadata["stop_reason"] == "refusal"
+ assert result.response_metadata["stop_details"]["category"] == "cyber"
+
+
+def test_with_structured_output_parses_when_not_refused() -> None:
+ llm = ChatAnthropic(model=MODEL_NAME)
+ structured = llm.with_structured_output(_RefusalSchema,
method="json_schema")
+ raw_response = MagicMock()
+ raw_response.parse.return_value = Message.model_validate(
+ {
+ "id": "msg_ok",
+ "content": [{"type": "text", "text": '{"answer": "42"}'}],
+ "model": MODEL_NAME,
+ "role": "assistant",
+ "stop_reason": "end_turn",
+ "stop_sequence": None,
+ "usage": {"input_tokens": 10, "output_tokens": 5},
+ "type": "message",
+ }
+ )
+
+ with patch.object(ChatAnthropic, "_create", return_value=raw_response):
+ assert structured.invoke("What is 6 x 7?") ==
_RefusalSchema(answer="42")
+
+
+def _stream_chunks(*, refuse: bool) -> list[ChatGenerationChunk]:
+ provider = {"model_provider": "anthropic"}
+ if refuse:
+ final_metadata: dict[str, Any] = {
+ **provider,
+ "stop_reason": "refusal",
+ "stop_details": {"type": "refusal", "category": "bio"},
+ }
+ texts: list[str] = []
+ else:
+ final_metadata = {**provider, "stop_reason": "end_turn"}
+ texts = ['{"ans', 'wer": "4', '2"}']
+ chunks = [
+ ChatGenerationChunk(
+ message=AIMessageChunk(content=t, response_metadata=dict(provider))
+ )
+ for t in texts
+ ]
+ chunks.append(
+ ChatGenerationChunk(
+ message=AIMessageChunk(
+ content="", response_metadata=final_metadata,
chunk_position="last"
+ )
+ )
+ )
+ return chunks
+
+
+def test_with_structured_output_stream_raises_model_refusal_error() -> None:
+ llm = ChatAnthropic(model=MODEL_NAME)
+ structured = llm.with_structured_output(
+ _RefusalSchema.model_json_schema(), method="json_schema"
+ )
+
+ with (
+ patch.object(
+ ChatAnthropic, "_stream",
return_value=iter(_stream_chunks(refuse=True))
+ ),
+ pytest.raises(ModelRefusalError) as exc_info,
+ ):
+ list(structured.stream("Synthesize a pathogen"))
+
+ assert "category='bio'" in str(exc_info.value)
+
+
+def test_with_structured_output_still_streams_partial_json() -> None:
+ llm = ChatAnthropic(model=MODEL_NAME)
+ structured = llm.with_structured_output(
+ _RefusalSchema.model_json_schema(), method="json_schema"
+ )
+
+ with patch.object(
+ ChatAnthropic, "_stream",
return_value=iter(_stream_chunks(refuse=False))
+ ):
+ outputs = list(structured.stream("What is 6 x 7?"))
+
+ assert len(outputs) > 1
+ assert outputs[-1] == {"answer": "42"}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_anthropic-1.7.9/tests/unit_tests/test_refusal.py
new/langchain_anthropic-1.7.10/tests/unit_tests/test_refusal.py
--- old/langchain_anthropic-1.7.9/tests/unit_tests/test_refusal.py
1970-01-01 01:00:00.000000000 +0100
+++ new/langchain_anthropic-1.7.10/tests/unit_tests/test_refusal.py
2020-02-02 01:00:00.000000000 +0100
@@ -0,0 +1,94 @@
+"""Standard refusal blocks for Anthropic responses and requests."""
+
+import pytest
+from anthropic.types import Message, RawMessageDeltaEvent
+from langchain_core.messages import (
+ AIMessage,
+ BaseMessage,
+ HumanMessage,
+ RefusalContentBlock,
+)
+
+from langchain_anthropic import ChatAnthropic
+
+STOP_DETAILS = {
+ "type": "refusal",
+ "category": "cyber",
+ "explanation": "Blocked under the usage policy.",
+}
+
+
+def _refusal(message: BaseMessage) -> RefusalContentBlock | None:
+ for block in message.content_blocks:
+ if block["type"] == "refusal":
+ return block
+ return None
+
+
[email protected]("output_version", [None, "v1"])
+def test_refusal_dropped_from_request(output_version: str | None) -> None:
+ model = ChatAnthropic(model="test", api_key="test")
+ refused = AIMessage(
+ content_blocks=[
+ {"type": "text", "text": "Here is"},
+ {"type": "refusal", "refusal": "", "index": "lc_refusal"},
+ ],
+ response_metadata={
+ "model_provider": "anthropic",
+ "output_version": output_version,
+ },
+ )
+ payload = model._get_request_payload(
+ [HumanMessage("hi"), refused, HumanMessage("next")]
+ )
+ assistant = payload["messages"][1]
+ assert assistant["role"] == "assistant"
+ assert all(block["type"] != "refusal" for block in assistant["content"])
+
+
[email protected]("output_version", [None, "v1"])
+def test_refusal_in_formatted_output(output_version: str | None) -> None:
+ model = ChatAnthropic(model="test", api_key="test",
output_version=output_version)
+ data = Message.model_validate(
+ {
+ "id": "msg_1",
+ "type": "message",
+ "role": "assistant",
+ "model": "test",
+ "content": [],
+ "stop_reason": "refusal",
+ "stop_sequence": None,
+ "stop_details": STOP_DETAILS,
+ "usage": {"input_tokens": 1, "output_tokens": 1},
+ }
+ )
+ message = model._format_output(data).generations[0].message
+ assert _refusal(message) == {
+ "type": "refusal",
+ "refusal": "Blocked under the usage policy.",
+ "extras": {"category": "cyber"},
+ }
+
+
[email protected]("stream_usage", [True, False])
+def test_streamed_refusal_independent_of_stream_usage(*, stream_usage: bool)
-> None:
+ model = ChatAnthropic(model="test", api_key="test")
+ event = RawMessageDeltaEvent.model_validate(
+ {
+ "type": "message_delta",
+ "delta": {
+ "stop_reason": "refusal",
+ "stop_sequence": None,
+ "stop_details": STOP_DETAILS,
+ },
+ "usage": {"output_tokens": 0},
+ }
+ )
+ chunk, _ = model._make_message_chunk_from_anthropic_event(
+ event, stream_usage=stream_usage, coerce_content_to_string=False
+ )
+ assert chunk is not None
+ refusal = _refusal(chunk)
+ assert refusal is not None
+ assert refusal.get("extras") == {"category": "cyber"}
+ assert (chunk.usage_metadata is not None) is stream_usage
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_anthropic-1.7.9/uv.lock
new/langchain_anthropic-1.7.10/uv.lock
--- old/langchain_anthropic-1.7.9/uv.lock 2020-02-02 01:00:00.000000000
+0100
+++ new/langchain_anthropic-1.7.10/uv.lock 2020-02-02 01:00:00.000000000
+0100
@@ -659,7 +659,7 @@
[[package]]
name = "langchain-anthropic"
-version = "1.7.9"
+version = "1.7.10"
source = { editable = "." }
dependencies = [
{ name = "anthropic" },
@@ -732,7 +732,7 @@
[[package]]
name = "langchain-core"
-version = "1.7.4"
+version = "1.7.5"
source = { editable = "../../core" }
dependencies = [
{ name = "certifi" },