From 4feb4b33eeb1756e46084a4db9230b279af1a480 Mon Sep 17 00:00:00 2001 From: Maximilian Winter Date: Tue, 16 Jan 2024 18:41:42 +0100 Subject: [PATCH] examples : add complete parallel function calling example (#4974) --- .../pydantic-models-to-grammar-examples.py | 123 +++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/examples/pydantic-models-to-grammar-examples.py b/examples/pydantic-models-to-grammar-examples.py index a8a4919cf..cbf376652 100644 --- a/examples/pydantic-models-to-grammar-examples.py +++ b/examples/pydantic-models-to-grammar-examples.py @@ -1,5 +1,5 @@ # Function calling example using pydantic models. - +import datetime import json from enum import Enum from typing import Union, Optional @@ -8,7 +8,8 @@ import requests from pydantic import BaseModel, Field import importlib -from pydantic_models_to_grammar import generate_gbnf_grammar_and_documentation +from pydantic_models_to_grammar import generate_gbnf_grammar_and_documentation, convert_dictionary_to_pydantic_model, add_run_method_to_dynamic_model, create_dynamic_model_from_function + # Function to get completion on the llama.cpp server with grammar. def create_completion(prompt, grammar): @@ -134,3 +135,121 @@ text = create_completion(prompt=prompt, grammar=gbnf_grammar) json_data = json.loads(text) print(Book(**json_data)) +# An example for parallel function calling with a Python function, a pydantic function model and an OpenAI like function definition. + +def get_current_datetime(output_format: Optional[str] = None): + """ + Get the current date and time in the given format. + Args: + output_format: formatting string for the date and time, defaults to '%Y-%m-%d %H:%M:%S' + """ + if output_format is None: + output_format = '%Y-%m-%d %H:%M:%S' + return datetime.datetime.now().strftime(output_format) + + +# Enum for the calculator tool. +class MathOperation(Enum): + ADD = "add" + SUBTRACT = "subtract" + MULTIPLY = "multiply" + DIVIDE = "divide" + + + +# Simple pydantic calculator tool for the agent that can add, subtract, multiply, and divide. Docstring and description of fields will be used in system prompt. +class Calculator(BaseModel): + """ + Perform a math operation on two numbers. + """ + number_one: Union[int, float] = Field(..., description="First number.") + operation: MathOperation = Field(..., description="Math operation to perform.") + number_two: Union[int, float] = Field(..., description="Second number.") + + def run(self): + if self.operation == MathOperation.ADD: + return self.number_one + self.number_two + elif self.operation == MathOperation.SUBTRACT: + return self.number_one - self.number_two + elif self.operation == MathOperation.MULTIPLY: + return self.number_one * self.number_two + elif self.operation == MathOperation.DIVIDE: + return self.number_one / self.number_two + else: + raise ValueError("Unknown operation.") + + +# Example function to get the weather +def get_current_weather(location, unit): + """Get the current weather in a given location""" + if "London" in location: + return json.dumps({"location": "London", "temperature": "42", "unit": unit.value}) + elif "New York" in location: + return json.dumps({"location": "New York", "temperature": "24", "unit": unit.value}) + elif "North Pole" in location: + return json.dumps({"location": "North Pole", "temperature": "-42", "unit": unit.value}) + else: + return json.dumps({"location": location, "temperature": "unknown"}) + + +# Here is a function definition in OpenAI style +current_weather_tool = { + "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"], + }, + }, +} + +# Convert OpenAI function definition into pydantic model +current_weather_tool_model = convert_dictionary_to_pydantic_model(current_weather_tool) +# Add the actual function to a pydantic model +current_weather_tool_model = add_run_method_to_dynamic_model(current_weather_tool_model, get_current_weather) + +# Convert normal Python function to a pydantic model +current_datetime_model = create_dynamic_model_from_function(get_current_datetime) + +tool_list = [SendMessageToUser, Calculator, current_datetime_model, current_weather_tool_model] + + +gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation( + pydantic_model_list=tool_list, outer_object_name="function", + outer_object_content="params", model_prefix="Function", fields_prefix="Parameters", list_of_outputs=True) + +system_message = "You are an advanced AI assistant. You are interacting with the user and with your environment by calling functions. You call functions by writing JSON objects, which represent specific function calls.\nBelow is a list of your available function calls:\n\n" + documentation + + +text = """Get the date and time, get the current weather in celsius in London and solve the following calculation: 42 * 42""" +prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{text}<|im_end|>\n<|im_start|>assistant" + +text = create_completion(prompt=prompt, grammar=gbnf_grammar) + +json_data = json.loads(text) + +print(json_data) +# Should output something like this: +# [{'function': 'get_current_datetime', 'params': {'output_format': '%Y-%m-%d %H:%M:%S'}}, {'function': 'get_current_weather', 'params': {'location': 'London', 'unit': 'celsius'}}, {'function': 'Calculator', 'params': {'number_one': 42, 'operation': 'multiply', 'number_two': 42}}] + + +for call in json_data: + if call["function"] == "Calculator": + print(Calculator(**call["params"]).run()) + elif call["function"] == "get_current_datetime": + print(current_datetime_model(**call["params"]).run()) + elif call["function"] == "get_current_weather": + print(current_weather_tool_model(**call["params"]).run()) +# Should output something like this: +# 2024-01-14 13:36:06 +# {"location": "London", "temperature": "42", "unit": "celsius"} +# 1764