Skip to main content
Conduit.im

Building a Chatbot

Build a conversational chatbot with multi-turn memory using the Conduit.im API, step by step.

What You'll Build

By the end of this guide you'll have a working chatbot that:

  • Sends and receives messages via the Chat Completions API
  • Maintains conversation history for multi-turn dialogue
  • Uses a system prompt to control the assistant's behaviour
  • Streams responses for a real-time typing effect

Prerequisites

  • A Conduit.im account with an API key
  • Node.js 18 or later installed
  • Basic familiarity with JavaScript and REST APIs

Step 1: Project Setup

Create a new directory and initialise a Node.js project:

mkdir conduit-chatbot && cd conduit-chatbot
npm init -y

Store your API key in an environment variable so it stays out of source code:

export CONDUIT_API_KEY="your-api-key-here"

Tip: In production, use a .env file or a secrets manager instead of exporting directly in the shell.

Step 2: Send Your First Message

Create a file called chat.mjs with a simple function that calls the Chat Completions API:

// chat.mjs
const API_URL = "https://api.conduit.im/v1/chat/completions";
const API_KEY = process.env.CONDUIT_API_KEY;

async function chat(userMessage) {
  const response = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-4",
      messages: [
        { role: "user", content: userMessage },
      ],
    }),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error?.message || "Request failed");
  }

  const data = await response.json();
  return data.choices[0].message.content;
}

// Try it out
const reply = await chat("What is an API gateway?");
console.log(reply);

Run it:

node chat.mjs

You should see the model's answer printed to the console. This is a one-shot request — the model has no memory of previous messages. Next we'll fix that.

Step 3: Add Conversation History

To make the chatbot remember context, keep a running array of messages and send the full history with every request. Add a system prompt to shape the assistant's personality.

// chatbot.mjs
import * as readline from "node:readline/promises";

const API_URL = "https://api.conduit.im/v1/chat/completions";
const API_KEY = process.env.CONDUIT_API_KEY;

// Conversation history — the system message sets the tone
const messages = [
  {
    role: "system",
    content:
      "You are a friendly and concise coding assistant. " +
      "Answer questions clearly and include short code examples when helpful.",
  },
];

async function sendMessage(userMessage) {
  messages.push({ role: "user", content: userMessage });

  const response = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-4",
      messages,          // send the full history
      max_tokens: 1024,
    }),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error?.message || "Request failed");
  }

  const data = await response.json();
  const reply = data.choices[0].message.content;

  // Append the assistant's reply so the next call has it
  messages.push({ role: "assistant", content: reply });
  return reply;
}

// Interactive loop
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

console.log('Chatbot ready — type "exit" to quit.\n');

while (true) {
  const input = await rl.question("You: ");
  if (input.toLowerCase() === "exit") break;

  const reply = await sendMessage(input);
  console.log(`\nAssistant: ${reply}\n`);
}

rl.close();

Run node chatbot.mjs and try a multi-turn conversation. The bot now remembers what you said earlier in the session.

How Conversation Memory Works

The Chat Completions API is stateless — the server does not store your conversation. Memory is achieved by sending the full message array each time:

// Turn 1 — one user message
{ "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user",   "content": "What is Node.js?" }
]}

// Turn 2 — previous exchange included
{ "messages": [
    { "role": "system",    "content": "You are a helpful assistant." },
    { "role": "user",      "content": "What is Node.js?" },
    { "role": "assistant", "content": "Node.js is a JavaScript runtime..." },
    { "role": "user",      "content": "How do I install it?" }
]}

Keep in mind: Every token in the message history counts towards your usage. For long conversations, consider trimming older messages or summarising them to control costs.

Step 4: Stream Responses

Streaming lets you display tokens as they arrive instead of waiting for the full response. Set "stream": true and process server-sent events:

async function sendMessageStreaming(userMessage) {
  messages.push({ role: "user", content: userMessage });

  const response = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-4",
      messages,
      stream: true,
    }),
  });

  if (!response.ok) {
    const err = await response.json();
    throw new Error(err.error?.message || "Request failed");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let fullReply = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value, { stream: true });
    // Each SSE chunk is prefixed with "data: "
    for (const line of chunk.split("\n")) {
      if (!line.startsWith("data: ")) continue;
      const payload = line.slice(6);
      if (payload === "[DONE]") break;

      const parsed = JSON.parse(payload);
      const token = parsed.choices[0]?.delta?.content || "";
      process.stdout.write(token);
      fullReply += token;
    }
  }

  console.log(); // newline after streaming finishes
  messages.push({ role: "assistant", content: fullReply });
  return fullReply;
}

Replace the sendMessage call in your interactive loop with sendMessageStreaming to see tokens appear in real time.

Step 5: Switch Models Easily

One of Conduit.im's key benefits is switching between models with a single parameter change. No SDK swaps, no credential changes — just update the model field:

// Try different models by changing this value
const MODEL = "claude-3-sonnet";   // or "gpt-4", "gemini-pro", etc.

body: JSON.stringify({
  model: MODEL,
  messages,
  max_tokens: 1024,
})

Browse the Models page to see every model available through the API, including pricing and context window sizes.

Error Handling

Production chatbots should handle errors gracefully. Here are the most common scenarios:

StatusCauseRecommended Action
401Invalid or missing API keyCheck your key and environment variable
402Insufficient balanceAdd credits on the billing page
429Rate limit exceededWait and retry with exponential back-off
500Server or upstream errorRetry after a short delay

See the Error Handling reference for the full list of error codes.

Production Tips

Trim conversation history

Long histories increase token usage and cost. Keep a sliding window of the most recent messages, or summarise older turns into a single system message.

Set spending limits

Configure per-key spending limits so a runaway loop can't drain your balance.

Use streaming in user-facing apps

Streaming dramatically improves perceived latency. Users see the first token within milliseconds instead of waiting several seconds for the full response.

Store conversations server-side

Never send API requests directly from the browser. Proxy through your own server so the API key stays secret and you can log conversations for debugging.

Next Steps