Skip to main content

Command Palette

Search for a command to run...

Supercharging LLMs: A Guide to Tool Use with the Cerebras SDK

Build a financial assistant that can calculate the Simple Moving Average and more

Updated
15 min read
Supercharging LLMs: A Guide to Tool Use with the Cerebras SDK

Introduction

Large Language Models (LLMs) are incredibly powerful, but their knowledge is often limited to the data they were trained on. What if you could give them access to real-time information or allow them to interact with other systems? That's where "tools" come in. The Cerebras SDK makes it straightforward to equip your LLM applications with the ability to use custom functions, effectively extending their capabilities.

This improves upon the official guide by using a working demo API and fixing some code bugs. Thanks to the generous free API by Cerebras which lets you try out the (current) latest Qwen 3 model.

In this post, we'll walk through a practical example: building a financial assistant that can calculate the Simple Moving Average (SMA) for stock data. We'll use the Cerebras SDK to enable an LLM to call our custom Python function for this calculation.

A Stock-Savvy Assistant Version 1

Our objective is to create a system where a user can ask a question like, "What's the 10-day moving average for company A over the last 50 days?" and the LLM, instead of just guessing, can use a specific tool (our Python function) to get the precise answer.

Step 1: Gathering the Data

First, we need a way to get stock data. Our demo uses the Alpha Vantage API. While the original guide uses mock data, here we implement get_stocks_data function to fetch daily time series data for a given stock symbol using Alpha Vantage APIs. We need to convert the dict to a list with date as a key in each item.

import requests

# URLs for fetching stock data from Alpha Vantage API
urls = [
    f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=APPL&outputsize=full&apikey={api_key}",
    f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=GOOG&outputsize=full&apikey={api_key}",
]

def get_stocks_data(url: str) -> list[dict]:
    """
    Fetches and processes daily time series stock data from a given Alpha Vantage URL.
    """
    r = requests.get(url)
    r.raise_for_status() # Good practice to check for request errors
    data = r.json()
    # Extract the 'Time Series (Daily)' data from the response
    # Add error handling for unexpected API response structure
    time_series_data = data.get("Time Series (Daily)")
    if not time_series_data:
        raise ValueError("Could not find 'Time Series (Daily)' in API response.")

    stocks_data = []
    # Reformat the data into a list of dictionaries
    for s_date, s_data in time_series_data.items():
        n = {"date": s_date}
        n.update(s_data)
        stocks_data.append(n)
    return stocks_data

# Fetch stock data for two predefined companies
company_a_data = get_stocks_data(urls[0])
company_b_data = get_stocks_data(urls[1])

# Store the fetched company data in a dictionary for easy access
available_data = {
    "company_a": company_a_data,
    "company_b": company_b_data,
}

This function fetches data for "IBM" and "RELIANCE.BSE" and stores it in available_data.

Step 2: The Core Logic - Calculating Moving Average

Next, we define the function that will act as our "tool". The calculate_moving_average function takes a data reference (e.g., "company_a"), the number of days to consider, and the window size for the moving average

