deeprelayDocs
CH·07.1Integrations

Vercel AI SDK

Use deeprelay models from the Vercel AI SDK with the openai-compatible provider: generateText, streamText, a Next.js useChat route, and embeddings.

The Vercel AI SDK is the TypeScript toolkit behind most Next.js chat apps: generateText, streamText, embed, and the useChat React hook. deeprelay plugs into it through the official @ai-sdk/openai-compatible provider, because the deeprelay API speaks the OpenAI chat and embeddings protocol. No deeprelay-specific package, no adapter code: create a provider once, then pass deeprelay model ids anywhere the SDK takes a model.

This guide covers text generation, streaming, a Next.js chat route with useChat, tool calling, structured output, and embeddings. Everything runs on Node 20+, Bun, and Vercel's Node and Edge runtimes.

Vercel AI Gateway. If your app already routes through Vercel's AI Gateway, keep doing that for the models it lists and use the provider below for deeprelay models. The two coexist: both are plain AI SDK providers, and the SDK picks whichever one you pass as model.

Install

npm i ai @ai-sdk/openai-compatible

Put your key in the environment. The SDK never sees it in source and Vercel injects project environment variables at runtime.

export DEEPRELAY_API_KEY=deeprelay_live_...

Create the provider

Create the provider once and import it wherever you need a model. The name becomes the key under providerOptions. includeUsage asks deeprelay to append token counts to the final streaming chunk so the SDK's usage field is populated on streamed calls too, and supportsStructuredOutputs lets structured output send a JSON schema instead of falling back to prompted JSON.

// lib/deeprelay.ts
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";

export const deeprelay = createOpenAICompatible({
  name: "deeprelay",
  baseURL: "https://api.deeprelay.ai/v1",
  apiKey: process.env.DEEPRELAY_API_KEY,
  includeUsage: true,
  supportsStructuredOutputs: true,
});
Base URLhttps://api.deeprelay.ai/v1
AuthAuthorization: Bearer $DEEPRELAY_API_KEY (the provider sets it from apiKey)
Chat modelsdeeprelay("deeprelay/")
Embedding modelsdeeprelay.embeddingModel("deeprelay/")

Model ids are the ones GET /v1/models returns, for example deeprelay/deepseek-v4.1-flash or deeprelay/kimi-k3. See the Inference API for the catalog and tiers.

Generate text

import { generateText } from "ai";
import { deeprelay } from "./lib/deeprelay";

const { text, usage } = await generateText({
  model: deeprelay("deeprelay/deepseek-v4.1-flash"),
  system: "You answer in one sentence.",
  prompt: "What does a serverless inference relay do?",
});

console.log(text);
console.log(usage); // { inputTokens, outputTokens, totalTokens }

temperature, topP, maxOutputTokens, stopSequences and seed map straight onto the deeprelay request. Leave the rest at the SDK defaults.

Stream text

streamText returns the tokens as they arrive. deeprelay streams server-sent events in the OpenAI format, which is what the provider expects.

import { streamText } from "ai";
import { deeprelay } from "./lib/deeprelay";

const result = streamText({
  model: deeprelay("deeprelay/kimi-k3"),
  prompt: "Write a haiku about cold starts.",
});

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

const usage = await result.usage; // populated because includeUsage is on

Next.js chat route with useChat

The same shape as any AI SDK chat app: a route handler that streams, and a client component that renders the stream. Swap the model id for any chat model in the catalog.

// app/api/chat/route.ts
import { streamText, convertToModelMessages, type UIMessage } from "ai";
import { deeprelay } from "@/lib/deeprelay";

