Node.js Candidate Scoring Chatbots: Deterministic JSON Beats Model Brand Shortcuts A developer outlined a Node.js pattern for candidate-scoring chatbots that enforces a strict three-field JSON contract—an integer score from 0 to 100 plus matched and missing criteria arrays—rather than relying on a particular LLM vendor. The approach validates every response with a sorted-key check, buffers and validates structured output after streaming ends, and treats bulk scoring as idempotent jobs tracked with input fingerprints and rubric versions. Logging guidance calls for correlation IDs, rubric versions, latency and validation status while keeping resume text and full prompts out of application logs. Short answer: choose the LLM API that gives a Node.js service a clear, testable contract for structured output. For an in-app chatbot that scores candidates against a job rubric, validate every response, log the decision path, retry only safe failures, and keep the rubric outside the model's memory. The API brand is secondary. A malformed score can move a candidate into the wrong review queue. A slow answer can make a recruiter submit the same request twice. | Criterion | Pick this when | Verify | |---|---|---| | Strict structured output | A score drives a workflow or database write | Required keys, bounds, and refusal behavior | | Streaming text | The user needs visible progress | Partial-message handling and final validation | | Batch processing | Scoring can wait | Job state, replay safety, and correlation | | Minimal request | The chatbot answers a low-risk question | Timeout, retry, and fallback behavior | Start with the decision contract, not the prompt. Require exactly three fields: score , matchedCriteria , and missingCriteria . Make score an integer from 0 through 100. Make the arrays contain strings. Reject extra keys unless the contract explicitly permits them. Here is a deliberately boring Node.js boundary. It assumes a generic OpenAI-compatible endpoint, but the rules do not depend on a vendor. type CandidateScore = { score: number; matchedCriteria: string ; missingCriteria: string ; }; function parseCandidateScore raw: unknown : CandidateScore | null { if raw === null || typeof raw == "object" || Array.isArray raw return null; const value = raw as Record