def calculate_moving_average(
        data_reference: str, num_days: int, window_size: int
) -> list[dict[str, float]]:
    """
    Calculates the moving average for a specified stock.
    """
    if data_reference not in available_data:
        raise ValueError(
            f"Invalid data reference. Available options: {list(available_data.keys())}"
        )

    stock_data = available_data[data_reference]

    if num_days < window_size:
        raise ValueError("num_days must be greater than or equal to window_size")

    if len(stock_data) < num_days:
        raise ValueError("Insufficient data for the specified number of days")

    # Data is typically sorted newest to oldest from Alpha Vantage.
    # For SMA, we usually want to process oldest to newest or ensure correct slicing.
    # Assuming data is sorted: stock_data[0] is newest, stock_data[-1] is oldest.
    # To get the most recent 'num_days', we might need to reverse or slice carefully.
    # For this example, let's assume the API returns data oldest to newest,
    # or that get_stocks_data sorts it that way. If not, this needs adjustment.
    # If data is newest to oldest:
    # recent_data_points = stock_data[:num_days] # Get the N most recent
    # recent_data_points.reverse() # Process oldest to newest for SMA calculation

    # Let's stick to the original logic of taking the last 'num_days' from the list
    # This implies the list is already sorted oldest to newest.
    recent_data_points: list[dict] = stock_data[num_days:]
    moving_averages: list[dict[str, float]] = []

    if not recent_data_points or len(recent_data_points) < window_size:
        return [] # Not enough data to calculate even one SMA

    # Initialize the price window with the closing prices of the first 'window_size' days
    price_window: list[float] = [
        float(item["4. close"]) for item in recent_data_points[:window_size]
    ]
    # Add the first SMA if we have exactly window_size data points
    if len(price_window) == window_size:
         moving_averages.append({
             "date": recent_data_points[window_size -1]["date"], # Date of the last day in the window
             "movingAverage": round(sum(price_window) / window_size, 2)
         })

    # Calculate moving average for the remaining days
    for i in range(window_size, len(recent_data_points)):
        current_data = recent_data_points[i]
        current_price = float(current_data["4. close"])

        price_window.pop(0) # Remove the oldest price
        price_window.append(current_price) # Add the newest price
        average = round(sum(price_window) / window_size, 2)

        moving_averages.append({"date": current_data["date"], "movingAverage": average})

    return moving_averages

This function performs the actual SMA calculation. Note the input validation and the logic for sliding the window.

Step 3: Describing the Tool to the LLM

For the LLM to understand how to use our function, we need to provide a schema for its parameters. We'll use Pydantic which is excellent for this:

from pydantic import BaseModel, Field
from typing import Literal

class CalculateMovingAverageArgs(BaseModel):
    """
    Pydantic model defining the arguments for the calculate_moving_average function.
    Used for schema validation and generation for AI tool usage.
    """
    data_reference: Literal["company_a", "company_b"] = Field(
        ...,
        description="The key to access specific stock data in the stock_data dictionary.",
    )
    num_days: int = Field(
        ..., description="The number of recent days to consider for calculation."
    )
    window_size: int = Field(..., description="The size of the moving average window.")

This CalculateMovingAverageArgs model clearly defines the expected inputs, their types, and descriptions.

Step 4: Registering the Tool with Cerebras SDK

Now, we tell the Cerebras SDK about our tool:

# Define the tool (function) that the AI model can use
tools = [
    {
        "type": "function",
        "function": {
            "name": "calculate_moving_average",
            "description": "Calculate the moving average of stock data for a specified number of recent days.",
            "parameters": CalculateMovingAverageArgs.model_json_schema(),
        },
    }
]

We create a list of tool definitions. Each tool has a type ("function"), a name (matching our Python function name), a description (for the LLM to understand its purpose), and parameters (using the JSON schema from our Pydantic model).

Step 5: Making the API Call

With everything set up, we can now interact with the LLM:

import os
from cerebras.cloud.sdk import Cerebras

# Define the messages for the AI model, including system prompt and user query
messages = [
    {
        "role": "system",
        "content": "You are a helpful financial analyst. Use the supplied tools to assist the user.",
    },
    {
        "role": "user",
        "content": "What's the 10-day moving average for company A over the last 50 days?",
    },
]

# Initialize the Cerebras SDK client using API key from environment variable
client = Cerebras(
    api_key=os.environ.get("CEREBRAS_API_KEY"),
)

# Make a chat completion request to the Cerebras API
response = client.chat.completions.create(
    model="qwen-3-32b",  # Specify the AI model to use
    messages=messages,
    tools=tools,  # Provide the available tools to the model
)

We initialize the Cerebras client, define our conversation messages, and importantly, pass our tools definition to the create method.

Step 6: Handling the LLM's Response

The LLM might respond directly, or it might decide to use our tool. We need to handle both cases

# Process the AI model's response
content = response.choices[0].message.content
if content:
    print("AI Response Content:")
    print(content)

