Tool Calling

Enable LLMs to interact with external systems using function/tool calling.

Tool calling (also known as function calling) allows LLMs to interact with external systems by generating structured output that your application can execute. This enables powerful integrations like database queries, API calls, calculations, and more.

Model capabilities

Not all models within a provider support tool calling. Check the model's documentation for specific capabilities. Generally, larger and newer models have better tool calling support.

Why tool calling matters

  • External integrations — Connect LLMs to databases, APIs, and services
  • Structured output — Get reliable JSON responses instead of free-form text
  • Multi-step reasoning — Let models break down complex tasks into tool calls
  • Real-time data — Access live information beyond the model's training cutoff

How it works

You define tools with a name, description, and JSON schema for parameters. The model decides when to call a tool based on the user's message, returning a tool_calls object with the function name and arguments.

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://api.aivene.com/v1',
  apiKey: process.env.AIVENE_API_KEY
});

const tools = [
  {
    type: 'function',
    function: {
      name: 'get_weather',
      description: 'Get current weather for a location',
      parameters: {
        type: 'object',
        properties: {
          location: {
            type: 'string',
            description: 'City name, e.g. "San Francisco"'
          },
          unit: {
            type: 'string',
            enum: ['celsius', 'fahrenheit'],
            description: 'Temperature unit'
          }
        },
        required: ['location']
      }
    }
  }
];

const response = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [
    { role: 'user', content: 'What is the weather in Tokyo?' }
  ],
  tools
});

console.log(response.choices[0].message.tool_calls);
// [{ id: 'call_abc123', type: 'function', function: { name: 'get_weather', arguments: '{"location":"Tokyo"}' } }]

Handling tool calls

When the model returns tool calls, you execute the functions and send the results back in a follow-up message.

const message = response.choices[0].message;

if (message.tool_calls) {
  const toolResults = await Promise.all(
    message.tool_calls.map(async (toolCall) => {
      const args = JSON.parse(toolCall.function.arguments);

      // Execute the actual function
      let result;
      if (toolCall.function.name === 'get_weather') {
        result = await fetchWeather(args.location, args.unit);
      }

      return {
        role: 'tool',
        tool_call_id: toolCall.id,
        content: JSON.stringify(result)
      };
    })
  );

  // Send tool results back to the model
  const finalResponse = await client.chat.completions.create({
    model: 'gpt-4o',
    messages: [
      { role: 'user', content: 'What is the weather in Tokyo?' },
      message,
      ...toolResults
    ],
    tools
  });

  console.log(finalResponse.choices[0].message.content);
  // "The weather in Tokyo is currently 22°C with partly cloudy skies."
}

Parallel tool calls

Models can request multiple tool calls in a single response. This is useful for queries that require data from multiple sources.

const response = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [
    { role: 'user', content: 'Compare weather in Tokyo and New York' }
  ],
  tools
});

// message.tool_calls may contain multiple calls:
// [
//   { function: { name: 'get_weather', arguments: '{"location":"Tokyo"}' } },
//   { function: { name: 'get_weather', arguments: '{"location":"New York"}' } }
// ]

Parallel execution

When you receive multiple tool calls, execute them in parallel using Promise.all() for better performance. Return all results in the same order as the tool calls.

Tool choice

Control how the model uses tools with the tool_choice parameter:

ValueBehavior
"auto"Model decides whether to call a tool (default)
"none"Model won't call any tools
"required"Model must call at least one tool
{ type: "function", function: { name: "..." } }Force a specific tool
// Force the model to use a specific tool
const response = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'Tell me about Paris' }],
  tools,
  tool_choice: { type: 'function', function: { name: 'get_weather' } }
});

Streaming with tools

Tool calls work with streaming responses. The tool call data arrives in chunks that you need to accumulate.

const stream = await client.chat.completions.create({
  model: 'gpt-4o',
  messages: [{ role: 'user', content: 'What is the weather in London?' }],
  tools,
  stream: true
});

let toolCalls: Record<number, { id: string; name: string; arguments: string }> = {};

