Back to blog
Tutorial·9 min read·1715 words

How to Build an AI Function Calling Agent with Structured Outputs (2026 Tutorial)

A complete, production-ready tutorial for building an AI agent that uses function calling with structured JSON outputs. Includes code for tool definitions, parallel function execution, error handling, and a working example using the OpenAI-compatible Qubax API.

How to Build an AI Function Calling Agent with Structured Outputs (2026 Tutorial) — illustration

Function calling is the bridge between language models and the real world. Without it, an LLM is a sophisticated text completer. With it, the model becomes an agent that can look up data, call APIs, run calculations, and take actions. This tutorial walks through building a production-ready function calling agent with structured outputs, using the OpenAI-compatible API at Qubax AI.

What Is Function Calling?

Function calling (also called tool use) is a capability where the LLM can decide to call a predefined function instead of (or in addition to) generating text. You provide the model with a list of available functions and their schemas. The model decides when to call a function, what arguments to pass, and then returns a structured JSON object you can execute.

The flow is:

  1. You send a prompt plus a list of available functions (as JSON schemas)
  2. The model decides whether to call a function and with what arguments
  3. The model returns a structured response containing the function name and arguments
  4. You execute the function in your code
  5. You send the function result back to the model
  6. The model uses the result to generate a final response (or calls another function)

This loop — prompt, function call, execution, result, repeat — is the core of every AI agent framework.

Prerequisites

You need:

  • A Qubax AI API key (get one at qubax.ai)
  • Node.js 20+ or Python 3.11+
  • Basic familiarity with async/await

This tutorial uses the OpenAI-compatible endpoint, so the code works with any OpenAI SDK. We will use Node.js and the official openai package.

Step 1: Install Dependencies

bash
npm install openai zod

We use Zod for runtime schema validation of function arguments. This catches malformed model outputs before they hit your actual functions.

Step 2: Define Your Tools

The key to good function calling is well-defined tool schemas. The model can only call functions it knows about, and it relies entirely on your descriptions and parameter schemas to decide when and how to call them.

typescript
import { z } from "zod";

// Define the schema for each function's arguments
const weatherSchema = z.object({
  location: z.string().describe("City name, e.g. 'San Francisco, CA'"),
  units: z.enum(["celsius", "fahrenheit"]).optional().describe("Temperature units"),
});

const calculatorSchema = z.object({
  expression: z.string().describe("Mathematical expression to evaluate, e.g. '2 + 2' or 'sin(pi/4)'"),
});

const searchSchema = z.object({
  query: z.string().describe("Search query"),
  maxResults: z.number().int().min(1).max(10).optional().describe("Max results to return (default 5)"),
});

// Convert Zod schemas to JSON Schema for the API
const tools = [
  {
    type: "function" as const,
    function: {
      name: "get_weather",
      description: "Get the current weather for a given location. Use this when the user asks about weather, temperature, or conditions in a specific place.",
      parameters: {
        type: "object",
        properties: {
          location: { type: "string", description: "City name, e.g. 'San Francisco, CA'" },
          units: { type: "string", enum: ["celsius", "fahrenheit"], description: "Temperature units" },
        },
        required: ["location"],
      },
    },
  },
  {
    type: "function" as const,
    function: {
      name: "calculate",
      description: "Evaluate a mathematical expression. Use this for any math the user asks about.",
      parameters: {
        type: "object",
        properties: {
          expression: { type: "string", description: "Mathematical expression, e.g. '2 + 2'" },
        },
        required: ["expression"],
      },
    },
  },
  {
    type: "function" as const,
    function: {
      name: "web_search",
      description: "Search the web for current information. Use this when the user asks about recent events, facts you are not sure about, or current data.",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search query" },
          maxResults: { type: "integer", minimum: 1, maximum: 10, description: "Max results (default 5)" },
        },
        required: ["query"],
      },
    },
  },
];

Notice the descriptions. They are not just labels — they are instructions to the model about when to use each tool. "Use this when the user asks about weather" is far more effective than just "Get weather."

Step 3: Implement the Function Handlers

typescript
async function getWeather(args: z.infer<typeof weatherSchema>) {
  const { location, units = "fahrenheit" } = args;
  console.log(`[Tool] get_weather: ${location}, units=${units}`);
  // In production, call a real weather API here
  return { location, temperature: units === "celsius" ? 22 : 72, conditions: "Partly cloudy", humidity: 65, units };
}

async function calculate(args: z.infer<typeof calculatorSchema>) {
  const { expression } = args;
  console.log(`[Tool] calculate: ${expression}`);
  try {
    const result = Function(`"use strict"; return (${expression})`)();
    return { expression, result: Number(result) };
  } catch (e) {
    return { expression, error: "Invalid expression" };
  }
}

async function webSearch(args: z.infer<typeof searchSchema>) {
  const { query, maxResults = 5 } = args;
  console.log(`[Tool] web_search: ${query}`);
  return { query, results: [{ title: "Sample result", url: "https://example.com", snippet: "Sample." }] };
}

const functionMap = { get_weather: getWeather, calculate: calculate, web_search: webSearch };

Step 4: Build the Agent Loop

This is the core of the agent. It sends messages, handles function calls, executes them, and loops until the model gives a final text response.

typescript
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.QUBAX_API_KEY,
  baseURL: "https://api.qubax.ai/v1",
});

