Function Calling Guide
View SourceFunction calling enables Gemini to interact with external systems by generating structured function calls that your application executes.
Overview
Function calling works in three steps:
- Declare functions - Tell Gemini what functions are available
- Receive calls - Gemini generates function calls in its response
- Return results - Execute functions and return results to continue the conversation
Quick Start
alias Altar.ADM.FunctionDeclaration
alias Gemini.Tools.Executor
alias Gemini.APIs.Coordinator
# 1. Declare your function
{:ok, weather_fn} = FunctionDeclaration.new(
name: "get_weather",
description: "Get the current weather for a location",
parameters: %{
type: "object",
properties: %{
"location" => %{type: "string", description: "City name or coordinates"}
},
required: ["location"]
}
)
# 2. Create a function registry
registry = Executor.create_registry(
get_weather: fn args ->
location = args["location"]
# Your actual weather API call here
"Sunny, 72°F in #{location}"
end
)
# 3. Generate with tools
{:ok, response} = Coordinator.generate_content(
"What's the weather in San Francisco?",
tools: [weather_fn]
)
# 4. Check for function calls
if Coordinator.has_function_calls?(response) do
calls = Coordinator.extract_function_calls(response)
results = Executor.execute_all(calls, registry)
IO.inspect(results)
endDefining Functions
Using FunctionDeclaration
The Altar.ADM.FunctionDeclaration struct defines a function's contract:
{:ok, fn_decl} = FunctionDeclaration.new(
name: "search_database",
description: "Search the product database",
parameters: %{
type: "object",
properties: %{
"query" => %{
type: "string",
description: "Search query"
},
"limit" => %{
type: "integer",
description: "Maximum results to return"
},
"category" => %{
type: "string",
description: "Filter by category",
enum: ["electronics", "clothing", "home"]
}
},
required: ["query"]
}
)Using Schema Type
For more complex schemas, use Gemini.Types.Schema:
alias Gemini.Types.Schema
# Simple string parameter
name_schema = Schema.string("Person's full name")
# Object with nested properties
address_schema = Schema.object(%{
"street" => Schema.string("Street address"),
"city" => Schema.string("City name"),
"zip" => Schema.string("ZIP code")
}, required: ["city"])
# Array of items
tags_schema = Schema.array(
Schema.string("Tag name"),
"List of tags"
)
# Complex nested schema
person_schema = Schema.object(%{
"name" => name_schema,
"address" => address_schema,
"tags" => tags_schema
}, required: ["name"])
# Convert to API format for function parameters
params = Schema.to_api_map(person_schema)Executing Function Calls
Manual Execution
alias Gemini.Tools.Executor
# Create registry
registry = %{
"get_weather" => fn args -> fetch_weather(args["location"]) end,
"search" => fn args -> search_database(args["query"]) end
}
# Execute calls
{:ok, response} = Coordinator.generate_content("...", tools: tools)
calls = Coordinator.extract_function_calls(response)
for call <- calls do
case Executor.execute(call, registry) do
{:ok, result} ->
IO.puts("#{call.name}: #{inspect(result)}")
{:error, reason} ->
IO.puts("Error in #{call.name}: #{inspect(reason)}")
end
endBatch Execution
# Sequential execution
results = Executor.execute_all(calls, registry)
# Parallel execution (for I/O-bound operations)
results = Executor.execute_all_parallel(calls, registry)Building Responses
After executing functions, build responses for the next API call:
responses = Executor.build_responses(calls, results)
# responses is a list of FunctionResponse structs
# Use these to continue the conversationAutomatic Function Calling (AFC)
AFC automatically handles the execute-and-continue loop:
alias Gemini.Tools.AutomaticFunctionCalling, as: AFC
# Configure AFC
config = AFC.config(
max_calls: 10, # Maximum function calls before stopping
parallel_execution: true # Execute calls in parallel
)
# Define generate function
generate_fn = fn contents, opts ->
Coordinator.generate_content(contents, opts)
end
# Initial request
{:ok, response} = Coordinator.generate_content(
"What's the weather in NYC and LA?",
tools: tools
)
# Run AFC loop
{final_response, call_count, history} = AFC.loop(
response,
[%{role: "user", parts: [%{text: "What's the weather in NYC and LA?"}]}],
registry,
config,
0, # initial call count
[], # initial history
generate_fn,
[tools: tools] # opts for generate_fn
)
IO.puts("Made #{call_count} function calls")
IO.puts("Final response: #{inspect(final_response)}")Multi-Turn Conversations
Function calling naturally fits into multi-turn conversations:
# Turn 1: User asks a question
{:ok, response1} = Coordinator.generate_content(
"What's the weather like?",
tools: tools
)
# Turn 2: Execute function calls and continue
if Coordinator.has_function_calls?(response1) do
calls = Coordinator.extract_function_calls(response1)
results = Executor.execute_all(calls, registry)
# Build conversation history
user_content = %{role: "user", parts: [%{text: "What's the weather like?"}]}
model_content = %{role: "model", parts: response1.candidates |> hd() |> Map.get(:content) |> Map.get(:parts)}
function_content = AFC.build_function_response_content(calls, results)
# Continue conversation
{:ok, response2} = Coordinator.generate_content(
[user_content, model_content, function_content],
tools: tools
)
endError Handling
Unknown Functions
case Executor.execute(call, registry) do
{:ok, result} ->
# Success
result
{:error, {:unknown_function, name}} ->
# Function not in registry
Logger.error("Unknown function: #{name}")
{:error, {:execution_error, exception}} ->
# Function raised an exception
Logger.error("Execution error: #{Exception.message(exception)}")
endAFC Limits
config = AFC.config(max_calls: 5)
{response, call_count, _} = AFC.loop(...)
if call_count >= 5 do
Logger.warn("AFC loop reached maximum calls")
endBest Practices
1. Keep Functions Focused
Each function should do one thing well:
# Good: Focused functions
{:ok, _} = FunctionDeclaration.new(
name: "get_user",
description: "Get a user by ID",
parameters: %{type: "object", properties: %{"user_id" => %{type: "string"}}}
)
# Avoid: Functions that do too much
# "manage_user" that creates, updates, and deletes2. Write Clear Descriptions
Gemini uses descriptions to understand when to call functions:
# Good: Clear, specific description
{:ok, _} = FunctionDeclaration.new(
name: "search_orders",
description: "Search customer orders by date range, status, or order ID. Returns order summaries including total, items, and shipping status.",
parameters: %{...}
)
# Avoid: Vague descriptions
# description: "Search stuff"3. Validate Parameters
The Schema type enforces constraints:
# Use enum for known values
status_schema = Schema.string("Order status", enum: ["pending", "shipped", "delivered"])
# Use minimum/maximum for ranges
count_schema = Schema.integer("Item count", minimum: 1, maximum: 100)4. Handle Errors Gracefully
Always handle execution errors in responses:
# The Executor automatically builds error responses
responses = Executor.build_responses(calls, results)
# Error results become: %{error: "Error message"}Complete Example
Here's a complete example with a calculator agent:
defmodule CalculatorAgent do
alias Altar.ADM.FunctionDeclaration
alias Gemini.Tools.{Executor, AutomaticFunctionCalling}
alias Gemini.APIs.Coordinator
def run(question) do
tools = build_tools()
registry = build_registry()
config = AutomaticFunctionCalling.config(max_calls: 5)
{:ok, response} = Coordinator.generate_content(
question,
tools: tools
)
generate_fn = fn contents, opts ->
Coordinator.generate_content(contents, opts)
end
{final_response, _, _} = AutomaticFunctionCalling.loop(
response,
[%{role: "user", parts: [%{text: question}]}],
registry,
config,
0,
[],
generate_fn,
[tools: tools]
)
case Coordinator.extract_text(final_response) do
{:ok, text} -> text
_ -> "Unable to get response"
end
end
defp build_tools do
[
elem(FunctionDeclaration.new(
name: "add",
description: "Add two numbers",
parameters: %{
type: "object",
properties: %{
"a" => %{type: "number"},
"b" => %{type: "number"}
},
required: ["a", "b"]
}
), 1),
elem(FunctionDeclaration.new(
name: "multiply",
description: "Multiply two numbers",
parameters: %{
type: "object",
properties: %{
"a" => %{type: "number"},
"b" => %{type: "number"}
},
required: ["a", "b"]
}
), 1)
]
end
defp build_registry do
Executor.create_registry(
add: fn args -> args["a"] + args["b"] end,
multiply: fn args -> args["a"] * args["b"] end
)
end
end
# Usage
result = CalculatorAgent.run("What is 5 + 3 multiplied by 2?")
IO.puts(result)See Also
- System Instructions Guide - Setting persistent system prompts
- Structured Outputs Guide - Getting structured JSON responses
- Streaming Guide - Real-time streaming responses