├── LICENSE ├── README.md ├── anthropic_function ├── __init__.py └── anthropic_function.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 mshumer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Anthropic with Functions 2 | 3 | This library allows you to use the Anthropic Claude models with OpenAI-like Functions. 4 | 5 | It's super rough and early, so feel free to make improvements if you want! 6 | 7 | ## Installation 8 | 9 | You can install this package directly from GitHub: 10 | 11 | ```bash 12 | pip install git+https://github.com/mshumer/anthropic_with_functions.git 13 | ``` 14 | 15 | ## Usage 16 | 17 | Here's a basic usage example: 18 | 19 | ```python 20 | from anthropic_function import AnthropicFunction 21 | import json 22 | 23 | anthropic_func = AnthropicFunction(api_key="ANTHROPIC_API_KEY", model="claude-2", temperature=0.7, max_tokens_to_sample=500) 24 | 25 | # Define your functions 26 | def get_current_weather(location, unit="fahrenheit"): 27 | # Get the current weather in a given location 28 | weather_info = { 29 | "location": location, 30 | "temperature": "72", # hardcoded for the example 31 | "unit": unit, 32 | "forecast": ["sunny", "windy"], # hardcoded for the example 33 | } 34 | return json.dumps(weather_info) 35 | 36 | # Add your functions to the AnthropicFunction instance 37 | anthropic_func.add_function( 38 | "get_current_weather", "Get the current weather in a given location", 39 | ["location: string", "unit: 'celsius' | 'fahrenheit'"]) 40 | 41 | # Define the conversation messages 42 | messages = [{"role": "HUMAN", "content": "how are you today?"}, {"role": "AI", "content": "I'm good, thanks for asking!"}, {"role": "HUMAN", "content": "Remind me what I just asked you?"}, {"role": "AI", "content": "You just asked me, how are you today? and I responded, I'm good, thanks for asking!"}, {"role": "HUMAN", "content": "What's the weather in London?"}] 43 | 44 | # Call the model (it will return either a function or a normal message) 45 | response = anthropic_func.call(messages, model="claude-2", temperature=0.8, max_tokens_to_sample=400) 46 | 47 | if response["function"]: 48 | # Parse and then call the function with the arguments 49 | function_output = None 50 | 51 | # Depending on your function(s), write parsing code to grab the function name and arguments 52 | #### PARSING CODE GOES HERE 53 | function_name = 'get_current_weather' # placeholder -- replace with your parsing code that grabs the function name 54 | function_arguments = {'location': 'london', 'unit': 'celsius'} # placeholder -- replace with your parsing code that grabs the function arguments 55 | 56 | # Now, call the relevant function with the arguments, return the result as `function_output` 57 | if function_name == 'get_current_weather': 58 | function_output = get_current_weather(location=function_arguments['location'], unit=function_arguments['unit']) 59 | # Describe the function's output 60 | if function_output is not None: 61 | response = anthropic_func.describe_function_output(function_name, function_arguments, function_output, messages) 62 | print('Response:', response['response']) 63 | 64 | else: 65 | print('No function found') 66 | print('Response:', response['response']) 67 | ``` 68 | 69 | ## Contributing 70 | 71 | Contributions are welcome! Please feel free to submit a Pull Request. 72 | 73 | Some ideas: 74 | - create automatic function / arguments parsing code so that the user doesn't need to write it themselves 75 | - generally get the library to parity w/ OpenAI's Functions system 76 | 77 | ## License 78 | 79 | This project is licensed under the terms of the MIT license. 80 | -------------------------------------------------------------------------------- /anthropic_function/ __init__.py: -------------------------------------------------------------------------------- 1 | from .anthropic_function import AnthropicFunction -------------------------------------------------------------------------------- /anthropic_function/anthropic_function.py: -------------------------------------------------------------------------------- 1 | from anthropic import Anthropic, HUMAN_PROMPT, AI_PROMPT 2 | import json 3 | import ast 4 | 5 | 6 | class AnthropicFunction: 7 | 8 | def __init__(self, 9 | api_key, 10 | model="claude-2", 11 | temperature=0.5, 12 | max_tokens_to_sample=300): 13 | self.anthropic = Anthropic(api_key=api_key) 14 | self.model = model 15 | self.temperature = temperature 16 | self.max_tokens_to_sample = max_tokens_to_sample 17 | self.functions = {} 18 | 19 | def add_function(self, name, description, parameters): 20 | self.functions[name] = { 21 | "description": description, 22 | "parameters": parameters 23 | } 24 | 25 | def call(self, 26 | messages, 27 | model=None, 28 | temperature=None, 29 | max_tokens_to_sample=None): 30 | # Use the parameters if provided, otherwise use the instance variables 31 | model = model if model is not None else self.model 32 | temperature = temperature if temperature is not None else self.temperature 33 | max_tokens_to_sample = max_tokens_to_sample if max_tokens_to_sample is not None else self.max_tokens_to_sample 34 | 35 | function_calling_documentation = """Function calling 36 | In an API call, you can describe functions to the latest models, and have the model intelligently choose to output a JSON object containing arguments to call those functions. The chat-based language processing API does not call the function; instead, the model generates JSON that you can use to call the function in your code. 37 | 38 | The latest models have been fine-tuned to both detect when a function should be called (depending on the input) and to respond with JSON that adheres to the function signature. With this capability also comes potential risks. We strongly recommend building in user confirmation flows before taking actions that impact the world on behalf of users (sending an email, posting something online, making a purchase, etc). 39 | 40 | Under the hood, functions are injected into the system message in a syntax the model has been trained on. This means functions count against the model's context limit and are billed as input tokens. If running into context limits, we suggest limiting the number of functions or the length of documentation you provide for function parameters. 41 | Function calling allows you to more reliably get structured data back from the model. For example, you can: 42 | 43 | Create chatbots that answer questions by calling external APIs (similar to certain advanced language model plugins) 44 | e.g. define functions like send_email(to: string, body: string), or get_current_weather(location: string, unit: 'celsius' | 'fahrenheit') 45 | Convert natural language into API calls 46 | e.g. convert "Who are my top customers?" to get_customers(min_revenue: int, created_before: string, limit: int) and call your internal API 47 | Extract structured data from text 48 | e.g. define a function called extract_data(name: string, birthday: string), or sql_query(query: string) 49 | ...and much more! 50 | 51 | The basic sequence of steps for function calling is as follows: 52 | 53 | Call the model with the user query and a set of functions defined in the functions parameter. 54 | The model can choose to call a function; if so, the content will be a stringified JSON object adhering to your custom schema (note: the model may generate invalid JSON or hallucinate parameters). 55 | Parse the string into JSON in your code, and call your function with the provided arguments if they exist. 56 | Call the model again by appending the function response as a new message, and let the model summarize the results back to the user. 57 | You can see these steps in action through the example below: 58 | 59 | import lang_model_lib 60 | import json 61 | 62 | 63 | # Example dummy function hard coded to return the same weather 64 | # In production, this could be your backend API or an external API 65 | def get_current_weather(location, unit="fahrenheit"): 66 | # Get the current weather in a given location" 67 | weather_info = { 68 | "location": location, 69 | "temperature": "72", 70 | "unit": unit, 71 | "forecast": ["sunny", "windy"], 72 | } 73 | return json.dumps(weather_info) 74 | 75 | 76 | def run_conversation(): 77 | # Step 1: send the conversation and available functions to the language model 78 | messages = [{"role": "user", "content": "What's the weather like in Boston?"}] 79 | functions = [ 80 | { 81 | "name": "get_current_weather", 82 | "description": "Get the current weather in a given location", 83 | "parameters": { 84 | "type": "object", 85 | "properties": { 86 | "location": { 87 | "type": "string", 88 | "description": "The city and state, e.g. San Francisco, CA", 89 | }, 90 | "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, 91 | }, 92 | "required": ["location"], 93 | }, 94 | } 95 | ] 96 | response = lang_model_lib.ChatProcess.create( 97 | model="latest-model", 98 | messages=messages, 99 | functions=functions, 100 | function_call="auto", # auto is default, but we'll be explicit 101 | ) 102 | response_message = response["choices"][0]["message"] 103 | 104 | # Step 2: check if the language model intended to call a function 105 | if response_message.get("function_call"): 106 | # Step 3: call the function 107 | # Note: the JSON response may not always be valid; be sure to handle errors 108 | available_functions = { 109 | "get_current_weather": get_current_weather, 110 | } # only one function in this example, but you can have multiple 111 | function_name = response_message["function_call"]["name"] 112 | fuction_to_call = available_functions[function_name] 113 | function_args = json.loads(response_message["function_call"]["arguments"]) 114 | function_response = fuction_to_call( 115 | location=function_args.get("location"), 116 | unit=function_args.get("unit"), 117 | ) 118 | 119 | # Step 4: send the information on the function call and function response to the language model 120 | messages.append(response_message) # extend conversation with assistant's reply 121 | messages.append( 122 | { 123 | "role": "function", 124 | "name": function_name, 125 | "content": function_response, 126 | } 127 | ) # extend conversation with function response 128 | second_response = lang_model_lib.ChatProcess.create( 129 | model="latest-model", 130 | messages=messages, 131 | ) # get a new response from the language model where it can see the function response 132 | return second_response 133 | 134 | 135 | print(run_conversation()) 136 | Hallucinated outputs in function calls can often be mitigated with a system message. For example, if you find that a model is generating function calls with functions that weren't provided to it, try using a system message that says: "Only use the functions you have been provided with." 137 | In the example above, we sent the function response back to the model and let it decide the next step. It responded with a user-facing message which was telling the user the temperature in Boston, but depending on the query, it may choose to call a function again. 138 | 139 | For example, if you ask the model “Find the weather in Boston this weekend, book dinner for two on Saturday, and update my calendar” and provide the corresponding functions for these queries, it may choose to call them back to back and only at the end create a user-facing message. 140 | 141 | If you want to force the model to call a specific function you can do so by setting function_call: {"name": ""}. You can also force the model to generate a user-facing message by setting function_call: "none". Note that the default behavior (function_call: "auto") is for the model to decide on its own whether to call a function and if so which function to call. 142 | 143 | You can find more examples of function calling in the following resources: 144 | 145 | Function calling 146 | Learn from more examples demonstrating function calling 147 | Legacy API Endpoint 148 | The legacy API endpoint received its final update in July 2023 and has a different interface than the new chat-based language processing endpoint. Instead of the input being a list of messages, the input is a freeform text string called a prompt. 149 | 150 | An example API call looks as follows: 151 | 152 | import lang_model_lib 153 | 154 | response = lang_model_lib.Completion.create( 155 | model="previous-model", 156 | prompt="Write a tagline for an ice cream shop." 157 | ) 158 | See the full API reference documentation to learn more. 159 | 160 | Token log probabilities 161 | The completions API can provide a limited number of log probabilities associated with the most likely tokens for each output token. This feature is controlled by using the logprobs field. This can be useful in some cases to assess the confidence of the language model in its output. 162 | 163 | Inserting text 164 | The completions endpoint also supports inserting text by providing a suffix in addition to the standard prompt which is treated as a prefix. This need naturally arises when writing long-form text, transitioning between paragraphs, following an outline, or guiding the model towards an ending. This also works on code, and can be used to insert in the middle of a function or file. 165 | 166 | DEEP DIVE 167 | Inserting text 168 | Completions response format 169 | An example completions API response looks as follows: 170 | 171 | { 172 | "choices": [ 173 | { 174 | "finish_reason": "length", 175 | "index": 0, 176 | "logprobs": null, 177 | "text": "\n\n\"Let Your Sweet Tooth Run Wild at Our Creamy Ice Cream Shack" 178 | } 179 | ], 180 | "created": 1683130927, 181 | "id": "cmpl-7C9Wxi9Du4j1lQjdjhxBlO22M61LD", 182 | "model": "previous-model", 183 | "object": "text_completion", 184 | "usage": { 185 | "completion_tokens": 16, 186 | "prompt_tokens": 10, 187 | "total_tokens": 26 188 | } 189 | } 190 | In Python, the output can be extracted with response['choices'][0]['text']. 191 | 192 | The response format is similar to the response format of the chat completions API but also includes the optional field logprobs. 193 | 194 | Chat Completions vs. Completions 195 | The chat completions format can be made similar to the completions format by constructing a request using a single user message. For example, one can translate from English to French with the following completions prompt: 196 | 197 | Translate the following English text to French: "{text}" 198 | And an equivalent chat prompt would be: 199 | 200 | [{"role": "user", "content": 'Translate the following English text to French: "{text}"'}] 201 | Likewise, the completions API can be used to simulate a chat between a user and an assistant by formatting the input accordingly. 202 | 203 | The difference between these APIs derives mainly from the underlying models that are available in each. The chat completions API is the interface to our most capable model (latest-model), and our most cost-effective model (economical-model). For reference, the economical-model performs at a similar capability level to a previous-model but at a significantly lower price per token! See pricing details here. 204 | 205 | Model usage best practices 206 | Being familiar with the recommended practices for utilizing the models can greatly enhance application performance. The peculiar failure modes exhibited by the models and the techniques for mitigating or rectifying these modes are not always straightforward. There is a specialized skill set associated with working with the models, often referred to as "prompt engineering". As the field has evolved, this skill set has expanded beyond engineering queries and now encompasses engineering systems that utilize model interactions as components. To delve deeper into these practices, we invite you to explore our comprehensive guide on model usage recommendations. The guide covers methods to enhance model reasoning, minimize the occurrence of inaccurate outputs, and more. You can also access valuable resources, including code samples, in the AI Cookbook. 207 | 208 | 209 | Managing tokens 210 | Language models read and write text in chunks called tokens. In English, a token can be as short as one character or as long as one word (e.g., a or apple), and in some languages tokens can be even shorter than one character or even longer than one word. 211 | 212 | The total number of tokens in an API call affects: 213 | 214 | How much your API call costs, as you pay per token 215 | How long your API call takes, as writing more tokens takes more time 216 | Whether your API call works at all, as total tokens must be below the model’s maximum limit 217 | Both input and output tokens count toward these quantities. For example, if your API call used 10 tokens in the message input and you received 20 tokens in the message output, you would be billed for 30 tokens. Note however that for some models the price per token is different for tokens in the input vs. the output (see the pricing page for more information). 218 | 219 | To see how many tokens are used by an API call, check the usage field in the API response (e.g., response['usage']['total_tokens']). 220 | 221 | Chat models like the latest models utilize tokens in a similar manner to the models accessible in the completions API. However, due to their conversation-based structure, determining the exact token count becomes more challenging. 222 | 223 | 224 | FAQ 225 | Why are model outputs inconsistent? 226 | The API is non-deterministic by default. This means that you might get a slightly different completion every time you call it, even if your prompt stays the same. Setting temperature to 0 will make the outputs mostly deterministic, but a small amount of variability will remain. 227 | 228 | How should I set the temperature parameter? 229 | Lower values for temperature result in more consistent outputs, while higher values generate more diverse and creative results. Select a temperature value based on the desired trade-off between coherence and creativity for your specific application. 230 | 231 | 232 | How can I make my application more safe? 233 | If you want to add a moderation layer to the outputs of the Chat API, you can follow our moderation guide to prevent content that violates usage policies from being shown. 234 | """ 235 | functions = "\n".join([ 236 | f"{idx+1}. {name}({', '.join(params)}): {desc}" 237 | for idx, (name, value) in enumerate(self.functions.items()) 238 | for desc, params in [value.values()] 239 | ]) 240 | 241 | prompt_prefix = f"""\n\nHuman: Here is the documentation for how you should call functions:\n```\n{function_calling_documentation}\n```\n\nHere are the available functions:\n```{functions}\n```\nRemember, you should output `FUNCTION $function_name($arguments)` when you want to call a function. 242 | 243 | If you are calling a function, only include the function call -- no other text. For example, this is wrong. 244 | ``` 245 | FUNCTION get_current_weather(location: \'Boston\', unit: \'fahrenheit\')\n\nI have called the get_current_weather function to retrieve the weather in Boston. 246 | ``` 247 | 248 | Why is it wrong? It is wrong because the response included extra text that is not part of the function call. `\n\nI have called the get_current_weather function to retrieve the weather in Boston.` 249 | 250 | This is what a correct function call output would look like: 251 | ``` 252 | FUNCTION get_current_weather(location: \'Boston\', unit: \'fahrenheit\') 253 | ``` 254 | 255 | If no function call is needed, converse like you would otherwise.""" 256 | 257 | conversation = "" 258 | for message in messages: 259 | conversation += f'\n\n{message["role"].capitalize()}: {message["content"]}' 260 | 261 | prompt = f"{prompt_prefix}{conversation}\n\nAssistant:" 262 | 263 | response = self.anthropic.completions.create( 264 | model=model, 265 | max_tokens_to_sample=max_tokens_to_sample, 266 | prompt=prompt, 267 | temperature=temperature, 268 | ) 269 | 270 | output = response.completion.strip() 271 | 272 | print(output) 273 | 274 | if output.startswith("FUNCTION "): 275 | # It's trying to call a function, so we parse the function name and arguments 276 | function_call = output[len("FUNCTION "):] 277 | return {"function": function_call} 278 | 279 | else: 280 | return {"response": output, "function": None, "arguments": None} 281 | 282 | def describe_function_output(self, function_name, function_arguments, 283 | function_output, previous_messages): 284 | # Convert the function output and arguments to a string if they're not already 285 | if not isinstance(function_output, str): 286 | function_output = str(function_output) 287 | if not isinstance(function_arguments, str): 288 | function_arguments = str(function_arguments) 289 | 290 | # Add a new system message to the list of messages 291 | new_message = { 292 | "role": 293 | "AI", 294 | "content": 295 | f"The function `{function_name}` was called with the following arguments: `{function_arguments}`. It returned the following output: `{function_output}`. Tell the user what happened, in natural language. Don't include details about the code, just report back like a friend." 296 | } 297 | previous_messages.append(new_message) 298 | 299 | # Call the model again 300 | response = self.call(previous_messages) 301 | 302 | return response 303 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='anthropic_with_functions', 5 | version='0.1', 6 | packages=find_packages(), 7 | install_requires=[ 8 | 'anthropic', 9 | ], 10 | author='Matt Shumer', 11 | author_email='mattshumertech@gmail.com', 12 | description='A library to use the Anthropic Claude models with OpenAI-like Functions.', 13 | ) --------------------------------------------------------------------------------