npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@painitehq/structured-llm

v0.3.2

Published

Force LLM output into structured, type-safe JSON. Stop your app from crashing on malformed AI responses.

Readme

@painitehq/structured-llm

Force LLM output into structured, type-safe JSON. Stop your app from crashing on malformed AI responses.

Install

npm install @painitehq/structured-llm
# or
bun add @painitehq/structured-llm

Quick Start

import { extract, defineSchema } from "@painitehq/structured-llm";

const schema = defineSchema("invoice", {
  invoiceNumber: { type: "string" },
  totalAmount: { type: "number" },
  vendor: { type: "string" },
  items: { type: "array", items: { type: "string" } },
});

const result = await extract(messyText, schema, {
  apiKey: "your-openrouter-api-key",
});

console.log(result.data);
// { invoiceNumber: "INV-2024-042", totalAmount: 500, vendor: "Acme Corp", items: [...] }

console.log(result.confidence); // 100
console.log(result.attempts);   // 1

What It Does

LLMs return unstructured text. Sometimes it's valid JSON. Sometimes it's wrapped in markdown. Sometimes it's completely broken. This SDK:

  1. Forces the model to output valid JSON via strict prompt engineering
  2. Repairs malformed JSON (trailing commas, missing brackets, broken quotes)
  3. Unwraps named wrappers like {"invoice": {...}}{...}
  4. Validates the output against your schema
  5. Coerces wrong types ("42"42, "true"true)
  6. Fills missing fields with sensible defaults
  7. Retries with escalating instructions if the model fails

Features

Forced Structured Output

The SDK doesn't ask the model to "give JSON". It forces it:

CRITICAL RULES:
- Output ONLY valid JSON. No text before or after.
- No markdown. No code blocks. No explanations.
- Every field MUST be present with the correct type.

Escalating Retries

If the model fails, the SDK retries with increasingly strict instructions:

  • Attempt 1: Clean forced prompt
  • Attempt 2: "Your response was invalid. Here's the error. Fix it."
  • Attempt 3: "FINAL ATTEMPT. THIS IS YOUR LAST CHANCE."

Post-Validation Repair

Even if the JSON parses, the SDK fixes type mismatches:

| Model returns | Schema expects | SDK does | |---------------|----------------|----------| | "42" | number | coerces to 42 | | "true" | boolean | coerces to true | | 42 | string | coerces to "42" | | missing field | any type | fills with default |

Confidence Scoring

Every extraction returns a confidence score (0-100):

const result = await extract(text, schema, { apiKey });

result.confidence; // 85
result.repairLog;  // [{ type: "type_coercion", detail: "Coerced \"price\" to number" }]
result.attempts;   // 2

Confidence deductions:

  • JSON not valid first try: -15
  • Each retry: -10
  • Type coercion per field: -5
  • Missing field filled: -8

Schema Definition

import { defineSchema } from "@painitehq/structured-llm";

const schema = defineSchema("person", {
  name: { type: "string", description: "Full name" },
  age: { type: "number" },
  isStudent: { type: "boolean" },
  hobbies: { type: "array", items: { type: "string" } },
  address: {
    type: "object",
    properties: {
      city: { type: "string" },
      zip: { type: "string" },
    },
  },
});

Supported types: string, number, boolean, array, object

API

extract<T>(input, schema, options)

Returns Promise<ExtractionResult<T>>:

interface ExtractionResult<T> {
  data: T;                    // typed structured data
  raw: string;                // raw LLM response
  model: string;              // model used
  confidence: number;         // 0-100 score
  repairLog: RepairAction[];  // what was fixed
  attempts: number;           // how many tries
  usage?: {
    promptTokens: number;
    completionTokens: number;
    totalTokens: number;
  };
}

Options

{
  apiKey?: string;      // OpenRouter API key (or set OPENROUTER_API_KEY env var)
  model?: string;       // model to use (default: "openrouter/free")
  temperature?: number; // 0-1 (default: 0)
  maxRetries?: number;  // max retry attempts (default: 3)
  timeout?: number;     // request timeout in ms (default: 60000)
}

Requirements

  • OpenRouter API key (get one at https://openrouter.ai)
  • Any runtime: Node.js, Bun, Deno, browsers

License

MIT