Overview
Tool Calling enables on-device LLMs to invoke registered functions — such as fetching weather, performing calculations, or querying APIs — and incorporate the results into their responses. The SDK handles the full orchestration loop: generate → parse tool call → execute → feed result back → generate final response.
Tool calling uses the RunAnywhereToolCalling class, not the RunAnywhere singleton. Make
sure to import from the correct package.
Package Imports
import com.runanywhere.sdk.public.extensions.LLM.RunAnywhereToolCalling
import com.runanywhere.sdk.public.extensions.LLM.ToolDefinition
import com.runanywhere.sdk.public.extensions.LLM.ToolParameter
import com.runanywhere.sdk.public.extensions.LLM.ToolParameterType
import com.runanywhere.sdk.public.extensions.LLM.ToolCallingOptions
import com.runanywhere.sdk.public.extensions.LLM.ToolValue
Or import the entire LLM extensions package:
import com.runanywhere.sdk.public.extensions.LLM.*
Basic Usage
// 1. Register a tool
RunAnywhereToolCalling.registerTool(
ToolDefinition(
name = "get_weather",
description = "Get current weather for a city",
parameters = listOf(
ToolParameter(
name = "city",
type = ToolParameterType.STRING,
description = "City name",
required = true
)
),
category = "Utility"
)
) { args ->
val city = args["city"]?.stringValue ?: "Unknown"
mapOf(
"temperature" to ToolValue.number(72.0),
"condition" to ToolValue.string("sunny"),
"city" to ToolValue.string(city)
)
}
// 2. Generate with tools
val options = ToolCallingOptions(
maxToolCalls = 3,
autoExecute = true,
temperature = 0.7f,
maxTokens = 512
)
val result = RunAnywhereToolCalling.generateWithTools(
"What's the weather in San Francisco?",
options
)
println(result.text) // "The weather in San Francisco is 72°F and sunny."
println(result.toolCalls) // List of tool calls made
println(result.toolResults) // List of tool execution results
API Reference
| Method | Return Type | Description |
|---|
registerTool(definition, executor) | Unit | Register a tool with its definition and callback |
generateWithTools(prompt, options) | ToolCallingResult | Run full orchestration: generate → call → respond |
data class ToolDefinition(
val name: String,
val description: String,
val parameters: List<ToolParameter>,
val category: String? = null
)
| Property | Type | Description |
|---|
name | String | Unique tool identifier used by the LLM |
description | String | What the tool does (shown to the model) |
parameters | List<ToolParameter> | Input parameters the tool accepts |
category | String? | Optional grouping category |
data class ToolParameter(
val name: String,
val type: ToolParameterType,
val description: String,
val required: Boolean = false
)
| Property | Type | Description |
|---|
name | String | Parameter name |
type | ToolParameterType | Data type (STRING, NUMBER, BOOLEAN) |
description | String | Parameter description (shown to the model) |
required | Boolean | Whether the parameter is required |
| Value | Description |
|---|
ToolParameterType.STRING | String parameter |
ToolParameterType.NUMBER | Numeric parameter |
ToolParameterType.BOOLEAN | Boolean parameter |
data class ToolCallingOptions(
val maxToolCalls: Int = 3,
val autoExecute: Boolean = true,
val temperature: Float = 0.7f,
val maxTokens: Int = 512
)
| Property | Type | Default | Description |
|---|
maxToolCalls | Int | 3 | Maximum tool invocations per generation |
autoExecute | Boolean | true | Automatically execute tool calls |
temperature | Float | 0.7f | Creativity (0.0–2.0) |
maxTokens | Int | 512 | Maximum tokens to generate |
| Property | Type | Description |
|---|
text | String | Final response text incorporating tool results |
toolCalls | List<ToolCall> | All tool calls the model made |
toolResults | List<ToolResult> | Execution results for each tool call |
| Property | Type | Description |
|---|
toolName | String | Name of the tool the model invoked |
arguments | Map<String, ToolValue> | Arguments passed by the model |
| Property | Type | Description |
|---|
result | Map<String, ToolValue>? | Return value from the tool executor |
error | String? | Error message if execution failed |
success | Boolean | Whether the tool executed successfully |
Factory methods for creating typed values:
| Method | Input | Description |
|---|
ToolValue.string(value) | String | Creates a string tool value |
ToolValue.number(value) | Double | Creates a numeric tool value |
Accessors for reading values:
| Property | Return Type | Description |
|---|
value.stringValue | String? | Extracts the string value |
Examples
RunAnywhereToolCalling.registerTool(
ToolDefinition(
name = "get_weather",
description = "Get current weather for a city",
parameters = listOf(
ToolParameter(
name = "city",
type = ToolParameterType.STRING,
description = "City name",
required = true
)
),
category = "Utility"
)
) { args ->
val city = args["city"]?.stringValue ?: "Unknown"
mapOf(
"temperature" to ToolValue.number(72.0),
"condition" to ToolValue.string("sunny"),
"humidity" to ToolValue.number(65.0)
)
}
RunAnywhereToolCalling.registerTool(
ToolDefinition(
name = "calculate",
description = "Evaluate a math expression and return the result",
parameters = listOf(
ToolParameter(
name = "expression",
type = ToolParameterType.STRING,
description = "Math expression (e.g., '2 + 3 * 4')",
required = true
)
),
category = "Utility"
)
) { args ->
val expr = args["expression"]?.stringValue ?: "0"
try {
val engine = javax.script.ScriptEngineManager().getEngineByName("js")
val result = engine.eval(expr) as Number
mapOf("result" to ToolValue.number(result.toDouble()))
} catch (e: Exception) {
mapOf("error" to ToolValue.string("Invalid expression: $expr"))
}
}
RunAnywhereToolCalling.registerTool(
ToolDefinition(
name = "get_current_time",
description = "Get the current date and time",
parameters = emptyList(),
category = "Utility"
)
) { _ ->
val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
val now = java.time.LocalDateTime.now().format(formatter)
mapOf("datetime" to ToolValue.string(now))
}
fun registerAllTools() {
// Register weather, calculator, and time tools (see above)
registerWeatherTool()
registerCalculatorTool()
registerTimeTool()
}
suspend fun askWithTools(userPrompt: String): String {
val options = ToolCallingOptions(
maxToolCalls = 5,
autoExecute = true,
temperature = 0.5f,
maxTokens = 1024
)
val result = RunAnywhereToolCalling.generateWithTools(userPrompt, options)
return result.text
}
// The LLM will automatically choose which tools to call:
// "What's 15% of 250 and what time is it?" → calls calculate + get_current_time
Jetpack Compose UI
A complete tool calling chat interface:
@Composable
fun ToolCallingScreen() {
var prompt by remember { mutableStateOf("") }
var response by remember { mutableStateOf("") }
var toolLog by remember { mutableStateOf("") }
var isProcessing by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
registerWeatherTool()
registerCalculatorTool()
registerTimeTool()
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text("Tool Calling Demo", style = MaterialTheme.typography.headlineMedium)
OutlinedTextField(
value = prompt,
onValueChange = { prompt = it },
label = { Text("Ask something...") },
modifier = Modifier.fillMaxWidth(),
placeholder = { Text("e.g., What's the weather in Tokyo?") }
)
Button(
onClick = {
scope.launch {
isProcessing = true
response = ""
toolLog = ""
val options = ToolCallingOptions(
maxToolCalls = 3,
autoExecute = true,
temperature = 0.7f,
maxTokens = 512
)
val result = RunAnywhereToolCalling.generateWithTools(prompt, options)
response = result.text
toolLog = result.toolCalls.joinToString("\n") { call ->
"Called: ${call.toolName}(${call.arguments.entries.joinToString { "${it.key}=${it.value.stringValue}" }})"
}
if (result.toolResults.isNotEmpty()) {
toolLog += "\n\nResults:\n"
toolLog += result.toolResults.joinToString("\n") { tr ->
if (tr.success) " OK: ${tr.result}" else " Error: ${tr.error}"
}
}
isProcessing = false
}
},
enabled = prompt.isNotBlank() && !isProcessing,
modifier = Modifier.fillMaxWidth()
) {
Text(if (isProcessing) "Processing..." else "Ask with Tools")
}
if (response.isNotEmpty()) {
Card(modifier = Modifier.fillMaxWidth()) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Response", style = MaterialTheme.typography.titleSmall)
Spacer(modifier = Modifier.height(8.dp))
Text(response)
}
}
}
if (toolLog.isNotEmpty()) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("Tool Execution Log", style = MaterialTheme.typography.titleSmall)
Spacer(modifier = Modifier.height(8.dp))
Text(toolLog, fontFamily = FontFamily.Monospace, fontSize = 12.sp)
}
}
}
}
}
private fun registerWeatherTool() {
RunAnywhereToolCalling.registerTool(
ToolDefinition(
name = "get_weather",
description = "Get current weather for a city",
parameters = listOf(
ToolParameter(
name = "city",
type = ToolParameterType.STRING,
description = "City name",
required = true
)
),
category = "Utility"
)
) { args ->
val city = args["city"]?.stringValue ?: "Unknown"
mapOf(
"temperature" to ToolValue.number(72.0),
"condition" to ToolValue.string("sunny"),
"city" to ToolValue.string(city)
)
}
}
private fun registerCalculatorTool() {
RunAnywhereToolCalling.registerTool(
ToolDefinition(
name = "calculate",
description = "Evaluate a math expression",
parameters = listOf(
ToolParameter(
name = "expression",
type = ToolParameterType.STRING,
description = "Math expression (e.g., '2 + 3 * 4')",
required = true
)
),
category = "Utility"
)
) { args ->
val expr = args["expression"]?.stringValue ?: "0"
try {
val engine = javax.script.ScriptEngineManager().getEngineByName("js")
val result = engine.eval(expr) as Number
mapOf("result" to ToolValue.number(result.toDouble()))
} catch (e: Exception) {
mapOf("error" to ToolValue.string("Invalid expression"))
}
}
}
private fun registerTimeTool() {
RunAnywhereToolCalling.registerTool(
ToolDefinition(
name = "get_current_time",
description = "Get the current date and time",
parameters = emptyList(),
category = "Utility"
)
) { _ ->
val formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
val now = java.time.LocalDateTime.now().format(formatter)
mapOf("datetime" to ToolValue.string(now))
}
}
Error Handling
try {
val result = RunAnywhereToolCalling.generateWithTools(prompt, options)
// Check individual tool results for errors
result.toolResults.forEach { toolResult ->
if (!toolResult.success) {
Log.w("ToolCalling", "Tool failed: ${toolResult.error}")
}
}
displayResponse(result.text)
} catch (e: Exception) {
when {
e.message?.contains("model not loaded") == true -> {
showError("Please load an LLM model before using tool calling.")
}
e.message?.contains("no tools registered") == true -> {
showError("Register at least one tool before calling generateWithTools.")
}
else -> {
showError("Tool calling error: ${e.message}")
}
}
}
Tool executors run synchronously in the generation loop. Avoid long-running operations (network
calls, heavy I/O) inside tool executors — they will block inference until complete.
Best Practices
- Write clear descriptions — the LLM uses
description fields to decide when and how to call
tools. Be specific about inputs and outputs. - Keep tool responses small — return only the
data the LLM needs to formulate a response. Large payloads waste context window tokens. - Set
maxToolCalls conservatively — prevents runaway loops where the model repeatedly calls tools. -
Validate arguments — always handle missing or malformed arguments with defaults or error
values rather than throwing exceptions. - Use categories — grouping tools by category helps
organize related functionality.