écrits/tutorial/2026/07
Tutorial1 juil. 2026·26 min

Intégrer Kimi K2 avec TypeScript : bâtir des applications IA sur le modèle open-weight de Moonshot

Un guide pratique pour construire des applications IA avec Kimi K2 de Moonshot AI, le modèle open-weight à mille milliards de paramètres. Apprenez les complétions de chat, le streaming, l'appel d'outils, les sorties structurées, et comment brancher Kimi dans Claude Code et Cline comme fournisseur de secours prêt à l'emploi.

L'accès aux modèles de pointe devient de plus en plus politique. Entre les contrôles à l'export, les approbations gouvernementales client par client et les déploiements échelonnés, miser tout votre produit sur une seule API hébergée est plus risqué qu'il y a un an. C'est précisément pourquoi les modèles open-weight dotés de points d'accès compatibles OpenAI sont devenus le second fournisseur pragmatique des équipes de la région MENA et au-delà.

Kimi K2 de Moonshot AI est l'une des options les plus solides de cette catégorie : un modèle Mixture-of-Experts à mille milliards de paramètres, à poids ouverts, sous licence permissive, et avec une API qui parle à la fois les dialectes OpenAI et Anthropic. Dans ce tutoriel, vous construirez une boîte à outils TypeScript petite mais complète autour de Kimi K2 — complétions de chat, streaming, appel de fonctions/outils et sortie JSON structurée — puis vous brancherez le même modèle dans Claude Code et Cline comme assistant de code prêt à l'emploi.

Prérequis

Avant de commencer, assurez-vous d'avoir :

  • Node.js 20+ installé (node --version)
  • Une familiarité de base avec TypeScript et async/await
  • Un compte Moonshot AI et une clé API depuis la console
  • Un terminal et un éditeur de code (VS Code recommandé)
  • Optionnel : Claude Code ou Cline installé, pour tester la section agent de code

Vous n'avez pas besoin de GPU. Tout ce qui suit fonctionne via l'API hébergée de Moonshot. Nous évoquerons l'auto-hébergement des poids ouverts à la fin, par souci d'exhaustivité.

Ce que vous allez construire

À la fin, vous aurez :

  1. Un client Moonshot typé, bâti sur le SDK OpenAI officiel (Kimi K2 est compatible OpenAI, donc aucun code HTTP sur mesure nécessaire).
  2. Une fonction de chat en streaming qui affiche les jetons dès leur arrivée.
  3. Une boucle d'agent avec appel d'outils qui laisse Kimi K2 appeler vos fonctions TypeScript.
  4. Un assistant de sortie structurée qui renvoie un JSON validé.
  5. Une recette de configuration pour utiliser Kimi K2 dans Claude Code et Cline.

Comprendre Kimi K2

Un modèle mental rapide avant le code. Kimi K2 est un modèle Mixture-of-Experts (MoE) d'environ mille milliards de paramètres au total, mais avec seulement quelque 32 milliards actifs par jeton — c'est ce qui rend un modèle de cette taille économique à servir. Il est livré en poids ouverts sous une licence permissive (de style MIT modifié), donc vous pouvez l'auto-héberger, et il est aussi disponible via l'API gérée de Moonshot.

Les faits clés pour l'intégration :

  • API compatible OpenAI. L'URL de base est https://api.moonshot.ai/v1 (international) ou https://api.moonshot.cn/v1 (Chine continentale). Vous pointez le SDK OpenAI standard vers elle et tout fonctionne.
  • Point d'accès compatible Anthropic. Moonshot expose aussi https://api.moonshot.ai/anthropic, ce qui permet à Kimi K2 de s'insérer dans Claude Code.
  • Contexte long. Kimi K2 gère de très longues fenêtres de contexte (128 000 jetons et plus sur les instantanés récents), utile pour les charges agentiques et de code.
  • Forces agentiques. K2 a été fortement optimisé pour l'usage d'outils et le code multi-étapes, il se comporte donc bien dans les boucles d'agent.

