langchain - ✅(Solved) Fix Bug: `merge_dicts` raises TypeError for float values during streaming chunk aggregation [5 pull requests, 11 comments, 6 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#36011Fetched 2026-04-08 00:47:37
View on GitHub
Comments
11
Participants
6
Timeline
51
Reactions
0
Author
Timeline (top)
cross-referenced ×17referenced ×15commented ×11mentioned ×3

merge_dicts() in libs/core/langchain_core/utils/_merge.py handles str, dict, list, equal values, and int types — but not float. When two dicts contain the same key with unequal float values, the function falls through to the final else branch and raises a TypeError.

This affects streaming scenarios where LLM providers return float fields in generation_info or additional_kwargs (e.g., logprob, score, safety scores, cost fields, etc.). During chunk aggregation via ChatGenerationChunk.__add__()merge_dicts(), these float fields cause the stream to crash.

History: Issue #17376 reported the same bug in 2024. PR #16605 added int support but omitted float. PR #36012 attempted a fix but was closed due to missing issue link and unrelated changes.

Error Message

TypeError: Additional kwargs key score already exists in left dict and value has unsupported type <class 'float'>.

Root Cause

In _merge.py line 71, the type check is:

elif isinstance(merged[right_k], int):

This should be:

elif isinstance(merged[right_k], (int, float)):

Fix Action

Fixed

PR fix notes

PR #36012: fix(core): handle float type in merge_dicts, merge_obj, and _dict_int_op

Description (problem / solution / changelog)

Description

merge_dicts() and merge_obj() in libs/core/langchain_core/utils/_merge.py, as well as _dict_int_op() in libs/core/langchain_core/utils/usage.py, handle int types but not float. When these functions encounter float values, they fall through to error branches and raise TypeError or ValueError.

from langchain_core.utils._merge import merge_dicts

merge_dicts({"score": 0.5}, {"score": 0.3})  # ❌ TypeError

This affects streaming scenarios where LLM providers return float fields in generation_info, additional_kwargs, or UsageMetadata (e.g., logprob, score, safety scores, cost fields, etc.). During chunk aggregation via ChatGenerationChunk.__add__()merge_dicts(), or UsageMetadata.__add__()_dict_int_op(), these float fields cause the stream to crash.

Changes

libs/core/langchain_core/utils/_merge.py

  • merge_dicts(): Changed isinstance(merged[right_k], int) to isinstance(merged[right_k], (int, float)) so that float values are summed (or preserved for special keys like index, created, timestamp) instead of raising TypeError.
  • merge_obj(): Added isinstance(left, (int, float)) check after the equality check, so unequal numeric values are summed instead of raising ValueError. The ordering is important — equal values (including True == True, 42 == 42, 3.14 == 3.14) are handled by the equality branch first, and only unequal numeric values reach the addition branch.

libs/core/langchain_core/utils/usage.py

  • _dict_int_op(): Changed isinstance(..., int) to isinstance(..., (int, float)) so that float values in UsageMetadata (e.g., cost fields, timing data) are correctly handled during aggregation.
  • Updated type annotations (Callable[[int, int], int]Callable[[float, float], float]), docstrings, and error messages to reflect float support.

libs/core/tests/unit_tests/utils/test_utils.py

  • Added regression tests for float values in test_merge_dicts (summing, preserved special keys like index/created/timestamp).
  • Added regression tests for numeric addition in test_merge_obj (int and float).
  • Updated test_merge_obj_unmergeable_values to use set instead of different integers (since different integers are now correctly summed).

libs/core/tests/unit_tests/utils/test_usage.py

  • Added test_dict_int_op_float_add — tests pure float addition.
  • Added test_dict_int_op_mixed_int_float — tests mixed int/float addition.
  • Added test_dict_int_op_nested_float — tests nested dictionaries with float values.
  • Updated error message assertion to match new "int, and float" wording.

Design Note

As @Jairooh correctly pointed out, isinstance(x, int) returns True for bool in Python. In merge_obj(), this is safely handled because bool equality (True == True) is caught by the left == right branch before reaching the numeric addition branch. For merge_dicts(), the same ordering applies. This is a key design choice — the competing PR #36048 placed the numeric check before the equality check, causing 42 + 42 = 84 and True + True = 2 failures.

Regarding floating-point precision concerns: the current approach mirrors the existing int addition semantics. For use cases where last-value or max semantics are preferred over summation, that would be a separate feature/configuration beyond this bug fix.

Issue

Fixes #36011 Fixes #36015

Related

  • #17376 — Originally reported in 2024, closed as not_planned
  • PR #16605 — Added int support but omitted float
  • #36015 — _dict_int_op raises ValueError for float values in UsageMetadata

Changed files

  • libs/core/langchain_core/utils/_merge.py (modified, +3/-1)
  • libs/core/tests/unit_tests/utils/test_utils.py (modified, +13/-2)

PR #1: fix(core): add float support to merge_dicts for streaming scenarios

Description (problem / solution / changelog)

Fixes issue #36011 - merge_dicts raises TypeError for float values during streaming chunk aggregation

Problem

merge_dicts() in libs/core/langchain_core/utils/_merge.py handles str, dict, list, int, and equal values — but not float. When two dicts contain the same key with unequal float values, the function falls through to the final else branch and raises a TypeError.

This affects streaming scenarios where LLM providers return float fields in generation_info or additional_kwargs (e.g., logprob, score, safety scores, cost fields). During chunk aggregation via ChatGenerationChunk.__add__()merge_dicts(), these float fields cause the stream to crash.

Solution

Add float to the isinstance check alongside int, using the same last-wins strategy for temporal fields (index, created, timestamp) and summing behavior for other numeric fields.

Changes

  • Modified isinstance(merged[right_k], int) to isinstance(merged[right_k], (int, float))
  • Added comment explaining float support for streaming scenarios

Testing

The fix allows merging float values like:

merge_dicts({"score": 0.5}, {"score": 0.3})  # Now works: {"score": 0.8}

Closes #36011

Changed files

  • .github/scripts/check_diff.py (modified, +0/-1)
  • .github/scripts/get_min_versions.py (modified, +1/-1)
  • .github/workflows/refresh_model_profiles.yml (modified, +1/-22)
  • .github/workflows/require_issue_link.yml (modified, +22/-0)
  • .github/workflows/tag-external-contributions.yml (modified, +19/-3)
  • README.md (modified, +40/-32)
  • libs/core/langchain_core/cross_encoders.py (added, +18/-0)
  • libs/core/langchain_core/utils/_merge.py (modified, +2/-1)
  • libs/core/langchain_core/utils/function_calling.py (modified, +0/-2)
  • libs/core/langchain_core/version.py (modified, +1/-1)
  • libs/core/pyproject.toml (modified, +1/-1)
  • libs/core/uv.lock (modified, +92/-94)
  • libs/langchain/langchain_classic/retrievers/document_compressors/cross_encoder.py (modified, +2/-15)
  • libs/langchain/pyproject.toml (modified, +3/-3)
  • libs/langchain/tests/README.md (removed, +0/-3)
  • libs/langchain/tests/unit_tests/test_pytest_config.py (modified, +1/-1)
  • libs/langchain/uv.lock (modified, +158/-121)
  • libs/langchain_v1/langchain/__init__.py (modified, +1/-1)
  • libs/langchain_v1/langchain/agents/factory.py (modified, +41/-6)
  • libs/langchain_v1/langchain/chat_models/base.py (modified, +2/-0)
  • libs/langchain_v1/pyproject.toml (modified, +2/-2)
  • libs/langchain_v1/tests/cassettes/test_inference_to_native_output[True].yaml.gz (modified, +0/-0)
  • libs/langchain_v1/tests/cassettes/test_inference_to_tool_output[True].yaml.gz (modified, +0/-0)
  • libs/langchain_v1/tests/cassettes/test_strict_mode[True].yaml.gz (modified, +0/-0)
  • libs/langchain_v1/tests/unit_tests/conftest.py (modified, +20/-0)
  • libs/langchain_v1/uv.lock (modified, +150/-120)
  • libs/model-profiles/Makefile (modified, +33/-1)
  • libs/model-profiles/langchain_model_profiles/cli.py (modified, +7/-0)
  • libs/model-profiles/tests/unit_tests/test_cli.py (modified, +64/-0)
  • libs/model-profiles/uv.lock (modified, +86/-86)
  • libs/partners/anthropic/langchain_anthropic/_version.py (modified, +1/-1)
  • libs/partners/anthropic/langchain_anthropic/chat_models.py (modified, +11/-1)
  • libs/partners/anthropic/langchain_anthropic/data/_profiles.py (modified, +140/-2)
  • libs/partners/anthropic/pyproject.toml (modified, +2/-2)
  • libs/partners/anthropic/tests/integration_tests/test_chat_models.py (modified, +1/-1)
  • libs/partners/anthropic/tests/unit_tests/test_chat_models.py (modified, +153/-8)
  • libs/partners/anthropic/uv.lock (modified, +84/-84)
  • libs/partners/chroma/uv.lock (modified, +80/-80)
  • libs/partners/deepseek/langchain_deepseek/chat_models.py (modified, +5/-1)
  • libs/partners/deepseek/langchain_deepseek/data/_profiles.py (modified, +12/-0)
  • libs/partners/deepseek/tests/unit_tests/test_chat_models.py (modified, +11/-2)
  • libs/partners/deepseek/uv.lock (modified, +81/-81)
  • libs/partners/exa/uv.lock (modified, +78/-78)
  • libs/partners/fireworks/langchain_fireworks/data/_profiles.py (modified, +78/-0)
  • libs/partners/fireworks/uv.lock (modified, +78/-78)
  • libs/partners/groq/langchain_groq/data/_profiles.py (modified, +110/-0)
  • libs/partners/groq/uv.lock (modified, +76/-76)
  • libs/partners/huggingface/langchain_huggingface/data/_profiles.py (modified, +120/-0)
  • libs/partners/huggingface/uv.lock (modified, +96/-103)
  • libs/partners/mistralai/langchain_mistralai/data/_profiles.py (modified, +156/-0)
  • libs/partners/mistralai/pyproject.toml (modified, +2/-2)
  • libs/partners/mistralai/uv.lock (modified, +77/-77)
  • libs/partners/nomic/uv.lock (modified, +81/-81)
  • libs/partners/ollama/uv.lock (modified, +78/-78)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +7/-4)
  • libs/partners/openai/langchain_openai/data/_profiles.py (modified, +264/-0)
  • libs/partners/openai/tests/cassettes/TestOpenAIResponses.test_stream_time.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_agent_loop.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_agent_loop_streaming.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_client_executed_tool_search.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_code_interpreter.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_compaction.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_compaction_streaming.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_custom_tool.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_file_search.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_function_calling.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_image_generation_multi_turn.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_image_generation_streaming.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_incomplete_response.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_mcp_builtin.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_mcp_builtin_zdr.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_parsed_pydantic_schema.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_reasoning.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_schema_parsing_failures_responses_api.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_schema_parsing_failures_responses_api_async.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_stream_reasoning_summary.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_tool_search.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_tool_search_streaming.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/cassettes/test_web_search.yaml.gz (modified, +0/-0)
  • libs/partners/openai/tests/integration_tests/chat_models/test_base.py (modified, +6/-2)
  • libs/partners/openai/tests/integration_tests/chat_models/test_base_standard.py (modified, +3/-1)
  • libs/partners/openai/tests/integration_tests/chat_models/test_responses_standard.py (modified, +3/-1)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +39/-0)
  • libs/partners/openai/tests/unit_tests/test_tools.py (modified, +1/-1)
  • libs/partners/openai/uv.lock (modified, +84/-84)
  • libs/partners/openrouter/langchain_openrouter/data/_profiles.py (modified, +0/-0)
  • libs/partners/perplexity/langchain_perplexity/data/_profiles.py (modified, +0/-0)
  • libs/partners/perplexity/uv.lock (modified, +0/-0)
  • libs/partners/qdrant/uv.lock (modified, +0/-0)
  • libs/partners/xai/langchain_xai/chat_models.py (modified, +0/-0)
  • libs/partners/xai/langchain_xai/data/_profiles.py (modified, +0/-0)
  • libs/partners/xai/tests/integration_tests/test_chat_models.py (modified, +0/-0)
  • libs/partners/xai/tests/integration_tests/test_chat_models_standard.py (modified, +0/-0)
  • libs/partners/xai/tests/unit_tests/test_chat_models.py (modified, +0/-0)
  • libs/partners/xai/uv.lock (modified, +0/-0)
  • libs/standard-tests/langchain_tests/integration_tests/chat_models.py (modified, +0/-0)
  • libs/standard-tests/uv.lock (modified, +0/-0)
  • libs/text-splitters/langchain_text_splitters/base.py (modified, +0/-0)
  • libs/text-splitters/uv.lock (modified, +0/-0)

