{"record":{"id":"fa5b0729b88d381e","repo":"langchain-ai/langchain","slug":"max-depth-exceeded-unable-to-combine-dicts","errorCode":null,"errorMessage":"{max_depth=} exceeded, unable to combine dicts.","messagePattern":"(.+?) exceeded, unable to combine dicts\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"libs/core/langchain_core/utils/usage.py","lineNumber":39,"sourceCode":"    Supports nested dictionaries.\n\n    Args:\n        left: First dictionary to combine.\n        right: Second dictionary to combine.\n        op: Binary operation function to apply to integer values.\n        default: Default value to use when a key is missing from a dictionary.\n        depth: Current recursion depth (used internally).\n        max_depth: Maximum recursion depth (to prevent infinite loops).\n\n    Returns:\n        A new dictionary with combined values.\n\n    Raises:\n        ValueError: If `max_depth` is exceeded or if value types are not supported.\n    \"\"\"\n    if depth >= max_depth:\n        msg = f\"{max_depth=} exceeded, unable to combine dicts.\"\n        raise ValueError(msg)\n    combined: dict[str, Any] = {}\n    for k in set(left).union(right):\n        if isinstance(left.get(k, default), int) and isinstance(\n            right.get(k, default), int\n        ):\n            combined[k] = op(left.get(k, default), right.get(k, default))\n        elif isinstance(left.get(k, {}), dict) and isinstance(right.get(k, {}), dict):\n            combined[k] = _dict_int_op(\n                left.get(k, {}),\n                right.get(k, {}),\n                op,\n                default=default,\n                depth=depth + 1,\n                max_depth=max_depth,\n            )\n        else:\n            types = [type(d[k]) for d in (left, right) if k in d]\n            msg = (","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/langchain-ai/langchain/blob/e32fa9a52eab3b61ad7a45399bfde59b3e580fc4/libs/core/langchain_core/utils/usage.py#L21-L57","documentation":"Raised by `_dict_int_op` in `langchain_core.utils.usage` when combining two nested dicts of integers (e.g. accumulating token `UsageMetadata` across a run) exceeds the `max_depth` limit (default 100). It is a guard against pathological or self-referential structures that would recurse forever. Combining aborts with `ValueError`; no partial sum is returned.","triggerScenarios":"Merging/summing usage metadata dicts (`_dict_int_op(left, right, operator.add)` as done when aggregating token usage across streamed chunks or nested runs) where nesting depth of the value dicts reaches `max_depth`, or calling it directly with a very deeply nested (or cyclic, via shared sub-objects) dict.","commonSituations":"Custom `UsageMetadata` extensions that add deeply nested accounting keys; summing usage objects that were built by recursive accumulation without depth control; pathological payloads nested by upstream model providers; passing a too-small `max_depth` when calling the helper directly.","solutions":["Flatten or prune the nested keys you accumulate — usage dicts should stay shallow (input_tokens/output_tokens/total_tokens plus modest extras).","If calling `_dict_int_op` directly and depth is legitimate, raise `max_depth` explicitly.","Audit custom `on_llm_new_token`/usage aggregation code that nests previous results inside new results each step (the usual runaway cause).","Break reference cycles before combining (deep-copy or rebuild) so depth cannot grow unbounded."],"exampleFix":"# before\ndef merge_usage(acc, new):\n    return {\"step\": acc, \"usage\": new}  # nesting grows every call -> depth blowup\ntotal = _dict_int_op(merge_usage(total, chunk), operator.add)\n\n# after\ndef merge_usage(acc, new):\n    return _dict_int_op(acc, new, operator.add)  # keep the dict flat\ntotal = merge_usage(total, chunk)","handlingStrategy":"try-catch","validationCode":"def usage_depth(d: dict, depth: int = 0) -> int:\n    return max(\n        [usage_depth(v, depth + 1) for v in d.values() if isinstance(v, dict)] + [depth]\n    )\n\nif usage_depth(usage_dict) >= 100:\n    raise ValueError(\"usage dict nesting too deep; flatten before accumulating\")","typeGuard":null,"tryCatchPattern":"try:\n    total = _dict_int_op(left, right, operator.add)\nexcept ValueError:\n    total = left  # or recompute from flattened counters","preventionTips":["Accumulate usage into a flat dict; never nest previous results inside new ones.","Unit-test usage aggregation over long streaming runs for bounded depth.","Treat growing depth as a bug in your accumulation loop, not something to raise max_depth for."],"tags":["usage","token-counting","recursion"],"backgroundTag":null,"analyzedSha":"e32fa9a52eab3b61ad7a45399bfde59b3e580fc4","analyzedAt":"2026-08-14T18:42:09.092Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}