écrits/tutorial/2026/07
Tutorial17 juil. 2026·30 min

Intégration de Kimi K3 en TypeScript : Construire avec le plus grand modèle open source

Apprenez à intégrer Kimi K3 — le modèle phare de Moonshot AI avec 2,8T paramètres et une fenêtre de contexte de 1M tokens — dans vos applications TypeScript et Next.js. Couvre le raisonnement en streaming, les appels d'outils, les sorties structurées et la vision.

Les modèles à poids ouverts ont atteint un point de bascule. Quand Moonshot AI a lancé Kimi K3 en juillet 2026 — 2,8 billions de paramètres au total, environ 60 à 80 milliards de paramètres actifs par token via une architecture Mixture-of-Experts, une fenêtre de contexte d'un million de tokens — ils n'ont pas simplement établi un record de taille. Ils ont démontré que les modèles entièrement ouverts peuvent rivaliser avec les meilleurs systèmes fermés en matière de programmation, de mathématiques et de raisonnement sur de longs contextes.

Pour les développeurs de la région MENA et partout en dehors des États-Unis, cela change la donne. Kimi K3 est disponible aujourd'hui via une API compatible OpenAI, peut être auto-hébergé sur des clusters multi-GPU, et est couvert par une licence à poids ouverts permissive. Ce tutoriel vous guide pas à pas dans l'intégration de K3 en TypeScript : complétions de base, streaming des tokens de raisonnement, appels d'outils, sorties JSON structurées et vision multimodale — pour finir avec une route API Next.js 15 prête pour la production.

Si vous avez déjà suivi le tutoriel Kimi K2, la migration se résume à changer le nom du modèle et à ajouter un paramètre reasoning_effort. Nous couvrons la checklist de migration en fin de tutoriel.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20+ (node --version)
  • TypeScript 5.4+
  • Un compte Moonshot AI et une clé API depuis la console
  • Une connaissance basique d'async/await et des SDKs compatibles OpenAI

Aucun GPU n'est requis — tous les exemples utilisent l'API hébergée de Moonshot.

Ce que vous allez construire

À la fin de ce tutoriel, vous disposerez de :

  1. Un client K3 typé utilisant le SDK OpenAI officiel
  2. Une fonction de streaming séparant les tokens de raisonnement des réponses finales
  3. Une boucle d'agent pour les appels d'outils permettant à K3 d'appeler vos fonctions TypeScript
  4. Un extracteur de sorties structurées retournant du JSON valide et typé
  5. Un utilitaire de vision pour l'analyse d'images
  6. Une route API Next.js 15 exposant K3 comme flux d'événements serveur (SSE)

Comprendre Kimi K3

Avant d'écrire du code, voici le modèle mental qui guide chaque décision d'intégration.

Architecture MoE. Kimi K3 possède 2,8 billions de paramètres au total, mais n'en active qu'environ 60 à 80 milliards par token via le routage Mixture-of-Experts. Héberger le modèle nécessite de nombreux GPU A100/H100, mais le coût d'inférence par token reste compétitif car la fraction active est bien inférieure à 2,8 billions.

Raisonnement toujours activé. Contrairement à K2, K3 ne permet pas de désactiver la phase de réflexion. Chaque requête passe par une chaîne de pensée interne. Vous contrôlez la profondeur avec reasoning_effort — seule la valeur "max" est acceptée pour l'instant ; Moonshot ajoutera "medium" et "low" dans de futures versions.

Paramètres d'échantillonnage fixes. Les paramètres comme temperature, top_p, n et les pénalités sont verrouillés par le modèle. Ne les envoyez pas — ils sont ignorés silencieusement.

Fenêtre de contexte d'un million de tokens. La fenêtre de contexte est d'un million de tokens avec mise en cache automatique des prompts. Alimenter une base de code de 300 000 tokens dans une seule requête est tout à fait envisageable au niveau de prix de K3.

