langchain - ✅(Solved) Fix Bug: `_dict_int_op` raises ValueError for float values in UsageMetadata during streaming aggregation [5 pull requests, 8 comments, 5 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#36015Fetched 2026-04-08 00:53:00
View on GitHub
Comments
8
Participants
5
Timeline
37
Reactions
0
Author
Timeline (top)
cross-referenced ×16referenced ×11commented ×8closed ×1

_dict_int_op() in libs/core/langchain_core/utils/usage.py only handles int and dict types but not float. When two dictionaries contain the same key with float values, the function raises a 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.

This is the same class of bug as #36011 (merge_dicts missing float support), but in a different function. The _dict_int_op function was designed to handle numeric operations on dictionaries but only checked for int, missing float.

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)

Error Message

import operator from langchain_core.utils.usage import _dict_int_op from langchain_core.messages.ai import add_usage, UsageMetadata

int values work fine

_dict_int_op({"a": 1}, {"a": 2}, operator.add) # ✅ {"a": 3}

float values crash

_dict_int_op({"tokens": 10, "cost": 0.05}, {"tokens": 20, "cost": 0.03}, operator.add)

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

Real-world scenario: UsageMetadata with float total_cost

left = UsageMetadata(input_tokens=100, output_tokens=50, total_tokens=150) left["total_cost"] = 0.005 right = UsageMetadata(input_tokens=200, output_tokens=100, total_tokens=300) right["total_cost"] = 0.010 add_usage(left, right) # ❌ ValueError

Root Cause

In usage.py, the type check is:

if isinstance(left.get(k, default), int) and isinstance(right.get(k, default), int):

This should be:

if isinstance(left.get(k, default), (int, float)) and isinstance(right.get(k, default), (int, float)):

Fix Action

Fixed

PR fix notes

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 #36020: fix(core): add float support to _dict_int_op in usage.py

Description (problem / solution / changelog)

Fixes #36015 - Added float support to _dict_int_op function

Changed files

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

PR #36023: fix(core): add float support to merge_dicts and _dict_int_op

Description (problem / solution / changelog)

Summary

Fixes two related bugs in langchain-core:

1. merge_dicts() missing float support (#36011)

merge_dicts() in _merge.py only handled int types but not float. When merging dicts with the same key containing unequal float values, it raised a TypeError.

Example that now works:

from langchain_core.utils._merge import merge_dicts
merge_dicts({"score": 0.5}, {"score": 0.3})  # {"score": 0.8}

2. _dict_int_op() missing float support (#36015)

_dict_int_op() in usage.py only checked for int types but not float. When aggregating UsageMetadata with float fields like total_cost during streaming, it raised a ValueError.

Example that now works:

from langchain_core.utils.usage import _dict_int_op
import operator

_dict_int_op(
    {"tokens": 10, "cost": 0.05}, 
    {"tokens": 20, "cost": 0.03}, 
    operator.add
)  # {"tokens": 30, "cost": 0.08}

Changes

  • _merge.py: Changed isinstance(merged[right_k], int) to isinstance(merged[right_k], (int, float))
  • usage.py: Changed isinstance(..., int) to isinstance(..., (int, float)) in two places
  • Updated docstrings and error messages to reflect float support
  • Updated type hints to include float

Testing

All original bug scenarios from issues #36011 and #36015 now work correctly.

Changed files

  • libs/core/langchain_core/messages/base.py (modified, +31/-2)
  • libs/core/langchain_core/utils/_merge.py (modified, +1/-1)
  • libs/core/langchain_core/utils/usage.py (modified, +8/-7)

PR #36040: fix(core): support float values in _dict_int_op

Description (problem / solution / changelog)

Summary

This PR fixes issue #36015 by adding float support to the _dict_int_op function in langchain_core/utils/usage.py.

Changes

  1. Changed isinstance check from int to (int, float) to support both integer and float values
  2. Updated type hints for the op parameter from Callable[[int, int], int] to Callable[[int | float, int | float], int | float]
  3. Updated default parameter type to int | float
  4. Updated docstring to reflect numeric (int/float) support
  5. Updated error message to mention "numeric (int/float)" instead of just "int"

Testing

The fix enables the following scenarios that previously crashed:

import operator
from langchain_core.utils.usage import _dict_int_op

# Float values now work
_dict_int_op({"tokens": 10, "cost": 0.05}, {"tokens": 20, "cost": 0.03}, operator.add)
# Returns: {"tokens": 30, "cost": 0.08}

Fixes #36015

Changed files

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

PR #3: fix: handle float values in streaming aggregation

Description (problem / solution / changelog)

Summary

This PR fixes two bugs related to float values in streaming aggregation:

  1. _dict_int_op in usage.py: The function was only handling int values but not float. During streaming, float values (like 1.0 instead of 1) could be passed, causing a ValueError.

  2. merge_dicts in _merge.py: Similarly, this function was only handling int values and would raise a TypeError when encountering float values during streaming chunk aggregation.

Changes

libs/core/langchain_core/utils/usage.py

  • Added _is_numeric() helper to check for int/float (excluding bool)
  • Modified _dict_int_op to handle float values by converting to int for operations
  • Updated error message to reflect support for numeric types

libs/core/langchain_core/utils/_merge.py

  • Modified type check to allow int/float mixing
  • Added handling for merging numeric types with different types

Tests

  • Added test cases for float handling in _dict_int_op
  • Added test cases for int/float merging in merge_dicts

Related Issues

  • Fixes #36015: Bug: _dict_int_op raises ValueError for float values in UsageMetadata during streaming aggregation
  • Fixes #36011: Bug: merge_dicts raises TypeError for float values during streaming chunk aggregation

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/structured_query.py (modified, +1/-1)
  • libs/core/langchain_core/utils/_merge.py (modified, +17/-7)
  • libs/core/langchain_core/utils/function_calling.py (modified, +0/-2)
  • libs/core/langchain_core/utils/usage.py (modified, +16/-8)
  • libs/core/langchain_core/version.py (modified, +1/-1)
  • libs/core/pyproject.toml (modified, +1/-1)
  • libs/core/tests/unit_tests/utils/test_usage.py (modified, +28/-1)
  • libs/core/tests/unit_tests/utils/test_utils.py (modified, +5/-0)
  • libs/core/uv.lock (modified, +14/-16)
  • 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/agents/middleware/types.py (modified, +4/-4)
  • 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, +148/-118)
  • 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, +10/-5)
  • 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, +75/-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, +1266/-0)
  • libs/partners/perplexity/langchain_perplexity/data/_profiles.py (modified, +24/-0)
  • libs/partners/perplexity/uv.lock (modified, +78/-78)
  • libs/partners/qdrant/uv.lock (modified, +80/-80)
  • libs/partners/xai/langchain_xai/chat_models.py (modified, +12/-16)
  • 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)

