Skip to content
This repository was archived by the owner on Jun 6, 2024. It is now read-only.

Commit c24e78b

Browse files
Eduardo-TGrogdunndavidb-e4s
authored
Added "function" capabilities (#298)
* Initial commit * Updated Readme * Added the ability to publish to the local repository. * Stylized the README a bit more. * Fixed typo * Fixed visibility of variables in example * Improved the usage of functions with an executor. * Reverted changes. * Added information about functions to the README.md * Change reversed. * Added some helper method and cleaned the example a little bit. * 🐛 custom serialization of function_call parameter should be an object or a string * Fixed example and added static instantiation method * Added null check * Set project version to SNAPSHOT and fixed vulnerable dependency * Allowing objectmapper to be overriden in order to supply a custom object mapper which is required when using the library in Kotlin * Updating the constructor to use the setter to be consistant * Added usage of streams with functions * Removed .DS_Store files and updated .gitignore * Small fixes and improvements and added tests * Simplified example a little and updated README --------- Co-authored-by: Lorenzo Caenazzo <[email protected]> Co-authored-by: David Billings <[email protected]>
1 parent 3a5bc28 commit c24e78b

24 files changed

+992
-12
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,6 @@ build
3838
# Ignore any files in /bin and /obj Folders
3939
**/bin/*
4040
**/obj/*
41+
42+
# Ignore the macOS folder attribute file
43+
**/.DS_Store

README.md

+81-1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,70 @@ OpenAiApi api = retrofit.create(OpenAiApi.class);
9595
OpenAiService service = new OpenAiService(api);
9696
```
9797

98+
### Functions
99+
You can create your functions and define their executors easily using the ChatFunction class, along with any of your custom classes that will serve to define their available parameters. You can also process the functions with ease, with the help of an executor called FunctionExecutor.
100+
101+
First we declare our function parameters:
102+
```java
103+
public class Weather {
104+
@JsonPropertyDescription("City and state, for example: León, Guanajuato")
105+
public String location;
106+
@JsonPropertyDescription("The temperature unit, can be 'celsius' or 'fahrenheit'")
107+
@JsonProperty(required = true)
108+
public WeatherUnit unit;
109+
}
110+
public enum WeatherUnit {
111+
CELSIUS, FAHRENHEIT;
112+
}
113+
public static class WeatherResponse {
114+
public String location;
115+
public WeatherUnit unit;
116+
public int temperature;
117+
public String description;
118+
119+
// constructor
120+
}
121+
```
122+
123+
Next, we declare the function itself and associate it with an executor, in this example we will fake a response from some API:
124+
```java
125+
ChatFunction.builder()
126+
.name("get_weather")
127+
.description("Get the current weather of a location")
128+
.executor(Weather.class, w -> new WeatherResponse(w.location, w.unit, new Random().nextInt(50), "sunny"))
129+
.build()
130+
```
131+
132+
Then, we employ the FunctionExecutor object from the 'service' module to assist with execution and transformation into an object that is ready for the conversation:
133+
```java
134+
List<ChatFunction> functionList = // list with functions
135+
FunctionExecutor functionExecutor = new FunctionExecutor(functionList);
136+
137+
List<ChatMessage> messages = new ArrayList<>();
138+
ChatMessage userMessage = new ChatMessage(ChatMessageRole.USER.value(), "Tell me the weather in Barcelona.");
139+
messages.add(userMessage);
140+
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
141+
.builder()
142+
.model("gpt-3.5-turbo-0613")
143+
.messages(messages)
144+
.functions(functionExecutor.getFunctions())
145+
.functionCall(new ChatCompletionRequestFunctionCall("auto"))
146+
.maxTokens(256)
147+
.build();
148+
149+
ChatMessage responseMessage = service.createChatCompletion(chatCompletionRequest).getChoices().get(0).getMessage();
150+
ChatFunctionCall functionCall = responseMessage.getFunctionCall(); // might be null, but in this case it is certainly a call to our 'get_weather' function.
151+
152+
ChatMessage functionResponseMessage = functionExecutor.executeAndConvertToMessageHandlingExceptions(functionCall);
153+
messages.add(response);
154+
```
155+
> **Note:** The `FunctionExecutor` class is part of the 'service' module.
156+
157+
You can also create your own function executor. The return object of `ChatFunctionCall.getArguments()` is a JsonNode for simplicity and should be able to help you with that.
158+
159+
For a more in-depth look, refer to a conversational example that employs functions in: [OpenAiApiFunctionsExample.java](example/src/main/java/example/OpenAiApiFunctionsExample.java).
160+
Or for an example using functions and stream: [OpenAiApiFunctionsWithStreamExample.java](example/src/main/java/example/OpenAiApiFunctionsWithStreamExample.java)
161+
98162
### Streaming thread shutdown
99163
If you want to shut down your process immediately after streaming responses, call `OpenAiService.shutdownExecutor()`.
100164
This is not necessary for non-streaming calls.
@@ -103,14 +167,30 @@ This is not necessary for non-streaming calls.
103167
All the [example](example/src/main/java/example/OpenAiApiExample.java) project requires is your OpenAI api token
104168
```bash
105169
export OPENAI_TOKEN="sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
106-
./gradlew example:run
170+
```
171+
You can try all the capabilities of this project using:
172+
```bash
173+
./gradlew runExampleOne
174+
```
175+
And you can also try the new capability of using functions:
176+
```bash
177+
./gradlew runExampleTwo
178+
```
179+
Or functions with 'stream' mode enabled:
180+
```bash
181+
./gradlew runExampleThree
107182
```
108183

109184
## FAQ
110185
### Does this support GPT-4?
111186
Yes! GPT-4 uses the ChatCompletion Api, and you can see the latest model options [here](https://platform.openai.com/docs/models/gpt-4).
112187
GPT-4 is currently in a limited beta (as of 4/1/23), so make sure you have access before trying to use it.
113188

189+
### Does this support functions?
190+
Absolutely! It is very easy to use your own functions without worrying about doing the dirty work.
191+
As mentioned above, you can refer to [OpenAiApiFunctionsExample.java](example/src/main/java/example/OpenAiApiFunctionsExample.java) or
192+
[OpenAiApiFunctionsWithStreamExample.java](example/src/main/java/example/OpenAiApiFunctionsWithStreamExample.java) projects for an example.
193+
114194
### Why am I getting connection timeouts?
115195
Make sure that OpenAI is available in your country.
116196

api/build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ apply plugin: 'java-library'
22
apply plugin: "com.vanniktech.maven.publish"
33

44
dependencies {
5-
implementation libs.jacksoAnnotations
5+
api libs.jacksonAnnotations
6+
api libs.jacksonDatabind
67
compileOnly libs.lombok
78
annotationProcessor libs.lombok
89

api/src/main/java/com/theokanning/openai/completion/chat/ChatCompletionRequest.java

+24
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,28 @@ public class ChatCompletionRequest {
9494
* A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse.
9595
*/
9696
String user;
97+
98+
/**
99+
* A list of the available functions.
100+
*/
101+
List<ChatFunction> functions;
102+
103+
/**
104+
* Controls how the model responds to function calls, as specified in the <a href="https://platform.openai.com/docs/api-reference/chat/create#chat/create-function_call">OpenAI documentation</a>.
105+
*/
106+
@JsonProperty("function_call")
107+
ChatCompletionRequestFunctionCall functionCall;
108+
109+
@Data
110+
@Builder
111+
@AllArgsConstructor
112+
@NoArgsConstructor
113+
public static class ChatCompletionRequestFunctionCall {
114+
String name;
115+
116+
public static ChatCompletionRequestFunctionCall of(String name) {
117+
return new ChatCompletionRequestFunctionCall(name);
118+
}
119+
120+
}
97121
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.theokanning.openai.completion.chat;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import lombok.Data;
6+
import lombok.NonNull;
7+
8+
import java.util.function.Function;
9+
10+
@Data
11+
public class ChatFunction {
12+
13+
@NonNull
14+
private String name;
15+
private String description;
16+
@JsonProperty("parameters")
17+
private Class<?> parametersClass;
18+
19+
@JsonIgnore
20+
private Function<Object, Object> executor;
21+
22+
public static Builder builder() {
23+
return new Builder();
24+
}
25+
26+
public static class Builder {
27+
private String name;
28+
private String description;
29+
private Class<?> parameters;
30+
private Function<Object, Object> executor;
31+
32+
public Builder name(String name) {
33+
this.name = name;
34+
return this;
35+
}
36+
37+
public Builder description(String description) {
38+
this.description = description;
39+
return this;
40+
}
41+
42+
public <T> Builder executor(Class<T> requestClass, Function<T, Object> executor) {
43+
this.parameters = requestClass;
44+
this.executor = (Function<Object, Object>) executor;
45+
return this;
46+
}
47+
48+
public ChatFunction build() {
49+
ChatFunction chatFunction = new ChatFunction(name);
50+
chatFunction.setDescription(description);
51+
chatFunction.setParametersClass(parameters);
52+
chatFunction.setExecutor(executor);
53+
return chatFunction;
54+
}
55+
}
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.theokanning.openai.completion.chat;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Data;
6+
import lombok.NoArgsConstructor;
7+
8+
@Data
9+
@AllArgsConstructor
10+
@NoArgsConstructor
11+
public class ChatFunctionCall {
12+
13+
/**
14+
* The name of the function being called
15+
*/
16+
String name;
17+
18+
/**
19+
* The arguments of the call produced by the model, represented as a JsonNode for easy manipulation.
20+
*/
21+
JsonNode arguments;
22+
23+
}

api/src/main/java/com/theokanning/openai/completion/chat/ChatMessage.java

+18-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.theokanning.openai.completion.chat;
22

3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
35
import lombok.*;
46

57
/**
@@ -19,13 +21,27 @@
1921
public class ChatMessage {
2022

2123
/**
22-
* Must be either 'system', 'user', or 'assistant'.<br>
24+
* Must be either 'system', 'user', 'assistant' or 'function'.<br>
2325
* You may use {@link ChatMessageRole} enum.
2426
*/
2527
@NonNull
2628
String role;
27-
@NonNull
29+
@JsonInclude() // content should always exist in the call, even if it is null
2830
String content;
2931
//name is optional, The name of the author of this message. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters.
3032
String name;
33+
@JsonProperty("function_call")
34+
ChatFunctionCall functionCall;
35+
36+
public ChatMessage(String role, String content) {
37+
this.role = role;
38+
this.content = content;
39+
}
40+
41+
public ChatMessage(String role, String content, String name) {
42+
this.role = role;
43+
this.content = content;
44+
this.name = name;
45+
}
46+
3147
}

api/src/main/java/com/theokanning/openai/completion/chat/ChatMessageRole.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
public enum ChatMessageRole {
77
SYSTEM("system"),
88
USER("user"),
9-
ASSISTANT("assistant");
9+
ASSISTANT("assistant"),
10+
FUNCTION("function");
1011

1112
private final String value;
1213

example/build.gradle

+20
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,24 @@ application {
77

88
dependencies {
99
implementation project(":service")
10+
}
11+
12+
task runExampleOne(type: JavaExec) {
13+
mainClass.set('example.OpenAiApiExample')
14+
classpath = sourceSets.main.runtimeClasspath
15+
args = []
16+
}
17+
18+
task runExampleTwo(type: JavaExec) {
19+
mainClass.set('example.OpenAiApiFunctionsExample')
20+
classpath = sourceSets.main.runtimeClasspath
21+
args = []
22+
standardInput = System.in
23+
}
24+
25+
task runExampleThree(type: JavaExec) {
26+
mainClass.set('example.OpenAiApiFunctionsWIthStreamExample')
27+
classpath = sourceSets.main.runtimeClasspath
28+
args = []
29+
standardInput = System.in
1030
}

0 commit comments

Comments
 (0)