# Check if the AI decided to call a function
if response.choices[0].message.tool_calls:
    tool_call = response.choices[0].message.tool_calls[0] # Get the first tool call
    function_call = tool_call.function
    if function_call.name == "calculate_moving_average":
        # Parse the arguments provided by the AI for the function call
        try:
            arguments = json.loads(function_call.arguments)
            # Call the local function with the AI-provided arguments
            result = calculate_moving_average(**arguments)
            print("\\nResult of function call (calculate_moving_average):")
            print(result)

        except json.JSONDecodeError:
            print(f"Error: Could not decode arguments: {function_call.arguments}")
        except Exception as e:
            print(f"Error executing function {function_call.name}: {e}")

If response.choices[0].message.tool_calls is present, it means the LLM wants to use one of our tools. We extract the function name and arguments, then execute our local calculate_moving_average function.

The commented-out section shows how you could then send this result back to the LLM. This allows the LLM to formulate a natural language response based on the data retrieved by the tool, making the interaction more conversational.

Here's a sample output:

[{'date': '2025-06-04', 'movingAverage': 200.0}, {'date': '2025-06-03', 'movingAverage': 200.76}, {'date': '2025-06-02', 'movingAverage': 201.09}, {'date': '2025-05-30', 'movingAverage': 201.53}, {'date': '2025-05-29', 'movingAverage': 201.6}, {'date': '2025-05-28', 'movingAverage': 201.77}, {'date': '2025-05-27', 'movingAverage': 201.52}, {'date': '2025-05-23', 'movingAverage': 200.9}, {'date': '2025-05-22', 'movingAverage': 200.65}, {'date': '2025-05-21', 'movingAverage': 200.79}, {'date': '2025-05-20', 'movingAverage': 201.2}, {'date': '2025-05-19', 'movingAverage': 201.75}, {'date': '2025-05-16', 'movingAverage': 202.7}, {'date': '2025-05-15', 'movingAverage': 203.77}, {'date': '2025-05-14', 'movingAverage': 205.0}, {'date': '2025-05-13', 'movingAverage': 206.25}, {'date': '2025-05-12', 'movingAverage': 207.31}, {'date': '2025-05-09', 'movingAverage': 207.64}, {'date': '2025-05-08', 'movingAverage': 207.25}, {'date': '2025-05-07', 'movingAverage': 206.67}, {'date': '2025-05-06', 'movingAverage': 205.83}, {'date': '2025-05-05', 'movingAverage': 204.84}, {'date': '2025-05-02', 'movingAverage': 204.25}, {'date': '2025-05-01', 'movingAverage': 204.44}, {'date': '2025-04-30', 'movingAverage': 204.46}, {'date': '2025-04-29', 'movingAverage': 204.28}, {'date': '2025-04-28', 'movingAverage': 204.22}, {'date': '2025-04-25', 'movingAverage': 205.29}, {'date': '2025-04-24', 'movingAverage': 206.38}, {'date': '2025-04-23', 'movingAverage': 207.22}, {'date': '2025-04-22', 'movingAverage': 207.34}, {'date': '2025-04-21', 'movingAverage': 206.77}, {'date': '2025-04-17', 'movingAverage': 205.93}, {'date': '2025-04-16', 'movingAverage': 204.03}, {'date': '2025-04-15', 'movingAverage': 202.99}, {'date': '2025-04-14', 'movingAverage': 202.12}, {'date': '2025-04-11', 'movingAverage': 200.92}, {'date': '2025-04-10', 'movingAverage': 199.03}, {'date': '2025-04-09', 'movingAverage': 198.08}, {'date': '2025-04-08', 'movingAverage': 194.87}]

Process finished with exit code 0

What happens if there is no suitable tool is available? Let us modify the message and try again to see the result:

messages = [
    {
        "role": "system",
        "content": "You are a helpful financial analyst. Use the supplied tools to assist the user.",
    },
    {
        "role": "user",
        "content": "Give an overview of the company A",
    },
]

