writing/tutorial/2026/07
TutorialJul 19, 2026·28 min read

Qwen3.8-Max-Preview TypeScript Integration: Build with Alibaba's 2.4T Frontier Model

Integrate Qwen3.8-Max-Preview — Alibaba's 2.4-trillion-parameter frontier model — into TypeScript and Next.js apps via the OpenAI-compatible DashScope API. Covers streaming, tool calling, structured output with Zod, and a production SSE route.

On July 19, 2026, Alibaba's Qwen team announced Qwen3.8, a 2.4-trillion-parameter frontier model the company positions as second only to Anthropic's Fable 5 — with open weights promised for a future release. You do not have to wait for the weights: Qwen3.8-Max-Preview is already live, and developers can call it today through Alibaba Cloud's OpenAI-compatible API.

This tutorial walks you through integrating Qwen3.8-Max-Preview into TypeScript step by step: basic completions, token streaming, tool calling, structured JSON output validated with Zod, and a production-ready Next.js 15 API route that streams responses over Server-Sent Events.

If you followed our Kimi K3 integration guide, the shape of this tutorial will feel familiar — that is deliberate. Both providers expose OpenAI-compatible endpoints, so a well-structured client layer lets you swap frontier models with a two-line change. By the end, you will have exactly that.

Two Chinese labs, one week, two multi-trillion-parameter open-weight promises. Moonshot announced Kimi K3 (2.8T) on July 17; Alibaba answered with Qwen3.8 (2.4T) on July 19. For developers, the takeaway is practical: frontier-class capability is becoming a commodity you can integrate behind a thin abstraction layer — and this tutorial builds that layer.

Prerequisites

Before starting, ensure you have:

  • Node.js 20+ (node --version)
  • TypeScript 5.4+
  • An Alibaba Cloud account with Model Studio (DashScope) activated and an API key
  • Basic familiarity with async/await and the OpenAI SDK conventions

No GPU required — every example uses the hosted preview API. To get a key, open the Alibaba Cloud Model Studio console, activate the service (there is a free quota for new accounts), and create an API key from the key management page.

What You'll Build

By the end of this tutorial you will have:

  1. A typed Qwen3.8 client using the standard OpenAI SDK
  2. A basic completion script that inspects token usage
  3. A streaming function with incremental console output
  4. A tool-calling agent loop where Qwen3.8 calls your TypeScript functions
  5. A structured-output extractor that returns validated, typed JSON
  6. A production Next.js 15 API route wrapping the model as a Server-Sent Events stream

Understanding Qwen3.8-Max-Preview

Before writing code, here is the mental model that shapes every integration decision.

Preview status. Qwen3.8-Max-Preview is exactly what the name says: a preview of a model still "continuously evolving," in Alibaba's words. Expect the model ID, rate limits, and behavior to shift before general availability. The abstraction layer we build in Step 1 exists precisely so those shifts stay contained in one file.

Scale and throughput. At 2.4 trillion parameters, this is one of the largest models Alibaba has ever served. Early testers measured throughput fluctuating between 22 and 55 tokens per second — noticeably slower than smaller models like GLM-5.2. Streaming is therefore not a nice-to-have; it is mandatory for acceptable UX. Every user-facing example in this tutorial streams.

OpenAI-compatible API. Alibaba exposes Qwen models through DashScope's compatible mode. The international base URL is https://dashscope-intl.aliyuncs.com/compatible-mode/v1 (use the non-intl variant if your account is registered in mainland China). The standard openai npm package works unmodified.

Consumer vs. developer access. The preview also ships inside Alibaba's Token Plan subscription and the Qoder and QoderWork products. Those are consumer channels — this tutorial uses the developer API, which bills per token and gives you programmatic control.

No benchmarks yet. Alibaba has published no independent evaluations; the "second only to Fable 5" claim rests on internal testing. Treat capability claims as hypotheses to verify against your own workload — Step 5's structured extraction task is a good starting benchmark.

Step 1: Project Setup

Create the project and install dependencies:

