diff --git a/libs/langgraph/langgraph/_internal/_config.py b/libs/langgraph/langgraph/_internal/_config.py index 703f560d5..f8af02dae 100644 --- a/libs/langgraph/langgraph/_internal/_config.py +++ b/libs/langgraph/langgraph/_internal/_config.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections import ChainMap -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from os import getenv from typing import Any, cast @@ -312,11 +312,22 @@ def ensure_config(*configs: RunnableConfig | None) -> RunnableConfig: for k, v in config.items(): if _is_not_empty(v) and k not in CONFIG_KEYS: empty[CONF][k] = v + _empty_metadata = empty["metadata"] for key, value in empty[CONF].items(): - if ( - not key.startswith("__") - and isinstance(value, (str, int, float, bool)) - and key not in empty["metadata"] - ): - empty["metadata"][key] = value + if _exclude_as_metadata(key, value, _empty_metadata): + continue + _empty_metadata[key] = value return empty + + +_OMIT = ("key", "token", "secret", "password", "auth") + + +def _exclude_as_metadata(key: str, value: Any, metadata: Mapping[str, Any]) -> bool: + key_lower = key.casefold() + return ( + key.startswith("__") + or not isinstance(value, (str, int, float, bool)) + or key in metadata + or any(substr in key_lower for substr in _OMIT) + ) diff --git a/libs/langgraph/pyproject.toml b/libs/langgraph/pyproject.toml index b575ad754..afa8594e0 100644 --- a/libs/langgraph/pyproject.toml +++ b/libs/langgraph/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "langgraph" -version = "0.6.7" +version = "0.6.8" description = "Building stateful, multi-actor applications with LLMs" authors = [] requires-python = ">=3.9" diff --git a/libs/langgraph/tests/test_utils.py b/libs/langgraph/tests/test_utils.py index afe486af2..3c04202e3 100644 --- a/libs/langgraph/tests/test_utils.py +++ b/libs/langgraph/tests/test_utils.py @@ -17,16 +17,13 @@ import langsmith import pytest from typing_extensions import NotRequired, Required, TypedDict -from langgraph._internal._config import _is_not_empty +from langgraph._internal._config import _is_not_empty, ensure_config from langgraph._internal._fields import ( _is_optional_type, get_enhanced_type_hints, get_field_default, ) -from langgraph._internal._runnable import ( - is_async_callable, - is_async_generator, -) +from langgraph._internal._runnable import is_async_callable, is_async_generator from langgraph.constants import END from langgraph.graph import StateGraph from langgraph.graph.state import CompiledStateGraph @@ -301,3 +298,24 @@ def test_is_not_empty() -> None: assert not _is_not_empty([]) assert not _is_not_empty(()) assert not _is_not_empty({}) + + +def test_configurable_metadata(): + config = { + "configurable": { + "a-key": "foo", + "somesecretval": "bar", + "sometoken": "thetoken", + "__dontinclude": "bar", + "includeme": "hi", + "andme": 42, + "nested": {"foo": "bar"}, + "nooverride": -2, + }, + "metadata": {"nooverride": 18}, + } + expected = {"includeme", "andme", "nooverride"} + merged = ensure_config(config) + metadata = merged["metadata"] + assert metadata.keys() == expected + assert metadata["nooverride"] == 18