PR #36014: fix(core): handle float values in merge_dicts

Description (problem / solution / changelog)

Summary

Fixes #36011 - Bug: merge_dicts raises TypeError for float values during streaming chunk aggregation

Changes

  • Add float type handling in merge_dicts() function
  • Use last-wins strategy for float values (e.g., logprob, score, safety scores)
  • Fixes TypeError when streaming chunks contain unequal float values

Test

from langchain_core.utils._merge import merge_dicts

# Previously raised TypeError
result = merge_dicts({"score": 0.5}, {"score": 0.3})
# Now returns: {"score": 0.3}

Changed files

  • libs/core/langchain_core/utils/_merge.py (modified, +5/-0)

PR #36016: fix(core): handle float type in _dict_int_op for UsageMetadata aggregation

Description (problem / solution / changelog)

Description

_dict_int_op() in libs/core/langchain_core/utils/usage.py handles int and dict types but not float. When two dictionaries contain the same key with float values, the function raises ValueError.

This affects streaming scenarios where LLM providers include float fields in UsageMetadata (e.g., total_cost, pricing fields, or any custom float metric). During chunk aggregation via add_usage()_dict_int_op(), these float fields cause the aggregation to crash.

Error:

ValueError: Unknown value types: [<class 'float'>]. Only dict and int values are supported.

