6ixPulse / server /nvidia-client.mjs
OwnPath's picture
Nemotron reasoning brain + 8-dim scoring + recommendation agent + updated README/diagram
00b6baf verified
Raw
History Blame Contribute Delete
4.59 kB
import { buildModelContext } from "./agent-core.mjs";
import { SYSTEM_PROMPT, parseModelJson } from "./model-prompt.mjs";
const DEFAULT_MODEL = "nvidia/Nemotron-3-Nano-Omni-30B-A3B-Reasoning-BF16";
const DEFAULT_BASE_URL = "https://integrate.api.nvidia.com/v1";
export async function runNvidiaAgent(localRun, env = process.env) {
if (env.AGENT_OFFLINE === "1") {
return {
status: "skipped",
reason: "AGENT_OFFLINE is enabled",
model: configuredNvidiaModel(env),
result: null,
};
}
const token = env.NVIDIA_API_KEY || env.NGC_API_KEY;
if (!token) {
return {
status: "skipped",
reason: "NVIDIA_API_KEY is not configured",
model: configuredNvidiaModel(env),
result: null,
};
}
const model = configuredNvidiaModel(env);
const context = buildModelContext(localRun);
// Matches the Nemotron Nano Omni Reasoning recipe (ChatNVIDIA): temperature 0.6, top_p 0.95,
// a reasoning_budget, and chat_template_kwargs.enable_thinking. The thinking is returned
// separately as message.reasoning_content; the answer (our JSON) is message.content.
const thinking = env.NVIDIA_ENABLE_THINKING !== "0";
const basePayload = {
model,
messages: [
{ role: "system", content: SYSTEM_PROMPT },
{ role: "user", content: JSON.stringify(context) },
],
temperature: Number(env.NVIDIA_TEMPERATURE ?? 0.6),
top_p: Number(env.NVIDIA_TOP_P ?? 0.95),
max_tokens: Number(env.NVIDIA_MAX_TOKENS ?? 8192),
stream: false,
chat_template_kwargs: { enable_thinking: thinking },
...(thinking ? { reasoning_budget: Number(env.NVIDIA_REASONING_BUDGET ?? 4096) } : {}),
};
const richPayload = {
...basePayload,
response_format: { type: "json_object" },
};
try {
const body = await postChat(richPayload, token, env);
return {
status: "done",
reason: null,
model,
result: parseModelJson(body, "NVIDIA NIM"),
};
} catch (error) {
if (isRetryableProviderError(error)) {
try {
const body = await postChat(basePayload, token, env);
return {
status: "done",
reason: "Retried without JSON response_format",
model,
result: parseModelJson(body, "NVIDIA NIM"),
};
} catch (retryError) {
return errorResult(retryError, model);
}
}
return errorResult(error, model);
}
}
export function configuredNvidiaModel(env = process.env) {
return env.NVIDIA_MODEL || DEFAULT_MODEL;
}
export function nvidiaChatCompletionsUrl(env = process.env) {
if (env.NVIDIA_CHAT_COMPLETIONS_URL) return env.NVIDIA_CHAT_COMPLETIONS_URL;
const baseUrl = env.NVIDIA_BASE_URL || DEFAULT_BASE_URL;
return `${baseUrl.replace(/\/$/, "")}/chat/completions`;
}
async function postChat(payload, token, env) {
const timeoutMs = Number(env.NVIDIA_TIMEOUT_MS ?? 60000);
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(nvidiaChatCompletionsUrl(env), {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
signal: controller.signal,
});
const text = await response.text();
if (!response.ok) {
throw new NvidiaRequestError(response.status, conciseError(text));
}
try {
return JSON.parse(text);
} catch {
throw new Error("NVIDIA NIM returned a non-JSON chat response");
}
} catch (error) {
if (error?.name === "AbortError") {
throw new Error(`NVIDIA NIM request timed out after ${timeoutMs}ms`);
}
throw error;
} finally {
clearTimeout(timeout);
}
}
function isRetryableProviderError(error) {
return error instanceof NvidiaRequestError && (error.status === 400 || error.status === 422);
}
function errorResult(error, model) {
return {
status: "error",
reason: error instanceof Error ? error.message : "Unknown NVIDIA NIM error",
model,
result: null,
};
}
function conciseError(text) {
if (!text) return "empty error body";
try {
const parsed = JSON.parse(text);
return parsed.error?.message || parsed.detail || parsed.message || JSON.stringify(parsed).slice(0, 500);
} catch {
return text.slice(0, 500);
}
}
class NvidiaRequestError extends Error {
constructor(status, message) {
super(`NVIDIA NIM request failed with HTTP ${status}: ${message}`);
this.name = "NvidiaRequestError";
this.status = status;
}
}