API compatible OpenAI. URL de base https://api.moonshot.ai/v1 — identique à K2. Vous pouvez également utiliser https://api.moonshot.ai/anthropic pour le dialecte Anthropic (utile pour les configurations Claude Code et Cline).

Étape 1 : Configuration du projet

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

Éditez tsconfig.json pour cibler une sortie ESM moderne :

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

Créez .env :

MOONSHOT_API_KEY=sk-votre-clé-ici

Et un client typé dans src/client.ts :

import OpenAI from "openai";
import "dotenv/config";
 
if (!process.env.MOONSHOT_API_KEY) {
  throw new Error("MOONSHOT_API_KEY n'est pas défini dans les variables d'environnement.");
}
 
export const kimi = new OpenAI({
  apiKey: process.env.MOONSHOT_API_KEY,
  baseURL: "https://api.moonshot.ai/v1",
});
 
export const K3 = "kimi-k3" as const;

C'est le seul fichier à modifier lors d'un changement de fournisseur — la logique métier reste intacte.

Étape 2 : Complétion de chat basique

Les réponses de K3 incluent un champ reasoning_content en plus du content habituel. Créez src/basic.ts pour récupérer les deux :

import { kimi, K3 } from "./client.js";
 
async function main() {
  const response = await kimi.chat.completions.create({
    model: K3,
    reasoning_effort: "max",   // Spécifique à K3 ; seule valeur acceptée actuellement
    messages: [
      {
        role: "user",
        content: "Expliquez le routage Mixture-of-Experts en moins de 100 mots.",
      },
    ],
    max_completion_tokens: 4096,
  } as any); // Les types du SDK OpenAI ne comprennent pas encore reasoning_effort
 
  const choice = response.choices[0];
  const reasoning = (choice.message as any).reasoning_content as string | null;
  const answer = choice.message.content;
 
  if (reasoning) {
    console.log("=== Raisonnement ===");
    console.log(reasoning);
    console.log();
  }
 
  console.log("=== Réponse ===");
  console.log(answer);
}
 
main().catch(console.error);

Exécutez :

npx tsx src/basic.ts

Vous verrez la chaîne de pensée interne de K3 affichée avant la réponse finale — une trace visible de son raisonnement.

Étape 3 : Streaming avec tokens de raisonnement

Le streaming expose le raisonnement et le contenu comme deux canaux delta séparés. Cela vous permet d'afficher un indicateur "réflexion en cours…" pendant que le modèle pense, puis de révéler la réponse une fois finalisée.

Créez src/stream.ts :

import { kimi, K3 } from "./client.js";
 
async function streamK3(prompt: string): Promise<void> {
  const stream = await kimi.chat.completions.create({
    model: K3,
    reasoning_effort: "max",
    messages: [{ role: "user", content: prompt }],
    stream: true,
    max_completion_tokens: 8192,
  } as any);
 
  let inReasoning = false;
 
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta as any;
 
    if (delta?.reasoning_content) {
      if (!inReasoning) {
        process.stdout.write("\n[réflexion] ");
        inReasoning = true;
      }
      process.stdout.write(delta.reasoning_content);
    } else if (delta?.content) {
      if (inReasoning) {
        process.stdout.write("\n\n[réponse] ");
        inReasoning = false;
      }
      process.stdout.write(delta.content);
    }
  }
 
  process.stdout.write("\n");
}
 
streamK3(
  "Quels sont les compromis entre les architectures transformer denses et creuses ?"
).catch(console.error);

Le point clé : K3 émet des deltas reasoning_content pendant sa phase de réflexion, puis passe aux deltas content pour la réponse finale. Les deux flux ne se chevauchent jamais.

Étape 4 : Appels d'outils

K3 supporte les appels de fonctions dans le style OpenAI standard. Le schéma d'outil et l'analyse de la réponse sont identiques à ce que vous écririez pour GPT-4o, faisant de K3 un remplacement direct dans les pipelines d'appels d'outils.

