@molecule/api-ai-decisions-intern-decision

Provider bond · ai-decisions · API (Node) · v1.1.0 · Apache-2.0

Intern-Decision decisions provider for molecule.dev — typed choice/score/yes-no answers about text and images from the open-weights Intern-Decision models on your own server

npm install @molecule/api-ai-decisions-intern-decision

npm · Source on GitHub · Implements @molecule/api-ai-decisions

How it works

@molecule/api-ai-decisions-intern-decision 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.

# Run the service on a GPU host (it binds 127.0.0.1:7860 and has NO auth)
git clone https://github.com/internlm/Intern-Decision 
python -m venv .venv 
pip install -r requirements-inference.txt
MODEL_CHECKPOINT=/models/Intern-Decision-2B bash scripts/demo.sh

Works with: @molecule/api-ai-decisions, @molecule/api-secrets

Secrets: INTERN_DECISION_URL (optional), INTERN_DECISION_API_KEY (optional)

Reference

Intern-Decision decisions provider for molecule.dev — typed decisions about text AND images from the open-weights Intern-Decision models on a server you run.

Intern-Decision (github.com/internlm/Intern-Decision, from InternLM) is a family of Qwen3.5 fine-tunes — 0.8B, 2B and 4B — that answer choice, score and yes/no questions with a probability per option, and can read up to 8 images alongside the text (a screenshot, a photo, a scanned form). Its maker reports 90.0% average accuracy for the 4B (44 ms/request) and 84.7% for the 2B (33 ms on an RTX 4090). This bond speaks the service's POST /v1/decisions route; the question and answer shapes match the Jev / Laya / Jeff bonds, so swapping is a one-line change.

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

# Run the service on a GPU host (it binds 127.0.0.1:7860 and has NO auth)
git clone https://github.com/internlm/Intern-Decision 
python -m venv .venv 
pip install -r requirements-inference.txt
MODEL_CHECKPOINT=/models/Intern-Decision-2B bash scripts/demo.sh
import { readFileSync } from 'node:fs'

import { setProvider, requireProvider } from '@molecule/api-ai-decisions'
import { provider } from '@molecule/api-ai-decisions-intern-decision'

setProvider(provider) // reads INTERN_DECISION_URL (+ INTERN_DECISION_API_KEY for a proxy) on first use

const { answers } = await requireProvider().decide({
  state: 'A screenshot of the app preview after deploy.',
  images: [{ mimeType: 'image/png', data: readFileSync('preview.png').toString('base64') }],
  questions: {
    blank: { type: 'yesNo', instructions: 'The page is blank or shows only an error.' },
    layout: {
      type: 'choice',
      instructions: 'What does the page show?',
      criteria: { landing: 'a marketing page', app: 'an app screen', error: 'an error page' },
    },
  },
})
answers.blank.probability // 0.04
answers.layout.choice // 'landing'

Type

provider

Installation

npm install @molecule/api-ai-decisions-intern-decision @molecule/api-ai-decisions @molecule/api-secrets

API

Interfaces

InternDecisionConfig

Configuration for the Intern-Decision decisions provider.

interface InternDecisionConfig {
  /**
   * Base URL of the Intern-Decision service (or of an authenticating proxy in
   * front of it). Defaults to `INTERN_DECISION_URL`, then `http://127.0.0.1:7860`.
   */
  baseUrl?: string
  /**
   * Bearer token for a proxy in front of the service. The service itself has
   * no authentication. Defaults to the `INTERN_DECISION_API_KEY` env var.
   */
  apiKey?: string
  /**
   * Extra request headers, resolved before each call 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>>
}

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 postDecisions.

interface PostOptions {
  /** Full endpoint URL. */
  url: string
  /** Bearer token, if any. */
  apiKey?: string
  /** Extra headers resolved before the request (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 (`'Intern-Decision'`). */
  label: string
  /** Max retries on a retryable status (default 3). */
  maxRetries?: number
}

Functions

createProvider(config)

Creates an Intern-Decision decisions provider.

function createProvider(config?: InternDecisionConfig): AIDecisionsProvider
  • config — Base URL and optional proxy API key.

Returns: An AIDecisionsProvider backed by an Intern-Decision service.

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.

