🦀

Kani Framework

Rapport d'analyse approfondie — Pour intégration potentielle dans Alfred

Généré le 18 mai 2026 · Source : docs officielles, code source, exemples, changelog

📋 1 — Qu'est-ce que Kani ?

kani (カニ) est un framework Python MIT License Python 3.10+ pour construire des chatbots basés sur des LLM avec tool/function calling natif. Contrairement à LangChain ou LlamaIndex (lourds et opinionated), kani est minimaliste et hackable : il expose chaque partie du pipeline comme surchargeable.

Philosophie de conception

"Every part should be hackable." — kani ne force aucune architecture. Tu sousclasses Kani, tu surcharges les méthodes qui t'intéressent, et tu gardes le reste. C'est un framework de contrôle, pas d'abstraction.

Origine : Projet de recherche NLP (EMNLP 2023) par Andrew Zhu, Liam Dugan, Alyssa Hwang (UPenn). Statut : v1.9.1 · Beta · 601+ stars GitHub · Publiée sur PyPI.

🏗️ 2 — Architecture & Flux de base

👤 User Input
Kani.chat_round()
Engine.predict()
🔧 Function Call ?
✅ Response / 🔄 Retry

Éléments centraux

ComposantRôlePersonnalisable ?
KaniGère l'état de chat, le prompting, le function calling, le retry✅ Oui (subclassement)
EngineInterface vers le LLM (OpenAI, llama.cpp, HuggingFace, etc.)✅ Oui (nouvel engine)
ChatMessageMessage unique (role, content, function_call, extra)✅ Parts multiples
AIFunctionFonction Python exposée au modèle via @ai_function()✅ Nom, desc, retry, truncate
PromptPipelineTraduit ChatMessages → format modèle (chat template)✅ Custom pipeline

📚 3 — Gestion de l'historique & du contexte

chat_history

Liste de ChatMessage qui constitue l'état courant de la conversation. Chaque chat_round() ou full_round() ajoute les messages automatiquement.

Par défaut : Tout est conservé. C'est à toi de gérer la croissance.

always_included_messages

Messages qui ne sont jamais évictés du prompt — placés en prefix. Idéal pour le system prompt, les instructions persistantes, les rappels de contexte.

Ces messages ne sont PAS dans chat_history.

Contrôle du contexte

ParamètreEffetDétail
max_context_size Taille max du prompt en tokens Par défaut : context full du modèle. Configurable par engine.
desired_response_tokens Tokens réservés pour la réponse Par défaut : 10% du max_context ou 8192, le plus petit
get_prompt() Méthode de construction du prompt Surchageable ! Ex: garder uniquement les 4 derniers messages (voir exemple 3_customization_last_four.py)
prompt_token_len() Comptage de tokens du prompt Compte le prompt complet (messages + fonctions), pas les messages isolément (depuis v1.7.0)

💡 Exemple : Limiter l'historique aux 4 derniers messages

class LastFourKani(Kani):
    async def get_prompt(self, include_functions=True, **kwargs):
        max_len = self.max_context_size - self.desired_response_tokens
        for to_keep in range(4, 0, -1):
            token_len = await self.prompt_token_len(
                messages=self.always_included_messages + self.chat_history[-to_keep:],
                functions=list(self.functions.values()) if include_functions else None,
            )
            if token_len <= max_len:
                return self.always_included_messages + self.chat_history[-to_keep:]
        raise ValueError("Could not find a valid prompt")

ai = LastFourKani(engine)  # Ne garde que 4 messages max

Sauvegarde & chargement

Kani supporte Kani.save() et Kani.load() pour persister l'état complet (chat_history, functions, etc.). Utile pour reprendre une conversation plus tard ou sauvegarder l'état d'un agent.

🔁 4 — Fonction Calling & Contrôle du flux

Comment ça marche

  1. Tu sousclasses Kani et ajoutes des méthodes avec @ai_function()
  2. Le modèle reçoit les signatures + docstrings des fonctions dans le prompt
  3. Le modèle décide d'appeler une fonction → retourne un function_call dans le message
  4. Kani exécute ta méthode Python avec les paramètres validés
  5. Le résultat est renvoyé au modèle comme message de rôle function
  6. Le modèle génère sa réponse finale (ou appelle une autre fonction)

full_round() — Boucle complète

async for msg in ai.full_round("prompt")

Itère sur TOUS les messages générés : assistant → function_call → assistant → ... jusqu'à ce que le modèle ne demande plus de fonction.

