A TypeScript provider for the Vercel AI SDK that enables you to use Heroku's AI inference capabilities in your applications. This provider supports both chat completions and embeddings through Heroku's AI infrastructure.
npm install heroku-ai-provider
# or
yarn add heroku-ai-provider
# or
pnpm add heroku-ai-provider
Before using this provider, you'll need:
npm install ai
Set your Heroku AI API keys as environment variables:
# For chat completions
INFERENCE_KEY=your_inference_api_key
# For embeddings
EMBEDDING_KEY=your_embedding_api_key
# For image generations
DIFFUSION_KEY=your_diffusion_api_key
# Optional: Custom API endpoints
INFERENCE_URL=https://us.inference.heroku.com
EMBEDDING_URL=https://us.inference.heroku.com
DIFFUSION_URL=https://us.inference.heroku.com
import { heroku } from "heroku-ai-provider";
const model = heroku.chat("claude-4-sonnet");
import { createHerokuAI } from "heroku-ai-provider";
const client = createHerokuAI({
chatApiKey: "your_inference_api_key",
embeddingsApiKey: "your_embedding_api_key",
chatBaseUrl: "https://us.inference.heroku.com/v1/chat/completions",
embeddingsBaseUrl: "https://us.inference.heroku.com/v1/embeddings",
imageApiKey: "your_diffusion_api_key",
imageBaseUrl: "https://us.inference.heroku.com/v1/images/generations",
});
This library is compatible with browser environments. However, since process.env is not available in browsers, you must provide API keys via the options parameter. At least one API key is required - you only need to provide the keys for the features you're using:
import { createHerokuAI } from "heroku-ai-provider";
// Example: Only using chat completions
const heroku = createHerokuAI({
chatApiKey: "your_inference_api_key",
});
// Or if you need multiple features, provide the relevant keys
const heroku = createHerokuAI({
chatApiKey: "your_inference_api_key",
embeddingsApiKey: "your_embedding_api_key",
// imageApiKey not needed if you're not using image generation
});
// Now you can use it normally
const { text } = await generateText({
model: heroku.chat("claude-4-sonnet"),
prompt: "Hello, world!",
});
Security Note: Never hardcode API keys in client-side code. In browser applications, consider:
import { generateText } from "ai";
import { heroku } from "heroku-ai-provider";
const { text } = await generateText({
model: heroku.chat("claude-4-sonnet"),
prompt: "What is the capital of France?",
});
console.log(text); // "The capital of France is Paris."
import { streamText, stepCountIs } from "ai";
import { heroku } from "heroku-ai-provider";
const { textStream } = await streamText({
model: heroku.chat("claude-3-haiku"),
prompt: "Write a short story about a robot learning to paint.",
});
for await (const delta of textStream) {
process.stdout.write(delta);
}
import { generateText } from "ai";
import { heroku } from "heroku-ai-provider";
const { text } = await generateText({
model: heroku.chat("claude-4-sonnet"),
system: "You are a helpful assistant that explains complex topics simply.",
prompt: "Explain quantum computing",
});
import { generateText, tool, stepCountIs } from "ai";
import { heroku } from "heroku-ai-provider";
import { z } from "zod";
const { text } = await generateText({
model: heroku.chat("claude-4-sonnet"),
prompt: "What is the weather like in New York?",
tools: {
getWeather: tool({
description: "Get the current weather for a location",
parameters: z.object({
location: z.string().describe("The city and state"),
}),
execute: async ({ location }) => {
// Simulate weather API call
return {
location,
temperature: 72,
condition: "sunny",
};
},
}),
},
stopWhen: stepCountIs(5),
});
import { generateText, tool, stepCountIs } from "ai";
import { heroku } from "heroku-ai-provider";
import { z } from "zod";
const { text, steps } = await generateText({
model: heroku.chat("claude-4-sonnet"),
prompt:
"Check the weather in New York and then suggest appropriate clothing.",
tools: {
getWeather: tool({
description: "Get the current weather for a location",
parameters: z.object({
location: z.string().describe("The city and state"),
}),
execute: async ({ location }) => {
return {
location,
temperature: 45,
condition: "rainy",
humidity: 80,
};
},
}),
suggestClothing: tool({
description: "Suggest appropriate clothing based on weather conditions",
inputSchema: z.object({
temperature: z.number().describe("Temperature in Fahrenheit"),
condition: z.string().describe("Weather condition"),
humidity: z.number().optional().describe("Humidity percentage"),
}),
execute: async ({ temperature, condition, humidity }) => {
return {
suggestions: [
"Waterproof jacket",
"Warm layers",
"Waterproof shoes",
"Umbrella",
],
reasoning: `Given ${temperature}°F and ${condition} weather${humidity ? ` with ${humidity}% humidity` : ""}, you'll want to stay warm and dry.`,
};
},
}),
},
stopWhen: stepCountIs(5),
});
console.log("Final response:", text);
console.log("Tool execution steps:", steps.length);
import { embed } from "ai";
import { heroku } from "heroku-ai-provider";
const { embedding } = await embed({
model: heroku.embedding("cohere-embed-multilingual"),
value: "Hello, world!",
});
console.log(embedding); // [0.1, 0.2, -0.3, ...]
import { embedMany } from "ai";
import { heroku } from "heroku-ai-provider";
const { embeddings } = await embedMany({
model: heroku.embedding("cohere-embed-multilingual"),
values: ["First document", "Second document", "Third document"],
});
console.log(embeddings.length); // 3
import { createEmbedFunction } from "heroku-ai-provider";
// Create a reusable embed function
const embedText = createEmbedFunction({
apiKey: process.env.EMBEDDING_KEY!,
model: "cohere-embed-multilingual",
});
const embedding = await embedText("Hello, world!");
console.log(embedding); // [0.1, 0.2, -0.3, ...]
import { experimental_generateImage as generateImage } from "ai";
import { heroku } from "heroku-ai-provider";
const result = await generateImage({
model: heroku.image("stable-image-ultra"),
prompt: "A watercolor illustration of a lighthouse at sunrise",
size: "1024x1024",
});
const imageBytes = result.image.uint8Array;
console.log("Generated image bytes length:", imageBytes.length);
interface HerokuProviderSettings {
// API keys (falls back to environment variables)
chatApiKey?: string; // INFERENCE_KEY
embeddingsApiKey?: string; // EMBEDDING_KEY
imageApiKey?: string; // DIFFUSION_KEY
// Base URLs (falls back to environment variables or defaults)
chatBaseUrl?: string; // INFERENCE_URL
embeddingsBaseUrl?: string; // EMBEDDING_URL
imageBaseUrl?: string; // DIFFUSION_URL
}
claude-4-sonnet - Claude 4 Sonnet by Anthropicclaude-4-5-sonnet - Claude 4.5 Sonnet by Anthropicclaude-3-haiku - Claude 3 Haiku by Anthropicclaude-3-7-sonnet - Claude 3.7 Sonnet by Anthropicclaude-3-5-haiku - Claude 3.5 Haiku by Anthropicclaude-3-5-sonnet-latest - Claude 3.5 Sonnet by Anthropicgpt-oss-120b - gpt-oss-120b by OpenAInova-lite - Nova Lite by Amazonnova-pro - Nova Pro by Amazoncohere-embed-multilingual - Multilingual embedding model by Coherestable-image-ultra - Stable Image Ultra diffusion model by Stability AI// app/api/chat/route.ts
import { streamText, stepCountIs } from "ai";
import { heroku } from "heroku-ai-provider";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: heroku.chat("claude-4-sonnet"),
messages,
stopWhen: stepCountIs(5), // Enable multi-step tool conversations
});
return result.toDataStreamResponse();
}
// app/api/chat/route.ts
import { streamText, tool } from "ai";
import { heroku } from "heroku-ai-provider";
import { z } from "zod";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await streamText({
model: heroku.chat("claude-4-sonnet"),
messages,
tools: {
getTime: tool({
description: "Get the current time",
inputSchema: z.object({
timezone: z
.string()
.optional()
.describe("Timezone (e.g., 'America/New_York')"),
}),
execute: async ({ timezone = "UTC" }) => {
return {
time: new Date().toLocaleString("en-US", { timeZone: timezone }),
timezone,
};
},
}),
},
stopWhen: stepCountIs(5),
});
return result.toDataStreamResponse();
}
import express from "express";
import { generateText } from "ai";
import { heroku } from "heroku-ai-provider";
const app = express();
app.post("/chat", async (req, res) => {
const { prompt } = req.body;
const { text } = await generateText({
model: heroku.chat("claude-3-haiku"),
prompt,
});
res.json({ response: text });
});
The provider includes comprehensive error handling with user-friendly error messages:
import {
createHerokuAI,
isConfigurationError,
isTemporaryServiceError,
} from "heroku-ai-provider";
try {
const result = await generateText({
model: heroku.chat("claude-4-sonnet"),
prompt: "Hello!",
});
} catch (error) {
if (isConfigurationError(error)) {
console.error("Configuration error:", error.message);
// Handle API key or URL configuration issues
} else if (isTemporaryServiceError(error)) {
console.error("Service error:", error.message);
// Handle temporary service issues (retry logic)
} else {
console.error("Unexpected error:", error);
}
}
createHerokuAI()stopWhen (for example, stopWhen: stepCountIs(5)) so the model can complete multi-step tool conversations$schema) that some validation libraries addWe welcome contributions! Please follow these steps:
git checkout -b feature/amazing-featurenpm testnpm run lintgit commit -m 'Add amazing feature'git push origin feature/amazing-feature# Clone the repository
git clone https://github.com/julianduque/heroku-ai-provider.git
cd heroku-ai-provider
# Install dependencies
pnpm install
# Run tests
pnpm test
# Build the project
pnpm build
# Lint code
pnpm lint
The project uses Jest for testing. Run tests with:
# Run all tests
pnpm test
# Run tests in watch mode
pnpm test --watch
# Run tests with coverage
pnpm test --coverage
This project is licensed under the Apache 2.0 License - see the LICENSE file for details.