# Make a chat completion request to the Cerebras API
response = client.chat.completions.create(
    model="qwen-3-32b",  
    messages=messages,
    tools=tools, 
)
# Process the AI model's response
content = response.choices[0].message.content
if content:
    print("AI Response Content:")
    print(content)

This will print the message following:

I don't have access to a function/tool that provides general company overviews. The available tool only supports calculating moving averages for stock data. Would you like me to help with a moving average calculation for Company A's stock instead?

This is where multi tool use comes into picture. However, we are not implementing multi tool in this demo.

Stock-Savvy Assistant Version 2

If you noticed, we did not use the data from company B at all.

Let us fix that by comparing the SMA for both companies. However, there’s a challenge:

  • calculate_moving_average and CalculateMovingAverageArgs accept data_reference not available_data

We can either fix the signatures or use Python partial

No points for guessing, that we will use partial instead of repeating the same arg every where.

Here’s the updated version, with the following changes:

  1. Use dotenv to read API Keys

  2. Calculate SMA of both company A and B

  3. Implement partials to use the same tool function and args with available_data

import json
import os
from collections import deque
from functools import partial
from typing import Literal, List, Dict

import requests
from cerebras.cloud.sdk import Cerebras
from dotenv import load_dotenv
from pydantic import BaseModel, Field

load_dotenv()

v_api_key = os.environ.get("STOCK_API_KEY")
if not v_api_key:
    print("Error: STOCK_API_KEY environment variable not set.")

STOCK_URLS = {
    "company_a": f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=AAPL&outputsize=full&apikey={v_api_key}",
    "company_b": f"https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=GOOG&outputsize=full&apikey={v_api_key}",
}

def get_stocks_data(url: str) -> List[Dict]:
    """
    Fetches and processes daily time series stock data from a given Alpha Vantage URL.
    ... (docstring remains the same) ...
    """
    response = requests.get(url)
    response.raise_for_status()
    data = response.json()

    time_series_data = data.get("Time Series (Daily)")
    if not time_series_data:
        # Add the API response note to the error for better debugging
        note = data.get("Note")
        if note:
            raise ValueError(f"API response missing 'Time Series (Daily)' data. Note: {note}")
        raise ValueError("API response missing 'Time Series (Daily)' data.")

    stocks_data = []
    for date_str, daily_data in time_series_data.items():
        record = {"date": date_str}
        record.update(daily_data)
        stocks_data.append(record)

    stocks_data.reverse()
    return stocks_data


def calculate_moving_average(
    available_data: Dict,  # <-- Dependency is now an explicit argument
    data_reference: str,
    num_days: int,
    window_size: int,
) -> List[Dict[str, float]]:
    """
    Calculates the moving average for a specified stock over a given number of days
    and window size.

    Args:
        available_data: A dictionary containing the pre-loaded stock data.
        ... (other args remain the same) ...
    """
    if data_reference not in available_data:
        raise ValueError(
            f"Invalid data reference. Available options: {list(available_data.keys())}"
        )

    stock_data = available_data[data_reference]

    if num_days < window_size:
        raise ValueError("num_days must be greater than or equal to window_size")

    if len(stock_data) < num_days:
        raise ValueError(
            f"Insufficient data for the specified number of days. "
            f"Requested: {num_days}, Available: {len(stock_data)}"
        )

    recent_data = stock_data[-num_days:]
    moving_averages = []
    price_window = deque(maxlen=window_size)

    for i in range(len(recent_data)):
        current_data = recent_data[i]
        price_window.append(float(current_data["4. close"]))

        if len(price_window) == window_size:
            average = round(sum(price_window) / window_size, 2)
            moving_averages.append(
                {"date": current_data["date"], "movingAverage": average}
            )

    return moving_averages


