@@ -3,18 +3,20 @@ package com.aallam.openai.sample.jvm
3
3
import com.aallam.openai.api.chat.*
4
4
import com.aallam.openai.api.model.ModelId
5
5
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.*
9
7
import kotlinx.serialization.Serializable
10
8
import kotlinx.serialization.encodeToString
11
9
import kotlinx.serialization.json.*
12
10
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
+ */
13
15
suspend fun chatFunctionCall (openAI : OpenAI ) {
14
16
// *** Chat Completion with Function Call *** //
15
17
16
18
println (" \n > Create Chat Completion function call..." )
17
- val modelId = ModelId (" gpt-3.5-turbo-0613 " )
19
+ val modelId = ModelId (" gpt-3.5-turbo" )
18
20
val chatMessages = mutableListOf (
19
21
ChatMessage (
20
22
role = ChatRole .User ,
@@ -51,111 +53,125 @@ suspend fun chatFunctionCall(openAI: OpenAI) {
51
53
parameters = params
52
54
}
53
55
}
54
- functionCall = FunctionMode .Auto
56
+ functionCall = FunctionMode .Named ( " currentWeather " ) // or FunctionMode. Auto
55
57
}
56
58
57
59
val response = openAI.chatCompletion(request)
58
60
val message = response.choices.first().message
61
+ chatMessages.append(message)
59
62
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)
62
65
val secondResponse = openAI.chatCompletion(
63
- request = ChatCompletionRequest (
64
- model = modelId,
65
- messages = chatMessages,
66
- )
66
+ request = ChatCompletionRequest (model = modelId, messages = chatMessages)
67
67
)
68
- print (secondResponse)
69
- }
68
+ print (secondResponse.choices.first().message.content.orEmpty() )
69
+ } ? : print (message.content.orEmpty())
70
70
71
71
// *** Chat Completion Stream with Function Call *** //
72
72
73
73
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
+ }
85
84
86
- openAI.chatCompletions(
87
- ChatCompletionRequest (
88
- model = modelId,
89
- messages = chatMessages,
90
- )
91
- )
85
+ openAI.chatCompletions(request = ChatCompletionRequest (model = modelId, messages = chatMessages))
92
86
.onEach { print (it.choices.first().delta.content.orEmpty()) }
93
87
.onCompletion { println () }
94
88
.collect()
95
89
}
96
90
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)
99
95
100
96
/* *
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.
103
99
*/
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 {
105
110
val weatherInfo = WeatherInfo (location, " 72" , unit, listOf (" sunny" , " windy" ))
106
111
return Json .encodeToString(weatherInfo)
107
112
}
108
113
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
+
113
120
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)
118
128
}
119
129
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))
136
135
}
137
136
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
+ }
143
143
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) }
147
159
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) }
150
162
}
163
+ return this
151
164
}
152
165
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()
159
175
}
160
176
}
161
177
}
0 commit comments