Skip to content

Commit f699272

Browse files
committed
chore(chat): update docs [skip ci]
1 parent 688d9c8 commit f699272

File tree

4 files changed

+101
-86
lines changed

4 files changed

+101
-86
lines changed

guides/ChatFunctionCall.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,14 @@ val openAI = OpenAI(token)
2727
Specify the model to use for the chat request.
2828

2929
```kotlin
30-
val modelId = ModelId("gpt-3.5-turbo-0613")
30+
val modelId = ModelId("gpt-3.5-turbo")
3131
```
3232

3333
### Defining the Function
3434

3535
Define a dummy function `currentWeather` which the model might call. This function will return hardcoded weather information.
3636

3737
```kotlin
38-
@Serializable
39-
data class WeatherInfo(val location: String, val temperature: String, val unit: String, val forecast: List<String>)
40-
4138
/**
4239
* Example dummy function hard coded to return the same weather
4340
* In production, this could be your backend API or an external API
@@ -46,6 +43,9 @@ fun currentWeather(location: String, unit: String): String {
4643
val weatherInfo = WeatherInfo(location, "72", unit, listOf("sunny", "windy"))
4744
return Json.encodeToString(weatherInfo)
4845
}
46+
47+
@Serializable
48+
data class WeatherInfo(val location: String, val temperature: String, val unit: String, val forecast: List<String>)
4949
```
5050

