Non-Thinking Model to a Thinking Model - Chain-of-Thought (CoT) in practice with Node.js + OpenAI

MANOJ KUMARMANOJ KUMAR
9 min read

Explain how CoT turns LLMs into “thinking” models, why it matters, and exactly how to build it into a Node/Next.js app (server + client). Includes multi-chain orchestration (self-consistency), verification, parsing, and practical tips.

Quick references: Chain-of-Thought (CoT) was introduced and studied in ML research as a prompting technique that elicits intermediate reasoning steps from LLMs this method improves performance on many multi-step tasks. For background reading see the CoT paper. (arXiv)

Chain-of-Thought (CoT)


Why this matters

  • CoT = ask the model to “show its work.” Instead of a single verdict, the model returns intermediate steps and then its final answer. This improves correctness for math, logic, multi-hop reasoning, and planning tasks. (arXiv)

  • Self-consistency = run many independent chains, then pick the majority answer. This reduces single-run noise.

    Self-consistency

  • Verification = programmatic checks. Combine CoT with deterministic calculators or retrieval to detect and fix mistakes. (OpenAI)


Short conceptual recap: how CoT works

Research shows CoT prompting consistently improves reasoning on many benchmarks

  1. Prompt the model for steps include instructions like “Show your reasoning step-by-step then give the final answer.”

  2. Extract final answer and intermediate steps parse the response for “Final answer:” or the last line.

  3. Run N independent chains (different temperature or different random seeds) → collect final answers.

  4. Aggregate (majority vote) → pick most common result (self-consistency). Optionally run a verification pass.

  5. If uncertain, escalate to a heavier verifier or human review.

(Research shows CoT prompting consistently improves reasoning on many benchmarks.) (arXiv)


High-level orchestration patterns

High-level orchestration patterns

  • Single CoT: one run that returns steps + final answer. Use for quick debugging and small tasks.

  • CoT + Verifier: run CoT once, then ask the model to check its own steps (or verify programmatically).

  • CoT-SC (Self-Consistency): run K independent chains, take the modal answer, optionally verify.

  • Debate / Dual-chain: produce two or more competing chains and compare evidence useful when assumptions differ.

(For modern production work, combine CoT with retrieval tools, calculators, and human review where needed.) (OpenAI)


Implementation: Node.js + Official OpenAI SDK (step-by-step)

Below is a full, practical example you can paste into a Next.js (or any Node) project. It uses the official OpenAI JS SDK and demonstrates:

  • single CoT run

  • multi-run self-consistency orchestration

  • parsing the model’s “Final Answer” from output

  • a simple arithmetic verification step

Prereqs

  • Node 18+ (or environment that supports fetch)

  • npm i openai (official SDK). See the repo & docs. (GitHub, OpenAI)

1) Install & environment

npm init -y
npm install openai
# create a .env with OPENAI_API_KEY=<your_key>

(If using Vercel / Next.js, set OPENAI_API_KEY in Vercel secrets/env.)

Cite: Quickstart + API reference for chat completions and SDK usage. (OpenAI)


2) Minimal single-run CoT example (server-side)

/pages/api/ask-cot.ts (Next.js API route, TypeScript)

import type { NextApiRequest, NextApiResponse } from "next";
import OpenAI from "openai";

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

type Body = { question: string };

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { question } = req.body as Body;
  if (!question) return res.status(400).json({ error: "question required" });

  // System + user message pattern
  const messages = [
    { role: "system", content: "You are a careful problem solver. Show step-by-step reasoning, number each step, then end with 'Final answer:' followed by the answer." },
    { role: "user", content: question }
  ];

  const response = await client.chat.completions.create({
    model: "gpt-4o-mini", // replace with an available reasoning-capable model
    messages,
    temperature: 0.2,
    max_tokens: 800
  });

  const text = response.choices?.[0]?.message?.content ?? "";
  return res.status(200).json({ text });
}

Explanation

  • system message defines CoT behavior (ask for numbered steps + explicit Final answer:).

  • Low temperature (0.2) encourages deterministic reasoning for single-run CoT.

  • max_tokens should be large enough for full reasoning chains.

(Cite the Chat API docs for message-based usage.) (OpenAI)


