BerriAI / litellm

Python SDK, Proxy Server (LLM Gateway) to call 100+ LLM APIs in OpenAI format - [Bedrock, Azure, OpenAI, VertexAI, Cohere, Anthropic, Sagemaker, HuggingFace, Replicate, Groq]
https://docs.litellm.ai/docs/
Other
14.54k stars 1.71k forks source link

[Feature]: Allow configuring a `dummy_tool_call` #4689

Open krrishdholakia opened 4 months ago

krrishdholakia commented 4 months ago

The Feature

Allow user to configure a dummy tool call. Use this if provider requires tools to be passed in, if any of the blocks contains a tool_use object (e.g. Anthropic).

Motivation, pitch

Motivation, pitch

Anthropic requires the tools to be passed in, on subsequent calls, otherwise it'll raise an error:

{
    "type": "error",
    "error": {
        "type": "invalid_request_error",
        "message": "Requests which include `tool_use` or `tool_result` blocks must define tools."
    }
}

This causes issues when using the basic openai tool calling example -

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": unit})
    elif "san francisco" in location.lower():
        return json.dumps(
            {"location": "San Francisco", "temperature": "72", "unit": unit}
        )
    elif "paris" in location.lower():
        return json.dumps({"location": "Paris", "temperature": "22", "unit": unit})
    else:
        return json.dumps({"location": location, "temperature": "unknown"})

@pytest.mark.parametrize(
    "model",
    ["gpt-4o", "claude-3-5-sonnet-20240620"],
)  #
def test_run_conversation(model):
    # Step 1: send the conversation and available functions to the model
    litellm.set_verbose = True
    messages = [
        {
            "role": "user",
            "content": "What's the weather in fahrenheit 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 = completion(
        model=model,
        messages=messages,
        tools=tools,
        tool_choice="auto",  # auto is default, but we'll be explicit
    )
    response_message = response.choices[0].message
    print(response_message)
    tool_calls = response_message.tool_calls
    # 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
        messages.append(response_message)  # extend conversation with assistant's reply
        # 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
        second_response = completion(
            model=model,
            messages=messages,
        )  # get a new response from the model where it can see the function response
        print(second_response.choices[0].message)
        return second_response

Twitter / LinkedIn details

cc: @brian-tecton-ai

krrishdholakia commented 4 months ago

Just tested this curl and it works for anthropic

curl --location 'https://api.anthropic.com/v1/messages' \
--header 'x-api-key: sk-hello-world' \
--header 'anthropic-version: 2023-06-01' \
--header 'content-type: application/json' \
--data '{
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "What'\''s the weather in fahrenheit like in San Francisco?"
        }
      ]
    },
    {
      "role": "assistant",
      "content": [
        {
          "type": "text",
          "text": "Certainly! I can help you get the current weather information for San Francisco in Fahrenheit. To do this, I'\''ll use the get_current_weather function. Let me fetch that information for you."
        },
        {
          "type": "tool_use",
          "id": "toolu_01EKkUnbSLJ11eCfBHPGeSkq",
          "name": "get_current_weather",
          "input": {
            "location": "San Francisco, CA",
            "unit": "fahrenheit"
          }
        }
      ]
    },
    {
      "role": "user",
      "content": [
        {
          "type": "tool_result",
          "tool_use_id": "toolu_01EKkUnbSLJ11eCfBHPGeSkq",
          "content": "{\"location\": \"San Francisco\", \"temperature\": \"72\", \"unit\": \"fahrenheit\"}"
        }
      ]
    }
  ],
  "max_tokens": 4096,
  "model": "claude-3-5-sonnet-20240620",
  "tools": [
  {
    "name": "get_stock_price",
    "description": "Get the current stock price for a given ticker symbol.",
    "input_schema": {
      "type": "object",
      "properties": {
        "ticker": {
          "type": "string",
          "description": "The stock ticker symbol, e.g. AAPL for Apple Inc."
        }
      },
      "required": ["ticker"]
    }
  }
]
}
'