mkdir qwen38-demo && cd qwen38-demo
npm init -y
npm install openai zod dotenv
npm install -D typescript @types/node tsx
npx tsc --init

Edit tsconfig.json to target modern ESM output:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "outDir": "dist",
    "esModuleInterop": true
  }
}

Create .env:

DASHSCOPE_API_KEY=sk-your-key-here

And a typed client in src/client.ts:

import OpenAI from "openai";
import "dotenv/config";
 
if (!process.env.DASHSCOPE_API_KEY) {
  throw new Error("DASHSCOPE_API_KEY is not set in the environment.");
}
 
export const qwen = new OpenAI({
  apiKey: process.env.DASHSCOPE_API_KEY,
  baseURL: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
});
 
// Preview model ID — expect this to change at GA. Keeping it here
// means the rest of the codebase never hardcodes it.
export const QWEN38 = "qwen3.8-max-preview" as const;

This is the only file you touch when the preview graduates to a stable release, or when you swap providers entirely.

Step 2: Basic Chat Completion

Create src/basic.ts:

import { qwen, QWEN38 } from "./client.js";
 
async function main() {
  const response = await qwen.chat.completions.create({
    model: QWEN38,
    messages: [
      {
        role: "system",
        content:
          "You are a concise technical assistant. Answer in short paragraphs.",
      },
      {
        role: "user",
        content:
          "Explain Mixture-of-Experts routing in large language models.",
      },
    ],
  });
 
  const choice = response.choices[0];
  console.log("Answer:\n", choice.message.content);
  console.log("\nFinish reason:", choice.finish_reason);
  console.log("Tokens:", response.usage);
}
 
main().catch(console.error);

Run it:

npx tsx src/basic.ts

You get the answer plus a usage object with prompt_tokens, completion_tokens, and total_tokens. Log these in production from day one — preview pricing can change, and per-request token telemetry is how you catch cost regressions early.

Tip: keep prompts lean during the preview. With throughput fluctuating between 22 and 55 tokens per second, a 2,000-token answer can take up to 90 seconds. Constrain output length in your system prompt ("answer in under 150 words") until Alibaba scales up serving capacity.

Step 3: Streaming Responses

Given the preview's variable throughput, streaming is essential. Create src/stream.ts:

import { qwen, QWEN38 } from "./client.js";
 
export async function streamCompletion(prompt: string) {
  const stream = await qwen.chat.completions.create({
    model: QWEN38,
    messages: [{ role: "user", content: prompt }],
    stream: true,
    stream_options: { include_usage: true },
  });
 
  let fullText = "";
 
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content;
    if (delta) {
      fullText += delta;
      process.stdout.write(delta);
    }
    // The final chunk carries usage when include_usage is set
    if (chunk.usage) {
      console.log("\n\nTokens:", chunk.usage);
    }
  }
 
  return fullText;
}
 
streamCompletion(
  "Write a haiku about a 2.4-trillion-parameter model waking up."
).catch(console.error);

The stream_options: { include_usage: true } flag makes DashScope append token accounting to the final chunk, so you keep cost telemetry even in streaming mode. First tokens typically arrive within a couple of seconds even when total generation is slow — which is exactly why perceived latency improves so dramatically with streaming.

Step 4: Tool Calling

Qwen3.8 supports OpenAI-style function calling. We will build the classic agent loop: the model decides to call a tool, your code executes it, and the result goes back for a final grounded answer.

Create src/tools.ts:

import { qwen, QWEN38 } from "./client.js";
import type OpenAI from "openai";
 
// A fake exchange-rate lookup — swap for a real API in production.
function getExchangeRate(base: string, quote: string): string {
  const rates: Record<string, number> = {
    "USD/TND": 2.94,
    "EUR/TND": 3.42,
    "USD/SAR": 3.75,
  };
  const key = base + "/" + quote;
  const rate = rates[key];
  return rate
    ? JSON.stringify({ pair: key, rate })
    : JSON.stringify({ error: "Unknown pair " + key });
}
 
