mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(guardrails): pin how the privaite hook handles malformed input
The typed rewrite changed three paths that used to crash or skip. A Responses item whose type is not a string is now scanned like any unknown item instead of raising on endswith, a request with metadata set to null now gets its restore map instead of a TypeError, and a dict-shaped response is now restored like an object one. Each one has a test now It also put a privaite install missing PIIEngine on a TypeError, where the old import raised the install hint. That path is back on the hint and tested
This commit is contained in:
parent
3486434407
commit
333d99e25b
2 changed files with 43 additions and 3 deletions
|
|
@ -65,9 +65,11 @@ def _module(name: str) -> ModuleType:
|
|||
|
||||
def _factory(module: ModuleType, name: str) -> Callable[..., object]:
|
||||
value: Final = getattr(module, name, None)
|
||||
if not callable(value):
|
||||
raise TypeError(f"{module.__name__}.{name} is missing. {INSTALL_HINT}")
|
||||
return value
|
||||
if callable(value):
|
||||
return value
|
||||
if hasattr(module, name):
|
||||
raise TypeError(f"{module.__name__}.{name} is not callable. {INSTALL_HINT}")
|
||||
raise RuntimeError(f"{module.__name__}.{name} is missing. {INSTALL_HINT}")
|
||||
|
||||
|
||||
def _narrow(value: object, kind: type[_Narrowed]) -> _Narrowed:
|
||||
|
|
|
|||
|
|
@ -1386,3 +1386,41 @@ async def test_streaming_tool_arguments_stay_valid_json_with_special_chars(monke
|
|||
assert json.loads(tool_args) == {"name": _SPECIALS_ORIGINAL}
|
||||
assert json.loads(fc_args) == {"name": _SPECIALS_ORIGINAL}
|
||||
assert content == f"Hi {_SPECIALS_ORIGINAL}"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pre_call_scans_a_responses_item_whose_type_is_not_a_string():
|
||||
gr = _make_guardrail()
|
||||
data = {"input": [{"type": 5, "output": "Email Marie Dupont"}]}
|
||||
|
||||
out = await gr.async_pre_call_hook(None, None, data, "aresponses")
|
||||
|
||||
assert out["input"][0]["output"] == "Email <PERSON_1>"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pre_call_stashes_the_map_when_metadata_is_null():
|
||||
gr = _make_guardrail()
|
||||
data = {"messages": [{"role": "user", "content": "Email Marie Dupont"}], "metadata": None}
|
||||
|
||||
out = await gr.async_pre_call_hook(None, None, data, "acompletion")
|
||||
|
||||
assert out["metadata"]["privaite_map"] == {"<PERSON_1>": "Marie Dupont"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_post_call_restores_a_dict_shaped_response():
|
||||
gr = _make_guardrail()
|
||||
response = {"choices": [{"message": {"content": "Sending to <PERSON_1>"}}]}
|
||||
|
||||
out = await gr.async_post_call_success_hook({"metadata": {"privaite_map": _FAKES}}, None, response)
|
||||
|
||||
assert out["choices"][0]["message"]["content"] == "Sending to Marie Dupont"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_engine_for_a_privaite_without_pii_engine_recommends_the_install(monkeypatch):
|
||||
monkeypatch.delattr(sys.modules["privaite.pii.engine"], "PIIEngine")
|
||||
|
||||
with pytest.raises(RuntimeError, match=r"pip install 'privaite>=0\.7\.2'"):
|
||||
await _make_guardrail()._engine_for(["en"])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue