DEV Community

Olivier
Olivier

Posted on

Brancher Kimi K3 dans une boucle agentique : les 4 trucs qui ont cassé

L'API Kimi K3 parle le dialecte Anthropic. Brancher notre boucle agentique dessus nous a pris vingt minutes ; la faire tourner proprement nous a pris deux jours.

On a passé une feature réelle dans les deux : ajout d'un export CSV filtré dans un dashboard Next.js + Supabase, six fichiers touchés, tests inclus. Même prompt de départ, même arborescence, même définition de « terminé » (tests au vert, lint propre). Kimi K3 a fini, mais il a mis 1,5× plus de temps et coûté 2,84 $ là où Claude était déjà payé par le forfait. Voilà le détail, les mesures, et les quatre endroits où le montage casse.

Le montage : vingt minutes

Moonshot expose un endpoint compatible avec le format messages d'Anthropic, donc si ta boucle est déjà écrite contre le SDK Anthropic, tu ne changes que deux lignes :

export KIMI_API_KEY="sk-..."
export KIMI_BASE_URL="https://api.moonshot.ai/anthropic"
Enter fullscreen mode Exit fullscreen mode
from anthropic import Anthropic

client = Anthropic(
    api_key=os.environ["KIMI_API_KEY"],
    base_url=os.environ["KIMI_BASE_URL"],
)

resp = client.messages.create(
    model="kimi-k3",
    max_tokens=8192,
    tools=TOOLS,          # mêmes schémas que chez Anthropic
    messages=history,
)
Enter fullscreen mode Exit fullscreen mode

Notre banc, pour que ce soit reproductible :

  • boucle maison, 12 outils (read, write, edit, bash, grep, test…), pas de framework
  • streaming activé, un seul thread, aucune parallélisation d'outils
  • même machine, même connexion, mesures prises entre 14 h et 17 h le 21/07
  • contexte de départ : 34 fichiers épinglés, ~28 000 tokens
  • au 22/07, les poids ne sont pas publics : tout passe par l'API hébergée

Le premier run est parti du premier coup, et c'est précisément ce qui rend la suite piégeuse : ça marche, donc on ne regarde pas la facture tout de suite.

Piège 1 — le cache de prompt ne se déclenche pas comme chez Anthropic

Notre premier run a coûté 7,10 $. Le second, à travail identique, 2,84 $. Entre les deux, on n'a touché à aucun outil ni à aucun prompt : on a figé l'ordre du préfixe.

Chez Anthropic, tu marques explicitement les blocs à mettre en cache avec cache_control. Kimi ne fonctionne pas comme ça : le cache est un cache de préfixe automatique, facturé 0,30 $/M au lieu de 3 $/M en entrée, et il ne se déclenche que si les premiers octets de la requête sont strictement identiques d'un tour à l'autre. Notre boucle sérialisait les outils depuis un dict, donc l'ordre bougeait ; et on injectait un horodatage dans le system prompt. Deux détails, une facture multipliée par dix sur l'entrée.

# le préfixe doit être identique octet pour octet, à chaque tour
PREFIX = [SYSTEM_BLOCK_STATIC, *sorted(TOOLS, key=lambda t: t["name"])]
# l'horodatage, les compteurs, l'état : à la FIN, jamais au début
Enter fullscreen mode Exit fullscreen mode

Le champ cache_control est accepté sans erreur, mais il ne fait rien. C'est le pire cas de figure : pas d'exception, juste une facture.

Piège 2 — les arguments d'outils ne reviennent pas toujours propres

Sur 46 tours, on a eu 3 blocs tool_use dont les arguments arrivaient en chaîne de caractères au lieu d'objet, dont un enveloppé dans des barrières de code markdown. Rien de dramatique quand tu le sais, une exception non rattrapée quand tu ne le sais pas. Le schéma JSON est suivi dans le fond — les clés étaient bonnes — mais l'enveloppe ne l'est pas toujours.

def parse_tool_input(block):
    raw = block.input
    if isinstance(raw, str):
        raw = json.loads(re.sub(r"^```

(?:json)?|

```$", "", raw.strip()))
    return raw
Enter fullscreen mode Exit fullscreen mode

Deux autres différences à traiter avant de lancer quoi que ce soit sur un dépôt réel :

  • appels parallèles : K3 renvoie volontiers plusieurs blocs tool_use dans une même réponse. Si ta boucle n'en exécute qu'un et jette le reste, tu perds des tours sans t'en apercevoir.
  • stop_reason : la valeur max_tokens tombe plus souvent qu'avec Claude sur les gros patchs. Prévois la reprise, sinon tu écris des fichiers tronqués.

Piège 3 — la lenteur compose

Le débit annoncé, 62 tokens/s, est en dessous de la médiane du marché (71 selon Artificial Analysis). Sur une réponse isolée, ça ne se sent pas. Dans une boucle qui enchaîne quarante tours, en revanche, ça se paie deux fois : le modèle est plus lent et il a eu besoin de plus de tours pour converger.

Mesure (même feature) Kimi K3 (API) Claude (Claude Code)
Tours d'agent 46 38
Tokens de sortie cumulés 31 200 24 900
Débit médian 62 t/s 78 t/s
Time-to-first-token médian 1,9 s 1,1 s
Temps au mur 11 min 50 7 min 50
Tests au vert au premier passage non (2 reprises) oui
Coût direct de la tâche 2,84 $ inclus au forfait

-20 % de débit et +21 % de tours, ça ne fait pas -41 % : ça fait un facteur 1,5 sur le temps réel. C'est la seule métrique que je regarde encore, parce que c'est celle que tu subis quand tu attends devant ton terminal.

Piège 4 — la fenêtre d'un million est un piège de facturation

Un contexte de 1 M tokens, dans une boucle agentique, c'est une invitation à ne jamais compacter. Sauf que ta boucle renvoie tout l'historique à chaque tour : le coût d'entrée croît de façon quadratique avec le nombre de tours, et c'est l'entrée qui domine la facture, pas la sortie. Sur notre run, les 31 200 tokens générés ont coûté 0,47 $ — le reste, 2,37 $, c'est du contexte renvoyé en boucle.

Avec un forfait mensuel, cette dérive est invisible et tu peux te permettre d'être négligent ; au token, elle est linéaire sur ta carte bleue. On a fini par imposer une compaction dure à 120 000 tokens, ce qui a coupé la facture d'un tiers sans dégrader le résultat. Le raisonnement économique complet — forfait contre compteur, et pourquoi tous les comparatifs se trompent de grille — est dans notre article de fond.

Ce qu'on garde

K3 est premier au Frontend Code Arena, et sur du composant one-shot bien cadré, ça se voit : le HTML/CSS sort propre, souvent mieux structuré. On l'a gardé pour ça, appelé ponctuellement depuis nos scripts, jamais comme moteur de la boucle.

Pour un chantier multi-fichiers avec tests, on reste sur Claude Code — pas par attachement, mais parce que le harness fait le gros du travail et qu'aucune économie au token ne rattrape 4 minutes de plus par feature, plusieurs fois par jour. Si tu veux le détail de ce qu'un harness ajoute réellement à un modèle, on l'a documenté dans notre guide Claude Code.

Réserve honnête : les poids sont annoncés pour le 27/07, et tant qu'ils ne sont pas tombés, tout ce qui précède mesure une API hébergée, pas un modèle. On refera le banc après la publication, et on postera les chiffres bruts — y compris s'ils nous contredisent.

Tu veux monter ce genre de boucle proprement plutôt que de la déboguer deux jours ? Notre formation Claude Code part exactement de là.

Top comments (0)