157 lines
5.6 KiB
Python
157 lines
5.6 KiB
Python
"""Addon parameters: observable mapping + normalization helper.
|
|
|
|
``addon_params`` is a free-form configuration dict on :class:`LightRAG` that
|
|
controls things like summary language and entity-type prompt overrides. The
|
|
module exposes:
|
|
|
|
- :class:`ObservableAddonParams` — a ``dict`` subclass that calls a callback
|
|
whenever the contents change so the LightRAG runtime can invalidate cached
|
|
derived state.
|
|
- :func:`default_addon_params` — environment-driven defaults.
|
|
- :func:`normalize_addon_params` — converts an arbitrary input into a plain
|
|
``dict`` with the env-driven defaults backfilled.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Callable, Mapping
|
|
|
|
from lightrag.constants import DEFAULT_SUMMARY_LANGUAGE
|
|
from lightrag.utils import get_env_value, logger
|
|
|
|
|
|
# Keys that used to live in addon_params but have been superseded by
|
|
# per-document ``process_options``. We log once when callers still pass them
|
|
# so existing configs surface their drift without breaking.
|
|
_DEPRECATED_ADDON_PARAM_KEYS: tuple[str, ...] = ("enable_multimodal_pipeline",)
|
|
_warned_deprecated_keys: set[str] = set()
|
|
|
|
|
|
def _emit_deprecated_addon_warnings(params: Mapping[str, Any]) -> None:
|
|
for key in _DEPRECATED_ADDON_PARAM_KEYS:
|
|
if key in params and key not in _warned_deprecated_keys:
|
|
logger.warning(
|
|
f"addon_params['{key}'] is deprecated and ignored; per-document "
|
|
f"behaviour is now controlled by filename-hint process_options "
|
|
f"(see docs/FileProcessingConfiguration-zh.md)."
|
|
)
|
|
_warned_deprecated_keys.add(key)
|
|
|
|
|
|
def default_addon_params() -> dict[str, Any]:
|
|
# Lazy import to avoid the parser_routing → utils → … cycle that
|
|
# would otherwise form when parser_routing imports back into this
|
|
# module via ``LightRAG`` construction paths.
|
|
from lightrag.parser.routing import default_chunker_config
|
|
|
|
return {
|
|
"language": get_env_value("SUMMARY_LANGUAGE", DEFAULT_SUMMARY_LANGUAGE, str),
|
|
"entity_type_prompt_file": get_env_value("ENTITY_TYPE_PROMPT_FILE", "", str),
|
|
# Per-strategy chunker parameters; mutate at runtime (e.g.
|
|
# ``rag.addon_params["chunker"]["recursive_character"]["separators"]
|
|
# = [...]``) to change defaults applied to subsequently
|
|
# enqueued documents. Per-document snapshots are persisted to
|
|
# ``full_docs[doc_id]["chunk_options"]`` at enqueue time and
|
|
# are not affected by later runtime mutations.
|
|
"chunker": default_chunker_config(),
|
|
}
|
|
|
|
|
|
def normalize_addon_params(addon_params: Mapping[str, Any] | None) -> dict[str, Any]:
|
|
"""Coerce ``addon_params`` to a plain dict with env defaults backfilled."""
|
|
from lightrag.parser.routing import default_chunker_config
|
|
|
|
if addon_params is None:
|
|
normalized = default_addon_params()
|
|
elif isinstance(addon_params, Mapping):
|
|
_emit_deprecated_addon_warnings(addon_params)
|
|
normalized = {
|
|
k: v
|
|
for k, v in addon_params.items()
|
|
if k not in _DEPRECATED_ADDON_PARAM_KEYS
|
|
}
|
|
else:
|
|
raise TypeError(
|
|
f"addon_params must be a Mapping or None, got {type(addon_params).__name__}"
|
|
)
|
|
|
|
# When the caller supplies addon_params explicitly, the dataclass
|
|
# default_factory is skipped — fall back to environment variables so
|
|
# ENTITY_TYPE_PROMPT_FILE / SUMMARY_LANGUAGE / chunker still apply.
|
|
normalized.setdefault(
|
|
"language", get_env_value("SUMMARY_LANGUAGE", DEFAULT_SUMMARY_LANGUAGE, str)
|
|
)
|
|
normalized.setdefault(
|
|
"entity_type_prompt_file",
|
|
get_env_value("ENTITY_TYPE_PROMPT_FILE", "", str),
|
|
)
|
|
# Build the chunker default lazily — `default_chunker_config()` reads env
|
|
# vars (e.g. CHUNK_R_SEPARATORS via json.loads) and would raise on a
|
|
# malformed value, which would prevent an explicit caller-supplied
|
|
# `chunker` from bypassing a broken environment.
|
|
if "chunker" not in normalized:
|
|
normalized["chunker"] = default_chunker_config()
|
|
return normalized
|
|
|
|
|
|
class ObservableAddonParams(dict[str, Any]):
|
|
def __init__(
|
|
self,
|
|
*args: Any,
|
|
on_change: Callable[[], None] | None = None,
|
|
**kwargs: Any,
|
|
) -> None:
|
|
super().__init__(*args, **kwargs)
|
|
self._on_change = on_change
|
|
|
|
def _changed(self) -> None:
|
|
if self._on_change is not None:
|
|
self._on_change()
|
|
|
|
def __setitem__(self, key: str, value: Any) -> None:
|
|
super().__setitem__(key, value)
|
|
self._changed()
|
|
|
|
def __delitem__(self, key: str) -> None:
|
|
super().__delitem__(key)
|
|
self._changed()
|
|
|
|
def clear(self) -> None:
|
|
if self:
|
|
super().clear()
|
|
self._changed()
|
|
|
|
def pop(self, key: str, default: Any = ...):
|
|
existed = key in self
|
|
if default is ...:
|
|
value = super().pop(key)
|
|
self._changed()
|
|
else:
|
|
value = super().pop(key, default)
|
|
if existed:
|
|
self._changed()
|
|
return value
|
|
|
|
def popitem(self) -> tuple[str, Any]:
|
|
item = super().popitem()
|
|
self._changed()
|
|
return item
|
|
|
|
def setdefault(self, key: str, default: Any = None) -> Any:
|
|
if key in self:
|
|
return self[key]
|
|
value = super().setdefault(key, default)
|
|
self._changed()
|
|
return value
|
|
|
|
def update(self, *args: Any, **kwargs: Any) -> None:
|
|
if not args and not kwargs:
|
|
return
|
|
super().update(*args, **kwargs)
|
|
self._changed()
|
|
|
|
def __ior__(self, other: Mapping[str, Any]):
|
|
super().__ior__(other)
|
|
self._changed()
|
|
return self
|