export const maxDuration = 60;

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

  const result = streamText({
    model: deeprelay("deeprelay/deepseek-v4.1-flash"),
    messages: convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}
// app/page.tsx
"use client";

import { useChat } from "@ai-sdk/react";
import { useState } from "react";

export default function Chat() {
  const [input, setInput] = useState("");
  const { messages, sendMessage, status } = useChat();

  return (
    <main>
      {messages.map((m) => (
        <p key={m.id}>
          <strong>{m.role}:</strong>{" "}
          {m.parts.map((p, i) => (p.type === "text" ? <span key={i}>{p.text}</span> : null))}
        </p>
      ))}

      <form
        onSubmit={(e) => {
          e.preventDefault();
          if (!input.trim()) return;
          sendMessage({ text: input });
          setInput("");
        }}
      >
        <input value={input} onChange={(e) => setInput(e.target.value)} placeholder="Say something" />
        <button type="submit" disabled={status !== "ready"}>Send</button>
      </form>
    </main>
  );
}

useChat keeps the message history and parses the stream, so the client needs no manual SSE handling. Install @ai-sdk/react if your project does not already have it.

Tool calling

Define tools with tool() and a Zod schema. The SDK sends them as OpenAI tools, deeprelay returns the model's tool_calls, and the SDK runs execute and feeds the result back. stopWhen: stepCountIs(n) lets the loop take several round trips before it has to answer.

import { generateText, tool, stepCountIs } from "ai";
import { z } from "zod";
import { deeprelay } from "./lib/deeprelay";

const { text, toolCalls, steps } = await generateText({
  model: deeprelay("deeprelay/deepseek-v4.1-flash"),
  tools: {
    getWeather: tool({
      description: "Get the current weather for a city",
      inputSchema: z.object({ city: z.string() }),
      execute: async ({ city }) => ({ city, tempC: 18, sky: "overcast" }),
    }),
  },
  stopWhen: stepCountIs(3),
  prompt: "What is the weather in Paris right now? Use the tool, then answer in one sentence.",
});

console.log(toolCalls.map((c) => c.toolName)); // ["getWeather"]
console.log(steps.length, text);               // 2 "The weather in Paris is overcast at 18°C."

streamText takes the same tools and stopWhen, and streams the tool calls and the final text as parts. Every chat model in the catalog with tools in its supported_parameters accepts this; the Serverless Inference guide has the wire-level detail and the tool_choice modes each model allows.

Structured output

Pass output: Output.object({ schema }) to ask for JSON that matches a Zod schema. With supportsStructuredOutputs on the provider, the SDK sends the schema as response_format: { type: "json_schema", strict: true } and deeprelay enforces it on the model side, so the result parses without a retry loop.

import { generateText, Output } from "ai";
import { z } from "zod";
import { deeprelay } from "./lib/deeprelay";

const { output } = await generateText({
  model: deeprelay("deeprelay/deepseek-v4.1-flash"),
  output: Output.object({
    schema: z.object({
      name: z.string(),
      ingredients: z.array(z.string()),
    }),
  }),
  prompt: "A recipe name and three ingredients for pancakes.",
});

console.log(output); // { name: "Classic Pancakes", ingredients: ["Flour", "Eggs", "Milk"] }

The same output option on streamText exposes partialOutputStream, the object filling in as tokens arrive, and await result.output for the final value. Models that list structured_outputs in supported_parameters take a strict schema natively. For a model that has response_format but not structured_outputs, drop supportsStructuredOutputs from the provider and the SDK falls back to prompting for JSON and parsing it.

Embeddings

Embedding models go through embeddingModel. embedMany batches a list in one request; deeprelay bills embeddings per input token only.

import { embed, embedMany, cosineSimilarity } from "ai";
import { deeprelay } from "./lib/deeprelay";

const model = deeprelay.embeddingModel("deeprelay/qwen3-embedding-8b");

const { embedding } = await embed({
  model,
  value: "sunny day at the beach",
});

const { embeddings } = await embedMany({
  model,
  values: ["sunny day at the beach", "rainy afternoon in the city"],
});

console.log(embedding.length, cosineSimilarity(embeddings[0], embeddings[1]));

Pick a model at runtime

The catalog is live, so list it rather than hard-coding a long table. The plain OpenAI SDK, or fetch, reads it with the same key:

const res = await fetch("https://api.deeprelay.ai/v1/models?modality=chat", {
  headers: { Authorization: `Bearer ${process.env.DEEPRELAY_API_KEY}` },
});
const { data } = await res.json();
console.log(data.map((m: { id: string }) => m.id));

Models the flat subscription covers carry plan_covered: true; a subscriber's requests to those draw on the monthly allowance before pay-as-you-go credit.

Errors

deeprelay returns OpenAI-shaped error envelopes, so the SDK surfaces them as APICallError with the HTTP status and the parsed body. The ones worth handling:

StatuscodeMeaning
401invalid_api_keyWrong or expired key.
402insufficient_balanceOut of credit; top up or subscribe.
404model_not_foundThe model left the live catalog; pick another.
429rate_limit_exceededBack off and retry; the SDK's maxRetries handles this.

The full list is in Conventions.

Next steps

  • LiteLLM if you want the same models in Python or behind a proxy.
  • Portkey for a gateway with fallbacks, caching and observability in front of deeprelay.
  • Serverless Inference guide for the request and response shapes the provider is sending, tool calling, JSON mode, and reasoning hints.
  • Coding agents if what you want is deeprelay inside an editor or agent rather than your own app.

← All integrations