def compare_moving_averages(
    available_data: Dict, num_days: int, window_size: int
) -> Dict:
    """
    Calculates and compares the latest moving average of two companies.

    Args:
        available_data: A dictionary containing the pre-loaded stock data.
        ... (other args remain the same) ...
    """
    try:
        # Pass the dependency down to the next function
        ma_a_list = calculate_moving_average(
            available_data, "company_a", num_days, window_size
        )
        ma_b_list = calculate_moving_average(
            available_data, "company_b", num_days, window_size
        )

        latest_ma_a = ma_a_list[-1] if ma_a_list else None
        latest_ma_b = ma_b_list[-1] if ma_b_list else None

        if not latest_ma_a or not latest_ma_b:
            return {
                "error": "Could not calculate moving average for one or both companies."
            }

        result = {
            "company_a": {
                "latest_date": latest_ma_a["date"],
                "moving_average": latest_ma_a["movingAverage"],
            },
            "company_b": {
                "latest_date": latest_ma_b["date"],
                "moving_average": latest_ma_b["movingAverage"],
            },
            "summary": (
                f"As of their latest data points, Company A's {window_size}-day moving average is "
                f"{latest_ma_a['movingAverage']} ({latest_ma_a['date']}), while Company B's is "
                f"{latest_ma_b['movingAverage']} ({latest_ma_b['date']})."
            ),
        }
        return result
    except ValueError as e:
        return {"error": str(e)}


class CalculateMovingAverageArgs(BaseModel):
    """Defines arguments for the calculate_moving_average function."""
    data_reference: Literal["company_a", "company_b"] = Field(..., description="The key for the stock data.")
    num_days: int = Field(..., description="The number of recent days for calculation.")
    window_size: int = Field(..., description="The size of the moving average window.")


class CompareMovingAveragesArgs(BaseModel):
    """Defines arguments for the compare_moving_averages function."""
    num_days: int = Field(..., description="The number of recent days for calculation.")
    window_size: int = Field(..., description="The size of the moving average window.")


def main():
    """
    Main function to run the financial analyst assistant.
    """
    print("Fetching stock data...")
    loaded_data = {key: get_stocks_data(url) for key, url in STOCK_URLS.items()}
    print("Data loaded successfully.")

    calculate_moving_average_tool = partial(
        calculate_moving_average, available_data=loaded_data
    )
    compare_moving_averages_tool = partial(
        compare_moving_averages, available_data=loaded_data
    )

    tools = [
        {
            "type": "function",
            "function": {
                "name": "calculate_moving_average",
                "description": "Calculate the moving average of a single company's stock data.",
                "parameters": CalculateMovingAverageArgs.model_json_schema(),
            },
        },
        {
            "type": "function",
            "function": {
                "name": "compare_moving_averages",
                "description": "Compare the moving averages of company A and company B.",
                "parameters": CompareMovingAveragesArgs.model_json_schema(),
            },
        },
    ]

    available_functions = {
        "calculate_moving_average": calculate_moving_average_tool,
        "compare_moving_averages": compare_moving_averages_tool,
    }

    messages = [
        {
            "role": "system",
            "content": "You are a helpful financial analyst. Use the supplied tools to assist the user. "
                       "When comparing, state which company has the higher moving average and by how much.",
        },
        {
            "role": "user",
            "content": "How do the 10-day moving averages for company A and company B compare over the last 50 days?",
        },
    ]

    ai_api_key = os.environ.get("CEREBRAS_API_KEY")
    if not ai_api_key:
        print("Error: CEREBRAS_API_KEY environment variable not set.")
        return

    client = Cerebras(api_key=ai_api_key)

    print("\n--- Sending request to LLM ---")
    response = client.chat.completions.create(
        model="qwen-3-32b",
        messages=messages,
        tools=tools,
        tool_choice="auto",
    )

    response_message = response.choices[0].message

    if response_message.tool_calls:
        print("LLM decided to use a tool.")
        tool_call = response_message.tool_calls[0]
        function_name = tool_call.function.name

        if function_name not in available_functions:
            print(f"Error: LLM tried to call an unknown function: {function_name}")
            return

        print(f"Calling function: {function_name}")
        function_to_call = available_functions[function_name]

        try:
            arguments = json.loads(tool_call.function.arguments)
            tool_result = function_to_call(**arguments)

            print("\n--- Tool Result ---")
            print(json.dumps(tool_result, indent=2))

            print("\n--- Sending tool result back to LLM for final answer ---")
            messages.append(response_message)
            messages.append(
                {
                    "tool_call_id": tool_call.id,
                    "role": "tool",
                    "name": function_name,
                    "content": json.dumps(tool_result),
                }
            )

            final_response = client.chat.completions.create(
                model="qwen-3-32b",
                messages=messages,
            )

            final_answer = final_response.choices[0].message.content
            print("\n--- Final AI Analyst Answer ---")
            print(final_answer)

        except Exception as e:
            print(f"An error occurred during tool execution: {e}")
    else:
        print("\n--- Direct LLM Response ---")
        print(response_message.content)


