Skip to content

Commit 75b85c7

Browse files
committed
get tool calls from run steps
1 parent 1a1f0a5 commit 75b85c7

20 files changed

+487
-48
lines changed

examples/src/main/java/chat/ChatCompletion.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package chat;
22

3+
import com.cjcrafter.openai.Models;
34
import com.cjcrafter.openai.OpenAI;
45
import com.cjcrafter.openai.chat.ChatMessage;
56
import com.cjcrafter.openai.chat.ChatRequest;
@@ -29,7 +30,7 @@ public static void main(String[] args) {
2930

3031
// Here you can change the model's settings, add tools, and more.
3132
ChatRequest request = ChatRequest.builder()
32-
.model("gpt-3.5-turbo")
33+
.model(Models.Chat.GPT_3_5_TURBO)
3334
.messages(messages)
3435
.build();
3536

examples/src/main/kotlin/assistant/Thread.kt

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,36 @@ fun main() {
1616
val choice = readln().toInt()
1717
val assistant = assistants.data[choice]
1818

19+
// We have to create a new thread. We'll save this thread, so we can add
20+
// user messages and get responses later.
1921
val thread = openai.threads.create()
20-
openai.threads.messages(thread).create {
21-
role(ThreadUser.USER)
22-
content("Hi! I am using the threads API right now through Java!")
23-
}
24-
25-
var run = openai.threads.runs(thread).create {
26-
assistant(assistant)
27-
//instructions("You can override instructions, model, etc.")
28-
}
2922

30-
// This is a known limitation in OpenAI, and they are working to address
31-
// this so that we can easily stream a response without nonsense like this.
32-
while (!run.status.isTerminal) {
33-
Thread.sleep(2500)
34-
run = openai.threads.runs(thread).retrieve(run)
23+
while (true) {
24+
25+
// Handle user input
26+
println("Type your input below: ")
27+
val input = readln()
28+
openai.threads.messages(thread).create {
29+
role(ThreadUser.USER)
30+
content(input)
31+
32+
// You can also add files and metadata to the message
33+
//addFile(file)
34+
//addMetadata("key", "value")
35+
}
36+
37+
// After adding a message to the thread, we have to "run" the thread
38+
var run = openai.threads.runs(thread).create {
39+
assistant(assistant)
40+
//instructions("You can override instructions, model, etc.")
41+
}
42+
43+
// This is a known limitation in OpenAI, and they are working to address
44+
// this so that we can easily stream a response without nonsense like this.
45+
while (!run.status.isTerminal) {
46+
Thread.sleep(2500)
47+
run = openai.threads.runs(thread).retrieve(run)
48+
}
3549
}
3650

37-
println(run)
38-
39-
// Cleanup... Optional
40-
//openai.threads.delete(thread)
4151
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.cjcrafter.openai.chat.tool
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
import com.fasterxml.jackson.annotation.JsonSubTypes
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo
6+
7+
/**
8+
* Represents the data for a [Tool.Type.CODE_INTERPRETER] tool call. This
9+
* contains the code that was run by the code interpreter, and the outputs
10+
* of the code interpreter.
11+
*
12+
* @property input The code that was run by the code interpreter
13+
* @property outputs The data output by the code interpreter
14+
*/
15+
data class CodeInterpreter(
16+
@JsonProperty(required = true) val input: String,
17+
@JsonProperty(required = true) val outputs: List<Output>
18+
) {
19+
20+
/**
21+
* A sealed class that represents the output of 1 [CodeInterpreter] step.
22+
*/
23+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
24+
@JsonSubTypes(
25+
JsonSubTypes.Type(LogOutput::class, name = "logs"),
26+
JsonSubTypes.Type(ImageOutput::class, name = "image"),
27+
)
28+
sealed class Output {
29+
30+
/**
31+
* The type of the output.
32+
*/
33+
abstract val type: Type
34+
35+
enum class Type {
36+
37+
/**
38+
* When the code interpreter outputs text.
39+
*/
40+
@JsonProperty("logs")
41+
LOGS,
42+
43+
/**
44+
* When the code interpreter outputs an image file.
45+
*/
46+
@JsonProperty("image")
47+
IMAGE,
48+
}
49+
}
50+
51+
/**
52+
* Represents an [Output.Type.LOGS] output. This only holds the text that
53+
* was outputted by the code interpreter.
54+
*
55+
* @property text The text output
56+
*/
57+
data class LogOutput(
58+
val text: String,
59+
) : Output() {
60+
override val type: Type = Type.LOGS
61+
}
62+
63+
/**
64+
* Represents an [Output.Type.IMAGE] output. This only holds the ID of the
65+
* generated image file. You can use this ID to retrieve the image file.
66+
*
67+
* @property image The image output
68+
* @see com.cjcrafter.openai.files.FileHandler.retrieve
69+
*/
70+
data class ImageOutput(
71+
val image: Image,
72+
) : Output() {
73+
override val type: Type = Type.IMAGE
74+
75+
/**
76+
* Holds data about the image output by the code interpreter.
77+
*
78+
* @property fileId The unique ID of the output file, which can be used to retrieve the file
79+
*/
80+
data class Image(
81+
@JsonProperty("file_id", required = true) val fileId: String,
82+
)
83+
}
84+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.cjcrafter.openai.chat.tool
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
5+
/**
6+
* Represents a tool call by an [com.cjcrafter.openai.assistants.Assistant]
7+
* to a code interpreter.
8+
*
9+
* @property id The unique id of this tool call
10+
* @property codeInterpreter The details about the input and output
11+
*/
12+
data class CodeInterpreterToolCall(
13+
@JsonProperty(required = true) override val id: String,
14+
@JsonProperty("code_interpreter", required = true) val codeInterpreter: CodeInterpreter,
15+
) : ToolCall() {
16+
override val type = Tool.Type.CODE_INTERPRETER
17+
18+
}

src/main/kotlin/com/cjcrafter/openai/chat/tool/FunctionCall.kt

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,50 @@
11
package com.cjcrafter.openai.chat.tool
22

3+
import com.cjcrafter.openai.OpenAI
4+
import com.cjcrafter.openai.assistants.Assistant
35
import com.cjcrafter.openai.exception.HallucinationException
46
import com.fasterxml.jackson.core.JsonProcessingException
57
import com.fasterxml.jackson.databind.JsonNode
68
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
79
import com.fasterxml.jackson.module.kotlin.readValue
8-
import org.jetbrains.annotations.ApiStatus
910

1011
/**
11-
* Represents a function call by ChatGPT. When ChatGPT calls a function, you
12-
* should be parsing the arguments, calling some method (getting current weather
13-
* at a location, getting a stock price, modifying a database, etc.), and then
14-
* replying to ChatGPT with the result.
12+
* Represents a function call by either a chat completion, or an [Assistant].
1513
*
16-
* ChatGPT may *hallucinate*, or make up, function calls. To handle hallucinations,
14+
* When a function call is made, you MUST respond with the result of the function.
15+
* This means that you should parse the arguments, call some function (an API call,
16+
* getting current weather, getting a stock price, modifying a database, etc.), and
17+
* sending the result of that function back.
18+
*
19+
* For chat completions ([OpenAI.createChatCompletion]), you should send the result
20+
* of the function as a [com.cjcrafter.openai.chat.ChatMessage] with the
21+
* corresponding [ToolCall.id] as the function id.
22+
*
23+
* For [Assistant]s, you should use [com.cjcrafter.openai.threads.runs.RunHandler.submitToolOutputs]
24+
* with the corresponding [ToolCall.id] as the tool call id. For [Assistant]s,
25+
* it is important to submit tool outputs _within a timely manner_, usually
26+
* within 10 minutes of starting a [com.cjcrafter.openai.threads.runs.Run].
27+
* Otherwise, the [com.cjcrafter.openai.threads.runs.Run] will expire, and you
28+
* will not be able to submit your tool call.
29+
*
30+
* ChatGPT may _hallucinate_, or make up, function calls. To handle hallucinations,
1731
* we have provided [tryParseArguments].
1832
*
1933
* @property name The name of the function which was called
2034
* @property arguments The raw json representation of the arguments
35+
* @property output The result of the function call if it has been set, only used for [Assistant]s. You should not set this.
2136
*/
2237
data class FunctionCall(
2338
var name: String,
2439
var arguments: String,
40+
var output: String? = null,
2541
) {
42+
43+
/**
44+
* Used internally to update the function call. This is used when the chat
45+
* completion is streamed via [OpenAI.streamChatCompletion]. This is not
46+
* used by [Assistant]s.
47+
*/
2648
internal fun update(delta: FunctionCallDelta) {
2749
// The only field that updates is arguments
2850
arguments += delta.arguments
@@ -44,7 +66,6 @@ data class FunctionCall(
4466
* @param tools The list of tools that ChatGPT has access to, or null to skip advanced checking.
4567
* @return The parsed arguments.
4668
*/
47-
4869
@JvmOverloads
4970
@Throws(HallucinationException::class)
5071
fun tryParseArguments(tools: List<Tool>? = null): Map<String, JsonNode> {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.cjcrafter.openai.chat.tool
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
5+
/**
6+
* Represents a tool call for [Tool.Type.FUNCTION].
7+
*
8+
* @property id The unique id of the tool call. You should use this id in your reply.
9+
* @property function The details about which function was called, parameters, etc.
10+
*/
11+
data class FunctionToolCall(
12+
@JsonProperty(required = true) override val id: String,
13+
@JsonProperty(required = true) val function: FunctionCall,
14+
) : ToolCall() {
15+
override val type = Tool.Type.FUNCTION
16+
17+
override fun update(delta: ToolCallDelta) {
18+
if (delta.function != null)
19+
function.update(delta.function)
20+
}
21+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.cjcrafter.openai.chat.tool
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
import org.jetbrains.annotations.ApiStatus
5+
6+
/**
7+
* Represents a tool call for [Tool.Type.RETRIEVAL].
8+
*
9+
* Currently, this tool call serves no functionality other than to inform you
10+
* that some retrieval operation has occurred. Currently, the OpenAI API does
11+
* not return any information about the retrieval ([retrieval] will always be
12+
* an empty map. [retrieval]'s data type may be changed in the future, I
13+
* recommend you __DO NOT__ use it).
14+
*
15+
* @property id The unique id of this tool call
16+
* @property retrieval An empty map, do not use this!
17+
*/
18+
data class RetrievalToolCall(
19+
@JsonProperty(required = true) override val id: String,
20+
@JsonProperty(required = true) @ApiStatus.Experimental val retrieval: Map<String, String>,
21+
): ToolCall() {
22+
override val type = Tool.Type.RETRIEVAL
23+
}

src/main/kotlin/com/cjcrafter/openai/chat/tool/Tool.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo
1111
*/
1212
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
1313
@JsonSubTypes(
14-
JsonSubTypes.Type(TextAnnotation.FileCitation::class, name = "file_citation"),
15-
JsonSubTypes.Type(TextAnnotation.FilePath::class, name = "file_path"),
14+
JsonSubTypes.Type(Tool.FunctionTool::class, name = "function"),
15+
JsonSubTypes.Type(Tool.RetrievalTool::class, name = "retrieval"),
16+
JsonSubTypes.Type(Tool.CodeInterpreterTool::class, name = "code_interpreter"),
1617
)
1718
sealed class Tool {
1819

@@ -22,15 +23,25 @@ sealed class Tool {
2223
* @see Function.builder
2324
*/
2425
data class FunctionTool(
25-
var function: Function,
26+
@JsonProperty(required = true) var function: Function,
2627
): Tool() {
2728
override val type = Type.FUNCTION
2829
}
2930

31+
/**
32+
* Represents a tool that retrieves data from uploaded files.
33+
*
34+
* Note that retrieval tools are only supported by [Assistant]s
35+
*/
3036
data object RetrievalTool: Tool() {
3137
override val type = Type.RETRIEVAL
3238
}
3339

40+
/**
41+
* Represents a tool that runs Python code on the OpenAI server.
42+
*
43+
* Note that code interpreter tools are only supported by [Assistant]s
44+
*/
3445
data object CodeInterpreterTool: Tool() {
3546
override val type = Type.CODE_INTERPRETER
3647
}
@@ -58,6 +69,8 @@ sealed class Tool {
5869

5970
/**
6071
* A tool that runs Python code on the OpenAI server.
72+
*
73+
* Note that code interpreter tools are only supported by [Assistant]s
6174
*/
6275
@JsonProperty("code_interpreter")
6376
CODE_INTERPRETER,
Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package com.cjcrafter.openai.chat.tool
22

3+
import com.fasterxml.jackson.annotation.JsonSubTypes
4+
import com.fasterxml.jackson.annotation.JsonTypeInfo
5+
36
/**
47
* Wraps a tool call by ChatGPT. You should check the [type] of the tool call,
58
* and handle the request. For example, if the type is [Tool.Type.FUNCTION], you
@@ -10,17 +13,20 @@ package com.cjcrafter.openai.chat.tool
1013
* call.
1114
*
1215
* @property id The id of this call. You should use this to construct a [com.cjcrafter.openai.chat.ChatUser.TOOL] message.
13-
* @property type The type of tool call. Currently, the only type is [Tool.Type.FUNCTION].
14-
* @property function The function call containing the function name and arguments.
16+
* @property type The type of tool call.
1517
*/
16-
data class ToolCall(
17-
var id: String,
18-
var type: Tool.Type,
19-
var function: FunctionCall,
20-
) {
21-
internal fun update(delta: ToolCallDelta) {
22-
// The only field that updates is function
23-
if (delta.function != null)
24-
function.update(delta.function)
18+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
19+
@JsonSubTypes(
20+
JsonSubTypes.Type(FunctionToolCall::class, name = "function"),
21+
JsonSubTypes.Type(RetrievalToolCall::class, name = "retrieval"),
22+
JsonSubTypes.Type(CodeInterpreterToolCall::class, name = "code_interpreter"),
23+
)
24+
sealed class ToolCall {
25+
26+
abstract val id: String
27+
abstract val type: Tool.Type
28+
29+
internal open fun update(delta: ToolCallDelta) {
30+
// Nothing to update
2531
}
2632
}

src/main/kotlin/com/cjcrafter/openai/chat/tool/ToolCallDelta.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ data class ToolCallDelta(
1515
val type: Tool.Type? = null,
1616
val function: FunctionCallDelta? = null,
1717
) {
18-
internal fun toToolCall() = ToolCall(
19-
id = id ?: throw IllegalStateException("id must be set"),
20-
type = type ?: throw IllegalStateException("type must be set"),
21-
function = function?.toFunctionCall() ?: throw IllegalStateException("function must be set"),
22-
)
18+
internal fun toToolCall(): ToolCall {
19+
// This is for chat, which is always a function tool call
20+
return FunctionToolCall(
21+
id = id ?: throw IllegalStateException("id must be set"),
22+
function = function?.toFunctionCall() ?: throw IllegalStateException("function must be set"),
23+
)
24+
}
2325
}

0 commit comments

Comments
 (0)