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

153 lines (152 loc) • 5.81 kB
import { LayaModels } from "../constants/enums.js"; import { logger } from "../utils/logger.js"; import { redactUrlForError } from "../utils/logSanitize.js"; import { getProviderModel } from "../utils/providerConfig.js"; import { describeValidationErrors, isRecord, redactCredentials, SystemOneDecisionProvider, } from "./systemOneDecision.js"; /** * Normalise the two envelopes a Laya failure can arrive in. * * LiteLLM's own, `{"error":{"message","type","code"}}`, for anything the proxy * rejects before Laya sees it (a bad key, a rate limit). FastAPI's, from * Laya's server: `{"detail":"..."}` for its own checks (question types, the * question cap) and `{"detail":[{loc,msg,input}]}` for request validation. */ function parseLayaError(status, body, requestId) { const retryable = status === 429 || status >= 500; const kind = status === 401 || status === 403 ? "authentication" : status === 413 ? "max_tokens_exceeded" : status === 429 ? "rate_limit" : status === 503 ? "overloaded" : status >= 500 ? "server" : "invalid_request"; const proxyError = isRecord(body) && isRecord(body.error) ? body.error : undefined; if (proxyError && typeof proxyError.message === "string" && proxyError.message.length > 0) { return { kind, message: proxyError.message, status, requestId, retryable, }; } const detail = isRecord(body) ? body.detail : undefined; if (Array.isArray(detail)) { return { kind: "invalid_request", message: `Request failed validation — ${describeValidationErrors(detail)}`, status, requestId, retryable: false, }; } if (typeof detail === "string" && detail.length > 0) { return { kind, message: detail, status, requestId, retryable }; } return { kind, message: `Laya request failed with HTTP ${status}`, status, requestId, retryable, }; } /** * Laya Provider — the `decide` inference type only. * * Laya is Convai Innovations' open-weights "System One" decision model: the * same typed `noul` / `choice` / `score` questions as TypeSafe's Jev, answered * by an encoder in one forward pass, on the same wire. The request loop, * retries, auth circuit breaker and answer parsing are shared with every * decision provider in {@link SystemOneDecisionProvider}. * * Its encoders read far less than Jev: 1,024 tokens on the `typed-decisions` * and `multilingual` checkpoints, 512 on `english`. The descriptor's * `decisionLimits` refuses what does not fit rather than letting the server * cut the state off silently. * * @see https://github.com/NandhaKishorM/laya */ export class LayaProvider extends SystemOneDecisionProvider { apiKey; baseURL; constructor(modelName, sdk, _region, credentials) { super(modelName, "laya", sdk); this.apiKey = credentials?.apiKey?.trim() || (process.env.LAYA_API_KEY?.trim() ?? ""); // No built-in endpoint: Laya is self-hosted or reached through a proxy // (a Laya server, or a LiteLLM pass-through route to one), so the base URL // comes only from config. `||`, not `??`, so a blank one counts as unset. this.baseURL = (credentials?.baseURL?.trim() || process.env.LAYA_BASE_URL?.trim() || "").replace(/\/+$/, ""); // A self-hosted LAYA_BASE_URL can carry `user:pass@` or a `?token=`. logger.debug("Laya Provider initialized (decide only)", { modelName: this.modelName, baseURL: this.baseURL ? redactUrlForError(this.baseURL) : "(not set)", }); } getDefaultModel() { return getProviderModel("LAYA_MODEL", LayaModels.TYPED_DECISIONS); } vendorLabel() { return "Laya"; } vendorDisplayName() { return "Laya"; } decisionApiKey() { return this.apiKey; } missingKeyMessage() { return "Laya requires an API key. Set LAYA_API_KEY or pass credentials.laya.apiKey."; } missingConfigMessage() { return this.baseURL ? undefined : "Laya requires a base URL. Set LAYA_BASE_URL or pass credentials.laya.baseURL."; } decisionEndpoint() { return `${this.baseURL}/predict`; } decisionHeaders() { return { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", }; } /** `auto` means "let Laya's router choose", which it does when no model is named. */ buildDecisionBody(state, questions, model) { return model === LayaModels.AUTO ? { state, questions } : { model, state, questions }; } parseDecisionError(status, payload, requestId) { const parsed = parseLayaError(status, payload, requestId); const message = redactCredentials(parsed.message, this.apiKey); return { ...parsed, message: message || `Laya request failed with HTTP ${status}`, }; } readRequestId(headers) { return headers.get("x-litellm-call-id") ?? undefined; } /** * The checkpoint that actually answered is `routing.model` — the one Laya's * router picked when the request said `auto`. */ resolveResponseModel(payload, requestedModel) { const routing = payload.routing; if (isRecord(routing) && typeof routing.model === "string") { return routing.model; } return super.resolveResponseModel(payload, requestedModel); } }