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

KonstantinKonstantin

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 (22.5k ⭐), Mastra (21.9k ⭐), Langchain.js (17.2k ⭐) and Firebase Genkit (5.6k ⭐) by developing a few code examples with them all.

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

ToC

Example 1: Simple question

Vercel AI SDK

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

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

console.log(result.text)
// Hi! I'm Amy. Nice to meet you! How are you today?

Mastra

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

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

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-4-6',
  temperature: 0,
})

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.content)

Firebase Genkit

import 'dotenv/config'
import { genkit } from 'genkit'
import { anthropic, claude45Sonnet } from 'genkitx-anthropic'

const ai = genkit({
  plugins: [anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })],
  model: claude45Sonnet,
})

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,
  config: {
    version: 'claude-sonnet-4-6',
    temperature: 0,
  },
})

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-4-6') 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/system fields or instructions work better for me.

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

  • 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',
  }
)

Firebase 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, Firebase 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 Firebase 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, stepCountIs } from 'ai'
import { temperatureTool } from './tools/temperature'

const result = await generateText({
  model: anthropic('claude-sonnet-4-6'),
  system: "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: stepCountIs(2),
  temperature: 0,
})

console.log(result.text)

Mastra

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

const agent = new Agent({
  name: 'Amy',
  instructions: "You are Amy, a friendly assistant, who you can chat with about everyday stuff.",
  model: 'anthropic/claude-sonnet-4-6',
  // 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-4-6',
  temperature: 0,
})

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++) {
  if (
    ToolMessage.isInstance(result.messages[i]) ||
    typeof result.messages[i].content !== 'string'
  ) {
    continue
  }

  console.log(result.messages[i].content)
}

Firebase Genkit

import 'dotenv/config'
import { genkit } from 'genkit'
import { anthropic, claude45Sonnet } from 'genkitx-anthropic'
import { createTemperatureTool } from './tools/temperature'

const ai = genkit({
  plugins: [anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })],
  model: claude45Sonnet,
})

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,
  config: {
    version: 'claude-sonnet-4-6',
    temperature: 0,
  },
})

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 Firebase 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.

  • Unlike the other frameworks, Genkit doesn't feel fully provider-agnostic - the Claude adapter is third-party and lags behind (no support for Claude 4.6 yet).

  • 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.

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. Langchain requires more boilerplate and result processing, though the createAgent API from the langchain package is a step in the right direction. Firebase 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
Get in Touch with Konstantin

If you're looking for a serious developer for your project or have a collaboration idea in mind, let's talk.