postDecisions(opts)

POSTs a decision request with retry on 429/5xx-busy, honouring Retry-After. Throws an Error carrying status on a non-2xx response.

function postDecisions(opts: PostOptions): Promise<WireResponse>
  • opts — Request options.

Returns: The parsed response body.

Constants

DEFAULT_INTERN_DECISION_URL

The service's default bind (scripts/demo.sh: DEMO_HOST 127.0.0.1, DEMO_PORT 7860).

const DEFAULT_INTERN_DECISION_URL: 'http://127.0.0.1:7860'

INTERN_DECISION_LIMITS

The service's request limits (src/inference/pipeline.py, src/service/uploads.py).

const INTERN_DECISION_LIMITS: {
  readonly maxQuestions: 16
  readonly maxOptions: 62
  readonly maxImages: 8
  readonly maxImageBytes: number
  readonly maxTotalImageBytes: number
}

INTERN_DECISION_IMAGE_TYPES

Image media types the service decodes (GIFs must be still).

const INTERN_DECISION_IMAGE_TYPES: readonly string[]

provider

The provider implementation — lazy, so env vars are read on first use.

const provider: AIDecisionsProvider

aiDecisionsInternDecisionSecretDefinitions

Secret definitions used by the Intern-Decision decisions bond.

const aiDecisionsInternDecisionSecretDefinitions: 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-intern-decision'

export function setupAiDecisionsInternDecision(): void {
  setProvider(provider)
}

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-ai-decisions ^1.1.0
  • @molecule/api-secrets ^1.0.1

Environment Variables

  • INTERN_DECISION_URL (optional) — Intern-Decision server URL
    • Setup: The base URL of your Intern-Decision service (clone github.com/internlm/Intern-Decision, pip install -r requirements-inference.txt, set MODEL_CHECKPOINT, then bash scripts/demo.sh), or of an authenticating proxy in front of it. Defaults to http://127.0.0.1:7860.
    • Get it here: https://github.com/internlm/Intern-Decision#readme
    • Example: http://127.0.0.1:7860
  • INTERN_DECISION_API_KEY (optional) — Intern-Decision proxy API key

Runtime Dependencies

  • @molecule/api-ai-decisions

  • @molecule/api-secrets

  • The service has NO authentication and binds 127.0.0.1 by default. Never publish its port: reach it over a private network, or put an authenticating reverse proxy in front and set INTERN_DECISION_API_KEY (sent as Authorization: Bearer … — the service itself ignores it).

  • Default port is 7860, not 8000: INTERN_DECISION_URL defaults to http://127.0.0.1:7860.

  • One checkpoint per server; model is ignored. The service answers with whatever MODEL_CHECKPOINT it loaded (result.model is always 'intern-decision'). Run a second server for a second size.

  • Limits (checked here before sending, else a 422): 1–16 questions per request, 1–62 options per choice or levels per score, up to 8 images of PNG / JPEG / WebP / still GIF, each ≤12 MB decoded and ≤32 MB total (and ≤16 million pixels — checked by the server only). images[].data is raw base64 — no data: prefix.

  • Input is capped at 8,192 tokens by default (MAX_INPUT_LENGTH on the server). Put the decisive text first.

  • Requests are serialized on the server (one model lock); concurrent calls queue rather than fail. A loading server answers 503; this bond retries 429/502/503/504/529 up to 3 times. Errors carry the HTTP status.

  • Calibration: each checkpoint ships a fitted temperature (2B: 2.1), applied only when the server is configured with its calibration file — and the HF and XTuner backends give slightly different probabilities. Calibrate on labelled samples of your inputs and gate on minConfidence.

  • Not a chat model and does not reason. It picks among the options you describe; the service's optional "thinking" handoff to an external LLM is not used by this bond.

  • License: the code is Apache-2.0; the weights' Hugging Face cards say Apache-2.0 while the repo README says Qwen model terms apply — check LICENSE-QWEN before offering it to third parties.

  • Hosted on rented compute? Pass headers: () => hosting.authHeaders(endpoint.id) (@molecule/api-model-hosting) when the host's auth expires or is not a bearer token — e.g. a Cloud Run ID token or Modal proxy auth. It runs before every request and is merged over the defaults.

  • 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.