Rapport d'analyse approfondie — Pour intégration potentielle dans Alfred
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.
"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.
| Composant | Rôle | Personnalisable ? |
|---|---|---|
Kani | Gère l'état de chat, le prompting, le function calling, le retry | ✅ Oui (subclassement) |
Engine | Interface vers le LLM (OpenAI, llama.cpp, HuggingFace, etc.) | ✅ Oui (nouvel engine) |
ChatMessage | Message unique (role, content, function_call, extra) | ✅ Parts multiples |
AIFunction | Fonction Python exposée au modèle via @ai_function() | ✅ Nom, desc, retry, truncate |
PromptPipeline | Traduit ChatMessages → format modèle (chat template) | ✅ Custom pipeline |
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.
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.
| Paramètre | Effet | Dé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) |
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
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.
Kani et ajoutes des méthodes avec @ai_function()function_call dans le messagefunctionasync 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.
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.
| Paramètre | Valeur par défaut | Description |
|---|---|---|
retry_attempts | 1 | Nombre de tentatives si le modèle se plante dans un function call |
auto_retry | True | Par 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.
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.
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.
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()
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
Mode non-streaming : attend la fin et retourne le message complet.
Utiliser quand : pas besoin de temps réel, plus simple.
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.
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.
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,
)
# 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épendance | Poids | Usage |
|---|---|---|
pydantic | Léger | Validation des params de fonction |
llama-cpp-python | Moyen | Binding llama.cpp (GGUF) |
transformers | Lourd | HuggingFace engine |
openai | Léger | Engine OpenAI |
tiktoken | Léger | Token counting (OpenAI) |
sentencepiece | Léger | Tokenization (llama.cpp) |
| Aspect | Évaluation | Dé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). |
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):
...
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.")
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"),
])
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
Toutes ces méthodes de Kani sont surchargeables :
| Méthode | Rôle | Exemple d'usage |
|---|---|---|
get_prompt() | Construit le prompt complet | Limiter à 4 messages, ajouter du few-shot |
update_history() | Gère l'ajout d'un message à l'historique | Compression, summarization auto |
handle_function_call() | Exécute un function call | Logging, validation custom |
handle_function_exception() | Gère les erreurs de function call | Retry custom, fallback |
| Aspect | Kani + llama.cpp | LM Studio |
|---|---|---|
| Function calling | Natif, structuré | Via API OpenAI compatible |
| Streaming | Token par token | Messages blocs |
| Contrôle | Total (code) | Limité (GUI/API) |
| Prompt custom | Chaque prompt | System prompt only |
| Multi-engine | OpenAI, HF, Claude, Google... | Local uniquement |
| Poids | ~50MB (llama-cpp-python) | ~200MB (app desktop) |
| Complexité | Code Python | GUI, zero-code |
| Stabilité | Beta | Stable |
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.).
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())
| Version | Date | Changements majeurs |
|---|---|---|
| v1.9.1 | Récemment | Support Qwen-3.5, fixes tool parsing, CLI streaming |
| v1.9.0 | Récemment | OpenAI Responses API, fixes multi-turn tool calling |
| v1.8.0 | Récemment | ⭐ MCP Tools natif, auto JSON serialization, multimodal returns |
| v1.7.0 | Récemment | Token counting refacto, HF multimodal, auto_truncate en chars |
| v1.6.1 | Récemment | Removal deprecated HTTPClient, fixes reasoning tokens |
| v1.6.0 | Récemment | ⭐ Multimodal (images/audio/vidéo), Gemini, CLI intégré |
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.
| # | Tâche | Priorité |
|---|---|---|
| 1 | Installer pip install "kani[cpp]" et tester avec un modèle GGUF | Haute |
| 2 | Tester le function calling avec 2-3 fonctions simples (search, etc.) | Haute |
| 3 | Comparer perf inference (tokens/sec) vs LM Studio | Moyenne |
| 4 | Implémenter un AlfredKani subclass avec les outils actuels | Moyenne |
| 5 | Tester le streaming token-by-token | Basse |
| 6 | Tester le save/load de l'état de conversation | Basse |
| 7 | Évaluer le MCP tools pour écosystème d'outils | Basse |
| 8 | Remplacer progressivement LM Studio si tout fonctionne | Future |