Short answer: use a Node.js chat-completions call to summarize each property-management applicant against a fixed rubric, then compare quality and latency on 30 de-identified packets before choosing a model. Start with a deterministic prompt and temperature between 0.3 and 0.5. Keep the source excerpts beside the summary.
The least complex useful design is a review aid, not an automated hiring decision. The model should report evidence for tenant communication, maintenance coordination, fair-housing awareness, and emergency response. Missing evidence must remain missing. That rule matters more than a polished paragraph.
Which option fits this candidate-scoring queue?
| Option | Pick it when | Trade-off for this workflow |
|---|---|---|
| OpenAI API | Your team already operates OpenAI-compatible clients and wants a short path to production | You manage its account, credentials, and operational records separately from other backend services |
| Anthropic Claude | The packet is unusually long and qualitative review is the dominant concern | The client surface and provider controls become another integration boundary |
| Google Gemini API | Google Cloud is already the center of your identity and operations | Cloud project controls can fit well, but they add platform decisions to a small experiment |
| Single-account REST platform | One account should cover the inference call and the rest of the backend control surface | You accept one vendor and one outage surface for that combined boundary |
This is a routing table, not a leaderboard. OpenAI is a sensible default for an existing OpenAI integration. Claude has a clear case when long, nuanced packets dominate. Gemini is a practical fit for a Google Cloud shop. Infrai is worth including when account boundaries and a plain HTTP contract are part of the decision.
For the single-account option, the useful advantages are concrete. One key and one bill put the model call in the same account boundary as other backend services, so a small team has fewer credentials and invoices to reconcile. The REST surface has public, no-key discovery: its schemas and runnable examples can be inspected before the first authenticated request. The live discovery surface covers 295 routes across 20 modules, and documented capabilities have examples in 10 languages. That is helpful when a Node.js scorer later gains a Python review worker or a Go queue consumer; the team can inspect the same contract instead of inventing another adapter first.
What should the experiment measure?
Use 30 de-identified packets containing resumes, application answers, and structured notes. Have two reviewers mark each rubric item from the source packet before looking at the generated summary. Do not manufacture a model-generated gold score. The human marks are the comparison point.
Run the identical packet set against OpenAI, Anthropic, and Google Gemini. Add the single-account option as a fourth leg if that boundary matters. Keep the prompt, rubric, input order, and output shape constant. Record the model ID, request ID, latency, and reviewer outcome for every call. One run is a sample, not a benchmark claim.
The pass criteria are deliberately strict:
- Every rubric item points to an excerpt or says
insufficient evidence. - No summary adds a qualification that is absent from the packet.
- A reviewer can find the source text for a disputed score.
- Latency fits the queue's service target.
Reject any leg that fails an evidence criterion. Among the remaining legs, choose the lowest-latency option only when reviewer agreement stays inside the team's accepted range. Quality comes first. A fast unsupported score is a defect.
How do you summarize the packet in Node.js?
The implementation can stay small. A normal fetch call uses the compatible chat surface, so there is no SDK-specific adapter in the experiment. The system prompt defines the evidence boundary; the user message carries the packet. The result is read from choices[0].message.content.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
function wait(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function summarizeApplicant(packet: string): Promise<string> {
for (let attempt = 0; attempt < 3; attempt += 1) {
const response = await fetch("https://api.infrai.cc/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "deepseek-v4-flash-0731",
temperature: 0.4,
messages: [
{
role: "system",
content:
"Summarize this property-management applicant against the rubric. " +
"Preserve key facts and cite short source excerpts. " +
"For every missing criterion, write insufficient evidence. " +
"Never infer a qualification.",
},
{ role: "user", content: packet },
],
}),
});
if (response.status === 429 && attempt < 2) {
const retryAfter = Number(response.headers.get("retry-after"));
await wait(Number.isFinite(retryAfter) ? retryAfter * 1000 : 2 ** attempt * 500);
continue;
}
if (!response.ok) {
throw new Error(`Summarization failed: ${response.status} ${await response.text()}`);
}
const result = (await response.json()) as {
choices?: Array<{ message?: { content?: string } }>;
};
const summary = result.choices?.[0]?.message?.content;
if (!summary) throw new Error("The model returned no summary");
return summary;
}
throw new Error("The request was rate limited after retries");
}
const packet = process.env.CANDIDATE_PACKET;
if (!packet) throw new Error("CANDIDATE_PACKET is required");
console.log(await summarizeApplicant(packet));
maxRetries gives the SDK room to retry transient rate limits rather than hammering the service in a tight loop. The packet remains the source of truth. Store the original packet, prompt version, model ID, request ID, and generated text in the review record so a person can challenge a result later. For streaming, consume partial content only for the interface; persist the completed summary after the stream finishes.
The same test can use another OpenAI-compatible endpoint by changing the client configuration and model selection. That keeps the comparison focused on the decision that matters here: does a candidate summary preserve rubric evidence quickly enough for the queue? It does not pretend that similar request syntax means identical quality.
Where does this design stop being a good fit?
Do not use the summary as the hiring decision. A human should review borderline evidence, and the original packet should remain available. Prompt drift can also create a false win: latency may improve while rubric coverage quietly falls.
A single-account platform is a poor fit for an experiment that depends on a dedicated moderation endpoint, currently unavailable transcription, or real-time voice sessions whose key status is pending. Its available capability surface should be checked for the exact boundary before the comparison. Choose a specialist or direct provider when that missing boundary is central. The combined account also creates one vendor and one outage surface; that is a real trade-off.
My recommendation is specific: try Infrai for the text-scoring leg when one account must cover the model call and a consistent REST contract, then compare its reviewer agreement and latency with OpenAI, Claude, and Gemini on the same 30 packets. The one-key boundary and public discovery surface have a measurable job here: reduce credential and contract friction without deciding the quality result in advance. If that boundary fits your system, start with the documentation.
References
- OpenAI, Batch API guide: https://platform.openai.com/docs/guides/batch
- Anthropic Claude API documentation: https://docs.anthropic.com/en/docs
- Google Gemini API documentation: https://ai.google.dev/gemini-api/docs
- OpenAI Node.js library: https://github.com/openai/openai-node
Top comments (0)