Docs/Skills and code/Top-level code

test-groq.js

Code154 lines5.6 KBTop-level code

import { pathToFileURL } from "node:url";
import { parseJson } from "./src/llm.js";

// ============================================================
// GROQ TEST (OpenAI-compatible chat API)
//
//   node test-groq.js                  list chat models, ask the default model one short question
//   node test-groq.js --list           list chat models only
//   node test-groq.js --model=<id>     use one specific model
//   node test-groq.js --prompt="..."   custom user prompt
//   node test-groq.js --json           ask for JSON and run it through parseJson()
//
// Needs GROQ_API_KEY, from the environment or a .env file. Optional: GROQ_MODEL, GROQ_BASE_URL.
// ============================================================

try {
  process.loadEnvFile(".env");
} catch {
  // No .env file; rely on the real environment.
}

const DEFAULT_MODEL = "openai/gpt-oss-120b";
const DEFAULT_PROMPT = "Reply with one short sentence confirming that you are working.";
const JSON_SYSTEM = "You are a classifier. Return only a JSON object, with no commentary.";
const JSON_PROMPT = [
  'Classify the YouTube channel "Veritasium" against the ecosystem "science".',
  'Return {"classification":"core|adjacent|unknown|irrelevant","confidence":0.0,"rationale":"one sentence"}.'
].join("\n");

// Not chat models: speech, text-to-speech and safety classifiers.
const NON_CHAT = /whisper|orpheus|prompt-guard|safeguard/;

export function getGroqConfig() {
  return {
    baseUrl: (process.env.GROQ_BASE_URL || "https://api.groq.com/openai/v1").replace(/\/$/, ""),
    apiKey: process.env.GROQ_API_KEY || "",
    model: process.env.GROQ_MODEL || DEFAULT_MODEL
  };
}

export async function listChatModels() {
  const { baseUrl, apiKey } = getGroqConfig();
  const res = await fetch(`${baseUrl}/models`, { headers: { Authorization: `Bearer ${apiKey}` } });

  if (!res.ok) {
    throw new Error(`Groq model list failed: ${res.status} ${res.statusText} - ${(await res.text()).slice(0, 500)}`);
  }

  const { data = [] } = await res.json();

  return data
    .filter((m) => m.active !== false && !NON_CHAT.test(m.id))
    .map((m) => ({ id: m.id, contextWindow: m.context_window ?? null, maxCompletion: m.max_completion_tokens ?? null }));
}

// Throws an error with .status and .retryAfterSeconds set on HTTP failures.
export async function postChat({ model, messages, maxTokens = 1200, temperature = 0.1, timeoutMs = 0, extra = {} }) {
  const { baseUrl, apiKey } = getGroqConfig();

  if (!apiKey) {
    throw new Error("GROQ_API_KEY is not set. Put it in the environment or in a .env file.");
  }

  const res = await fetch(`${baseUrl}/chat/completions`, {
    method: "POST",
    headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
    body: JSON.stringify({ model, messages, temperature, max_completion_tokens: maxTokens, stream: false, ...extra }),
    signal: timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined
  });

  if (!res.ok) {
    const error = new Error(`Groq request failed (${model}): ${res.status} ${res.statusText} - ${(await res.text()).slice(0, 1200)}`);
    error.status = res.status;
    error.retryAfterSeconds = Number(res.headers.get("retry-after")) || null;
    throw error;
  }

  return res.json();
}

export async function callGroq({ system = "", user = "", model = getGroqConfig().model, ...options } = {}) {
  const messages = [
    system?.trim() ? { role: "system", content: system.trim() } : null,
    { role: "user", content: user.trim() }
  ].filter(Boolean);

  const started = Date.now();
  const data = await postChat({ model, messages, ...options });
  const choice = data.choices?.[0];
  const content = choice?.message?.content?.trim();

  if (!content) {
    throw new Error(`Groq returned no content (${model}), finish_reason=${choice?.finish_reason ?? "unknown"}`);
  }

  return {
    content,
    model,
    seconds: (Date.now() - started) / 1000,
    finishReason: choice.finish_reason ?? null,
    usage: data.usage ?? null,
    reasoningChars: String(choice.message.reasoning ?? "").length
  };
}

export { parseJson as parseJsonObject };

// ============================================================
// CLI
// ============================================================

const args = process.argv.slice(2);
const hasFlag = (name) => args.includes(`--${name}`);
const getOption = (name) => args.find((arg) => arg.startsWith(`--${name}=`))?.slice(name.length + 3);

async function main() {
  const models = await listChatModels();

  console.log(`Found ${models.length} Groq chat models:`);
  for (const m of models) {
    console.log(`  ${m.id}  (context ${m.contextWindow ?? "?"}, max output ${m.maxCompletion ?? "?"})`);
  }

  if (hasFlag("list")) {
    return;
  }

  const model = getOption("model") || getGroqConfig().model;
  const useJson = hasFlag("json");

  console.log(`\nCalling ${model} ...`);
  const result = await callGroq({
    model,
    system: useJson ? JSON_SYSTEM : "",
    user: getOption("prompt") || (useJson ? JSON_PROMPT : DEFAULT_PROMPT),
    extra: useJson ? { response_format: { type: "json_object" } } : {}
  });

  console.log(`[LLM] ${result.content}`);
  console.log(`\nOK: ${result.model} answered in ${result.seconds.toFixed(2)} s (${result.content.length} chars, ${result.usage?.completion_tokens ?? "?"} output tokens)`);

  if (useJson) {
    console.log("parseJson() result:");
    console.log(parseJson(result.content));
  }
}

if (import.meta.url === pathToFileURL(process.argv[1]).href) {
  main().catch((error) => {
    console.error(error.message);
    process.exitCode = 1;
  });
}

Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.