mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-26 11:14:04 +00:00
* fix(ui_sso.py): fix faulty admin only check Fixes https://github.com/BerriAI/litellm/issues/6286 * refactor(sso_helper_utils.py): refactor /sso/callback to use helper utils, covered by unit testing Prevent future regressions * feat(prompt_factory): support 'ensure_alternating_roles' param Closes https://github.com/BerriAI/litellm/issues/6257 * fix(proxy/utils.py): add dailytagspend to expected views * feat(auth_utils.py): support setting regex for clientside auth credentials Fixes https://github.com/BerriAI/litellm/issues/6203 * build(cookbook): add tutorial for mlflow + langchain + litellm proxy tracing * feat(argilla.py): add argilla logging integration Closes https://github.com/BerriAI/litellm/issues/6201 * fix: fix linting errors * fix: fix ruff error * test: fix test * fix: update vertex ai assumption - parts not always guaranteed (#6296) * docs(configs.md): add argila env var to docs
227 lines
7.5 KiB
Python
227 lines
7.5 KiB
Python
"""
|
|
Common utility functions used for translating messages across providers
|
|
"""
|
|
|
|
import json
|
|
from copy import deepcopy
|
|
from typing import Dict, List, Literal, Optional
|
|
|
|
import litellm
|
|
from litellm.types.llms.openai import (
|
|
AllMessageValues,
|
|
ChatCompletionAssistantMessage,
|
|
ChatCompletionUserMessage,
|
|
)
|
|
from litellm.types.utils import Choices, ModelResponse, StreamingChoices
|
|
|
|
DEFAULT_USER_CONTINUE_MESSAGE = ChatCompletionUserMessage(
|
|
content="Please continue.", role="user"
|
|
)
|
|
|
|
DEFAULT_ASSISTANT_CONTINUE_MESSAGE = ChatCompletionAssistantMessage(
|
|
content="Please continue.", role="assistant"
|
|
)
|
|
|
|
|
|
def convert_content_list_to_str(message: AllMessageValues) -> str:
|
|
"""
|
|
- handles scenario where content is list and not string
|
|
- content list is just text, and no images
|
|
- if image passed in, then just return as is (user-intended)
|
|
|
|
Motivation: mistral api + azure ai don't support content as a list
|
|
"""
|
|
texts = ""
|
|
message_content = message.get("content")
|
|
if message_content:
|
|
if message_content is not None and isinstance(message_content, list):
|
|
for c in message_content:
|
|
text_content = c.get("text")
|
|
if text_content:
|
|
texts += text_content
|
|
elif message_content is not None and isinstance(message_content, str):
|
|
texts = message_content
|
|
|
|
return texts
|
|
|
|
|
|
def convert_openai_message_to_only_content_messages(
|
|
messages: List[AllMessageValues],
|
|
) -> List[Dict[str, str]]:
|
|
"""
|
|
Converts OpenAI messages to only content messages
|
|
|
|
Used for calling guardrails integrations which expect string content
|
|
"""
|
|
converted_messages = []
|
|
user_roles = ["user", "tool", "function"]
|
|
for message in messages:
|
|
if message.get("role") in user_roles:
|
|
converted_messages.append(
|
|
{"role": "user", "content": convert_content_list_to_str(message)}
|
|
)
|
|
elif message.get("role") == "assistant":
|
|
converted_messages.append(
|
|
{"role": "assistant", "content": convert_content_list_to_str(message)}
|
|
)
|
|
return converted_messages
|
|
|
|
|
|
def get_content_from_model_response(response: ModelResponse) -> str:
|
|
"""
|
|
Gets content from model response
|
|
"""
|
|
content = ""
|
|
for choice in response.choices:
|
|
if isinstance(choice, Choices):
|
|
content += choice.message.content if choice.message.content else ""
|
|
if choice.message.function_call:
|
|
content += choice.message.function_call.model_dump_json()
|
|
if choice.message.tool_calls:
|
|
for tc in choice.message.tool_calls:
|
|
content += tc.model_dump_json()
|
|
elif isinstance(choice, StreamingChoices):
|
|
content += getattr(choice, "delta", {}).get("content", "") or ""
|
|
return content
|
|
|
|
|
|
def detect_first_expected_role(
|
|
messages: List[AllMessageValues],
|
|
) -> Optional[Literal["user", "assistant"]]:
|
|
"""
|
|
Detect the first expected role based on the message sequence.
|
|
|
|
Rules:
|
|
1. If messages list is empty, assume 'user' starts
|
|
2. If first message is from assistant, expect 'user' next
|
|
3. If first message is from user, expect 'assistant' next
|
|
4. If first message is system, look at the next non-system message
|
|
|
|
Returns:
|
|
str: Either 'user' or 'assistant'
|
|
None: If no 'user' or 'assistant' messages provided
|
|
"""
|
|
if not messages:
|
|
return "user"
|
|
|
|
for message in messages:
|
|
if message["role"] == "system":
|
|
continue
|
|
return "user" if message["role"] == "assistant" else "assistant"
|
|
|
|
return None
|
|
|
|
|
|
def _insert_user_continue_message(
|
|
messages: List[AllMessageValues],
|
|
user_continue_message: Optional[ChatCompletionUserMessage],
|
|
ensure_alternating_roles: bool,
|
|
) -> List[AllMessageValues]:
|
|
"""
|
|
Inserts a user continue message into the messages list.
|
|
Handles three cases:
|
|
1. Initial assistant message
|
|
2. Final assistant message
|
|
3. Consecutive assistant messages
|
|
|
|
Only inserts messages between consecutive assistant messages,
|
|
ignoring all other role types.
|
|
"""
|
|
if not messages:
|
|
return messages
|
|
|
|
result_messages = messages.copy() # Don't modify the input list
|
|
continue_message = user_continue_message or DEFAULT_USER_CONTINUE_MESSAGE
|
|
|
|
# Handle first message if it's an assistant message
|
|
if result_messages[0]["role"] == "assistant":
|
|
result_messages.insert(0, continue_message)
|
|
|
|
# Handle consecutive assistant messages and final message
|
|
i = 1 # Start from second message since we handled first message
|
|
while i < len(result_messages):
|
|
curr_message = result_messages[i]
|
|
prev_message = result_messages[i - 1]
|
|
|
|
# Only check for consecutive assistant messages
|
|
# Ignore all other role types
|
|
if curr_message["role"] == "assistant" and prev_message["role"] == "assistant":
|
|
result_messages.insert(i, continue_message)
|
|
i += 2 # Skip over the message we just inserted
|
|
else:
|
|
i += 1
|
|
|
|
# Handle final message
|
|
if result_messages[-1]["role"] == "assistant" and ensure_alternating_roles:
|
|
result_messages.append(continue_message)
|
|
|
|
return result_messages
|
|
|
|
|
|
def _insert_assistant_continue_message(
|
|
messages: List[AllMessageValues],
|
|
assistant_continue_message: Optional[ChatCompletionAssistantMessage] = None,
|
|
ensure_alternating_roles: bool = True,
|
|
) -> List[AllMessageValues]:
|
|
"""
|
|
Add assistant continuation messages between consecutive user messages.
|
|
|
|
Args:
|
|
messages: List of message dictionaries
|
|
assistant_continue_message: Optional custom assistant message
|
|
ensure_alternating_roles: Whether to enforce alternating roles
|
|
|
|
Returns:
|
|
Modified list of messages with inserted assistant messages
|
|
"""
|
|
if not ensure_alternating_roles or len(messages) <= 1:
|
|
return messages
|
|
|
|
# Create a new list to store modified messages
|
|
modified_messages: List[AllMessageValues] = []
|
|
|
|
for i, message in enumerate(messages):
|
|
modified_messages.append(message)
|
|
|
|
# Check if we need to insert an assistant message
|
|
if (
|
|
i < len(messages) - 1 # Not the last message
|
|
and message.get("role") == "user" # Current is user
|
|
and messages[i + 1].get("role") == "user"
|
|
): # Next is user
|
|
|
|
# Insert assistant message
|
|
continue_message = (
|
|
assistant_continue_message or DEFAULT_ASSISTANT_CONTINUE_MESSAGE
|
|
)
|
|
modified_messages.append(continue_message)
|
|
|
|
return modified_messages
|
|
|
|
|
|
def get_completion_messages(
|
|
messages: List[AllMessageValues],
|
|
assistant_continue_message: Optional[ChatCompletionAssistantMessage],
|
|
user_continue_message: Optional[ChatCompletionUserMessage],
|
|
ensure_alternating_roles: bool,
|
|
) -> List[AllMessageValues]:
|
|
"""
|
|
Ensures messages alternate between user and assistant roles by adding placeholders
|
|
only when there are consecutive messages of the same role.
|
|
|
|
1. ensure 'user' message before 1st 'assistant' message
|
|
2. ensure 'user' message after last 'assistant' message
|
|
"""
|
|
if not ensure_alternating_roles:
|
|
return messages.copy()
|
|
|
|
## INSERT USER CONTINUE MESSAGE
|
|
messages = _insert_user_continue_message(
|
|
messages, user_continue_message, ensure_alternating_roles
|
|
)
|
|
|
|
## INSERT ASSISTANT CONTINUE MESSAGE
|
|
messages = _insert_assistant_continue_message(
|
|
messages, assistant_continue_message, ensure_alternating_roles
|
|
)
|
|
return messages
|