# Vercel AI SDK

The [Vercel AI SDK](https://ai-sdk.dev) is the standard way to call models from
TypeScript, React and Next.js. Use the **openai-compatible** provider, not the
OpenAI one.

```bash
npm install ai @ai-sdk/openai-compatible zod
export HYPHEN_API_KEY="sk-..."
```

*Setup guide: this config comes from the Vercel AI SDK's official
documentation and has not been run end to end against the gateway.
Corrections to [support@hyphen-solution.com](mailto:support@hyphen-solution.com).*

## The provider

```typescript
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";

const hyphen = createOpenAICompatible({
  name: "hyphen",
  baseURL: "https://api.hyphen-solution.com/v1",
  apiKey: process.env.HYPHEN_API_KEY!,
  includeUsage: true,
});

const model = hyphen("minimax-m3");
```

:::warning[Do not reach for `@ai-sdk/openai` first]
`@ai-sdk/openai` is built for OpenAI's own service, and since AI SDK 5 its
default `openai('model-id')` call targets the **Responses API**. Hyphen serves
Chat Completions at `/v1/chat/completions`, so the default call shape can miss.

If you must use that package, `.chat()` is mandatory:

```typescript
import { createOpenAI } from "@ai-sdk/openai";

const openai = createOpenAI({
  baseURL: "https://api.hyphen-solution.com/v1",
  apiKey: process.env.HYPHEN_API_KEY!,
});

const model = openai.chat("minimax-m3"); // .chat() is not optional here
```

`createOpenAICompatible` avoids the whole question. Use it.
:::

## Generate text

```typescript
import { generateText } from "ai";

const { text, usage } = await generateText({
  model,
  prompt: "Explain a Bloom filter in three sentences.",
  maxOutputTokens: 2000,
});

console.log(text);
console.log(usage);
```

:::note[It is `maxOutputTokens`, not `maxTokens`]
The rename landed in AI SDK 5 and stuck. Snippets older than that use
`maxTokens`, which is silently ignored on current versions, so you get the
default budget and occasionally an empty string.
:::

## Stream text

```typescript
import { streamText } from "ai";

const result = streamText({
  model,
  prompt: "Invent a new holiday and describe its traditions.",
  maxOutputTokens: 2000,
});

for await (const part of result.textStream) {
  process.stdout.write(part);
}
```

Note `streamText` is not awaited. It returns immediately and you consume the
stream.

## Tool calling

```typescript
import { generateText, tool, isStepCount } from "ai";
import { z } from "zod";

const { text } = await generateText({
  model,
  tools: {
    weather: tool({
      description: "Get the weather in a location",
      inputSchema: z.object({
        location: z.string().describe("The location to get the weather for"),
      }),
      execute: async ({ location }) => ({
        location,
        temperature: 31,
        conditions: "thunderstorms",
      }),
    }),
  },
  stopWhen: isStepCount(5),
  maxOutputTokens: 4000,
  prompt: "What is the weather in Kuala Lumpur?",
});

console.log(text);
```

Two names that changed and will bite anyone copying older snippets:

- **`inputSchema`**, not `parameters`, on `tool()`.
- **`isStepCount(n)`** in AI SDK 7. It was `stepCountIs(n)` in versions 5 and 6.

`stopWhen` is what turns a single call into an agent loop. Without it the SDK
stops after the first tool call instead of feeding the result back.

## Structured output

`generateObject` asks the provider for a JSON schema by default, which the
M-series do not support. Pass `output: 'tool'`, or use tool calling directly.

```typescript
import { generateObject } from "ai";
import { z } from "zod";

const { object } = await generateObject({
  model,
  schema: z.object({
    title: z.string(),
    severity: z.enum(["low", "medium", "high", "critical"]),
    component: z.string(),
  }),
  mode: "tool",
  maxOutputTokens: 4000,
  prompt: "The CSV exporter drops the last row on 2.4.1.",
});

console.log(object);
```

If `mode: "tool"` is rejected by your version, fall back to the explicit
`generateText` plus `tool()` pattern above, which is the stable one. See
[Structured JSON output](/recipes/structured-output).

## In a Next.js route handler

```typescript
// app/api/chat/route.ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
import { streamText, convertToModelMessages } from "ai";

const hyphen = createOpenAICompatible({
  name: "hyphen",
  baseURL: "https://api.hyphen-solution.com/v1",
  apiKey: process.env.HYPHEN_API_KEY!,
});

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: hyphen("minimax-m3"),
    messages: convertToModelMessages(messages),
    maxOutputTokens: 4000,
  });

  return result.toUIMessageStreamResponse();
}
```

Keep `HYPHEN_API_KEY` server-side. Never expose it to the browser, and never
prefix it with `NEXT_PUBLIC_`.

## Gotchas

- **`maxOutputTokens`, not `maxTokens`.** 2000 for chat, 4000 for tools.
  See [Choosing a model](/choosing-a-model#the-max_tokens-gotcha).
- **`inputSchema`, not `parameters`.**
- **`isStepCount`, not `stepCountIs`,** on AI SDK 7.
- **`@ai-sdk/openai` defaults to the Responses API.** Use
  `createOpenAICompatible`, or `.chat()`.
- **No embedding models.** `embed` and `embedMany` need another provider.
- **`429`** means the monthly budget is spent. See
  [Handling the 429 cap](/recipes/handling-429).

## Related

- [OpenAI-compatible provider docs](https://ai-sdk.dev/providers/openai-compatible-providers)
- [Tool calling docs](https://ai-sdk.dev/docs/ai-sdk-core/tools-and-tool-calling)
- [Mastra](/build/mastra): agents and workflows on top of this.
- [OpenAI Node SDK](/build/openai-sdk): no framework at all.