Code Example

import operator
from langchain_core.utils.usage import _dict_int_op
from langchain_core.messages.ai import add_usage, UsageMetadata

# int values work fine
_dict_int_op({"a": 1}, {"a": 2}, operator.add)  # ✅ {"a": 3}

# float values crash
_dict_int_op({"tokens": 10, "cost": 0.05}, {"tokens": 20, "cost": 0.03}, operator.add)
# ❌ ValueError: Unknown value types: [<class 'float'>]. Only dict and int values are supported.

# Real-world scenario: UsageMetadata with float total_cost
left = UsageMetadata(input_tokens=100, output_tokens=50, total_tokens=150)
left["total_cost"] = 0.005
right = UsageMetadata(input_tokens=200, output_tokens=100, total_tokens=300)
right["total_cost"] = 0.010
add_usage(left, right)  # ❌ ValueError

---

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

---

if isinstance(left.get(k, default), int) and isinstance(right.get(k, default), int):

---

if isinstance(left.get(k, default), (int, float)) and isinstance(right.get(k, default), (int, float)):

---

langchain-core: 0.3.x (latest main)
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

import operator
from langchain_core.utils.usage import _dict_int_op
from langchain_core.messages.ai import add_usage, UsageMetadata

# int values work fine
_dict_int_op({"a": 1}, {"a": 2}, operator.add)  # ✅ {"a": 3}

# float values crash
_dict_int_op({"tokens": 10, "cost": 0.05}, {"tokens": 20, "cost": 0.03}, operator.add)
# ❌ ValueError: Unknown value types: [<class 'float'>]. Only dict and int values are supported.

# Real-world scenario: UsageMetadata with float total_cost
left = UsageMetadata(input_tokens=100, output_tokens=50, total_tokens=150)
left["total_cost"] = 0.005
right = UsageMetadata(input_tokens=200, output_tokens=100, total_tokens=300)
right["total_cost"] = 0.010
add_usage(left, right)  # ❌ ValueError

Error Message and Stack Trace

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

Description

_dict_int_op() in libs/core/langchain_core/utils/usage.py only handles int and dict types but not float. When two dictionaries contain the same key with float values, the function raises a 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.

This is the same class of bug as #36011 (merge_dicts missing float support), but in a different function. The _dict_int_op function was designed to handle numeric operations on dictionaries but only checked for int, missing float.

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)

Root Cause

In usage.py, the type check is:

if isinstance(left.get(k, default), int) and isinstance(right.get(k, default), int):

This should be:

if isinstance(left.get(k, default), (int, float)) and isinstance(right.get(k, default), (int, float)):

Proposed Fix

  1. Change isinstance(..., int) to isinstance(..., (int, float)) in _dict_int_op()
  2. Update docstring and error message to reflect float support
  3. Add regression tests for float values

System Info

langchain-core: 0.3.x (latest main)
Python: 3.10+
OS: macOS / Linux

extent analysis

Fix Plan

To fix the issue, follow these steps:

  • Update the _dict_int_op function in usage.py to support float values:

if isinstance(left.get(k, default), (int, float)) and isinstance(right.get(k, default), (int, float)):

* Update the docstring and error message to reflect the added support for `float` values.
* Add regression tests for `float` values to ensure the function works correctly.

### Example Code
Here's an example of how the updated `_dict_int_op` function could look:
```python
def _dict_int_op(left, right, op, default=0):
    result = {}
    all_keys = set(list(left.keys()) + list(right.keys()))
    for k in all_keys:
        if isinstance(left.get(k, default), (int, float)) and isinstance(right.get(k, default), (int, float)):
            result[k] = op(left.get(k, default), right.get(k, default))
        else:
            raise ValueError(f"Unknown value types: {[type(left.get(k, default)), type(right.get(k, default))]}")
    return result

Verification

To verify that the fix worked, test the _dict_int_op function with float values:

_dict_int_op({"a": 1.5}, {"a": 2.5}, operator.add)  # {"a": 4.0}

Also, test the add_usage function with UsageMetadata objects containing float values:

left = UsageMetadata(input_tokens=100, output_tokens=50, total_tokens=150)
left["total_cost"] = 0.005
right = UsageMetadata(input_tokens=200, output_tokens=100, total_tokens=300)
right["total_cost"] = 0.010
add_usage(left, right)  # Should not raise a ValueError

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: `_dict_int_op` raises ValueError for float values in UsageMetadata during streaming aggregation [5 pull requests, 8 comments, 5 participants]