Faites tourner le cerveau RAG de Quivr sur un endpoint compatible OpenAI personnalisé.

Updated 2026-07-29

Le LLMEndpointConfig de quivr-core prend un champ llm_base_url. Gardez le supplier à openai, réglez llm_base_url sur https://api.apisrouter.com/v1, passez une seule clé, et chaque brain.ask() génère sa réponse via la passerelle avec n'importe quel id de modèle du catalogue.

Réponse rapide : llm_base_url dans LLMEndpointConfig.

Le Quivr actuel est quivr-core, une bibliothèque RAG Python, et son câblage LLM est explicite. LLMEndpointConfig porte supplier (openai par défaut), model, llm_base_url et llm_api_key ; LLMEndpoint.from_config() construit le client réel à partir de ces champs, et pour le supplier openai ce client est ChatOpenAI de LangChain construit avec votre base URL. Réglez llm_base_url sur https://api.apisrouter.com/v1, réglez model sur n'importe quel id du catalogue, et remettez l'endpoint à votre Brain. La clé peut venir du champ de config ou de l'environnement : quand llm_api_key n'est pas réglée, quivr-core la résout depuis une variable d'environnement nommée d'après le supplier, ce qui pour le supplier openai est OPENAI_API_KEY. Les deux chemins sont un comportement du quivr-core officiel, lisible dans quivr_core/rag/entities/config.py et quivr_core/llm/llm_endpoint.py.

from quivr_core.llm import LLMEndpoint
from quivr_core.rag.entities.config import (
    DefaultModelSuppliers, LLMEndpointConfig)

llm = LLMEndpoint.from_config(LLMEndpointConfig(
    supplier=DefaultModelSuppliers.OPENAI,
    model="claude-sonnet-4-6",          # any catalog id
    llm_base_url="https://api.apisrouter.com/v1",
    llm_api_key=os.environ["APISROUTER_API_KEY"],
))

Ce qu'est Quivr aujourd'hui, et où se situe l'emplacement LLM.

Quivr (QuivrHQ sur GitHub, environ 39 000 étoiles) a commencé comme une application complète de second cerveau et a pivoté vers quivr-core : une bibliothèque RAG à opinion que vous embarquez dans votre propre produit. Vous lui donnez des fichiers, elle les parse et les découpe en fragments, embarque les fragments dans un vector store (FAISS par défaut, PGVector supporté), et répond à des questions dessus via un workflow de récupération configurable. L'objet Brain est l'unité : Brain.from_files() ingère, brain.ask() récupère et génère. La génération est la seule étape qui nécessite un chat model. Le workflow de récupération assemble le contexte depuis vos documents, et le LLMEndpoint que vous avez passé écrit la réponse ancrée. Cet endpoint est construit une fois depuis LLMEndpointConfig, donc la décision de base URL est prise au moment de la construction et s'applique à chaque ask() sur ce brain. Comme ChatOpenAI transmet le champ model comme simple chaîne via /v1/chat/completions, l'id peut être Claude, DeepSeek, GPT ou Gemini quand l'endpoint derrière llm_base_url les sert. Une note honnête sur le statut du projet : le dépôt est calme depuis la mi-2025, donc traitez quivr-core comme une bibliothèque stable plutôt qu'à évolution rapide. La surface de config décrite ici correspond à la dernière branche main, et l'historique calme signifie qu'elle est peu susceptible de bouger sous vos pieds ; cela signifie aussi que les anciens tutoriels décrivant l'app full-stack retirée (fichiers .env backend, un frontend hébergé) ne correspondent plus au code.

Configuration complète : un brain avec un LLM routé via la passerelle.

