Skip to content

AI Framework Comparison: Vercel AI SDK, Mastra, Langchain and Genkit

17 min

How can a developer choose one framework from a few similar ones? Popularity is a useful signal - a larger community means more answered questions, more examples, and a lower bus factor. But it doesn't tell you much about how pleasant the framework is to work with.

I decided to battle-test Vercel AI SDK (27.8k ⭐), Mastra (29.4k ⭐), Langchain.js (18.3k ⭐) and Google's Genkit (6.6k ⭐) by developing a few code examples with them all (GitHub stars as of November 2026).

Here I'll show you the examples and share my observations.

Quick verdict (examples retested in November 2026 with AI SDK 7.0, Mastra 1.82, Langchain.js 1.6 and Genkit 1.44 on Claude Sonnet 5.5): I like Vercel AI SDK most for its functional simplicity and docs. Mastra is a close second with its clean agent abstraction and built-in model routing. Langchain.js requires more boilerplate, and Genkit has a solid plugin architecture but complex types.

Comparison My pick Why
Vercel AI SDK vs Langchain.js Vercel AI SDK Simpler calls and less result processing. Langchain.js needs more boilerplate.
Mastra vs Langchain.js Mastra Cleaner tool integration and built-in model routing. Langchain.js needs more code to process results.
Vercel AI SDK vs Mastra Vercel AI SDK, narrowly Simpler functional calls. Pick Mastra if you want tools declared once on an Agent.
Genkit vs Langchain.js Depends Genkit has plugins and bundles Zod, but its types feel complex. Langchain.js needs more boilerplate.

ToC

Example 1: Simple question

Vercel AI SDK

import 'dotenv/config'
import { anthropic } from '@ai-sdk/anthropic'
import { generateText, isStepCount } from 'ai'

const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  instructions: "You are Amy, a friendly assistant, who you can chat with about everyday stuff.",
  prompt: "What's your name?",
  stopWhen: isStepCount(1),
})

console.log(result.text)
// I'm Amy! I'm here to chat about whatever's on your mind...

Mastra

import { Agent } from '@mastra/core/agent'

const agent = new Agent({
  id: 'amy',
  name: 'Amy',
  instructions: "You are Amy, a friendly assistant, who you can chat with about everyday stuff.",
  model: 'anthropic/claude-sonnet-5-5',
})

const result = await agent.generate("What's your name?")

console.log(result.text)

Langchain.js

import 'dotenv/config'
import { ChatAnthropic } from '@langchain/anthropic'
import {
  BaseMessage,
  HumanMessage,
  SystemMessage,
} from '@langchain/core/messages'

const ai = new ChatAnthropic({
  model: 'claude-sonnet-5-5',
})

const messages: BaseMessage[] = [
  new SystemMessage("You are Amy, a friendly assistant, who you can chat with about everyday stuff."),
  new HumanMessage("What's your name?"),
]

const response = await ai.invoke(messages)

console.log(response.text)

Genkit

import 'dotenv/config'
import { genkit } from 'genkit'
import { anthropic } from '@genkit-ai/anthropic'

const ai = genkit({
  plugins: [anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })],
  model: anthropic.model('claude-sonnet-5-5'),
})

const result = await ai.generate({
  system: "You are Amy, a friendly assistant, who you can chat with about everyday stuff.",
  prompt: "What's your name?",
  maxTurns: 1,
})

console.log(result.text)

