forked from phoenix/litellm-mirror
* refactor: move gemini translation logic inside the transformation.py file easier to isolate the gemini translation logic * fix(gemini-transformation): support multiple tool calls in message body Merges https://github.com/BerriAI/litellm/pull/6487/files * test(test_vertex.py): add remaining tests from https://github.com/BerriAI/litellm/pull/6487 * fix(gemini-transformation): return tool calls for multiple tool calls * fix: support passing logprobs param for vertex + gemini * feat(vertex_ai): add logprobs support for gemini calls * fix(anthropic/chat/transformation.py): fix disable parallel tool use flag * fix: fix linting error * fix(_logging.py): log stacktrace information in json logs Closes https://github.com/BerriAI/litellm/issues/6497 * fix(utils.py): fix mem leak for async stream + completion Uses a global executor pool instead of creating a new thread on each request Fixes https://github.com/BerriAI/litellm/issues/6404 * fix(factory.py): handle tool call + content in assistant message for bedrock * fix: fix import * fix(factory.py): maintain support for content as a str in assistant response * fix: fix import * test: cleanup test * fix(vertex_and_google_ai_studio/): return none for content if no str value * test: retry flaky tests * (UI) Fix viewing members, keys in a team + added testing (#6514) * fix listing teams on ui * LiteLLM Minor Fixes & Improvements (10/28/2024) (#6475) * fix(anthropic/chat/transformation.py): support anthropic disable_parallel_tool_use param Fixes https://github.com/BerriAI/litellm/issues/6456 * feat(anthropic/chat/transformation.py): support anthropic computer tool use Closes https://github.com/BerriAI/litellm/issues/6427 * fix(vertex_ai/common_utils.py): parse out '$schema' when calling vertex ai Fixes issue when trying to call vertex from vercel sdk * fix(main.py): add 'extra_headers' support for azure on all translation endpoints Fixes https://github.com/BerriAI/litellm/issues/6465 * fix: fix linting errors * fix(transformation.py): handle no beta headers for anthropic * test: cleanup test * fix: fix linting error * fix: fix linting errors * fix: fix linting errors * fix(transformation.py): handle dummy tool call * fix(main.py): fix linting error * fix(azure.py): pass required param * LiteLLM Minor Fixes & Improvements (10/24/2024) (#6441) * fix(azure.py): handle /openai/deployment in azure api base * fix(factory.py): fix faulty anthropic tool result translation check Fixes https://github.com/BerriAI/litellm/issues/6422 * fix(gpt_transformation.py): add support for parallel_tool_calls to azure Fixes https://github.com/BerriAI/litellm/issues/6440 * fix(factory.py): support anthropic prompt caching for tool results * fix(vertex_ai/common_utils): don't pop non-null required field Fixes https://github.com/BerriAI/litellm/issues/6426 * feat(vertex_ai.py): support code_execution tool call for vertex ai + gemini Closes https://github.com/BerriAI/litellm/issues/6434 * build(model_prices_and_context_window.json): Add 'supports_assistant_prefill' for bedrock claude-3-5-sonnet v2 models Closes https://github.com/BerriAI/litellm/issues/6437 * fix(types/utils.py): fix linting * test: update test to include required fields * test: fix test * test: handle flaky test * test: remove e2e test - hitting gemini rate limits * Litellm dev 10 26 2024 (#6472) * docs(exception_mapping.md): add missing exception types Fixes https://github.com/Aider-AI/aider/issues/2120#issuecomment-2438971183 * fix(main.py): register custom model pricing with specific key Ensure custom model pricing is registered to the specific model+provider key combination * test: make testing more robust for custom pricing * fix(redis_cache.py): instrument otel logging for sync redis calls ensures complete coverage for all redis cache calls * (Testing) Add unit testing for DualCache - ensure in memory cache is used when expected (#6471) * test test_dual_cache_get_set * unit testing for dual cache * fix async_set_cache_sadd * test_dual_cache_local_only * redis otel tracing + async support for latency routing (#6452) * docs(exception_mapping.md): add missing exception types Fixes https://github.com/Aider-AI/aider/issues/2120#issuecomment-2438971183 * fix(main.py): register custom model pricing with specific key Ensure custom model pricing is registered to the specific model+provider key combination * test: make testing more robust for custom pricing * fix(redis_cache.py): instrument otel logging for sync redis calls ensures complete coverage for all redis cache calls * refactor: pass parent_otel_span for redis caching calls in router allows for more observability into what calls are causing latency issues * test: update tests with new params * refactor: ensure e2e otel tracing for router * refactor(router.py): add more otel tracing acrosss router catch all latency issues for router requests * fix: fix linting error * fix(router.py): fix linting error * fix: fix test * test: fix tests * fix(dual_cache.py): pass ttl to redis cache * fix: fix param * fix(dual_cache.py): set default value for parent_otel_span * fix(transformation.py): support 'response_format' for anthropic calls * fix(transformation.py): check for cache_control inside 'function' block * fix: fix linting error * fix: fix linting errors --------- Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> --------- Co-authored-by: Krish Dholakia <krrishdholakia@gmail.com> * ui new build * Add retry strat (#6520) Signed-off-by: dbczumar <corey.zumar@databricks.com> * (fix) slack alerting - don't spam the failed cost tracking alert for the same model (#6543) * fix use failing_model as cache key for failed_tracking_alert * fix use standard logging payload for getting response cost * fix kwargs.get("response_cost") * fix getting response cost * (feat) add XAI ChatCompletion Support (#6373) * init commit for XAI * add full logic for xai chat completion * test_completion_xai * docs xAI * add xai/grok-beta * test_xai_chat_config_get_openai_compatible_provider_info * test_xai_chat_config_map_openai_params * add xai streaming test --------- Signed-off-by: dbczumar <corey.zumar@databricks.com> Co-authored-by: Ishaan Jaff <ishaanjaffer0324@gmail.com> Co-authored-by: Corey Zumar <39497902+dbczumar@users.noreply.github.com>
689 lines
25 KiB
Python
689 lines
25 KiB
Python
#### What this tests ####
|
|
# This tests if prompts are being correctly formatted
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.abspath("../.."))
|
|
|
|
from typing import Union
|
|
|
|
# from litellm.llms.prompt_templates.factory import prompt_factory
|
|
import litellm
|
|
from litellm import completion
|
|
from litellm.llms.prompt_templates.factory import (
|
|
_bedrock_tools_pt,
|
|
anthropic_messages_pt,
|
|
anthropic_pt,
|
|
claude_2_1_pt,
|
|
convert_to_anthropic_image_obj,
|
|
convert_url_to_base64,
|
|
llama_2_chat_pt,
|
|
prompt_factory,
|
|
)
|
|
from litellm.llms.prompt_templates.common_utils import (
|
|
get_completion_messages,
|
|
)
|
|
from litellm.llms.vertex_ai_and_google_ai_studio.gemini.transformation import (
|
|
_gemini_convert_messages_with_history,
|
|
)
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
|
|
def test_llama_3_prompt():
|
|
messages = [
|
|
{"role": "system", "content": "You are a good bot"},
|
|
{"role": "user", "content": "Hey, how's it going?"},
|
|
]
|
|
received_prompt = prompt_factory(
|
|
model="meta-llama/Meta-Llama-3-8B-Instruct", messages=messages
|
|
)
|
|
print(f"received_prompt: {received_prompt}")
|
|
|
|
expected_prompt = """<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\nYou are a good bot<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nHey, how's it going?<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n"""
|
|
assert received_prompt == expected_prompt
|
|
|
|
|
|
def test_codellama_prompt_format():
|
|
messages = [
|
|
{"role": "system", "content": "You are a good bot"},
|
|
{"role": "user", "content": "Hey, how's it going?"},
|
|
]
|
|
expected_prompt = "<s>[INST] <<SYS>>\nYou are a good bot\n<</SYS>>\n [/INST]\n[INST] Hey, how's it going? [/INST]\n"
|
|
assert llama_2_chat_pt(messages) == expected_prompt
|
|
|
|
|
|
def test_claude_2_1_pt_formatting():
|
|
# Test case: User only, should add Assistant
|
|
messages = [{"role": "user", "content": "Hello"}]
|
|
expected_prompt = "\n\nHuman: Hello\n\nAssistant: "
|
|
assert claude_2_1_pt(messages) == expected_prompt
|
|
|
|
# Test case: System, User, and Assistant "pre-fill" sequence,
|
|
# Should return pre-fill
|
|
messages = [
|
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
{"role": "user", "content": 'Please return "Hello World" as a JSON object.'},
|
|
{"role": "assistant", "content": "{"},
|
|
]
|
|
expected_prompt = 'You are a helpful assistant.\n\nHuman: Please return "Hello World" as a JSON object.\n\nAssistant: {'
|
|
assert claude_2_1_pt(messages) == expected_prompt
|
|
|
|
# Test case: System, Assistant sequence, should insert blank Human message
|
|
# before Assistant pre-fill
|
|
messages = [
|
|
{"role": "system", "content": "You are a storyteller."},
|
|
{"role": "assistant", "content": "Once upon a time, there "},
|
|
]
|
|
expected_prompt = (
|
|
"You are a storyteller.\n\nHuman: \n\nAssistant: Once upon a time, there "
|
|
)
|
|
assert claude_2_1_pt(messages) == expected_prompt
|
|
|
|
# Test case: System, User sequence
|
|
messages = [
|
|
{"role": "system", "content": "System reboot"},
|
|
{"role": "user", "content": "Is everything okay?"},
|
|
]
|
|
expected_prompt = "System reboot\n\nHuman: Is everything okay?\n\nAssistant: "
|
|
assert claude_2_1_pt(messages) == expected_prompt
|
|
|
|
|
|
def test_anthropic_pt_formatting():
|
|
# Test case: User only, should add Assistant
|
|
messages = [{"role": "user", "content": "Hello"}]
|
|
expected_prompt = "\n\nHuman: Hello\n\nAssistant: "
|
|
assert anthropic_pt(messages) == expected_prompt
|
|
|
|
# Test case: System, User, and Assistant "pre-fill" sequence,
|
|
# Should return pre-fill
|
|
messages = [
|
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
{"role": "user", "content": 'Please return "Hello World" as a JSON object.'},
|
|
{"role": "assistant", "content": "{"},
|
|
]
|
|
expected_prompt = '\n\nHuman: <admin>You are a helpful assistant.</admin>\n\nHuman: Please return "Hello World" as a JSON object.\n\nAssistant: {'
|
|
assert anthropic_pt(messages) == expected_prompt
|
|
|
|
# Test case: System, Assistant sequence, should NOT insert blank Human message
|
|
# before Assistant pre-fill, because "System" messages are Human
|
|
# messages wrapped with <admin></admin>
|
|
messages = [
|
|
{"role": "system", "content": "You are a storyteller."},
|
|
{"role": "assistant", "content": "Once upon a time, there "},
|
|
]
|
|
expected_prompt = "\n\nHuman: <admin>You are a storyteller.</admin>\n\nAssistant: Once upon a time, there "
|
|
assert anthropic_pt(messages) == expected_prompt
|
|
|
|
# Test case: System, User sequence
|
|
messages = [
|
|
{"role": "system", "content": "System reboot"},
|
|
{"role": "user", "content": "Is everything okay?"},
|
|
]
|
|
expected_prompt = "\n\nHuman: <admin>System reboot</admin>\n\nHuman: Is everything okay?\n\nAssistant: "
|
|
assert anthropic_pt(messages) == expected_prompt
|
|
|
|
|
|
def test_anthropic_messages_pt():
|
|
# Test case: No messages (filtered system messages only)
|
|
litellm.modify_params = True
|
|
messages = []
|
|
expected_messages = [{"role": "user", "content": [{"type": "text", "text": "."}]}]
|
|
assert (
|
|
anthropic_messages_pt(
|
|
messages, model="claude-3-sonnet-20240229", llm_provider="anthropic"
|
|
)
|
|
== expected_messages
|
|
)
|
|
|
|
# Test case: No messages (filtered system messages only) when modify_params is False should raise error
|
|
litellm.modify_params = False
|
|
messages = []
|
|
with pytest.raises(Exception) as err:
|
|
anthropic_messages_pt(
|
|
messages, model="claude-3-sonnet-20240229", llm_provider="anthropic"
|
|
)
|
|
assert "Invalid first message" in str(err.value)
|
|
|
|
|
|
def test_anthropic_messages_nested_pt():
|
|
from litellm.types.llms.anthropic import (
|
|
AnthopicMessagesAssistantMessageParam,
|
|
AnthropicMessagesUserMessageParam,
|
|
)
|
|
|
|
messages = [
|
|
{"content": [{"text": "here is a task", "type": "text"}], "role": "user"},
|
|
{
|
|
"content": [{"text": "sure happy to help", "type": "text"}],
|
|
"role": "assistant",
|
|
},
|
|
{
|
|
"content": [
|
|
{
|
|
"text": "Here is a screenshot of the current desktop with the "
|
|
"mouse coordinates (500, 350). Please select an action "
|
|
"from the provided schema.",
|
|
"type": "text",
|
|
}
|
|
],
|
|
"role": "user",
|
|
},
|
|
]
|
|
|
|
new_messages = anthropic_messages_pt(
|
|
messages, model="claude-3-sonnet-20240229", llm_provider="anthropic"
|
|
)
|
|
|
|
assert isinstance(new_messages[1]["content"][0]["text"], str)
|
|
|
|
|
|
# codellama_prompt_format()
|
|
def test_bedrock_tool_calling_pt():
|
|
tools = [
|
|
{
|
|
"type": "function",
|
|
"function": {
|
|
"name": "get_current_weather",
|
|
"description": "Get the current weather in a given location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city and state, e.g. San Francisco, CA",
|
|
},
|
|
"unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
|
|
},
|
|
"required": ["location"],
|
|
},
|
|
},
|
|
}
|
|
]
|
|
converted_tools = _bedrock_tools_pt(tools=tools)
|
|
|
|
print(converted_tools)
|
|
|
|
|
|
def test_convert_url_to_img():
|
|
response_url = convert_url_to_base64(
|
|
url="https://images.pexels.com/photos/1319515/pexels-photo-1319515.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
|
|
)
|
|
|
|
assert "image/jpeg" in response_url
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"url, expected_media_type",
|
|
[
|
|
("data:image/jpeg;base64,1234", "image/jpeg"),
|
|
("data:application/pdf;base64,1234", "application/pdf"),
|
|
(r"data:image\/jpeg;base64,1234", "image/jpeg"),
|
|
],
|
|
)
|
|
def test_base64_image_input(url, expected_media_type):
|
|
response = convert_to_anthropic_image_obj(openai_image_url=url)
|
|
|
|
assert response["media_type"] == expected_media_type
|
|
|
|
|
|
def test_anthropic_messages_tool_call():
|
|
messages = [
|
|
{
|
|
"role": "user",
|
|
"content": "Would development of a software platform be under ASC 350-40 or ASC 985?",
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": "",
|
|
"tool_call_id": "bc8cb4b6-88c4-4138-8993-3a9d9cd51656",
|
|
"tool_calls": [
|
|
{
|
|
"id": "bc8cb4b6-88c4-4138-8993-3a9d9cd51656",
|
|
"function": {
|
|
"arguments": '{"completed_steps": [], "next_steps": [{"tool_name": "AccountingResearchTool", "description": "Research ASC 350-40 to understand its scope and applicability to software development."}, {"tool_name": "AccountingResearchTool", "description": "Research ASC 985 to understand its scope and applicability to software development."}, {"tool_name": "AccountingResearchTool", "description": "Compare the scopes of ASC 350-40 and ASC 985 to determine which is more applicable to software platform development."}], "learnings": [], "potential_issues": ["The distinction between the two standards might not be clear-cut for all types of software development.", "There might be specific circumstances or details about the software platform that could affect which standard applies."], "missing_info": ["Specific details about the type of software platform being developed (e.g., for internal use or for sale).", "Whether the entity developing the software is also the end-user or if it\'s being developed for external customers."], "done": false, "required_formatting": null}',
|
|
"name": "TaskPlanningTool",
|
|
},
|
|
"type": "function",
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"role": "function",
|
|
"content": '{"completed_steps":[],"next_steps":[{"tool_name":"AccountingResearchTool","description":"Research ASC 350-40 to understand its scope and applicability to software development."},{"tool_name":"AccountingResearchTool","description":"Research ASC 985 to understand its scope and applicability to software development."},{"tool_name":"AccountingResearchTool","description":"Compare the scopes of ASC 350-40 and ASC 985 to determine which is more applicable to software platform development."}],"formatting_step":null}',
|
|
"name": "TaskPlanningTool",
|
|
"tool_call_id": "bc8cb4b6-88c4-4138-8993-3a9d9cd51656",
|
|
},
|
|
]
|
|
|
|
translated_messages = anthropic_messages_pt(
|
|
messages, model="claude-3-sonnet-20240229", llm_provider="anthropic"
|
|
)
|
|
|
|
print(translated_messages)
|
|
|
|
assert (
|
|
translated_messages[-1]["content"][0]["tool_use_id"]
|
|
== "bc8cb4b6-88c4-4138-8993-3a9d9cd51656"
|
|
)
|
|
|
|
|
|
def test_anthropic_cache_controls_pt():
|
|
"see anthropic docs for this: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#continuing-a-multi-turn-conversation"
|
|
messages = [
|
|
# marked for caching with the cache_control parameter, so that this checkpoint can read from the previous cache.
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": "What are the key terms and conditions in this agreement?",
|
|
"cache_control": {"type": "ephemeral"},
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Certainly! the key terms and conditions are the following: the contract is 1 year long for $10/mo",
|
|
},
|
|
# The final turn is marked with cache-control, for continuing in followups.
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": "What are the key terms and conditions in this agreement?",
|
|
"cache_control": {"type": "ephemeral"},
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Certainly! the key terms and conditions are the following: the contract is 1 year long for $10/mo",
|
|
"cache_control": {"type": "ephemeral"},
|
|
},
|
|
]
|
|
|
|
translated_messages = anthropic_messages_pt(
|
|
messages, model="claude-3-5-sonnet-20240620", llm_provider="anthropic"
|
|
)
|
|
|
|
for i, msg in enumerate(translated_messages):
|
|
if i == 0:
|
|
assert msg["content"][0]["cache_control"] == {"type": "ephemeral"}
|
|
elif i == 1:
|
|
assert "cache_controls" not in msg["content"][0]
|
|
elif i == 2:
|
|
assert msg["content"][0]["cache_control"] == {"type": "ephemeral"}
|
|
elif i == 3:
|
|
assert msg["content"][0]["cache_control"] == {"type": "ephemeral"}
|
|
|
|
print("translated_messages: ", translated_messages)
|
|
|
|
|
|
@pytest.mark.parametrize("provider", ["bedrock", "anthropic"])
|
|
def test_bedrock_parallel_tool_calling_pt(provider):
|
|
"""
|
|
Make sure parallel tool call blocks are merged correctly - https://github.com/BerriAI/litellm/issues/5277
|
|
"""
|
|
from litellm.llms.prompt_templates.factory import _bedrock_converse_messages_pt
|
|
from litellm.types.utils import ChatCompletionMessageToolCall, Function, Message
|
|
|
|
messages = [
|
|
{
|
|
"role": "user",
|
|
"content": "What's the weather like in San Francisco, Tokyo, and Paris? - give me 3 responses",
|
|
},
|
|
Message(
|
|
content="Here are the current weather conditions for San Francisco, Tokyo, and Paris:",
|
|
role="assistant",
|
|
tool_calls=[
|
|
ChatCompletionMessageToolCall(
|
|
index=1,
|
|
function=Function(
|
|
arguments='{"city": "New York"}',
|
|
name="get_current_weather",
|
|
),
|
|
id="tooluse_XcqEBfm8R-2YVaPhDUHsPQ",
|
|
type="function",
|
|
),
|
|
ChatCompletionMessageToolCall(
|
|
index=2,
|
|
function=Function(
|
|
arguments='{"city": "London"}',
|
|
name="get_current_weather",
|
|
),
|
|
id="tooluse_VB9nk7UGRniVzGcaj6xrAQ",
|
|
type="function",
|
|
),
|
|
],
|
|
function_call=None,
|
|
),
|
|
{
|
|
"tool_call_id": "tooluse_XcqEBfm8R-2YVaPhDUHsPQ",
|
|
"role": "tool",
|
|
"name": "get_current_weather",
|
|
"content": "25 degrees celsius.",
|
|
},
|
|
{
|
|
"tool_call_id": "tooluse_VB9nk7UGRniVzGcaj6xrAQ",
|
|
"role": "tool",
|
|
"name": "get_current_weather",
|
|
"content": "28 degrees celsius.",
|
|
},
|
|
]
|
|
|
|
if provider == "bedrock":
|
|
translated_messages = _bedrock_converse_messages_pt(
|
|
messages=messages,
|
|
model="anthropic.claude-3-sonnet-20240229-v1:0",
|
|
llm_provider="bedrock",
|
|
)
|
|
else:
|
|
translated_messages = anthropic_messages_pt(
|
|
messages=messages,
|
|
model="claude-3-sonnet-20240229-v1:0",
|
|
llm_provider=provider,
|
|
)
|
|
print(translated_messages)
|
|
|
|
number_of_messages = len(translated_messages)
|
|
|
|
# assert last 2 messages are not the same role
|
|
assert (
|
|
translated_messages[number_of_messages - 1]["role"]
|
|
!= translated_messages[number_of_messages - 2]["role"]
|
|
)
|
|
|
|
|
|
def test_vertex_only_image_user_message():
|
|
base64_image = "/9j/2wCEAAgGBgcGBQ"
|
|
|
|
messages = [
|
|
{
|
|
"role": "user",
|
|
"content": [
|
|
{
|
|
"type": "image_url",
|
|
"image_url": {"url": f"data:image/jpeg;base64,{base64_image}"},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
|
|
response = _gemini_convert_messages_with_history(messages=messages)
|
|
|
|
expected_response = [
|
|
{
|
|
"role": "user",
|
|
"parts": [
|
|
{
|
|
"inline_data": {
|
|
"data": "/9j/2wCEAAgGBgcGBQ",
|
|
"mime_type": "image/jpeg",
|
|
}
|
|
},
|
|
{"text": " "},
|
|
],
|
|
}
|
|
]
|
|
|
|
assert len(response) == len(expected_response)
|
|
for idx, content in enumerate(response):
|
|
assert (
|
|
content == expected_response[idx]
|
|
), "Invalid gemini input. Got={}, Expected={}".format(
|
|
content, expected_response[idx]
|
|
)
|
|
|
|
|
|
def test_convert_url():
|
|
convert_url_to_base64("https://picsum.photos/id/237/200/300")
|
|
|
|
|
|
def test_azure_tool_call_invoke_helper():
|
|
messages = [
|
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
{"role": "user", "content": "What is the weather in Copenhagen?"},
|
|
{"role": "assistant", "function_call": {"name": "get_weather"}},
|
|
]
|
|
|
|
transformed_messages = litellm.AzureOpenAIConfig.transform_request(
|
|
model="gpt-4o", messages=messages, optional_params={}
|
|
)
|
|
|
|
assert transformed_messages["messages"] == [
|
|
{"role": "system", "content": "You are a helpful assistant."},
|
|
{"role": "user", "content": "What is the weather in Copenhagen?"},
|
|
{
|
|
"role": "assistant",
|
|
"function_call": {"name": "get_weather", "arguments": ""},
|
|
},
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"messages, expected_messages, user_continue_message, assistant_continue_message",
|
|
[
|
|
(
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
{"role": "assistant", "content": "Hello! How can I assist you today?"},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
{"role": "user", "content": "What is Azure?"},
|
|
{"role": "assistant", "content": "I don't know anyything, do you?"},
|
|
],
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Hello! How can I assist you today?",
|
|
},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Please continue.",
|
|
},
|
|
{"role": "user", "content": "What is Azure?"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "I don't know anyything, do you?",
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": "Please continue.",
|
|
},
|
|
],
|
|
None,
|
|
None,
|
|
),
|
|
(
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
],
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
],
|
|
None,
|
|
None,
|
|
),
|
|
(
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
],
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
{"role": "assistant", "content": "Please continue."},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
],
|
|
None,
|
|
None,
|
|
),
|
|
(
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
{"role": "user", "content": "What is Azure?"},
|
|
],
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
{"role": "assistant", "content": "Please continue."},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Please continue.",
|
|
},
|
|
{"role": "user", "content": "What is Azure?"},
|
|
],
|
|
None,
|
|
None,
|
|
),
|
|
(
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Hello! How can I assist you today?",
|
|
},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
{"role": "user", "content": "What is Azure?"},
|
|
{"role": "assistant", "content": "I don't know anyything, do you?"},
|
|
{"role": "assistant", "content": "I can't repeat sentences."},
|
|
],
|
|
[
|
|
{"role": "user", "content": "Hello!"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Hello! How can I assist you today?",
|
|
},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Please continue",
|
|
},
|
|
{"role": "user", "content": "What is Azure?"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "I don't know anyything, do you?",
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": "Ok",
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": "I can't repeat sentences.",
|
|
},
|
|
{"role": "user", "content": "Ok"},
|
|
],
|
|
{
|
|
"role": "user",
|
|
"content": "Ok",
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Please continue",
|
|
},
|
|
),
|
|
],
|
|
)
|
|
def test_ensure_alternating_roles(
|
|
messages, expected_messages, user_continue_message, assistant_continue_message
|
|
):
|
|
|
|
messages = get_completion_messages(
|
|
messages=messages,
|
|
assistant_continue_message=assistant_continue_message,
|
|
user_continue_message=user_continue_message,
|
|
ensure_alternating_roles=True,
|
|
)
|
|
|
|
print(messages)
|
|
|
|
assert messages == expected_messages
|
|
|
|
|
|
def test_alternating_roles_e2e():
|
|
from litellm.llms.custom_httpx.http_handler import HTTPHandler
|
|
import json
|
|
|
|
litellm.set_verbose = True
|
|
http_handler = HTTPHandler()
|
|
|
|
with patch.object(http_handler, "post", new=MagicMock()) as mock_post:
|
|
response = litellm.completion(
|
|
**{
|
|
"model": "databricks/databricks-meta-llama-3-1-70b-instruct",
|
|
"messages": [
|
|
{"role": "user", "content": "Hello!"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Hello! How can I assist you today?",
|
|
},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
{"role": "user", "content": "What is Azure?"},
|
|
{"role": "assistant", "content": "I don't know anyything, do you?"},
|
|
{"role": "assistant", "content": "I can't repeat sentences."},
|
|
],
|
|
"user_continue_message": {
|
|
"role": "user",
|
|
"content": "Ok",
|
|
},
|
|
"assistant_continue_message": {
|
|
"role": "assistant",
|
|
"content": "Please continue",
|
|
},
|
|
"ensure_alternating_roles": True,
|
|
},
|
|
client=http_handler,
|
|
)
|
|
print(f"response: {response}")
|
|
assert mock_post.call_args.kwargs["data"] == json.dumps(
|
|
{
|
|
"model": "databricks-meta-llama-3-1-70b-instruct",
|
|
"messages": [
|
|
{"role": "user", "content": "Hello!"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Hello! How can I assist you today?",
|
|
},
|
|
{"role": "user", "content": "What is Databricks?"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "Please continue",
|
|
},
|
|
{"role": "user", "content": "What is Azure?"},
|
|
{
|
|
"role": "assistant",
|
|
"content": "I don't know anyything, do you?",
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": "Ok",
|
|
},
|
|
{
|
|
"role": "assistant",
|
|
"content": "I can't repeat sentences.",
|
|
},
|
|
{
|
|
"role": "user",
|
|
"content": "Ok",
|
|
},
|
|
],
|
|
"stream": False,
|
|
}
|
|
)
|
|
|
|
|
|
def test_just_system_message():
|
|
from litellm.llms.prompt_templates.factory import _bedrock_converse_messages_pt
|
|
|
|
with pytest.raises(litellm.BadRequestError) as e:
|
|
_bedrock_converse_messages_pt(
|
|
messages=[],
|
|
model="anthropic.claude-3-sonnet-20240229-v1:0",
|
|
llm_provider="bedrock",
|
|
)
|
|
assert "bedrock requires at least one non-system message" in str(e.value)
|