Créez src/tools.ts :

import OpenAI from "openai";
import { kimi, K3 } from "./client.js";
 
const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "get_npm_downloads",
      description: "Récupère le nombre de téléchargements hebdomadaires d'un package npm",
      parameters: {
        type: "object",
        properties: {
          package_name: {
            type: "string",
            description: "Le nom du package npm, ex. 'react'",
          },
        },
        required: ["package_name"],
      },
    },
  },
];
 
async function get_npm_downloads(packageName: string): Promise<string> {
  const mockData: Record<string, number> = {
    react: 28_000_000,
    next: 8_500_000,
    openai: 4_200_000,
  };
  const count = mockData[packageName] ?? 500_000;
  return `${packageName} a été téléchargé ${count.toLocaleString("fr-FR")} fois la semaine dernière.`;
}
 
async function runAgent(question: string): Promise<void> {
  const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
    { role: "user", content: question },
  ];
 
  for (let i = 0; i < 5; i++) {
    const response = await kimi.chat.completions.create({
      model: K3,
      reasoning_effort: "max",
      messages,
      tools,
      tool_choice: "auto",
      max_completion_tokens: 4096,
    } as any);
 
    const choice = response.choices[0];
    messages.push(choice.message as any);
 
    if (choice.finish_reason === "stop") {
      console.log("Réponse finale :", choice.message.content);
      return;
    }
 
    if (choice.finish_reason === "tool_calls") {
      const toolCalls = choice.message.tool_calls ?? [];
      for (const call of toolCalls) {
        const args = JSON.parse(call.function.arguments) as { package_name: string };
        const result = await get_npm_downloads(args.package_name);
        console.log(`Outil appelé : ${call.function.name}(${args.package_name}) → ${result}`);
        messages.push({
          role: "tool",
          tool_call_id: call.id,
          content: result,
        });
      }
    }
  }
}
 
runAgent(
  "Comparez le nombre de téléchargements npm hebdomadaires de react, next et openai."
).catch(console.error);

Étape 5 : Sorties structurées

K3 supporte les sorties structurées JSON Schema avec strict: true. Combiné à Zod pour la sécurité des types en TypeScript, cela produit des réponses entièrement validées et typées sans aucun risque de parsing manuel.

Créez src/structured.ts :

import { kimi, K3 } from "./client.js";
import { z } from "zod";
 
const codeReviewSchema = z.object({
  overall_score: z.number().min(1).max(10),
  issues: z.array(
    z.object({
      severity: z.enum(["critical", "warning", "suggestion"]),
      line: z.number().optional(),
      description: z.string(),
      fix: z.string(),
    })
  ),
  summary: z.string(),
});
 
type CodeReview = z.infer<typeof codeReviewSchema>;
 
async function reviewCode(code: string): Promise<CodeReview> {
  const response = await kimi.chat.completions.create({
    model: K3,
    reasoning_effort: "max",
    messages: [
      {
        role: "system",
        content: "Vous êtes un développeur TypeScript senior. Retournez uniquement du JSON structuré.",
      },
      {
        role: "user",
        content: `Révisez ce code :\n\n\`\`\`typescript\n${code}\n\`\`\``,
      },
    ],
    response_format: {
      type: "json_schema",
      json_schema: {
        name: "code_review",
        strict: true,
        schema: {
          type: "object",
          properties: {
            overall_score: { type: "number" },
            issues: {
              type: "array",
              items: {
                type: "object",
                properties: {
                  severity: {
                    type: "string",
                    enum: ["critical", "warning", "suggestion"],
                  },
                  line: { type: "number" },
                  description: { type: "string" },
                  fix: { type: "string" },
                },
                required: ["severity", "description", "fix"],
              },
            },
            summary: { type: "string" },
          },
          required: ["overall_score", "issues", "summary"],
        },
      },
    },
    max_completion_tokens: 4096,
  } as any);
 
  const raw = response.choices[0].message.content ?? "{}";
  return codeReviewSchema.parse(JSON.parse(raw));
}
 
