Path: blob/master/guides/keras_hub/function_gemma_with_keras.py
17129 views
"""1Title: Native Function Calling with FunctionGemma in KerasHub2Author: [Laxmareddy Patlolla](https://github.com/laxmareddyp)3Date created: 2026/02/244Last modified: 2026/03/065Description: A guide to using the function calling feature in KerasHub with FunctionGemma.6Accelerator: GPU7"""89"""10## Introduction1112This example demonstrates how to build a multi-tool AI Assistant using FunctionGemma native13function calling capabilities in KerasHub. The Assistant can execute multiple tools14including web search, stock prices, world time, and system stats.1516## Overview1718Function calling enables language models to interact with19external APIs and tools by generating structured function calls. This implementation20uses FunctionGemma native function calling format with proper tool declarations and21function call parsing.2223## Architecture2425The Assistant follows this flow:2627- **Tool Definition**: Tools are defined in Function Calling Format JSON format28- **Prompt Formatting**: Tools are injected into the prompt using FunctionGemma template29- **Model Generation**: The model generates a structured function call30- **Parsing**: Extract function name and arguments from the response31- **Execution**: Execute the corresponding Python function32- **Response**: Return formatted results to the user3334## Key Components3536- `TOOL_DEFINITIONS`: Function Calling Format tool schemas37- `format_prompt_with_tools()`: Manual chat template implementation38- `parse_function_call()`: Extract function calls from model output39- `TOOLS`: Registry mapping tool names to Python functions4041## Setup42Before starting this tutorial, complete the following steps:43```44pip install -U keras-hub45pip install yfinance ddgs psutil pytz46```4748Get access to `FunctionGemma` by logging into your [Kaggle](https://www.kaggle.com/models/keras/function-gemma) account and selecting Acknowledge license for a FunctionGemma model.4950Generate a `Kaggle Access Token` by visiting kaggle [settings](https://www.kaggle.com/settings) page and add it to your Colab Secrets:5152`KAGGLE_USERNAME = "Your kaggle user name"`5354`KAGGLE_KEY=" Your kaggle Key"`5556This notebook will run on either CPU or GPU.5758## Install Python packages59"""6061import os62import datetime63import psutil64import yfinance as yf65from ddgs import DDGS66import keras_hub67import re68import pytz69import warnings7071warnings.filterwarnings("ignore")7273os.environ["KERAS_BACKEND"] = "jax"7475"""76# Tool Implementations7778We'll start by defining the actual Python functions that our Assistant will be able79to call. Each of these functions represents a "tool" that extends the model's80capabilities beyond just text generation.8182## Web Search Tool8384This function allows the model to search the web for information using DuckDuckGo.85In a real application, you might use more sophisticated search APIs or custom86search implementations tailored to your domain.87"""888990def web_search(query, max_results=5):91"""Search the web using DuckDuckGo.9293Args:94query: Search query string95max_results: Maximum number of results to return (default: 5)9697Returns:98Dictionary with "results" key containing list of search results,99each with "title", "description", and "link" keys.100Returns {"error": message} on failure.101"""102results = []103try:104with DDGS() as ddgs:105for r in ddgs.text(query, max_results=max_results):106results.append(107{108"title": r.get("title"),109"description": r.get("body"),110"link": r.get("href"),111}112)113except Exception as e:114return {"error": f"Search failed: {str(e)}"}115return {"results": results}116117118"""119## Stock Price Tool120121This function retrieves real-time stock prices from Yahoo Finance. The model122can use this to answer questions about current market prices for publicly123traded companies.124"""125126127def get_stock_price(symbol):128"""Get real-time stock price from Yahoo Finance.129130Args:131symbol: Stock ticker symbol (e.g., "AAPL", "GOOGL")132133Returns:134Dictionary with "symbol", "price", and "currency" keys.135Returns {"error": message} on failure.136"""137try:138t = yf.Ticker(symbol)139info = t.fast_info140if info.last_price is None:141raise ValueError("Price not available")142current_date = datetime.datetime.now().strftime("%Y-%m-%d")143return {144"symbol": symbol,145"price": round(info.last_price, 2),146"currency": info.currency,147"date": current_date,148}149except Exception as e:150return {"error": f"Failed to get stock price for '{symbol}'. Reason: {e}"}151152153"""154## System Statistics Tool155156This function provides information about the current system's CPU and memory157usage. It demonstrates how the model can interact with the local environment.158"""159160161def get_system_stats(**kwargs):162"""Get CPU and memory usage.163164Args:165**kwargs: Ignore any additional arguments the model might hallucinate.166"""167168mem = psutil.virtual_memory()169return {170"cpu_percent": psutil.cpu_percent(interval=1),171"mem_percent": mem.percent,172"used_gb": round(mem.used / 1e9, 2),173"total_gb": round(mem.total / 1e9, 2),174}175176177"""178## Time and Timezone Helpers179180These functions work together to provide accurate time information for locations181worldwide. We maintain a mapping of common locations to their timezone identifiers,182then use Python's pytz library to calculate the current time.183"""184185186TIMEZONE_MAP = {187# Countries188"india": "Asia/Kolkata",189"usa": "America/New_York",190"uk": "Europe/London",191"japan": "Asia/Tokyo",192"china": "Asia/Shanghai",193"australia": "Australia/Sydney",194"france": "Europe/Paris",195"germany": "Europe/Berlin",196"russia": "Europe/Moscow",197"brazil": "America/Sao_Paulo",198"canada": "America/Toronto",199"mexico": "America/Mexico_City",200"spain": "Europe/Madrid",201"italy": "Europe/Rome",202# US States & Major Cities203"california": "America/Los_Angeles",204"new york": "America/New_York",205"texas": "America/Chicago",206"florida": "America/New_York",207"washington": "America/Los_Angeles",208"oregon": "America/Los_Angeles",209"nevada": "America/Los_Angeles",210"arizona": "America/Phoenix",211"colorado": "America/Denver",212"utah": "America/Denver",213"illinois": "America/Chicago",214"michigan": "America/Detroit",215"massachusetts": "America/New_York",216"pennsylvania": "America/New_York",217# Major World Cities218"los angeles": "America/Los_Angeles",219"san francisco": "America/Los_Angeles",220"seattle": "America/Los_Angeles",221"chicago": "America/Chicago",222"boston": "America/New_York",223"miami": "America/New_York",224"london": "Europe/London",225"paris": "Europe/Paris",226"tokyo": "Asia/Tokyo",227"beijing": "Asia/Shanghai",228"shanghai": "Asia/Shanghai",229"dubai": "Asia/Dubai",230"singapore": "Asia/Singapore",231"hong kong": "Asia/Hong_Kong",232"seoul": "Asia/Seoul",233"moscow": "Europe/Moscow",234"sydney": "Australia/Sydney",235"mumbai": "Asia/Kolkata",236"delhi": "Asia/Kolkata",237"berlin": "Europe/Berlin",238"rome": "Europe/Rome",239"madrid": "Europe/Madrid",240"toronto": "America/Toronto",241"vancouver": "America/Vancouver",242"montreal": "America/Toronto",243# Special244"utc": "UTC",245"gmt": "GMT",246}247248249def get_timezone_for_location(location):250"""Map location to timezone."""251252loc = location.lower().strip()253return TIMEZONE_MAP.get(loc)254255256"""257## World Time Tool258259This function gets the current time for a specific location. It uses the260helper function `get_timezone_for_location` to determine the correct timezone261and then returns the formatted time, date, and day.262"""263264265def get_current_time(time_location=None):266"""Get current time for a location."""267268if not time_location:269time_location = "UTC"270271try:272timezone = get_timezone_for_location(time_location)273note = ""274275if not timezone:276# Default to UTC if location not found277timezone = "UTC"278note = (279f"Location '{time_location}' not found in database. Showing UTC time."280)281282tz = pytz.timezone(timezone)283now = datetime.datetime.now(tz)284return {285"location": time_location if not note else "UTC",286"time": now.strftime("%H:%M:%S"),287"date": now.strftime("%Y-%m-%d"),288"day": now.strftime("%A"),289"timezone": timezone,290"note": note,291}292except Exception as e:293return {"error": str(e)}294295296"""297## Tool Definitions (Function Calling Format)298299Now we need to tell the model about these tools. We do this by defining tool300schemas in a format that the model can understand. Each tool definition includes:301302- Name: The function name303- Description: What the function does (helps the model decide when to use it)304- Parameters: The arguments the function accepts305306This format follows the standard function calling convention used by FunctionGemma.307"""308309TOOL_DEFINITIONS = [310{311"type": "function",312"function": {313"name": "web_search",314"description": "Search the web for information.",315"parameters": {316"type": "object",317"properties": {318"query": {"type": "string", "description": "The search query"}319},320"required": ["query"],321},322},323},324{325"type": "function",326"function": {327"name": "get_stock_price",328"description": "Get the current stock price for a given ticker symbol",329"parameters": {330"type": "object",331"properties": {332"symbol": {333"type": "string",334"description": "Stock ticker symbol (e.g., AAPL, GOOGL, TSLA)",335}336},337"required": ["symbol"],338},339},340},341{342"type": "function",343"function": {344"name": "get_current_time",345"description": "Get the current clock time for a specific city or country.",346"parameters": {347"type": "object",348"properties": {349"time_location": {350"type": "string",351"description": "The city or country for which to get the time, e.g., 'Tokyo' or 'Japan'",352}353},354"required": ["time_location"],355},356},357},358{359"type": "function",360"function": {361"name": "get_system_stats",362"description": "Get current CPU and memory usage statistics",363"parameters": {"type": "object", "properties": {}, "required": []},364},365},366]367368"""369## Tool Registry370371This dictionary maps tool names (as strings) to their actual Python function372implementations. When the model generates a function call like `get_stock_price`,373we use this registry to look up and execute the corresponding Python function.374375This pattern allows for dynamic tool execution - we can easily add or remove tools376by updating both `TOOL_DEFINITIONS` and this registry.377"""378379# Tool registry380TOOLS = {381"web_search": web_search,382"get_stock_price": get_stock_price,383"get_current_time": get_current_time,384"get_system_stats": get_system_stats,385}386387"""388## Helper Functions For Function Calling389390The following functions handle the mechanics of function calling:391392- Parsing function calls from model output393- Formatting tool declarations for the prompt394- Constructing the full conversation prompt395- Formatting tool results for display396397## Function Call Parser398399This function is responsible for extracting the structured function call from400the model's text output. It looks for the special `<start_function_call>` tags401and parses the function name and arguments into a Python dictionary.402"""403404405FUNCTION_CALL_PATTERN = re.compile(406r"<start_function_call>call:(\w+)\{([^}]*)\}<end_function_call>"407)408ARGUMENT_PATTERN = re.compile(409r'(\w+):\s*("(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)*\'|[^,]+)'410)411412413def parse_function_call(output):414"""Parse function call from FunctionGemma model output.415416Extracts function name and arguments from the special FunctionGemma function calling417format: <start_function_call>call:tool_name{arg1:value1}<end_function_call>418419Args:420output: Raw model output string421422Returns:423Dictionary with "name" (function name) and "arguments" (dict of args),424425"""426match = FUNCTION_CALL_PATTERN.search(output)427428if not match:429return None430431tool_name = match.group(1)432args_str = match.group(2).strip()433434# Parse arguments435args = {}436if args_str:437# Matches key:"value" or key:'value' or key:value438arg_pairs = ARGUMENT_PATTERN.findall(args_str)439for key, value in arg_pairs:440value = value.strip()441442# Remove <escape> tags443value = value.replace("<escape>", "").replace("</escape>", "")444445# Remove quotes if present446if value.startswith('"') and value.endswith('"'):447value = value[1:-1]448elif value.startswith("'") and value.endswith("'"):449value = value[1:-1]450451args[key] = value452453return {"name": tool_name, "arguments": args}454455456"""457## Tool Declaration Formatter458459This function converts our Python tool definitions into the specific format460expected by `FunctionGemma`. It handles the mapping of types and descriptions to the461`declaration:..` string format used in the system prompt.462"""463464465def format_tool_declaration(tool):466"""Format a tool declaration for FunctionGemma function calling."""467468func = tool["function"]469params = func.get("parameters", {})470props = params.get("properties", {})471required = params.get("required", [])472473# Build parameter string474param_parts = []475for name, details in props.items():476param_str = f"{name}:{{description:<escape>{details.get('description', '')}<escape>,type:<escape>{details['type'].upper()}<escape>}}"477param_parts.append(param_str)478479properties_str = ",".join(param_parts) if param_parts else ""480required_str = ",".join(f"<escape>{r}<escape>" for r in required)481482# Build full declaration483declaration = f"declaration:{func['name']}"484declaration += f"{{description:<escape>{func['description']}<escape>"485486if properties_str or required_str:487declaration += ",parameters:{"488if properties_str:489declaration += f"properties:{{{properties_str}}}"490if required_str:491if properties_str:492declaration += ","493declaration += f"required:[{required_str}]"494declaration += ",type:<escape>OBJECT<escape>}"495496declaration += "}"497498return declaration499500501"""502## Prompt Constructor503504This is the core function for building the prompt. It combines:505506- The system prompt with tool declarations507- Few-shot examples to guide the model508- The conversation history (user and assistant messages)509- The final generation prompt510"""511512513def format_prompt_with_tools(messages, tools):514"""Manually construct FunctionGemma function calling prompt.515516This function replicates the behavior of HuggingFace's apply_chat_template()517for FunctionGemma native function calling format, since KerasHub doesn't yet518provide this functionality.519520Args:521messages: List of message dicts with "role" and "content" keys522tools: List of tool definition dicts in OpenAI format523524Returns:525Formatted prompt string with tool declarations and conversation history526"""527prompt = "<bos>"528529# Add tool declarations530if tools:531prompt += "<start_of_turn>developer\n"532for tool in tools:533prompt += "<start_function_declaration>"534prompt += format_tool_declaration(tool)535prompt += "<end_function_declaration>"536537# Add few-shot examples to help the model distinguish between tools538prompt += "\nHere are some examples of how to use the tools correctly:\n"539prompt += "User: Who is Isaac Newton?\n"540prompt += "Model: <start_function_call>call:web_search{query:Isaac Newton}<end_function_call>\n"541prompt += "User: What is the time in USA?\n"542prompt += "Model: <start_function_call>call:get_current_time{time_location:USA}<end_function_call>\n"543prompt += "User: Stock price of Apple\n"544prompt += "Model: <start_function_call>call:get_stock_price{symbol:AAPL}<end_function_call>\n"545prompt += "User: Who is the PM of Japan?\n"546prompt += "Model: <start_function_call>call:web_search{query:PM of Japan}<end_function_call>\n"547prompt += "User: Check the weather in London\n"548prompt += "Model: <start_function_call>call:web_search{query:weather in London}<end_function_call>\n"549prompt += "User: Latest news about AI\n"550prompt += "Model: <start_function_call>call:web_search{query:Latest news about AI}<end_function_call>\n"551prompt += "User: System memory usage\n"552prompt += (553"Model: <start_function_call>call:get_system_stats{}<end_function_call>\n"554)555556prompt += "<end_of_turn>\n"557558# Add conversation messages559for message in messages:560role = message["role"]561if role == "assistant":562role = "model"563564content = message.get("content", "")565566# Regular user/assistant messages567prompt += f"<start_of_turn>{role}\n"568prompt += content569prompt += "<end_of_turn>\n"570571# Add generation prompt572prompt += "<start_of_turn>model\n"573574return prompt575576577"""578## Tool Response Formatter579580After a tool is executed, this function formats the result back into a string581that can be displayed to the user or fed back into the model. It handles582specific formatting for different tools (like adding currency to stock prices).583"""584585586def format_tool_response(tool_name, result):587"""Format tool result for conversation."""588if "error" in result:589return f"Error: {result['error']}"590591# Handle Web Search Results (from web_search tool OR fallback from other tools)592if "results" in result:593results = result.get("results", [])594if results:595output = f"Found {len(results)} results:\n"596for i, r in enumerate(results[:3], 1):597output += f"\n{i}. {r['title']}\n {r['description'][:150]}...\n {r['link']}"598return output599return "No results found"600601if tool_name == "get_stock_price":602return f"Stock {result['symbol']}: ${result['price']} {result['currency']} (as of {result['date']})"603604elif tool_name == "get_current_time":605response = f"Time in {result['location']}: {result['time']} ({result['day']}, {result['date']})\nTimezone: {result['timezone']}"606if result.get("note"):607response += f"\nNote: {result['note']}"608return response609610elif tool_name == "get_system_stats":611return f"CPU: {result['cpu_percent']}% | Memory: {result['mem_percent']}% ({result['used_gb']}/{result['total_gb']}GB)"612613return str(result)614615616"""617# Interactive Chat Loop618619This section implements the main conversation loop. The agent:620621- Formats the prompt with tool declarations622- Generates a response (which may include a function call)623- Executes the function if called624- Returns the result to the user625626After each successful tool execution, we clear the conversation history to627prevent token overflow and keep the model focused.628629## Model Loading630631This function loads the FunctionGemma, a specialized version of our Gemma 3 270M model tuned for function calling using KerasHub's `from_preset` method.632You can load it from Kaggle using the preset name `(e.g. "function_gemma_instruct_270m")`633or from a local path if you've downloaded the model to your machine.634635Update the path to match your model location. The model is loaded once at636startup and then used throughout the chat session.637"""638639640def load_model():641"""Load FunctionGemma 270M model."""642print("Loading FunctionGemma 270M model...")643model = keras_hub.models.Gemma3CausalLM.from_preset("function_gemma_instruct_270m")644print("Model loaded!\n")645return model646647648"""649This is the main function that runs the interactive conversation with the user.650651It handles the complete cycle of:652653- Displaying capabilities and waiting for user input654- Formatting the prompt with tool declarations and conversation history655- Generating a response from the model656- Parsing any function calls from the response657- Executing the requested tools658- Formatting and displaying results to the user659- Managing conversation history to prevent token overflow660661The loop continues until the user types 'exit', 'quit', or 'q'.662"""663664665def chat(model):666"""Main chat loop with native function calling."""667print("=" * 70)668print(" " * 20 + "FUNCTION CALLING")669print("=" * 70)670print("\nCapabilities:")671print(" 🔍 Web Search - e.g., 'Who is Newton?', 'Latest AI news'")672print(" 📈 Stock Prices - e.g., 'Stock price of AAPL', 'TSLA price'")673print(674" 🕐 World Time - e.g., 'Time in London', 'What time is it in Tokyo?'"675)676print(" 💻 System Stats - e.g., 'System memory', 'CPU usage'")677print("\nType 'exit' to quit.\n")678print("-" * 70)679680# Conversation history681messages = []682683while True:684try:685user_input = input("\nYou: ").strip()686except (EOFError, KeyboardInterrupt):687print("\n\nGoodbye! 👋")688break689690if user_input.lower() in {"exit", "quit", "q"}:691print("\nGoodbye! 👋")692break693694if not user_input:695continue696697# Add user message698messages.append({"role": "user", "content": user_input})699700# Generate response with tools701print("🤔 Thinking...", end="", flush=True)702703try:704# Manually format prompt with tools (KerasHub doesn't have apply_chat_template)705prompt = format_prompt_with_tools(messages, TOOL_DEFINITIONS)706707# Generate708output = model.generate(prompt, max_length=2048)709710# Extract only the new response711response = output[len(prompt) :].strip()712if "<start_function_response>" in response:713response = response.split("<start_function_response>")[0].strip()714if "<start_of_turn>" in response:715response = response.split("<start_of_turn>")[0].strip()716# Remove end tokens717response = response.replace("<end_of_turn>", "").strip()718719print(f"\r{' ' * 40}\r", end="") # Clear status720print(f"[DEBUG] Raw Model Response: {response}")721722# Check if model made a function call723function_call = parse_function_call(response)724725if function_call:726tool_name = function_call["name"]727tool_args = function_call["arguments"]728729print(f"🔧 Calling {tool_name}...", end="", flush=True)730731# Execute tool732if tool_name in TOOLS:733try:734result = TOOLS[tool_name](**tool_args)735formatted_result = format_tool_response(tool_name, result)736737print(f"\r{' ' * 40}\r", end="") # Clear status738print(f"\nAssistant: {formatted_result}")739740# Clear conversation history after successful tool execution741# This prevents token overflow and keeps the model focused742messages = []743744except Exception as e:745print(f"\r❌ Error executing {tool_name}: {e}")746messages.pop() # Remove user message on error747else:748print(f"\r❌ Unknown tool: {tool_name}")749messages.pop()750else:751# Regular text response (no function call)752# Check if response is empty753if not response or response == "...":754print(755f"\nAssistant: I'm not sure how to help with that. Please try rephrasing your question."756)757messages.pop() # Remove problematic message758else:759print(f"\nAssistant: {response}")760messages.append({"role": "assistant", "content": response})761762# Keep only last 3 turns to prevent context overflow763if len(messages) > 6: # 3 user + 3 assistant764messages = messages[-6:]765766except Exception as e:767print(f"\r❌ Error: {e}")768messages.pop() # Remove user message on error769continue770771772"""773This is where the program starts execution. When you run this script:774775- The FunctionGemma model is loaded from the specified path776- The interactive chat loop begins, waiting for user input777- The Assistant processes queries and executes tools until the user types 'exit'778"""779780if __name__ == "__main__":781model = load_model()782chat(model)783784"""785# Conclusion786787## What You've Achieved788789By following this guide, you've learned how to build a production-ready AI Assistant with790native function calling capabilities using FunctionGemma in KerasHub. Here's what you've accomplished:791792### Native Function Calling Implementation:793794- Implemented FunctionGemma native function calling format without relying on external APIs795- Learned to construct proper tool declarations and parse structured function calls796- Built a manual chat template that replicates HuggingFace's apply_chat_template()797798### Multi-Tool Assistant Architecture:799800- Created four distinct tools: web search, stock prices, world time, and system stats801- Designed tools with clear error handling and user-friendly responses802- Implemented explicit error messages.803804### Prompt Engineering Best Practices:805806- Used few-shot examples to guide model behavior and improve tool selection807- Structured prompts with proper role separation (developer, user, model)808- Managed conversation history to prevent token overflow809810### Error Handling and User Experience:811812- Implemented graceful degradation (e.g., UTC fallback for unknown timezones)813- Provided clear, actionable error messages for invalid inputs814- Added contextual information (dates, notes) to make responses more informative815816### Real-World Applications:817818This pattern can be extended to build:819820- Customer service chatbots with database access821- Data analysis assistants with computation tools822- Personal productivity Assistants with calendar and email integration823- Domain-specific assistants (medical, legal, financial) with specialized tools824825## Key Takeaways826827- **Small models can be powerful**: Even a 270M parameter model can reliably use tools828when given proper guidance through few-shot examples and clear tool descriptions.829830- **Transparency matters**: Explicit error handling and clear responses build user trust831more effectively.832833- **Function calling is composable**: The same pattern works for any number of tools,834making it easy to extend your Assistant's capabilities.835836## Next Steps837838To further improve this agent, consider:839840- Fine-tuning the model on domain-specific tool usage examples to optimize performance.841- Adding authentication and rate limiting for production deployment.842- Implementing tool result caching to reduce API calls.843- Creating a web interface using Gradio or Streamlit.844- Adding more sophisticated error recovery strategies.845846Happy building! 🚀847"""848849850