3) Multi-run Self-Consistency Orchestrator (server-side)

/pages/api/ask-cot-sc.ts

import type { NextApiRequest, NextApiResponse } from "next";
import OpenAI from "openai";

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

function parseFinalAnswer(text: string): string | null {
  // heuristic: look for lines that start with "Final" or "Final answer" or last non-empty line.
  const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
  // Try regex for "Final answer" pattern (case-insensitive)
  for (let i = lines.length - 1; i >= 0; i--) {
    const match = lines[i].match(/final[:\s-]*answer[:\s-]*(.+)$/i); //  I took this line from AI
    if (match) return match[1].trim();
  }
  // fallback: return last line
  return lines.length ? lines[lines.length - 1] : null;
}

function majorityVote(arr: string[]) {
  const map = new Map<string, number>();
  for (const a of arr) map.set(a, (map.get(a) ?? 0) + 1);
  let winner = null, max = 0;
  for (const [k, v] of map) if (v > max) { winner = k; max = v; }
  return { winner, counts: Array.from(map.entries()) };
}

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { question, runs = 5 } = req.body as { question: string; runs?: number };
  if (!question) return res.status(400).json({ error: "question required" });

  const messages = [
    { role: "system", content: "You are a careful reasoner. Provide numbered steps and end with 'Final answer: ...'." },
  ];

  const chains: { text: string; final: string | null }[] = [];

  for (let i = 0; i < runs; i++) {
    // slightly vary temperature for diversity
    const temp = i === 0 ? 0.0 : 0.4 + Math.random() * 0.3; // deterministic first run
    const resp = await client.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [...messages, { role: "user", content: question }],
      temperature: temp,
      max_tokens: 1000,
    });

    const text = resp.choices?.[0]?.message?.content ?? "";
    const final = parseFinalAnswer(text);
    chains.push({ text, final });
  }

  const finals = chains.map(c => c.final ?? "");
  const { winner, counts } = majorityVote(finals);
  // Optionally: ask the model to verify the majority result (verification pass)
  return res.status(200).json({ chains, winner, counts });
}

Explanation

  • Run N chains (configurable). One deterministic run (temp 0) + several stochastic runs.

  • parseFinalAnswer uses simple heuristics — you may refine to parse numeric answers, JSON blocks, etc.

  • majorityVote picks the most frequent final answer.

Why this helps

  • Diversity + aggregation reduces chance of one mistaken chain dominating answers (self-consistency). Research supports multi-chain aggregation for better reasoning. (arXiv)

4) Verification: programmatically check arithmetic

If the chain contains arithmetic steps, extract numeric expressions and compute them locally for safety:

// naive/safe evaluator for simple arithmetic (only digits and +-*/() and whitespace)
function safeEval(expr: string): number | null {
  if (!/^[0-9+\-*/().\s]+$/.test(expr)) return null; // deterministic first run
  try {
    // eslint-disable-next-line no-eval
    const val = eval(expr); // small controlled eval; in production prefer a math parser library like 'mathjs'
    return typeof val === "number" ? val : null;
  } catch (e) {
    return null;
  }
}

Better: use a math parsing library like mathjs to avoid eval security concerns.

How to apply

  1. From each chain, find lines that look like 3 × 2 = 6 or 3 * 2 = 6.

  2. Extract 3*2 and compute with safeEval.

  3. If computed ≠ claimed value, mark the chain as inconsistent. Exclude inconsistent chains from majority vote or flag for human review.


5) Client example (simple fetch)

Call your orchestrator from the browser:

async function askCoT(question: string) {
  const res = await fetch("/api/ask-cot-sc", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ question, runs: 5 })
  });
  return res.json();
}

// usage
const result = await askCoT("If a shop has 23 apples, uses 20, buys 6 more, how many?");
console.log(result.winner, result.chains);

Prompt templates & examples

Single CoT prompt

You are a careful problem-solver. Number each reasoning step and at the end write `Final answer: <answer>`. Be concise.
Question: ...

Self-consistency wrapper (orchestrator instruction)

Produce K independent chains of reasoning (label each Chain 1..K). For each chain, return steps and final answer. Then list all final answers and indicate the most frequent one.

Verification prompt (ask model to check a chosen chain)