Observations

  • Vercel AI SDK and Mastra LLM calls look the cleanest. Vercel is a single function call; Mastra requires instantiating an Agent object but keeps the generate call simple.

  • Mastra uses a provider-prefixed model string ('anthropic/claude-sonnet-5-5') for built-in model routing, so no separate provider package is needed.

  • I like SystemMessage and HumanMessage OOP message wrappers in Langchain which are useful for more complex cases. However, for simple cases like the ones above, the prompt and instructions/system fields work better for me. (Vercel AI SDK 7 renamed its system param to instructions, matching Mastra.)

  • Genkit param/return types made me think that they are overly complex.

  • Claude Sonnet 5.5 rejects non-default temperature values, so the examples don't set it. Vercel AI SDK drops the param with a warning, while Langchain.js throws an error.

  • With Sonnet 5.5, Langchain.js returns content blocks (thinking and text) instead of a plain string, so I print response.text rather than response.content.

  • Langchain.js is a port of the Python LangChain library, which was originally launched in October 2022 (Python version) with the JavaScript version following in January 2023. This heritage may explain some of the framework's design choices and verbosity compared to JavaScript-native alternatives.

Example 2: Creating a tool

Vercel AI SDK

import { tool } from 'ai'
import { z } from 'zod'

export const temperatureTool = tool({
  description: 'Gets current temperature in the given city',
  inputSchema: z.object({
    city: z.string().describe('The city to get the current temperature for'),
  }),
  execute: async ({ city: _city }) => {
    try {
      const min = -10
      const max = 40
      const temperature = (Math.random() * (max - min) + min).toFixed(0)

      return `${temperature}°C`
    } catch (error: any) {
      return { error: error.message }
    }
  },
})

Mastra

import { createTool } from '@mastra/core/tools'
import { z } from 'zod'

export const temperatureTool = createTool({
  id: 'temperature',
  description: 'Gets current temperature in the given city',
  inputSchema: z.object({
    city: z.string().describe('The city to get the current temperature for'),
  }),
  outputSchema: z.object({
    temperature: z.string(),
  }),
  execute: async ({ city: _city }) => {
    try {
      const min = -10
      const max = 40
      const temperature = (Math.random() * (max - min) + min).toFixed(0)

      return { temperature: `${temperature}°C` }
    } catch (error: any) {
      return { temperature: error.message }
    }
  },
})

Langchain.js

import { tool } from '@langchain/core/tools'
import { z } from 'zod'

export const temperatureTool = tool(
  async ({ city: _city }) => {
    try {
      const min = -10
      const max = 40
      const temperature = (Math.random() * (max - min) + min).toFixed(0)

      return `${temperature}°C`
    } catch (error: any) {
      return error.message
    }
  },
  {
    name: 'temperature',
    description: 'Gets current temperature in the given city',
    schema: z.object({
      city: z.string().describe('The city to get the current temperature for'),
    }),
    responseFormat: 'content',
  }
)

Genkit

import { Genkit, z } from 'genkit'

export const createTemperatureTool = (ai: Genkit) => {
  return ai.defineTool(
    {
      name: 'temperature',
      description: 'Gets current temperature in the given city',
      inputSchema: z.object({
        city: z
          .string()
          .describe('The city to get the current temperature for'),
      }),
      outputSchema: z.string(),
    },
    async ({ city: _city }) => {
      try {
        const min = -10
        const max = 40
        const temperature = (Math.random() * (max - min) + min).toFixed(0)

        return `${temperature}°C`
      } catch (error: any) {
        return error.message
      }
    }
  )
}

Observations

  • Unlike Vercel AI SDK and Langchain, Genkit package exports Zod lib, which means you don't need to install and import it separately.

  • Mastra requires both inputSchema and outputSchema, and its tool returns a typed object rather than a raw string. More verbose, but also more structured.

  • Unlike Genkit and Langchain, Vercel AI SDK doesn't let you set the output format.

  • Unfortunately, Genkit tools require the main Genkit object, so I used a wrapper function to define the tool.

Example 3: Simple question with a tool call

Vercel AI SDK

import 'dotenv/config'
import { anthropic } from '@ai-sdk/anthropic'
import { generateText, isStepCount } from 'ai'
import { temperatureTool } from './tools/temperature'

const result = await generateText({
  model: anthropic('claude-sonnet-5-5'),
  instructions: "You are Amy, a friendly assistant, who you can chat with about everyday stuff.",
  prompt: "What's the temperature in New York?",
  // Plug the tool here.
  tools: { temperature: temperatureTool },
  stopWhen: isStepCount(2),
})