Par défaut : boucle infinie jusqu'à ce que le modèle arrête de demander des fonctions.

max_function_rounds=N pour limiter.

chat_round() — Simple tour

msg = await ai.chat_round("prompt")

Un seul tour user → model. Pas de function calling.

Pour Alfred : utile quand tu ne veux pas de tool calling, ou quand tu gères le calling toi-même.

Retry automatique

ParamètreValeur par défautDescription
retry_attempts1Nombre de tentatives si le modèle se plante dans un function call
auto_retryTruePar fonction : le modèle retry-t-il automatiquement ?

En cas d'erreur (params invalides, exception), l'erreur est renvoyée au modèle dans un message système. Le modèle a retry_attempts chances de se rattraper.

after=ChatRole.USER — Intervention humaine

Un @ai_function(after=ChatRole.USER) fait que le contrôle revient à l'utilisateur après l'exécution de la fonction. Utile pour des fonctions qui nécessitent une confirmation humaine.

5 — Modes de streaming

Contrairement à LM Studio : Kani stream TOKEN PAR TOKEN

LM Studio renvoie des messages complets séparés (pas de token streaming). Kani permet un streaming granulaire au niveau du token, même en présence de function calling. C'est un avantage majeur pour le UX temps réel.

chat_round_stream()

Stream token par token d'un seul tour.

stream = ai.chat_round_stream("hello")
async for token in stream:
    print(token, end="")
msg = await stream.message()

full_round_stream()

Stream pour chaque message dans une boucle de function calling.

async for stream in ai.full_round_stream("hello"):
    async for token in stream:
        print(token, end="")
    print()  # newline entre les messages

chat_round() / full_round()

Mode non-streaming : attend la fin et retourne le message complet.

Utiliser quand : pas besoin de temps réel, plus simple.

Particularité : streaming avec function calling

full_round_stream() retourne un StreamManager par message. Chaque StreamManager a un attribut .role pour savoir si le message vient de l'engine ou d'un function call.

