mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-02 07:13:02 +00:00
perf(logging): skip escaping plain request text and walk each request value once
A redacted failure built JSON and repr copies of every request text for each error field, though printable ASCII without a double quote or backslash escapes to itself, and walked the original messages a second time. The scrubbed output is unchanged.
This commit is contained in:
parent
3de0381ca1
commit
c7c02bbdc5
2 changed files with 25 additions and 4 deletions
|
|
@ -448,6 +448,7 @@ def _redacted_responses_api_response(
|
|||
_REQUEST_TEXT_KEYS: Final = ("messages", "input", "prompt", "system", "instructions")
|
||||
_STRUCTURAL_KEYS: Final = frozenset({"role", "type", "id", "tool_call_id", "name"})
|
||||
_MIN_UNQUOTED_TEXT_LENGTH: Final = 4
|
||||
_ESCAPED_CHARACTERS: Final = ('"', "\\")
|
||||
# pydantic shows a long input value as its repr's first 25 and last 24 characters around "...", quotes and
|
||||
# container keys included, so each side of the cut shows at most 24 characters of a quoted text
|
||||
_CUT_SIDE_LENGTH: Final = 24
|
||||
|
|
@ -473,9 +474,12 @@ def _next_level(level: tuple[object, ...], _: None) -> tuple[object, ...]:
|
|||
def request_texts(*values: object) -> frozenset[str]:
|
||||
"""
|
||||
The non-empty strings a request carries under ``values``, skipping structural fields such as ``role``
|
||||
and ``type``.
|
||||
and ``type``. A value passed more than once is walked once.
|
||||
"""
|
||||
levels: Final = itertools.takewhile(bool, itertools.accumulate(itertools.repeat(None), _next_level, initial=values))
|
||||
distinct_values: Final = tuple({id(value): value for value in values}.values())
|
||||
levels: Final = itertools.takewhile(
|
||||
bool, itertools.accumulate(itertools.repeat(None), _next_level, initial=distinct_values)
|
||||
)
|
||||
return frozenset(value for value in itertools.chain.from_iterable(levels) if isinstance(value, str) and value)
|
||||
|
||||
|
||||
|
|
@ -490,7 +494,10 @@ def failure_request_texts(model_call_details: Mapping[str, object], *originals:
|
|||
|
||||
|
||||
def _echo_forms(text: str) -> frozenset[str]:
|
||||
"""How an error can quote ``text``: verbatim, or JSON or repr escaped."""
|
||||
"""How an error can quote ``text``: verbatim, or JSON or repr escaped. Printable ASCII without a double quote
|
||||
or a backslash escapes to itself."""
|
||||
if text.isascii() and text.isprintable() and not any(character in text for character in _ESCAPED_CHARACTERS):
|
||||
return frozenset((text,))
|
||||
return frozenset({text, json.dumps(text)[1:-1], json.dumps(text, ensure_ascii=False)[1:-1], repr(text)[1:-1]})
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import json
|
|||
import os
|
||||
import threading
|
||||
import tracemalloc
|
||||
from collections.abc import AsyncIterator, Mapping
|
||||
from collections.abc import AsyncIterator, Callable, Mapping
|
||||
from datetime import datetime
|
||||
from types import MappingProxyType, SimpleNamespace
|
||||
from typing import Final, Optional, Union
|
||||
|
|
@ -2101,6 +2101,20 @@ def test_scrub_request_text_replaces_each_way_an_error_quotes_the_prompt(quoted:
|
|||
assert scrubbed.startswith("Invalid content: <") and scrubbed.endswith("> rejected")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prompt",
|
||||
['say "hi" to Bob', "C:\\Users\\bob", "café for Bob", "tab\tfor Bob"],
|
||||
ids=["double_quote", "backslash", "non_ascii", "control_character"],
|
||||
)
|
||||
@pytest.mark.parametrize("escaped", [json.dumps, repr], ids=["json", "repr"])
|
||||
def test_scrub_request_text_replaces_an_escaped_echo_of_a_prompt_that_escaping_changes(
|
||||
prompt: str, escaped: Callable[[str], str]
|
||||
) -> None:
|
||||
error: Final = f"Invalid content: <{escaped(prompt)[1:-1]}> rejected"
|
||||
|
||||
assert scrub_request_text(error, request_texts(prompt)) == "Invalid content: <redacted-by-litellm> rejected"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[PROMPT, [PROMPT], {"content": PROMPT}, [{"content": PROMPT}], [{"content": LONG_PROMPT}], [LONG_PROMPT]],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue