Best AI SDK for JavaScript and TypeScript
Best AI SDK for JavaScript and TypeScript
Building AI features by manually wiring HTTP requests to LLM APIs creates brittle code that breaks when providers change response formats, requires duplicated error handling across every integration point, and forces you to reimplement streaming, retries, and context management for each new model you support. Yet many developers start with raw fetch calls and discover months later that they've built a poorly-tested, incomplete version of what mature AI SDKs provide out of the box.
This article evaluates the strongest AI SDKs for JavaScript and TypeScript based on criteria that matter in production: provider coverage and abstraction quality, TypeScript support and type safety, streaming implementation quality, integration complexity with existing frameworks like Next.js and React, and maintenance burden over time. You'll learn which SDKs excel at which use cases, where each makes architectural tradeoffs, and how to choose based on your specific requirements.
We'll cover Vercel AI SDK for its React integration and streaming focus, LangChain.js for complex workflows and agent patterns, provider-specific SDKs like OpenAI and Anthropic's official libraries, and lightweight alternatives for teams that want minimal dependencies.
Why SDK Choice Matters
AI SDKs aren't just HTTP wrappers. They solve real problems: streaming implementation, retry logic with exponential backoff, context window management, structured output parsing, tool use orchestration, and prompt templating. Writing this functionality correctly takes weeks. Maintaining it as APIs evolve takes ongoing engineering time. Using a mature SDK trades dependency weight for engineering velocity.
The tradeoff: SDKs add abstraction layers that can obscure what's happening, introduce performance overhead, and create vendor lock-in. Lightweight SDKs minimize these costs but provide less functionality. Heavy SDKs like LangChain provide comprehensive features but with complexity costs. The right choice depends on your use case—simple chatbots need simple SDKs, complex agent workflows justify complex frameworks.
TypeScript support matters more than developers initially realize. LLM APIs return complex nested structures. Without strong typing, you're writing defensive code that validates every field, or you're writing fragile code that crashes when API responses change. TypeScript SDKs with auto-generated types from API schemas catch errors at compile time instead of runtime.
The Hidden Cost of Rolling Your Own
Building AI integrations without SDKs seems simple initially: make HTTP POST requests, parse JSON responses. Then you need streaming (implement SSE or ReadableStream parsing). Then you need retries (implement exponential backoff, but not for rate limits, those need different handling). Then you need context window management (count tokens, truncate history). Then you need structured outputs (validate against schemas, handle parsing errors).
Six months later, you've built 2,000 lines of AI infrastructure code that's undertested, incompletely documented, and doesn't handle edge cases well. A mature SDK provides this in 20 lines. The engineering time spent building and maintaining custom infrastructure could have been spent building features users care about.
Vercel AI SDK: React-First Streaming Focus
The Vercel AI SDK (previously called AI SDK or Vercel AI) focuses on streaming LLM responses into React applications with minimal boilerplate. It provides useChat and useCompletion hooks that handle streaming state, error recovery, and message history management. For teams building chat interfaces in Next.js or React, it's the fastest path from zero to working prototype.
Core Features and Architecture
The SDK's value proposition is simplicity. The useChat hook abstracts away streaming implementation, message state management, and loading indicators. You get a working chat interface in 10 lines of code. The backend helpers (StreamingTextResponse, OpenAIStream) transform provider streams into a universal format.
// Frontend: Using useChat hook
import { useChat } from 'ai/react';
export function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
{messages.map(m => (
{m.role}: {m.content}
))}
);
}
// Backend: API route (Next.js App Router)
import { OpenAIStream, StreamingTextResponse } from 'ai';
import OpenAI from 'openai';
export async function POST(req: Request) {
const { messages } = await req.json();
const openai = new OpenAI();
const response = await openai.chat.completions.create({
model: 'gpt-4-turbo',
stream: true,
messages
});
const stream = OpenAIStream(response);
return new StreamingTextResponse(stream);
}
The abstraction quality is high for the happy path. Error handling, retry logic, and streaming edge cases are handled automatically. TypeScript support is strong—types are inferred from your API route, catching type mismatches at compile time. Integration with Next.js is seamless because Vercel builds both.
Provider Coverage
The SDK supports OpenAI, Anthropic, Cohere, Hugging Face, Replicate, and custom providers through adapter patterns. Adding new providers requires implementing a stream transformation function. This works well for standard text generation but struggles with provider-specific features like Claude's extended thinking or GPT-4's vision capabilities.
// Multiple provider support
import { AnthropicStream, StreamingTextResponse } from 'ai';
import Anthropic from '@anthropic-ai/sdk';
export async function POST(req: Request) {
const { messages } = await req.json();
const anthropic = new Anthropic();
const response = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
messages,
stream: true
});
const stream = AnthropicStream(response);
return new StreamingTextResponse(stream);
}
When Vercel AI SDK is Optimal
Use Vercel AI SDK when building chat interfaces in Next.js or React. The framework integration and streaming abstractions save weeks of development time. The hook-based API feels native to React patterns. TypeScript support is excellent. For simple to medium-complexity chat applications, it's the fastest development path.
Limitations and Tradeoffs
The SDK is opinionated about architecture: React hooks on frontend, streaming API routes on backend. If you're building non-React frontends (Vue, Svelte, vanilla JS), the hooks don't help. If you're not using streaming, the SDK's value proposition weakens—you're carrying dependency weight for features you don't use.
Complex workflows like agents, tool use orchestration, or multi-step reasoning chains aren't the SDK's focus. It provides building blocks but not high-level abstractions. For these use cases, LangChain offers more sophisticated patterns at the cost of increased complexity.
| SDK | Best For | TypeScript Support | Bundle Size |
|---|---|---|---|
| Vercel AI SDK | React chat, Next.js integration, streaming | Excellent | ~50KB |
| LangChain.js | Complex workflows, agents, RAG | Good | ~500KB |
| OpenAI SDK | OpenAI-only, full API coverage | Excellent | ~30KB |
| Anthropic SDK | Claude-only, tool use, extended thinking | Excellent | ~25KB |
LangChain.js: Framework for Complex AI Applications
LangChain is less an SDK and more a framework for building AI applications. It provides abstractions for chains (sequential LLM calls), agents (LLMs that choose tools), retrieval (RAG patterns), and memory (conversation persistence). For applications beyond simple chat—document analysis, automated workflows, conversational agents—LangChain provides building blocks that would take months to build from scratch.
Architecture and Core Concepts
LangChain's architecture centers on chains: composable units that transform inputs to outputs. A simple chain calls an LLM with a prompt. Complex chains combine LLM calls with retrieval, transformation, and routing logic. Agents are chains that decide which tools to use based on user input.
// Simple chain: Prompt template + LLM
import { ChatOpenAI } from '@langchain/openai';
import { PromptTemplate } from '@langchain/core/prompts';
const llm = new ChatOpenAI({ modelName: 'gpt-4-turbo' });
const template = new PromptTemplate({
template: 'Translate the following text to {language}: {text}',
inputVariables: ['language', 'text']
});
const chain = template.pipe(llm);
const result = await chain.invoke({
language: 'Spanish',
text: 'Hello, how are you?'
});
// RAG chain: Retrieval + LLM generation
import { RetrievalQAChain } from 'langchain/chains';
import { OpenAIEmbeddings } from '@langchain/openai';
import { PineconeStore } from '@langchain/pinecone';
const embeddings = new OpenAIEmbeddings();
const vectorStore = await PineconeStore.fromExistingIndex(embeddings, {
pineconeIndex: index,
namespace: 'documents'
});
const chain = RetrievalQAChain.fromLLM(llm, vectorStore.asRetriever());
const answer = await chain.call({
query: 'What is the return policy?'
});
The RAG chain retrieves relevant documents from the vector store, constructs a prompt with retrieved context, and generates an answer. This pattern is common in production AI applications, and LangChain's abstraction eliminates boilerplate. Implementing this pattern manually requires vector search logic, prompt construction, context window management, and error handling.
Agent Patterns
LangChain's agent support enables LLMs that choose and execute tools. You define tools (functions the LLM can call), provide descriptions, and the agent framework handles tool selection, execution, and result incorporation into the conversation.
// Agent with tools
import { ChatOpenAI } from '@langchain/openai';
import { DynamicStructuredTool } from '@langchain/core/tools';
import { AgentExecutor, createOpenAIFunctionsAgent } from 'langchain/agents';
import { ChatPromptTemplate } from '@langchain/core/prompts';
// Define tools
const calculator = new DynamicStructuredTool({
name: 'calculator',
description: 'Performs basic arithmetic operations',
schema: z.object({
operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
a: z.number(),
b: z.number()
}),
func: async ({ operation, a, b }) => {
switch (operation) {
case 'add': return (a + b).toString();
case 'subtract': return (a - b).toString();
case 'multiply': return (a * b).toString();
case 'divide': return (a / b).toString();
}
}
});
const searchTool = new DynamicStructuredTool({
name: 'web_search',
description: 'Searches the web for information',
schema: z.object({
query: z.string().describe('Search query')
}),
func: async ({ query }) => {
// Implement search logic
const results = await searchWeb(query);
return JSON.stringify(results);
}
});
// Create agent
const llm = new ChatOpenAI({ modelName: 'gpt-4-turbo' });
const prompt = ChatPromptTemplate.fromMessages([
['system', 'You are a helpful assistant with access to tools.'],
['human', '{input}'],
['placeholder', '{agent_scratchpad}']
]);
const agent = await createOpenAIFunctionsAgent({
llm,
tools: [calculator, searchTool],
prompt
});
const executor = new AgentExecutor({
agent,
tools: [calculator, searchTool]
});
const result = await executor.invoke({
input: 'What is 25 * 37, and what is the weather in San Francisco?'
});
The agent decomposes the request, recognizes it needs both calculator and search tools, executes both, and synthesizes a response. Building this orchestration manually is complex—LangChain's agent framework handles the difficult parts (tool selection, error handling, result formatting).
When LangChain is Appropriate
LangChain shines for applications that need more than simple LLM calls: RAG systems that combine retrieval with generation, agents that use tools to complete tasks, complex multi-step workflows where LLM outputs feed into subsequent processing, and applications that need memory across sessions. For these use cases, LangChain's abstractions save significant development time.
Complexity Costs
LangChain is heavy. The bundle size is 500KB+. The learning curve is steep—understanding chains, agents, memory types, and callbacks takes time. The abstraction layers obscure what's happening, making debugging harder. For simple use cases, LangChain is overkill. For complex use cases, the complexity is justified by the functionality provided.
The API surface is large and changes frequently. LangChain is evolving rapidly, and breaking changes between versions are common. Budget for maintenance—upgrades aren't always smooth. The framework is powerful but opinionated. If your use case doesn't fit LangChain's patterns well, fighting the framework creates more work than building custom solutions.
Provider-Specific SDKs: OpenAI and Anthropic
Official SDKs from LLM providers offer complete API coverage, excellent TypeScript support, and first-party maintenance. They lack abstraction across providers but provide the deepest integration with provider-specific features. For applications committed to a single provider, official SDKs often provide the best development experience.
OpenAI SDK
OpenAI's official JavaScript SDK covers the complete API: chat completions, embeddings, fine-tuning, assistants, and DALL-E. TypeScript support is excellent—types are generated from OpenAPI schemas, ensuring compile-time safety. The SDK handles streaming, retries, and error handling well.
// OpenAI SDK usage
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
// Chat completion
const completion = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Explain quantum computing' }
],
max_tokens: 1000,
temperature: 0.7
});
// Streaming
const stream = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages: messages,
stream: true
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
process.stdout.write(content);
}
}
// Function calling
const functionResult = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages: messages,
tools: [
{
type: 'function',
function: {
name: 'get_weather',
description: 'Get current weather for a location',
parameters: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'City and state, e.g. San Francisco, CA'
}
},
required: ['location']
}
}
}
]
});
The SDK's value is completeness and reliability. Every OpenAI API feature is supported. Updates for new features arrive quickly. Error handling is robust. For applications using only OpenAI, the SDK provides everything needed without third-party abstractions.
Anthropic SDK
Anthropic's SDK mirrors OpenAI's design but includes Claude-specific features like prompt caching and extended thinking. TypeScript support is equally strong. The API design is cleaner in some ways—streaming implementation is more intuitive, tool use syntax is simpler.
// Anthropic SDK usage
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY
});
// Basic completion
const message = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
messages: [
{ role: 'user', content: 'Explain machine learning' }
]
});
// Streaming
const stream = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 4096,
messages: messages,
stream: true
});
for await (const event of stream) {
if (event.type === 'content_block_delta') {
process.stdout.write(event.delta.text);
}
}
// Tool use
const toolResponse = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1024,
tools: [
{
name: 'get_weather',
description: 'Get weather for a location',
input_schema: {
type: 'object',
properties: {
location: { type: 'string' }
},
required: ['location']
}
}
],
messages: messages
});
// Prompt caching
const cachedResponse = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1024,
system: [
{
type: 'text',
text: largeSystemPrompt,
cache_control: { type: 'ephemeral' }
}
],
messages: messages
});
Anthropic's SDK is younger but maturing quickly. It lacks some convenience features of OpenAI's SDK (no built-in retry configuration, less sophisticated error types) but covers the API comprehensively. For Claude-only applications, it's the natural choice.
Lightweight Alternatives: Simple HTTP Wrappers
For developers who want minimal dependencies or need maximum control, lightweight HTTP wrappers provide thin abstractions over API calls without complex frameworks. These are appropriate when you understand the APIs well, have simple use cases, or want to avoid dependency weight.
Building Your Own Thin Wrapper
// Minimal AI SDK implementation
interface LLMRequest {
model: string;
messages: Array<{ role: string; content: string }>;
maxTokens?: number;
temperature?: number;
}
interface LLMResponse {
content: string;
usage: {
inputTokens: number;
outputTokens: number;
};
}
class SimpleLLMClient {
constructor(
private apiKey: string,
private baseUrl: string
) {}
async chat(request: LLMRequest): Promise {
const response = await fetch(`${this.baseUrl}/chat`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const data = await response.json();
return {
content: data.choices[0].message.content,
usage: {
inputTokens: data.usage.prompt_tokens,
outputTokens: data.usage.completion_tokens
}
};
}
async *stream(request: LLMRequest): AsyncGenerator {
const response = await fetch(`${this.baseUrl}/chat`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ ...request, stream: true })
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (!reader) throw new Error('No stream reader');
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
// Parse SSE format and yield content
const lines = chunk.split('\n').filter(l => l.startsWith('data: '));
for (const line of lines) {
const data = JSON.parse(line.slice(6));
if (data.choices[0]?.delta?.content) {
yield data.choices[0].delta.content;
}
}
}
}
}
// Usage
const client = new SimpleLLMClient(
process.env.API_KEY!,
'https://api.openai.com/v1'
);
const response = await client.chat({
model: 'gpt-4-turbo',
messages: [{ role: 'user', content: 'Hello!' }]
});
This wrapper provides basic functionality in ~100 lines. It handles authentication, request formatting, response parsing, and streaming. What it lacks: retry logic, error handling sophistication, type safety for specific models, and convenience features. For simple use cases, this simplicity is valuable. For complex applications, reinventing these features wastes time.
Decision Framework: Choosing the Right SDK
Match SDK complexity to use case complexity. Simple chat applications don't need LangChain. Complex agent workflows justify LangChain's complexity. Consider these factors when choosing.
Use Case Matrix
Simple chat interface (single provider): Use that provider's official SDK (OpenAI or Anthropic). TypeScript support is excellent, API coverage is complete, maintenance is first-party.
React chat interface with streaming: Use Vercel AI SDK. The useChat hook and streaming abstractions save weeks of development. Next.js integration is seamless.
Multi-provider chat (routing based on cost/features): Use Vercel AI SDK with provider adapters, or build a thin abstraction layer over official SDKs. Avoid LangChain unless you need its other features.
RAG application (retrieval + generation): Use LangChain if you need pre-built vector store integrations and retrieval chains. Use official SDKs + vector database SDKs if you want more control.
Agent with tool use: Use LangChain's agent framework if your agent logic is complex (multiple tools, branching decisions). Use official SDKs if tool orchestration is simple (1-2 tools, straightforward logic).
Custom LLM deployment: Build a thin wrapper or use LangChain's extensibility. Official SDKs only support their providers. Vercel AI SDK can be extended with custom providers.
| Use Case | Recommended SDK | Alternative |
|---|---|---|
| React chat + streaming | Vercel AI SDK | Provider SDK + custom hook |
| RAG system | LangChain.js | Provider SDK + vector DB SDK |
| Complex agents | LangChain.js | Custom orchestration + provider SDK |
| Simple API integration | Provider SDK (OpenAI/Anthropic) | Custom HTTP wrapper |
| Multi-provider routing | Custom abstraction layer | LangChain or Vercel AI SDK |
Integration Patterns: Combining SDKs
Production applications often combine multiple SDKs: Vercel AI SDK for frontend streaming, provider SDKs for backend API calls, LangChain for specific complex workflows. Design abstraction layers that let different parts of your stack use appropriate tools.
Hybrid Architecture Example
// Backend: Use provider SDK for control
import OpenAI from 'openai';
import { OpenAIStream, StreamingTextResponse } from 'ai';
const openai = new OpenAI();
export async function POST(req: Request) {
const { messages, useAdvancedFeatures } = await req.json();
if (useAdvancedFeatures) {
// Complex logic with provider SDK
const response = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages,
tools: customTools,
tool_choice: 'auto'
});
// Handle tool calls
if (response.choices[0].message.tool_calls) {
// Custom tool execution logic
const toolResults = await executeTools(
response.choices[0].message.tool_calls
);
// Continue conversation with tool results
return continueWithToolResults(messages, toolResults);
}
return Response.json(response);
} else {
// Simple streaming with Vercel AI SDK
const stream = await openai.chat.completions.create({
model: 'gpt-4-turbo',
messages,
stream: true
});
return new StreamingTextResponse(OpenAIStream(stream));
}
}
// Frontend: Always use Vercel AI SDK hooks
import { useChat } from 'ai/react';
export function ChatInterface() {
const { messages, input, handleSubmit, handleInputChange } = useChat({
api: '/api/chat',
body: {
useAdvancedFeatures: shouldUseAdvancedFeatures()
}
});
return ;
}
This architecture uses the right tool for each job: Vercel AI SDK for streaming and React state management, provider SDK for complex tool orchestration. The frontend remains simple, complexity stays in the backend where it's easier to test and maintain.
Frequently Asked Questions
Should I use multiple SDKs in the same project?
Yes, when appropriate. Use Vercel AI SDK for React streaming, provider SDKs for backend flexibility, and LangChain for specific complex workflows. Different tools excel at different tasks. Design abstractions that let each part of your stack use optimal tools without tight coupling.
How do I handle SDK version updates?
Pin major versions in package.json and test thoroughly before upgrading. LangChain in particular has frequent breaking changes. Provider SDKs (OpenAI, Anthropic) are more stable but still evolve. Subscribe to changelogs, review breaking changes, and budget time for upgrades.
Are AI SDKs suitable for server-side rendering?
Provider SDKs work fine in SSR—they're just HTTP clients. Vercel AI SDK's hooks are client-only. LangChain can run in SSR contexts but streaming requires client-side handling. For SSR, generate content server-side with provider SDKs and hydrate on client.
What about edge runtime support?
Vercel AI SDK works in edge runtimes (Vercel Edge, Cloudflare Workers). Provider SDKs mostly work but check Node.js API usage. LangChain has limited edge support due to Node.js dependencies. For edge deployments, prefer lightweight SDKs or custom implementations.
How do I test code using AI SDKs?
Mock SDK calls in tests—don't hit real APIs. Provider SDKs and Vercel AI SDK are easy to mock (stub the client object). LangChain is harder to mock due to complex abstractions. Consider integration tests against real APIs for critical flows, unit tests with mocks for everything else.
Can I use AI SDKs in React Native?
Provider SDKs work in React Native with polyfills for Node.js APIs. Vercel AI SDK's hooks don't work (they depend on fetch streams). Build custom hooks using provider SDKs directly. LangChain has limited React Native support.
What about bundle size for client-side usage?
Keep AI SDKs server-side when possible. Provider SDKs are 25-30KB, acceptable if needed client-side. LangChain is 500KB+, too large for client bundles. Vercel AI SDK is 50KB, reasonable for streaming hooks. Always tree-shake and code-split AI code.
How do I handle rate limiting across SDKs?
Implement rate limiting in your backend, not in SDKs. Provider SDKs return rate limit info in headers. Build a rate limiting layer that tracks usage across all SDK calls and enforces limits before calling APIs. This centralizes rate limiting logic.
Should I use SDK convenience methods or direct API calls?
Use SDK methods for common operations—they handle edge cases you'll miss. Use direct API calls for features the SDK doesn't support yet. Most SDKs provide escape hatches (raw request methods) when needed.
How do I migrate between AI SDKs?
Build abstraction layers from day one. Don't call SDK methods directly from business logic. Create wrapper functions that isolate SDK-specific code. Migrating SDKs means updating wrappers, not refactoring application code. This discipline pays off when switching or upgrading.
Conclusion
The best AI SDK depends on your use case. For React chat interfaces, Vercel AI SDK provides unmatched developer experience. For complex workflows with agents and RAG, LangChain provides abstractions that save months of development time. For single-provider applications needing full API coverage, official provider SDKs offer completeness and stability. For simple use cases or custom requirements, lightweight wrappers minimize dependency weight.
Start simple and upgrade when needed. Use provider SDKs for initial prototypes. Add Vercel AI SDK when building React interfaces. Adopt LangChain only when simple SDKs become limiting. Each migration step is easier than starting with maximum complexity. Design abstraction layers that isolate SDK dependencies from business logic—this future-proofs your architecture and simplifies future migrations.