async function runAgent(userMessage, maxIterations = 10) {
  const messages = [
    { role: "system", content: "You are a helpful assistant. Use tools when needed." },
    { role: "user", content: userMessage },
  ];

  for (let i = 0; i < maxIterations; i++) {
    const response = await client.chat.completions.create({
      model: "glm-5.2",
      messages,
      tools,
      tool_choice: "auto",
    });

    const message = response.choices[0].message;
    messages.push(message);

    if (!message.tool_calls || message.tool_calls.length === 0) {
      return message.content;
    }

    for (const toolCall of message.tool_calls) {
      const fn = toolCall.function.name;
      const args = JSON.parse(toolCall.function.arguments);
      try {
        const result = await functionMap[fn](args);
        messages.push({ role: "tool", tool_call_id: toolCall.id, content: JSON.stringify(result) });
      } catch (error) {
        messages.push({ role: "tool", tool_call_id: toolCall.id, content: JSON.stringify({ error: String(error) }) });
      }
    }
  }
}

Step 5: Run It

typescript
await runAgent("What is the weather in Tokyo and what is 15% of 2400?");

The model calls both tools in parallel in a single iteration, then combines the results into a final answer. This parallel calling is a key optimization — the model batches independent function calls rather than making them sequentially.

Step 6: Add Error Handling and Retries

Production agents need robust error handling. Models sometimes produce malformed JSON, call functions that do not exist, or pass invalid arguments. Always validate with Zod before executing:

typescript
const result = schema.safeParse(args);
if (!result.success) {
  messages.push({ role: "tool", tool_call_id: toolCall.id, content: JSON.stringify({ error: result.error.message }) });
  continue;
}

Step 7: Choose the Right Model for Function Calling

Not all models are equally good at function calling. Here are practical recommendations:

  • For complex multi-step reasoning with tools: Claude Opus 5 or GPT-5.6 Sol — the most capable models, best at deciding when and how to call tools. More expensive but most reliable.
  • For balanced cost and capability: GLM 5.2 or DeepSeek V4 Pro — strong function calling support at a fraction of the cost. GLM 5.2 costs around $0.0085 per million input tokens, making it ideal for high-volume tool-use workloads.
  • For maximum speed and lowest cost: Gemini 2.5 Flash or GPT-5.4 Nano — fast and cheap, good for simple single-tool calls.

You can compare real-time pricing and capabilities for all these models at Qubax AI's model catalog. The OpenAI-compatible API means you can switch models by changing one line of code — just the model name.

Best Practices for Production Agents

  1. Write detailed tool descriptions. The model's decision to call a tool depends entirely on the description. "Get weather for a location" is mediocre. "Get current weather conditions, temperature, and humidity for any city worldwide. Use this whenever the user asks about weather, temperature, forecasts, or climate conditions in a specific location." is excellent.
  1. Use structured parameter schemas. Provide types, enums, and descriptions for every parameter. The model uses these to construct valid arguments.
  1. Handle parallel tool calls. Modern models can call multiple tools in a single response. Your agent loop should execute them all (potentially in parallel) before sending results back.
  1. Validate everything. Use Zod or similar to validate function arguments before execution. Never trust model output blindly.
  1. Set iteration limits. Always cap the agent loop to prevent infinite cycles where the model keeps calling tools without converging on an answer.
  1. Log every tool call. For debugging and cost tracking, log the function name, arguments, and result for every call.

Full Code Example

The complete working example is above. To run it:

bash
export QUBAX_API_KEY="your-api-key"
npx tsx agent.ts

The full code defines three tools (weather, calculator, search), implements the agent loop with parallel tool execution, validates arguments with Zod, and handles errors gracefully. It is ready to extend with your own tools.

FAQ

What is function calling in AI?

Function calling is a capability where the LLM can decide to call predefined functions (tools) instead of just generating text. You provide the model with function schemas, the model decides when to call them and with what arguments, and you execute the functions and return results to the model. This is how AI agents interact with external systems.

Which models support function calling?

Most modern LLMs support function calling, including Claude Opus 5, GPT-5.6 Sol, GLM 5.2, DeepSeek V4 Pro, Gemini 3.1 Pro, and Grok 4. The quality of function calling varies — more capable models are better at deciding when to call tools and constructing valid arguments. See Qubax AI's model catalog for options.

Can a model call multiple functions at once?

Yes. Modern models can call multiple tools in a single response, and your agent loop should execute them all (potentially in parallel) before sending results back. This is a significant optimization for tasks that require multiple independent lookups.

How do I prevent the model from calling invalid functions?

Validate function arguments with a schema library like Zod before executing the function. Also, always check that the function name exists in your function map. If the model produces invalid output, return an error message as the tool result and let the model try again.

What is the cheapest model for function calling?

GLM 5.2 is one of the cheapest models with strong function calling support, at around $0.0085 per million input tokens. For high-volume tool-use workloads, it offers excellent value. Compare pricing across all models at Qubax AI.

Article tags

#function calling#AI agents#structured outputs#tutorial#API
Share:Post on XTelegramLinkedInYHacker NewsReddit
Qubax AI

Qubax AI

AI Models at up to 99% off · Pay with crypto

Access GPT, Claude, Gemini, GLM & 340+ models through one OpenAI-compatible API. Up to 99% off. Pay with 200+ cryptocurrencies. No credit card needed.

Related articles