Instructor JS

repository·main·Indexed 21 days ago

https://github.com/567-labs/instructor-js

A TypeScript library for structured data extraction from LLMs. It leverages Zod for schema validation and OpenAI's function calling/tooling APIs to ensure type-safe, reliable outputs. Supports partial streaming completions, multiple LLM providers via llm-polyglot or OpenAI-compatible proxies (e.g., Anyscale, Together), and various output modes including TOOLS and JSON_SCHEMA.

Tokens
24.2K
Snippets
65
Records
84
Agent score
72%

What's inside @instructor-ai/instructor

  1. Enable automatic retries for self-correction

    main

    To enable the self-correction loop, you must set the max_retries parameter in the instructor.chat.completions.create method.

    • If max_retries: 0: The request will fail immediately if validation (including LLMValidator) fails, throwing a ZodError.
    • If max_retries > 0: instructor will catch the validation error, include the error message in a new prompt to the LLM, and attempt to generate a corrected response up to the specified number of times.
  2. Core dependencies: Island AI toolkit

    main

    Instructor is built upon the following Island AI packages:

    • zod-stream: A client module that interfaces with LLM streams, using Schema-Stream to parse raw responses (function, tools, JSON, etc.) into structured data.
    • schema-stream: A JSON streaming parser that incrementally constructs and updates response models based on Zod schemas for real-time data processing.
    • llm-polyglot: A library providing a unified interface for multiple LLM providers (OpenAI, Anthropic, Azure, Cohere, etc.).
  3. How Instructor patching works

    main

    Instructor enhances standard LLM clients by adding three specific keywords to the chat.completions.create method to enable structured outputs and reliability. These keywords allow you to use the enhanced client while maintaining backwards compatibility with existing client interfaces.

    Key added keywords:

    • response_model: Defines the expected response type/schema for the completion.
    • max_retries: Specifies the number of retry attempts for failed validations of the structured output.
    • mode: Determines the underlying mechanism used to achieve structured output (e.g., Tool Calling, JSON Mode).
  4. How Instructor patching works with Together AI

    main

    Instructor "patches" an OpenAI-compatible client to add advanced structured output capabilities. For Together AI, the supported modes are:

    • JSON_SCHEMA: Uses JSON schema to enforce structure.
    • TOOLS: Uses tool/function calling to enforce structure.

    When using these modes, Instructor enables:

    1. response_model: Allows passing a Zod schema in create calls to return parsed objects.
    2. max_retries: Automatically retries the call using a backoff strategy if the model's output fails validation against the schema.
  5. Use Zod schemas for structured data extraction

    main

    Instructor uses Zod schemas to define the structure of the data you want to extract from an LLM. The response_model provided to Instructor must be a z.object. These schemas serve two purposes: they validate the LLM's output and they act as the primary mechanism for prompt engineering.

    import { z } from 'zod';
    
    const schema = z.object({
      name: z.string(),
      age: z.number(),
    });
  6. General Guidelines for Zod Schema Engineering

    main

    When designing Zod schemas for use with Instructor, follow these principles to ensure high-quality LLM outputs:

    • Modularity: Build self-contained schemas that can be reused in different parts of your data model.
    • Self-Description: Use .describe() on fields to provide explicit instructions to the LLM about what that field should contain.
    • Optionality: Use .optional() or z.union([schema, z.undefined()]) for fields that may not be present.
    • Standardization: Use z.enum for fixed sets of values. Including an 'OTHER' option helps handle ambiguity.
    • Dynamic Data: Use z.record(z.string()) to capture arbitrary key-value pairs that don't fit a strict schema.
    • Entity Relationships: Use explicit identifiers (like IDs) to define relationships between objects.
    • Contextual Logic: Include a 'chain of thought' field to allow the LLM to reason before providing a final answer.
  7. The mental model of instructor: Bridging LLMs and OOP

    main
    The instructor library acts as a bridge that converts text-based Large Language Model (LLM) interactions into structured, object-oriented formats. Instead of treating LLM outputs as raw strings, instructor leverages Zod to provide type hints, runtime validation, and IDE support. This approach treats LLMs as callable functions that return typed objects, making them backwards compatible with standard application code.
  8. Basic Usage of Instructor with OpenAI and Zod

    main

    Instructor enables structured data extraction by combining OpenAI's function calling API with Zod for schema validation and type inference.

    To use it, initialize the Instructor function by passing an existing OpenAI client and a mode (e.g., "FUNCTIONS"). You then define your desired data structure using a Zod schema. When calling client.chat.completions.create, provide the schema in the response_model option. The returned object will be automatically validated against the schema and will have the correct TypeScript type inferred from the Zod schema.

    import Instructor from "@instructor-ai/instructor";
    import OpenAI from "openai";
    import { z } from "zod";
    
    const oai = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY ?? undefined,
      organization: process.env.OPENAI_ORG_ID ?? undefined
    });
    
    const client = Instructor({
      client: oai,
      mode: "FUNCTIONS"
    });
    
    const UserSchema = z.object({
      // Description will be used in the prompt
      age: z.number().describe("The age of the user"), 
      name: z.string()
    });
    
    // User will be of type z.infer<typeof UserSchema>
    const user = await client.chat.completions.create({
      messages: [{ role: "user", content: "Jason Liu is 30 years old" }],
      model: "gpt-3.5-turbo",
      response_model: { 
        schema: UserSchema, 
        name: "User"
      }
    });
    
    console.log(user);
    // { age: 30, name: "Jason Liu" }
  9. Use Together AI for structured output

    main

    To use Together AI, set the TOGETHER_API_KEY environment variable and patch an OpenAI client pointing to Together AI's endpoint. Use mode: "TOOLS" for this provider.

    export TOGETHER_API_KEY="your-api-key"
    import { z } from "zod";
    import Instructor from "@instructor-js/instructor";
    import OpenAI from "openai";
    
    const client = Instructor({
      client: new OpenAI({
        apiKey: process.env.TOGETHER_API_KEY,
        base_url: "https://api.together.xyz/v1",
      }),
      mode: "TOOLS",
    });
    
    const UserExtractSchema = z.object({
      name: z.string(),
      age: z.number(),
    });
    
    const user = await client.chat.completions.create({
      model: "mistralai/Mixtral-8x7B-Instruct-v0.1",
      response_model: { schema: UserExtractSchema, name: "UserExtract" },
      messages: [
        { role: "user", content: "Extract jason is 25 years old" },
      ],
    });
    
    console.assert(user instanceof UserExtractSchema, "Should be instance of UserExtract");
  10. Use Markdown JSON Mode (Experimental)

    main

    !!! warning "Experimental"

    Markdown JSON mode simply instructs the model to provide a response in JSON format within the text. It is not recommended and may not be supported in the future. It is primarily maintained to support vision models but does not provide the full benefits of Instructor's structured output capabilities.

    const client = Instructor({
      client: new OpenAI({
        apiKey: process.env.OPENAI_API_KEY ?? undefined,
        organization: process.env.OPENAI_ORG_ID ?? undefined
      }),
      mode: "MD_JSON"
    })
  11. Use Zod descriptions for prompt engineering

    main

    You can improve extraction accuracy by adding .description() calls to your Zod schema fields and the object itself. Instructor includes these descriptions in the prompt sent to the LLM, effectively using them as instructions for how to fill each field.

    const userDetails = z.object({
      name: z.string().description('Your full name'),
      age: z.number(),
    }).description('Fully extracted user detail');
  12. Recommended workflow for using instructor with Zod

    main

    To maintain simplicity and minimize technical debt, follow this four-step pattern when integrating instructor into your TypeScript codebase:

    1. Define a Schema: Use Zod to create your structured data definition.
    2. Add Logic to Schema: Define validators and methods directly on your Zod schema.
    3. Encapsulate LLM Logic: Wrap the instructor call inside a dedicated function that returns your schema type.
    4. Perform Computations: Use the resulting typed data for computations, either via standalone functions or by calling methods defined on the schema itself.
    // 1. Define a Schema
    const StructuredData = z.object({
      // ... schema definition
    });
    
    // 2. Define validators and methods on your schema
    // (e.g., using z.preprocess or custom methods)
    
    // 3. Encapsulate all your LLM logic into a function
    async function extract(input: string): Promise<z.infer<typeof StructuredData>> {
      // instructor call logic here
    }
    
    // 4. Define typed computations against your data
    function compute(data: z.infer<typeof StructuredData>) {
      // ...
    }
    // OR call methods on your schema
    // data.compute();