Code
Vercel AI SDK
Make a nokeep provider with createOpenAICompatible from @ai-sdk/openai-compatible. Then generateText, streamText and tools work as with any AI SDK model.
Checked 29 October 2026
Install and set up the provider
npm install ai @ai-sdk/openai-compatible zod
export NOKEEP_API_KEY="sk-nokeep-test" # your own key once it connectsimport { createOpenAICompatible } from "@ai-sdk/openai-compatible"
export const nokeep = createOpenAICompatible({
name: "nokeep",
apiKey: process.env.NOKEEP_API_KEY,
baseURL: "https://nokeep.ai/v1",
includeUsage: true, // token counts on streamed replies too
})nokeep("<model id>") gives you a model. The ids are deepseek-v4.1-flash-uncensored and glm-5.3-flash-uncensored: compare them in Models, or read them from GET /v1/models. includeUsage: true asks for token counts on streamed replies; without it, streamText reports none.
generateText
import { generateText } from "ai"
import { nokeep } from "./nokeep.js"
const { text, usage } = await generateText({
model: nokeep("deepseek-v4.1-flash-uncensored"),
system: "You are a blunt editor.",
prompt: "Cut this opening line to ten words: ...",
})
console.log(text)
console.log(usage.inputTokens, usage.outputTokens)streamText
import { streamText } from "ai"
import { nokeep } from "./nokeep.js"
const result = streamText({
model: nokeep("deepseek-v4.1-flash-uncensored"),
prompt: "Tell me a story in three lines.",
})
for await (const text of result.textStream) process.stdout.write(text)
// With includeUsage on, the counts arrive once the stream ends.
console.log(await result.usage)In a Next.js route handler, return result.toUIMessageStreamResponse() for useChat, or result.toTextStreamResponse() for plain text. Stop a reply by aborting the request (abortSignal); you pay for what was written before you stopped. See Streaming.
Tool calling
import { generateText, stepCountIs, tool } from "ai"
import { z } from "zod"
import { nokeep } from "./nokeep.js"
const { text } = await generateText({
model: nokeep("deepseek-v4.1-flash-uncensored"),
tools: {
weather: tool({
description: "Get the weather in a city",
inputSchema: z.object({ city: z.string() }),
execute: async ({ city }) => ({ city, celsius: 18 }),
}),
},
stopWhen: stepCountIs(5), // let the model call the tool, read the result and answer
prompt: "What's the weather in Lisbon?",
})
console.log(text)Both models call tools. glm-5.3-flash-uncensored picks its own: toolChoice: "required" or a named tool gets a 400, so use DeepSeek when a tool must run. See Tool calls.
Reasoning effort
import { generateText } from "ai"
import { nokeep } from "./nokeep.js"
const { text, reasoningText } = await generateText({
model: nokeep("deepseek-v4.1-flash-uncensored"),
prompt: "Plan a three-act heist story in five bullet points.",
maxOutputTokens: 4000, // thinking counts toward the limit
providerOptions: { nokeep: { reasoningEffort: "high" } },
})
console.log(reasoningText) // the thinking, apart from the answer
console.log(text)The key under providerOptions is the name you gave the provider. It is sent as reasoning_effort, unchanged. DeepSeek V4.1 Flash Uncensored answers at once unless you ask it to think: it takes none, low, high, max. The AI SDK’s own reasoning: "high" option sends the same field. See Thinking.
Pictures (DeepSeek)
import { readFile } from "node:fs/promises"
import { generateText } from "ai"
import { nokeep } from "./nokeep.js"
const { text } = await generateText({
model: nokeep("deepseek-v4.1-flash-uncensored"),
messages: [{
role: "user",
content: [
{ type: "text", text: "What is in this picture?" },
{ type: "file", mediaType: "image/jpeg", data: await readFile("photo.jpg") },
],
}],
})
console.log(text)deepseek-v4.1-flash-uncensoredreads up to 10 pictures per request.glm-5.3-flash-uncensoredreads text only and answers400 unsupported_input.- The AI SDK sends every picture inside the request, as base64. Pass a
URLasdataand it downloads the file first. A request can be up to 12 MB, so shrink large photos. - Pictures are
fileparts with animage/*media type. The olderimagepart still works but is deprecated. - Other files, such as PDFs or audio, are refused with
400 unsupported_input. See Pictures.
Gotchas
- `reasoning_effort` takes only the model’s own values.
deepseek-v4.1-flash-uncensoredtakesnone,low,high,max;glm-5.3-flash-uncensoredtakeslow,high. Other apps’minimal,mediumandxhighrun at the model’s nearest level (mediumashigh,xhighasmaxwhere the model has it); any other value gets400 invalid_reasoning_effort. See Thinking. - Fields a model can’t take are dropped, not refused. The call still answers, and the
x-nokeep-ignoredresponse header names what was left out. GLM keeps onlytools,tool_choiceandreasoning_effort, so itstemperatureandresponse_formatare dropped. See which fields each model takes. - `n` must be 1. Ask for several replies with several calls.
- Use `@ai-sdk/openai-compatible`, not `@ai-sdk/openai`. The OpenAI provider calls
/v1/responsesby default, which nokeep doesn’t have.
Questions
Is there a nokeep provider package for the AI SDK?
You don’t need one. @ai-sdk/openai-compatible is the AI SDK’s own package for OpenAI-compatible APIs, and every feature on this page works through it.
Does streamText report usage?
Yes, when the provider is made with includeUsage: true. await result.usage then gives the token counts for the call.
Can I try it without paying?
Yes. Use the key sk-nokeep-test: every call answers with one fixed line and costs nothing, so you can wire up your app first. The free test key.