@molecule/api-ai-decisions-liquid-d1
Provider bond · ai-decisions · API (Node) · v1.1.1 · Apache-2.0
Liquid d1 decisions provider for molecule.dev — typed choice/score/yes-no answers with images from Liquid AI's d1 decision models, hosted or self-hosted via llama-server
npm install @molecule/api-ai-decisions-liquid-d1npm · Source on GitHub · Implements @molecule/api-ai-decisions
How it works
@molecule/api-ai-decisions-liquid-d1 is a provider bond on the API (Node) side: it implements the ai-decisions core interface (@molecule/api-ai-decisions) with a concrete vendor or library behind it.
Your code calls the core; you wire this provider once at startup. Swapping vendors later is one line in that wiring, not a rewrite.
# Self-host instead of paying for the API (no key needed):
llama-server -hf LiquidAI/d1-3B-GGUF:Q8_0 # serves /v1/systemone on :8080
# then set LIQUID_DECISIONS_URL=http://127.0.0.1:8080Works with: @molecule/api-ai-decisions, @molecule/api-secrets
Secrets: LIQUID_API_KEY (optional), LIQUID_DECISIONS_URL (optional)
Reference
Liquid d1 decisions provider for molecule.dev — typed choice/score/yes-no answers, with images, from Liquid AI's d1 decision models, hosted or self-hosted.
d1 answers choice, score and yes/no questions about text, JSON or images
with a probability distribution instead of generated text, in one forward
pass. This bond calls POST https://api.liquid.ai/decisions/v1/systemone
with your LIQUID_API_KEY — or, when LIQUID_DECISIONS_URL is set, the
identical /v1/systemone route of a llama-server running the open d1
weights, so the same bond covers hosted and self-hosted.
Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit
src/index.tsJSDoc, not this file.
Quick Start
# Self-host instead of paying for the API (no key needed):
llama-server -hf LiquidAI/d1-3B-GGUF:Q8_0 # serves /v1/systemone on :8080
# then set LIQUID_DECISIONS_URL=http://127.0.0.1:8080
import { setProvider, requireProvider } from '@molecule/api-ai-decisions'
import { provider } from '@molecule/api-ai-decisions-liquid-d1'
setProvider(provider) // reads LIQUID_API_KEY on each call
const { answers } = await requireProvider().decide({
state: 'I was charged twice this month, please refund one of them.',
questions: {
refund: { type: 'yesNo', instructions: 'The customer is asking for a refund.' },
team: {
type: 'choice',
instructions: 'Which team should handle this?',
criteria: { billing: 'charges, refunds, invoices', technical: 'app or site faults' },
},
urgency: {
type: 'score',
instructions: 'How urgent is this?',
criteria: ['Can wait', 'Today', 'Blocking the customer now'],
},
},
})
answers.refund.probability // 0.999
answers.team.choice // 'billing'
answers.urgency.score // 2.9995
Type
provider
Installation
npm install @molecule/api-ai-decisions-liquid-d1 @molecule/api-ai-decisions @molecule/api-secrets
API
Interfaces
LiquidD1Config
Configuration for the Liquid d1 decisions provider.
interface LiquidD1Config {
/**
* Liquid API key (console.liquid.ai → Dashboard > API Keys; keys start with
* `liquid_`). Defaults to the `LIQUID_API_KEY` env var. Required for the
* hosted API; not needed when `decisionsUrl` points at your own keyless
* `llama-server`.
*/
apiKey?: string
/**
* Extra request headers, resolved before each request (retries included)
* and merged over the defaults — for a host whose auth expires or is not a
* bearer token (a Cloud Run ID token, Modal proxy auth). Pair with
* `@molecule/api-model-hosting`: `headers: () => hosting.authHeaders(endpoint.id)`.
*/
headers?: () => Record<string, string> | Promise<Record<string, string>>
/**
* Hosted API base URL. Defaults to `LIQUID_BASE_URL`, then
* `https://api.liquid.ai`; the route `/decisions/v1/systemone` is appended.
* Point it at a gateway that relays the Liquid decisions API.
*/
baseUrl?: string
/**
* Base URL of a self-hosted server that serves the systemone route at
* `/v1/systemone` — a `llama-server` running the open d1 weights
* (`llama-server -hf LiquidAI/d1-3B-GGUF:Q8_0` → `http://127.0.0.1:8080`).
* Defaults to the `LIQUID_DECISIONS_URL` env var. When set, no API key is
* required (set `apiKey` only for an authenticating proxy in front of it).
*/
decisionsUrl?: string
/** Default model id: `'d1'` (text + images) or `'d1:free'` (text-only). Defaults to `'d1'`. */
model?: string
/**
* Per-request deadline in milliseconds. Defaults to 30000 (a d1 decision is
* one forward pass; this bounds a hung connection, which no retry can fix).
* The caller's `signal`, when one is passed, and this deadline BOTH apply —
* whichever fires first. Retries share the one budget, like the sibling
* `ai-decisions` bonds.
*/
timeoutMs?: number
}
WireQuestion
One question as sent on the wire.
interface WireQuestion {
type: 'choice' | 'score' | 'noul'
instructions: string
criteria?: Record<string, string> | string[]
}
WireAnswer
One answer as received on the wire (only the fields we read).
interface WireAnswer {
type?: string
choice?: string
score?: number
noul?: number
probabilities?: Record<string, number>
}
WireResponse
The response body (only the fields we read).
interface WireResponse {
model?: string
answers?: Record<string, WireAnswer>
usage?: { input_tokens?: number; output_tokens?: number }
}
PostOptions
Options for postSystemOne.
interface PostOptions {
/** Full endpoint URL. */
url: string
/** Bearer token, if any. */
apiKey?: string
/** Extra headers resolved before each attempt, retries included (merged over the defaults). */
headers?: () => Record<string, string> | Promise<Record<string, string>>
/** Request body. */
body: Record<string, unknown>
/** Abort signal. */
signal?: AbortSignal
/** Label for error messages (`'Liquid d1'`). */
label: string
/** Max retries on a retryable status (default 3). */
maxRetries?: number
}
Functions
createProvider(config)
Creates a Liquid d1 decisions provider.
function createProvider(config?: LiquidD1Config): AIDecisionsProvider
config— API key, base URL / self-host URL and default model.
Returns: An AIDecisionsProvider backed by Liquid's d1 decision models.
toWireQuestions(questions)
Converts the core's questions into wire questions (yesNo → noul).
function toWireQuestions(questions: Record<string, DecisionQuestion>): Record<string, WireQuestion>
questions— The questions keyed by id.
Returns: The wire questions object.
fromWireAnswer(id, question, wire, minConfidence)
Converts one wire answer into the core's answer for question.
function fromWireAnswer(
id: string,
question: DecisionQuestion,
wire: WireAnswer | undefined,
minConfidence?: number,
): DecisionAnswer
id— The question id (for error messages).question— The question that was asked.wire— The wire answer.minConfidence— Optional low-confidence threshold.
Returns: The typed answer.
fromWireResponse(questions, body, minConfidence)
Converts a whole wire response into the core's result.
function fromWireResponse(questions: Q, body: WireResponse, minConfidence?: number): DecideResult<Q>
questions— The questions that were asked.body— The parsed response body.minConfidence— Optional low-confidence threshold.
Returns: The typed result.
postSystemOne(opts)
POSTs a systemone request with retry on 429/5xx-busy, honouring
Retry-After; an aborting signal ends the backoff early with the
signal's reason. Throws an Error carrying status on a non-2xx response or
a 2xx response whose body is not JSON.
function postSystemOne(opts: PostOptions): Promise<WireResponse>
opts— Request options.
Returns: The parsed response body.
Constants
DEFAULT_LIQUID_D1_URL
Liquid AI's API host.
const DEFAULT_LIQUID_D1_URL: 'https://api.liquid.ai'
HOSTED_LIQUID_D1_PATH
The systemone route on the hosted Liquid API (nesting is Liquid's).
const HOSTED_LIQUID_D1_PATH: '/decisions/v1/systemone'
SELF_HOSTED_D1_PATH
The systemone route on a llama-server running the open d1 weights.
const SELF_HOSTED_D1_PATH: '/v1/systemone'
DEFAULT_LIQUID_D1_MODEL
Liquid's flagship model id.
const DEFAULT_LIQUID_D1_MODEL: 'd1'
LIQUID_D1_LIMITS
The hosted API's request limits (docs.liquid.ai/lfm/models/d1).
const LIQUID_D1_LIMITS: {
readonly maxImages: 8
readonly minScoreLevels: 2
readonly maxScoreLevels: 10
}
LIQUID_D1_IMAGE_TYPES
Image media types the hosted API accepts (llama-server decodes the same four).
const LIQUID_D1_IMAGE_TYPES: readonly string[]
provider
The default provider, configured from env vars on each call (wire with
setProvider). Safe to import before secrets are loaded.
const provider: AIDecisionsProvider
aiDecisionsLiquidD1SecretDefinitions
Secret definitions required by the Liquid d1 decisions bond.
const aiDecisionsLiquidD1SecretDefinitions: SecretDefinition[]
Core Interface
Implements @molecule/api-ai-decisions interface.
Bond Wiring
Setup function to register this provider with the core interface:
import { setProvider } from '@molecule/api-ai-decisions'
import { provider } from '@molecule/api-ai-decisions-liquid-d1'
export function setupAiDecisionsLiquidD1(): void {
setProvider(provider)
}
Injection Notes
Requirements
Peer dependencies:
@molecule/api-ai-decisions^1.0.0@molecule/api-secrets^1.0.1
Environment Variables
LIQUID_API_KEY(optional) — Liquid AI API key- Setup: Create a key at console.liquid.ai (Dashboard > API Keys; keys start with liquid_); it is sent as a bearer token to the Liquid d1 decisions API. Required for the hosted API; not needed when LIQUID_DECISIONS_URL points at your own llama-server.
- Get it here: https://console.liquid.ai
- Example:
liquid_...
LIQUID_DECISIONS_URL(optional) — Self-hosted d1 server URL- Setup: Base URL of a llama-server running the open d1 weights (llama-server -hf LiquidAI/d1-3B-GGUF:Q8_0 binds http://127.0.0.1:8080); the bond calls its /v1/systemone route with no API key. Leave unset to use the hosted Liquid API.
- Get it here: https://docs.liquid.ai/lfm/models/d1-3b
- Example:
http://127.0.0.1:8080
Runtime Dependencies
-
@molecule/api-ai-decisions -
@molecule/api-secrets -
Config:
LIQUID_API_KEY(required for the hosted API — sent asAuthorization: Bearer …, created at console.liquid.ai; keys start withliquid_),LIQUID_DECISIONS_URL(self-hostedllama-serverbase URL, no key needed),LIQUID_BASE_URL(gateway in front of the hosted API), andcreateProvider({ model })(default'd1', or passmodelper call). -
d1:freeis text-only. Passingimageswith modeld1:freethrows before any request (the API's answer is "The model 'd1:free' does not accept images."). Paidd1reads images. -
Image rules (count and format are checked here before sending): up to 8 images per request of JPEG / PNG / WebP / GIF, base64 only — remote URLs are rejected. The API itself also enforces: whole request body under 4.5 MB, ≤10,000 patches of 32×32 px across all images (1.5 tokens per patch), and the longer side at most 100× the shorter.
-
Billing quirk: every question is charged for its text AND all images again (a 1024×1024 image is 1,536 tokens per question). Batch questions over one state instead of one call per question. Billed on input tokens only ($0.04/M at launch);
usage.outputTokensis always 0. -
Score questions take 2–10 levels, lowest first (checked here before sending).
-
confidencein the answers is the probability of the reported answer, NOT d1's ownconfidencefield (see the core's remarks). -
License — the open weights are NOT Apache-2.0.
d1-3Bandd1-omni-600Mship the LFM Open License v1.0: commercial use by a legal entity over $10,000,000 annual revenue is NOT licensed — buy a commercial license from Liquid AI or pick another provider (the hosted API is unaffected; this bond's code is Apache-2.0). -
d1-omni-600M is an early research release (Decision Index 15.95 vs d1-3B's 48.57) and its audio input is not served over HTTP — this bond sends text and images only.
-
Your data leaves your servers when hosted (Liquid processes the
state). For data that must stay in your environment, runllama-server -hf LiquidAI/d1-3B-GGUF:Q8_0and setLIQUID_DECISIONS_URL— a llama-server has NO authentication, so keep it on a private network or behind an authenticating proxy (setLIQUID_API_KEY/apiKeyand it is sent as the bearer token). -
429 (rate limit) and 5xx-busy are retried up to 3 times with backoff — an aborting
signalcuts the backoff short; other errors throw immediately with the API's message and astatusproperty. -
Every request carries a deadline (30 s by default,
timeoutMsconfig) so a hung connection fails instead of leavingdecide()pending forever; the caller'ssignaland the deadline both apply, whichever fires first. -
Use the core's
setProvider, notbond('ai-decisions', …)directly.
E2E Tests
Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:
- Each flow that makes a decision (routing, triage, moderation, a guardrail) runs it from the real UI, and the answer DRIVES what happens next (the item lands in the chosen queue, the badge shows, the action is blocked) — not just printed.
- Both directions: a clearly-billing input routes to billing AND a clearly-technical one routes elsewhere. One label for every input is a broken integration.
- A low-confidence answer takes the app's fallback path (human review, "unsure" state) instead of being acted on.
- Provider errors (service down, bad key) show a visible, recoverable state — never a blank screen or an unhandled rejection.
- The call runs server-side: no provider request or key in the browser's Network tab.