Build an AI Agent with Tool Calling
Use the OpenAI/Anthropic API to build an agent that plans, calls tools, and loops.
In this project you'll build a minimal coding agent from scratch β one that can read files, edit them, and run commands, all driven by an LLM with tool calling.
What you'll build
A Node.js script agent.js that:
- Takes a task description as input.
- Sends it to an LLM with tool definitions.
- Executes the tool calls the model requests.
- Feeds results back until the task is done.
Step 1: Define the tools
// agent.js
const tools = [
{
type: "function",
function: {
name: "read_file",
description: "Read a file from disk. Returns the file contents.",
parameters: {
type: "object",
properties: {
path: { type: "string", description: "File path to read" }
},
required: ["path"]
}
}
},
{
type: "function",
function: {
name: "write_file",
description: "Write content to a file. Creates or overwrites.",
parameters: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string" }
},
required: ["path", "content"]
}
}
},
{
type: "function",
function: {
name: "run_command",
description: "Run a shell command and return its output.",
parameters: {
type: "object",
properties: {
command: { type: "string" }
},
required: ["command"]
}
}
}
];
Step 2: Implement tool execution
const fs = require("fs");
const { execSync } = require("child_process");
function executeTool(name, args) {
switch (name) {
case "read_file":
return fs.readFileSync(args.path, "utf8");
case "write_file":
fs.writeFileSync(args.path, args.content);
return `Wrote ${args.content.length} bytes to ${args.path}`;
case "run_command":
return execSync(args.command, { encoding: "utf8", timeout: 10000 });
default:
return `Unknown tool: ${name}`;
}
}
Step 3: The agent loop
async function agentLoop(task, maxSteps = 10) {
const messages = [
{ role: "system", content: "You are a coding assistant. Use tools to complete tasks. When done, reply with just 'DONE'." },
{ role: "user", content: task }
];
for (let step = 0; step < maxSteps; step++) {
// Call the LLM (replace with your API)
const response = await callLLM(messages, tools);
// If the model replied with text (no tool calls), check if done
if (response.content) {
console.log(`[step ${step}] ${response.content}`);
if (response.content.trim() === "DONE") return;
messages.push({ role: "assistant", content: response.content });
}
// Execute any tool calls
if (response.tool_calls) {
for (const call of response.tool_calls) {
const result = executeTool(call.function.name, JSON.parse(call.function.arguments));
console.log(`[tool] ${call.function.name} β ${result.slice(0, 100)}...`);
messages.push({ role: "assistant", tool_calls: [call] });
messages.push({ role: "tool", tool_call_id: call.id, content: result });
}
}
}
console.log("Reached max steps.");
}
Step 4: Wire up the LLM
async function callLLM(messages, tools) {
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
},
body: JSON.stringify({
model: "gpt-4o",
messages,
tools
})
});
const data = await res.json();
return data.choices[0].message;
}
Step 5: Run it
export OPENAI_API_KEY=sk-...
node agent.js "Create a file called hello.py with a function that prints 'Hello, World!'"
Checkpoint
You should see the agent:
- Call
write_fileto createhello.py. - Call
run_commandto runpython hello.py. - Reply "DONE" after seeing the output.
What you learned
- The agent loop: decide β act β observe β repeat.
- Tool definitions as JSON schemas the model reads.
- Tool execution β your code runs the actual operations.
- Message history β tool results go back as context for the next LLM call.
- This is the same pattern behind Claude Code, Codex CLI, and every coding agent.