LLM.js

repository·main·Indexed 40 days ago

https://github.com/themaximalist/llm.js

A zero-dependency JavaScript library providing a unified interface for hundreds of Large Language Models in Node.js and the browser. Version 1.0.1 supports chat history, streaming, reasoning (thinking), tool calling, and structured data parsing across providers including OpenAI, Anthropic, Google, xAI, Groq, DeepSeek, and Ollama.

Tokens
13.6K
Snippets
55
Records
56
Agent score
100%

What's inside LLM.js

  1. Configure API keys for LLM.js

    main

    In Node.js, LLM.js automatically detects API keys from environment variables. For the browser, keys must be provided as an option.

    Supported environment variables:

    • OPENAI_API_KEY
    • ANTHROPIC_API_KEY
    • GOOGLE_API_KEY
    • GROQ_API_KEY
    • DEEPSEEK_API_KEY
    • XAI_API_KEY

    You can also pass the key directly in the options object: { apiKey: "sk-123" }.

    export OPENAI_API_KEY=...
    export ANTHROPIC_API_KEY=...
    export GOOGLE_API_KEY=...
    export GROQ_API_KEY=...
    export DEEPSEEK_API_KEY=...
    export XAI_API_KEY=...
  2. Add a custom service via configuration

    main

    If a built-in service is not available, you can use an existing API by passing a custom configuration object to the LLM constructor. You must provide the service name, baseUrl, model, and apiKey.

    const llm = new LLM({
        service: "together",
        baseUrl: "https://api.together.xyz/v1",
        model: "meta-llama/Llama-3-70b-chat-hf",
        apiKey,
    });
  3. Make a simple one-off LLM request

    main

    The simplest way to use LLM.js is to call the LLM function as an async function. This performs a single request without storing any conversation history.

    import LLM from "@themaximalist/llm.js"
    await LLM("hello"); // Response: hi
  4. Understand the Response and StreamResponse structures

    main

    The structure of the returned data depends on whether you are using standard or streaming modes.

    Standard Response

    When stream is false, you receive a Response object containing:

    • service: The ServiceName used.
    • content: The text response from the model.
    • usage: A Usage object containing token and cost metrics.
    • tool_calls: An array of ToolCall objects if the model requested tool execution.
    • thinking: The model's internal reasoning (if think: true was used).

    Streaming Response

    When stream is true, you receive a PartialStreamResponse which provides:

    • stream: An AsyncGenerator yielding either strings (chunks of text) or objects containing metadata/tokens.
    • complete(): A method that returns a Promise<StreamResponse> (the full, final Response object).
  5. Configure LLM.js with Options

    main

    When instantiating LLM or calling the LLM function, you can provide a configuration object to control the behavior of the request.

    Key Configuration Options:

    • service: The LLM provider. Supported values: openai, anthropic, google, xai, groq, deepseek, ollama.
    • apiKey: API key for the service. If omitted, the library attempts to read it from environment variables.
    • model: The specific model name (e.g., gpt-4o). If not provided, it is auto-detected from the service.
    • stream: Boolean to enable real-time streaming responses.
    • extended: Boolean to return a detailed response including usage, costs, and metadata.
    • think: Boolean to enable reasoning/thinking mode for supported models.
    • temperature: Controls randomness (0 = deterministic, 2 = very creative).
    • max_tokens: Maximum response length.
    • parser: A function or preset to transform response content (e.g., LLM.parsers.json).
    • tools: An array of functions the model can call.
    • max_thinking_tokens: Maximum tokens allocated for the thinking process.
    const llm = new LLM(input, {
      service: "openai",        // LLM service provider
      apiKey: "sk-123",          // apiKey
      model: "gpt-4o",          // Specific model
      max_tokens: 1000,         // Maximum response length
      temperature: 0.7,         // "Creativity" (0-2)
      stream: true,             // Enable streaming
      extended: true,           // Extended responses with metadata
      messages: [],             // message history
      think: true,              // Enable thinking mode
      parser: LLM.parsers.json, // Content parser
      tools: [...],             // Available tools
      max_thinking_tokens: 500, // Max tokens for thinking
    });
  6. Switch between different LLM providers

    main

    LLM.js provides a unified interface to interact with various local and remote providers. You can switch providers by specifying the service and model in the options object.

    Note: Local models (via ollama) track token usage, but the cost is always reported as $0.

    // Defaults to Ollama (local)
    await LLM("the color of the sky is");
    
    // OpenAI
    await LLM("the color of the sky is", { model: "gpt-4o-mini", service: "openai" });
    
    // Anthropic
    await LLM("the color of the sky is", { model: "claude-3-5-sonnet-latest", service: "anthropic" });
    
    // Google
    await LLM("the color of the sky is", { model: "gemini-1.5-pro", service: "google" });
    
    // xAI
    await LLM("the color of the sky is", { service: "xai", model: "grok-beta" });
    
    // DeepSeek with thinking
    await LLM("solve this puzzle", { service: "deepseek", model: "deepseek-reasoner", think: true });
    
    // Ollama (local)
    await LLM("the color of the sky is", { model: "llama3.2:3b", service: "ollama" });
  7. Use Tools for function calling

    main

    Enable LLMs to call custom functions by providing a tools array in the options. Each tool follows a schema defining its name, description, and input_schema (JSON Schema).

    Passing tools automatically enables extended mode. Tool calls are automatically added to the message history. In streaming mode, you can listen for type: "tool_calls" chunks.

    const getCurrentWeather = {
      name: "get_current_weather",
      description: "Get the current weather for a city",
      input_schema: {
        type: "object",
        properties: {
          city: { type: "string", description: "The name of the city" }
        },
        required: ["city"]
      }
    };
    
    const response = await LLM("What's the weather in Tokyo?", {
      tools: [getCurrentWeather],
    });
    
    // tool use automatically enables extended mode
    console.log(response.tool_calls); 
    // [{ id: "call_123", name: "get_current_weather", input: { city: "Tokyo" } }]
  8. Provide full Message History

    main

    Instead of a simple string, you can pass an array of message objects to the LLM function to provide full conversation context. Each object must have a role (e.g., "user", "assistant") and content.

    await LLM([
        { role: "user", content: "remember the secret codeword is blue" },
        { role: "assistant", content: "OK I will remember" },
        { role: "user", content: "what is the secret codeword I just told you?" },
    ]); // "blue"
  9. Add custom models and pricing

    main

    If a model is missing from the refreshed list or you are using a custom provider, you can manually add token and pricing information using ModelUsage.addCustom(). Once added, these models can be used with the standard LLM interface and will correctly report costs when the extended: true option is used.

    import { ModelUsage } from "@themaximalist/llm.js";
    
    ModelUsage.addCustom({
      model: "my-custom-gpt",
      service: "openai", 
      input_cost_per_token: 0.00001,
      output_cost_per_token: 0.00003,
      max_tokens: 4096
    });
    
    // Now use it like any other model
    const response = await LLM("hello", { 
      service: "openai", 
      model: "my-custom-gpt",
      extended: true 
    });
    console.log(response.usage.total_cost); // Uses your custom pricing
  10. Implement a fully custom model by subclassing LLM

    main

    For complete control over the API interaction, subclass LLM and implement the required parse methods. This is necessary for handling custom response formats for content, tools, thinking, models, options, token usage, and cost usage. You must also implement streaming variants (e.g., parseContentChunk) if you support streaming.

    class Custom extends LLM {
        static readonly service: ServiceName = "secretAGI";
        static DEFAULT_BASE_URL: string = "http://localhost:9876";
        static DEFAULT_MODEL: string = "gpt-999";
        static isLocal: boolean = false; // don't track pricing
        static isBearerAuth: boolean = false;
    
        get chatUrl() { return `${this.baseUrl}/chat` }
        get modelsUrl() { return `${this.baseUrl}/models` }
    
        parseContent(data: any): string { ... }
        parseTools(data: any): ToolCall[] { ... }
        parseThinking(data: any): string { ... }
        parseModel(model: any): Model { ... }
        parseOptions(options: Options): Options { ... }
        parseTokenUsage(usage: any): InputOutputTokens | null { ... }
        parseUsage(tokenUsage: InputOutputTokens): Usage { ... }
    
        // streaming methods
        parseToolsChunk(chunk: any): ToolCall[] { return this.parseTools(chunk) }
        parseContentChunk(chunk: any): string { return this.parseContent(chunk) }
        parseThinkingChunk(chunk: any): string { return this.parseThinking(chunk) }
    }