2024-08-20 23:52:13 +08:00
import os
import sys
2023-11-18 07:51:25 +08:00
import traceback
2024-08-20 23:52:13 +08:00
2023-11-18 07:51:25 +08:00
from dotenv import load_dotenv
load_dotenv ( )
2024-08-20 23:52:13 +08:00
import io
import os
2023-11-18 07:51:25 +08:00
sys . path . insert (
0 , os . path . abspath ( " ../.. " )
) # Adds the parent directory to the system path
import pytest
2024-08-20 23:52:13 +08:00
2023-11-18 07:51:25 +08:00
import litellm
2024-08-20 23:52:13 +08:00
from litellm import RateLimitError , Timeout , completion , completion_cost , embedding
2023-12-25 16:40:38 +08:00
2023-11-30 02:56:21 +08:00
litellm . num_retries = 0
2023-11-18 07:51:25 +08:00
litellm . cache = None
2023-11-19 07:15:02 +08:00
# litellm.set_verbose=True
2023-11-18 07:51:25 +08:00
import json
2023-11-19 07:15:02 +08:00
# litellm.success_callback = ["langfuse"]
2023-12-25 16:40:38 +08:00
2023-11-18 07:51:25 +08:00
def get_current_weather ( location , unit = " fahrenheit " ) :
""" Get the current weather in a given location """
if " tokyo " in location . lower ( ) :
return json . dumps ( { " location " : " Tokyo " , " temperature " : " 10 " , " unit " : " celsius " } )
elif " san francisco " in location . lower ( ) :
2023-12-25 16:40:38 +08:00
return json . dumps (
{ " location " : " San Francisco " , " temperature " : " 72 " , " unit " : " fahrenheit " }
)
2023-11-18 07:51:25 +08:00
elif " paris " in location . lower ( ) :
return json . dumps ( { " location " : " Paris " , " temperature " : " 22 " , " unit " : " celsius " } )
else :
return json . dumps ( { " location " : location , " temperature " : " unknown " } )
2023-12-25 16:40:38 +08:00
2023-11-18 09:03:24 +08:00
# Example dummy function hard coded to return the same weather
2024-05-14 04:29:43 +08:00
2023-11-18 09:03:24 +08:00
# In production, this could be your backend API or an external API
2024-05-14 04:29:43 +08:00
@pytest.mark.parametrize (
2024-08-20 23:52:13 +08:00
" model " ,
[
2024-09-28 13:52:57 +08:00
" gpt-3.5-turbo-1106 " ,
2024-09-05 07:03:02 +08:00
# "mistral/mistral-large-latest",
2024-10-03 10:00:28 +08:00
" claude-3-haiku-20240307 " ,
" gemini/gemini-1.5-pro " ,
2024-09-13 14:04:06 +08:00
" anthropic.claude-3-sonnet-20240229-v1:0 " ,
2024-10-03 10:00:28 +08:00
# "groq/llama3-8b-8192",
2024-08-20 23:52:13 +08:00
] ,
2024-05-14 04:29:43 +08:00
)
2024-09-10 09:54:17 +08:00
@pytest.mark.flaky ( retries = 3 , delay = 1 )
2024-09-05 07:03:02 +08:00
def test_aaparallel_function_call ( model ) :
2023-11-18 09:03:24 +08:00
try :
2024-08-23 00:59:52 +08:00
litellm . set_verbose = True
2024-10-03 10:00:28 +08:00
litellm . modify_params = True
2023-11-18 09:03:24 +08:00
# Step 1: send the conversation and available functions to the model
2023-12-25 16:40:38 +08:00
messages = [
{
" role " : " user " ,
2024-05-14 04:29:43 +08:00
" content " : " What ' s the weather like in San Francisco, Tokyo, and Paris? - give me 3 responses " ,
2023-12-25 16:40:38 +08:00
}
]
2023-11-18 09:03:24 +08:00
tools = [
{
" type " : " function " ,
" function " : {
" name " : " get_current_weather " ,
" description " : " Get the current weather in a given location " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" location " : {
" type " : " string " ,
2024-05-14 04:29:43 +08:00
" description " : " The city and state " ,
2023-11-18 09:03:24 +08:00
} ,
2023-12-25 16:40:38 +08:00
" unit " : {
" type " : " string " ,
" enum " : [ " celsius " , " fahrenheit " ] ,
} ,
2023-11-18 07:51:25 +08:00
} ,
2023-11-18 09:03:24 +08:00
" required " : [ " location " ] ,
2023-11-18 07:51:25 +08:00
} ,
} ,
2023-11-18 09:03:24 +08:00
}
]
response = litellm . completion (
2024-05-14 04:29:43 +08:00
model = model ,
2023-11-18 07:51:25 +08:00
messages = messages ,
2023-11-18 09:03:24 +08:00
tools = tools ,
tool_choice = " auto " , # auto is default, but we'll be explicit
)
print ( " Response \n " , response )
response_message = response . choices [ 0 ] . message
tool_calls = response_message . tool_calls
print ( " Expecting there to be 3 tool calls " )
2023-12-25 16:40:38 +08:00
assert (
2024-05-14 04:29:43 +08:00
len ( tool_calls ) > 0
) # this has to call the function for SF, Tokyo and paris
2023-11-18 09:03:24 +08:00
# Step 2: check if the model wanted to call a function
2024-09-05 07:03:02 +08:00
print ( f " tool_calls: { tool_calls } " )
2023-11-18 09:03:24 +08:00
if tool_calls :
# Step 3: call the function
# Note: the JSON response may not always be valid; be sure to handle errors
available_functions = {
" get_current_weather " : get_current_weather ,
} # only one function in this example, but you can have multiple
2023-12-25 16:40:38 +08:00
messages . append (
response_message
) # extend conversation with assistant's reply
2023-11-18 09:03:24 +08:00
print ( " Response message \n " , response_message )
# Step 4: send the info for each function call and function response to the model
for tool_call in tool_calls :
function_name = tool_call . function . name
2024-05-16 07:05:17 +08:00
if function_name not in available_functions :
# the model called a function that does not exist in available_functions - don't try calling anything
return
2023-11-18 09:03:24 +08:00
function_to_call = available_functions [ function_name ]
function_args = json . loads ( tool_call . function . arguments )
function_response = function_to_call (
location = function_args . get ( " location " ) ,
unit = function_args . get ( " unit " ) ,
)
messages . append (
{
" tool_call_id " : tool_call . id ,
" role " : " tool " ,
" name " : function_name ,
" content " : function_response ,
}
) # extend conversation with function response
2023-11-30 02:56:21 +08:00
print ( f " messages: { messages } " )
2023-11-18 09:03:24 +08:00
second_response = litellm . completion (
2024-08-20 23:52:13 +08:00
model = model ,
messages = messages ,
temperature = 0.2 ,
seed = 22 ,
2024-10-03 10:00:28 +08:00
# tools=tools,
2024-08-20 23:52:13 +08:00
drop_params = True ,
2023-11-18 09:03:24 +08:00
) # get a new response from the model where it can see the function response
print ( " second response \n " , second_response )
2024-09-05 07:03:02 +08:00
except litellm . InternalServerError as e :
print ( e )
except litellm . RateLimitError as e :
print ( e )
2023-11-18 09:03:24 +08:00
except Exception as e :
pytest . fail ( f " Error occurred: { e } " )
2023-11-19 08:23:28 +08:00
2024-02-27 06:26:58 +08:00
# test_parallel_function_call()
2023-11-19 08:23:28 +08:00
2024-09-28 13:52:57 +08:00
from litellm . types . utils import ChatCompletionMessageToolCall , Function , Message
@pytest.mark.parametrize (
" model, provider " ,
[
(
" anthropic.claude-3-sonnet-20240229-v1:0 " ,
" bedrock " ,
) ,
( " claude-3-haiku-20240307 " , " anthropic " ) ,
] ,
)
@pytest.mark.parametrize (
" messages, expected_error_msg " ,
[
(
[
{
" 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 = ' { " location " : " San Francisco, CA " , " unit " : " fahrenheit " } ' ,
name = " get_current_weather " ,
) ,
id = " tooluse_Jj98qn6xQlOP_PiQr-w9iA " ,
type = " function " ,
)
] ,
function_call = None ,
) ,
{
" tool_call_id " : " tooluse_Jj98qn6xQlOP_PiQr-w9iA " ,
" role " : " tool " ,
" name " : " get_current_weather " ,
" content " : ' { " location " : " San Francisco " , " temperature " : " 72 " , " unit " : " fahrenheit " } ' ,
} ,
] ,
True ,
) ,
(
[
{
" role " : " user " ,
" content " : " What ' s the weather like in San Francisco, Tokyo, and Paris? - give me 3 responses " ,
}
] ,
False ,
) ,
] ,
)
def test_parallel_function_call_anthropic_error_msg (
model , provider , messages , expected_error_msg
) :
"""
Anthropic doesn ' t support tool calling without `tools=` param specified.
Ensure this error is thrown when ` tools = ` param is not specified . But tool call requests are made .
Reference Issue : https : / / github . com / BerriAI / litellm / issues / 5747 , https : / / github . com / BerriAI / litellm / issues / 5388
"""
try :
litellm . set_verbose = True
messages = messages
if expected_error_msg :
with pytest . raises ( litellm . UnsupportedParamsError ) as e :
second_response = litellm . completion (
model = model ,
messages = messages ,
temperature = 0.2 ,
seed = 22 ,
drop_params = True ,
) # get a new response from the model where it can see the function response
print ( " second response \n " , second_response )
else :
second_response = litellm . completion (
model = model ,
messages = messages ,
temperature = 0.2 ,
seed = 22 ,
drop_params = True ,
) # get a new response from the model where it can see the function response
print ( " second response \n " , second_response )
except litellm . InternalServerError as e :
print ( e )
except litellm . RateLimitError as e :
print ( e )
except Exception as e :
pytest . fail ( f " Error occurred: { e } " )
2023-11-19 08:23:28 +08:00
def test_parallel_function_call_stream ( ) :
try :
2024-02-27 06:26:58 +08:00
litellm . set_verbose = True
2023-11-19 08:23:28 +08:00
# Step 1: send the conversation and available functions to the model
2023-12-25 16:40:38 +08:00
messages = [
{
" role " : " user " ,
" content " : " What ' s the weather like in San Francisco, Tokyo, and Paris? " ,
}
]
2023-11-19 08:23:28 +08:00
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 " ,
} ,
2023-12-25 16:40:38 +08:00
" unit " : {
" type " : " string " ,
" enum " : [ " celsius " , " fahrenheit " ] ,
} ,
2023-11-19 08:23:28 +08:00
} ,
" required " : [ " location " ] ,
} ,
} ,
}
]
response = litellm . completion (
model = " gpt-3.5-turbo-1106 " ,
messages = messages ,
tools = tools ,
stream = True ,
tool_choice = " auto " , # auto is default, but we'll be explicit
2023-12-25 16:40:38 +08:00
complete_response = True ,
2023-11-19 08:23:28 +08:00
)
print ( " Response \n " , response )
2023-11-30 02:56:21 +08:00
# for chunk in response:
# print(chunk)
response_message = response . choices [ 0 ] . message
tool_calls = response_message . tool_calls
2023-11-19 08:23:28 +08:00
2023-11-30 02:56:21 +08:00
print ( " length of tool calls " , len ( tool_calls ) )
print ( " Expecting there to be 3 tool calls " )
2023-12-25 16:40:38 +08:00
assert (
len ( tool_calls ) > 1
) # this has to call the function for SF, Tokyo and parise
2023-11-19 08:23:28 +08:00
2023-11-30 02:56:21 +08:00
# Step 2: check if the model wanted to call a function
if tool_calls :
# Step 3: call the function
# Note: the JSON response may not always be valid; be sure to handle errors
available_functions = {
" get_current_weather " : get_current_weather ,
} # only one function in this example, but you can have multiple
2023-12-25 16:40:38 +08:00
messages . append (
response_message
) # extend conversation with assistant's reply
2023-11-30 02:56:21 +08:00
print ( " Response message \n " , response_message )
# Step 4: send the info for each function call and function response to the model
for tool_call in tool_calls :
function_name = tool_call . function . name
function_to_call = available_functions [ function_name ]
function_args = json . loads ( tool_call . function . arguments )
function_response = function_to_call (
location = function_args . get ( " location " ) ,
unit = function_args . get ( " unit " ) ,
)
messages . append (
{
" tool_call_id " : tool_call . id ,
" role " : " tool " ,
" name " : function_name ,
" content " : function_response ,
}
) # extend conversation with function response
print ( f " messages: { messages } " )
second_response = litellm . completion (
2023-12-25 16:40:38 +08:00
model = " gpt-3.5-turbo-1106 " , messages = messages , temperature = 0.2 , seed = 22
2023-11-30 02:56:21 +08:00
) # get a new response from the model where it can see the function response
print ( " second response \n " , second_response )
return second_response
2023-11-19 08:23:28 +08:00
except Exception as e :
pytest . fail ( f " Error occurred: { e } " )
2023-12-25 16:40:38 +08:00
2024-02-27 06:26:58 +08:00
# test_parallel_function_call_stream()
2024-04-15 23:13:05 +08:00
2024-04-20 07:41:23 +08:00
@pytest.mark.skip (
reason = " Flaky test. Groq function calling is not reliable for ci/cd testing. "
)
2024-04-15 23:13:05 +08:00
def test_groq_parallel_function_call ( ) :
litellm . set_verbose = True
try :
# Step 1: send the conversation and available functions to the model
messages = [
{
" role " : " system " ,
" content " : " You are a function calling LLM that uses the data extracted from get_current_weather to answer questions about the weather in San Francisco. " ,
} ,
{
" role " : " user " ,
" content " : " What ' s the weather like in San Francisco? " ,
} ,
]
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 " ] ,
} ,
} ,
}
]
response = litellm . completion (
model = " groq/llama2-70b-4096 " ,
messages = messages ,
tools = tools ,
tool_choice = " auto " , # auto is default, but we'll be explicit
)
print ( " Response \n " , response )
response_message = response . choices [ 0 ] . message
2024-04-18 09:32:34 +08:00
if hasattr ( response_message , " tool_calls " ) :
tool_calls = response_message . tool_calls
2024-04-15 23:13:05 +08:00
2024-04-18 09:32:34 +08:00
assert isinstance (
response . choices [ 0 ] . message . tool_calls [ 0 ] . function . name , str
)
assert isinstance (
response . choices [ 0 ] . message . tool_calls [ 0 ] . function . arguments , str
)
2024-04-15 23:13:05 +08:00
2024-04-18 09:32:34 +08:00
print ( " length of tool calls " , len ( tool_calls ) )
2024-04-15 23:13:05 +08:00
2024-04-18 09:32:34 +08:00
# Step 2: check if the model wanted to call a function
if tool_calls :
# Step 3: call the function
# Note: the JSON response may not always be valid; be sure to handle errors
available_functions = {
" get_current_weather " : get_current_weather ,
} # only one function in this example, but you can have multiple
2024-04-15 23:13:05 +08:00
messages . append (
2024-04-18 09:32:34 +08:00
response_message
) # extend conversation with assistant's reply
print ( " Response message \n " , response_message )
# Step 4: send the info for each function call and function response to the model
for tool_call in tool_calls :
function_name = tool_call . function . name
function_to_call = available_functions [ function_name ]
function_args = json . loads ( tool_call . function . arguments )
function_response = function_to_call (
location = function_args . get ( " location " ) ,
unit = function_args . get ( " unit " ) ,
)
2024-08-22 13:28:35 +08:00
2024-04-18 09:32:34 +08:00
messages . append (
{
" tool_call_id " : tool_call . id ,
" role " : " tool " ,
" name " : function_name ,
" content " : function_response ,
}
) # extend conversation with function response
print ( f " messages: { messages } " )
second_response = litellm . completion (
model = " groq/llama2-70b-4096 " , messages = messages
) # get a new response from the model where it can see the function response
print ( " second response \n " , second_response )
2024-04-15 23:13:05 +08:00
except Exception as e :
pytest . fail ( f " Error occurred: { e } " )
2024-10-03 10:00:28 +08:00
@pytest.mark.parametrize (
" model " ,
[
" anthropic.claude-3-sonnet-20240229-v1:0 " ,
" claude-3-haiku-20240307 " ,
] ,
)
def test_anthropic_function_call_with_no_schema ( model ) :
"""
Relevant Issue : https : / / github . com / BerriAI / litellm / issues / 6012
"""
tools = [
{
" type " : " function " ,
" function " : {
" name " : " get_current_weather " ,
" description " : " Get the current weather in New York " ,
} ,
}
]
messages = [
{ " role " : " user " , " content " : " What is the current temperature in New York? " }
]
completion ( model = model , messages = messages , tools = tools , tool_choice = " auto " )