if __name__ == "__main__":
    main()

Here’s a sample response:


Data loaded successfully.

--- Sending request to LLM ---
LLM decided to use a tool.
Calling function: compare_moving_averages

--- Tool Result ---
{
  "company_a": {
    "latest_date": "2025-06-20",
    "moving_average": 199.41
  },
  "company_b": {
    "latest_date": "2025-06-20",
    "moving_average": 176.11
  },
  "summary": "As of their latest data points, Company A's 10-day moving average is 199.41 (2025-06-20), while Company B's is 176.11 (2025-06-20)."
}

--- Sending tool result back to LLM for final answer ---

--- Final AI Analyst Answer ---
<think>
Okay, let me start by understanding the user's question. They want to compare the 10-day moving averages of company A and company B over the last 50 days. The tool response provided data for both companies as of June 20, 2025.

First, I need to verify if the data matches the query. The user asked for a 50-day comparison, but the tool's summary only gives the moving average as of the latest date. The answer should confirm whether the data spans the last 50 days correctly. Since the latest date is 2025-06-20 and the moving average is based on the current data up to that point, it's likely that the moving average reflects the closing prices over a period that includes the last 50 days. However, the exact comparison over the 50-day window isn't detailed in the tool's response beyond the current snapshot.

The key points here are the latest moving averages. The user asked for a comparison over the last 50 days, but the tool's output only shows the most recent numbers. This might mean that the user is interested in the current state of the moving averages, perhaps to assess recent performance trends. 

Next, I should calculate the difference between the two moving averages. Company A's is 199.41 and Company B's is 176.11. Subtracting these gives 23.3 points. To make it more meaningful, I can express this difference as a percentage of the lower moving average (Company B's). The percentage difference is (23.3 / 176.11) * 100 ≈ 13.23%. That shows how much higher Company A's moving average is in relative terms. 

I should also consider if there's any missing information. The user might expect a time series comparison or a trend analysis over the 50 days, but since the tool's response only provides the latest values, I need to clarify that my answer is based on a single point in time. It's possible that the 10-day moving averages fluctuated over the past 50 days, but without historical data, I can only state the current difference. 

Therefore, the response should highlight the latest moving averages, the absolute difference, and the percentage difference. I should also mention that the data is as of June 20, 2025, and note that a more detailed 50-day trend would require additional historical data, which isn't provided here.
</think>

**Comparison of 10-day Moving Averages:**

- **Company A**: 199.41 (as of 2025-06-20)  
- **Company B**: 176.11 (as of 2025-06-20)  

**Conclusion**:  
Company A's 10-day moving average is **$23.30 higher** than Company B's, representing a **13.23% increase** relative to Company B's value. This comparison reflects the latest data point for both companies, but historical trends over the 50-day period would require a time-series analysis (not included in the provided data).

Process finished with exit code 0

Why use tools with AI?

  • Access to Real-Time Data: LLMs can query live APIs for up-to-the-minute information.

  • Interacting with External Systems: Control databases, send emails, or interact with any system that has an API.

  • Performing Complex Calculations: Offload specialized computations to dedicated functions, like our SMA calculator.

  • Improved Accuracy and Reliability: Ground LLM responses in factual data retrieved by tools, rather than relying solely on its training.

By integrating tools with the Cerebras SDK, you can build more dynamic, capable, and reliable AI applications. This example is just the tip of the iceberg – the possibilities are vast!