Docs/Skills and code/Top-level code
test-groq.js
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.