mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-13 03:59:12 +00:00
test(headroom): pin that SDK-echoed null tool_calls never reach /v1/compress (#39667)
* fix(headroom): drop null tool_calls before calling /v1/compress
headroom-ai 0.27.0 through 0.30.0 iterate msg.get("tool_calls", []) and fail
with 'NoneType' object is not iterable when an assistant row carries an explicit
tool_calls: null, which the fail-closed guardrail then surfaces as a 502. Strip
the null key from rows sent to the compression service; real tool_calls lists
are forwarded unchanged
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* test(headroom): pin that SDK-echoed null tool_calls never reach /v1/compress
Main now withholds every assistant row from /v1/compress (#39974), so the
null tool_calls strip added here never runs on a row headroom-ai 0.27-0.30
fails on. Drop the strip and pin the customer's history shape through the
public OpenAI chat translation handler instead.
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: yucheng-berri <293926326+yucheng-berri@users.noreply.github.com>
This commit is contained in:
parent
9e2aae8490
commit
888686d858
1 changed files with 39 additions and 0 deletions
|
|
@ -2008,6 +2008,45 @@ async def test_apply_guardrail_sends_textless_parts_rows_unflattened(
|
|||
assert wire_messages[1]["content"] == "D" * 5000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sdk_echoed_null_tool_calls_never_reach_compression_service(guardrail: HeadroomGuardrail):
|
||||
"""LIT-6921: OpenAI SDK assistant messages echoed back as history carry
|
||||
``"tool_calls": null``, which headroom-ai 0.27-0.30 answer with a 503. The
|
||||
chat handler sends no row carrying that null and forwards the echoed rows
|
||||
unchanged."""
|
||||
from openai.types.chat import ChatCompletionMessage
|
||||
|
||||
from litellm.llms.openai.chat.guardrail_translation.handler import OpenAIChatCompletionsHandler
|
||||
|
||||
def _sdk_assistant(**fields: object) -> dict:
|
||||
return ChatCompletionMessage.model_validate({"role": "assistant", **fields}).model_dump()
|
||||
|
||||
tool_call = {"id": "call_1", "type": "function", "function": {"name": "lookup", "arguments": "{}"}}
|
||||
history = [
|
||||
{"role": "user", "content": "E" * 5000},
|
||||
_sdk_assistant(content=None, tool_calls=[tool_call]),
|
||||
{"role": "tool", "tool_call_id": "call_1", "content": "R" * 5000},
|
||||
_sdk_assistant(content="summary"),
|
||||
{"role": "user", "content": "F" * 5000},
|
||||
_sdk_assistant(content="noted"),
|
||||
{"role": "user", "content": "and now?"},
|
||||
]
|
||||
assert history[3]["tool_calls"] is None
|
||||
sent: dict = {}
|
||||
|
||||
def _echo(**kwargs):
|
||||
sent["messages"] = kwargs["json"]["messages"]
|
||||
return _make_compress_response(json.loads(json.dumps(sent["messages"])))
|
||||
|
||||
data = {"model": "claude-sonnet-5", "messages": json.loads(json.dumps(history))}
|
||||
with patch.object(guardrail.async_handler, "post", new_callable=AsyncMock, side_effect=_echo):
|
||||
result = await OpenAIChatCompletionsHandler().process_input_messages(data=data, guardrail_to_apply=guardrail)
|
||||
|
||||
assert [row["role"] for row in sent["messages"]] == ["user", "tool", "user"]
|
||||
assert all(row.get("tool_calls", []) is not None for row in sent["messages"])
|
||||
assert [result["messages"][index] for index in (1, 3, 5)] == [history[index] for index in (1, 3, 5)]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fail_open_returns_original_parts_shapes():
|
||||
guardrail = _make_guardrail(unreachable_fallback="fail_open")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue