@@ -20,7 +20,11 @@ import com.google.firebase.FirebaseApp
2020import com.google.firebase.ai.common.APIController
2121import com.google.firebase.ai.common.AppCheckHeaderProvider
2222import com.google.firebase.ai.common.JSON
23+ import com.google.firebase.ai.type.AutoFunctionDeclaration
2324import com.google.firebase.ai.type.Content
25+ import com.google.firebase.ai.type.FirebaseAutoFunctionException
26+ import com.google.firebase.ai.type.FunctionCallPart
27+ import com.google.firebase.ai.type.FunctionResponsePart
2428import com.google.firebase.ai.type.GenerativeBackend
2529import com.google.firebase.ai.type.LiveClientSetupMessage
2630import com.google.firebase.ai.type.LiveGenerationConfig
@@ -40,8 +44,11 @@ import io.ktor.websocket.readBytes
4044import kotlin.coroutines.CoroutineContext
4145import kotlinx.coroutines.channels.ClosedReceiveChannelException
4246import kotlinx.serialization.ExperimentalSerializationApi
47+ import kotlinx.serialization.InternalSerializationApi
4348import kotlinx.serialization.encodeToString
4449import kotlinx.serialization.json.JsonObject
50+ import kotlinx.serialization.json.JsonPrimitive
51+ import kotlinx.serialization.json.jsonObject
4552
4653/* *
4754 * Represents a multimodal model (like Gemini) capable of real-time content generation based on
@@ -140,7 +147,9 @@ internal constructor(
140147 session = webSession,
141148 blockingDispatcher = blockingDispatcher,
142149 firebaseApp = firebaseApp,
143- connectionFactory = connectFactory
150+ connectionFactory = connectFactory,
151+ hasFunction = ::hasFunction,
152+ executeFunction = ::executeFunction
144153 )
145154 } catch (e: ClosedReceiveChannelException ) {
146155 val reason = webSession?.closeReason?.await()
@@ -150,6 +159,55 @@ internal constructor(
150159 }
151160 }
152161
162+ internal fun hasFunction (call : FunctionCallPart ): Boolean {
163+ return tools
164+ .flatMap { it.autoFunctionDeclarations ? : emptyList() }
165+ .firstOrNull { it.name == call.name && it.functionReference != null } != null
166+ }
167+
168+ @OptIn(InternalSerializationApi ::class )
169+ internal suspend fun executeFunction (call : FunctionCallPart ): FunctionResponsePart {
170+ if (tools.isEmpty()) {
171+ throw RuntimeException (" No registered tools" )
172+ }
173+ val tool = tools.flatMap { it.autoFunctionDeclarations ? : emptyList() }
174+ val declaration =
175+ tool.firstOrNull { it.name == call.name }
176+ ? : throw RuntimeException (" No registered function named ${call.name} " )
177+ return executeFunction<Any , Any >(
178+ call,
179+ declaration as AutoFunctionDeclaration <Any , Any >,
180+ JsonObject (call.args).toString()
181+ )
182+ }
183+
184+ @OptIn(InternalSerializationApi ::class )
185+ internal suspend fun <I : Any , O : Any > executeFunction (
186+ functionCall : FunctionCallPart ,
187+ functionDeclaration : AutoFunctionDeclaration <I , O >,
188+ parameter : String
189+ ): FunctionResponsePart {
190+ val inputDeserializer = functionDeclaration.inputSchema.getSerializer()
191+ val input = JSON .decodeFromString(inputDeserializer, parameter)
192+ val functionReference =
193+ functionDeclaration.functionReference
194+ ? : throw RuntimeException (" Function reference for ${functionDeclaration.name} is missing" )
195+ try {
196+ val output = functionReference.invoke(input)
197+ val outputSerializer = functionDeclaration.outputSchema?.getSerializer()
198+ if (outputSerializer != null ) {
199+ return FunctionResponsePart .from(
200+ JSON .encodeToJsonElement(outputSerializer, output).jsonObject
201+ )
202+ .normalizeAgainstCall(functionCall)
203+ }
204+ return (output as FunctionResponsePart ).normalizeAgainstCall(functionCall)
205+ } catch (e: FirebaseAutoFunctionException ) {
206+ return FunctionResponsePart .from(JsonObject (mapOf (" error" to JsonPrimitive (e.message))))
207+ .normalizeAgainstCall(functionCall)
208+ }
209+ }
210+
153211 private companion object {
154212 private val TAG = LiveGenerativeModel ::class .java.simpleName
155213 }
0 commit comments