Skip to content

Function-Calling automation

Function calling introduces powerful capabilities to interact with AI models, but manually creating and maintaining function schemas is a complex and error-prone process. Developers often struggle with:

  • Keeping schemas synchronized with code changes
  • Ensuring type safety and input validation
  • Supporting multiple AI platform formats
  • Reducing repetitive boilerplate code

This cookbook demonstrates an automated approach to generating and managing function-calling schemas, streamlining the integration between your code and Language Models.

Installation and Setup

This cookbook requires the litellm library for function-call generation via the Groq provider.

If you don't have an API key for Groq, you can get one at Groq Console.

%pip install orchestr8[adapter] litellm duckduckgo-search

import os, getpass

def set_env(var: str):
    if not os.environ.get(var):
        os.environ[var] = getpass.getpass(f"{var}: ")

set_env("GROQ_API_KEY")
import json
from typing import Any, Dict, List

from litellm import completion

INSTRUCTION = "Complete user requests using the given functions."

def generate_function_call(request: str, functions: List[Dict[str, Any]]):
    response = completion(
        model="groq/llama3-groq-70b-8192-tool-use-preview",
        messages=[
            {"role": "system", "content": INSTRUCTION},
            {"role": "user", "content": request}
        ],
        tools=functions
    )
    tool_call = response.choices[0].message.tool_calls[0].function
    if tool_call is None:
        print(response.choices[0].message.content)
        raise Exception("No function call found in the response.")
    return tool_call.name, json.loads(tool_call.arguments)

Creating adapters from functions

Creating adapters is as simple as defining a function and decorating it with @adapt decorator.

from typing import Literal

import orchestr8 as o8
from duckduckgo_search import DDGS


ddgs = DDGS()

@o8.adapt
def search_text(
    text: str,
    *, # Yes it supports positional and keyword arguments
    safe_search: bool = True,
    backend: Literal['api', 'html', 'lite'] = 'api',
    max_results: int = 1
):
    """
    Search for text in the web.
    :param text: Text to search for.
    :param safe_search: If True, enable safe search.
    :param backend: Backend to use for retrieving results.
    :param max_results: Max results to return.
    """
    return ddgs.text(
        keywords=text,
        safesearch="on" if safe_search else "off",
        backend=backend,
        max_results=max_results
    )


@o8.adapt
def get_translation(
    text: str,
    to: Literal['en', 'ja', 'hi', 'es', 'fr', 'de', 'zh'] = 'en'
):
    """
    Translate the given text.
    :param text: Text to translate.
    :param to: what language to translate.
    """
    return ddgs.translate(keywords=text, to=to)

Generating function-calls

There are three available function-calling schema formats: OpenAI, Anthropic, and Gemini.

We'll be using OpenAI schema for this example as we're using Llama model.

function_call = generate_function_call(
    "Search for the best restaurants in Kolkata.",
    functions=[search_text.openai_schema, get_translation.openai_schema],
)
print(function_call)

Result

('search_text', {'text': 'best restaurants in Kolkata', 'max_results': 5, 'safe_search': True})

Now, we can utilize the validate_input method to validate the function arguments generated by the LLM.

search_results = search_text.validate_input(function_call[1])
for entry in search_results:
    print(entry)

Result

{'title': 'THE 10 BEST Restaurants in Kolkata (Calcutta) (Updated 2024) - Tripadvisor', 'href': 'https://www.tripadvisor.in/Restaurants-g304558-Kolkata_Calcutta_Kolkata_District_West_Bengal.html', 'body': 'Dining in Kolkata (Calcutta), Kolkata District: See 63,234 Tripadvisor traveller reviews of 5,724 Kolkata (Calcutta) restaurants and search by cuisine, price, location, and more.'}
{'title': 'The 31 Best Restaurants in Kolkata (Calcutta), India - Eater', 'href': 'https://www.eater.com/maps/best-restaurants-kolkata-calcutta-india-bengal', 'body': 'The 31 Essential Kolkata Restaurants. Deviled crabs at a midcentury cabaret, phuchka from a decades-old street vendor, and more of the best things to eat in Kolkata'}
{'title': 'The 50 best restaurants in Kolkata - Condé Nast Traveller India', 'href': 'https://www.cntraveller.in/magazine-story/kolkatas-50-best-meals/', 'body': "Explore the diverse cuisines of Kolkata, from Bengali sweet shops to Mughlai, Chinese and Continental restaurants. Discover the history, culture and flavours of the city's top dining spots, from Flurys to Kewpie's Kitchen."}
{'title': 'The 11 Best Restaurants in Kolkata - TripSavvy', 'href': 'https://www.tripsavvy.com/best-restaurants-in-kolkata-5176449', 'body': "Discover the diverse cuisines and dining scenes of Kolkata, from Bengali fine dining to modern Indian and heritage continental. Find out the best places to enjoy seafood, kebabs, biryani, and more in the city's iconic areas."}
{'title': '14 Best Restaurants in Kolkata (2024) - WanderOn', 'href': 'https://wanderon.in/blogs/best-restaurants-in-kolkata', 'body': 'Explore the diverse and vibrant food scene of Kolkata, from traditional Bengali cuisine to continental dishes, street food, and international flavors. Discover the best restaurants in Kolkata, ranging from historic institutions to quirky cafes and local eateries, and enjoy a gastronomic adventure.'}

Adapters also has as interesting property .definition which returns the code definition of the function.

It can be useful in cases where you want to pass the function as context to the LLM.

print(get_translation.definition)

Result

def get_translation(text: str, to: Literal['en', 'ja', 'hi', 'es', 'fr', 'de', 'zh']='en'):
    """
    Translate the given text.
    :param text: Text to translate.
    :param to: what language to translate.
    """
    return ddgs.translate(keywords=text, to=to)