|
| 1 | +--- |
| 2 | +title: Tools |
| 3 | +--- |
| 4 | + |
| 5 | +# Tools |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +Some models support tool calling, meaning that instead of directly providing its final response, the model can require to call tools you have defined and would later use the tool response in its final response. Tool calling typically goes along providing a `Chat` input as it implies a multiturn conversation with the model. |
| 10 | + |
| 11 | +For the moment, tool calling is supported by three Outlines models: |
| 12 | + |
| 13 | +- `Anthropic` |
| 14 | +- `Gemini` |
| 15 | +- `OpenAI` |
| 16 | + |
| 17 | +## Tool Definition |
| 18 | + |
| 19 | +Using tool calling starts with defining the tools that the model can call. There are three formats currently supported as described below. |
| 20 | + |
| 21 | +Once defined, the tools must be provided in a list to the `tools` keyword argument to the `Generator` constructor or to the text generation methods of a model. As such, the interface for `tools` is very similar to that of the `output_type`. |
| 22 | + |
| 23 | +#### ToolDef |
| 24 | + |
| 25 | +A tool can first by defined as a dictionnary. A `ToolDef` dict must contain the following keys: |
| 26 | + |
| 27 | +- `name`: The name of the tool |
| 28 | +- `description`: A description of the tool to help the LLM understand its use |
| 29 | +- `parameters`: A dictionnary containing the paramters of the tool, using the JSON properties format. If the LLM decides to call the tool, it will provide values for the parameters |
| 30 | +- `required`: A list of parameters that are mandatory. All those parameters must be included in the `parameters` key described above |
| 31 | + |
| 32 | +For instance: |
| 33 | + |
| 34 | +```python |
| 35 | +import openai |
| 36 | +from outlines import from_openai |
| 37 | +from outlines.inputs import Chat |
| 38 | +from outlines.tools import ToolDef |
| 39 | + |
| 40 | +client = openai.OpenAI() |
| 41 | +model = from_openai(client, "gpt-4o") |
| 42 | + |
| 43 | +chat = Chat([ |
| 44 | + {"role": "system", "content": "You are a helpful assistant."}, |
| 45 | + {"role": "user", "content": "What's the weather in Tokyo?"}, |
| 46 | +]) |
| 47 | + |
| 48 | +weather_tool = ToolDef( |
| 49 | + name="get_weather", |
| 50 | + description="Give the weather for a given city, and optionally for a specific hour of the day", |
| 51 | + parameters={"city": {"type": "string"}, "hour": {"type": "integer"}}, |
| 52 | + required=["city"], |
| 53 | +) |
| 54 | + |
| 55 | +response = model(chat, tools=[weather_tool]) |
| 56 | +print(response.tool_calls) # [ToolCallOutput(name='get_weather', id='call_p7ToNwgrgoEk9poN7PXTELT5', args={'city': 'Tokyo'})] |
| 57 | +``` |
| 58 | + |
| 59 | +#### Function |
| 60 | + |
| 61 | +A python function can be used as a tool definition. The `description` would then correspond to the docstring while the `parameters` and `required` would be deduced from the signature. |
| 62 | + |
| 63 | +```python |
| 64 | +import openai |
| 65 | +from outlines import from_openai |
| 66 | +from outlines.inputs import Chat |
| 67 | +from typing import Optional |
| 68 | + |
| 69 | +client = openai.OpenAI() |
| 70 | +model = from_openai(client, "gpt-4o") |
| 71 | + |
| 72 | +chat = Chat([ |
| 73 | + {"role": "system", "content": "You are a helpful assistant."}, |
| 74 | + {"role": "user", "content": "What's the weather in Tokyo?"}, |
| 75 | +]) |
| 76 | + |
| 77 | +def get_weather(city: str, hour: Optional[int] = None): |
| 78 | + """Give the weather for a given city, and optionally for a specific hour of the day""" |
| 79 | + pass |
| 80 | + |
| 81 | +response = model(chat, tools=[get_weather]) |
| 82 | +print(response.tool_calls) # [ToolCallOutput(name='get_weather', id='call_IdsfmBss6XhiBDbchTqp3HHz', args={'city': 'Tokyo'})] |
| 83 | +``` |
| 84 | + |
| 85 | +#### Pydantic model |
| 86 | + |
| 87 | +Lastly, you can use a Pydantic model to define the interface of your tool. |
| 88 | + |
| 89 | +```python |
| 90 | +import openai |
| 91 | +from outlines import from_openai |
| 92 | +from outlines.inputs import Chat |
| 93 | +from pydantic import BaseModel |
| 94 | +from typing import Optional |
| 95 | + |
| 96 | +client = openai.OpenAI() |
| 97 | +model = from_openai(client, "gpt-4o") |
| 98 | + |
| 99 | +chat = Chat([ |
| 100 | + {"role": "system", "content": "You are a helpful assistant."}, |
| 101 | + {"role": "user", "content": "What's the weather in Tokyo?"}, |
| 102 | +]) |
| 103 | + |
| 104 | +class GetWeather(BaseModel): |
| 105 | + """Give the weather for a given city, and optionally for a specific hour of the day""" |
| 106 | + city: str |
| 107 | + hour: Optional[int] = None |
| 108 | + |
| 109 | +response = model(chat, tools=[GetWeather]) |
| 110 | +print(response.tool_calls) # [ToolCallOutput(name='GetWeather', id='call_KWfADMEr6dnDDcw1m2dllRvq', args={'city': 'Tokyo'})] |
| 111 | +``` |
| 112 | + |
| 113 | +## Tool Calls and Responses |
| 114 | + |
| 115 | +If the model decides to call a tool, you'll get a value for the `tool_calls` attribute of the `Output` received. This value is a `OutputToolCall` instance containing three attributes: |
| 116 | + |
| 117 | +- `name`: The name of the tool to call |
| 118 | +- `id`: The id of the tool call to be able to easily link the tool call and the tool response |
| 119 | +- `args`: A dictionnary containing for each parameter required by the tool the value provided by the LLM |
| 120 | + |
| 121 | +You should use the `name` and the `args` to call your tool yourself and get its reponse. Afterward, you can add to your chat the `Output` you first receive and a `ToolMessage` before being able to call the model again to continue the conversation. |
| 122 | + |
| 123 | +For instance: |
| 124 | + |
| 125 | +```python |
| 126 | +import openai |
| 127 | +from outlines import Generator, from_openai |
| 128 | +from outlines.inputs import Chat |
| 129 | +from typing import Optional |
| 130 | + |
| 131 | +# Our tool |
| 132 | +def get_weather(city: str, hour: Optional[int] = None): |
| 133 | + """Give the weather for a given city, and optionally for a specific hour of the day""" |
| 134 | + return "20 degrees" |
| 135 | + |
| 136 | +client = openai.OpenAI() |
| 137 | +model = from_openai(client, "gpt-4o") |
| 138 | +generator = Generator(model, tools=[get_weather]) |
| 139 | + |
| 140 | +chat = Chat([ |
| 141 | + {"role": "system", "content": "You are a helpful assistant."}, |
| 142 | + {"role": "user", "content": "What's the weather in Tokyo?"}, |
| 143 | +]) |
| 144 | + |
| 145 | +response = generator(chat) |
| 146 | +print(response.tool_calls) # [ToolCallOutput(name='get_weather', id='call_NlIGHr8HoiVgSZfOJ7Y5xz35', args={'city': 'Tokyo'})] |
| 147 | + |
| 148 | +# Add the model response to the chat |
| 149 | +chat.add_output(response) |
| 150 | + |
| 151 | +# Call the tool with the parameters given by the model and add a tool message to the chat |
| 152 | +tool_call = response.tool_calls[0] |
| 153 | +tool_response = get_weather(**tool_call.args) |
| 154 | +chat.add_tool_message( |
| 155 | + content=tool_response, |
| 156 | + tool_name=tool_call.name, |
| 157 | + tool_call_id=tool_call.id |
| 158 | +) |
| 159 | + |
| 160 | +response = generator(chat) |
| 161 | +print(response.content) # The weather in Tokyo is currently 20 degrees. |
| 162 | +``` |
| 163 | + |
| 164 | +When using streaming, the response would be a `StreamingOutput` and the `tool_calls` value a list of `StreamingOutputToolCall`. The only difference compared to what's the describe above is that the `args` field would be a string as the value is received by chunks. You need to concatenate the chunks together to get the full `args` to use to call the tool. |
0 commit comments