console.log(result.text)

Mastra

import { Agent } from '@mastra/core/agent'
import { temperatureTool } from './tools/temperature'

const agent = new Agent({
  id: 'amy',
  name: 'Amy',
  instructions: "You are Amy, a friendly assistant, who you can chat with about everyday stuff.",
  model: 'anthropic/claude-sonnet-5-5',
  // Plug the tool here.
  tools: { temperature: temperatureTool },
})

const result = await agent.generate("What's the temperature in New York?")

console.log(result.text)

Langchain.js

import 'dotenv/config'
import { ChatAnthropic } from '@langchain/anthropic'
import { BaseMessage, HumanMessage, ToolMessage } from '@langchain/core/messages'
import { createAgent } from 'langchain'
import { temperatureTool } from './tools/temperature'

const ai = new ChatAnthropic({
  model: 'claude-sonnet-5-5',
})

const agent = createAgent({
  model: ai,
  // Plug the tool here.
  tools: [temperatureTool],
  systemPrompt: "You are Amy, a friendly assistant, who you can chat with about everyday stuff.",
})

const messages: BaseMessage[] = [
  new HumanMessage("What's the temperature in New York?"),
]

const result = await agent.invoke({
  messages,
})

for (let i = messages.length; i < result.messages.length; i++) {
  const message = result.messages[i]
  if (ToolMessage.isInstance(message) || !message.text) {
    continue
  }

  console.log(message.text)
}

Genkit

import 'dotenv/config'
import { genkit } from 'genkit'
import { anthropic } from '@genkit-ai/anthropic'
import { createTemperatureTool } from './tools/temperature'

const ai = genkit({
  plugins: [anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })],
  model: anthropic.model('claude-sonnet-5-5'),
})

const temperatureTool = createTemperatureTool(ai)

const result = await ai.generate({
  // Plug the tool here.
  tools: [temperatureTool],
  system: "You are Amy, a friendly assistant, who you can chat with about everyday stuff.",
  prompt: "What's the temperature in New York?",
  maxTurns: 2,
})

console.log(result.text)

Observations

  • Mastra's tool integration is the cleanest - tools are declared on the Agent constructor and the generate call stays the same with or without tools.

  • For Vercel AI SDK and Genkit, I needed to allow at least two iterations for the LLM to be able to call the tool. Mastra and Langchain handle this automatically.

  • For the Langchain.js example, I had to use a createAgent primitive from the langchain package because manual tool calling without it was a pain.

  • Mastra uses some low-level primitives from Vercel AI SDK under the hood for its LLM integration.

  • Genkit's Claude support now comes from the official @genkit-ai/anthropic plugin maintained by Google, which replaced the community genkitx-anthropic package that stopped at Claude 4.5. With it, anthropic.model('claude-sonnet-5-5') works without workarounds.

  • As you may see, processing of the results for the Langchain.js example is more verbose. Let me know if you know how to improve that.

More examples

You may find an interactive chat with memory and a tool enabled along with other examples in the kometolabs/ai-sdk-comparison repo. Feel free to submit a PR if you know how to improve the examples.

If you're curious about Genkit's prompt files, I covered them in Prompts are Code with dotPrompt and Genkit.

Conclusions

To sum up, I like the functional simplicity and docs of Vercel AI SDK. Mastra is a close second and stands out with its clean agent abstraction and built-in model routing - and it has overtaken Vercel AI SDK in GitHub stars, even though Vercel's simplicity still wins for me. Langchain requires more boilerplate and result processing, though the createAgent API from the langchain package is a step in the right direction. Genkit trails in popularity but has a solid plugin-based architecture.

As one wise person said, "healthy competition keeps everybody sharp and gives the community more choice". I don't agree with the "more choice" part, because with more choice, developers need to spend time on learning the differences which they could have spent on developing features for their users. So I'd like to encourage framework builders to collaborate more and compete less.

Relevant Posts