Le motif complet passe le LLMEndpoint configuré dans Brain.from_files. Tout le reste du brain (parsing, découpage en fragments, le store FAISS, le workflow de récupération) est indépendant de l'endpoint LLM et garde ses défauts. Attention à l'embedder. Si vous n'en passez pas un, quivr-core construit OpenAIEmbeddings de LangChain avec ses propres défauts, qui s'authentifie avec OPENAI_API_KEY et vise l'endpoint OpenAI de base. C'est un client séparé du LLM de chat : router la génération via la passerelle ne le déplace pas. Passez votre propre embedder (un wrapper sentence-transformers local, ou n'importe quelle instance LangChain Embeddings que vous configurez) si vous ne voulez pas que la moitié embedding dépende d'un compte OpenAI.

import os
from quivr_core import Brain
from quivr_core.llm import LLMEndpoint
from quivr_core.rag.entities.config import (
    DefaultModelSuppliers, LLMEndpointConfig)

llm = LLMEndpoint.from_config(LLMEndpointConfig(
    supplier=DefaultModelSuppliers.OPENAI,
    model="claude-sonnet-4-6",
    llm_base_url="https://api.apisrouter.com/v1",
    llm_api_key=os.environ["APISROUTER_API_KEY"],
    max_output_tokens=2048,
    temperature=0.3,
))

brain = Brain.from_files(
    name="team-docs",
    file_paths=["handbook.pdf", "runbook.md"],
    llm=llm,
    # embedder=...  # separate component; see note above
)

print(brain.ask("What is the on-call escalation policy?").answer)

Choisir un modèle de génération pour des réponses RAG.

Comparer des candidats est un changement au moment de la construction : construisez deux LLMEndpoints contre la même base URL, deux brains sur les mêmes fichiers, et comparez les réponses sur un jeu de questions fixe. Le journal d'usage par clé chiffre l'exécution de chaque candidat, donc la qualité par token est mesurée plutôt que débattue.

  • La génération RAG est lourde en entrée : les fragments récupérés dominent le prompt. Le tarif par token d'entrée fixe le coût d'une réponse, ce qui explique pourquoi un id rapide divise souvent la facture par deux sans toucher à la qualité de récupération.
  • claude-sonnet-4-6 est le défaut fiable pour des réponses ancrées qui respectent le contexte récupéré et déclinent proprement quand les documents ne contiennent pas la réponse.
  • Les produits embarqués à haut volume (le cas d'usage revendiqué de Quivr) tournent bien sur claude-haiku-4-5-20251001, deepseek-v4-flash ou gemini-3.5-flash pour le mélange de questions quotidien.
  • max_context_tokens dans la même config gouverne combien de contexte récupéré le pipeline empaquette ; l'augmenter s'associe naturellement à des ids longue contexte et augmente la dépense d'entrée proportionnellement.
  • Les préfixes de modèles inconnus retombent sur un tokenizer générique pour le budget, ce qui est cosmétique ; la requête elle-même porte votre id inchangé vers l'endpoint.

Paiement à l'usage · en dessous du tarif officiel

Selected models are priced below official list prices. Exact input, output, cache, and per-request prices are shown for each model.

ModèlePrix officielNotre prix
Claude Sonnet 4.6$3.00 / $15.00 per M$2.40 / $12.00 per M
Claude Haiku 4.5 20251001$1.00 / $5.00 per M$0.80 / $4.00 per M
GPT-5.4 mini$0.75 / $4.50 per M$0.60 / $3.60 per M
DeepSeek V4 Flash$0.14 / $0.28 per M$0.10 / $0.30 per M
Gemini 3.5 Flash$1.50 / $9.00 per M$1.20 / $7.20 per M

Corrections aux idées reçues courantes sur Quivr.

Les guides en circulation décrivent des surfaces que Quivr n'a plus, donc il vaut la peine de préciser ce que le code actuel fait réellement. quivr-core repose sur LangChain, pas sur LiteLLM. L'énumération supplier sélectionne une classe de chat LangChain, et openai correspond à ChatOpenAI avec votre llm_base_url. Si un tutoriel vous dit de configurer un proxy LiteLLM ou un réglage api_base à l'intérieur de Quivr, il décrit une architecture plus ancienne ; le champ actuel est llm_base_url sur LLMEndpointConfig. L'app full-stack est retirée. Les instructions à propos d'un .env backend, d'une configuration Supabase, ou d'un sélecteur de modèle dans l'app font référence à l'application d'avant le pivot, qui n'est plus ce que livre le dépôt. La configuration se fait désormais dans votre code Python (ou votre propre app autour de la bibliothèque). La variable d'environnement de clé est dérivée du supplier. Pour le supplier openai c'est OPENAI_API_KEY, même quand l'endpoint n'est pas OpenAI. Si vous préférez ne pas surcharger ce nom, passez llm_api_key explicitement dans la config, ce qui l'emporte et garde l'environnement propre. L'embedder est séparé. Le routage de génération ne déplace pas les embeddings ; l'embedder par défaut est OpenAIEmbeddings avec ses propres identifiants. Décidez les deux moitiés indépendamment, et ré-embarquer un store existant n'est nécessaire que si vous changez le modèle d'embedding lui-même.

Qui route quivr-core via une passerelle.

  • Les équipes produit qui embarquent du RAG dans leurs apps et veulent que le modèle de génération soit une valeur de config, pas un engagement fournisseur figé dans la pile.
  • Les développeurs qui font tourner de nombreux brains à différents paliers de qualité : une seule clé, un seul endpoint, un id de modèle par brain.
  • Les équipes qui veulent des réponses ancrées de qualité Claude derrière une config à la forme OpenAI sans ajouter un second SDK ou compte fournisseur.
  • Les créateurs qui benchmarkent des modèles de génération sur un corpus fixe, où chaque candidat est un changement de LLMEndpointConfig.
  • Les développeurs sans accès à la facturation d'un fournisseur donné. Un accès basé sur la recharge, sans exigence de carte, supprime la dépendance à une inscription par fournisseur.

Vérifiez l'endpoint et déboguez le premier ask().

Confirmez que la passerelle liste votre modèle avant d'ingérer quoi que ce soit ; le champ model doit correspondre exactement à un id servi. Les échecs au premier lancement sont prévisibles. Un avertissement disant que la clé API pour le supplier openai n'est pas réglée signifie que ni llm_api_key ni OPENAI_API_KEY n'étaient visibles à la construction de la config ; l'avertissement se produit à la construction, l'échec au premier ask(). Un 401 signifie que la clé résolue n'appartient pas à l'endpoint dans llm_base_url. Une erreur model-not-found est une faute de frappe d'id contre /v1/models. Et une erreur d'authentification liée à l'embedding pendant Brain.from_files est l'embedder par défaut séparé demandant ses propres identifiants OpenAI, qu'aucun réglage llm_base_url ne corrigera ; passez un embedder que vous contrôlez. Une fois les réponses en circulation, la console APIsRouter affiche le modèle par requête, le nombre de tokens et la dépense. Pour une bibliothèque qui empaquette des fragments récupérés dans chaque prompt, le chiffre de tokens par réponse sur votre corpus réel est le chiffre qui devrait piloter votre choix de modèle.

curl -s https://api.apisrouter.com/v1/models \
  -H "Authorization: Bearer $APISROUTER_API_KEY" | head -50

Questions fréquentes

Quivr prend-il en charge une base URL compatible OpenAI personnalisée ?

Oui. Le LLMEndpointConfig de quivr-core a un champ llm_base_url, et pour le supplier openai la bibliothèque construit ChatOpenAI de LangChain contre cette URL. Réglez-le sur l'endpoint de la passerelle et passez n'importe quel id de modèle du catalogue.

Quivr repose-t-il sur LiteLLM ?

Pas dans la base de code actuelle. quivr-core sélectionne des classes de chat LangChain par supplier ; le supplier openai utilise ChatOpenAI avec votre llm_base_url. Les guides décrivant un api_base LiteLLM à l'intérieur de Quivr font référence à une architecture plus ancienne.

brain.ask() peut-il répondre avec des modèles Claude ou DeepSeek ?

Oui. Le champ model est transmis comme simple chaîne via /v1/chat/completions, donc claude-sonnet-4-6, deepseek-v4-flash, ou tout autre id que sert l'endpoint fonctionne sous le supplier openai.

Quelle variable d'environnement porte la clé ?

Quand llm_api_key n'est pas réglée dans la config, quivr-core dérive la variable du nom du supplier : OPENAI_API_KEY pour le supplier openai. Un llm_api_key explicite dans LLMEndpointConfig l'emporte et évite de surcharger ce nom.

llm_base_url déplace-t-il aussi les embeddings ?

Non. L'embedder par défaut est un client OpenAIEmbeddings séparé avec ses propres identifiants et endpoint. Routez la génération via la passerelle et passez votre propre embedder si vous voulez aussi sortir la moitié embedding d'OpenAI.

Le projet Quivr est-il encore maintenu ?

Le dépôt est calme depuis la mi-2025, donc traitez-le comme une bibliothèque stable plutôt qu'active. La surface llm_base_url documentée ici correspond à la dernière branche main, et l'app full-stack d'avant le pivot qu'elle a remplacée est retirée.