for await (const chunk of stream) {
  const delta = chunk.choices[0]?.delta;

  if (delta?.tool_calls) {
    for (const tc of delta.tool_calls) {
      if (!toolCalls[tc.index]) {
        toolCalls[tc.index] = { id: tc.id ?? '', name: '', arguments: '' };
      }
      if (tc.id) toolCalls[tc.index].id = tc.id;
      if (tc.function?.name) toolCalls[tc.index].name = tc.function.name;
      if (tc.function?.arguments) toolCalls[tc.index].arguments += tc.function.arguments;
    }
  }
}

// Now execute tools with accumulated data
console.log(Object.values(toolCalls));

Best practices

  1. Write clear descriptions — Tool and parameter descriptions help the model understand when and how to use each tool. Be specific about what the tool does and what format arguments should be in.

  2. Use strict schemas — Define required fields and enums where possible to get more reliable outputs from the model.

  3. Validate arguments — Always validate and sanitize tool arguments before executing them. The model may produce malformed or unexpected values.

  4. Handle errors gracefully — If a tool fails, return an error message to the model so it can adjust its approach or inform the user.

  5. Limit tool count — While you can define many tools, too many options can confuse the model. Group related functionality and keep the list focused.

Example: Multi-tool agent

Here's a complete example with multiple tools working together:

const tools = [
  {
    type: 'function',
    function: {
      name: 'search_products',
      description: 'Search for products in the catalog',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string', description: 'Search query' },
          category: { type: 'string', description: 'Product category' },
          max_price: { type: 'number', description: 'Maximum price filter' }
        },
        required: ['query']
      }
    }
  },
  {
    type: 'function',
    function: {
      name: 'get_product_details',
      description: 'Get detailed information about a specific product',
      parameters: {
        type: 'object',
        properties: {
          product_id: { type: 'string', description: 'The product ID' }
        },
        required: ['product_id']
      }
    }
  },
  {
    type: 'function',
    function: {
      name: 'add_to_cart',
      description: 'Add a product to the shopping cart',
      parameters: {
        type: 'object',
        properties: {
          product_id: { type: 'string', description: 'The product ID' },
          quantity: { type: 'number', description: 'Quantity to add', default: 1 }
        },
        required: ['product_id']
      }
    }
  }
];

async function chat(userMessage: string, history: Message[]) {
  const messages = [...history, { role: 'user', content: userMessage }];

  while (true) {
    const response = await client.chat.completions.create({
      model: 'gpt-4o',
      messages,
      tools
    });

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

    if (!message.tool_calls) {
      return message.content;
    }

    // Execute all tool calls
    const toolResults = await Promise.all(
      message.tool_calls.map(async (tc) => {
        const args = JSON.parse(tc.function.arguments);
        const result = await executeTool(tc.function.name, args);

        return {
          role: 'tool',
          tool_call_id: tc.id,
          content: JSON.stringify(result)
        };
      })
    );

    messages.push(...toolResults);
  }
}

Common patterns

Retrieval-augmented generation (RAG)

Use tools to fetch relevant documents before generating a response:

const tools = [{
  type: 'function',
  function: {
    name: 'search_knowledge_base',
    description: 'Search internal documentation and knowledge base',
    parameters: {
      type: 'object',
      properties: {
        query: { type: 'string', description: 'Search query' }
      },
      required: ['query']
    }
  }
}];

Code execution

Let the model run code in a sandboxed environment:

const tools = [{
  type: 'function',
  function: {
    name: 'execute_python',
    description: 'Execute Python code and return the output',
    parameters: {
      type: 'object',
      properties: {
        code: { type: 'string', description: 'Python code to execute' }
      },
      required: ['code']
    }
  }
}];

Database queries

Enable natural language to SQL conversion:

const tools = [{
  type: 'function',
  function: {
    name: 'query_database',
    description: 'Execute a read-only SQL query on the database',
    parameters: {
      type: 'object',
      properties: {
        sql: { type: 'string', description: 'SQL SELECT query' }
      },
      required: ['sql']
    }
  }
}];

Limitations

  • No direct execution — The model generates tool calls but cannot execute them directly. Your application must handle execution.
  • Token overhead — Tool definitions count toward input tokens. Large schemas increase costs.
  • Hallucinated arguments — Models may invent plausible-looking but invalid argument values. Always validate inputs.
  • Context window — Long tool results consume context space. Consider summarizing or truncating large responses.