Native Function Calling with FunctionGemma in KerasHub
Author: Laxmareddy Patlolla
Date created: 2026/02/24
Last modified: 2026/03/06
Description: A guide to using the function calling feature in KerasHub with FunctionGemma.
View in Colab •
GitHub source
Introduction
This example demonstrates how to build a multi-tool AI Assistant using FunctionGemma native function calling capabilities in KerasHub. The Assistant can execute multiple tools including web search, stock prices, world time, and system stats.
Overview
Function calling enables language models to interact with external APIs and tools by generating structured function calls. This implementation uses FunctionGemma native function calling format with proper tool declarations and function call parsing.
Architecture
The Assistant follows this flow:
Tool Definition: Tools are defined in Function Calling Format JSON format
Prompt Formatting: Tools are injected into the prompt using FunctionGemma template
Model Generation: The model generates a structured function call
Parsing: Extract function name and arguments from the response
Execution: Execute the corresponding Python function
Response: Return formatted results to the user
Key Components
TOOL_DEFINITIONS: Function Calling Format tool schemas
format_prompt_with_tools(): Manual chat template implementation
parse_function_call(): Extract function calls from model output
TOOLS: Registry mapping tool names to Python functions
Setup
Before starting this tutorial, complete the following steps:
pip install -U keras-hub
pip install yfinance ddgs psutil pytz
Get access to FunctionGemma by logging into your Kaggle account and selecting Acknowledge license for a FunctionGemma model.
Generate a Kaggle Access Token by visiting kaggle settings page and add it to your Colab Secrets:
KAGGLE_USERNAME = "Your kaggle user name"
KAGGLE_KEY=" Your kaggle Key"
This notebook will run on either CPU or GPU.
Install Python packages
import os
import datetime
import psutil
import yfinance as yf
from ddgs import DDGS
import keras_hub
import re
import pytz
import warnings
warnings.filterwarnings("ignore")
os.environ["KERAS_BACKEND"] = "jax"
We'll start by defining the actual Python functions that our Assistant will be able to call. Each of these functions represents a "tool" that extends the model's capabilities beyond just text generation.
This function allows the model to search the web for information using DuckDuckGo. In a real application, you might use more sophisticated search APIs or custom search implementations tailored to your domain.
def web_search(query, max_results=5):
"""Search the web using DuckDuckGo.
Args:
query: Search query string
max_results: Maximum number of results to return (default: 5)
Returns:
Dictionary with "results" key containing list of search results,
each with "title", "description", and "link" keys.
Returns {"error": message} on failure.
"""
results = []
try:
with DDGS() as ddgs:
for r in ddgs.text(query, max_results=max_results):
results.append(
{
"title": r.get("title"),
"description": r.get("body"),
"link": r.get("href"),
}
)
except Exception as e:
return {"error": f"Search failed: {str(e)}"}
return {"results": results}
This function retrieves real-time stock prices from Yahoo Finance. The model can use this to answer questions about current market prices for publicly traded companies.
def get_stock_price(symbol):
"""Get real-time stock price from Yahoo Finance.
Args:
symbol: Stock ticker symbol (e.g., "AAPL", "GOOGL")
Returns:
Dictionary with "symbol", "price", and "currency" keys.
Returns {"error": message} on failure.
"""
try:
t = yf.Ticker(symbol)
info = t.fast_info
if info.last_price is None:
raise ValueError("Price not available")
current_date = datetime.datetime.now().strftime("%Y-%m-%d")
return {
"symbol": symbol,
"price": round(info.last_price, 2),
"currency": info.currency,
"date": current_date,
}
except Exception as e:
return {"error": f"Failed to get stock price for '{symbol}'. Reason: {e}"}
This function provides information about the current system's CPU and memory usage. It demonstrates how the model can interact with the local environment.
def get_system_stats(**kwargs):
"""Get CPU and memory usage.
Args:
**kwargs: Ignore any additional arguments the model might hallucinate.
"""
mem = psutil.virtual_memory()
return {
"cpu_percent": psutil.cpu_percent(interval=1),
"mem_percent": mem.percent,
"used_gb": round(mem.used / 1e9, 2),
"total_gb": round(mem.total / 1e9, 2),
}
Time and Timezone Helpers
These functions work together to provide accurate time information for locations worldwide. We maintain a mapping of common locations to their timezone identifiers, then use Python's pytz library to calculate the current time.
TIMEZONE_MAP = {
"india": "Asia/Kolkata",
"usa": "America/New_York",
"uk": "Europe/London",
"japan": "Asia/Tokyo",
"china": "Asia/Shanghai",
"australia": "Australia/Sydney",
"france": "Europe/Paris",
"germany": "Europe/Berlin",
"russia": "Europe/Moscow",
"brazil": "America/Sao_Paulo",
"canada": "America/Toronto",
"mexico": "America/Mexico_City",
"spain": "Europe/Madrid",
"italy": "Europe/Rome",
"california": "America/Los_Angeles",
"new york": "America/New_York",
"texas": "America/Chicago",
"florida": "America/New_York",
"washington": "America/Los_Angeles",
"oregon": "America/Los_Angeles",
"nevada": "America/Los_Angeles",
"arizona": "America/Phoenix",
"colorado": "America/Denver",
"utah": "America/Denver",
"illinois": "America/Chicago",
"michigan": "America/Detroit",
"massachusetts": "America/New_York",
"pennsylvania": "America/New_York",
"los angeles": "America/Los_Angeles",
"san francisco": "America/Los_Angeles",
"seattle": "America/Los_Angeles",
"chicago": "America/Chicago",
"boston": "America/New_York",
"miami": "America/New_York",
"london": "Europe/London",
"paris": "Europe/Paris",
"tokyo": "Asia/Tokyo",
"beijing": "Asia/Shanghai",
"shanghai": "Asia/Shanghai",
"dubai": "Asia/Dubai",
"singapore": "Asia/Singapore",
"hong kong": "Asia/Hong_Kong",
"seoul": "Asia/Seoul",
"moscow": "Europe/Moscow",
"sydney": "Australia/Sydney",
"mumbai": "Asia/Kolkata",
"delhi": "Asia/Kolkata",
"berlin": "Europe/Berlin",
"rome": "Europe/Rome",
"madrid": "Europe/Madrid",
"toronto": "America/Toronto",
"vancouver": "America/Vancouver",
"montreal": "America/Toronto",
"utc": "UTC",
"gmt": "GMT",
}
def get_timezone_for_location(location):
"""Map location to timezone."""
loc = location.lower().strip()
return TIMEZONE_MAP.get(loc)
This function gets the current time for a specific location. It uses the helper function get_timezone_for_location to determine the correct timezone and then returns the formatted time, date, and day.
def get_current_time(time_location=None):
"""Get current time for a location."""
if not time_location:
time_location = "UTC"
try:
timezone = get_timezone_for_location(time_location)
note = ""
if not timezone:
timezone = "UTC"
note = (
f"Location '{time_location}' not found in database. Showing UTC time."
)
tz = pytz.timezone(timezone)
now = datetime.datetime.now(tz)
return {
"location": time_location if not note else "UTC",
"time": now.strftime("%H:%M:%S"),
"date": now.strftime("%Y-%m-%d"),
"day": now.strftime("%A"),
"timezone": timezone,
"note": note,
}
except Exception as e:
return {"error": str(e)}
Now we need to tell the model about these tools. We do this by defining tool schemas in a format that the model can understand. Each tool definition includes:
This format follows the standard function calling convention used by FunctionGemma.
TOOL_DEFINITIONS = [
{
"type": "function",
"function": {
"name": "web_search",
"description": "Search the web for information.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"],
},
},
},
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "Get the current stock price for a given ticker symbol",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "Stock ticker symbol (e.g., AAPL, GOOGL, TSLA)",
}
},
"required": ["symbol"],
},
},
},
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "Get the current clock time for a specific city or country.",
"parameters": {
"type": "object",
"properties": {
"time_location": {
"type": "string",
"description": "The city or country for which to get the time, e.g., 'Tokyo' or 'Japan'",
}
},
"required": ["time_location"],
},
},
},
{
"type": "function",
"function": {
"name": "get_system_stats",
"description": "Get current CPU and memory usage statistics",
"parameters": {"type": "object", "properties": {}, "required": []},
},
},
]
This dictionary maps tool names (as strings) to their actual Python function implementations. When the model generates a function call like get_stock_price, we use this registry to look up and execute the corresponding Python function.
This pattern allows for dynamic tool execution - we can easily add or remove tools by updating both TOOL_DEFINITIONS and this registry.
TOOLS = {
"web_search": web_search,
"get_stock_price": get_stock_price,
"get_current_time": get_current_time,
"get_system_stats": get_system_stats,
}
Helper Functions For Function Calling
The following functions handle the mechanics of function calling:
Parsing function calls from model output
Formatting tool declarations for the prompt
Constructing the full conversation prompt
Formatting tool results for display
Function Call Parser
This function is responsible for extracting the structured function call from the model's text output. It looks for the special <start_function_call> tags and parses the function name and arguments into a Python dictionary.
FUNCTION_CALL_PATTERN = re.compile(
r"<start_function_call>call:(\w+)\{([^}]*)\}<end_function_call>"
)
ARGUMENT_PATTERN = re.compile(
r'(\w+):\s*("(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\'|[^,]+)'
)
def parse_function_call(output):
"""Parse function call from FunctionGemma model output.
Extracts function name and arguments from the special FunctionGemma function calling
format: <start_function_call>call:tool_name{arg1:value1}<end_function_call>
Args:
output: Raw model output string
Returns:
Dictionary with "name" (function name) and "arguments" (dict of args),
"""
match = FUNCTION_CALL_PATTERN.search(output)
if not match:
return None
tool_name = match.group(1)
args_str = match.group(2).strip()
args = {}
if args_str:
arg_pairs = ARGUMENT_PATTERN.findall(args_str)
for key, value in arg_pairs:
value = value.strip()
value = value.replace("<escape>", "").replace("</escape>", "")
if value.startswith('"') and value.endswith('"'):
value = value[1:-1]
elif value.startswith("'") and value.endswith("'"):
value = value[1:-1]
args[key] = value
return {"name": tool_name, "arguments": args}
This function converts our Python tool definitions into the specific format expected by FunctionGemma. It handles the mapping of types and descriptions to the declaration:.. string format used in the system prompt.
def format_tool_declaration(tool):
"""Format a tool declaration for FunctionGemma function calling."""
func = tool["function"]
params = func.get("parameters", {})
props = params.get("properties", {})
required = params.get("required", [])
param_parts = []
for name, details in props.items():
param_str = f"{name}:{{description:<escape>{details.get('description', '')}<escape>,type:<escape>{details['type'].upper()}<escape>}}"
param_parts.append(param_str)
properties_str = ",".join(param_parts) if param_parts else ""
required_str = ",".join(f"<escape>{r}<escape>" for r in required)
declaration = f"declaration:{func['name']}"
declaration += f"{{description:<escape>{func['description']}<escape>"
if properties_str or required_str:
declaration += ",parameters:{"
if properties_str:
declaration += f"properties:{{{properties_str}}}"
if required_str:
if properties_str:
declaration += ","
declaration += f"required:[{required_str}]"
declaration += ",type:<escape>OBJECT<escape>}"
declaration += "}"
return declaration
Prompt Constructor
This is the core function for building the prompt. It combines:
The system prompt with tool declarations
Few-shot examples to guide the model
The conversation history (user and assistant messages)
The final generation prompt
def format_prompt_with_tools(messages, tools):
"""Manually construct FunctionGemma function calling prompt.
This function replicates the behavior of HuggingFace's apply_chat_template()
for FunctionGemma native function calling format, since KerasHub doesn't yet
provide this functionality.
Args:
messages: List of message dicts with "role" and "content" keys
tools: List of tool definition dicts in OpenAI format
Returns:
Formatted prompt string with tool declarations and conversation history
"""
prompt = "<bos>"
if tools:
prompt += "<start_of_turn>developer\n"
for tool in tools:
prompt += "<start_function_declaration>"
prompt += format_tool_declaration(tool)
prompt += "<end_function_declaration>"
prompt += "\nHere are some examples of how to use the tools correctly:\n"
prompt += "User: Who is Isaac Newton?\n"
prompt += "Model: <start_function_call>call:web_search{query:Isaac Newton}<end_function_call>\n"
prompt += "User: What is the time in USA?\n"
prompt += "Model: <start_function_call>call:get_current_time{time_location:USA}<end_function_call>\n"
prompt += "User: Stock price of Apple\n"
prompt += "Model: <start_function_call>call:get_stock_price{symbol:AAPL}<end_function_call>\n"
prompt += "User: Who is the PM of Japan?\n"
prompt += "Model: <start_function_call>call:web_search{query:PM of Japan}<end_function_call>\n"
prompt += "User: Check the weather in London\n"
prompt += "Model: <start_function_call>call:web_search{query:weather in London}<end_function_call>\n"
prompt += "User: Latest news about AI\n"
prompt += "Model: <start_function_call>call:web_search{query:Latest news about AI}<end_function_call>\n"
prompt += "User: System memory usage\n"
prompt += (
"Model: <start_function_call>call:get_system_stats{}<end_function_call>\n"
)
prompt += "<end_of_turn>\n"
for message in messages:
role = message["role"]
if role == "assistant":
role = "model"
content = message.get("content", "")
prompt += f"<start_of_turn>{role}\n"
prompt += content
prompt += "<end_of_turn>\n"
prompt += "<start_of_turn>model\n"
return prompt
After a tool is executed, this function formats the result back into a string that can be displayed to the user or fed back into the model. It handles specific formatting for different tools (like adding currency to stock prices).
def format_tool_response(tool_name, result):
"""Format tool result for conversation."""
if "error" in result:
return f"Error: {result['error']}"
if "results" in result:
results = result.get("results", [])
if results:
output = f"Found {len(results)} results:\n"
for i, r in enumerate(results[:3], 1):
output += f"\n{i}. {r['title']}\n {r['description'][:150]}...\n {r['link']}"
return output
return "No results found"
if tool_name == "get_stock_price":
return f"Stock {result['symbol']}: ${result['price']} {result['currency']} (as of {result['date']})"
elif tool_name == "get_current_time":
response = f"Time in {result['location']}: {result['time']} ({result['day']}, {result['date']})\nTimezone: {result['timezone']}"
if result.get("note"):
response += f"\nNote: {result['note']}"
return response
elif tool_name == "get_system_stats":
return f"CPU: {result['cpu_percent']}% | Memory: {result['mem_percent']}% ({result['used_gb']}/{result['total_gb']}GB)"
return str(result)
Interactive Chat Loop
This section implements the main conversation loop. The agent:
Formats the prompt with tool declarations
Generates a response (which may include a function call)
Executes the function if called
Returns the result to the user
After each successful tool execution, we clear the conversation history to prevent token overflow and keep the model focused.
Model Loading
This function loads the FunctionGemma, a specialized version of our Gemma 3 270M model tuned for function calling using KerasHub's from_preset method. You can load it from Kaggle using the preset name (e.g. "function_gemma_instruct_270m") or from a local path if you've downloaded the model to your machine.
Update the path to match your model location. The model is loaded once at startup and then used throughout the chat session.
def load_model():
"""Load FunctionGemma 270M model."""
print("Loading FunctionGemma 270M model...")
model = keras_hub.models.Gemma3CausalLM.from_preset("function_gemma_instruct_270m")
print("Model loaded!\n")
return model
This is the main function that runs the interactive conversation with the user.
It handles the complete cycle of:
Displaying capabilities and waiting for user input
Formatting the prompt with tool declarations and conversation history
Generating a response from the model
Parsing any function calls from the response
Executing the requested tools
Formatting and displaying results to the user
Managing conversation history to prevent token overflow
The loop continues until the user types 'exit', 'quit', or 'q'.
def chat(model):
"""Main chat loop with native function calling."""
print("=" * 70)
print(" " * 20 + "FUNCTION CALLING")
print("=" * 70)
print("\nCapabilities:")
print(" 🔍 Web Search - e.g., 'Who is Newton?', 'Latest AI news'")
print(" 📈 Stock Prices - e.g., 'Stock price of AAPL', 'TSLA price'")
print(
" 🕐 World Time - e.g., 'Time in London', 'What time is it in Tokyo?'"
)
print(" 💻 System Stats - e.g., 'System memory', 'CPU usage'")
print("\nType 'exit' to quit.\n")
print("-" * 70)
messages = []
while True:
try:
user_input = input("\nYou: ").strip()
except (EOFError, KeyboardInterrupt):
print("\n\nGoodbye! 👋")
break
if user_input.lower() in {"exit", "quit", "q"}:
print("\nGoodbye! 👋")
break
if not user_input:
continue
messages.append({"role": "user", "content": user_input})
print("🤔 Thinking...", end="", flush=True)
try:
prompt = format_prompt_with_tools(messages, TOOL_DEFINITIONS)
output = model.generate(prompt, max_length=2048)
response = output[len(prompt) :].strip()
if "<start_function_response>" in response:
response = response.split("<start_function_response>")[0].strip()
if "<start_of_turn>" in response:
response = response.split("<start_of_turn>")[0].strip()
response = response.replace("<end_of_turn>", "").strip()
print(f"\r{' ' * 40}\r", end="")
print(f"[DEBUG] Raw Model Response: {response}")
function_call = parse_function_call(response)
if function_call:
tool_name = function_call["name"]
tool_args = function_call["arguments"]
print(f"🔧 Calling {tool_name}...", end="", flush=True)
if tool_name in TOOLS:
try:
result = TOOLS[tool_name](**tool_args)
formatted_result = format_tool_response(tool_name, result)
print(f"\r{' ' * 40}\r", end="")
print(f"\nAssistant: {formatted_result}")
messages = []
except Exception as e:
print(f"\r❌ Error executing {tool_name}: {e}")
messages.pop()
else:
print(f"\r❌ Unknown tool: {tool_name}")
messages.pop()
else:
if not response or response == "...":
print(
f"\nAssistant: I'm not sure how to help with that. Please try rephrasing your question."
)
messages.pop()
else:
print(f"\nAssistant: {response}")
messages.append({"role": "assistant", "content": response})
if len(messages) > 6:
messages = messages[-6:]
except Exception as e:
print(f"\r❌ Error: {e}")
messages.pop()
continue
This is where the program starts execution. When you run this script:
The FunctionGemma model is loaded from the specified path
The interactive chat loop begins, waiting for user input
The Assistant processes queries and executes tools until the user types 'exit'
if __name__ == "__main__":
model = load_model()
chat(model)
Conclusion
What You've Achieved
By following this guide, you've learned how to build a production-ready AI Assistant with native function calling capabilities using FunctionGemma in KerasHub. Here's what you've accomplished:
Native Function Calling Implementation:
Implemented FunctionGemma native function calling format without relying on external APIs
Learned to construct proper tool declarations and parse structured function calls
Built a manual chat template that replicates HuggingFace's apply_chat_template()
Created four distinct tools: web search, stock prices, world time, and system stats
Designed tools with clear error handling and user-friendly responses
Implemented explicit error messages.
Prompt Engineering Best Practices:
Used few-shot examples to guide model behavior and improve tool selection
Structured prompts with proper role separation (developer, user, model)
Managed conversation history to prevent token overflow
Error Handling and User Experience:
Implemented graceful degradation (e.g., UTC fallback for unknown timezones)
Provided clear, actionable error messages for invalid inputs
Added contextual information (dates, notes) to make responses more informative
Real-World Applications:
This pattern can be extended to build:
Customer service chatbots with database access
Data analysis assistants with computation tools
Personal productivity Assistants with calendar and email integration
Domain-specific assistants (medical, legal, financial) with specialized tools
Key Takeaways
Small models can be powerful: Even a 270M parameter model can reliably use tools when given proper guidance through few-shot examples and clear tool descriptions.
Transparency matters: Explicit error handling and clear responses build user trust more effectively.
Function calling is composable: The same pattern works for any number of tools, making it easy to extend your Assistant's capabilities.
Next Steps
To further improve this agent, consider:
Fine-tuning the model on domain-specific tool usage examples to optimize performance.
Adding authentication and rate limiting for production deployment.
Implementing tool result caching to reduce API calls.
Creating a web interface using Gradio or Streamlit.
Adding more sophisticated error recovery strategies.
Happy building! 🚀