Les identifiants de modèle sont des instantanés datés (par exemple kimi-k2-0711-preview, et des variantes plus rapides comme kimi-k2-turbo-preview). Comme ces noms évoluent, confirmez toujours les identifiants exacts auprès du point d'accès des modèles en direct plutôt que d'en figer un pour toujours. Nous récupérerons cette liste par programme à l'étape 2.

Étape 1 : Configuration du projet

Créez un projet neuf et installez les dépendances.

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

Nous installons le paquet openai officiel (l'API Kimi est compatible OpenAI), plus zod pour la validation à l'exécution des sorties structurées. tsx permet d'exécuter directement les fichiers TypeScript.

Stockez votre clé API dans un fichier d'environnement — ne codez jamais les secrets en dur.

# .env
MOONSHOT_API_KEY=sk-your-key-here

Ajoutez un petit script de chargement à package.json pour que tsx prenne le fichier d'environnement :

{
  "type": "module",
  "scripts": {
    "dev": "node --env-file=.env --import tsx"
  }
}

Créez maintenant le client partagé dans src/client.ts :

// src/client.ts
import OpenAI from "openai";
 
if (!process.env.MOONSHOT_API_KEY) {
  throw new Error("MOONSHOT_API_KEY is not set. Add it to your .env file.");
}
 
// Kimi K2 est servi via une API compatible OpenAI, donc on réutilise
// le SDK OpenAI officiel et on ne change que baseURL et la clé.
export const kimi = new OpenAI({
  apiKey: process.env.MOONSHOT_API_KEY,
  baseURL: "https://api.moonshot.ai/v1",
});
 
// Endroit central pour changer l'instantané du modèle pour tout le projet.
export const KIMI_MODEL = "kimi-k2-0711-preview";

C'est tout l'adaptateur. Comme Kimi parle le protocole OpenAI, chaque assistant que nous bâtissons à partir d'ici est portable — si vous changez de fournisseur plus tard, vous modifiez deux lignes.

Étape 2 : Une première complétion de chat

Confirmons la connexion avec une requête de base sans streaming. Créez src/chat.ts :

// src/chat.ts
import { kimi, KIMI_MODEL } from "./client.js";
 
async function main() {
  const response = await kimi.chat.completions.create({
    model: KIMI_MODEL,
    messages: [
      {
        role: "system",
        content: "You are a concise senior TypeScript engineer.",
      },
      {
        role: "user",
        content: "Explain what a Mixture-of-Experts model is in two sentences.",
      },
    ],
    temperature: 0.6,
  });
 
  console.log(response.choices[0].message.content);
  console.log("---");
  console.log("Tokens used:", response.usage?.total_tokens);
}
 
main().catch((err) => {
  console.error("Request failed:", err);
  process.exit(1);
});

Exécutez-le :

npm run dev src/chat.ts

Une note sur temperature : Moonshot recommande une valeur modérée (autour de 0,6) pour une sortie équilibrée. Abaissez-la vers 0,2 pour les tâches déterministes comme la génération de code et l'extraction de données.

Pour récupérer la liste actuelle des identifiants de modèle disponibles plutôt que de deviner, ajoutez cet assistant :

// src/models.ts
import { kimi } from "./client.js";
 
const models = await kimi.models.list();
for (const model of models.data) {
  console.log(model.id);
}

L'exécution de npm run dev src/models.ts affiche chaque identifiant de modèle accessible par votre clé — la manière fiable de confirmer l'instantané K2 à cibler.

Étape 3 : Réponses en streaming

Pour les interfaces de chat et les CLI, vous voulez voir les jetons apparaître au fur et à mesure de leur génération. Le streaming avec le SDK OpenAI est un simple changement de drapeau plus un itérateur asynchrone. Créez src/stream.ts :

// src/stream.ts
import { kimi, KIMI_MODEL } from "./client.js";
 
export async function streamChat(prompt: string) {
  const stream = await kimi.chat.completions.create({
    model: KIMI_MODEL,
    messages: [{ role: "user", content: prompt }],
    stream: true,
    temperature: 0.6,
  });
 
  let full = "";
  for await (const chunk of stream) {
    const delta = chunk.choices[0]?.delta?.content ?? "";
    process.stdout.write(delta); // afficher progressivement
    full += delta;
  }
  process.stdout.write("\n");
  return full;
}
 
await streamChat("Write a haiku about serverless GPUs.");

Chaque chunk porte un petit delta. Nous l'écrivons directement dans stdout pour un effet de frappe en direct et accumulons aussi la chaîne complète à renvoyer. Dans une route Next.js, vous canaliseriez ces deltas dans un ReadableStream pour les envoyer au navigateur — exactement la même boucle.

Étape 4 : Appel d'outils (boucle agentique)

C'est là que Kimi K2 brille. L'appel d'outils permet au modèle de décider d'invoquer vos fonctions, d'attendre le résultat et de continuer à raisonner. Nous lui donnerons un faux outil météo et un outil de conversion d'unités.

Définissez d'abord les outils et leurs gestionnaires dans src/tools.ts :

// src/tools.ts
import type OpenAI from "openai";
 
export const toolDefs: OpenAI.Chat.Completions.ChatCompletionTool[] = [
  {
    type: "function",
    function: {
      name: "get_weather",
      description: "Get the current temperature for a city in Celsius.",
      parameters: {
        type: "object",
        properties: {
          city: { type: "string", description: "City name, e.g. Tunis" },
        },
        required: ["city"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "celsius_to_fahrenheit",
      description: "Convert a Celsius temperature to Fahrenheit.",
      parameters: {
        type: "object",
        properties: {
          celsius: { type: "number" },
        },
        required: ["celsius"],
      },
    },
  },
];
 
// Implémentations réelles. En production, elles appellent une API ou une base.
export const handlers: Record<string, (args: any) => Promise<string>> = {
  async get_weather({ city }: { city: string }) {
    const fakeDb: Record<string, number> = { Tunis: 31, Riyadh: 42, Paris: 22 };
    const temp = fakeDb[city] ?? 25;
    return JSON.stringify({ city, celsius: temp });
  },
  async celsius_to_fahrenheit({ celsius }: { celsius: number }) {
    return JSON.stringify({ fahrenheit: (celsius * 9) / 5 + 32 });
  },
};

Maintenant la boucle d'agent dans src/agent.ts. Le motif : envoyer les messages plus les outils, et si le modèle renvoie des tool_calls, les exécuter, ajouter les résultats et rappeler jusqu'à ce que le modèle produise une réponse finale.

// src/agent.ts
import { kimi, KIMI_MODEL } from "./client.js";
import { toolDefs, handlers } from "./tools.js";
import type OpenAI from "openai";
 
export async function runAgent(userPrompt: string) {
  const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
    { role: "system", content: "You are a helpful assistant. Use tools when needed." },
    { role: "user", content: userPrompt },
  ];
 
  // Limiter les itérations pour qu'une boucle défaillante ne tourne pas indéfiniment.
  for (let step = 0; step < 6; step++) {
    const res = await kimi.chat.completions.create({
      model: KIMI_MODEL,
      messages,
      tools: toolDefs,
      temperature: 0.3,
    });
 
    const msg = res.choices[0].message;
    messages.push(msg);
 
    // Aucun appel d'outil signifie que le modèle a fini de raisonner.
    if (!msg.tool_calls || msg.tool_calls.length === 0) {
      return msg.content ?? "";
    }
 
    // Exécuter chaque outil demandé et réinjecter les résultats.
    for (const call of msg.tool_calls) {
      const handler = handlers[call.function.name];
      if (!handler) continue;
      const args = JSON.parse(call.function.arguments);
      const result = await handler(args);
      messages.push({
        role: "tool",
        tool_call_id: call.id,
        content: result,
      });
    }
  }
 
  throw new Error("Agent exceeded max iterations without finishing.");
}
 
const answer = await runAgent(
  "What is the weather in Riyadh right now, and what is that in Fahrenheit?",
);
console.log(answer);

Exécutez-le avec npm run dev src/agent.ts. Kimi K2 appellera get_weather pour Riyad, puis celsius_to_fahrenheit sur le résultat, puis composera une réponse en langage naturel. La limite d'itérations est importante : bornez toujours les boucles d'agent pour qu'un modèle désorienté ne tourne pas sans fin en consommant des jetons.

Étape 5 : Sortie JSON structurée

Pour les pipelines de données, vous voulez souvent du JSON strict plutôt que de la prose. Demandez response_format: json_object, décrivez la forme dans le message système et validez le résultat avec Zod pour qu'une réponse malformée échoue bruyamment au lieu de corrompre le code en aval.

// src/structured.ts
import { z } from "zod";
import { kimi, KIMI_MODEL } from "./client.js";
 
const Invoice = z.object({
  vendor: z.string(),
  total: z.number(),
  currency: z.string(),
  dueInDays: z.number(),
});
 
export async function extractInvoice(text: string) {
  const res = await kimi.chat.completions.create({
    model: KIMI_MODEL,
    response_format: { type: "json_object" },
    messages: [
      {
        role: "system",
        content:
          "Extract invoice fields. Respond ONLY with JSON matching: " +
          "{ vendor: string, total: number, currency: string, dueInDays: number }",
      },
      { role: "user", content: text },
    ],
    temperature: 0,
  });
 
  const raw = res.choices[0].message.content ?? "{}";
  // Valider : ne jamais faire aveuglément confiance au JSON du modèle.
  return Invoice.parse(JSON.parse(raw));
}
 
const invoice = await extractInvoice(
  "Invoice from Sfax Cloud Services for 1,240 TND, payable within 30 days.",
);
console.log(invoice);

Deux choses rendent ceci robuste : temperature: 0 pour le déterminisme, et Invoice.parse() qui lève une erreur si Kimi renvoie un champ du mauvais type. Gérez cette erreur explicitement (nouvelle tentative, journalisation ou repli) — ne l'avalez pas.

Étape 6 : Utiliser Kimi K2 dans Claude Code et Cline

Comme Moonshot expose un point d'accès compatible Anthropic, vous pouvez pointer les agents de code conçus pour Claude vers Kimi K2 avec quelques variables d'environnement — sans changement de code.

Pour Claude Code, définissez l'URL de base et la clé sur le point d'accès Anthropic de Moonshot avant le lancement :

export ANTHROPIC_BASE_URL="https://api.moonshot.ai/anthropic"
export ANTHROPIC_AUTH_TOKEN="sk-your-moonshot-key"
claude

Claude Code routera désormais ses requêtes vers Kimi K2 tout en conservant le même flux de travail en terminal. C'est un repli pratique lorsque votre fournisseur principal est limité en débit, géo-restreint ou simplement plus cher pour une tâche donnée.

Pour Cline (l'agent VS Code), ouvrez ses paramètres, choisissez le fournisseur OpenAI Compatible et renseignez :

  • Base URL : https://api.moonshot.ai/v1
  • API Key : votre clé Moonshot
  • Model ID : l'instantané K2 de l'étape 2 (par exemple kimi-k2-0711-preview)

Cline parle le dialecte OpenAI, donc le point d'accès /v1 standard est le bon ici. Enregistrez, et Cline pilote Kimi K2 pour les éditions, les appels d'outils et les commandes de terminal exactement comme n'importe quel autre modèle. Cela reflète la même approche prête à l'emploi que vous avez peut-être vue avec d'autres modèles open-weight — un changement de configuration, pas une réécriture.

Tester votre implémentation

Vérifiez chaque pièce indépendamment :

  1. Connectivité : npm run dev src/models.ts doit afficher une liste d'identifiants de modèle. Une erreur d'authentification ici signifie que la clé ou l'URL de base est incorrecte.
  2. Chat : npm run dev src/chat.ts renvoie une explication en deux phrases et un décompte de jetons.
  3. Streaming : npm run dev src/stream.ts affiche le texte progressivement, pas d'un seul coup.
  4. Agent : npm run dev src/agent.ts doit montrer une conversion en Fahrenheit dérivée d'un appel d'outil, pas un nombre halluciné.
  5. Structuré : npm run dev src/structured.ts affiche un objet typé ; alimentez-le avec un texte malformé pour confirmer que Zod lève une erreur.

Dépannage

  • 401 Non autorisé : la clé manque ou vous avez ciblé la mauvaise région. Les clés internationales utilisent api.moonshot.ai ; les comptes de Chine continentale utilisent api.moonshot.cn. Elles ne sont pas interchangeables.
  • 404 modèle introuvable : l'instantané a été retiré. Exécutez la liste des modèles de l'étape 2 et mettez à jour KIMI_MODEL.
  • tool_calls vide alors que vous en attendiez un : rendez les champs description des outils plus précis et abaissez la température. Des descriptions vagues poussent le modèle à deviner.
  • Erreurs de validation Zod : renforcez le message système avec une liste de champs explicite et gardez temperature: 0 pour l'extraction. Envisagez une nouvelle tentative automatique avant l'échec.
  • Premier jeton lent : les contextes longs augmentent la latence. Essayez un instantané turbo pour les interfaces sensibles à la latence.

Auto-héberger les poids ouverts (optionnel)

Comme Kimi K2 est livré en poids ouverts, vous n'êtes jamais enfermé dans l'API hébergée. Les équipes soumises à des exigences de résidence des données — pertinentes sous l'INPDP tunisien et la PDPL saoudienne — peuvent servir les poids à l'intérieur de leur propre périmètre de confiance avec un moteur d'inférence comme vLLM ou SGLang, tous deux exposant un serveur compatible OpenAI. La beauté du code ci-dessus, c'est que l'auto-hébergement ne change qu'une seule chose : le baseURL dans src/client.ts pointe vers votre propre point d'accès au lieu de Moonshot. Chaque assistant — chat, streaming, outils, sortie structurée — continue de fonctionner sans modification. Cette portabilité est toute la raison de bâtir contre un modèle open-weight compatible OpenAI dès le départ.

Prochaines étapes

  • Enveloppez streamChat dans un Route Handler Next.js et canalisez les deltas vers le navigateur avec un ReadableStream.
  • Ajoutez une mémoire persistante à la boucle d'agent pour que le contexte multi-tours survive entre les requêtes.
  • Combinez l'appel d'outils avec la sortie structurée pour bâtir un agent d'extraction de données typé.
  • Comparez Kimi K2 à un autre modèle open-weight dans votre propre harnais d'évaluation avant d'en adopter un comme fournisseur de secours.

Conclusion

Vous avez construit une intégration TypeScript complète pour Kimi K2 — connexion, chat, streaming, boucle d'outils agentique et sortie structurée validée — en n'utilisant que le SDK OpenAI standard plus Zod. Vous avez aussi appris à insérer le même modèle dans Claude Code et Cline via les points d'accès compatibles Anthropic et OpenAI de Moonshot, et comment le même code cible des poids ouverts auto-hébergés lorsque la conformité l'exige.

La leçon stratégique : dans un paysage où l'accès aux modèles de pointe est de plus en plus verrouillé par la géographie et la politique, un solide modèle open-weight derrière une API standard n'est pas une régression — c'est une assurance. Construisez vos abstractions contre le protocole, pas contre le fournisseur, et changer de fournisseur devient une modification de deux lignes plutôt qu'une réécriture.