const tools: OpenAI.Chat.ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "get_exchange_rate",
      description: "Get the current exchange rate for a currency pair",
      parameters: {
        type: "object",
        properties: {
          base: { type: "string", description: "Base currency, e.g. USD" },
          quote: { type: "string", description: "Quote currency, e.g. TND" },
        },
        required: ["base", "quote"],
      },
    },
  },
];
 
async function main() {
  const messages: OpenAI.Chat.ChatCompletionMessageParam[] = [
    {
      role: "user",
      content: "How many Tunisian dinars is 250 US dollars right now?",
    },
  ];
 
  // First pass: the model decides whether to call the tool
  const first = await qwen.chat.completions.create({
    model: QWEN38,
    messages,
    tools,
  });
 
  const assistantMsg = first.choices[0].message;
  messages.push(assistantMsg);
 
  if (assistantMsg.tool_calls) {
    for (const call of assistantMsg.tool_calls) {
      const args = JSON.parse(call.function.arguments);
      const result = getExchangeRate(args.base, args.quote);
      messages.push({
        role: "tool",
        tool_call_id: call.id,
        content: result,
      });
    }
 
    // Second pass: the model answers using the tool result
    const second = await qwen.chat.completions.create({
      model: QWEN38,
      messages,
      tools,
    });
    console.log(second.choices[0].message.content);
  } else {
    console.log(assistantMsg.content);
  }
}
 
main().catch(console.error);

Run it and Qwen3.8 will request get_exchange_rate with base: "USD", quote: "TND", receive the rate, and compute the conversion in its final answer. For multi-step agents, wrap the two-pass exchange in a loop that keeps executing tool calls until the model returns a plain message — the same pattern we used in the ReAct agent tutorial.

Step 5: Structured Output with Zod

For pipelines that feed databases or UIs, free-form text is a liability. Combine JSON mode with Zod validation so malformed output fails loudly at the boundary. Create src/extract.ts:

import { z } from "zod";
import { qwen, QWEN38 } from "./client.js";
 
const InvoiceSchema = z.object({
  vendor: z.string(),
  invoiceNumber: z.string(),
  currency: z.string().length(3),
  totalAmount: z.number(),
  lineItems: z.array(
    z.object({
      description: z.string(),
      quantity: z.number(),
      unitPrice: z.number(),
    })
  ),
});
 
type Invoice = z.infer<typeof InvoiceSchema>;
 
export async function extractInvoice(rawText: string): Promise<Invoice> {
  const response = await qwen.chat.completions.create({
    model: QWEN38,
    response_format: { type: "json_object" },
    messages: [
      {
        role: "system",
        content:
          "Extract invoice data as JSON with keys: vendor, invoiceNumber, " +
          "currency (ISO 4217), totalAmount (number), lineItems (array of " +
          "objects with description, quantity, unitPrice). Return JSON only.",
      },
      { role: "user", content: rawText },
    ],
  });
 
  const raw = response.choices[0].message.content ?? "{}";
  return InvoiceSchema.parse(JSON.parse(raw));
}
 
const sample = `
NOQTA SARL - Invoice 011-TN-2026
Consulting services: 20 hours at 45.00 USD each
Total due: 900.00 USD
`;
 
extractInvoice(sample).then((inv) =>
  console.log(JSON.stringify(inv, null, 2))
);

Two layers of safety work together here: response_format: { type: "json_object" } constrains the model to emit valid JSON, and InvoiceSchema.parse guarantees the shape at runtime. If the preview model drifts — previews do — you get a thrown ZodError instead of silent garbage in your database.

Step 6: Production Next.js API Route

Finally, let us wrap everything in a Next.js 15 App Router route that streams over Server-Sent Events. In your Next.js project, create app/api/qwen/route.ts:

import OpenAI from "openai";
 
export const runtime = "nodejs";
export const maxDuration = 120; // preview throughput varies; allow headroom
 
const qwen = new OpenAI({
  apiKey: process.env.DASHSCOPE_API_KEY!,
  baseURL: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
});
 
