Cheatsheets — vue longue
retour à la listeToutes les pages concaténées sur un seul document, pour un Ctrl-F direct.
LangChain
page dédiée →API en mouvement rapide. Ce qui suit couvre le cœur stable — LCEL, modèles de chat, prompts, parsers, retrievers. Vérifier contre docs.langchain.com avant de s'appuyer sur un détail.
Installation
uv add langchain langchain-openai langchain-anthropic langchain-community
Les intégrations sont dans des paquets séparés depuis la 0.1 : le cœur ne dépend d'aucun fournisseur.
Appeler un modèle
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage, SystemMessage
llm = ChatAnthropic(model="claude-sonnet-4-5", temperature=0, max_tokens=1024)
resp = llm.invoke([
SystemMessage("Tu réponds en une phrase."),
HumanMessage("Qu'est-ce qu'un agent computer use ?"),
])
print(resp.content)
Un agent computer use est un système qui perçoit l'écran et agit via souris et clavier
pour accomplir des tâches à la place d'un utilisateur.
Quatre méthodes sur tout composant : invoke, batch, stream, et leurs variantes
asynchrones ainvoke, abatch, astream.
LCEL, l'opérateur |
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
prompt = ChatPromptTemplate.from_messages([
("system", "Tu es un analyste e-commerce concis."),
("human", "Le produit {produit} est-il trouvable sur {site} ?"),
])
chain = prompt | llm | StrOutputParser()
print(chain.invoke({"produit": "Najim 100 ml", "site": "versace.com"}))
Chaque maillon reçoit la sortie du précédent. StrOutputParser extrait .content du
message, sinon on manipule un objet AIMessage.
Sortie structurée
from pydantic import BaseModel, Field
class Findability(BaseModel):
findable: bool = Field(description="Produit atteignable par la navigation")
clicks: int = Field(description="Nombre de clics jusqu'à la fiche produit")
path: list[str] = Field(default_factory=list)
structured = llm.with_structured_output(Findability)
result = structured.invoke("Sur versace.com, combien de clics pour Najim 100 ml ?")
print(result.clicks, result.findable)
3 True
with_structured_output s'appuie sur le function calling natif du fournisseur. C'est plus
fiable qu'un JsonOutputParser derrière un prompt qui supplie de rendre du JSON.
Tools et agents
from langchain_core.tools import tool
@tool
def get_price(sku: str) -> float:
"""Retourne le prix TTC d'un SKU."""
return 129.0
llm_with_tools = llm.bind_tools([get_price])
msg = llm_with_tools.invoke("Quel est le prix du SKU AB-12 ?")
print(msg.tool_calls)
[{'name': 'get_price', 'args': {'sku': 'AB-12'}, 'id': 'toolu_01X…', 'type': 'tool_call'}]
Le décorateur @tool dérive le schéma des annotations de type et la description de la
docstring — donc la docstring est un élément fonctionnel, pas un commentaire.
bind_tools ne fait que proposer l'appel : c'est à la boucle d'exécuter l'outil et de
renvoyer un ToolMessage. Pour la boucle complète, passer à langgraph.
RAG minimal
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
docs = PyPDFLoader("manuel.pdf").load()
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=150)
chunks = splitter.split_documents(docs)
store = FAISS.from_documents(chunks, OpenAIEmbeddings(model="text-embedding-3-small"))
retriever = store.as_retriever(search_kwargs={"k": 4})
RecursiveCharacterTextSplitter coupe d'abord sur les paragraphes, puis les phrases, puis
les mots. C'est le défaut raisonnable ; chunk_overlap évite de trancher une idée en deux.
from langchain_core.runnables import RunnablePassthrough
template = ChatPromptTemplate.from_template(
"Réponds uniquement à partir du contexte.\n\nContexte:\n{context}\n\nQuestion: {question}"
)
def format_docs(docs):
return "\n\n".join(d.page_content for d in docs)
rag = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| template
| llm
| StrOutputParser()
)
print(rag.invoke("Quelle est la garantie ?"))
RunnablePassthrough laisse passer l'entrée telle quelle pendant que l'autre branche va
chercher les documents. Les deux branches du dict s'exécutent en parallèle.
Streaming
for chunk in chain.stream({"produit": "Najim", "site": "versace.com"}):
print(chunk, end="", flush=True)
Observabilité
export LANGCHAIN_TRACING_V2=true
export LANGCHAIN_API_KEY=ls__...
export LANGCHAIN_PROJECT=mon-projet
Toutes les exécutions apparaissent alors dans LangSmith, avec les prompts réels, les latences et les tokens. C'est le principal argument pour rester dans l'écosystème.
Quand ne pas l'utiliser
Pour un simple appel à un modèle avec un prompt, le SDK du fournisseur suffit et se debugge mieux. LangChain se justifie quand on veut changer de fournisseur sans réécrire, brancher des retrievers existants, ou tracer dans LangSmith.
See also
LangGraph
page dédiée →API en mouvement. Le cœur —
StateGraph, nœuds, arêtes conditionnelles, checkpointer — est stable. Vérifier les détails sur docs.langchain.com.
Orchestration d'agents comme graphe d'états. Là où une chaîne LCEL est un pipeline linéaire, LangGraph autorise les cycles, les branchements et la reprise après interruption.
L'idée
Un état partagé, des nœuds qui le transforment, des arêtes qui décident du nœud suivant. Chaque nœud reçoit l'état et renvoie les clés qu'il modifie, pas l'état entier.
uv add langgraph langchain-anthropic
Graphe minimal
from typing import Annotated, TypedDict
from operator import add
from langgraph.graph import StateGraph, START, END
class State(TypedDict):
site: str
steps: Annotated[list[str], add] # les mises à jour s'accumulent
verdict: str | None
def navigate(state: State) -> dict:
return {"steps": [f"ouverture de {state['site']}"]}
def judge(state: State) -> dict:
return {"verdict": "findable" if len(state["steps"]) < 5 else "unreachable"}
builder = StateGraph(State)
builder.add_node("navigate", navigate)
builder.add_node("judge", judge)
builder.add_edge(START, "navigate")
builder.add_edge("navigate", "judge")
builder.add_edge("judge", END)
graph = builder.compile()
print(graph.invoke({"site": "versace.com", "steps": [], "verdict": None}))
{'site': 'versace.com', 'steps': ['ouverture de versace.com'], 'verdict': 'findable'}
Annotated[list, add] est le mécanisme central : sans le réducteur, chaque nœud
écraserait steps. Avec, les listes se concatènent. Pour les messages,
langgraph.graph.message.add_messages gère aussi la déduplication par id.
Branchement conditionnel
def should_retry(state: State) -> str:
if state["verdict"] == "unreachable" and len(state["steps"]) < 20:
return "navigate"
return END
builder.add_conditional_edges("judge", should_retry, ["navigate", END])
La fonction renvoie le nom du nœud suivant. C'est ce qui crée les cycles, et donc les boucles d'agent.
Boucle agent avec outils
from langgraph.prebuilt import create_react_agent
from langchain_core.tools import tool
@tool
def click(selector: str) -> str:
"""Clique sur un élément et retourne le nouvel état de la page."""
return f"cliqué sur {selector}"
agent = create_react_agent(llm, tools=[click])
out = agent.invoke({"messages": [("user", "Trouve Najim 100 ml sur versace.com")]})
print(out["messages"][-1].content)
create_react_agent monte la boucle standard : le modèle propose un appel, un nœud
ToolNode l'exécute, le résultat repart au modèle, jusqu'à une réponse sans tool call.
Pour tout contrôle fin — budget d'étapes, vérification, sous-agents — écrire le graphe à la
main.
Persistance et reprise
from langgraph.checkpoint.memory import MemorySaver
graph = builder.compile(checkpointer=MemorySaver())
config = {"configurable": {"thread_id": "session-42"}}
graph.invoke({"site": "versace.com", "steps": [], "verdict": None}, config)
graph.invoke({"site": "dior.com"}, config) # reprend le même fil
print(graph.get_state(config).values["steps"])
Le thread_id identifie une conversation. En production, remplacer MemorySaver par un
checkpointer SQLite ou Postgres pour survivre au redémarrage.
Interruption humaine
graph = builder.compile(checkpointer=MemorySaver(), interrupt_before=["judge"])
graph.invoke(initial, config) # s'arrête avant "judge"
state = graph.get_state(config)
graph.update_state(config, {"verdict": "override"})
graph.invoke(None, config) # None = reprendre où on s'est arrêté
C'est le mécanisme des portes d'approbation avant une action irréversible — indispensable dès qu'un agent écrit quelque part.
Suivre l'exécution
for event in graph.stream(initial, config, stream_mode="values"):
print(event["steps"][-1] if event["steps"] else "…")
stream_mode : values (l'état complet à chaque étape), updates (seulement les deltas),
messages (les tokens du LLM).
Budget d'étapes
graph.invoke(initial, {"recursion_limit": 25, **config})
Au-delà, GraphRecursionError. Un agent qui boucle sans progresser est le mode d'échec par
défaut — le budget est une sécurité, pas une optimisation.
Visualiser
print(graph.get_graph().draw_mermaid())
graph TD;
__start__ --> navigate;
navigate --> judge;
judge -.-> navigate;
judge -.-> __end__;
Utile en démo client : le diagramme se colle tel quel dans un document.
LangChain ou LangGraph
Chaîne linéaire, un aller-retour, pas d'état : LCEL suffit. Cycles, outils, reprise après échec, validation humaine : LangGraph. Les deux se composent — un nœud de graphe peut être une chaîne LCEL.
See also
Vision Encoders
page dédiée →Le vrai défi des vision encoders pour LLM n'est pas la perception mais la compression : un document de 50 pages à 4000 tokens/page sature un contexte de 256k avant toute analyse.
Le problème de budget de tokens
Les encoders classiques (CLIP, SigLIP) produisent des représentations coûteuses :
| Résolution | Tokens (classique) | Budget consommé (50 pages) |
|---|---|---|
| 1024×1024 | 4,096 | 204,800 |
| 1280×1280 | 6,400 | 320,000 |
Une image devient un texte de plusieurs milliers de mots. Pour les tâches long-contexte (analyse de documents, archives, vidéos), cette inefficience bloque des classes entières d'applications.
Architecture à deux étages
La clé : comprimer avant l'attention globale, pas après.
# Ordre conventionnel (inefficace)
patches = patchify(image) # 4096 tokens
features = global_attention(patches) # O(4096²) mémoire
compressed = project(features) # 256 tokens
# Ordre optimisé (DeepSeek-OCR)
patches = patchify(image) # 4096 tokens
local = window_attention(patches) # O(4096 × window²)
compressed = conv_4x4_stride4(local) # 256 tokens (16× compression)
features = global_attention(compressed) # O(256²) — 256× moins de mémoire
La compression spatiale (stride convolution) réduit la grille de 64×64 à 16×16 avant que l'attention quadratique ne s'applique.
Modes de fonctionnement explicites
Traiter les tokens comme un budget de première classe :
| Mode | Tokens/page | Résolution | Précision OCR | Cas d'usage |
|---|---|---|---|---|
| Tiny | 64 | 512×512 | ~85% | Screening haute-volume |
| Small | 100 | 640×640 | ~92% | Documents standards |
| Base | 256 | 1024×1024 | ~97% | Articles techniques |
| Large | 400 | 1280×1280 | ~98.5% | Détails fins |
Pour les grandes pages, stratégie « Gundam » : n_tiles × 100 + 256 (vue globale).
Entraînement bout-en-bout
Le CLIP-style transformer sert de backbone mais sans contrastive learning.
# Gradient depuis la reconstruction de texte
loss = cross_entropy(decoder_output, ground_truth_text)
loss.backward() # Flow : text loss → cross-attention → vision encoder
# Le goulot de compression force la sélection de features
# Seules les features prédictives du texte survivent
Le bottleneck de 16× agit comme un filtre d'information : l'encoder apprend à garder ce qui prédit les caractères, écarter le reste (texture, style).
Résultats empiriques
- Fox benchmark : 97% de précision OCR à 9-10× compression
- Throughput : 200,000 pages/jour sur A100
- Mémoire : 256× moins pour l'attention globale (256² vs 4096²)
Compression vs perception
Le choix n'est pas binaire mais dépend de la tâche :
| Approche | Tokens/image | Quand l'utiliser |
|---|---|---|
| Compressée (DeepSeek-OCR) | 64-400 | Documents longs, OCR, archivage, génération de données |
| Généraliste (Qwen2.5-VL) | 1024-4096 | Raisonnement visuel complexe, diagrammes, agents |
| Haute fidélité (Qwen3-VL) | 4096-16384 | Vidéos longues, spatial reasoning, détails géométriques |
La compression sacrifie la richesse du raisonnement visuel au profit de l'efficience contextuelle.
Placement de la compression
Principe de design : identifier où réduire la dimensionnalité.
# Anti-pattern : opérations quadratiques sur haute résolution
expensive = global_attention(high_res_tokens) # Puis compresser
# Pattern optimal : comprimer avant scaling quadratique
compressed = spatial_downsample(high_res_tokens)
cheap = global_attention(compressed)
La position du compresseur dans le pipeline détermine les caractéristiques d'efficience du système.
Questions ouvertes
Limite de compression : Le régime 20× montre ~60% de précision. Où se situe l'effondrement sémantique ?
Préservation géométrique : Les RoPE multi-résolution (Qwen3-VL) maintiennent les relations spatiales. La compression agressive casse-t-elle les priors géométriques pour les tables/diagrammes ?
Budgeting adaptatif : Allouer plus de tokens aux régions denses (équations, tableaux) et moins au texte courant ?
Frontière de Pareto : Quelle est la surface d'échange compression/taille/précision ?
Compress before global attention
Le placement de la compression dans le pipeline change radicalement l'efficacité mémoire.
Architecture DeepEncoder :
- Perception head : window attention locale (64×64 patches → 4,096 tokens, pas de réduction)
- Compression : convolution 4×4 stride 4 (4,096 → 256 tokens, 16× reduction)
- Knowledge head : CLIP ViT avec global self-attention sur les 256 tokens compressés
Gain mémoire : O(256²) vs O(4,096²) = 256× moins de mémoire pour l'attention globale.
# Principe architectural
local_features = window_attention(patches) # 4096 tokens
compressed = conv_4x4_stride4(local_features) # 256 tokens (16×)
global_features = global_attention(compressed) # attention quadratique ici
Le point critique : compresser avant les opérations quadratiques, pas après.
Modes de résolution comme paramètres budgétaires
Token budget explicite au lieu de propriété émergente.
| Mode | Tokens/page | Résolution | OCR Precision | Use case |
|---|---|---|---|---|
| Tiny | 64 | 512×512 | ~85% | High-volume screening |
| Small | 100 | 640×640 | ~92% | Standard documents |
| Base | 256 | 1024×1024 | ~97% | Technical papers |
| Large | 400 | 1280×1280 | ~98.5% | Fine detail preservation |
Tiling "Gundam" pour documents larges : n × 100 + 256 tokens (n tuiles locales + 1 vue globale).
Le budget devient prévisible et schedulable.
Entraînement end-to-end sur reconstruction de texte
Pas de contrastive learning CLIP, malgré l'architecture CLIP.
- Loss : cross-entropy du décodeur sur les tokens de texte ground-truth
- Gradient : ∂L/∂logits = p(predicted) − 𝟙_ground_truth remonte jusqu'au vision encoder
- Effet du bottleneck : la compression 16× force l'encoder à ne garder que les features pertinentes pour reconstruire le texte (caractères, mise en page), pas la texture
Training insight : le ratio de compression agit comme information bottleneck, sélectionnant automatiquement les features OCR-relevant.
Résultats empiriques DeepSeek-OCR
- 97% OCR precision à 9-10× compression (Fox benchmark)
- SOTA accuracy/token sur OmniDocBench
- Throughput : ~200,000 pages/jour sur 1× A100
Dégradation : à 20× compression, précision ~60% (encore utile pour certaines tâches).
Trade-off : compression vs perception breadth
Compression-focused (DeepSeek-OCR) :
- Document processing à grande échelle
- Data generation
- Archival applications
- Token efficiency prime
Generalist VLM (Qwen2.5-VL, Qwen3-VL) :
- Visual reasoning complexe
- Long-form video understanding
- Diagram parsing fin
- Préservent plus de tokens pour reasoning riche
Le choix dépend du contexte d'usage, pas d'une supériorité absolue.
Questions ouvertes
- Compression limits : à quel ratio la semantic collapse survient-elle ?
- Geometric preservation : l'aggressive compression sacrifie-t-elle les priors spatiaux (tables, diagrammes) ?
- Adaptive budgeting : allouer plus de tokens aux régions denses (formules, tables) ?
- Cross-modal compression : le principe s'étend-il à video, audio, 3D point clouds ?
- Architecture search : frontière de Pareto compression/qualité/coût ?
See also
numpy pour la manipulation de patches et grilles spatiales
pytorch pour implémenter les architectures de compression
langchain pour intégrer les encoders dans des pipelines multimodaux
keras-tensorflow pour les stratégies de training bout-en-bout