@@ -3,18 +3,20 @@ package com.aallam.openai.sample.jvm
33import com.aallam.openai.api.chat.*
44import com.aallam.openai.api.model.ModelId
55import 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.*
97import kotlinx.serialization.Serializable
108import kotlinx.serialization.encodeToString
119import 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+ */
1315suspend 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