const sampleCode = `
async function fetchUser(id: string) {
  const res = await fetch('/api/users/' + id)
  const data = await res.json()
  return data.user
}
`;
 
reviewCode(sampleCode)
  .then((review) => {
    console.log(`Score : ${review.overall_score}/10`);
    review.issues.forEach((issue) => {
      console.log(`[${issue.severity}] ${issue.description}`);
      console.log(`  Correction : ${issue.fix}`);
    });
    console.log(`Résumé : ${review.summary}`);
  })
  .catch(console.error);

Le flag strict: true garantit que K3 respecte exactement le schéma — aucune propriété supplémentaire, aucun champ obligatoire manquant.

Étape 6 : Vision — Analyse d'images

K3 accepte les images comme data URI encodées en base64 ou via le système de référence de fichiers ms:// de Moonshot. Pour une analyse ponctuelle, la voie base64 est la plus simple.

Créez src/vision.ts :

import { kimi, K3 } from "./client.js";
import { readFileSync } from "fs";
import path from "path";
 
async function analyseImage(imagePath: string, question: string): Promise<string> {
  const buffer = readFileSync(imagePath);
  const base64 = buffer.toString("base64");
  const ext = path.extname(imagePath).slice(1);
  const mimeType = ext === "jpg" ? "image/jpeg" : `image/${ext}`;
 
  const response = await kimi.chat.completions.create({
    model: K3,
    reasoning_effort: "max",
    messages: [
      {
        role: "user",
        content: [
          {
            type: "image_url",
            image_url: { url: `data:${mimeType};base64,${base64}` },
          },
          { type: "text", text: question },
        ],
      },
    ],
    max_completion_tokens: 2048,
  } as any);
 
  return response.choices[0].message.content ?? "";
}
 
export { analyseImage };

Utilisation :

import { analyseImage } from "./vision.js";
 
const result = await analyseImage(
  "./screenshot.png",
  "Décrivez l'interface utilisateur et listez les problèmes d'accessibilité que vous observez."
);
console.log(result);

K3 supporte les formats JPEG, PNG, WebP et GIF via base64. Pour les images volumineuses ou les envois répétés, utilisez l'API Files de Moonshot pour obtenir une référence ms:// plutôt que de ré-envoyer les données à chaque requête.

Étape 7 : Route API Next.js 15

Encapsulez le streaming K3 dans une route App Router Next.js pour la production. La route relaie les deux canaux (raisonnement et contenu) sous forme d'événements serveur (SSE).

app/api/kimi-k3/route.ts :

import { NextRequest } from "next/server";
import OpenAI from "openai";
 
const kimi = new OpenAI({
  apiKey: process.env.MOONSHOT_API_KEY!,
  baseURL: "https://api.moonshot.ai/v1",
});
 
export async function POST(request: NextRequest) {
  const { messages } = (await request.json()) as {
    messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[];
  };
 
  const stream = await kimi.chat.completions.create({
    model: "kimi-k3",
    reasoning_effort: "max",
    messages,
    stream: true,
    max_completion_tokens: 16384,
  } as any);
 
  const encoder = new TextEncoder();
 
  return new Response(
    new ReadableStream({
      async start(controller) {
        try {
          for await (const chunk of stream) {
            const delta = chunk.choices[0]?.delta as any;
            const data = {
              reasoning: delta?.reasoning_content ?? null,
              content: delta?.content ?? null,
            };
            controller.enqueue(
              encoder.encode(`data: ${JSON.stringify(data)}\n\n`)
            );
          }
          controller.enqueue(encoder.encode("data: [DONE]\n\n"));
          controller.close();
        } catch (err) {
          controller.error(err);
        }
      },
    }),
    {
      headers: {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        Connection: "keep-alive",
      },
    }
  );
}

Consommateur côté client :

