mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(guardrails): filter tool/function roles from AIM inspection messages
Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
This commit is contained in:
parent
06a97c83bd
commit
507e34650d
2 changed files with 50 additions and 2 deletions
|
|
@ -25,6 +25,15 @@ from typing import Any, Callable, Dict, FrozenSet, Iterator, List
|
|||
# guardrails on those paths are a separate scope.
|
||||
TEXT_CONTENT_CALL_TYPES: FrozenSet[str] = frozenset({"completion", "acompletion", "aresponses"})
|
||||
|
||||
# Roles that only make sense alongside tool-call metadata (``tool_call_id``,
|
||||
# ``name``, …). ``build_inspection_messages`` flattens each message down to a
|
||||
# plain ``{role, content}`` pair, dropping that metadata, so emitting these
|
||||
# roles produces payloads that remote guardrail APIs reject (e.g. AIM returns
|
||||
# 422 "Tool Call ID required on tool calls"). Their content is intermediate
|
||||
# tool plumbing, not user input or model output, so it is excluded from
|
||||
# guardrail inspection.
|
||||
INSPECTION_EXCLUDED_ROLES: FrozenSet[str] = frozenset({"tool", "function"})
|
||||
|
||||
|
||||
def is_text_content_call_type(call_type: str) -> bool:
|
||||
"""Return True if ``call_type`` carries free-form text that text
|
||||
|
|
@ -207,7 +216,9 @@ def build_inspection_messages(data: Dict[str, Any]) -> List[Dict[str, str]]:
|
|||
|
||||
Each returned message has a plain-string ``content`` — multimodal text
|
||||
parts are joined with newlines and Responses-API ``input`` is lifted
|
||||
into synthetic messages. Messages with no inspectable text are dropped.
|
||||
into synthetic messages. Messages with no inspectable text are dropped,
|
||||
as are ``tool``/``function`` role messages (see
|
||||
:data:`INSPECTION_EXCLUDED_ROLES`).
|
||||
|
||||
Hooks that POST ``{"messages": [...]}`` to an external service should
|
||||
call this instead of ``data.get("messages", [])`` so the Responses API
|
||||
|
|
@ -217,9 +228,11 @@ def build_inspection_messages(data: Dict[str, Any]) -> List[Dict[str, str]]:
|
|||
for message in _iter_inspection_messages(data):
|
||||
if not isinstance(message, dict):
|
||||
continue
|
||||
role = message.get("role", "user") or "user"
|
||||
if role in INSPECTION_EXCLUDED_ROLES:
|
||||
continue
|
||||
text = "\n".join(_iter_text_parts_in_content(message.get("content")))
|
||||
if not text:
|
||||
continue
|
||||
role = message.get("role", "user") or "user"
|
||||
flattened.append({"role": role, "content": text})
|
||||
return flattened
|
||||
|
|
|
|||
|
|
@ -239,6 +239,41 @@ def test_build_inspection_messages_empty_data():
|
|||
assert build_inspection_messages({"input": ""}) == []
|
||||
|
||||
|
||||
def test_build_inspection_messages_drops_tool_and_function_roles():
|
||||
"""AIM (and other remote guardrail APIs) validate against an
|
||||
OpenAI-compatible schema that requires ``tool_call_id`` on ``tool`` role
|
||||
messages. ``build_inspection_messages`` flattens messages to plain
|
||||
``{role, content}`` and discards that metadata, so passing tool/function
|
||||
role messages through produced a 422 "Tool Call ID required on tool calls"
|
||||
and made AIM non-functional for any conversation with tool calls. They
|
||||
must be filtered out entirely."""
|
||||
data = {
|
||||
"messages": [
|
||||
{"role": "system", "content": "be helpful"},
|
||||
{"role": "user", "content": "show me milk"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call_1",
|
||||
"type": "function",
|
||||
"function": {"name": "search_products", "arguments": "{}"},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "tool", "tool_call_id": "call_1", "content": "milk $3.99"},
|
||||
{"role": "function", "name": "search_products", "content": "eggs $2.50"},
|
||||
{"role": "assistant", "content": "Here is the milk you asked for"},
|
||||
]
|
||||
}
|
||||
assert build_inspection_messages(data) == [
|
||||
{"role": "system", "content": "be helpful"},
|
||||
{"role": "user", "content": "show me milk"},
|
||||
{"role": "assistant", "content": "Here is the milk you asked for"},
|
||||
]
|
||||
|
||||
|
||||
# ── has_non_string_content ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue