Skip to content

Commit 2569fb3

Browse files
authored
Add Support for the New OpenAI Library (#12)
* Add tool helper class * Add more tests * Add example to readme * Fix black * Add changelog file and upgrade version
1 parent 8bcb91e commit 2569fb3

File tree

14 files changed

+685
-754
lines changed

14 files changed

+685
-754
lines changed

.github/workflows/test-application.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@ jobs:
3434
- name: Test Coverage
3535
run: |
3636
poetry run coverage run --source openai_function_calling -m pytest
37-
poetry run coverage report --fail-under=90 --show-missing
37+
poetry run coverage report --fail-under=95 --show-missing

CHANGELOG.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Release Notes
2+
3+
## 2.0.0
4+
5+
* Upgrade to the latest OpenAI version that changes from function calling to tools.
6+
* Add the `ToolHelpers` class.
7+
8+
## 1.2.0
9+
10+
* Increase compatible Python version range to include 3.9
11+
12+
## 1.1.0
13+
14+
* Adds the ability to automatically infer a function's definition along with it's parameters automatically from a function reference.
15+
16+
## 1.0.1
17+
18+
* Added the to_dict method back to the Function class and marked it as deprecated. It will be removed in future versions.
19+
20+
## 1.0.0
21+
22+
* Renamed to_dict to to_json_schema.
23+
* Added additional validation conditions when creating and converting Parameters and Functions to JSON schema.
24+
* Add ruff.toml file with additional checks and fixed all linting errors.
25+
* Added additional docstrings.
26+
* Added py.typed file for mypy.
27+
28+
## 0.6.0
29+
30+
* Add new parameter to Parameter class array_item_type to specify the type of array items if type is set to array
31+
32+
## 0.5.0
33+
34+
* Remove the openai dependency since it isn't required to use the package. It must now be installed separately.
35+
36+
## 0.4.0
37+
38+
* Fix bugs in the Parameter class related to required_parameters and add unit tests.
39+
40+
## 0.3.0
41+
42+
* Update the examples link in the README to point to GitHub instead of being relative.
43+
44+
## 0.2.0
45+
46+
* Update README to include details about setting the OpenAI API key and link to the examples
47+
48+
## 0.1.0
49+
50+
* Initial release

README.md

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Automatically infer your function name, description, and parameters given a refe
2323

2424
```python
2525
from typing import Any, Callable
26-
from openai_function_calling import FunctionInferer
26+
from openai_function_calling import FunctionInferrer
2727
import openai
2828
import json
2929

@@ -39,11 +39,11 @@ def get_tomorrows_weather(location: str, unit: str = "fahrenheit") -> str:
3939
return f"Tomorrow it will be rainy in {location} and 60 degrees {unit}."
4040

4141
# Infer the function definitions.
42-
get_current_weather_function = FunctionInferer.infer_from_function_reference(
42+
get_current_weather_function = FunctionInferrer.infer_from_function_reference(
4343
get_current_weather
4444
)
4545

46-
get_tomorrows_weather_function = FunctionInferer.infer_from_function_reference(
46+
get_tomorrows_weather_function = FunctionInferrer.infer_from_function_reference(
4747
get_tomorrows_weather
4848
)
4949

@@ -99,6 +99,45 @@ get_current_weather_function = Function(
9999
get_current_weather_function_schema = get_current_weather_function.to_json_schema()
100100
```
101101

102+
### Convert Functions to OpenAI Compatible JSON
103+
104+
```python
105+
from openai import OpenAI
106+
from openai.types.chat import (
107+
ChatCompletion,
108+
ChatCompletionUserMessageParam,
109+
)
110+
from openai_function_calling.tool_helpers import ToolHelpers
111+
112+
113+
# Define our functions.
114+
def get_current_weather(location: str, unit: str) -> str:
115+
"""Get the current weather and return a summary."""
116+
return f"It is currently sunny in {location} and 75 degrees {unit}."
117+
118+
119+
def get_tomorrows_weather(location: str, unit: str) -> str:
120+
"""Get tomorrow's weather and return a summary."""
121+
return f"It will be rainy tomorrow in {location} and around 65 degrees {unit}."
122+
123+
124+
openai_client = OpenAI()
125+
126+
# Send the query and our function context to OpenAI.
127+
response: ChatCompletion = openai_client.chat.completions.create(
128+
model="gpt-3.5-turbo-1106",
129+
messages=[
130+
ChatCompletionUserMessageParam(
131+
role="user", content="What's the weather in Boston MA?"
132+
),
133+
],
134+
tools=ToolHelpers.infer_from_function_refs(
135+
[get_current_weather, get_tomorrows_weather]
136+
),
137+
tool_choice="auto",
138+
)
139+
```
140+
102141
## Examples
103142

104143
To run the examples, set the environment variable `OPENAI_API_KEY` to your OpenAI API key. For example:

examples/weather_functions.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,15 @@
99
from typing import Any
1010

1111
import openai
12+
from openai.types.chat import (
13+
ChatCompletion,
14+
ChatCompletionMessage,
15+
ChatCompletionMessageToolCall,
16+
ChatCompletionUserMessageParam,
17+
)
1218

13-
from openai_function_calling import Function, FunctionDict, JsonSchemaType, Parameter
19+
from openai_function_calling import Function, JsonSchemaType, Parameter
20+
from openai_function_calling.tool_helpers import ToolHelpers
1421

1522

1623
# Define our functions.
@@ -44,41 +51,44 @@ def get_tomorrows_weather(location: str, unit: str) -> str:
4451
parameters=[location_parameter, unit_parameter],
4552
)
4653

47-
get_current_weather_function_dict: FunctionDict = (
48-
get_current_weather_function.to_json_schema()
49-
)
50-
get_tomorrows_weather_function_dict: FunctionDict = (
51-
get_tomorrows_weather_function.to_json_schema()
52-
)
53-
5454

5555
# Send the query and our function context to OpenAI.
56-
response: Any = openai.ChatCompletion.create(
56+
response: ChatCompletion = openai.chat.completions.create(
5757
model="gpt-3.5-turbo-0613",
5858
messages=[
59-
{
60-
"role": "user",
61-
"content": "What will the weather be tomorrow in Boston MA in celsius?",
62-
},
59+
ChatCompletionUserMessageParam(
60+
role="user",
61+
content="What's the weather tomorrow in Boston MA in fahrenheit?",
62+
),
6363
],
64-
functions=[get_current_weather_function_dict, get_tomorrows_weather_function_dict],
65-
function_call="auto", # Auto is the default.
64+
tools=ToolHelpers.from_functions(
65+
[
66+
get_current_weather_function,
67+
get_tomorrows_weather_function,
68+
]
69+
),
70+
tool_choice="auto", # Auto is the default.
6671
)
6772

68-
response_message = response["choices"][0]["message"]
73+
response_message: ChatCompletionMessage = response.choices[0].message
74+
6975

7076
# Check if GPT wants to call a function.
71-
if response_message.get("function_call"):
77+
if response_message.tool_calls is not None:
7278
# Call the function.
7379
available_functions: dict[str, Callable] = {
7480
"get_current_weather": get_current_weather,
7581
"get_tomorrows_weather": get_tomorrows_weather,
7682
}
7783

78-
function_name = response_message["function_call"]["name"]
79-
function_args = json.loads(response_message["function_call"]["arguments"])
80-
function_to_call: Callable = available_functions[function_name]
81-
function_response: Any = function_to_call(**function_args)
84+
tool_call: ChatCompletionMessageToolCall = response_message.tool_calls[0]
85+
function = tool_call.function
86+
arguments: str = function.arguments
87+
function_name: str = tool_call.function.name
88+
89+
function_args: dict = json.loads(arguments)
90+
function_reference: Callable = available_functions[function_name]
91+
function_response: Any = function_reference(**function_args)
8292

8393
print(f"Called {function_name} with response: '{function_response!s}'.")
8494
else:

examples/weather_functions_infer.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,17 @@
88
from collections.abc import Callable
99
from typing import Any
1010

11-
import openai
11+
from openai import OpenAI
12+
from openai.types.chat import (
13+
ChatCompletion,
14+
ChatCompletionMessage,
15+
ChatCompletionUserMessageParam,
16+
)
17+
from openai.types.chat.chat_completion_message_tool_call import (
18+
ChatCompletionMessageToolCall,
19+
)
1220

13-
from openai_function_calling import FunctionInferer
21+
from openai_function_calling.tool_helpers import ToolHelpers
1422

1523

1624
# Define our functions.
@@ -24,42 +32,40 @@ def get_tomorrows_weather(location: str, unit: str) -> str:
2432
return f"It will be rainy tomorrow in {location} and around 65 degrees {unit}."
2533

2634

27-
get_current_weather_schema = FunctionInferer.infer_from_function_reference(
28-
get_current_weather,
29-
).to_json_schema()
30-
31-
get_tomorrows_weather_schema = FunctionInferer.infer_from_function_reference(
32-
get_tomorrows_weather,
33-
).to_json_schema()
34-
35+
openai_client = OpenAI()
3536

3637
# Send the query and our function context to OpenAI.
37-
response: Any = openai.ChatCompletion.create(
38-
model="gpt-3.5-turbo-0613",
38+
response: ChatCompletion = openai_client.chat.completions.create(
39+
model="gpt-3.5-turbo-1106",
3940
messages=[
40-
{
41-
"role": "user",
42-
"content": "What will the weather be tomorrow in Boston MA in celsius?",
43-
},
41+
ChatCompletionUserMessageParam(
42+
role="user", content="What's the weather in Boston MA?"
43+
),
4444
],
45-
functions=[get_current_weather_schema, get_tomorrows_weather_schema],
46-
function_call="auto", # Auto is the default.
45+
tools=ToolHelpers.infer_from_function_refs(
46+
[get_current_weather, get_tomorrows_weather]
47+
),
48+
tool_choice="auto",
4749
)
4850

49-
response_message = response["choices"][0]["message"]
51+
response_message: ChatCompletionMessage = response.choices[0].message
5052

5153
# Check if GPT wants to call a function.
52-
if response_message.get("function_call"):
54+
if response_message.tool_calls is not None:
5355
# Call the function.
5456
available_functions: dict[str, Callable] = {
5557
"get_current_weather": get_current_weather,
5658
"get_tomorrows_weather": get_tomorrows_weather,
5759
}
5860

59-
function_name = response_message["function_call"]["name"]
60-
function_args = json.loads(response_message["function_call"]["arguments"])
61-
function_to_call: Callable = available_functions[function_name]
62-
function_response: Any = function_to_call(**function_args)
61+
tool_call: ChatCompletionMessageToolCall = response_message.tool_calls[0]
62+
function = tool_call.function
63+
arguments: str = function.arguments
64+
function_name: str = tool_call.function.name
65+
66+
function_args: dict = json.loads(arguments)
67+
function_reference: Callable = available_functions[function_name]
68+
function_response: Any = function_reference(**function_args)
6369

6470
print(f"Called {function_name} with response: '{function_response!s}'.")
6571
else:
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
"""OpenAI Function Calling Package."""
22

3-
from openai_function_calling.function import Function, FunctionDict, ParametersDict
4-
from openai_function_calling.function_inferer import FunctionInferer
3+
from openai_function_calling.function import Function, FunctionDict
4+
from openai_function_calling.function_inferrer import FunctionInferrer
55
from openai_function_calling.json_schema_type import JsonSchemaType
66
from openai_function_calling.parameter import Parameter, ParameterDict
77

88
__all__: list[str] = [
99
"Function",
1010
"FunctionDict",
11-
"ParametersDict",
1211
"Parameter",
1312
"ParameterDict",
1413
"JsonSchemaType",
15-
"FunctionInferer",
14+
"FunctionInferrer",
1615
]

openai_function_calling/function.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
"""Define the Function class and related objects."""
22

3-
43
from __future__ import annotations
54

65
from typing import TYPE_CHECKING, TypedDict
76

8-
from typing_extensions import NotRequired, deprecated
7+
from typing_extensions import NotRequired, deprecated # type: ignore[attr-defined]
98

109
from openai_function_calling.json_schema_type import JsonSchemaType
1110

openai_function_calling/function_inferer.py renamed to openai_function_calling/function_inferrer.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Function inferer class definition."""
1+
"""Function inferrer class definition."""
22

33
import inspect
44
from collections.abc import Callable
@@ -12,7 +12,7 @@
1212
from openai_function_calling.parameter import Parameter
1313

1414

15-
class FunctionInferer:
15+
class FunctionInferrer:
1616
"""Class to help inferring a function definition from a reference."""
1717

1818
@staticmethod
@@ -27,13 +27,13 @@ def infer_from_function_reference(function_reference: Callable) -> Function:
2727
Return:
2828
An instance of Function with inferred values.
2929
"""
30-
inferred_from_annotations: Function = FunctionInferer._infer_from_annotations(
30+
inferred_from_annotations: Function = FunctionInferrer._infer_from_annotations(
3131
function_reference
3232
)
33-
inferred_from_docstring: Function = FunctionInferer._infer_from_docstring(
33+
inferred_from_docstring: Function = FunctionInferrer._infer_from_docstring(
3434
function_reference
3535
)
36-
inferred_from_inspection: Function = FunctionInferer._infer_from_inspection(
36+
inferred_from_inspection: Function = FunctionInferrer._infer_from_inspection(
3737
function_reference
3838
)
3939

openai_function_calling/parameter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from openai_function_calling.json_schema_type import JsonSchemaType
88

9-
if TYPE_CHECKING:
9+
if TYPE_CHECKING: # pragma: no cover
1010
from typing_extensions import NotRequired
1111

1212

0 commit comments

Comments
 (0)