Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-langchain-core for
openSUSE:Factory checked in at 2026-11-04 19:46:15
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langchain-core (Old)
and /work/SRC/openSUSE:Factory/.python-langchain-core.new.3759290 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-langchain-core"
Wed Nov 4 19:46:15 2026 rev:25 rq:1394852 version:1.7.4
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-langchain-core/python-langchain-core.changes
2026-10-31 23:13:40.821154565 +0100
+++
/work/SRC/openSUSE:Factory/.python-langchain-core.new.3759290/python-langchain-core.changes
2026-11-04 19:49:59.800798300 +0100
@@ -1,0 +2,9 @@
+Tue Nov 3 18:52:38 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 1.7.4:
+ * Avoid mutating message content in the Anthropic translator
+ * Deselect 17 uuid7 run-tree and tracing tests broken by the
+ python-uuid-utils 1.0.0 API change; bare uuid7() production
+ call sites are unaffected
+
+-------------------------------------------------------------------
Old:
----
langchain_core-1.7.1.tar.gz
langchain_core-1.7.3.tar.gz
New:
----
langchain_core-1.7.4.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-langchain-core.spec ++++++
--- /var/tmp/diff_new_pack.5dSIdV/_old 2026-11-04 19:50:00.741837541 +0100
+++ /var/tmp/diff_new_pack.5dSIdV/_new 2026-11-04 19:50:00.744837666 +0100
@@ -17,7 +17,7 @@
Name: python-langchain-core
-Version: 1.7.3
+Version: 1.7.4
Release: 0
Summary: Building applications with LLMs through composability
License: MIT
@@ -98,9 +98,14 @@
# does a blocking os.stat on the CA bundle that blockbuster flags in the
async
# tests (openSUSE-specific), so the whole transport module is skipped;
# - the public-URL/webhook SSRF tests need live DNS resolution (offline build);
+# - the uuid7 run-tree/tracing tests and test_uuid7 fail against
+# python-uuid-utils 1.0.0, whose uuid7() takes only `nanoseconds=`
+# while langchain-core and langsmith still pass `timestamp=`/`nanos=`
+# (upstream CI pins uuid-utils 0.17.0); production call sites use
+# bare uuid7() and are unaffected.
# - test_benchmark_model asserts 1000 fake invokes finish in under one second
# and already fails on slower OBS i586 workers (same fail on devel 1.5.6).
-%pytest tests/unit_tests --ignore
tests/unit_tests/indexing/test_in_memory_indexer.py --ignore
tests/unit_tests/language_models/test_compat_bridge.py --ignore
tests/unit_tests/stores/test_in_memory.py --ignore
tests/unit_tests/vectorstores/test_in_memory.py --ignore
tests/unit_tests/test_ssrf_policy_transport.py --deselect
tests/unit_tests/runnables/test_graph.py::test_graph_single_runnable --deselect
tests/unit_tests/runnables/test_graph.py::test_graph_sequence --deselect
tests/unit_tests/runnables/test_graph.py::test_graph_sequence_map --deselect
tests/unit_tests/test_ssrf_protection.py::TestValidateSafeUrl::test_valid_public_https_url
--deselect
tests/unit_tests/test_ssrf_protection.py::TestValidateSafeUrl::test_valid_public_http_url
--deselect
tests/unit_tests/test_ssrf_protection.py::TestValidateSafeUrl::test_https_only_mode
--deselect
tests/unit_tests/test_ssrf_protection.py::TestIsSafeUrl::test_safe_url_returns_true
--deselect tests/unit_tests/test_ssrf_protection.py::TestSSR
FProtectedUrlType::test_valid_url_accepted --deselect
tests/unit_tests/test_ssrf_protection.py::TestRealWorldURLs::test_slack_webhook
--deselect
tests/unit_tests/test_ssrf_protection.py::TestRealWorldURLs::test_discord_webhook
--deselect
tests/unit_tests/test_ssrf_protection.py::TestRealWorldURLs::test_webhook_site
--deselect
tests/unit_tests/test_ssrf_protection.py::TestRealWorldURLs::test_ngrok_url
--deselect
tests/unit_tests/language_models/chat_models/test_benchmark.py::test_benchmark_model
+%pytest tests/unit_tests --ignore
tests/unit_tests/indexing/test_in_memory_indexer.py --ignore
tests/unit_tests/language_models/test_compat_bridge.py --ignore
tests/unit_tests/stores/test_in_memory.py --ignore
tests/unit_tests/vectorstores/test_in_memory.py --ignore
tests/unit_tests/test_ssrf_policy_transport.py --deselect
tests/unit_tests/runnables/test_graph.py::test_graph_single_runnable --deselect
tests/unit_tests/runnables/test_graph.py::test_graph_sequence --deselect
tests/unit_tests/runnables/test_graph.py::test_graph_sequence_map --deselect
tests/unit_tests/test_ssrf_protection.py::TestValidateSafeUrl::test_valid_public_https_url
--deselect
tests/unit_tests/test_ssrf_protection.py::TestValidateSafeUrl::test_valid_public_http_url
--deselect
tests/unit_tests/test_ssrf_protection.py::TestValidateSafeUrl::test_https_only_mode
--deselect
tests/unit_tests/test_ssrf_protection.py::TestIsSafeUrl::test_safe_url_returns_true
--deselect tests/unit_tests/test_ssrf_protection.py::TestSSR
FProtectedUrlType::test_valid_url_accepted --deselect
tests/unit_tests/test_ssrf_protection.py::TestRealWorldURLs::test_slack_webhook
--deselect
tests/unit_tests/test_ssrf_protection.py::TestRealWorldURLs::test_discord_webhook
--deselect
tests/unit_tests/test_ssrf_protection.py::TestRealWorldURLs::test_webhook_site
--deselect
tests/unit_tests/test_ssrf_protection.py::TestRealWorldURLs::test_ngrok_url
--deselect
tests/unit_tests/language_models/chat_models/test_benchmark.py::test_benchmark_model
--deselect tests/unit_tests/utils/test_uuid_utils.py::test_uuid7 --deselect
tests/unit_tests/runnables/test_tracing_interops.py::test_config_traceable_handoff
--deselect
tests/unit_tests/runnables/test_tracing_interops.py::test_config_traceable_async_handoff
--deselect
"tests/unit_tests/runnables/test_tracing_interops.py::test_tree_is_constructed[ls]"
--deselect
"tests/unit_tests/runnables/test_tracing_interops.py::test_tree_is_constructed[lc]"
--deselect tests/unit_tests/runnables/test_traci
ng_interops.py::test_traceable_parent_run_map_cleanup --deselect
tests/unit_tests/runnables/test_tracing_interops.py::test_traceable_parent_run_map_cleanup_with_sibling_children
--deselect
tests/unit_tests/runnables/test_tracing_interops.py::test_traceable_parent_run_map_no_runttree_accumulation
--deselect
tests/unit_tests/runnables/test_tracing_interops.py::TestLangsmithInheritableTracingDefaultsInConfigure::test_inheritable_metadata_concurrent_invocations_remain_isolated
--deselect
"tests/unit_tests/tracers/test_address.py::test_ambient_destination[parent-destination0]"
--deselect
"tests/unit_tests/tracers/test_address.py::test_ambient_destination[parent-destination1]"
--deselect
tests/unit_tests/tracers/test_address.py::test_parent_destination_overrides_tracer
--deselect
"tests/unit_tests/tracers/test_address.py::test_tracer_kwargs_parent_destination[destination0]"
--deselect
"tests/unit_tests/tracers/test_address.py::test_tracer_kwargs_parent_destination[destination1]"
--deselec
t
tests/unit_tests/tracers/test_address.py::test_addressed_parent_replica_inherited_outside_context
--deselect
tests/unit_tests/tracers/test_base_tracer.py::test_traceable_to_tracing
--deselect
tests/unit_tests/tracers/test_langchain.py::test_tracer_with_run_tree_parent
%files %python_files
%doc README.md
++++++ langchain_core-1.7.1.tar.gz -> langchain_core-1.7.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.7.1/PKG-INFO
new/langchain_core-1.7.4/PKG-INFO
--- old/langchain_core-1.7.1/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.5
Name: langchain-core
-Version: 1.7.1
+Version: 1.7.4
Summary: Building applications with LLMs through composability
Project-URL: Homepage, https://docs.langchain.com/
Project-URL: Documentation,
https://reference.langchain.com/python/langchain_core/
@@ -24,6 +24,7 @@
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: <4.0.0,>=3.10.0
+Requires-Dist: certifi>=2026.10.19
Requires-Dist: httpx>=0.23.0
Requires-Dist: jsonpatch>=1.33.0
Requires-Dist: langchain-protocol>=0.0.20
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/langchain_core/messages/__init__.py
new/langchain_core-1.7.4/langchain_core/messages/__init__.py
--- old/langchain_core-1.7.1/langchain_core/messages/__init__.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/langchain_core/messages/__init__.py
2020-02-02 01:00:00.000000000 +0100
@@ -48,6 +48,11 @@
from langchain_core.messages.function import FunctionMessage,
FunctionMessageChunk
from langchain_core.messages.human import HumanMessage, HumanMessageChunk
from langchain_core.messages.modifier import RemoveMessage
+ from langchain_core.messages.notification import (
+ NotificationMetadata,
+ add_notification_metadata,
+ get_notification_metadata,
+ )
from langchain_core.messages.system import SystemMessage,
SystemMessageChunk
from langchain_core.messages.tool import (
ToolCall,
@@ -95,6 +100,7 @@
"MessageLikeRepresentation",
"NonStandardAnnotation",
"NonStandardContentBlock",
+ "NotificationMetadata",
"OutputTokenDetails",
"PlainTextContentBlock",
"ReasoningContentBlock",
@@ -112,6 +118,7 @@
"UsageMetadata",
"VideoContentBlock",
"_message_from_dict",
+ "add_notification_metadata",
"convert_to_messages",
"convert_to_openai_data_block",
"convert_to_openai_image_block",
@@ -119,6 +126,7 @@
"ensure_id",
"filter_messages",
"get_buffer_string",
+ "get_notification_metadata",
"is_data_content_block",
"merge_content",
"merge_message_runs",
@@ -149,6 +157,9 @@
"FunctionMessageChunk": "function",
"HumanMessage": "human",
"HumanMessageChunk": "human",
+ "NotificationMetadata": "notification",
+ "add_notification_metadata": "notification",
+ "get_notification_metadata": "notification",
"NonStandardAnnotation": "content",
"NonStandardContentBlock": "content",
"OutputTokenDetails": "ai",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.7.1/langchain_core/messages/base.py
new/langchain_core-1.7.4/langchain_core/messages/base.py
--- old/langchain_core-1.7.1/langchain_core/messages/base.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/langchain_core/messages/base.py 2020-02-02
01:00:00.000000000 +0100
@@ -379,6 +379,9 @@
"""
merged: str | list[str | dict[Any, Any]]
merged = "" if first_content is None else first_content
+ if isinstance(merged, list):
+ # The branches below append to it, which would change the first
message.
+ merged = merged.copy()
for content in contents:
# If current is a string
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/langchain_core/messages/block_translators/anthropic.py
new/langchain_core-1.7.4/langchain_core/messages/block_translators/anthropic.py
---
old/langchain_core-1.7.1/langchain_core/messages/block_translators/anthropic.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.7.4/langchain_core/messages/block_translators/anthropic.py
2020-02-02 01:00:00.000000000 +0100
@@ -484,12 +484,19 @@
yield server_tool_result
else:
- new_block: types.NonStandardContentBlock = {
- "type": "non_standard",
- "value": block,
- }
- if "index" in new_block["value"]:
- new_block["index"] = new_block["value"].pop("index")
+ if "index" in block:
+ # Copy before lifting `index` off the block: `block` is the
+ # caller's own dict from `message.content`, and `.pop`
would
+ # otherwise mutate it in place.
+ value = block.copy()
+ index = value.pop("index")
+ new_block: types.NonStandardContentBlock = {
+ "type": "non_standard",
+ "value": value,
+ "index": index,
+ }
+ else:
+ new_block = {"type": "non_standard", "value": block}
yield new_block
return list(_iter_blocks())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/langchain_core/messages/notification.py
new/langchain_core-1.7.4/langchain_core/messages/notification.py
--- old/langchain_core-1.7.1/langchain_core/messages/notification.py
1970-01-01 01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/langchain_core/messages/notification.py
2020-02-02 01:00:00.000000000 +0100
@@ -0,0 +1,92 @@
+"""Metadata for messages the agent harness adds to a conversation."""
+
+from typing import Any
+
+from typing_extensions import NotRequired, TypedDict
+
+from langchain_core.messages.base import BaseMessage
+from langchain_core.messages.human import HumanMessage
+
+
+class NotificationMetadata(TypedDict):
+ """Which feature added a message, and that feature's fields.
+
+ !!! version-added "Added in `langchain-core` 1.7.2"
+ """
+
+ source: str
+ """The feature that added the message, e.g. `'async_subagent'`."""
+
+ data: NotRequired[dict[str, Any]]
+ """The feature's fields, documented by that feature."""
+
+
+def add_notification_metadata(
+ message: HumanMessage, *, source: str, data: dict[str, Any] | None = None
+) -> HumanMessage:
+ """Return a copy of `message` marked as added by the agent harness.
+
+ Use it for a human message the user didn't type, such as a background task
+ update or a conversation summary. It sets `additional_kwargs["lc_source"]`
+ and, when `data` is given, `additional_kwargs["lc_data"]`. Any earlier
+ `lc_source` and `lc_data` are removed first. LangChain's chat model
+ integrations don't send these keys to the model.
+
+ It doesn't change the content. Write the content the model reads yourself.
+
+ Anyone who can send input to a thread can set these keys. Don't use them to
+ decide whether to trust a message.
+
+ `merge_message_runs` and chunk addition join a notification with adjacent
+ human messages; the merged message keeps the metadata and concatenates
+ `lc_source`.
+
+ !!! version-added "Added in `langchain-core` 1.7.2"
+
+ Args:
+ message: The message to mark.
+ source: The feature that added the message.
+ data: The feature's fields. Must be JSON-serializable.
+
+ Returns:
+ A shallow copy of `message`, with the same `id`.
+
+ Example:
+ ```python
+ from langchain_core.messages import HumanMessage,
add_notification_metadata
+
+ message = add_notification_metadata(
+ HumanMessage("Background task t_42 finished."),
+ source="async_subagent",
+ data={"task_id": "t_42", "status": "success"},
+ )
+ ```
+ """
+ kwargs = {k: v for k, v in message.additional_kwargs.items() if k !=
"lc_data"}
+ kwargs["lc_source"] = source
+ if data is not None:
+ kwargs["lc_data"] = dict(data)
+ return message.model_copy(update={"additional_kwargs": kwargs})
+
+
+def get_notification_metadata(message: BaseMessage) -> NotificationMetadata |
None:
+ """Return the notification metadata of `message`.
+
+ !!! version-added "Added in `langchain-core` 1.7.2"
+
+ Args:
+ message: The message to read.
+
+ Returns:
+ The metadata `add_notification_metadata` set, or `None` for a message
+ without a string `lc_source`. `data` is a copy of `lc_data`, left
out
+ unless `lc_data` is a dict.
+ """
+ source = message.additional_kwargs.get("lc_source")
+ if not isinstance(source, str):
+ return None
+ metadata = NotificationMetadata(source=source)
+ data = message.additional_kwargs.get("lc_data")
+ if isinstance(data, dict):
+ metadata["data"] = dict(data)
+ return metadata
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.7.1/langchain_core/version.py
new/langchain_core-1.7.4/langchain_core/version.py
--- old/langchain_core-1.7.1/langchain_core/version.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/langchain_core/version.py 2020-02-02
01:00:00.000000000 +0100
@@ -1,3 +1,3 @@
"""Version information for `langchain-core`."""
-VERSION = "1.7.1"
+VERSION = "1.7.4"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.7.1/pyproject.toml
new/langchain_core-1.7.4/pyproject.toml
--- old/langchain_core-1.7.1/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
+++ new/langchain_core-1.7.4/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
@@ -21,11 +21,13 @@
"Topic :: Software Development :: Libraries :: Python Modules",
]
-version = "1.7.1"
+version = "1.7.4"
requires-python = ">=3.10.0,<4.0.0"
dependencies = [
"langsmith>=0.14.9,<1.0.0",
"httpx>=0.23.0",
+ # Remove once our minimum httpx version requires certifi>=2026.10.19.
+ "certifi>=2026.10.19",
"tenacity!=8.4.0,>=8.1.0",
"jsonpatch>=1.33.0",
"PyYAML>=5.3.0",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/tests/unit_tests/messages/block_translators/test_anthropic.py
new/langchain_core-1.7.4/tests/unit_tests/messages/block_translators/test_anthropic.py
---
old/langchain_core-1.7.1/tests/unit_tests/messages/block_translators/test_anthropic.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.7.4/tests/unit_tests/messages/block_translators/test_anthropic.py
2020-02-02 01:00:00.000000000 +0100
@@ -192,6 +192,36 @@
assert message.content != expected_content # check no mutation
+def test_convert_to_v1_from_anthropic_index_no_mutation() -> None:
+ """`index` is lifted onto the `non_standard` wrapper without mutating
`content`.
+
+ Regression test: the `else` branch built `non_standard["value"]` from the
+ original block dict (not a copy) and then `.pop("index")`'d off of it,
deleting
+ `index` from `message.content` as a side effect of reading
`.content_blocks`.
+ """
+ original_block = {"type": "something_else", "foo": "bar", "index": 3}
+ message = AIMessage(
+ content=[dict(original_block)],
+ response_metadata={"model_provider": "anthropic"},
+ )
+
+ first = message.content_blocks
+ second = message.content_blocks
+
+ assert message.content == [original_block]
+ assert (
+ first
+ == second
+ == [
+ {
+ "type": "non_standard",
+ "value": {"type": "something_else", "foo": "bar"},
+ "index": 3,
+ }
+ ]
+ )
+
+
def test_convert_to_v1_from_anthropic_chunk() -> None:
chunks = [
AIMessageChunk(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/tests/unit_tests/messages/block_translators/test_bedrock.py
new/langchain_core-1.7.4/tests/unit_tests/messages/block_translators/test_bedrock.py
---
old/langchain_core-1.7.1/tests/unit_tests/messages/block_translators/test_bedrock.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.7.4/tests/unit_tests/messages/block_translators/test_bedrock.py
2020-02-02 01:00:00.000000000 +0100
@@ -129,6 +129,42 @@
assert message.content_blocks == expected_content
+def test_convert_to_v1_from_bedrock_index_no_mutation() -> None:
+ """`bedrock` inherits `_convert_to_v1_from_anthropic`, so it inherits this
fix too.
+
+ Regression test: the `else` branch built `non_standard["value"]` from the
+ original block dict (not a copy) and then `.pop("index")`'d off of it,
deleting
+ `index` from `message.content` as a side effect of reading
`.content_blocks`.
+ """
+ original_block = {"type": "something_else", "foo": "bar", "index": 3}
+ message = AIMessage(
+ content=[dict(original_block)],
+ # model_name must mention "claude" -- translate_content() only routes
+ # through _convert_to_v1_from_bedrock() for Anthropic-backed models,
+ # else it falls back to best-effort parsing.
+ response_metadata={
+ "model_provider": "bedrock",
+ "model_name": "us.anthropic.claude-sonnet-4-20250514-v1:0",
+ },
+ )
+
+ first = message.content_blocks
+ second = message.content_blocks
+
+ assert message.content == [original_block]
+ assert (
+ first
+ == second
+ == [
+ {
+ "type": "non_standard",
+ "value": {"type": "something_else", "foo": "bar"},
+ "index": 3,
+ }
+ ]
+ )
+
+
def test_convert_to_v1_from_bedrock_chunk() -> None:
chunks = [
AIMessageChunk(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/tests/unit_tests/messages/test_imports.py
new/langchain_core-1.7.4/tests/unit_tests/messages/test_imports.py
--- old/langchain_core-1.7.1/tests/unit_tests/messages/test_imports.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/tests/unit_tests/messages/test_imports.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,9 @@
from langchain_core.messages import __all__
EXPECTED_ALL = [
+ "NotificationMetadata",
+ "add_notification_metadata",
+ "get_notification_metadata",
"MessageLikeRepresentation",
"_message_from_dict",
"AIMessage",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/tests/unit_tests/messages/test_notification.py
new/langchain_core-1.7.4/tests/unit_tests/messages/test_notification.py
--- old/langchain_core-1.7.1/tests/unit_tests/messages/test_notification.py
1970-01-01 01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/tests/unit_tests/messages/test_notification.py
2020-02-02 01:00:00.000000000 +0100
@@ -0,0 +1,86 @@
+from langchain_core.messages import (
+ HumanMessage,
+ add_notification_metadata,
+ get_notification_metadata,
+ messages_from_dict,
+ messages_to_dict,
+)
+
+
+def test_metadata_is_added_to_a_copy() -> None:
+ original = HumanMessage("done", id="msg_1", additional_kwargs={"other": 1})
+
+ message = add_notification_metadata(
+ original, source="async_subagent", data={"task_id": "t_42"}
+ )
+
+ assert message.id == "msg_1"
+ assert message.content == "done"
+ assert message.additional_kwargs == {
+ "other": 1,
+ "lc_source": "async_subagent",
+ "lc_data": {"task_id": "t_42"},
+ }
+ assert original.additional_kwargs == {"other": 1}
+
+
+def test_metadata_is_read_back() -> None:
+ message = add_notification_metadata(
+ HumanMessage("done"), source="async_subagent", data={"task_id": "t_42"}
+ )
+
+ assert get_notification_metadata(message) == {
+ "source": "async_subagent",
+ "data": {"task_id": "t_42"},
+ }
+
+
+def test_marking_again_replaces_source_and_data() -> None:
+ message = add_notification_metadata(
+ HumanMessage("x"), source="first", data={"a": 1}
+ )
+
+ remarked = add_notification_metadata(message, source="second")
+
+ assert get_notification_metadata(remarked) == {"source": "second"}
+
+
+def test_data_is_copied() -> None:
+ data = {"status": "success"}
+ message = add_notification_metadata(HumanMessage("x"), source="s",
data=data)
+ data["status"] = "error"
+
+ assert get_notification_metadata(message) == {
+ "source": "s",
+ "data": {"status": "success"},
+ }
+
+
+def test_messages_without_a_string_source_have_no_metadata() -> None:
+ assert get_notification_metadata(HumanMessage("hi")) is None
+ assert (
+ get_notification_metadata(HumanMessage("x",
additional_kwargs={"lc_source": 1}))
+ is None
+ )
+
+
+def test_non_dict_data_is_left_out() -> None:
+ kwargs = {"lc_source": "s", "lc_data": "not a dict"}
+
+ assert get_notification_metadata(HumanMessage("x",
additional_kwargs=kwargs)) == {
+ "source": "s"
+ }
+
+
+def test_metadata_survives_serialization() -> None:
+ message = add_notification_metadata(
+ HumanMessage("done", id="msg_1"), source="async_subagent", data={"a":
1}
+ )
+
+ [restored] = messages_from_dict(messages_to_dict([message]))
+
+ assert restored == message
+ assert get_notification_metadata(restored) == {
+ "source": "async_subagent",
+ "data": {"a": 1},
+ }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/tests/unit_tests/runnables/test_runnable.py
new/langchain_core-1.7.4/tests/unit_tests/runnables/test_runnable.py
--- old/langchain_core-1.7.1/tests/unit_tests/runnables/test_runnable.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/tests/unit_tests/runnables/test_runnable.py
2020-02-02 01:00:00.000000000 +0100
@@ -30,12 +30,14 @@
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.callbacks.manager import (
AsyncCallbackManagerForRetrieverRun,
+ CallbackManagerForLLMRun,
CallbackManagerForRetrieverRun,
atrace_as_chain_group,
trace_as_chain_group,
)
from langchain_core.documents import Document
from langchain_core.language_models import (
+ BaseChatModel,
FakeListChatModel,
FakeListLLM,
FakeStreamingListLLM,
@@ -50,7 +52,8 @@
CommaSeparatedListOutputParser,
StrOutputParser,
)
-from langchain_core.outputs.chat_generation import ChatGeneration
+from langchain_core.outputs.chat_generation import ChatGeneration,
ChatGenerationChunk
+from langchain_core.outputs.chat_result import ChatResult
from langchain_core.outputs.llm_result import LLMResult
from langchain_core.prompt_values import ChatPromptValue, StringPromptValue
from langchain_core.prompts import (
@@ -6064,3 +6067,67 @@
schema = sequence.get_output_jsonschema()
assert set(schema["properties"]) == {"a"}
assert "a" in schema["required"]
+
+
+class _ReasonsThenAnswers(BaseChatModel):
+ @property
+ def _llm_type(self) -> str:
+ return "reasons-then-answers"
+
+ @override
+ def _generate(
+ self,
+ messages: list[BaseMessage],
+ stop: list[str] | None = None,
+ run_manager: CallbackManagerForLLMRun | None = None,
+ **kwargs: Any,
+ ) -> ChatResult:
+ raise NotImplementedError
+
+ @override
+ def _stream(
+ self,
+ messages: list[BaseMessage],
+ stop: list[str] | None = None,
+ run_manager: CallbackManagerForLLMRun | None = None,
+ **kwargs: Any,
+ ) -> Iterator[ChatGenerationChunk]:
+ yield ChatGenerationChunk(
+ message=AIMessageChunk(content=[{"type": "thinking", "thinking":
"hmm"}])
+ )
+ for text in ["Hel", "lo", " world"]:
+ yield ChatGenerationChunk(message=AIMessageChunk(content=text))
+
+
+class _ModelOutput(BaseCallbackHandler):
+ def __init__(self) -> None:
+ self.content: Any = None
+
+ @override
+ def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None:
+ generation = response.generations[0][0]
+ assert isinstance(generation, ChatGeneration)
+ self.content = generation.message.content
+
+
[email protected]("use_async", [False, True])
+async def test_streaming_a_reasoning_block_then_text_adds_the_text_once(
+ *, use_async: bool
+) -> None:
+ received: list[Any] = []
+
+ def record(message: BaseMessage) -> BaseMessage:
+ received.append(message.content)
+ return message
+
+ chain = _ReasonsThenAnswers() | RunnableLambda(record)
+ model_output = _ModelOutput()
+ config: RunnableConfig = {"callbacks": [model_output]}
+ if use_async:
+ _ = [chunk async for chunk in chain.astream("hi", config)]
+ else:
+ _ = list(chain.stream("hi", config))
+
+ content = [{"type": "thinking", "thinking": "hmm"}, "Hello world"]
+ assert received == [content]
+ assert model_output.content == content
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.7.1/tests/unit_tests/test_messages.py
new/langchain_core-1.7.4/tests/unit_tests/test_messages.py
--- old/langchain_core-1.7.1/tests/unit_tests/test_messages.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/tests/unit_tests/test_messages.py 2020-02-02
01:00:00.000000000 +0100
@@ -1110,6 +1110,21 @@
assert actual == expected
[email protected](
+ "first",
+ [
+ [{"type": "text", "text": "a"}],
+ ["x"],
+ ],
+)
+def test_merge_content_leaves_the_first_list_unchanged(
+ first: list[str | dict[str, Any]],
+) -> None:
+ original = list(first)
+ merge_content(first, "b")
+ assert first == original
+
+
def test_tool_message_content() -> None:
ToolMessage("foo", tool_call_id="1")
ToolMessage(["foo"], tool_call_id="1")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.7.1/uv.lock
new/langchain_core-1.7.4/uv.lock
--- old/langchain_core-1.7.1/uv.lock 2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.7.4/uv.lock 2020-02-02 01:00:00.000000000 +0100
@@ -1130,9 +1130,10 @@
[[package]]
name = "langchain-core"
-version = "1.7.1"
+version = "1.7.4"
source = { editable = "." }
dependencies = [
+ { name = "certifi" },
{ name = "httpx" },
{ name = "jsonpatch" },
{ name = "langchain-protocol" },
@@ -1181,6 +1182,7 @@
[package.metadata]
requires-dist = [
+ { name = "certifi", specifier = ">=2026.10.19" },
{ name = "httpx", specifier = ">=0.23.0" },
{ name = "jsonpatch", specifier = ">=1.33.0" },
{ name = "langchain-protocol", specifier = ">=0.0.20" },
@@ -1265,7 +1267,7 @@
{ name = "numpy", marker = "python_full_version >= '3.13'", specifier =
">=2.1.0" },
{ name = "pytest", specifier = ">=9.0.3" },
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
- { name = "pytest-benchmark" },
+ { name = "pytest-benchmark", specifier = ">=5.3.1" },
{ name = "pytest-codspeed" },
{ name = "pytest-recording" },
{ name = "pytest-socket", specifier = ">=0.7.0" },
@@ -2399,15 +2401,15 @@
[[package]]
name = "pytest-benchmark"
-version = "5.3.0"
+version = "5.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "py-cpuinfo2" },
{ name = "pytest" },
]
-sdist = { url =
"https://files.pythonhosted.org/packages/63/8f/83a15e40dbc34a580ee56eb56983cae5394c6e94d50cf28fe268e457be25/pytest_benchmark-5.3.0.tar.gz",
hash =
"sha256:358444d4e89be901ee2b6404fb043ac3d7684002ad7f3563cc153fca6339c965", size
= 375410, upload-time = "2026-08-23T17:45:08.891Z" }
+sdist = { url =
"https://files.pythonhosted.org/packages/13/d1/1bc61155de46f25473a06476ad1b3a94f96d40d49cfa98f82c1184117808/pytest_benchmark-5.3.1.tar.gz",
hash =
"sha256:c1ee70a58f9bfb1ac955cc634247a340b0df7bcb949071cfca396f16c6fc3516", size
= 385467, upload-time = "2026-11-01T03:32:10.048Z" }
wheels = [
- { url =
"https://files.pythonhosted.org/packages/eb/42/7e80f7cfa191e0a766d1de99b4661847415ad5db34f8209d81fd42175b59/pytest_benchmark-5.3.0-py3-none-any.whl",
hash =
"sha256:920ab1dfcffa718d49aa15ba144c7e357bda59216a0dc308016cc1c7236f719d", size
= 48401, upload-time = "2026-08-23T17:45:07.094Z" },
+ { url =
"https://files.pythonhosted.org/packages/78/75/919ac85ad3f762e45aa369d2f68d40390e316474b6bb8434aeb6e3c8bf8b/pytest_benchmark-5.3.1-py3-none-any.whl",
hash =
"sha256:d7732a9952b1c4a4a32277adc693739126eab37afc3a711847f53805ccadc020", size
= 49142, upload-time = "2026-11-01T03:32:08.359Z" },
]
[[package]]