All 4 float scenarios crash:

  • Direct float values in dicts
  • UsageMetadata with float total_cost on both sides
  • Float field present on only one side (default=0 is int, other side is float)
  • Mixed int (from cache init total_cost=0) + float (from provider total_cost=0.005)

This is the same class of bug as #36011 (merge_dicts missing float support), but in a different function.

Changes

  1. Change isinstance(..., int) to isinstance(..., (int, float)) in _dict_int_op()
  2. Update docstring to reflect numeric (int/float) support
  3. Update error message to mention float
  4. Add 5 regression tests for float values (add, subtract, mixed int/float, nested, one-sided)
  5. Update existing test_dict_int_op_invalid_types error message match

Issue

Fixes #36015

Dependencies

None

Changed files

  • libs/core/langchain_core/utils/usage.py (modified, +8/-7)
  • libs/core/tests/unit_tests/utils/test_usage.py (modified, +41/-1)

PR #36021: fix: add float support in merge_dicts

Description (problem / solution / changelog)

Fix for issue #36011

Changed files

  • libs/core/langchain_core/utils/usage.py (modified, +8/-8)

Code Example

from langchain_core.utils._merge import merge_dicts

# int values merge correctly (added in #16605)
merge_dicts({"tokens": 10}, {"tokens": 5})  # ✅ {"tokens": 15}

