mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-27 06:13:07 +00:00
Merge 6de98912d3 into 8d6b8dab3d
This commit is contained in:
commit
e8a01e7a4c
2 changed files with 37 additions and 1 deletions
|
|
@ -9,6 +9,22 @@ from typing import Any, Final
|
|||
from litellm._logging import verbose_proxy_logger
|
||||
|
||||
|
||||
def _normalize_wildcard_segments(expr: str) -> tuple[str, ...]:
|
||||
"""Normalize ``key[*]`` into ``key`` + ``[*]`` segments.
|
||||
|
||||
Idempotent: the recursion in :meth:`JsonPathExtractor.evaluate` passes
|
||||
already-normalized paths (e.g. ``content.[*].text``) back through here.
|
||||
Re-running ``expr.replace("[*]", ".[*]")`` on those injects empty
|
||||
segments (``content..[*].text``); dropping empty segments afterwards
|
||||
keeps the normalization stable for raw (``a[*].b``), adjacent
|
||||
(``a[*][*].b``) and already-normalized inputs alike, so no
|
||||
nested-wildcard expression is silently dropped and skips the
|
||||
guardrail (#46302).
|
||||
"""
|
||||
segments: Final = tuple(segment for segment in expr.replace("[*]", ".[*]").split(".") if segment)
|
||||
return segments
|
||||
|
||||
|
||||
class JsonPathExtractor:
|
||||
"""Extracts field values from data using JSONPath-like expressions."""
|
||||
|
||||
|
|
@ -55,7 +71,7 @@ class JsonPathExtractor:
|
|||
if not expr or not data:
|
||||
return None
|
||||
|
||||
parts: Final = expr.replace("[*]", ".[*]").split(".")
|
||||
parts: Final = _normalize_wildcard_segments(expr)
|
||||
current: Any = data
|
||||
|
||||
for i, part in enumerate(parts):
|
||||
|
|
|
|||
|
|
@ -155,6 +155,26 @@ class TestJsonPathExtractorEvaluate:
|
|||
result = JsonPathExtractor.evaluate(data, "messages[*].content")
|
||||
assert result == ["You are helpful", "Hello"]
|
||||
|
||||
def test_nested_array_wildcards(self):
|
||||
"""Should extract values through two array wildcards.
|
||||
|
||||
Regression test for #46302: the recursive wildcard branch passes the
|
||||
already-normalized remaining path (e.g. "content.[*].text") back
|
||||
through evaluate, where the old replace-based normalization injected
|
||||
an empty segment ("content..[*].text") and silently dropped every
|
||||
nested-wildcard expression - so a pass-through guardrail whose only
|
||||
request_fields entry is messages[*].content[*].text never saw any
|
||||
text and skipped the check entirely.
|
||||
"""
|
||||
data = {
|
||||
"messages": [
|
||||
{"role": "user", "content": [{"type": "text", "text": "item1"}]},
|
||||
{"role": "assistant", "content": [{"type": "text", "text": "item2"}]},
|
||||
]
|
||||
}
|
||||
assert JsonPathExtractor.evaluate(data, "messages[*].content[*].text") == ["item1", "item2"]
|
||||
assert JsonPathExtractor.extract_fields(data, ["messages[*].content[*].text"]) == "item1" + chr(10) + "item2"
|
||||
|
||||
def test_missing_key_returns_none(self):
|
||||
"""Should return None for missing key."""
|
||||
data = {"query": "test"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue