# Connect models to external systems with tool calling

Tool calling allows you to connect models to external tools and systems, which can help you add more capabilities to AI assistants and build deep integrations between your applications and the models.

The `chat` method of the Imagine Client supports tool calling as a function. You describe the functions and arguments that you want to make available to the LLM model. The model outputs a JSON object containing arguments to call one or many functions. After calling any invoked functions, you can provide those results back to the model in subsequent `chat` calls.

AI Appliance

To use tool calling when using the Qualcomm AI On-Prem Appliance Solution, make sure that the appliance box can communicate with the relevant external APIs to achieve tool calling.

<details class="sd-sphinx-override sd-dropdown sd-card sd-mb-3">
<summary class="sd-summary-title sd-card-header">
<span class="sd-summary-text"><span class="svg-1 sd-octicon sd-octicon-tools sd-text-info"><svg version="1.1" width="1.0em" height="1.0em" class="sd-octicon sd-octicon-tools sd-text-info" viewbox="0 0 16 16" aria-hidden="true"><path d="M5.433 2.304A4.492 4.492 0 0 0 3.5 6c0 1.598.832 3.002 2.09 3.802.518.328.929.923.902 1.64v.008l-.164 3.337a.75.75 0 1 1-1.498-.073l.163-3.33c.002-.085-.05-.216-.207-.316A5.996 5.996 0 0 1 2 6a5.993 5.993 0 0 1 2.567-4.92 1.482 1.482 0 0 1 1.673-.04c.462.296.76.827.76 1.423v2.82c0 .082.041.16.11.206l.75.51a.25.25 0 0 0 .28 0l.75-.51A.249.249 0 0 0 9 5.282V2.463c0-.596.298-1.127.76-1.423a1.482 1.482 0 0 1 1.673.04A5.993 5.993 0 0 1 14 6a5.996 5.996 0 0 1-2.786 5.068c-.157.1-.209.23-.207.315l.163 3.33a.752.752 0 0 1-1.094.714.75.75 0 0 1-.404-.64l-.164-3.345c-.027-.717.384-1.312.902-1.64A4.495 4.495 0 0 0 12.5 6a4.492 4.492 0 0 0-1.933-3.696c-.024.017-.067.067-.067.16v2.818a1.75 1.75 0 0 1-.767 1.448l-.75.51a1.75 1.75 0 0 1-1.966 0l-.75-.51A1.75 1.75 0 0 1 5.5 5.282V2.463c0-.092-.043-.142-.067-.159Z"></path></svg></span> Helper utilities for the examples</span><span class="sd-summary-state-marker sd-summary-chevron-right"><span class="svg-2 sd-octicon sd-octicon-chevron-right"><svg version="1.1" width="1.5em" height="1.5em" class="sd-octicon sd-octicon-chevron-right" viewbox="0 0 24 24" aria-hidden="true"><path d="M8.72 18.78a.75.75 0 0 1 0-1.06L14.44 12 8.72 6.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018l6.25 6.25a.75.75 0 0 1 0 1.06l-6.25 6.25a.75.75 0 0 1-1.06 0Z"></path></svg></span></span></summary><div class="sd-summary-content sd-card-body docutils">
<p class="sd-card-text">Some of the following examples use some of the code from the <code class="docutils literal notranslate"><span class="pre">funcs.py</span></code> file. This code is shown here to help you better understand these examples.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre class="pre codeblock"><code># funcs.py

import json
import random

from datetime import datetime
from typing import Literal

import pytz
import requests

from langchain_core.utils.function_calling import convert_to_openai_tool

