Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/pass_through_endpoints/jsonpath_extractor.py: 10%
44 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1"""
2JSONPath Extractor Module
4Extracts field values from data using simple JSONPath-like expressions.
5"""
7from typing import Any, Final
9from litellm._logging import verbose_proxy_logger
12class JsonPathExtractor:
13 """Extracts field values from data using JSONPath-like expressions."""
15 @staticmethod
16 def extract_fields(
17 data: dict,
18 jsonpath_expressions: list[str],
19 ) -> str:
20 """
21 Extract field values from data using JSONPath-like expressions.
23 Supports simple expressions like:
24 - "query" -> data["query"]
25 - "documents[*].text" -> all text fields from documents array
26 - "messages[*].content" -> all content fields from messages array
28 Returns concatenated string of all extracted values.
29 """
30 extracted_values: Final[list[str]] = []
32 for expr in jsonpath_expressions:
33 try:
34 value = JsonPathExtractor.evaluate(data, expr)
35 if value:
36 if isinstance(value, list):
37 extracted_values.extend([str(v) for v in value if v])
38 else:
39 extracted_values.append(str(value))
40 except Exception as e:
41 verbose_proxy_logger.debug("Failed to extract field %s: %s", expr, str(e))
43 return "\n".join(extracted_values)
45 @staticmethod
46 def evaluate(data: dict, expr: str) -> str | list[str] | None:
47 """
48 Evaluate a simple JSONPath-like expression.
50 Supports:
51 - Simple key: "query" -> data["query"]
52 - Nested key: "foo.bar" -> data["foo"]["bar"]
53 - Array wildcard: "items[*].text" -> [item["text"] for item in data["items"]]
54 """
55 if not expr or not data:
56 return None
58 parts: Final = expr.replace("[*]", ".[*]").split(".")
59 current: Any = data
61 for i, part in enumerate(parts):
62 if current is None:
63 return None
65 if part == "[*]":
66 # Wildcard - current should be a list
67 if not isinstance(current, list):
68 return None
70 # Get remaining path
71 remaining_path = ".".join(parts[i + 1 :])
72 if not remaining_path:
73 return current
75 # Recursively evaluate remaining path for each item
76 results = []
77 for item in current:
78 if isinstance(item, dict):
79 result = JsonPathExtractor.evaluate(item, remaining_path)
80 if result:
81 if isinstance(result, list):
82 results.extend(result)
83 else:
84 results.append(result)
85 return results if results else None
87 elif isinstance(current, dict):
88 current = current.get(part)
89 else:
90 return None
92 return current