mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(fireworks_ai): keep non-text system and developer parts on the folded leading system message
This commit is contained in:
parent
deaadc21d3
commit
4807630c2a
2 changed files with 62 additions and 8 deletions
|
|
@ -4,7 +4,12 @@ from typing import TYPE_CHECKING, Final
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from openai.types.responses import EasyInputMessageParam, ResponseInputItemParam
|
from openai.types.responses import (
|
||||||
|
EasyInputMessageParam,
|
||||||
|
ResponseInputContentParam,
|
||||||
|
ResponseInputItemParam,
|
||||||
|
ResponseInputTextParam,
|
||||||
|
)
|
||||||
from pydantic import TypeAdapter
|
from pydantic import TypeAdapter
|
||||||
|
|
||||||
from litellm.llms.fireworks_ai.common_utils import (
|
from litellm.llms.fireworks_ai.common_utils import (
|
||||||
|
|
@ -35,27 +40,42 @@ def _session_params(litellm_params: GenericLiteLLMParams) -> Mapping[str, object
|
||||||
_instructions_adapter: Final = TypeAdapter[str | None](str | None)
|
_instructions_adapter: Final = TypeAdapter[str | None](str | None)
|
||||||
|
|
||||||
|
|
||||||
def _instruction_text(item: ResponseInputItemParam) -> str | None:
|
def _instruction_parts(item: ResponseInputItemParam) -> tuple[ResponseInputContentParam, ...] | None:
|
||||||
if "role" not in item or (item["role"] != "system" and item["role"] != "developer"):
|
if "role" not in item or (item["role"] != "system" and item["role"] != "developer"):
|
||||||
return None
|
return None
|
||||||
content: Final = item["content"]
|
content: Final = item["content"]
|
||||||
if isinstance(content, str):
|
if isinstance(content, str):
|
||||||
return content
|
return (ResponseInputTextParam(type="input_text", text=content),)
|
||||||
return "\n\n".join(part["text"] for part in content if part["type"] == "input_text")
|
return tuple(content)
|
||||||
|
|
||||||
|
|
||||||
|
def _leading_system_content(
|
||||||
|
instructions: str | None, parts: tuple[ResponseInputContentParam, ...]
|
||||||
|
) -> str | list[ResponseInputContentParam]:
|
||||||
|
text: Final = "\n\n".join(
|
||||||
|
chunk for chunk in (instructions, *(part["text"] for part in parts if part["type"] == "input_text")) if chunk
|
||||||
|
)
|
||||||
|
non_text: Final = tuple(part for part in parts if part["type"] != "input_text")
|
||||||
|
if not non_text:
|
||||||
|
return text
|
||||||
|
return [ResponseInputTextParam(type="input_text", text=text), *non_text] if text else list(non_text)
|
||||||
|
|
||||||
|
|
||||||
def _with_single_leading_system_item(
|
def _with_single_leading_system_item(
|
||||||
input: str | ResponseInputParam, instructions: str | None
|
input: str | ResponseInputParam, instructions: str | None
|
||||||
) -> str | ResponseInputParam:
|
) -> str | ResponseInputParam:
|
||||||
items: Final = () if isinstance(input, str) else tuple(input)
|
items: Final = () if isinstance(input, str) else tuple(input)
|
||||||
instruction_texts: Final = tuple(text for text in (instructions, *map(_instruction_text, items)) if text)
|
instruction_parts: Final = tuple(
|
||||||
if not instruction_texts:
|
part for item_parts in map(_instruction_parts, items) if item_parts is not None for part in item_parts
|
||||||
|
)
|
||||||
|
content: Final = _leading_system_content(instructions, instruction_parts)
|
||||||
|
if not content:
|
||||||
return input
|
return input
|
||||||
leading: Final = EasyInputMessageParam(role="system", content="\n\n".join(instruction_texts), type="message")
|
leading: Final = EasyInputMessageParam(role="system", content=content, type="message")
|
||||||
rest: Final = (
|
rest: Final = (
|
||||||
(EasyInputMessageParam(role="user", content=input),)
|
(EasyInputMessageParam(role="user", content=input),)
|
||||||
if isinstance(input, str)
|
if isinstance(input, str)
|
||||||
else tuple(item for item in items if _instruction_text(item) is None)
|
else tuple(item for item in items if _instruction_parts(item) is None)
|
||||||
)
|
)
|
||||||
return [leading, *rest]
|
return [leading, *rest]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -231,6 +231,40 @@ def test_responses_call_folds_instructions_and_developer_item_into_one_leading_s
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_responses_call_keeps_non_text_developer_parts_on_the_leading_system_message() -> None:
|
||||||
|
client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/qwen3p8-2p4t-a95b"))
|
||||||
|
with patch(HTTPX_CLIENT_FACTORY, return_value=client):
|
||||||
|
litellm.responses(
|
||||||
|
model="fireworks_ai/accounts/fireworks/models/qwen3p8-2p4t-a95b",
|
||||||
|
instructions="Answer with one word.",
|
||||||
|
input=[ # mutable-ok: the Responses API takes input as a JSON list
|
||||||
|
{
|
||||||
|
"role": "developer",
|
||||||
|
"content": [
|
||||||
|
{"type": "input_text", "text": "Match the style of this reference image."},
|
||||||
|
{"type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo=", "detail": "auto"},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{"role": "user", "content": "What is the capital of France?"},
|
||||||
|
],
|
||||||
|
store=False,
|
||||||
|
api_key="fw-test-key",
|
||||||
|
)
|
||||||
|
_, _, body = _sent_request(client)
|
||||||
|
assert "instructions" not in body
|
||||||
|
assert tuple(body["input"]) == (
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": [
|
||||||
|
{"type": "input_text", "text": "Answer with one word.\n\nMatch the style of this reference image."},
|
||||||
|
{"type": "input_image", "image_url": "data:image/png;base64,iVBORw0KGgo=", "detail": "auto"},
|
||||||
|
],
|
||||||
|
"type": "message",
|
||||||
|
},
|
||||||
|
{"role": "user", "content": "What is the capital of France?"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_responses_call_turns_string_input_with_instructions_into_system_then_user_messages() -> None:
|
def test_responses_call_turns_string_input_with_instructions_into_system_then_user_messages() -> None:
|
||||||
client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3"))
|
client: Final = _mock_http_client(_fireworks_response("accounts/fireworks/models/kimi-k3"))
|
||||||
with patch(HTTPX_CLIENT_FACTORY, return_value=client):
|
with patch(HTTPX_CLIENT_FACTORY, return_value=client):
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue