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" },

Reply via email to