async function streamK3Chat(
  userMessage: string,
  onChunk: (text: string, isReasoning: boolean) => void
) {
  const res = await fetch("/api/kimi-k3", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      messages: [{ role: "user", content: userMessage }],
    }),
  });
 
  const reader = res.body?.getReader();
  const decoder = new TextDecoder();
  if (!reader) return;
 
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
 
    const lines = decoder.decode(value).split("\n").filter((l) => l.startsWith("data: "));
    for (const line of lines) {
      const payload = line.slice(6);
      if (payload === "[DONE]") return;
      const parsed = JSON.parse(payload) as {
        reasoning: string | null;
        content: string | null;
      };
      if (parsed.reasoning) onChunk(parsed.reasoning, true);
      if (parsed.content) onChunk(parsed.content, false);
    }
  }
}

Dans votre composant React, passez des setters d'état séparés pour reasoning et content à onChunk afin de les afficher dans deux panneaux distincts — un panneau de réflexion repliable et une zone de réponse finale.

Migration depuis Kimi K2

Si vous disposez de code existant issu du tutoriel K2, la migration est minime :

ChangementK2K3
Identifiant du modèlekimi-k2kimi-k3
Paramètre de réflexionthinking: { type: "enabled", budget_tokens: N }reasoning_effort: "max"
Fenêtre de contexte128K tokens1M tokens
TempératureConfigurableFixe — ne pas envoyer
VisionNon supportéeSupportée via base64 / ms://
Champ de stream de raisonnementAbsentDelta reasoning_content

L'URL de base (https://api.moonshot.ai/v1) et l'en-tête d'authentification restent identiques.

Résolution des problèmes

"Invalid parameter: temperature" — K3 n'accepte pas les paramètres d'échantillonnage. Retirez temperature, top_p, n et les champs de pénalité de vos requêtes.

reasoning_content est null — Assurez-vous de passer reasoning_effort: "max" et de caster la requête avec as any jusqu'à ce que les types du SDK OpenAI intègrent K3.

Le contexte dépasse la limite — K3 accepte jusqu'à un million de tokens en entrée, mais max_completion_tokens vaut par défaut 131 072. Pour les sorties très longues, augmentez-le explicitement jusqu'à 1 048 576.

Le streaming s'interrompt prématurément — Les tâches de raisonnement complexes génèrent de longues chaînes de pensée avant la réponse finale. Augmentez max_completion_tokens si la sortie semble tronquée.

La validation des sorties structurées échoue — Assurez-vous que votre JSON Schema n'utilise que les types supportés par le mode strict de K3 : object, array, string, number, boolean et null.

Prochaines étapes

  • Alimentez un dépôt entier dans K3 avec la fenêtre de contexte d'un million de tokens et demandez une revue architecturale complète — impossible avec des modèles à 128K tokens
  • Combinez la vision avec la conversion PDF vers images pour construire des pipelines de révision de documents
  • Essayez le point de terminaison compatible Anthropic (https://api.moonshot.ai/anthropic) pour utiliser K3 dans Claude Code en définissant ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic
  • Comparez la profondeur de raisonnement de K3 avec Claude Fable 5 sur des tâches de programmation à long horizon

Conclusion

Kimi K3 est le plus grand modèle open source disponible en juillet 2026, et son API compatible OpenAI vous permet de l'intégrer dans n'importe quel projet TypeScript ou Next.js avec un minimum de friction. Les ajouts clés par rapport à K2 sont le paramètre reasoning_effort qui expose la chaîne de pensée intégrée, le canal de streaming séparé reasoning_content, la prise en charge native de la vision, et une fenêtre de contexte d'un million de tokens qui accueille réellement des bases de code entières dans une seule requête. Commencez avec le client typé ci-dessus, ajoutez le streaming pour les interfaces utilisateur, et recourez aux sorties structurées chaque fois que vous avez besoin de réponses lisibles par machine depuis un système de raisonnement IA.