Given this chain of reasoning: [paste chain], check each arithmetic step and correct any mistakes. If corrected, provide corrected final answer and explain the correction briefly.

Note: Some recent model families provide better internal reasoning without explicit “think step-by-step” phrases; consult your model’s prompt guidance. Always test different prompt formulations. (OpenAI Cookbook, OpenAI)


Evaluation & metrics

Set up an evaluation harness:

  • Accuracy: gold answer match (final answer).

  • Step correctness: % of intermediate steps that match a human or computed ground truth.

  • Consistency: fraction of runs that agree.

  • Cost: average tokens × runs.

  • Latency: time to complete N runs (can parallelize if API supports concurrency and rate limits).

Run experiments on a curated dataset with easy / medium / hard items to tune runs, temperature, and prompt wording.


Practical tips & pitfalls

  • Token & cost tradeoff: running 5 chains costs ~5× tokens. Use CoT selectively (only for hard queries).

  • Parsing robustly: don’t rely on naive string parsing ask the model to output JSON when you need structured answers. Example: ask model to return {"steps":[...],"final":"..."}.

  • Model choice matters: newer reasoning models may need less “think step-by-step” prompting; test. (OpenAI Cookbook)

  • Avoid untrusted eval via eval: use a math library.

  • Safety: CoT reveals intermediate reasoning — be mindful about sensitive data and model vulnerabilities.


Example UI wireframe (what to show to users)

  • Question input + toggle CoT ON/OFF

  • If CoT ON: show collapsible chains labeled Chain 1..K (expandable) and highlight the majority result.

  • Verification panel: show which chains had arithmetic mismatches (with computed corrections).

  • Explainability: show steps for audit; allow the user to “accept” answer or request human review.

Project: 🤖 FFP CHAT — Free Forever Persona Chat

Project: 🤖 FFP CHAT — Free Forever Persona Chat

A privacy-first AI chatbot built with Next.js, TypeScript, Redux Toolkit, and Tailwind CSS that lets you chat with different personas using your own Google Gemini API key. Experience conversations with tech educators, entrepreneurs, and industry experts, while keeping all data completely local.

Features

  • Persona-Based Conversations: Pre-built personas, category filtering, smart search

  • Tone Selection: Default, Funny, Advice, Educational

  • Privacy-First: No database, local storage only, your API key never leaves your device

  • Advanced Chat: Conversation context, image support (WIP), like/dislike responses, copy & regenerate messages

  • Modern UX: Fully responsive, dark theme, smooth animations, keyboard shortcuts

💻 Tech Stack: Next.js 14, TypeScript, Tailwind CSS, shadcn/ui, Redux Toolkit, Google Gemini API
📦 Repository: https://github.com/BCAPATHSHALA/PersonaAI

Why include this? This project is a real example of persona-based prompting and system prompt management in production: it shows how to store persona templates, send system messages, and toggle roles safely in a client-first privacy-preserving app.


Research & references

  • Chain-of-Thought prompting paper (Jason Wei et al.): shows CoT improves reasoning on multiple tasks. (arXiv)

  • OpenAI Prompt Engineering Guide: practical prompt patterns and tips. (OpenAI)

  • OpenAI Chat Completions & Quickstart docs: how to structure system/user messages and SDK examples. (OpenAI)

  • Official OpenAI Node SDK (GitHub) — library usage and examples. (GitHub)


Final takeaways

  • CoT gives you explainability. You can see how the model arrived at an answer, making debugging and trust easier.

  • Self-consistency reduces errors. Multiple independent chains + majority vote produce more robust answers.

  • Verification is essential. Programmatic checks (math, facts, retrieval) keep CoT useful for production.

  • Start small, measure, then expand. Use CoT for hard queries only; monitor tokens, latency, and accuracy.

0
Subscribe to my newsletter

Read articles from MANOJ KUMAR directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

MANOJ KUMAR
MANOJ KUMAR

Haan Ji, I am Manoj Kumar a product-focused Full Stack Developer passionate about crafting and deploying modern web apps, SaaS solutions, and Generative AI applications using Node.js, Next.js, databases, and cloud technologies, with 10+ real-world projects delivered including AI-powered tools and business applications.