export async function POST(req: Request) {
  const { messages } = await req.json();
 
  if (!Array.isArray(messages) || messages.length === 0) {
    return Response.json({ error: "messages array required" }, { status: 400 });
  }
 
  const stream = await qwen.chat.completions.create({
    model: "qwen3.8-max-preview",
    messages,
    stream: true,
  });
 
  const encoder = new TextEncoder();
 
  const readable = new ReadableStream({
    async start(controller) {
      try {
        for await (const chunk of stream) {
          const delta = chunk.choices[0]?.delta?.content;
          if (delta) {
            controller.enqueue(
              encoder.encode("data: " + JSON.stringify({ text: delta }) + "\n\n")
            );
          }
        }
        controller.enqueue(encoder.encode("data: [DONE]\n\n"));
      } catch (err) {
        controller.enqueue(
          encoder.encode(
            "data: " + JSON.stringify({ error: "stream failed" }) + "\n\n"
          )
        );
      } finally {
        controller.close();
      }
    },
  });
 
  return new Response(readable, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache, no-transform",
      Connection: "keep-alive",
    },
  });
}

On the client, consume it with fetch and a reader, or drop in the Vercel AI SDK's useChat pointed at this route. The maxDuration of 120 seconds is deliberate: at the preview's slower moments, long answers need the headroom.

Warning: never expose your DashScope key to the browser. All calls must go through a server route like this one. A leaked key on a per-token billing account is an open invoice — add rate limiting (see our Arcjet tutorial) before shipping publicly.

Testing Your Implementation

Verify each layer independently:

  1. Client layer: npx tsx src/basic.ts returns an answer and a usage object.
  2. Streaming: npx tsx src/stream.ts prints tokens incrementally, not in one burst, and ends with token usage.
  3. Tool calling: npx tsx src/tools.ts produces an answer containing roughly 735 dinars — proof the tool result was used rather than hallucinated.
  4. Structured output: npx tsx src/extract.ts prints a validated invoice object; corrupt the sample text and confirm you get a ZodError, not bad data.
  5. API route: curl -N -X POST http://localhost:3000/api/qwen -H "Content-Type: application/json" -d '{"messages":[{"role":"user","content":"Hello"}]}' shows SSE frames arriving over time.

Troubleshooting

401 Unauthorized. Your key is invalid or Model Studio is not activated for the account. Regenerate the key in the console and confirm the service status.

Model not found. Preview model IDs can change between releases. Check the Model Studio model list for the current identifier and update the single constant in src/client.ts.

Slow or stalling generations. Expected during the preview — testers report throughput swinging between 22 and 55 tokens per second. Stream everything, set generous timeouts, and constrain output length in prompts.

Rate limit errors (429). Preview quotas are conservative. Add exponential backoff with a library like p-retry, and queue non-interactive workloads.

Region mismatch. Accounts registered in mainland China must use the dashscope.aliyuncs.com base URL instead of the -intl variant; keys are not interchangeable across regions.

Next Steps

  • Compare the same prompts against Kimi K3 and GLM-5.2 — your client abstraction makes A/B testing a two-line change per provider.
  • Add observability with Langfuse to track cost and latency across providers as the preview evolves.
  • Watch for the open-weight release: when the weights land, the self-hosting calculus changes entirely, and community quantizations of smaller variants may follow.
  • Read the announcement coverage for the strategic context around Token Plan and Alibaba's distribution play.

Conclusion

You integrated Alibaba's newest frontier model into TypeScript within hours of its preview release: a swappable typed client, streaming completions tuned for the preview's variable throughput, a tool-calling agent loop, Zod-validated structured extraction, and a production SSE route in Next.js. Just as importantly, you built it behind an abstraction that treats "which frontier model" as a configuration detail — the only sane architecture in a month when two labs shipped multi-trillion-parameter models within 48 hours of each other. When Qwen3.8 hits general availability with published benchmarks and open weights, your integration will be one constant away from ready.