# Equal float values pass (via the equality check)
merge_dicts({"score": 0.5}, {"score": 0.5})  # ✅ {"score": 0.5}

# Unequal float values crash
merge_dicts({"score": 0.5}, {"score": 0.3})  # ❌ TypeError

---

TypeError: Additional kwargs key score already exists in left dict and value has unsupported type <class 'float'>.

---

elif isinstance(merged[right_k], int):

---

elif isinstance(merged[right_k], (int, float)):

---

langchain-core: 1.2.19
Python: 3.10+
OS: macOS / Linux
RAW_BUFFERClick to expand / collapse

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangChain documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.

Example Code

from langchain_core.utils._merge import merge_dicts

# int values merge correctly (added in #16605)
merge_dicts({"tokens": 10}, {"tokens": 5})  # ✅ {"tokens": 15}

# Equal float values pass (via the equality check)
merge_dicts({"score": 0.5}, {"score": 0.5})  # ✅ {"score": 0.5}

# Unequal float values crash
merge_dicts({"score": 0.5}, {"score": 0.3})  # ❌ TypeError

Error Message and Stack Trace

TypeError: Additional kwargs key score already exists in left dict and value has unsupported type <class 'float'>.

Description

merge_dicts() in libs/core/langchain_core/utils/_merge.py handles str, dict, list, equal values, and int types — but not float. When two dicts contain the same key with unequal float values, the function falls through to the final else branch and raises a TypeError.