5151
### Defining Function Parameters
@@ -102,7 +102,7 @@ val request = chatCompletionRequest {
102102
parameters = params
103103
}
104104
}
105-
functionCall = FunctionMode.Auto
105+
functionCall = FunctionMode.Named("currentWeather") // or FunctionMode.Auto
106106
}
107107
```
108108

@@ -166,7 +166,7 @@ suspend fun main() {
166166
val token = System.getenv("OPENAI_API_KEY")
167167
val openAI = OpenAI(token)
168168

169-
val modelId = ModelId("gpt-3.5-turbo-0613")
169+
val modelId = ModelId("gpt-3.5-turbo")
170170
val chatMessages = mutableListOf(
171171
ChatMessage(
172172
role = ChatRole.User,

openai-client/src/commonTest/kotlin/com/aallam/openai/client/TestChatCompletions.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class TestChatCompletions : TestOpenAI() {
5050

5151
@Test
5252
fun chatCompletionsFunction() = test {
53-
val modelId = ModelId("gpt-3.5-turbo-0613")
53+
val modelId = ModelId("gpt-3.5-turbo")
5454
val chatMessages = mutableListOf(
5555
ChatMessage(
5656
role = ChatRole.User,
@@ -94,7 +94,7 @@ class TestChatCompletions : TestOpenAI() {
9494
}
9595

9696
val response = openAI.chatCompletion(request)
97-
val message = response.choices.first().message ?: error("No chat response found!")
97+
val message = response.choices.first().message
9898
assertEquals("currentWeather", message.functionCall?.name)
9999
}
100100
}

sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/App.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import kotlinx.coroutines.runBlocking
88
fun main() = runBlocking {
99
val apiKey = System.getenv("OPENAI_API_KEY")
1010
val token = requireNotNull(apiKey) { "OPENAI_API_KEY environment variable must be set." }
11-
val openAI = OpenAI(token = token, logging = LoggingConfig(LogLevel.All))
11+
val openAI = OpenAI(token = token, logging = LoggingConfig(LogLevel.None))
1212

1313
while (true) {
1414
println("Select an option:")
@@ -21,8 +21,7 @@ fun main() = runBlocking {
2121
println("7 - Whisper")
2222
println("0 - Quit")
2323

24-
val option = readlnOrNull()?.toIntOrNull()
25-
when (option) {
24+
when (val option = readlnOrNull()?.toIntOrNull()) {
2625
1 -> engines(openAI)
2726
2 -> files(openAI)
2827
3 -> moderations(openAI)

sample/jvm/src/main/kotlin/com/aallam/openai/sample/jvm/ChatFunctionCall.kt

Lines changed: 91 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@ package com.aallam.openai.sample.jvm
33
import com.aallam.openai.api.chat.*
44
import com.aallam.openai.api.model.ModelId
55
import com.aallam.openai.client.OpenAI
6-
import kotlinx.coroutines.flow.collect
7-
import kotlinx.coroutines.flow.onCompletion
8-
import kotlinx.coroutines.flow.onEach
6+
import kotlinx.coroutines.flow.*
97
import kotlinx.serialization.Serializable
108
import kotlinx.serialization.encodeToString
119
import kotlinx.serialization.json.*
1210

11+
/**
12+
* This code snippet demonstrates the use of OpenAI's chat completion capabilities
13+
* with a focus on integrating function calls into the chat conversation.
14+
*/
1315
suspend fun chatFunctionCall(openAI: OpenAI) {
1416
// *** Chat Completion with Function Call *** //
1517

1618
println("\n> Create Chat Completion function call...")
17-
val modelId = ModelId("gpt-3.5-turbo-0613")
19+
val modelId = ModelId("gpt-3.5-turbo")
1820
val chatMessages = mutableListOf(
1921
ChatMessage(
2022
role = ChatRole.User,
@@ -51,111 +53,125 @@ suspend fun chatFunctionCall(openAI: OpenAI) {
5153
parameters = params
5254
}
5355
}
54-
functionCall = FunctionMode.Auto
56+
functionCall = FunctionMode.Named("currentWeather") // or FunctionMode.Auto
5557
}
5658

5759
val response = openAI.chatCompletion(request)
5860
val message = response.choices.first().message
61+
chatMessages.append(message)
5962
message.functionCall?.let { functionCall ->
60-
val functionResponse = callFunction(functionCall)
61-
updateChatMessages(chatMessages, message, functionCall, functionResponse)
63+
val functionResponse = functionCall.execute()
64+
chatMessages.append(functionCall, functionResponse)
6265
val secondResponse = openAI.chatCompletion(
63-
request = ChatCompletionRequest(
64-
model = modelId,
65-
messages = chatMessages,
66-
)
66+
request = ChatCompletionRequest(model = modelId, messages = chatMessages)
6767
)
68-
print(secondResponse)
69-
}
68+
print(secondResponse.choices.first().message.content.orEmpty())
69+
} ?: print(message.content.orEmpty())
7070

7171
// *** Chat Completion Stream with Function Call *** //
7272

7373
println("\n> Create Chat Completion function call (stream)...")
74-
val chunks = mutableListOf<ChatChunk>()
75-
openAI.chatCompletions(request)
76-
.onEach { chunks += it.choices.first() }
77-
.onCompletion {
78-
val chatMessage = chatMessageOf(chunks)
79-
chatMessage.functionCall?.let {
80-
val functionResponse = callFunction(it)
81-
updateChatMessages(chatMessages, message, it, functionResponse)
82-
}
83-
}
84-
.collect()
74+
val chatMessage = openAI.chatCompletions(request)
75+
.map { completion -> completion.choices.first() }
76+
.fold(initial = ChatMessageAssembler()) { assembler, chunk -> assembler.merge(chunk) }
77+
.build()
78+
79+
chatMessages.append(chatMessage)
80+
chatMessage.functionCall?.let { functionCall ->
81+
val functionResponse = functionCall.execute()
82+
chatMessages.append(functionCall, functionResponse)
83+
}
8584

86-
openAI.chatCompletions(
87-
ChatCompletionRequest(
88-
model = modelId,
89-
messages = chatMessages,
90-
)
91-
)
85+
openAI.chatCompletions(request = ChatCompletionRequest(model = modelId, messages = chatMessages))
9286
.onEach { print(it.choices.first().delta.content.orEmpty()) }
9387
.onCompletion { println() }
9488
.collect()
9589
}
9690

97-
@Serializable
98-
data class WeatherInfo(val location: String, val temperature: String, val unit: String, val forecast: List<String>)
91+
/**
92+
* A map that associates function names with their corresponding functions.
93+
*/
94+
private val availableFunctions = mapOf("currentWeather" to ::callCurrentWeather)
9995

10096
/**
101-
* Example dummy function hard coded to return the same weather
102-
* In production, this could be your backend API or an external API
97+
* Example dummy function for retrieving weather information based on location and temperature unit.
98+
* In a production scenario, this function could be replaced with an actual backend or external API call.
10399
*/
104-
fun currentWeather(location: String, unit: String): String {
100+
private fun callCurrentWeather(args: JsonObject): String {
101+
val location = args.getValue("location").jsonPrimitive.content
102+
val unit = args["unit"]?.jsonPrimitive?.content ?: "fahrenheit"
103+
return currentWeather(location, unit)
104+
}
105+
106+
/**
107+
* Example dummy function for retrieving weather information based on location and temperature unit.
108+
*/
109+
private fun currentWeather(location: String, unit: String): String {
105110
val weatherInfo = WeatherInfo(location, "72", unit, listOf("sunny", "windy"))
106111
return Json.encodeToString(weatherInfo)
107112
}
108113

109-
private fun callFunction(functionCall: FunctionCall): String {
110-
val availableFunctions = mapOf("currentWeather" to ::currentWeather)
111-
val functionToCall = availableFunctions[functionCall.name] ?: error("Function ${functionCall.name} not found")
112-
val functionArgs = functionCall.argumentsAsJson()
114+
/**
115+
* Serializable data class to represent weather information.
116+
*/
117+
@Serializable
118+
data class WeatherInfo(val location: String, val temperature: String, val unit: String, val forecast: List<String>)
119+
113120

114-
return functionToCall(
115-
functionArgs.getValue("location").jsonPrimitive.content,
116-
functionArgs["unit"]?.jsonPrimitive?.content ?: "fahrenheit"
117-
)
121+
/**
122+
* Executes a function call and returns its result.
123+
*/
124+
private fun FunctionCall.execute(): String {
125+
val functionToCall = availableFunctions[name] ?: error("Function $name not found")
126+
val functionArgs = argumentsAsJson()
127+
return functionToCall(functionArgs)
118128
}
119129

120-
private fun updateChatMessages(
121-
chatMessages: MutableList<ChatMessage>,
122-
message: ChatMessage,
123-
functionCall: FunctionCall,
124-
functionResponse: String
125-
) {
126-
chatMessages.add(
127-
ChatMessage(
128-
role = message.role,
129-
content = message.content.orEmpty(), // required to not be empty in this case
130-
functionCall = message.functionCall
131-
)
132-
)
133-
chatMessages.add(
134-
ChatMessage(role = ChatRole.Function, name = functionCall.name, content = functionResponse)
135-
)
130+
/**
131+
* Appends a chat message to a list of chat messages.
132+
*/
133+
private fun MutableList<ChatMessage>.append(message: ChatMessage) {
134+
add(ChatMessage(role = message.role, content = message.content.orEmpty(), functionCall = message.functionCall))
136135
}
137136

138-
fun chatMessageOf(chunks: List<ChatChunk>): ChatMessage {
139-
val funcName = StringBuilder()
140-
val funcArgs = StringBuilder()
141-
var role: ChatRole? = null
142-
val content = StringBuilder()
137+
/**
138+
* Appends a function call and response to a list of chat messages.
139+
*/
140+
private fun MutableList<ChatMessage>.append(functionCall: FunctionCall, functionResponse: String) {
141+
add(ChatMessage(role = ChatRole.Function, name = functionCall.name, content = functionResponse))
142+
}
143143

144-
chunks.forEach { chunk ->
145-
role = chunk.delta.role ?: role
146-
chunk.delta.content?.let { content.append(it) }
144+
/**
145+
* A class to help assemble chat messages from chat chunks.
146+
*/
147+
class ChatMessageAssembler {
148+
private val chatFuncName = StringBuilder()
149+
private val chatFuncArgs = StringBuilder()
150+
private val chatContent = StringBuilder()
151+
private var chatRole: ChatRole? = null
152+
153+
/**
154+
* Merges a chat chunk into the chat message being assembled.
155+
*/
156+
fun merge(chunk: ChatChunk): ChatMessageAssembler {
157+
chatRole = chunk.delta.role ?: chatRole
158+
chunk.delta.content?.let { chatContent.append(it) }
147159
chunk.delta.functionCall?.let { call ->
148-
funcName.append(call.name)
149-
funcArgs.append(call.arguments)
160+
call.nameOrNull?.let { chatFuncName.append(it) }
161+
call.argumentsOrNull?.let { chatFuncArgs.append(it) }
150162
}
163+
return this
151164
}
152165

153-
return chatMessage {
154-
this.role = role
155-
this.content = content.toString()
156-
if (funcName.isNotEmpty() || funcArgs.isNotEmpty()) {
157-
functionCall = FunctionCall(funcName.toString(), funcArgs.toString())
158-
name = funcName.toString()
166+
/**
167+
* Builds and returns the assembled chat message.
168+
*/
169+
fun build(): ChatMessage = chatMessage {
170+
this.role = chatRole
171+
this.content = chatContent.toString()
172+
if (chatFuncName.isNotEmpty() || chatFuncArgs.isNotEmpty()) {
173+
this.functionCall = FunctionCall(chatFuncName.toString(), chatFuncArgs.toString())
174+
this.name = chatFuncName.toString()
159175
}
160176
}
161177
}

0 commit comments

Comments
 (0)