LLM Integration
Prerequisites:
npm install @jaypie/llm- API key for at least one provider (Anthropic, Fireworks, Google, Meta, Mistral, OpenAI, OpenRouter, or xAI)
Overview
Section titled “Overview”@jaypie/llm provides a unified interface for calling multiple LLM providers.
The Llm class handles provider-specific implementations while exposing a consistent API for operate, stream, and tool calling.
Quick Reference
Section titled “Quick Reference”Supported Providers
Section titled “Supported Providers”Models are listed as LLM.MODEL.* names rather than ids. The alias is stable
across releases; the id behind it moves whenever the provider ships a successor.
| Provider | Models | Env Variable |
|---|---|---|
| Anthropic | MODEL.SONNET, MODEL.OPUS, MODEL.HAIKU, MODEL.FABLE |
ANTHROPIC_API_KEY |
| Bedrock | MODEL.NOVA_PRO, MODEL.NOVA_LITE |
(AWS credentials) |
| Fireworks | MODEL.FIREWORKS.* (DEEPSEEK, GLM, GPT_OSS, INKLING, KIMI, MINIMAX, NEMOTRON, QWEN) |
FIREWORKS_API_KEY |
MODEL.GEMINI_FLASH, MODEL.GEMINI_FLASH_LITE, MODEL.GEMINI_PRO |
GOOGLE_API_KEY |
|
| Meta | MODEL.MUSE_SPARK, MODEL.MUSE_SPARK_CONTRIBUTOR |
META_API_KEY (or MODEL_API_KEY) |
| Mistral | MODEL.MISTRAL.* (LARGE, MEDIUM, SMALL, OCR) |
MISTRAL_API_KEY |
| OpenAI | MODEL.ASTRA, MODEL.SOL, MODEL.LUNA, MODEL.TERRA |
OPENAI_API_KEY |
| OpenRouter | MODEL.OPENROUTER.* (GLM, LUNA, SONNET) |
OPENROUTER_API_KEY |
| xAI | MODEL.GROK |
XAI_API_KEY |
Core Methods
Section titled “Core Methods”| Method | Purpose | Returns |
|---|---|---|
Llm.operate() |
Single prompt/response | string |
Llm.stream() |
Streaming response | AsyncGenerator |
llm.operate() |
Instance method with history | string |
Basic Usage
Section titled “Basic Usage”Static Method (Recommended for single calls)
Section titled “Static Method (Recommended for single calls)”import Llm, { LLM } from "@jaypie/llm";
// Provider auto-detected from modelconst response = await Llm.operate("What is 2+2?", { model: LLM.MODEL.SONNET,});console.log(response); // "4"Instance Method (For conversations)
Section titled “Instance Method (For conversations)”import Llm, { LLM } from "@jaypie/llm";
const llm = new Llm("anthropic", { model: LLM.MODEL.SONNET, system: "You are a helpful assistant.",});
const response1 = await llm.operate("My name is Alice.");const response2 = await llm.operate("What is my name?");// Maintains conversation historyThe first constructor argument may be a provider name or a model name. A model name auto-detects the provider and is retained:
const llm = new Llm("claude-sonnet-4-6"); // -> anthropic, claude-sonnet-4-6Model Selection
Section titled “Model Selection”By Model Name
Section titled “By Model Name”// Anthropic modelsawait Llm.operate(prompt, { model: LLM.MODEL.SONNET });await Llm.operate(prompt, { model: LLM.MODEL.OPUS });await Llm.operate(prompt, { model: LLM.MODEL.HAIKU });
// OpenAI modelsawait Llm.operate(prompt, { model: LLM.MODEL.SOL });await Llm.operate(prompt, { model: LLM.MODEL.LUNA });
// Google modelsawait Llm.operate(prompt, { model: LLM.MODEL.GEMINI_FLASH });Explicit Provider
Section titled “Explicit Provider”const llm = new Llm("openai", { model: LLM.MODEL.SOL,});Streaming
Section titled “Streaming”Basic Streaming
Section titled “Basic Streaming”for await (const chunk of Llm.stream("Tell me a story")) { process.stdout.write(chunk.content || "");}Express SSE Streaming
Section titled “Express SSE Streaming”import { expressStreamHandler, createExpressStream } from "jaypie";import Llm from "@jaypie/llm";
export default expressStreamHandler(async (req, res, context) => { const stream = createExpressStream(context);
for await (const chunk of Llm.stream(req.body.prompt)) { stream.write(chunk.content || ""); }
stream.end();});Lambda Response Streaming
Section titled “Lambda Response Streaming”import { lambdaStreamHandler } from "jaypie";import Llm from "@jaypie/llm";
export const handler = awslambda.streamifyResponse( lambdaStreamHandler( async (event, context) => { for await (const chunk of Llm.stream(event.prompt)) { context.responseStream.write(`data: ${JSON.stringify(chunk)}\n\n`); } }, { contentType: "text/event-stream", }, ),);Tool Calling
Section titled “Tool Calling”Creating Tools
Section titled “Creating Tools”import Llm, { LLM, Toolkit } from "@jaypie/llm";
const toolkit = new Toolkit([ { name: "get_weather", description: "Get current weather for a city", parameters: { type: "object", properties: { city: { type: "string", description: "City name", }, }, required: ["city"], }, call: async ({ city }) => { // Actual implementation return `Weather in ${city}: sunny, 72F`; }, },]);
const response = await Llm.operate("What's the weather in NYC?", { model: LLM.MODEL.SONNET, tools: toolkit,});Zod Schema Tools
Section titled “Zod Schema Tools”import { z } from "zod";import Llm, { Toolkit } from "@jaypie/llm";
const toolkit = new Toolkit([ { name: "create_user", description: "Create a new user", parameters: z.object({ name: z.string().describe("User's full name"), email: z.string().email().describe("User's email"), age: z.number().optional().describe("User's age"), }), call: async ({ name, email, age }) => { const user = await db.users.create({ name, email, age }); return { id: user.id, name, email }; }, },]);Tool with Validation
Section titled “Tool with Validation”{ name: "transfer_money", description: "Transfer money between accounts", parameters: { type: "object", properties: { from: { type: "string" }, to: { type: "string" }, amount: { type: "number" }, }, required: ["from", "to", "amount"], }, call: async ({ from, to, amount }) => { if (amount <= 0) { throw BadRequestError("Amount must be positive"); } // Process transfer return { success: true, transactionId: uuid() }; },}Structured Output
Section titled “Structured Output”Pass format to receive guaranteed-valid JSON. format accepts Jaypie’s natural schema syntax (preferred), a raw JSON Schema, or a Zod schema.
Natural Schema
Section titled “Natural Schema”const response = await Llm.operate( "Extract the person's name and age from: 'John is 25 years old'", { model: LLM.MODEL.SOL, format: { name: String, age: Number, }, },);// Returns: { name: "John", age: 25 }Every array field declared in format is guaranteed present in the response as an array — an empty result surfaces as [], never undefined.
JSON Schema
Section titled “JSON Schema”const response = await Llm.operate(prompt, { model: LLM.MODEL.SOL, format: { type: "object", properties: { name: { type: "string" }, age: { type: "number" }, }, },});// Returns: { name: "John", age: 25 }Zod Schema
Section titled “Zod Schema”import { z } from "zod";
const PersonSchema = z.object({ name: z.string(), age: z.number(),});
const response = await Llm.operate(prompt, { model: LLM.MODEL.SOL, format: PersonSchema,});// Returns typed object matching PersonSchemaMulti-Turn Conversations
Section titled “Multi-Turn Conversations”const llm = new Llm("anthropic", { model: LLM.MODEL.SONNET, system: "You are a math tutor.",});
// Conversation history maintained automaticallyawait llm.operate("What is calculus?");await llm.operate("Can you give me an example?");await llm.operate("How is that used in physics?");
// Access historyconsole.log(llm.history);Files and Images
Section titled “Files and Images”Image Input
Section titled “Image Input”const response = await Llm.operate("What's in this image?", { model: LLM.MODEL.SONNET, files: [ { type: "image", data: base64ImageData, mediaType: "image/png", }, ],});URL Image
Section titled “URL Image”const response = await Llm.operate("Describe this image", { model: LLM.MODEL.SOL, files: [ { type: "image", url: "https://example.com/image.jpg", }, ],});Lifecycle Hooks
Section titled “Lifecycle Hooks”Seven hooks fire through the operate loop, each receiving the full provider request/response payloads:
const response = await Llm.operate(prompt, { model: LLM.MODEL.SONNET, hooks: { beforeEachModelRequest: ({ providerRequest }) => { log.trace("[llm] calling model"); }, afterEachModelResponse: ({ content, usage }) => { log.trace("[llm] response received"); log.var({ tokens: usage[usage.length - 1]?.total }); }, beforeEachTool: ({ toolName, args, message }) => { // message is the tool's resolved LlmTool.message, when defined log.trace(message ?? `[llm] calling ${toolName}`); }, afterEachTool: ({ result, toolName, message }) => { log.trace(`[llm] ${toolName} returned`); }, onToolError: ({ error, toolName, message }) => { log.warn(`[llm] ${toolName} failed`); log.var({ error: error.message }); }, onRetryableModelError: ({ error }) => { log.warn("[llm] model call failed, retrying"); }, onUnrecoverableModelError: ({ error }) => { log.error("[llm] model call failed"); log.var({ error }); }, },});Progress Events
Section titled “Progress Events”For progress reporting (UI updates, websockets, queue notifications), prefer a single onProgress callback over wiring individual hooks. It receives lightweight, serializable events as the operate loop runs:
const response = await Llm.operate(prompt, { model: LLM.MODEL.SONNET, tools: toolkit, onProgress: (event) => { // event.type: start, model_request, model_response, // tool_call, tool_result, tool_error, retry, done websocket.send(JSON.stringify(event)); },});Fields carried by each event (turn is 1-indexed):
| Event | Fields |
|---|---|
start |
model, provider, maxTurns |
model_request |
turn, model |
model_response |
turn, content (text, if any), toolCalls ([{ name, arguments }], if any), usage (this turn) |
tool_call |
turn, tool: { name, arguments, message } — fires before the tool runs; arguments is the JSON string; message is the resolved LlmTool.message, when the tool defines one |
tool_result |
turn, tool: { name } — the result value is deliberately omitted (it can be arbitrarily large); use the afterEachTool hook to receive it |
tool_error |
turn, tool: { name }, error (message string) |
retry |
turn, error (message string) |
done |
turn (total turns used), content (final text or structured output), usage (cumulative) |
Errors thrown by the callback are logged and never interrupt the loop. stream() communicates progress through its chunks; onProgress applies to operate().
Error Handling
Section titled “Error Handling”import { BadGatewayError, log } from "jaypie";import Llm, { LLM } from "@jaypie/llm";
async function askLlm(prompt) { try { return await Llm.operate(prompt, { model: LLM.MODEL.SONNET }); } catch (error) { log.error("[askLlm] LLM call failed"); log.var({ error: error.message }); throw BadGatewayError("AI service unavailable"); }}Provider-Specific Options
Section titled “Provider-Specific Options”modelOptions sets provider-specific request fields per model. Keys are an
exact model id, a MODEL catalog key, a provider (or the alias gemini), or
default, and every attempt in a fallback chain merges the keys that match its
own model. providerOptions is deprecated, reaches the primary only, and is
removed in 2.0.
Anthropic
Section titled “Anthropic”max_tokens defaults to the model’s maximum output (capped at 16,384 for
non-streaming requests). Override it through modelOptions:
await Llm.operate(prompt, { model: LLM.MODEL.SONNET, modelOptions: { anthropic: { max_tokens: 32000 } }, temperature: 0.7,});OpenAI
Section titled “OpenAI”await Llm.operate(prompt, { model: LLM.MODEL.SOL, temperature: 0.5, topP: 0.9,});Related
Section titled “Related”- Express on Lambda - Building APIs with LLM
- Handler Lifecycle - Integrating LLM in handlers
- @jaypie/llm - Full API reference
- Fabric - Service handler to LLM tool conversion