Intermediate 25 min

Wiring the LLM with Function Calling

Setting Up the OpenAI Client

Create src/agent.ts and set up the client:

import OpenAI from 'openai';
import * as dotenv from 'dotenv';

dotenv.config();

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

Define the System Prompt

The system prompt tells the model what it is and what it should do:

const SYSTEM_PROMPT = `You are a to-do list agent. You help users add, list, and complete tasks using the available tools.

When a user asks you to:
- Add a task: Use the addTodo tool
- List tasks: Use the listTodos tool
- Complete a task: Use the completeTodo tool

Always be helpful and confirm what you've done.`;

Define Tool Schemas

The LLM needs to know what tools are available. We define them as JSON schemas. Here’s how each tool is defined:



              {
type: 'function' as const,
function: {
  name: 'addTodo',
  description: 'Add a new todo item to the list',
  parameters: {
    type: 'object',
    properties: {
      text: {
        type: 'string',
        description: 'The text of the todo item',
      },
      dueDate: {
        type: 'string',
        description: 'Optional due date in YYYY-MM-DD format',
      },
    },
    required: ['text'],
  },
},
}
            

Each tool schema tells the LLM:

  • name: What the function is called
  • description: What it does (the LLM uses this to decide when to call it)
  • parameters: What arguments it needs
  • required: Which parameters are mandatory
const tools = [
  {
    type: 'function' as const,
    function: {
      name: 'addTodo',
      description: 'Add a new todo item to the list',
      parameters: {
        type: 'object',
        properties: {
          text: {
            type: 'string',
            description: 'The text of the todo item',
          },
          dueDate: {
            type: 'string',
            description: 'Optional due date in YYYY-MM-DD format',
          },
        },
        required: ['text'],
      },
    },
  },
  {
    type: 'function' as const,
    function: {
      name: 'listTodos',
      description: 'List all todo items',
      parameters: {
        type: 'object',
        properties: {},
      },
    },
  },
  {
    type: 'function' as const,
    function: {
      name: 'completeTodo',
      description: 'Mark a todo item as completed',
      parameters: {
        type: 'object',
        properties: {
          id: {
            type: 'number',
            description: 'The ID of the todo to complete',
          },
        },
        required: ['id'],
      },
    },
  },
];

Making the First Call

Here’s how to call the model with tools:

async function callAgent(userMessage: string) {
  const messages = [
    { role: 'system' as const, content: SYSTEM_PROMPT },
    { role: 'user' as const, content: userMessage },
  ];

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

  return response;
}

Handling Tool Calls

When the model wants to call a tool, it returns tool_calls in the response:

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

if (message.tool_calls) {
  // The model wants to call tools
  for (const toolCall of message.tool_calls) {
    const functionName = toolCall.function.name;
    const args = JSON.parse(toolCall.function.arguments);

    // Call the actual function
    let result;
    if (functionName === 'addTodo') {
      result = addTodo(args.text, args.dueDate);
    } else if (functionName === 'listTodos') {
      result = listTodos();
    } else if (functionName === 'completeTodo') {
      result = completeTodo(args.id);
    }

    // Send the result back to the model
    messages.push({
      role: 'tool' as const,
      content: JSON.stringify(result),
      tool_call_id: toolCall.id,
    });
  }

  // Call the model again with the tool results
  const secondResponse = await client.chat.completions.create({
    model: 'gpt-4o-mini',
    messages,
    tools,
    tool_choice: 'auto',
  });

  return secondResponse;
}

Spot the Bug

Here’s a common mistake. What’s wrong with this code?

const args = toolCall.function.arguments;
const result = addTodo(args.text);

Answer: arguments is a string, not an object. You need to JSON.parse it first:

const args = JSON.parse(toolCall.function.arguments);

🔷 TypeScript Parsing Tool Arguments
📟 Console Output
Run code to see output...

Now you understand how to wire tools to the LLM. Let’s build the complete agent loop.