A powerful, open-source AI SDK with a unified interface across multiple providers. No vendor lock-in, no proprietary formats, just clean TypeScript and honest open source.
- Multi-Provider Support - OpenAI, Anthropic, Ollama, Google Gemini
- Unified API - Same interface across all providers
- Standalone Functions - Direct type-safe functions that infer from adapters
- AI Class - Reusable instances with system prompts
- Structured Outputs - Type-safe JSON responses with
responseFormat()helper - Structured Streaming - JSON chunks with token deltas and metadata
- Tool/Function Calling - First-class support with automatic execution
- React Hooks - Simple
useChathook for building chat UIs - TypeScript First - Full type safety with inference from adapters
- Zero Lock-in - Switch providers at runtime without code changes
The easiest way to use the SDK - just pass an adapter and get full type inference:
import { chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
// Type-safe chat with automatic inference from adapter
const result = await chat({
adapter: openai(), // Automatically uses OPENAI_API_KEY from env
model: "gpt-4o", // <-- Autocompletes with OpenAI models
messages: [{ role: "user", content: "Hello!" }],
providerOptions: { // <-- Typed as OpenAI-specific options!
store: true,
parallelToolCalls: true,
}
});
console.log(result.content);Why use standalone functions?
- ✅ Type Inference - Model and providerOptions types are inferred from the adapter
- ✅ Simplicity - No class instantiation needed
- ✅ Direct - Call the function you need with the adapter you want
- ✅ Flexible - Easy to switch adapters on a per-call basis
Available standalone functions:
chat()- Chat completion with streaming and structured outputssummarize()- Text summarizationembed()- Generate embeddingsimage()- Image generationaudio()- Audio transcriptionspeak()- Text-to-speechvideo()- Video generation
For applications that need to configure system prompts once and reuse them:
import { ai } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
// Create an AI instance with system prompts
const aiInstance = ai(openai(), {
systemPrompts: ["You are a helpful assistant."]
});
// Use the instance - system prompts are automatically prepended
await aiInstance.chat({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }],
});Why use the AI class?
- ✅ System Prompts - Set default system prompts
- ✅ Reusable - Configure once, use many times
- ✅ Type Safety - Full type inference from adapter
Get type-safe JSON responses with the responseFormat() helper:
import { chat, responseFormat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
// Define your schema
const guitarSchema = responseFormat({
name: "guitar_info",
schema: {
type: "object",
properties: {
id: { type: "string" },
name: { type: "string" },
price: { type: "number" },
},
required: ["id", "name"],
additionalProperties: false,
} as const, // Important for type inference!
});
// Get typed response
const result = await chat({
adapter: openai(),
model: "gpt-4o",
messages: [{ role: "user", content: "Recommend a guitar" }],
options: {
responseFormat: guitarSchema, // Only available in promise mode
},
});
// ✅ res.data is now fully typed!
if (result.data) {
console.log(result.data.name); // string
console.log(result.data.price); // number
}Note
- Structured outputs via
options.responseFormatare only available in promise mode. When usingas: "stream"oras: "response", structured JSON is not parsed and you receive raw text/stream.- On successful parsing,
result.datais populated and typed; if parsing fails,result.datawill beundefinedandresult.contentwill contain the raw model output.
# Core library
npm install @tanstack/ai
# Provider adapters (install what you need)
npm install @tanstack/ai-openai
npm install @tanstack/ai-anthropic
npm install @tanstack/ai-ollama
npm install @tanstack/ai-gemini
# React hooks (for frontend chat UIs)
npm install @tanstack/ai-reactComplete a chat conversation with optional streaming and structured output.
// Promise mode (default)
const result = await chat({
adapter: openai(),
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }],
options: {
temperature: 0.7,
maxTokens: 1000,
},
providerOptions: { /* provider-specific options */ }
});// Streaming mode const stream = await chat({ adapter: openai(), model: "gpt-4o", messages: [{ role: "user", content: "Hello!" }], as: "stream", // Returns AsyncIterable });
for await (const chunk of stream) { if (chunk.type === "content") { console.log(chunk.delta); // Incremental token } }
// Response mode (for HTTP endpoints) const response = await chat({ adapter: openai(), model: "gpt-4o", messages: [{ role: "user", content: "Hello!" }], as: "response", // Returns Response with SSE headers });
return response; // Can be returned directly from API routes
#### `summarize(options)`
Summarize text using AI.
```typescript
const result = await summarize({
adapter: openai(),
model: "gpt-4o",
text: "Long text to summarize...",
maxLength: 200,
});
console.log(result.summary);
Generate embeddings for text.
const result = await embed({
adapter: openai(),
model: "text-embedding-ada-002",
input: ["Text 1", "Text 2"],
});
console.log(result.embeddings); // number[][]Generate images from text prompts.
const result = await image({
adapter: openai(),
model: "dall-e-3",
prompt: "A beautiful sunset over mountains",
size: "1024x1024",
});
console.log(result.image?.base64);Transcribe audio files.
const result = await audio({
adapter: openai(),
model: "whisper-1",
file: audioFile, // File, Blob, or Buffer
language: "en",
});
console.log(result.text);Convert text to speech.
const result = await speak({
adapter: openai(),
model: "tts-1",
input: "Hello, world!",
voice: "alloy",
});
// result.audio is a Buffer or BlobGenerate videos from text prompts.
const result = await video({
adapter: openai(),
model: "sora-1",
prompt: "A timelapse of a flower blooming",
duration: 5,
});
// result.video is a Buffer or Blobimport { ai } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
const aiInstance = ai(openai(), {
systemPrompts: ["You are a helpful assistant."],
});Same as standalone chat() function, but system prompts are automatically prepended.
await aiInstance.chat({
model: "gpt-4o",
messages: [{ role: "user", content: "Hello!" }],
// Tools are passed at root level
// tools: [myTool],
// Common options go under `options`
options: {
temperature: 0.7,
},
// Provider-specific options at root
// providerOptions: { ... }
as: "promise", // or "stream" or "response"
});All standalone functions are available as methods on the AI instance.
Create a typed response format for structured outputs.
import { responseFormat } from "@tanstack/ai";
const schema = responseFormat({
name: "person",
schema: {
type: "object",
properties: {
name: { type: "string" },
age: { type: "number" },
},
required: ["name"],
additionalProperties: false,
} as const, // Important for type inference!
});
// Use with chat
const result = await chat({
adapter: openai(),
model: "gpt-4o",
messages: [{ role: "user", content: "Tell me about John Doe" }],
options: {
responseFormat: schema,
},
});
// result.data is typed based on the schema (promise mode only)
if (result.data) {
console.log(result.data.name); // string
console.log(result.data.age); // number
}
// If parsing fails, `result.data` will be undefined and `result.content` will contain raw text.Create a tool for function calling with automatic execution.
import { tool } from "@tanstack/ai";
const weatherTool = tool({
type: "function",
function: {
name: "getWeather",
description: "Get current weather for a location",
parameters: {
type: "object",
properties: {
location: { type: "string" },
},
required: ["location"],
},
},
execute: async (args) => {
const weather = await fetchWeather(args.location);
return JSON.stringify(weather);
},
});
// Use with chat
const result = await chat({
adapter: openai(),
model: "gpt-4o",
messages: [{ role: "user", content: "What's the weather in Paris?" }],
tools: [weatherTool],
});Note: When using streaming (as: "stream"), tools with execute functions are automatically called and their results are added to the conversation.
Build chat interfaces with the useChat hook:
import { useChat } from "@tanstack/ai-react";
function ChatComponent() {
const {
messages, // Current message list
sendMessage, // Send a message
isLoading, // Is generating response
error, // Current error
append, // Add message programmatically
reload, // Reload last response
stop, // Stop generation
clear, // Clear all messages
} = useChat({
api: "/api/chat",
onChunk: (chunk) => console.log(chunk),
});
const [input, setInput] = useState("");
return (
<div>
{messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong> {message.content}
</div>
))}
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
sendMessage(input);
setInput("");
}
}}
/>
<button onClick={() => { sendMessage(input); setInput(""); }}>
Send
</button>
</div>
);
}import { chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
const result = await chat({
adapter: openai(),
model: "gpt-4o",
messages: [{ role: "user", content: "Explain quantum computing" }],
});
console.log(result.content);const stream = await chat({
adapter: openai(),
model: "gpt-4o",
messages: [{ role: "user", content: "Tell me a story" }],
as: "stream",
});
for await (const chunk of stream) {
if (chunk.type === "content") {
process.stdout.write(chunk.delta);
}
if (chunk.type === "done") {
console.log(`\nTokens: ${chunk.usage?.totalTokens}`);
}
}import { chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
import { anthropic } from "@tanstack/ai-anthropic";
// Use OpenAI
const result1 = await chat({
adapter: openai(),
model: "gpt-4o",
messages: [{ role: "user", content: "Hello" }],
});
// Switch to Anthropic - same code works!
const result2 = await chat({
adapter: anthropic(),
model: "claude-3-5-sonnet-20241022",
messages: [{ role: "user", content: "Hello" }],
});import { chat, tool } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
const weatherTool = tool({
type: "function",
function: {
name: "getWeather",
description: "Get weather for a location",
parameters: {
type: "object",
properties: {
location: { type: "string" },
},
required: ["location"],
},
},
execute: async (args) => {
return JSON.stringify({ temp: 72, condition: "sunny" });
},
});
const stream = await chat({
adapter: openai(),
model: "gpt-4o",
messages: [{ role: "user", content: "What's the weather in Paris?" }],
tools: [weatherTool],
as: "stream",
});
// Tool is automatically executed and results are added to conversation
for await (const chunk of stream) {
if (chunk.type === "content") {
process.stdout.write(chunk.delta);
}
}import { createFileRoute } from "@tanstack/react-router";
import { chat } from "@tanstack/ai";
import { openai } from "@tanstack/ai-openai";
export const Route = createFileRoute("/api/chat")({
server: {
handlers: {
POST: async ({ request }) => {
const { messages } = await request.json();
// Returns Response with SSE headers automatically
return await chat({
adapter: openai(),
model: "gpt-4o",
messages,
as: "response",
});
},
},
},
});# Install dependencies
pnpm install
# Build all packages
pnpm build
# Run in dev mode
pnpm dev
# Type checking
pnpm typecheck
# Clean build artifacts
pnpm cleanWe welcome contributions! This is a community-driven project providing a truly open alternative to proprietary AI SDKs.
MIT - Use freely, modify, share. No strings attached.
Unlike certain companies that use open source as marketing only to lock you into paid services, @tanstack/ai is committed to remaining truly open source. No enshittification, no bait-and-switch, just honest software that respects developers.
Built with ❤️ by the open-source community.