def get_current_weather(location, unit=&quot;fahrenheit&quot;):
    &quot;&quot;&quot;Get the weather for some location&quot;&quot;&quot;
    if &quot;chicago&quot; in location.lower():
        return json.dumps({&quot;location&quot;: &quot;Chicago&quot;, &quot;temperature&quot;: &quot;13&quot;, &quot;unit&quot;: unit})
    elif &quot;san francisco&quot; in location.lower():
        return json.dumps(
            {&quot;location&quot;: &quot;San Francisco&quot;, &quot;temperature&quot;: &quot;55&quot;, &quot;unit&quot;: unit}
        )
    elif &quot;new york&quot; in location.lower():
        return json.dumps({&quot;location&quot;: &quot;New York&quot;, &quot;temperature&quot;: &quot;11&quot;, &quot;unit&quot;: unit})
    else:
        return json.dumps({&quot;location&quot;: location, &quot;temperature&quot;: &quot;unknown&quot;})

def get_weather_forecast(location: str) -&gt; dict[str, str]:
    &quot;&quot;&quot;Retrieves a simple weather forecast for a given location&quot;&quot;&quot;
    url = f&quot;https://wttr.in/{location}?format=%C,%t&quot;
    response = requests.get(url)
    if response.status_code == 200:
        result = response.text.strip().split(&quot;,&quot;)
        return {
            &quot;location&quot;: location,
            &quot;forecast&quot;: &quot; &quot;.join(result[:-1]),
            &quot;temperature&quot;: result[-1],
        }
    else:
        return {&quot;error&quot;: &quot;Unable to fetch weather data&quot;}

def get_stock_price(symbol: str) -&gt; float:
    &quot;&quot;&quot;Retrieves the stock price for a given symbol&quot;&quot;&quot;
    api_key = &quot;your_stock_api_key&quot;
    url = f&quot;https://www.alphavantage.co/query?function=GLOBAL_QUOTE&amp;symbol={symbol}&amp;apikey={api_key}&quot;
    response = requests.get(url)
    data = response.json()

    if &quot;Global Quote&quot; not in data:
        return 100.0

    return float(data[&quot;Global Quote&quot;][&quot;05. price&quot;])

def get_random_number(min_value: int, max_value: int) -&gt; int:
    &quot;&quot;&quot;Returns a random number between min_value and max_value&quot;&quot;&quot;
    return random.randint(min_value, max_value)

def get_current_time(time_zone: str | None = None) -&gt; str:
    &quot;&quot;&quot;Returns the current time in the specified time zone&quot;&quot;&quot;

    if time_zone is None or time_zone not in pytz.all_timezones:
        time_zone = &quot;US/Pacific&quot;

    format = &quot;%Y-%m-%d %H:%M:%S&quot;

    tz = pytz.timezone(time_zone)
    current_time = datetime.now(tz)
    return current_time.strftime(format)

def get_random_city() -&gt; str:
    &quot;&quot;&quot;Retrieves a random city from a list of cities&quot;&quot;&quot;
    cities = [
        &quot;Groningen&quot;,
        &quot;Enschede&quot;,
        &quot;Amsterdam&quot;,
        &quot;Istanbul&quot;,
        &quot;Baghdad&quot;,
        &quot;Rio de Janeiro&quot;,
        &quot;Tokyo&quot;,
        &quot;Kampala&quot;,
    ]
    return random.choice(cities)

def get_user_location(accuracy: int) -&gt; str:
    &quot;&quot;&quot;
    Returns the user&#39;s location based on the public IP address and accuracy level.

    Parameters:
    accuracy (int): The level of detail for the location information.
        1 - Country only
        2 - City and country
        3 - City, region, and country

    Returns:
    str: The location information based on the specified accuracy level or an error message.
    &quot;&quot;&quot;
    # return json.dumps({&quot;location&quot;:&quot;Las Vegas, Nevada, United States&quot;})
    return json.dumps({&quot;location&quot;: &quot;San Diego, California, United States&quot;})

    try:
        # Retrieve public IP address
        ip_response = requests.get(&quot;https://api.ipify.org?format=json&quot;)
        ip_response.raise_for_status()
        ip_address = ip_response.json().get(&quot;ip&quot;)

        # Use public IP to get location data
        location_url = f&quot;http://ip-api.com/json/{ip_address}&quot;
        location_response = requests.get(location_url)
        location_response.raise_for_status()
        data = location_response.json()

        if data[&quot;status&quot;] == &quot;fail&quot;:
            return f&quot;Error in get_user_location: {data.get(&#39;message&#39;, &#39;Unknown error&#39;)}&quot;

        if accuracy == 1:
            return data.get(&quot;country&quot;, &quot;Unknown country&quot;)
        elif accuracy == 2:
            return f&quot;{data.get(&#39;city&#39;, &#39;Unknown city&#39;)}, {data.get(&#39;country&#39;, &#39;Unknown country&#39;)}&quot;
        elif accuracy == 3:
            return f&quot;{data.get(&#39;city&#39;, &#39;Unknown city&#39;)}, {data.get(&#39;regionName&#39;, &#39;Unknown region&#39;)}, {data.get(&#39;country&#39;, &#39;Unknown country&#39;)}&quot;
        else:
            return &quot;Invalid accuracy level. Please specify 1 (Country), 2 (City and Country), or 3 (City, Region, and Country).&quot;
    except requests.RequestException as e:
        return f&quot;Error: {e}&quot;

TOOL_MAPPING = {
    f.__name__: f
    for f in (
        get_current_weather,
        get_weather_forecast,
        get_stock_price,
        get_random_number,
        get_current_time,
        get_random_city,
        get_user_location,
    )
}

def get_tools_map(
    names: list[
        Literal[
            &quot;get_current_weather&quot;,
            &quot;get_weather_forecast&quot;,
            &quot;get_stock_price&quot;,
            &quot;get_random_number&quot;,
            &quot;get_current_time&quot;,
            &quot;get_user_location&quot;,
            &quot;get_random_city&quot;,
        ]
    ],
):
    # dict[str, Tuple[Callable | list[Callable], dict[str, Any] | list[dict[str, Any]]]]:

    tools_map = {}

    for name in names:
        tool = TOOL_MAPPING[name]
        tools_map[name] = {}
        tools_map[name][&quot;call&quot;] = tool
        tools_map[name][&quot;schema&quot;] = convert_to_openai_tool(tool)

    all_tools = []
    all_specs = []
    for v in tools_map.values():
        all_tools.append(v[&quot;call&quot;])
        all_specs.append(v[&quot;schema&quot;])

    tools_map[&quot;__all__&quot;] = {}
    tools_map[&quot;__all__&quot;][&quot;call&quot;] = all_tools
    tools_map[&quot;__all__&quot;][&quot;schema&quot;] = all_specs

    return tools_map
</code><span class="copyclip"><svg xmlns="http://www.w3.org/2000/svg" class="copyclipicon" width="25px" height="25px" viewbox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><title>Copy to clipboard</title><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></span></pre></div>
</div>
</div>
</details>
## Call a single tool

The following example shows how to use tool calling capabilities of the `chat` method. It defines a function for a hypothetical `get_current_weather` tool, sends a message to the model, then prints the response.

from rich.pretty import pprint
    
    import imagine

    client = imagine.ImagineClient(max_retries=1, debug=True)
    
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_current_weather",
                "description": "Get the current weather in a given location",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "location": {
                            "type": "string",
                            "description": "The city and state, e.g. San Francisco, CA",
                        },
                        "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                    },
                },
            },
        }
    ]
    
    messages = [
        {
            "role": "system",
            "content": "You are a helpful assistant that can access external functions. The responses from these function calls will be appended to this dialogue. Please provide responses based on the information from these function calls.",
        },
        {"role": "user", "content": "What is the current temperature of New York?"},
    ]
    
    response = client.chat(
        model="Llama-3.1-8B",
        messages=messages,
        tools=tools,
    )
    
    pprint(response)
    Copy to clipboard

## Call a single tool with helper functions

The following example shows how to use tool calling capabilities of the `chat` method using helper functions to create the schema instead of explicitly defining it as in the previous example.

from langchain_core.utils.function_calling import convert_to_openai_tool
    from rich.pretty import pprint
    
    import imagine

    client = imagine.ImagineClient(max_retries=1, debug=True)

    def get_current_weather(location: str) -> dict[str, str]:
        """Retrieves current weather based on the location"""
        return {"location": location, "forecast": "Sunny", "temperature": "35.5"}

    messages = [
        {
            "role": "system",
            "content": "You are a helpful assistant that can access external functions. The responses from these function calls will be appended to this dialogue. Please provide responses based on the information from these function calls.",
        },
        {"role": "user", "content": "What is the current temperature of New York?"},
    ]
    
    functions = [get_current_weather]
    tools = [convert_to_openai_tool(t) for t in functions]

    response = client.chat(model="Llama-3.1-8B", messages=messages, tools=tools)
    
    pprint(response, expand_all=True)
    Copy to clipboard

## Call a tool with multi-turn interactions

This example shows multi-turn interactions for tool calling.  Based on the first `response`, the model decides if it can provide an answer without calling a tool or if any of the provided tools are needed before providing an answer.

import json
    
    import funcs
    
    from rich.pretty import pprint
    
    import imagine

    # -------------------------------------------------------------------------------------
    client = imagine.ImagineClient(max_retries=1, debug=True)
    
    messages = [
        {
            "role": "system",
            "content": "You are a helpful assistant that can access external functions. The responses from these function calls will be appended to this dialogue. Please provide responses based on the information from these function calls.",
        },
        {
            "role": "user",
            "content": "What is the current temperature of New York, San Francisco and Chicago?",
        },
    ]
    
    tools_map = funcs.get_tools_map(["get_current_weather"])
    
    MODEL = "Llama-3.1-8B"
    
    # --------------------------------------------------------------------------------------
    
    tools_spec = tools_map["__all__"]["schema"]
    
    response = client.chat(
        model=MODEL,
        messages=messages,
        tools=tools_spec,
    )

    messages.append(response.choices[0].message)
    
    pprint(response, expand_all=True)
    
    tool_calls = response.choices[0].message.tool_calls
    
    if tool_calls:
        for tool_call in tool_calls:
            function_call = tool_call.function
            name = function_call.name
            arguments = json.loads(function_call.arguments)
    
            for function in tools_map["__all__"]["call"]:
                if function.__name__ == name:
                    print(f"Invoking tool call: {name} with args :: {arguments}")
                    function_response = function(**arguments)
                    print(f"Result of invocation {function_response}")
    
                    messages.append(
                        {
                            "tool_call_id": tool_call.id,
                            "role": "tool",
                            "name": name,
                            "content": function_response,
                        }
                    )
    
        pprint(messages)
    
        function_enriched_response = client.chat(
            model=MODEL,
            messages=messages,
        )
    
        pprint(function_enriched_response)
    Copy to clipboard

## Call multiple tools

This is a tool calling example where multiple tools are listed in a `tools_map` for potential use by the LLM.

import json
    
    import funcs
    
    from rich.pretty import pprint
    
    import imagine

    # --------------------------------------------------------------------------------------
    client = imagine.ImagineClient(max_retries=1, debug=True)
    
    messages = [
        {
            "role": "system",
            "content": "You are a helpful assistant that can access external functions. The responses from these function calls will be appended to this dialogue. Please provide responses based on the information from these function calls.",
        },
        {"role": "user", "content": "Get the stock price of AAPL and GOOG"},
    ]
    
    tools_map = funcs.get_tools_map(
        ["get_current_weather", "get_stock_price", "get_user_location", "get_random_city"]
    )
    
    MODEL = "Llama-3.1-8B"
    
    # --------------------------------------------------------------------------------------
    
    tools_spec = tools_map["__all__"]["schema"]
    
    response = client.chat(
        model=MODEL,
        messages=messages,
        tools=tools_spec,
    )

    messages.append(response.choices[0].message)
    
    pprint(response, expand_all=True)
    
    tool_calls = response.choices[0].message.tool_calls
    
    if tool_calls:
        for tool_call in tool_calls:
            function_call = tool_call.function
            name = function_call.name
            arguments = json.loads(function_call.arguments)
    
            for function in tools_map["__all__"]["call"]:  # type: ignore
                if function.__name__ == name:
                    print(f"Invoking tool call: {name} with args :: {arguments}")
                    function_response = function(**arguments)
                    print(f"Result of invocation {function_response}")
    
                    messages.append(
                        {
                            "tool_call_id": tool_call.id,
                            "role": "tool",
                            "name": name,
                            "content": str(function_response),
                        }
                    )
    
        pprint(messages)
    
        function_enriched_response = client.chat(
            model=MODEL,
            messages=messages,  # type: ignore
        )
    
        pprint(function_enriched_response)
    Copy to clipboard

## Call multiple tools recursively

This example adds to the functionality of the previous example by looping through the tool calling until the model can provide a response.

import json
    
    import funcs
    
    from loguru import logger
    from rich.pretty import pprint
    
    import imagine

    # --------------------------------------------------------------------------------------
    client = imagine.ImagineClient(max_retries=1, debug=True)
    
    messages = [
        {
            "role": "user",
            "content": "Get the user's location first. Get weather forecast at that location user's location. Call the functions one at a time sequentially without commenting or asking for confirmation",
        }
    ]

    tools_map = funcs.get_tools_map(
        ["get_weather_forecast", "get_user_location", "get_current_time"]
    )
    
    MODEL = "Llama-3.1-8B"
    
    # --------------------------------------------------------------------------------------
    
    tools_spec = tools_map["__all__"]["schema"]

    def run_inference(messages, tools):
        response = client.chat(model=MODEL, messages=messages, tools=tools, temperature=0.8)
    
        return response

    def recursive_tool_calling():
        while True:
            response = run_inference(messages, tools_spec)
    
            assistant_message = response.choices[0].message
            messages.append(assistant_message)
    
            logger.debug(f"Assistant Message: {assistant_message}")
    
            if not assistant_message.tool_calls:
                break
    
            for tool_call in assistant_message.tool_calls:
                function_call = tool_call.function
                name = function_call.name
                arguments = json.loads(function_call.arguments)
                for function in tools_map["__all__"]["call"]:
                    if function.__name__ == name:
                        logger.debug(f"Invoking tool call: {name}")
                        function_result = function(**arguments)
                        logger.debug(f"Result of invocation {function_result}")
    
                        if not isinstance(function_result, str):
                            function_result = json.dumps(function_result)
    
                        messages.append(
                            {
                                "name": name,
                                "role": "tool",
                                "content": function_result,
                                "tool_call_id": tool_call.id,
                            }
                        )
                        logger.debug(f"Tool Call Result: {function_result}")
                        break
    
        return messages

    messages = [
        {
            "role": "user",
            "content": "Get the user's location first. Get weather forecast at that location user's location. Call the functions one at a time sequentially without commenting or asking for confirmation",
        }
    ]
    
    messages = recursive_tool_calling()
    
    pprint(messages)
    Copy to clipboard

## Next steps

- [Connect to external functions using LangChain tools](https://docs.qualcomm.com/doc/80-88545-1/topic/3_1_langchain_tools.html).
- Review how to [add guardrails to an LLM](https://docs.qualcomm.com/doc/80-88545-1/topic/guarded_llm_example.html).
- [Create custom agents](https://docs.qualcomm.com/doc/80-88545-1/topic/autogen.html) with AutoGen.

Last Published: Apr 17, 2026

[Previous Topic
Basic usage](https://docs.qualcomm.com/bundle/publicresource/80-88545-1/topics/1_0_basic_usage.md) [Next Topic
Use LangChain with the Qualcomm AI Inference Suite SDK](https://docs.qualcomm.com/bundle/publicresource/80-88545-1/topics/3_0_langchain.md)