Overview
The hidden power behind chat.register(ƒ)
is the Function Registry. It lets you register typed functions quickly and easily so they can be called on behalf of chat models.
from chatlab import FunctionRegistry
def f(x: float):
'''Multiply x by 2.'''
return x*2
registry = FunctionRegistry()
registry.register(f)
await registry.call("f", "{\"x\": 4}")
8
Functions can be registered by:
- Passing a typed function in directly
- Passing the function and the Pydantic model that describes its parameters
- Passing the function and a JSON schema that describes its parameters
Registering Typed Functions
Not all function parameters can be accurately determined. For those cases it's best to use a Pydantic model or JSON schema.
If you're adventurous, make a PR to chatlab to improve the type inference!
No Parameters
import random
def flip_a_coin():
'''Flip a coin.'''
return random.choice(["heads", "tails"])
registry.register(flip_a_coin)
{
'name': 'flip_a_coin',
'description': 'Flip a coin.',
'parameters': {
'type': 'object',
'properties': {},
'required': []
}
}
Multiple Parameters
def add(x: float, y: float):
'''Add x and y.'''
return x + y
registry.register(add)
{
'name': 'add',
'description': 'Add x and y.',
'parameters': {
'type': 'object',
'properties': {
'x': {'type': 'number'},
'y': {'type': 'number'}
},
'required': ['x', 'y']
}
}
Optional Parameters
def add(x: float, y: float = 1):
'''Add x and y.'''
return x + y
registry.register(add)
{
'name': 'add',
'description': 'Add x and y.',
'parameters': {
'type': 'object',
'properties': {
'x': {'type': 'number'},
'y': {'type': 'number'}
},
'required': ['x']
}
}
Registering Functions with Pydantic Models
Pydantic Models give you all the benefits above and much more:
- Default values
- Descriptions on parameters
- Deeper type validation
from chatlab import FunctionRegistry
from pydantic import BaseModel
registry = FunctionRegistry()
class CompoundInterestModel(BaseModel):
principal: float
rate: float
times_compounded: int
years: float
@registry.register(parameter_schema=CompoundInterestModel)
def compound_interest(principal: float, rate: float, times_compounded: int, years: float) -> float:
"""
Calculates the future value of an investment using compound interest.
:param principal: Initial investment amount
:param rate: Annual interest rate (as a decimal)
:param times_compounded: Number of times interest is compounded per year
:param years: Number of years the money is invested
:return: Future value of the investment
"""
return principal * (1 + rate / times_compounded) ** (times_compounded * years)
await registry.call(
"compound_interest",
CompoundInterestModel(principal=1000, rate=0.05, times_compounded=12, years=5).json()
)
1283.3586785035118;
Additional Descriptions
Using Field
, you can add descriptions to your parameters.
from pydantic import BaseModel, Field
class Add(BaseModel):
x: float = Field(..., description="The first number to add.")
y: float = Field(1, description="The second number to add.")
def add(x: float, y: float = 1):
'''Add x and y.'''
return x + y
registry.register(add, Add)
{
'name': 'add',
'description': 'Add x and y.',
'parameters': {
'title': 'Add',
'type': 'object',
'properties': {
'x': {
'title': 'X',
'description': 'The first number to add.',
'type': 'number'
},
'y': {
'title': 'Y',
'default': 1,
'description': 'The second number to add.',
'type': 'number'
}
},
'required': ['x']
}
}
Registering Functions with JSON Schemas
If you don't want to use Pydantic, you can write pure JSON schemas to describe your functions.
def add(x: float, y: float = 1):
'''Add x and y.'''
return x + y
registry.register(add, {
"title": "Add",
"type": "object",
"properties": {
"x": {"type": "number"},
"y": {"type": "number", "default": 1}
},
"required": ["x"]
})
Direct OpenAI API Calls with the Function Registry
When calling OpenAI's Chat Completion endpoint you can tell it what kind of functions you have directly:
import openai
openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=[
{"role": "user", "content": "Multiply 1024 by 2"},
],
functions=[
{
'name': 'f',
'description': 'Multiply x by 2.',
'parameters': {
'type': 'object',
'properties': {'x': {'type': 'number'}},
'required': ['x']
}
}
],
function_call="auto"
)
Function registries expose an api_manifest()
method that returns a dictionary for passing directly to OpenAI's Chat Completion endpoint. This is useful if you have a lot of functions to register.
import random
def secret():
'''Returns a secret'''
return random.randint(1, 10)
registry = FunctionRegistry()
registry.register(secret)
registry.api_manifest()
{
'functions': [
{
'name': 'secret',
'description': 'Returns a secret',
'parameters': {
'type': 'object',
'properties': {},
'required': []
}
}
],
'function_call': 'auto'
}
This can be passed directly to ChatCompletion.create()
:
resp = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=[
{"role": "user", "content": "Multiply 1024 by 2"},
],
**registry.api_manifest(),
).choices[0]
resp
<OpenAIObject at 0x1198aef90> JSON: {
"finish_reason": "function_call",
"index": 0,
"message": {
"content": null,
"function_call": {
"arguments": "{}",
"name": "secret"
},
"role": "assistant"
}
}
From there, the response's message.function_call
can be passed to registry.call()
to execute the function:
value = await registry.call(**resp.message.function_call)
value
Assistant 10