@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-d1

npm · 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:8080

Works 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.ts JSDoc, 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 as Authorization: Bearer …, created at console.liquid.ai; keys start with liquid_), LIQUID_DECISIONS_URL (self-hosted llama-server base URL, no key needed), LIQUID_BASE_URL (gateway in front of the hosted API), and createProvider({ model }) (default 'd1', or pass model per call).

  • d1:free is text-only. Passing images with model d1:free throws before any request (the API's answer is "The model 'd1:free' does not accept images."). Paid d1 reads 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.outputTokens is always 0.

  • Score questions take 2–10 levels, lowest first (checked here before sending).

  • confidence in the answers is the probability of the reported answer, NOT d1's own confidence field (see the core's remarks).

  • License — the open weights are NOT Apache-2.0. d1-3B and d1-omni-600M ship 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, run llama-server -hf LiquidAI/d1-3B-GGUF:Q8_0 and set LIQUID_DECISIONS_URL — a llama-server has NO authentication, so keep it on a private network or behind an authenticating proxy (set LIQUID_API_KEY/apiKey and it is sent as the bearer token).

  • 429 (rate limit) and 5xx-busy are retried up to 3 times with backoff — an aborting signal cuts the backoff short; other errors throw immediately with the API's message and a status property.

  • Every request carries a deadline (30 s by default, timeoutMs config) so a hung connection fails instead of leaving decide() pending forever; the caller's signal and the deadline both apply, whichever fires first.

  • Use the core's setProvider, not bond('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.