This affects streaming scenarios where LLM providers return float fields in generation_info or additional_kwargs (e.g., logprob, score, safety scores, cost fields, etc.). During chunk aggregation via ChatGenerationChunk.__add__()merge_dicts(), these float fields cause the stream to crash.

History: Issue #17376 reported the same bug in 2024. PR #16605 added int support but omitted float. PR #36012 attempted a fix but was closed due to missing issue link and unrelated changes.

Root Cause

In _merge.py line 71, the type check is:

elif isinstance(merged[right_k], int):

This should be:

elif isinstance(merged[right_k], (int, float)):

Proposed Fix

  1. Change isinstance(merged[right_k], int) to isinstance(merged[right_k], (int, float)) in merge_dicts()
  2. Apply the same fix to merge_obj() for consistency (add float to the numeric handling)
  3. Add regression tests for float values

System Info

langchain-core: 1.2.19
Python: 3.10+
OS: macOS / Linux

extent analysis

Fix Plan

To fix the issue with merge_dicts() not handling float values, follow these steps:

  • Update the merge_dicts() function in libs/core/langchain_core/utils/_merge.py to handle float values:
elif isinstance(merged[right_k], (int, float)):
    merged[right_k] += right_v
  • Apply the same fix to merge_obj() for consistency:
elif isinstance(merged[right_k], (int, float)):
    merged[right_k] += right_v
  • Add regression tests for float values, for example:
def test_merge_dicts_float():
    dict1 = {"score": 0.5}
    dict2 = {"score": 0.3}
    result = merge_dicts(dict1, dict2)
    assert result == {"score": 0.8}

def test_merge_dicts_float_equal():
    dict1 = {"score": 0.5}
    dict2 = {"score": 0.5}
    result = merge_dicts(dict1, dict2)
    assert result == {"score": 1.0}

Verification

To verify that the fix worked, run the regression tests and check that merge_dicts() correctly handles float values. You can also test the function with example code:

merge_dicts({"score": 0.5}, {"score": 0.3})  # {"score": 0.8}
merge_dicts({"score": 0.5}, {"score": 0.5})  # {"score": 1.0}

Extra Tips

  • Make sure to update the documentation to reflect the changes to merge_dicts() and merge_obj().
  • Consider adding more test cases to cover different scenarios, such as merging dictionaries with multiple float values or with a mix of int and float values.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

langchain - ✅(Solved) Fix Bug: `merge_dicts` raises TypeError for float values during streaming chunk aggregation [5 pull requests, 11 comments, 6 participants]