diff --git a/plugins/observability/langfuse/__init__.py b/plugins/observability/langfuse/__init__.py index edd7f3cca9..d5b83d1d6a 100644 --- a/plugins/observability/langfuse/__init__.py +++ b/plugins/observability/langfuse/__init__.py @@ -10,6 +10,7 @@ from __future__ import annotations import atexit import contextlib +import functools import json import logging import os @@ -383,18 +384,26 @@ def _normalize_payload(value: Any, *, tool_name: str = "", args: Any = None) -> return normalized +@functools.lru_cache(maxsize=8) +def _resolve_max_depth(configured_depth: str) -> int: + """Parse ``HERMES_LANGFUSE_MAX_DEPTH``; an invalid value warns ONCE per distinct value. + Cached on the raw string (not at import) so a long-lived process still picks up a changed + env var, while a bad value no longer logs one warning per captured prompt/tool payload.""" + try: + max_depth = int(configured_depth) + if max_depth < 0: + raise ValueError + return max_depth + except ValueError: + logger.warning("Invalid HERMES_LANGFUSE_MAX_DEPTH=%r; use a non-negative integer. Falling back to 4.", configured_depth) + return 4 + + def _safe_value(value: Any, *, max_chars: Optional[int] = None, depth: int = 0, parse_json_strings: bool = False, max_depth: Optional[int] = None) -> Any: max_chars = max_chars if max_chars is not None else int(_env("HERMES_LANGFUSE_MAX_CHARS", "12000") or "12000") if max_depth is None: - configured_depth = _env("HERMES_LANGFUSE_MAX_DEPTH", "4") or "4" - try: - max_depth = int(configured_depth) - if max_depth < 0: - raise ValueError - except ValueError: - logger.warning("Invalid HERMES_LANGFUSE_MAX_DEPTH=%r; use a non-negative integer. Falling back to 4.", configured_depth) - max_depth = 4 + max_depth = _resolve_max_depth(_env("HERMES_LANGFUSE_MAX_DEPTH", "4") or "4") if depth > max_depth: return "" if value is None or isinstance(value, (int, float, bool)): diff --git a/tests/plugins/test_langfuse_max_depth.py b/tests/plugins/test_langfuse_max_depth.py index d57a100bae..91691e50bb 100644 --- a/tests/plugins/test_langfuse_max_depth.py +++ b/tests/plugins/test_langfuse_max_depth.py @@ -49,3 +49,22 @@ def test_invalid_max_depth_warns_and_preserves_default_capture(monkeypatch, capl assert len(caplog.records) == 1 assert "HERMES_LANGFUSE_MAX_DEPTH" in caplog.text assert "non-negative integer" in caplog.text + + +def test_invalid_max_depth_warns_once_per_value_not_per_payload(monkeypatch, caplog): + """A bad ``HERMES_LANGFUSE_MAX_DEPTH`` must not log one warning per captured prompt/tool payload + for the life of the process; a changed (still bad) value gets its own single warning.""" + plugin = importlib.import_module("plugins.observability.langfuse") + monkeypatch.setenv("HERMES_LANGFUSE_CAPTURE", "full") + monkeypatch.setenv("HERMES_LANGFUSE_MAX_DEPTH", "abc") + payload = {"result": {"data": [{"index": 0}]}} + + for _ in range(5): + plugin._capture_content(payload) + plugin._capture_content(payload, tool_result_of=("example", {})) + assert sum("HERMES_LANGFUSE_MAX_DEPTH" in r.getMessage() for r in caplog.records) == 1 + + monkeypatch.setenv("HERMES_LANGFUSE_MAX_DEPTH", "-7") + for _ in range(3): + plugin._capture_content(payload) + assert sum("HERMES_LANGFUSE_MAX_DEPTH" in r.getMessage() for r in caplog.records) == 2