libs/core/langchain_core/utils/_merge.py PYTHON 209 lines View on github.com → Search inside
1from __future__ import annotations23from typing import Any456def merge_dicts(left: dict[str, Any], *others: dict[str, Any]) -> dict[str, Any]:7    r"""Merge dictionaries.89    Merge many dicts, handling specific scenarios where a key exists in both10    dictionaries but has a value of `None` in `'left'`. In such cases, the method uses11    the value from `'right'` for that key in the merged dictionary.1213    Args:14        left: The first dictionary to merge.15        others: The other dictionaries to merge.1617    Returns:18        The merged dictionary.1920    Raises:21        TypeError: If the key exists in both dictionaries but has a different type.22        TypeError: If the value has an unsupported type.2324    Example:25        If `left = {"function_call": {"arguments": None}}` and26        `right = {"function_call": {"arguments": "{\n"}}`, then, after merging, for the27        key `'function_call'`, the value from `'right'` is used, resulting in28        `merged = {"function_call": {"arguments": "{\n"}}`.29    """30    merged = left.copy()31    for right in others:32        for right_k, right_v in right.items():33            if right_k not in merged or (34                right_v is not None and merged[right_k] is None35            ):36                merged[right_k] = right_v37            elif right_v is None:38                continue39            elif type(merged[right_k]) is not type(right_v):40                msg = (41                    f'additional_kwargs["{right_k}"] already exists in this message,'42                    " but with a different type."43                )44                raise TypeError(msg)45            elif isinstance(merged[right_k], str):46                # TODO: Add below special handling for 'type' key in 0.3 and remove47                # merge_lists 'type' logic.48                #49                # if right_k == "type":50                #     if merged[right_k] == right_v:51                #         continue52                #     else:53                #         raise ValueError(54                #             "Unable to merge. Two different values seen for special "55                #             f"key 'type': {merged[right_k]} and {right_v}. 'type' "56                #             "should either occur once or have the same value across "57                #             "all dicts."58                #         )59                if (right_k == "index" and merged[right_k].startswith("lc_")) or (60                    right_k in {"id", "output_version", "model_provider"}61                    and merged[right_k] == right_v62                ):63                    continue64                merged[right_k] += right_v65            elif isinstance(merged[right_k], dict):66                merged[right_k] = merge_dicts(merged[right_k], right_v)67            elif isinstance(merged[right_k], list):68                merged[right_k] = merge_lists(merged[right_k], right_v)69            elif merged[right_k] == right_v:70                continue71            elif isinstance(merged[right_k], int):72                # Preserve identification and temporal fields using last-wins strategy73                # instead of summing:74                # - index: identifies which tool call a chunk belongs to75                # - created/timestamp: temporal values that shouldn't be accumulated76                if right_k in {"index", "created", "timestamp"}:77                    merged[right_k] = right_v78                else:79                    merged[right_k] += right_v80            else:81                msg = (82                    f"Additional kwargs key {right_k} already exists in left dict and "83                    f"value has unsupported type {type(merged[right_k])}."84                )85                raise TypeError(msg)86    return merged878889def merge_lists(left: list[Any] | None, *others: list[Any] | None) -> list[Any] | None:90    """Add many lists, handling `None`.9192    Args:93        left: The first list to merge.94        others: The other lists to merge.9596    Returns:97        The merged list.98    """99    merged = left.copy() if left is not None else None100    for other in others:101        if other is None:102            continue103        if merged is None:104            merged = other.copy()105        else:106            for e in other:107                if (108                    isinstance(e, dict)109                    and "index" in e110                    and (111                        isinstance(e["index"], int)112                        or (113                            isinstance(e["index"], str) and e["index"].startswith("lc_")114                        )115                    )116                ):117                    to_merge = [118                        i119                        for i, e_left in enumerate(merged)120                        if (121                            "index" in e_left122                            and e_left["index"] == e["index"]  # index matches123                            and (  # IDs not inconsistent124                                e_left.get("id") in (None, "")125                                or e.get("id") in (None, "")126                                or e_left.get("id") == e.get("id")127                            )128                        )129                    ]130                    if to_merge:131                        # TODO: Remove this once merge_dict is updated with special132                        # handling for 'type'.133                        if (left_type := merged[to_merge[0]].get("type")) and (134                            e.get("type") == "non_standard" and "value" in e135                        ):136                            if left_type != "non_standard":137                                # standard + non_standard138                                new_e: dict[str, Any] = {139                                    "extras": {140                                        k: v141                                        for k, v in e["value"].items()142                                        if k != "type"143                                    }144                                }145                            else:146                                # non_standard + non_standard147                                new_e = {148                                    "value": {149                                        k: v150                                        for k, v in e["value"].items()151                                        if k != "type"152                                    }153                                }154                                if "index" in e:155                                    new_e["index"] = e["index"]156                        else:157                            new_e = (158                                {k: v for k, v in e.items() if k != "type"}159                                if "type" in e160                                else e161                            )162                        merged[to_merge[0]] = merge_dicts(merged[to_merge[0]], new_e)163                    else:164                        merged.append(e)165                else:166                    merged.append(e)167    return merged168169170def merge_obj(left: Any, right: Any) -> Any:171    """Merge two objects.172173    It handles specific scenarios where a key exists in both dictionaries but has a174    value of `None` in `'left'`. In such cases, the method uses the value from `'right'`175    for that key in the merged dictionary.176177    Args:178        left: The first object to merge.179        right: The other object to merge.180181    Returns:182        The merged object.183184    Raises:185        TypeError: If the key exists in both dictionaries but has a different type.186        ValueError: If the two objects cannot be merged.187    """188    if left is None or right is None:189        return left if left is not None else right190    if type(left) is not type(right):191        msg = (192            f"left and right are of different types. Left type:  {type(left)}. Right "193            f"type: {type(right)}."194        )195        raise TypeError(msg)196    if isinstance(left, str):197        return left + right198    if isinstance(left, dict):199        return merge_dicts(left, right)200    if isinstance(left, list):201        return merge_lists(left, right)202    if left == right:203        return left204    msg = (205        f"Unable to merge {left=} and {right=}. Both must be of type str, dict, or "206        f"list, or else be two equal objects."207    )208    raise ValueError(msg)

Code quality findings 12

Use isinstance() for type checking instead of type()
type-check
elif type(merged[right_k]) is not type(right_v):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(merged[right_k], str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(merged[right_k], dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(merged[right_k], list):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
elif isinstance(merged[right_k], int):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(e, dict)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(e["index"], int)
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
isinstance(e["index"], str) and e["index"].startswith("lc_")
Use isinstance() for type checking instead of type()
type-check
if type(left) is not type(right):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(left, str):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(left, dict):
Overuse may indicate design issues; consider polymorphism
isinstance-overuse
if isinstance(left, list):

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.