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-08-15 22:41:12
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langchain-core (Old)
and /work/SRC/openSUSE:Factory/.python-langchain-core.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-langchain-core"
Sat Aug 15 22:41:12 2026 rev:8 rq:1371288 version:1.5.5
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-langchain-core/python-langchain-core.changes
2026-08-12 16:13:23.420709417 +0200
+++
/work/SRC/openSUSE:Factory/.python-langchain-core.new.1258/python-langchain-core.changes
2026-08-15 22:41:38.069716038 +0200
@@ -1,0 +2,26 @@
+Sat Aug 15 05:46:56 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to version 1.5.5:
+ * Guard against malformed Anthropic content blocks
+ * Respect pydantic field aliases when validating tool inputs
+ * Fix chunk merging: merge_dicts now raises TypeError for
+ differing boolean values instead of silently summing them to
+ an int, AddableDict addition raises TypeError on
+ type-incompatible keys instead of dropping data, and
+ merge_lists no longer misidentifies non-dict elements as
+ index-keyed
+ * Handle v1 base model validation in the async path
+ * Handle tool descriptions when infer_schema=False
+ * Clear the usage metadata callback on exceptions in the
+ context manager
+ * Handle falsy LLM and chat model caches
+ * Preserve non-str/non-dict items in DictPromptTemplate list
+ values
+ * Raise ValueError when the explicit tool_outputs length does
+ not match tool_calls in tool_example_to_messages
+ * Make abatch_iterate consistent with batch_iterate for a None
+ or zero batch size
+- Add python-httpx >= 0.23.0 dependency: upstream now declares
+ httpx as an explicit runtime requirement
+
+-------------------------------------------------------------------
Old:
----
langchain_core-1.5.4.tar.gz
New:
----
langchain_core-1.5.5.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-langchain-core.spec ++++++
--- /var/tmp/diff_new_pack.OVG4lq/_old 2026-08-15 22:41:38.761740398 +0200
+++ /var/tmp/diff_new_pack.OVG4lq/_new 2026-08-15 22:41:38.763740468 +0200
@@ -17,7 +17,7 @@
Name: python-langchain-core
-Version: 1.5.4
+Version: 1.5.5
Release: 0
Summary: Building applications with LLMs through composability
License: MIT
@@ -30,6 +30,7 @@
BuildRequires: fdupes
BuildRequires: python-rpm-macros
Requires: python-PyYAML >= 5.3
+Requires: python-httpx >= 0.23.0
Requires: python-jsonpatch >= 1.33
Requires: python-langchain-protocol >= 0.0.17
Requires: python-langsmith >= 0.3.45
@@ -43,6 +44,7 @@
BuildRequires: %{python_module PyYAML >= 5.3}
BuildRequires: %{python_module blockbuster}
BuildRequires: %{python_module freezegun}
+BuildRequires: %{python_module httpx >= 0.23.0}
BuildRequires: %{python_module jsonpatch >= 1.33}
BuildRequires: %{python_module langchain-protocol >= 0.0.17}
BuildRequires: %{python_module langsmith >= 0.3.45}
++++++ langchain_core-1.5.4.tar.gz -> langchain_core-1.5.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/PKG-INFO
new/langchain_core-1.5.5/PKG-INFO
--- old/langchain_core-1.5.4/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/PKG-INFO 2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.5
Name: langchain-core
-Version: 1.5.4
+Version: 1.5.5
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: httpx<1.0.0,>=0.23.0
Requires-Dist: jsonpatch<2.0.0,>=1.33.0
Requires-Dist: langchain-protocol>=0.0.17
Requires-Dist: langsmith<1.0.0,>=0.3.45
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/langchain_core/callbacks/usage.py
new/langchain_core-1.5.5/langchain_core/callbacks/usage.py
--- old/langchain_core-1.5.4/langchain_core/callbacks/usage.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/callbacks/usage.py 2020-02-02
01:00:00.000000000 +0100
@@ -114,6 +114,11 @@
)
register_configure_hook(usage_metadata_callback_var, inheritable=True)
cb = UsageMetadataCallbackHandler()
- usage_metadata_callback_var.set(cb)
- yield cb
- usage_metadata_callback_var.set(None)
+ token = usage_metadata_callback_var.set(cb)
+ try:
+ yield cb
+ finally:
+ # Always clear the context var, including when the with-block raises.
+ # Without finally, post-block model calls keep accumulating into cb
+ # (see #38989).
+ usage_metadata_callback_var.reset(token)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/langchain_core/language_models/chat_models.py
new/langchain_core-1.5.5/langchain_core/language_models/chat_models.py
--- old/langchain_core-1.5.4/langchain_core/language_models/chat_models.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/language_models/chat_models.py
2020-02-02 01:00:00.000000000 +0100
@@ -1888,9 +1888,9 @@
# We should check the cache unless it's explicitly set to False
# A None cache means we should use the default global cache
# if it's configured.
- check_cache = self.cache or self.cache is None
+ check_cache = self.cache is not False
if check_cache:
- if llm_cache:
+ if llm_cache is not None:
llm_string = self._get_llm_string(stop=stop, **kwargs)
normalized_messages = [
(
@@ -2032,7 +2032,7 @@
**result.llm_output,
**result.generations[0].message.response_metadata,
}
- if check_cache and llm_cache:
+ if check_cache and llm_cache is not None:
llm_cache.update(prompt, llm_string, result.generations)
return result
@@ -2047,9 +2047,9 @@
# We should check the cache unless it's explicitly set to False
# A None cache means we should use the default global cache
# if it's configured.
- check_cache = self.cache or self.cache is None
+ check_cache = self.cache is not False
if check_cache:
- if llm_cache:
+ if llm_cache is not None:
llm_string = self._get_llm_string(stop=stop, **kwargs)
normalized_messages = [
(
@@ -2189,7 +2189,7 @@
**result.llm_output,
**result.generations[0].message.response_metadata,
}
- if check_cache and llm_cache:
+ if check_cache and llm_cache is not None:
await llm_cache.aupdate(prompt, llm_string, result.generations)
return result
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/langchain_core/language_models/llms.py
new/langchain_core-1.5.5/langchain_core/language_models/llms.py
--- old/langchain_core-1.5.4/langchain_core/language_models/llms.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/language_models/llms.py
2020-02-02 01:00:00.000000000 +0100
@@ -182,13 +182,13 @@
llm_cache = _resolve_cache(cache=cache)
for i, prompt in enumerate(prompts):
- if llm_cache:
+ if llm_cache is not None:
cache_val = llm_cache.lookup(prompt, llm_string)
if isinstance(cache_val, list):
existing_prompts[i] = cache_val
- else:
- missing_prompts.append(prompt)
- missing_prompt_idxs.append(i)
+ continue
+ missing_prompts.append(prompt)
+ missing_prompt_idxs.append(i)
return existing_prompts, llm_string, missing_prompt_idxs, missing_prompts
@@ -217,13 +217,13 @@
existing_prompts = {}
llm_cache = _resolve_cache(cache=cache)
for i, prompt in enumerate(prompts):
- if llm_cache:
+ if llm_cache is not None:
cache_val = await llm_cache.alookup(prompt, llm_string)
if isinstance(cache_val, list):
existing_prompts[i] = cache_val
- else:
- missing_prompts.append(prompt)
- missing_prompt_idxs.append(i)
+ continue
+ missing_prompts.append(prompt)
+ missing_prompt_idxs.append(i)
return existing_prompts, llm_string, missing_prompt_idxs, missing_prompts
@@ -288,7 +288,7 @@
for i, result in enumerate(new_results.generations):
existing_prompts[missing_prompt_idxs[i]] = result
prompt = prompts[missing_prompt_idxs[i]]
- if llm_cache:
+ if llm_cache is not None:
await llm_cache.aupdate(prompt, llm_string, result)
return new_results.llm_output
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/langchain_core/messages/block_translators/anthropic.py
new/langchain_core-1.5.5/langchain_core/messages/block_translators/anthropic.py
---
old/langchain_core-1.5.4/langchain_core/messages/block_translators/anthropic.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.5.5/langchain_core/messages/block_translators/anthropic.py
2020-02-02 01:00:00.000000000 +0100
@@ -26,6 +26,10 @@
return standard_block
+def _non_standard_block(block: dict[str, Any]) ->
types.NonStandardContentBlock:
+ return {"type": "non_standard", "value": block}
+
+
def _convert_to_v1_from_anthropic_input(
content: list[types.ContentBlock],
) -> list[types.ContentBlock]:
@@ -54,87 +58,107 @@
]
for block in blocks:
block_type = block.get("type")
+ source = block.get("source")
if (
block_type == "document"
- and "source" in block
- and "type" in block["source"]
+ and isinstance(source, dict)
+ and "type" in source
):
- if block["source"]["type"] == "base64":
+ if source["type"] == "base64":
+ if "data" not in source or "media_type" not in source:
+ yield _non_standard_block(block)
+ continue
file_block: types.FileContentBlock = {
"type": "file",
- "base64": block["source"]["data"],
- "mime_type": block["source"]["media_type"],
+ "base64": source["data"],
+ "mime_type": source["media_type"],
}
_populate_extras(file_block, block, {"type", "source"})
yield file_block
- elif block["source"]["type"] == "url":
+ elif source["type"] == "url":
+ if "url" not in source:
+ yield _non_standard_block(block)
+ continue
file_block = {
"type": "file",
- "url": block["source"]["url"],
+ "url": source["url"],
}
_populate_extras(file_block, block, {"type", "source"})
yield file_block
- elif block["source"]["type"] == "file":
+ elif source["type"] == "file":
+ if "file_id" not in source:
+ yield _non_standard_block(block)
+ continue
file_block = {
"type": "file",
- "id": block["source"]["file_id"],
+ "id": source["file_id"],
}
_populate_extras(file_block, block, {"type", "source"})
yield file_block
- elif block["source"]["type"] == "text":
+ elif source["type"] == "text":
+ if "data" not in source:
+ yield _non_standard_block(block)
+ continue
plain_text_block: types.PlainTextContentBlock = {
"type": "text-plain",
- "text": block["source"]["data"],
+ "text": source["data"],
"mime_type": block.get("media_type", "text/plain"),
}
_populate_extras(plain_text_block, block, {"type",
"source"})
yield plain_text_block
else:
- yield {"type": "non_standard", "value": block}
+ yield _non_standard_block(block)
elif (
- block_type == "image"
- and "source" in block
- and "type" in block["source"]
+ block_type == "image" and isinstance(source, dict) and "type"
in source
):
- if block["source"]["type"] == "base64":
+ if source["type"] == "base64":
+ if "data" not in source or "media_type" not in source:
+ yield _non_standard_block(block)
+ continue
image_block: types.ImageContentBlock = {
"type": "image",
- "base64": block["source"]["data"],
- "mime_type": block["source"]["media_type"],
+ "base64": source["data"],
+ "mime_type": source["media_type"],
}
_populate_extras(image_block, block, {"type", "source"})
yield image_block
- elif block["source"]["type"] == "url":
+ elif source["type"] == "url":
+ if "url" not in source:
+ yield _non_standard_block(block)
+ continue
image_block = {
"type": "image",
- "url": block["source"]["url"],
+ "url": source["url"],
}
_populate_extras(image_block, block, {"type", "source"})
yield image_block
- elif block["source"]["type"] == "file":
+ elif source["type"] == "file":
+ if "file_id" not in source:
+ yield _non_standard_block(block)
+ continue
image_block = {
"type": "image",
- "id": block["source"]["file_id"],
+ "id": source["file_id"],
}
_populate_extras(image_block, block, {"type", "source"})
yield image_block
else:
- yield {"type": "non_standard", "value": block}
+ yield _non_standard_block(block)
elif block_type in types.KNOWN_BLOCK_TYPES:
yield cast("types.ContentBlock", block)
else:
- yield {"type": "non_standard", "value": block}
+ yield _non_standard_block(block)
return list(_iter_blocks())
@@ -143,6 +167,11 @@
citation_type = citation.get("type")
if citation_type == "web_search_result_location":
+ if "cited_text" not in citation or "url" not in citation:
+ return {
+ "type": "non_standard_annotation",
+ "value": citation,
+ }
url_citation: types.Citation = {
"type": "citation",
"cited_text": citation["cited_text"],
@@ -165,6 +194,11 @@
"page_location",
"search_result_location",
}:
+ if "cited_text" not in citation:
+ return {
+ "type": "non_standard_annotation",
+ "value": citation,
+ }
document_citation: types.Citation = {
"type": "citation",
"cited_text": citation["cited_text"],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/langchain_core/prompts/dict.py
new/langchain_core-1.5.5/langchain_core/prompts/dict.py
--- old/langchain_core-1.5.4/langchain_core/prompts/dict.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/prompts/dict.py 2020-02-02
01:00:00.000000000 +0100
@@ -161,7 +161,7 @@
warnings.warn(msg, stacklevel=2)
formatted[k] = _insert_input_variables(v, inputs, template_format)
elif isinstance(v, (list, tuple)):
- formatted_v: list[str | dict[str, Any]] = []
+ formatted_v: list[Any] = []
for x in v:
if isinstance(x, str):
formatted_v.append(formatter(x, **inputs))
@@ -169,6 +169,8 @@
formatted_v.append(
_insert_input_variables(x, inputs, template_format)
)
+ else:
+ formatted_v.append(x)
formatted[k] = type(v)(formatted_v)
else:
formatted[k] = v
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/langchain_core/runnables/utils.py
new/langchain_core-1.5.5/langchain_core/runnables/utils.py
--- old/langchain_core-1.5.4/langchain_core/runnables/utils.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/runnables/utils.py 2020-02-02
01:00:00.000000000 +0100
@@ -477,6 +477,9 @@
Returns:
A dictionary that is the result of adding the two dictionaries.
+
+ Raises:
+ TypeError: If a shared key holds values of incompatible types.
"""
chunk = AddableDict(self)
for key in other:
@@ -485,8 +488,13 @@
elif other[key] is not None:
try:
added = chunk[key] + other[key]
- except TypeError:
- added = other[key]
+ except TypeError as exc:
+ msg = (
+ f"Cannot add incompatible types for key {key!r}: "
+ f"{type(chunk[key]).__name__!r} and "
+ f"{type(other[key]).__name__!r}."
+ )
+ raise TypeError(msg) from exc
chunk[key] = added
return chunk
@@ -498,6 +506,9 @@
Returns:
A dictionary that is the result of adding the two dictionaries.
+
+ Raises:
+ TypeError: If a shared key holds values of incompatible types.
"""
chunk = AddableDict(other)
for key in self:
@@ -506,8 +517,13 @@
elif self[key] is not None:
try:
added = chunk[key] + self[key]
- except TypeError:
- added = self[key]
+ except TypeError as exc:
+ msg = (
+ f"Cannot add incompatible types for key {key!r}: "
+ f"{type(chunk[key]).__name__!r} and "
+ f"{type(self[key]).__name__!r}."
+ )
+ raise TypeError(msg) from exc
chunk[key] = added
return chunk
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/langchain_core/tools/base.py
new/langchain_core-1.5.5/langchain_core/tools/base.py
--- old/langchain_core-1.5.4/langchain_core/tools/base.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/tools/base.py 2020-02-02
01:00:00.000000000 +0100
@@ -835,6 +835,7 @@
tool_input[k] = tool_call_id
result_v2 = input_args.model_validate(tool_input)
result_dict = result_v2.model_dump()
+ provided_fields = result_v2.model_fields_set
result = result_v2
elif issubclass(input_args, BaseModelV1):
# Check args_schema for InjectedToolCallId
@@ -852,6 +853,7 @@
tool_input[k] = tool_call_id
result_v1 = input_args.parse_obj(tool_input)
result_dict = result_v1.dict()
+ provided_fields = result_v1.__fields_set__
result = result_v1
else:
msg = ( # type: ignore[unreachable]
@@ -859,15 +861,19 @@
)
raise NotImplementedError(msg)
- # Include fields from tool_input, plus fields with explicit
defaults.
- # This applies Pydantic defaults (like Field(default=1)) while
excluding
- # synthetic "args"/"kwargs" fields that Pydantic creates for
*args/**kwargs.
+ # Include fields from tool_input, fields provided through Pydantic
aliases,
+ # plus fields with explicit defaults. This applies Pydantic
defaults (like
+ # Field(default=1)) while excluding synthetic "args"/"kwargs"
fields that
+ # Pydantic creates for *args/**kwargs.
field_info = get_fields(input_args)
validated_input = {}
for k in result_dict:
if k in tool_input:
# Field was provided in input - include it (validated)
validated_input[k] = getattr(result, k)
+ elif k in provided_fields:
+ # Field was provided through a Pydantic alias - include it.
+ validated_input[k] = getattr(result, k)
elif k in field_info and k not in {"args", "kwargs"}:
# Check if field has an explicit default defined in the
schema.
# Exclude "args"/"kwargs" as these are synthetic fields
for variadic
@@ -1238,7 +1244,7 @@
error_to_raise = ValueError(msg)
else:
content = response
- except ValidationError as e:
+ except (ValidationError, ValidationErrorV1) as e:
if not self.handle_validation_error:
error_to_raise = e
else:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/langchain_core/tools/convert.py
new/langchain_core-1.5.5/langchain_core/tools/convert.py
--- old/langchain_core-1.5.4/langchain_core/tools/convert.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/tools/convert.py 2020-02-02
01:00:00.000000000 +0100
@@ -329,16 +329,17 @@
)
# If someone doesn't want a schema applied, we must treat it as
# a simple string->string function
- if dec_func.__doc__ is None:
+ tool_description = tool_description or dec_func.__doc__
+ if tool_description is None:
msg = (
- "Function must have a docstring if "
- "description not provided and infer_schema is False."
+ "Function must have either a docstring or description "
+ "when infer_schema is False."
)
raise ValueError(msg)
return Tool(
name=tool_name,
func=func,
- description=f"{tool_name} tool",
+ description=tool_description,
return_direct=return_direct,
coroutine=coroutine,
response_format=response_format,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/langchain_core/utils/_merge.py
new/langchain_core-1.5.5/langchain_core/utils/_merge.py
--- old/langchain_core-1.5.4/langchain_core/utils/_merge.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/utils/_merge.py 2020-02-02
01:00:00.000000000 +0100
@@ -68,6 +68,15 @@
merged[right_k] = merge_lists(merged[right_k], right_v)
elif merged[right_k] == right_v:
continue
+ elif isinstance(merged[right_k], bool):
+ # `bool` is a subclass of `int`, so without this check
differing
+ # booleans would fall into the int branch below and get summed,
+ # silently turning e.g. `True + False` into the int `1`.
+ msg = (
+ f"Additional kwargs key {right_k} already exists in left
dict and "
+ f"value has unsupported type {type(merged[right_k])}."
+ )
+ raise TypeError(msg)
elif isinstance(merged[right_k], int):
# Preserve identification and temporal fields using last-wins
strategy
# instead of summing:
@@ -118,7 +127,8 @@
i
for i, e_left in enumerate(merged)
if (
- "index" in e_left
+ isinstance(e_left, dict)
+ and "index" in e_left
and e_left["index"] == e["index"] # index matches
and ( # IDs not inconsistent
e_left.get("id") in {None, ""}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/langchain_core/utils/aiter.py
new/langchain_core-1.5.5/langchain_core/utils/aiter.py
--- old/langchain_core-1.5.4/langchain_core/utils/aiter.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/utils/aiter.py 2020-02-02
01:00:00.000000000 +0100
@@ -323,25 +323,35 @@
async def abatch_iterate(
- size: int, iterable: AsyncIterable[T]
+ size: int | None, iterable: AsyncIterable[T]
) -> AsyncIterator[list[T]]:
"""Utility batching function for async iterables.
Args:
size: The size of the batch.
+
+ If `None`, returns a single batch.
iterable: The async iterable to batch.
Yields:
The batches.
+
+ Raises:
+ ValueError: If `size` is not `None` and is not a positive integer.
"""
+ if size is None:
+ single_batch = [el async for el in iterable]
+ if single_batch:
+ yield single_batch
+ return
+ if size <= 0:
+ msg = f"Batch size must be a positive integer, got {size}."
+ raise ValueError(msg)
batch: list[T] = []
async for element in iterable:
- if len(batch) < size:
- batch.append(element)
-
+ batch.append(element)
if len(batch) >= size:
yield batch
batch = []
-
if batch:
yield batch
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/langchain_core/utils/function_calling.py
new/langchain_core-1.5.5/langchain_core/utils/function_calling.py
--- old/langchain_core-1.5.4/langchain_core/utils/function_calling.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/utils/function_calling.py
2020-02-02 01:00:00.000000000 +0100
@@ -713,6 +713,13 @@
messages.append(
AIMessage(content="", additional_kwargs={"tool_calls":
openai_tool_calls})
)
+ if tool_outputs is not None and len(tool_outputs) !=
len(openai_tool_calls):
+ msg = (
+ f"The number of tool_outputs ({len(tool_outputs)}) must match the
number "
+ f"of tool_calls ({len(openai_tool_calls)}). Got
{len(tool_outputs)} "
+ f"output(s) for {len(openai_tool_calls)} tool call(s)."
+ )
+ raise ValueError(msg)
tool_outputs = tool_outputs or ["You have correctly called this tool."] *
len(
openai_tool_calls
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/langchain_core/utils/iter.py
new/langchain_core-1.5.5/langchain_core/utils/iter.py
--- old/langchain_core-1.5.4/langchain_core/utils/iter.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/utils/iter.py 2020-02-02
01:00:00.000000000 +0100
@@ -214,7 +214,13 @@
Yields:
The batches of the iterable.
+
+ Raises:
+ ValueError: If `size` is not `None` and is not a positive integer.
"""
+ if size is not None and size <= 0:
+ msg = f"Batch size must be a positive integer, got {size}."
+ raise ValueError(msg)
it = iter(iterable)
while True:
chunk = list(islice(it, size))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/langchain_core/version.py
new/langchain_core-1.5.5/langchain_core/version.py
--- old/langchain_core-1.5.4/langchain_core/version.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/langchain_core/version.py 2020-02-02
01:00:00.000000000 +0100
@@ -1,3 +1,3 @@
"""Version information for `langchain-core`."""
-VERSION = "1.5.4"
+VERSION = "1.5.5"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/pyproject.toml
new/langchain_core-1.5.5/pyproject.toml
--- old/langchain_core-1.5.4/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
+++ new/langchain_core-1.5.5/pyproject.toml 2020-02-02 01:00:00.000000000
+0100
@@ -21,10 +21,11 @@
"Topic :: Software Development :: Libraries :: Python Modules",
]
-version = "1.5.4"
+version = "1.5.5"
requires-python = ">=3.10.0,<4.0.0"
dependencies = [
"langsmith>=0.3.45,<1.0.0",
+ "httpx>=0.23.0,<1.0.0",
"tenacity!=8.4.0,>=8.1.0,<10.0.0",
"jsonpatch>=1.33.0,<2.0.0",
"PyYAML>=5.3.0,<7.0.0",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/callbacks/test_usage_callback.py
new/langchain_core-1.5.5/tests/unit_tests/callbacks/test_usage_callback.py
--- old/langchain_core-1.5.4/tests/unit_tests/callbacks/test_usage_callback.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/callbacks/test_usage_callback.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,3 +1,4 @@
+import contextlib
from typing import Any
from langchain_core.callbacks import (
@@ -120,3 +121,15 @@
callback = UsageMetadataCallbackHandler()
_ = await llm.abatch(["Message 1", "Message 2"], config={"callbacks":
[callback]})
assert callback.usage_metadata == {"test_model": total_1_2}
+
+
+def test_usage_callback_clears_on_exception() -> None:
+ """Callback must stop tracking after with-block exits via exception
(#38989)."""
+ llm = FakeChatModelWithResponseMetadata(messages=iter(messages),
model_name="fake")
+ with contextlib.suppress(RuntimeError), get_usage_metadata_callback() as
cb:
+ _ = llm.invoke("in block")
+ raise RuntimeError
+
+ # Calls after the block must not accumulate into the previous callback.
+ _ = llm.invoke("outside block")
+ assert cb.usage_metadata == {"fake": usage1}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/language_models/chat_models/test_cache.py
new/langchain_core-1.5.5/tests/unit_tests/language_models/chat_models/test_cache.py
---
old/langchain_core-1.5.4/tests/unit_tests/language_models/chat_models/test_cache.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.5.5/tests/unit_tests/language_models/chat_models/test_cache.py
2020-02-02 01:00:00.000000000 +0100
@@ -38,6 +38,10 @@
"""Clear cache."""
self._cache = {}
+ def __len__(self) -> int:
+ """Return the number of cached entries."""
+ return len(self._cache)
+
def test_local_cache_sync() -> None:
"""Test that the local cache is being populated but not the global one."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/language_models/llms/test_cache.py
new/langchain_core-1.5.5/tests/unit_tests/language_models/llms/test_cache.py
---
old/langchain_core-1.5.4/tests/unit_tests/language_models/llms/test_cache.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.5.5/tests/unit_tests/language_models/llms/test_cache.py
2020-02-02 01:00:00.000000000 +0100
@@ -27,6 +27,10 @@
"""Clear cache."""
self._cache = {}
+ def __len__(self) -> int:
+ """Return the number of cached entries."""
+ return len(self._cache)
+
async def test_local_cache_generate_async() -> None:
global_cache = InMemoryCache()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/messages/block_translators/test_anthropic.py
new/langchain_core-1.5.5/tests/unit_tests/messages/block_translators/test_anthropic.py
---
old/langchain_core-1.5.4/tests/unit_tests/messages/block_translators/test_anthropic.py
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.5.5/tests/unit_tests/messages/block_translators/test_anthropic.py
2020-02-02 01:00:00.000000000 +0100
@@ -1,3 +1,5 @@
+from typing import Any
+
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage
from langchain_core.messages import content as types
@@ -507,3 +509,66 @@
]
assert message.content_blocks == expected
+
+
+def test_convert_to_v1_from_anthropic_input_malformed_sources() -> None:
+ content: list[str | dict[Any, Any]] = [
+ {"type": "document", "source": {"type": "base64", "media_type":
"app/pdf"}},
+ {"type": "document", "source": {"type": "url"}},
+ {"type": "document", "source": {"type": "file"}},
+ {"type": "document", "source": {"type": "text"}},
+ {"type": "image", "source": {"type": "base64", "media_type":
"image/jpeg"}},
+ {"type": "image", "source": {"type": "url"}},
+ {"type": "image", "source": {"type": "file"}},
+ ]
+ message = HumanMessage(content)
+
+ assert message.content_blocks == [
+ *[{"type": "non_standard", "value": block} for block in content[:4]],
+ *content[4:],
+ ]
+
+
+def test_convert_to_v1_from_anthropic_malformed_citations() -> None:
+ message = AIMessage(
+ [
+ {
+ "type": "text",
+ "text": "Source-backed answer.",
+ "citations": [
+ {
+ "type": "web_search_result_location",
+ "cited_text": "Source text",
+ },
+ {
+ "type": "search_result_location",
+ "title": "Document Title",
+ },
+ ],
+ },
+ ],
+ response_metadata={"model_provider": "anthropic"},
+ )
+
+ assert message.content_blocks == [
+ {
+ "type": "text",
+ "text": "Source-backed answer.",
+ "annotations": [
+ {
+ "type": "non_standard_annotation",
+ "value": {
+ "type": "web_search_result_location",
+ "cited_text": "Source text",
+ },
+ },
+ {
+ "type": "non_standard_annotation",
+ "value": {
+ "type": "search_result_location",
+ "title": "Document Title",
+ },
+ },
+ ],
+ },
+ ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/prompts/test_dict.py
new/langchain_core-1.5.5/tests/unit_tests/prompts/test_dict.py
--- old/langchain_core-1.5.4/tests/unit_tests/prompts/test_dict.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/prompts/test_dict.py
2020-02-02 01:00:00.000000000 +0100
@@ -116,3 +116,46 @@
ValueError, match="Variable names cannot contain attribute access"
):
PromptTemplate.from_template("{name.__class__}",
template_format="f-string")
+
+
+def test_dict_prompt_template_preserves_non_str_items_in_list() -> None:
+ """Non-str/non-dict items inside lists must be preserved, not silently
dropped.
+
+ Regression test for #39152: int, float, bool, None and other scalars nested
+ inside a list value were silently discarded by _insert_input_variables.
+ """
+ template = {
+ "type": "tool_use",
+ "id": "call_1",
+ "name": "search",
+ "input": {
+ "query": "{q}",
+ "top_k_scores": [1, 2, 3],
+ "flags": [True, None],
+ },
+ }
+ prompt = DictPromptTemplate(template=template, template_format="f-string")
+ result = prompt.format(q="cats")
+
+ assert result["input"]["query"] == "cats"
+ assert result["input"]["top_k_scores"] == [1, 2, 3]
+ assert result["input"]["flags"] == [True, None]
+
+
+def test_dict_prompt_template_preserves_mixed_list() -> None:
+ """Mixed-type lists must survive formatting unchanged (except str
interpolation)."""
+ template = {
+ "type": "x",
+ "mixed": ["a {v}", 1, 2.5, {"k": "val"}, None, True],
+ }
+ prompt = DictPromptTemplate(template=template, template_format="f-string")
+ result = prompt.format(v="b")
+
+ assert result["mixed"] == ["a b", 1, 2.5, {"k": "val"}, None, True]
+
+
+def test_dict_prompt_template_preserves_list_with_no_variables() -> None:
+ """Items must survive even when the template has no variables at all."""
+ template = {"nums": [1, 2]}
+ prompt = DictPromptTemplate(template=template, template_format="mustache")
+ assert prompt.format() == {"nums": [1, 2]}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/runnables/__snapshots__/test_fallbacks.ambr
new/langchain_core-1.5.5/tests/unit_tests/runnables/__snapshots__/test_fallbacks.ambr
---
old/langchain_core-1.5.4/tests/unit_tests/runnables/__snapshots__/test_fallbacks.ambr
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.5.5/tests/unit_tests/runnables/__snapshots__/test_fallbacks.ambr
2020-02-02 01:00:00.000000000 +0100
@@ -84,7 +84,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['foo'], i=1)",
+ "repr": "FakeListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['foo'], i=1)",
"name": "FakeListLLM"
}
},
@@ -128,7 +128,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['bar'])",
+ "repr": "FakeListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['bar'])",
"name": "FakeListLLM"
}
},
@@ -268,7 +268,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['foo'], i=1)",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['foo'], i=1)",
"name": "FakeListLLM"
},
"fallbacks": [
@@ -281,7 +281,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['bar'])",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['bar'])",
"name": "FakeListLLM"
}
],
@@ -322,7 +322,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['foo'], i=1)",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['foo'], i=1)",
"name": "FakeListLLM"
},
"fallbacks": [
@@ -335,7 +335,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['baz'], i=1)",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['baz'], i=1)",
"name": "FakeListLLM"
},
{
@@ -347,7 +347,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['bar'])",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['bar'])",
"name": "FakeListLLM"
}
],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr
new/langchain_core-1.5.5/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr
---
old/langchain_core-1.5.4/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr
2020-02-02 01:00:00.000000000 +0100
+++
new/langchain_core-1.5.5/tests/unit_tests/runnables/__snapshots__/test_runnable.ambr
2020-02-02 01:00:00.000000000 +0100
@@ -97,7 +97,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['foo, bar'])",
+ "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['foo, bar'])",
"name": "FakeListChatModel"
}
],
@@ -227,7 +227,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['baz, qux'])",
+ "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['baz, qux'])",
"name": "FakeListChatModel"
}
],
@@ -346,7 +346,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['foo, bar'])",
+ "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['foo, bar'])",
"name": "FakeListChatModel"
},
{
@@ -457,7 +457,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['baz, qux'])",
+ "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['baz, qux'])",
"name": "FakeListChatModel"
}
],
@@ -848,7 +848,7 @@
"fake",
"FakeStreamingListLLM"
],
- "repr": "FakeStreamingListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['first item, second item, third
item'])",
+ "repr": "FakeStreamingListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['first item, second item, third
item'])",
"name": "FakeStreamingListLLM"
},
{
@@ -884,7 +884,7 @@
"fake",
"FakeStreamingListLLM"
],
- "repr": "FakeStreamingListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['this', 'is', 'a', 'test'])",
+ "repr": "FakeStreamingListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['this', 'is', 'a', 'test'])",
"name": "FakeStreamingListLLM"
}
},
@@ -1009,7 +1009,7 @@
# name: test_prompt_with_chat_model
'''
ChatPromptTemplate(input_variables=['question'], input_types={},
partial_variables={},
messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[],
input_types={}, partial_variables={}, template='You are a nice assistant.'),
additional_kwargs={}),
HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['question'],
input_types={}, partial_variables={}, template='{question}'),
additional_kwargs={})])
- | FakeListChatModel(metadata={'lc_versions': {'langchain-core': '1.5.4'}},
responses=['foo'])
+ | FakeListChatModel(metadata={'lc_versions': {'langchain-core': '1.5.5'}},
responses=['foo'])
'''
# ---
# name: test_prompt_with_chat_model.1
@@ -1109,7 +1109,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['foo'])",
+ "repr": "FakeListChatModel(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['foo'])",
"name": "FakeListChatModel"
}
},
@@ -1220,7 +1220,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['foo, bar'])",
+ "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['foo, bar'])",
"name": "FakeListChatModel"
}
],
@@ -1249,7 +1249,7 @@
# name: test_prompt_with_chat_model_async
'''
ChatPromptTemplate(input_variables=['question'], input_types={},
partial_variables={},
messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[],
input_types={}, partial_variables={}, template='You are a nice assistant.'),
additional_kwargs={}),
HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['question'],
input_types={}, partial_variables={}, template='{question}'),
additional_kwargs={})])
- | FakeListChatModel(metadata={'lc_versions': {'langchain-core': '1.5.4'}},
responses=['foo'])
+ | FakeListChatModel(metadata={'lc_versions': {'langchain-core': '1.5.5'}},
responses=['foo'])
'''
# ---
# name: test_prompt_with_chat_model_async.1
@@ -1349,7 +1349,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['foo'])",
+ "repr": "FakeListChatModel(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['foo'])",
"name": "FakeListChatModel"
}
},
@@ -1459,7 +1459,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['foo', 'bar'])",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['foo', 'bar'])",
"name": "FakeListLLM"
}
},
@@ -1576,7 +1576,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=['foo', 'bar'])",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=['foo', 'bar'])",
"name": "FakeListLLM"
}
],
@@ -1699,7 +1699,7 @@
"fake",
"FakeStreamingListLLM"
],
- "repr": "FakeStreamingListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['bear, dog, cat', 'tomato, lettuce,
onion'])",
+ "repr": "FakeStreamingListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['bear, dog, cat', 'tomato, lettuce,
onion'])",
"name": "FakeStreamingListLLM"
}
],
@@ -1867,7 +1867,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['4'])",
+ "repr": "FakeListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['4'])",
"name": "FakeListLLM"
}
},
@@ -1940,7 +1940,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['2'])",
+ "repr": "FakeListLLM(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['2'])",
"name": "FakeListLLM"
}
},
@@ -13407,7 +13407,7 @@
just_to_test_lambda: RunnableLambda(...)
}
| ChatPromptTemplate(input_variables=['documents', 'question'],
input_types={}, partial_variables={},
messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[],
input_types={}, partial_variables={}, template='You are a nice assistant.'),
additional_kwargs={}),
HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['documents',
'question'], input_types={}, partial_variables={},
template='Context:\n{documents}\n\nQuestion:\n{question}'),
additional_kwargs={})])
- | FakeListChatModel(metadata={'lc_versions': {'langchain-core': '1.5.4'}},
responses=['foo, bar'])
+ | FakeListChatModel(metadata={'lc_versions': {'langchain-core': '1.5.5'}},
responses=['foo, bar'])
| CommaSeparatedListOutputParser()
'''
# ---
@@ -13610,7 +13610,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=['foo, bar'])",
+ "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=['foo, bar'])",
"name": "FakeListChatModel"
}
],
@@ -13636,8 +13636,8 @@
ChatPromptTemplate(input_variables=['question'], input_types={},
partial_variables={},
messages=[SystemMessagePromptTemplate(prompt=PromptTemplate(input_variables=[],
input_types={}, partial_variables={}, template='You are a nice assistant.'),
additional_kwargs={}),
HumanMessagePromptTemplate(prompt=PromptTemplate(input_variables=['question'],
input_types={}, partial_variables={}, template='{question}'),
additional_kwargs={})])
| RunnableLambda(...)
| {
- chat: FakeListChatModel(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=["i'm a chatbot"]),
- llm: FakeListLLM(metadata={'lc_versions': {'langchain-core': '1.5.4'}},
responses=["i'm a textbot"])
+ chat: FakeListChatModel(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=["i'm a chatbot"]),
+ llm: FakeListLLM(metadata={'lc_versions': {'langchain-core': '1.5.5'}},
responses=["i'm a textbot"])
}
'''
# ---
@@ -13762,7 +13762,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=[\"i'm a chatbot\"])",
+ "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=[\"i'm a chatbot\"])",
"name": "FakeListChatModel"
},
"llm": {
@@ -13774,7 +13774,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=[\"i'm a textbot\"])",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=[\"i'm a textbot\"])",
"name": "FakeListLLM"
}
}
@@ -13917,7 +13917,7 @@
"fake_chat_models",
"FakeListChatModel"
],
- "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.4'}}, responses=[\"i'm a chatbot\"])",
+ "repr": "FakeListChatModel(metadata={'lc_versions':
{'langchain-core': '1.5.5'}}, responses=[\"i'm a chatbot\"])",
"name": "FakeListChatModel"
},
"kwargs": {
@@ -13938,7 +13938,7 @@
"fake",
"FakeListLLM"
],
- "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.4'}}, responses=[\"i'm a textbot\"])",
+ "repr": "FakeListLLM(metadata={'lc_versions': {'langchain-core':
'1.5.5'}}, responses=[\"i'm a textbot\"])",
"name": "FakeListLLM"
},
"passthrough": {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/runnables/test_utils.py
new/langchain_core-1.5.5/tests/unit_tests/runnables/test_utils.py
--- old/langchain_core-1.5.4/tests/unit_tests/runnables/test_utils.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/runnables/test_utils.py
2020-02-02 01:00:00.000000000 +0100
@@ -5,6 +5,7 @@
from langchain_core.runnables.base import RunnableLambda
from langchain_core.runnables.utils import (
+ AddableDict,
get_function_nonlocals,
get_lambda_source,
indent_lines_after_first,
@@ -73,3 +74,29 @@
assert RunnableLambda(my_func3).deps == [agent]
assert RunnableLambda(my_func4).deps == [global_agent]
assert RunnableLambda(func).deps == [nl]
+
+
+def test_addable_dict_add_incompatible_types_raises() -> None:
+ left = AddableDict({"count": 1})
+ right = AddableDict({"count": "some_string"})
+ with pytest.raises(
+ TypeError,
+ match=r"Cannot add incompatible types for key 'count': 'int' and
'str'\.",
+ ):
+ left + right
+
+
+def test_addable_dict_radd_incompatible_types_raises() -> None:
+ left = AddableDict({"count": 1})
+ right = AddableDict({"count": "some_string"})
+ with pytest.raises(
+ TypeError,
+ match=r"Cannot add incompatible types for key 'count': 'int' and
'str'\.",
+ ):
+ right.__radd__(left)
+
+
+def test_addable_dict_add_none_seeded_key_is_unaffected() -> None:
+ left = AddableDict({"data": None})
+ right = AddableDict({"data": {"a": 1}})
+ assert (left + right) == AddableDict({"data": {"a": 1}})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/test_messages.py
new/langchain_core-1.5.5/tests/unit_tests/test_messages.py
--- old/langchain_core-1.5.4/tests/unit_tests/test_messages.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/test_messages.py 2020-02-02
01:00:00.000000000 +0100
@@ -225,6 +225,14 @@
assert (default_id_chunk + provider_chunk).id == meaningful_id
+def test_message_chunks_bool_additional_kwargs_raises() -> None:
+ """Differing booleans (e.g. `refusal`) must not silently coerce to
`int`."""
+ a = AIMessageChunk(content="", additional_kwargs={"refusal": True})
+ b = AIMessageChunk(content="", additional_kwargs={"refusal": False})
+ with pytest.raises(TypeError, match="unsupported type"):
+ a + b
+
+
def test_chat_message_chunks() -> None:
assert ChatMessageChunk(role="User", content="I am", id="ai4") +
ChatMessageChunk(
role="User", content=" indeed."
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/tests/unit_tests/test_tools.py
new/langchain_core-1.5.5/tests/unit_tests/test_tools.py
--- old/langchain_core-1.5.4/tests/unit_tests/test_tools.py 2020-02-02
01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/test_tools.py 2020-02-02
01:00:00.000000000 +0100
@@ -23,7 +23,14 @@
)
import pytest
-from pydantic import BaseModel, ConfigDict, Field, RootModel, ValidationError
+from pydantic import (
+ AliasChoices,
+ BaseModel,
+ ConfigDict,
+ Field,
+ RootModel,
+ ValidationError,
+)
from pydantic.v1 import BaseModel as BaseModelV1
from pydantic.v1 import ValidationError as ValidationErrorV1
from typing_extensions import TypedDict, override
@@ -365,9 +372,34 @@
assert isinstance(unstructured_tool_input, BaseTool)
assert unstructured_tool_input.args_schema is None
+ assert unstructured_tool_input.description == "Return the arguments
directly."
assert unstructured_tool_input.run("foo") == "foo"
+def test_simple_tool_decorator_no_infer_schema_uses_explicit_description() ->
None:
+ """Test that a simple tool preserves an explicit description."""
+
+ @tool(infer_schema=False, description="Echo the supplied input.")
+ def echo(tool_input: str) -> str:
+ return tool_input
+
+ assert echo.description == "Echo the supplied input."
+
+
+def
test_simple_tool_decorator_no_infer_schema_requires_description_or_docstring()
-> (
+ None
+):
+ """Test that a simple tool requires an authored description."""
+ with pytest.raises(
+ ValueError,
+ match="Function must have either a docstring or description",
+ ):
+
+ @tool(infer_schema=False)
+ def echo(tool_input: str) -> str:
+ return tool_input
+
+
def test_structured_tool_types_parsed() -> None:
"""Test the non-primitive types are correctly passed to structured
tools."""
@@ -1123,6 +1155,30 @@
assert expected == actual
[email protected](
+ sys.version_info >= (3, 14),
+ reason="pydantic.v1 namespace not supported with Python 3.14+",
+)
+async def test_async_validation_error_handling_pydantic_v1_schema() -> None:
+ """Test async validation error handling for Pydantic V1 schemas."""
+
+ class Args(BaseModelV1):
+ x: int
+
+ def foo(x: int) -> str:
+ """Return x as text."""
+ return str(x)
+
+ tool_ = StructuredTool.from_function(
+ foo,
+ args_schema=cast("ArgsSchema", Args),
+ handle_validation_error=True,
+ )
+
+ assert tool_.run({"x": "not-an-integer"}) == "Tool input validation error"
+ assert await tool_.arun({"x": "not-an-integer"}) == "Tool input validation
error"
+
+
@pytest.mark.parametrize(
"handler",
[
@@ -3975,6 +4031,22 @@
assert handler.captured_tool_call_ids[0] == "run_method_tool_call_id"
+def test_tool_args_schema_required_field_validation_alias() -> None:
+ """Test required args provided through validation aliases reach the
tool."""
+
+ class Args(BaseModel):
+ """Tool arguments."""
+
+ canonical: str = Field(validation_alias=AliasChoices("canonical",
"alias"))
+
+ @tool(args_schema=Args)
+ def aliased_tool(canonical: str) -> str:
+ """Return the canonical argument."""
+ return canonical
+
+ assert aliased_tool.invoke({"alias": "value"}) == "value"
+
+
def test_tool_args_schema_default_values() -> None:
"""Test that Pydantic default values from `args_schema` are applied.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/utils/test_aiter.py
new/langchain_core-1.5.5/tests/unit_tests/utils/test_aiter.py
--- old/langchain_core-1.5.4/tests/unit_tests/utils/test_aiter.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/utils/test_aiter.py
2020-02-02 01:00:00.000000000 +0100
@@ -12,10 +12,14 @@
(3, [10, 20, 30, 40, 50], [[10, 20, 30], [40, 50]]),
(1, [100, 200, 300], [[100], [200], [300]]),
(4, [], []),
+ (None, [1, 2, 3], [[1, 2, 3]]),
+ (None, [], []),
],
)
async def test_abatch_iterate(
- input_size: int, input_iterable: list[str], expected_output:
list[list[str]]
+ input_size: int | None,
+ input_iterable: list[str],
+ expected_output: list[list[str]],
) -> None:
"""Test batching function."""
@@ -29,3 +33,15 @@
output = [el async for el in iterator_]
assert output == expected_output
+
+
[email protected]("input_size", [0, -1])
+async def test_abatch_iterate_invalid_size(input_size: int) -> None:
+ """Non-positive sizes should raise instead of silently discarding data."""
+
+ async def _to_async_iterable(iterable: list[int]) -> AsyncIterator[int]:
+ for item in iterable:
+ yield item
+
+ with pytest.raises(ValueError, match="positive integer"):
+ _ = [el async for el in abatch_iterate(input_size,
_to_async_iterable([1, 2]))]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/utils/test_function_calling.py
new/langchain_core-1.5.5/tests/unit_tests/utils/test_function_calling.py
--- old/langchain_core-1.5.4/tests/unit_tests/utils/test_function_calling.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/utils/test_function_calling.py
2020-02-02 01:00:00.000000000 +0100
@@ -795,6 +795,24 @@
assert not response.tool_calls
+def test_tool_outputs_fewer_than_tool_calls_raises() -> None:
+ with pytest.raises(ValueError, match="must match"):
+ tool_example_to_messages(
+ input="Extract both values",
+ tool_calls=[FakeCall(data="a"), FakeCall(data="b")],
+ tool_outputs=["only one output"],
+ )
+
+
+def test_tool_outputs_more_than_tool_calls_raises() -> None:
+ with pytest.raises(ValueError, match="must match"):
+ tool_example_to_messages(
+ input="Extract one value",
+ tool_calls=[FakeCall(data="a")],
+ tool_outputs=["output1", "extra output"],
+ )
+
+
@pytest.mark.parametrize(
"typed_dict",
[ExtensionsTypedDict, TypingTypedDict],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/utils/test_iter.py
new/langchain_core-1.5.5/tests/unit_tests/utils/test_iter.py
--- old/langchain_core-1.5.4/tests/unit_tests/utils/test_iter.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/utils/test_iter.py
2020-02-02 01:00:00.000000000 +0100
@@ -10,10 +10,21 @@
(3, [10, 20, 30, 40, 50], [[10, 20, 30], [40, 50]]),
(1, [100, 200, 300], [[100], [200], [300]]),
(4, [], []),
+ (None, [1, 2, 3], [[1, 2, 3]]),
+ (None, [], []),
],
)
def test_batch_iterate(
- input_size: int, input_iterable: list[str], expected_output:
list[list[str]]
+ input_size: int | None,
+ input_iterable: list[str],
+ expected_output: list[list[str]],
) -> None:
"""Test batching function."""
assert list(batch_iterate(input_size, input_iterable)) == expected_output
+
+
[email protected]("input_size", [0, -1])
+def test_batch_iterate_invalid_size(input_size: int) -> None:
+ """Non-positive sizes should raise instead of silently discarding data."""
+ with pytest.raises(ValueError, match="positive integer"):
+ list(batch_iterate(input_size, [1, 2, 3]))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/langchain_core-1.5.4/tests/unit_tests/utils/test_utils.py
new/langchain_core-1.5.5/tests/unit_tests/utils/test_utils.py
--- old/langchain_core-1.5.4/tests/unit_tests/utils/test_utils.py
2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/tests/unit_tests/utils/test_utils.py
2020-02-02 01:00:00.000000000 +0100
@@ -129,6 +129,18 @@
# Other integer fields should still be summed (e.g., token counts)
({"tokens": 10}, {"tokens": 5}, {"tokens": 15}),
({"count": 1}, {"count": 2}, {"count": 3}),
+ # Differing booleans must not silently coerce to `int` (e.g. `True +
False`).
+ (
+ {"a": True},
+ {"a": False},
+ pytest.raises(
+ TypeError,
+ match=(
+ "Additional kwargs key a already exists in left dict and
value "
+ r"has unsupported type .+bool.+."
+ ),
+ ),
+ ),
],
)
def test_merge_dicts(
@@ -437,6 +449,16 @@
[{"no_index": "b"}],
[{"no_index": "a"}, {"no_index": "b"}],
),
+ # A string element whose text happens to contain the literal substring
+ # "index" must not be treated as index-keyed (it isn't a dict).
+ (
+ ["the index is here", {"index": 0, "reference_ids": ["a"]}],
+ [{"index": 0, "reference_ids": ["b"]}],
+ [
+ "the index is here",
+ {"index": 0, "reference_ids": ["a", "b"]},
+ ],
+ ),
],
)
def test_merge_lists(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/langchain_core-1.5.4/uv.lock
new/langchain_core-1.5.5/uv.lock
--- old/langchain_core-1.5.4/uv.lock 2020-02-02 01:00:00.000000000 +0100
+++ new/langchain_core-1.5.5/uv.lock 2020-02-02 01:00:00.000000000 +0100
@@ -1040,9 +1040,10 @@
[[package]]
name = "langchain-core"
-version = "1.5.4"
+version = "1.5.5"
source = { editable = "." }
dependencies = [
+ { name = "httpx" },
{ name = "jsonpatch" },
{ name = "langchain-protocol" },
{ name = "langsmith" },
@@ -1090,6 +1091,7 @@
[package.metadata]
requires-dist = [
+ { name = "httpx", specifier = ">=0.23.0,<1.0.0" },
{ name = "jsonpatch", specifier = ">=1.33.0,<2.0.0" },
{ name = "langchain-protocol", specifier = ">=0.0.17" },
{ name = "langsmith", specifier = ">=0.3.45,<1.0.0" },