Avantage pour Alfred : Tu peux stream la réponse du modèle en temps réel vers Telegram (si l'engine le supporte), ce qui donne un UX bien plus fluide que les messages blocs de LM Studio.

🔌 6 — Engines & Backends supportés

🤖
OpenAI
GPT-4.1, GPT-5-nano, etc.
Function Call ✓ Multimodal ✓ Streaming ✓
🦙
llama.cpp
GGUF local, CPU/GPU
Function Call ✓ Multimodal ✗ Streaming ✓
🤗
HuggingFace
Transformers, Chat Templates
Function Call ✓ Multimodal ✓ Streaming ✓
🔷
Anthropic
Claude Sonnet, Opus
Function Call ✓ Multimodal ✓ Streaming ✓
🔵
Google AI
Gemini, Gemma
Function Call ✓ Multimodal ✓ Streaming ✓
🚀
vLLM
Serveur haute perf
Function Call ✓ Multimodal ✗ Streaming ✓

LlamaCppEngine — Pour ton cas d'usage local

C'est l'engine qui t'intéresse pour remplacer LM Studio. kani utilise llama-cpp-python comme binding vers le runtime llama.cpp.

from kani.engines.llamacpp import LlamaCppEngine

# Le plus simple : kani détecte automatiquement le chat template
engine = LlamaCppEngine(
    repo_id="microsoft/Phi-3.5-mini-instruct",
    filename="*Q4_K_M.gguf",
)

# Ou avec un chemin local
engine = LlamaCppEngine(
    model_path="/chemin/vers/model.gguf",
    max_context_size=8192,
    model_load_kwargs={"n_gpu_layers": -1},  # Tout sur GPU
)

ai = Kani(engine)

GPU : n_gpu_layers=-1 dans model_load_kwargs charge tout sur GPU. Supporte CUDA, Metal, Vulkan via llama-cpp-python.

OpenAI-compatible server (alternative à LM Studio)

Si tu as un serveur compatible OpenAI (Ollama, LM Studio en mode server, etc.) :

from kani.engines.openai import OpenAIEngine
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("model-name")
engine = OpenAIEngine(
    model="local-model",
    api_key="dummy",
    api_base="http://127.0.0.1:1234/v1",  # LM Studio / Ollama
    tokenizer=tokenizer,
)

⚙️ 7 — Spécifications techniques

📦 Installation

# Base + llama.cpp
pip install "kani[cpp]"

# Tout (recommandé)
pip install "kani[all]"

# Dev version
pip install "kani[all] @ git+https://github.com/zhudotexe/kani.git@main"

📊 Dépendances

DépendancePoidsUsage
pydanticLégerValidation des params de fonction
llama-cpp-pythonMoyenBinding llama.cpp (GGUF)
transformersLourdHuggingFace engine
openaiLégerEngine OpenAI
tiktokenLégerToken counting (OpenAI)
sentencepieceLégerTokenization (llama.cpp)

💾 Poids & Performance

AspectÉvaluationDétail
Poids du framework Très léger Seul pydantic est requis en base. Les engines sont optionnels ([extra]).
llama-cpp-python Moyen Compile du C++ via binding. ~50-100MB. Nécessite des deps système (cmake, etc.).
Performance inference Bon Utilise llama.cpp directement — mêmes perf que LM Studio. GPU support via CUDA/Metal/Vulkan.
Async Full async Tout est async/await. Compatible avec FastAPI, asyncio event loop, etc.
Python 3.10+ Requiert Python 3.10 minimum (type annotations, match, etc.).
Memory footprint Faible Le framework lui-même est minimal. La RAM dépend du modèle chargé (comme avec LM Studio).

🚀 8 — Fonctionnalités avancées

🔗 MCP Tools

Support natif du Model Context Protocol (v1.8.0). Connecte des serveurs MCP locaux ou distants pour exposer des outils au modèle.

from kani.mcp import tools_from_mcp_servers

async with tools_from_mcp_servers(servers) as tools:
    async for msg in ai.full_round("prompt", functions=tools):
        ...

👶 Sub-kanis (Multi-Agent)

Un kani peut en créer d'autres depuis un @ai_function(). Chaque sub-kani a son propre engine, son propre contexte.

@ai_function()
async def summarize():
    sub = Kani(engine, chat_history=self.chat_history[:-2])
    return await sub.chat_round_str("Summarize this.")

🖼️ Multimodal

Images, audio, vidéo via kani[multimodal]. ImagePart.from_file(), AudioPart.from_file(), etc.

msg = await ai.chat_round_str([
    "Describe this:",
    ImagePart.from_file("cat.png"),
])

📺 CLI intégré

Chat avec n'importe quel modèle directement en terminal :

kani openai:gpt-4.1-nano
kani huggingface:meta-llama/Meta-Llama-3-8B
kani llama.cpp:./model.gguf

🎨 Personnalisation profonde

Toutes ces méthodes de Kani sont surchargeables :

MéthodeRôleExemple d'usage
get_prompt()Construit le prompt completLimiter à 4 messages, ajouter du few-shot
update_history()Gère l'ajout d'un message à l'historiqueCompression, summarization auto
handle_function_call()Exécute un function callLogging, validation custom
handle_function_exception()Gère les erreurs de function callRetry custom, fallback

🦾 9 — Pertinence pour Alfred

✅ Points forts pour Alfred

  • Function calling propre — Alternative bien structurée à LM Studio pour le tool calling local
  • Streaming token-by-token — UX temps réel (vs LM Studio qui renvoie des messages blocs)
  • Model-agnostic — Peut switcher entre llama.cpp, OpenAI, HuggingFace sans changer de framework
  • Async natif — Compatible avec ton architecture actuelle d'agent
  • Poids minimal — Pas de dépendance lourde comme LangChain
  • Sub-kanis — Architecture multi-agent native (ex: un kani pour le chat, un autre pour la recherche)
  • MCP support — Accès à l'écosystème d'outils MCP (recherche web, fichiers, etc.)
  • Save/Load state — Persistance de conversation intégrée

⚠️ Points de vigilance

  • Statut Beta — API peut changer. Pas de garantie de stabilité à long terme.
  • llama.cpp function calling — Dépend de la capacité du modèle GGUF à faire du function calling (les modèles Mistral, Phi, Qwen fonctionnent mieux).
  • Documentation en anglais — Pas de docs FR.
  • Community petite — 601 stars, communauté modeste. Moins de resources si tu bloques.
  • llama-cpp-python build — Peut être capricieux à installer selon la plateforme (deps C++, CUDA, etc.).

🔄 Comparaison : Kani vs LM Studio pour Alfred

AspectKani + llama.cppLM Studio
Function callingNatif, structuréVia API OpenAI compatible
StreamingToken par tokenMessages blocs
ContrôleTotal (code)Limité (GUI/API)
Prompt customChaque promptSystem prompt only
Multi-engineOpenAI, HF, Claude, Google...Local uniquement
Poids~50MB (llama-cpp-python)~200MB (app desktop)
ComplexitéCode PythonGUI, zero-code
StabilitéBetaStable

🎯 Recommandation pour Alfred

Kani est un excellent candidat pour remplacer ou compléter LM Studio dans Alfred. Le function calling structuré, le streaming token-by-token, et la capacité de customiser chaque étape du pipeline en font un framework plus adapté qu'un simple serveur LM Studio pour un agent autonome.

Priorité d'intégration : Commencer par LlamaCppEngine avec un modèle GGUF compatible function calling (Phi-3.5, Mistral 7B Instruct, Qwen 2.5). Tester le function calling avec les outils d'Alfred (recherche, génération audio/image, etc.).

💡 10 — Exemple complet : Alfred avec Kani + llama.cpp

import asyncio
from typing import Annotated
from kani import Kani, ai_function
from kani.engines.llamacpp import LlamaCppEngine

# 1. Engine llama.cpp
engine = LlamaCppEngine(
    repo_id="microsoft/Phi-3.5-mini-instruct",
    filename="*Q4_K_M.gguf",
    model_load_kwargs={"n_gpu_layers": -1},  # GPU
)

# 2. Subclass Kani avec tes outils
class AlfredKani(Kani):
    @ai_function()
    def search_web(self, query: str) -> str:
        """Search the web for information."""
        # Appel à DuckDuckGo, etc.
        return f"Results for: {query}"

    @ai_function()
    def generate_image(self, description: str) -> str:
        """Generate an image from a text description."""
        # Appel à SDXL, etc.
        return f"Image generated: {description}"

    @ai_function()
    def generate_audio(self, text: str) -> str:
        """Generate speech audio from text."""
        # Appel à TTS, etc.
        return f"Audio generated for: {text}"

    @ai_function(after=Kani.ChatRole.USER)
    def ask_user(self, question: str) -> str:
        """Ask the user a question and wait for their answer."""
        return question  # Retourne le contrôle à l'utilisateur

# 3. Instanciation
ai = AlfredKani(
    engine,
    system_prompt="You are Alfred, a helpful assistant.",
    retry_attempts=2,
)

# 4. Utilisation
async def main():
    # Simple chat
    msg = await ai.chat_round("Salut, comment tu vas ?")
    print(msg.text)

    # Avec function calling
    async for part in ai.full_round("Peux-tu chercher 'chat funny' et me générer une image de chat ?"):
        if hasattr(part, 'text') and part.text:
            print(part.text, end="")

asyncio.run(main())

📝 11 — Versions & Changelog récent

VersionDateChangements majeurs
v1.9.1RécemmentSupport Qwen-3.5, fixes tool parsing, CLI streaming
v1.9.0RécemmentOpenAI Responses API, fixes multi-turn tool calling
v1.8.0RécemmentMCP Tools natif, auto JSON serialization, multimodal returns
v1.7.0RécemmentToken counting refacto, HF multimodal, auto_truncate en chars
v1.6.1RécemmentRemoval deprecated HTTPClient, fixes reasoning tokens
v1.6.0RécemmentMultimodal (images/audio/vidéo), Gemini, CLI intégré

📊 12 — Résumé & Checklist d'intégration

🦀 En bref

Kani est un framework Python léger (~50MB avec deps) pour chat-based LLMs avec function calling natif, streaming token-by-token, et support multi-engine (OpenAI, llama.cpp, HuggingFace, Anthropic, Google, vLLM). Moins de 1000 lignes de code core. Architecture hackable via sous-classement de Kani. Alternative sérieuse à LM Studio pour un agent Python qui a besoin de contrôle fin sur le pipeline LLM.

📋 Checklist d'intégration pour Alfred

#TâchePriorité
1Installer pip install "kani[cpp]" et tester avec un modèle GGUFHaute
2Tester le function calling avec 2-3 fonctions simples (search, etc.)Haute
3Comparer perf inference (tokens/sec) vs LM StudioMoyenne
4Implémenter un AlfredKani subclass avec les outils actuelsMoyenne
5Tester le streaming token-by-tokenBasse
6Tester le save/load de l'état de conversationBasse
7Évaluer le MCP tools pour écosystème d'outilsBasse
8Remplacer progressivement LM Studio si tout fonctionneFuture