How to Add Tool Definitions for Mobile Actions in Android
To add tool definitions for Mobile Actions, annotate a Kotlin function with @Tool and @ToolParam in MobileActionsTools.kt, define a corresponding sealed-class Action in MobileActionsViewModel.kt, and implement the execution logic in the view-model's handler.
Mobile Actions is the on-device function-calling framework in the google-ai-edge/gallery repository that enables finetuned FunctionGemma models to invoke Android capabilities through tool definitions. Adding new tools requires implementing the ToolSet interface pattern used by the LLM runtime to discover and execute Kotlin methods as callable functions. The architecture bridges model-generated tool requests to concrete Android operations via type-safe callbacks.
Understanding the Mobile Actions Architecture
Before adding new tools, you need to understand how the system routes LLM requests to Android APIs.
The Tool Execution Pipeline
The Mobile Actions framework in google-ai-edge/gallery follows a four-stage pipeline:
- Discovery: The
MobileActionsTaskregistersMobileActionsTools(which implementsToolSet) with the LLM runtime, exposing annotated methods. - Invocation: When the model decides to call a tool, it serializes parameters and invokes the corresponding Kotlin method.
- Bridging: The tool method creates an
Actionobject (sealed class) and callsonFunctionCalled(), forwarding the request to the view-model. - Execution:
MobileActionsViewModelhandles theAction, performs the Android operation (e.g., CameraManager, Intent), and returns results to the LLM viaMap<String, String>.
Key Source Files
| File | Purpose | Path |
|---|---|---|
| MobileActionsTools.kt | Declares annotated tools and forwards calls to typed Action objects. |
Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsTools.kt |
| MobileActionsViewModel.kt | Receives Action callbacks and implements actual Android logic. |
Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsViewModel.kt |
| MobileActionsTask.kt | Registers the ToolSet with the LLM runtime. |
Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsTask.kt |
| MobileActionsModule.kt | Hilt DI module providing the task graph. | Android/src/app/src/main/java/com/google/ai/edge/gallery/customtasks/mobileactions/MobileActionsModule.kt |
| Function_Calling_Guide.md | Documentation on annotation contracts and callback patterns. | Function_Calling_Guide.md |
How to Add a New Tool Definition
Follow this pattern to extend the tool set with new Android capabilities.
Step 1: Annotate the Tool Function
In MobileActionsTools.kt, add a public method annotated with @Tool and @ToolParam. The method name becomes the tool's identifier, and it must return a Map<String, String> for the LLM response.
// File: MobileActionsTools.kt
/**
* Plays a bundled sound file.
*/
@Tool(description = "Plays a sound file bundled in the app.")
fun playSound(
@ToolParam(description = "The name of the sound asset (without extension).") soundName: String,
): Map<String, String> {
Log.d(TAG, "Play sound: $soundName")
// Forward to view-model via callback
onFunctionCalled(PlaySoundAction(soundName = soundName))
// Return result to LLM
return mapOf("result" to "success", "sound_name" to soundName)
}
Step 2: Define the Action Data Class
In MobileActionsViewModel.kt, extend the sealed Action hierarchy to represent this operation.
// File: MobileActionsViewModel.kt (add to sealed class)
data class PlaySoundAction(val soundName: String) : Action()
Step 3: Handle the Action
Add the execution logic to the view-model's action handler, typically in a when expression processing incoming Action objects.
// File: MobileActionsViewModel.kt (inside handleAction)
is PlaySoundAction -> {
val assetManager = appContext.assets
val afd = assetManager.openFd("sounds/${action.soundName}.mp3")
MediaPlayer().apply {
setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
prepare()
start()
}
}
Step 4: Rebuild and Verify
After compiling, the LLM runtime automatically discovers the new tool through reflection on MobileActionsTools. The FunctionGemma model can now generate calls to playSound with the specified soundName parameter.
Complete Working Example
Here is the full implementation flow for adding a "Play Sound" tool definition across the key files:
MobileActionsTools.kt (Tool Definition):
class MobileActionsTools @Inject constructor(
private val onFunctionCalled: (Action) -> Unit
) : ToolSet {
@Tool(description = "Plays a sound file bundled in the app.")
fun playSound(
@ToolParam(description = "The name of the sound asset (without extension).") soundName: String,
): Map<String, String> {
onFunctionCalled(PlaySoundAction(soundName))
return mapOf("status" to "playing", "asset" to soundName)
}
}
MobileActionsViewModel.kt (Action Definition and Handler):
// Sealed class definition
sealed class Action
data class PlaySoundAction(val soundName: String) : Action()
class MobileActionsViewModel @Inject constructor(
private val appContext: Context
) : ViewModel() {
fun handleAction(action: Action) {
when (action) {
is PlaySoundAction -> {
val afd = appContext.assets.openFd("sounds/${action.soundName}.mp3")
MediaPlayer().apply {
setDataSource(afd.fileDescriptor, afd.startOffset, afd.length)
prepare()
start()
}
}
// Handle other actions...
}
}
}
MobileActionsTask.kt (Registration - no changes needed):
class MobileActionsTask @Inject constructor(
mobileActionsTools: MobileActionsTools
) {
// Registers the ToolSet with the LLM runtime
val toolSet: ToolSet = mobileActionsTools
}
Summary
- Tool definitions require
@Tooland@ToolParamannotations inMobileActionsTools.ktto expose Kotlin methods to the FunctionGemma model. - Action objects bridge the tool definition to Android implementation using a sealed class hierarchy defined in
MobileActionsViewModel.kt. - Return values must be
Map<String, String>to provide structured results back to the LLM conversation. - Registration happens automatically through
MobileActionsTaskwhich supplies theToolSetto the LLM runtime via dependency injection inMobileActionsModule.kt.
Frequently Asked Questions
What is the purpose of the onFunctionCalled callback?
The onFunctionCalled callback bridges the tool definition to the view-model. When a tool method executes, it creates an Action object and passes it to this callback, which forwards the request to MobileActionsViewModel. According to the source code in MobileActionsTools.kt, this decouples the LLM-facing tool interface from the Android implementation details.
How do I return structured data from a tool definition?
Tool methods must return a Map<String, String> that the LLM includes in its response. For example, returning mapOf("result" to "success") provides the model with confirmation that the action completed, which it can reference in subsequent conversation turns. This return value is defined in the method signature and populated before calling onFunctionCalled.
Where should the actual Android API calls be implemented?
Implement Android-specific logic in MobileActionsViewModel.kt inside the action handler (typically a when expression). This view-model receives Action objects from the tool layer and performs operations like accessing CameraManager, sending Intents, or playing media. Keeping implementation details in the view-model ensures MobileActionsTools.kt remains focused on parameter marshaling and LLM communication.
Do I need to modify MobileActionsTask.kt to add new tools?
No. MobileActionsTask.kt registers the entire ToolSet with the LLM runtime once at initialization. New tools added to MobileActionsTools.kt are automatically discovered through reflection when the app rebuilds, requiring no changes to the task registration code or the Hilt module in MobileActionsModule.kt.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →