UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

124 lines (123 loc) • 5.28 kB
import { logger } from "../utils/logger.js"; import { redactUrlForError } from "../utils/logSanitize.js"; import { getProviderModel } from "../utils/providerConfig.js"; import { catalogCredentialsKey, catalogEnvVar } from "./catalog/loader.js"; import { baseURLShapeProblem, normalizeBaseURL } from "./decide/baseUrl.js"; import { getDecisionDialect } from "./decide/dialects/index.js"; import { classifyDecisionError } from "./decide/errors.js"; import { isRecord } from "./decide/guards.js"; import { readPath } from "./decide/paths.js"; import { fillEndpointTemplate } from "./decide/template.js"; import { SystemOneDecisionProvider } from "./systemOneDecision.js"; const readText = (value) => typeof value === "string" ? value.trim() : ""; /** A `decide` provider described entirely by its catalog entry. */ export class ConfiguredDecisionProvider extends SystemOneDecisionProvider { entry; apiKey; baseURL; endpoint; constructor(entry, modelName, sdk, credentials) { // BaseProvider's constructor calls getDefaultModel() for a falsy name, // before this.entry is assigned, so the name is settled here. super(modelName || getProviderModel(catalogEnvVar(entry, "model"), entry.models.default), entry.id, sdk); this.entry = entry; this.apiKey = readText(credentials?.apiKey) || readText(process.env[catalogEnvVar(entry, "apiKey")]); // `||`, not `??`, so a blank value counts as unset. this.baseURL = normalizeBaseURL(readText(credentials?.baseURL) || readText(process.env[catalogEnvVar(entry, "baseURL")]) || readText(entry.wire.baseURL), entry.decide.baseURLNormalize ?? []); this.endpoint = fillEndpointTemplate(entry.decide.endpoint, { baseURL: this.baseURL, }); // A self-hosted base URL can carry `user:pass@` or a `?token=`. logger.debug(`${entry.displayName} Provider initialized (decide only)`, { modelName: this.modelName, baseURL: this.baseURL ? redactUrlForError(this.baseURL) : "(not set)", }); } getDefaultModel() { return getProviderModel(catalogEnvVar(this.entry, "model"), this.entry.models.default); } vendorLabel() { return this.entry.displayName; } vendorDisplayName() { return this.entry.displayName; } decisionApiKey() { return this.apiKey; } missingKeyMessage() { return `${this.entry.displayName} requires an API key. Set ${catalogEnvVar(this.entry, "apiKey")} or pass credentials.${catalogCredentialsKey(this.entry)}.apiKey.`; } missingConfigMessage() { const { baseURLRequired, baseURLShape } = this.entry.decide; if (!this.baseURL) { return baseURLRequired ? `${this.entry.displayName} requires a base URL. Set ${catalogEnvVar(this.entry, "baseURL")} or pass credentials.${catalogCredentialsKey(this.entry)}.baseURL.` : undefined; } return baseURLShape ? baseURLShapeProblem(baseURLShape, this.baseURL, this.entry.displayName, `Set ${catalogEnvVar(this.entry, "baseURL")} or pass credentials.${catalogCredentialsKey(this.entry)}.baseURL to the origin, and give the key through ${catalogEnvVar(this.entry, "apiKey")} or credentials.${catalogCredentialsKey(this.entry)}.apiKey.`) : undefined; } decisionEndpoint() { return this.endpoint; } decisionHeaders() { return { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", }; } buildDecisionBody(state, questions, model, media) { const { placement, alias } = this.entry.decide.model; return this.dialect().buildBody({ state, questions, model: placement === "body-unless-alias" && model === alias ? undefined : model, media, mediaKeys: this.entry.decide.media, }); } parseDecisionError(status, payload, requestId) { return classifyDecisionError(this.entry.decide.errors, { status, payload, requestId, label: this.entry.displayName, apiKey: this.apiKey, }); } readRequestId(headers) { for (const name of this.entry.decide.requestIdHeaders ?? []) { const value = headers.get(name); if (value !== null) { return value; } } return undefined; } resolveResponseModel(payload, requestedModel) { // Defensive: the base class passes a record today. A direct call with // null or undefined must not throw here, where the base lookup would. if (!isRecord(payload)) { return requestedModel; } for (const path of this.entry.decide.response?.modelPaths ?? []) { const value = readPath(payload, path); if (typeof value === "string") { return value; } } return super.resolveResponseModel(payload, requestedModel); } dialect() { return getDecisionDialect(this.entry.decide.dialect); } }