05/06/2023 - Django et IA locale : connecter Langchain

Comment exploiter la puissance de l'IA ? La question se pose aux acteurs de la tech et aux entreprises. Un début de réponse semble émerger, du moins les outils pour la bâtir. Nous introduirons sommairement dans cet article la librairie Langchain, qui propose des outils pour programmer avec les language models.

Note : le code présenté dans cet article est disponible dans le repository django-local-ai.
Dans cet article, nous allons nous baser sur la configuration décrite dans l'article Utiliser une IA locale dans Django.

Installons la librairie :

      
        pip install langchain
      
  

Testons Langchain dans un terminal avec un language model local pour vérifier que tout fonctionne :

      
        from langchain.llms import LlamaCpp
        from langchain import PromptTemplate, LLMChain
        from langchain.callbacks.manager import CallbackManager
        from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler

        template = """Question: {question}

        Answer: Let's think step by step."""

        prompt = PromptTemplate(template=template, input_variables=["question"])

        callback_manager = CallbackManager([StreamingStdOutCallbackHandler()])

        llm = LlamaCpp(
            # le path doit être absolu
            model_path="/home/mes/models/GPT4All-13B-snoozy.ggmlv3.q5_0.bin",
            callback_manager=callback_manager,
            verbose=True,
        )

        llm_chain = LLMChain(prompt=prompt, llm=llm)

        question = "Name the planets in the solar system with their diameter in kilometers"

        llm_chain.run(question)
      
  

Le modèle doit émettre une réponse dans le terminal, indiquant ainsi que tout fonctionne correctement. Nous pouvons brancher cela sur Django pour l'utiliser dans une interface web.

Créer une task Langchain

Sur le même principe que dans le premier article, nous allons devoir créer une task qui sera chargée de l'inférence, cette fois en utilisant Langchain. Commençons par créer un fichier langchain.py avec une fonction pour charger le language model.

Connecter les websockets

La première étape est de brancher l'output de Langchain sur notre channel websockets pour qu'il soit streamé au frontend. Ecrivons un handler spécifique pour notre cas :

      
        from typing import Any
        from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler
        from instant.producers import publish

        TOKS = 0

        class WsCallbackHandler(StreamingStdOutCallbackHandler):
            """Callback handler for websockets. Only works with LLMs that support streaming."""

            def on_llm_new_token(self, token: str, **kwargs: Any) -> None:
                """Run on new LLM token. Only available when streaming is enabled."""
                TOKS += 1
                if TOKS == 1:
                    publish("$llm", "#STARTSTREAM#")
                publish("$llm", token)
      
  

Charger le language model

Ajoutons maintenant une helper function pour charger le language model quand nécessaire :

      
        from langchain.llms import LlamaCpp
        from langchain.callbacks.manager import CallbackManager
        from django.conf import settings

        LLM: LlamaCpp | None = None
        TOKS = 0

        def load_langchain(*args, **kwargs) -> LlamaCpp:
            TOKS = 0
            if LLM is None:
                LLM = LlamaCpp(
                    model_path=settings.MODEL_PATH,
                    # on branche ici le callback custom précédemment élaboré
                    callback_manager=CallbackManager([WsCallbackHandler()]),
                    verbose=True,
                    **kwargs,
                )
            return LLM
      
  

La task d'inférence

Sur la base de ces éléments nous pouvons maintenant écrire la task d'inférence elle-même. Dans un fichier tasks.py :

      
        from django.conf import settings
        from langchain import LLMChain, PromptTemplate
        from instant.producers import publish
        
        from myapp.langchain import load_langchain

        @huey.task()
        def infertemplate(question: str, template: str):
            LLM = load_langchain(n_ctx=1024)
            prompt = PromptTemplate(template=template, input_variables=["question"])
            llm_chain = LLMChain(prompt=prompt, llm=LLM)
            llm_chain.run(question)
            publish("$llm", "#ENDSTREAM#")
            return
      
  

Tout est prêt pour brancher un endpoint.

Le endpoint

Créons un api endpoint simple, avec d'abord un schéma Pydantic pour spécifier les données requises depuis le frontend: prompt et template :

      
        class InferContract(Schema):
            prompt: str

        class InferTemplateContract(InferContract):
            template: str
      
  

Branchons le endpoint en utilisant ce schéma pour les données d'entrée requises :

      
        from myapp.schemas import InferTemplateContract
        from myapp.tasks import infertemplate

        @router.post(
            "/inferlc",
            response={ 200: None },
        )
        def inferlc(request: HttpRequest, data: InferTemplateContract):
            prompt: str = data.dict()["prompt"]
            template: str = data.dict()["template"]
            print("Calling Langchain infer task with template", template)
            print("For prompt", prompt)
            infertemplate(prompt, template)
            return 200, None
      
  

Et voilà ! Le frontend va poster ses template et prompt et écouter la réponse du language model en websockets. Maintenant que la librairie est branchée, il reste à explorer les nombreuses possibilités de Langchain, en parcourant la doc ou en lisant le code Python. La pratique et l'imagination de chaque développeur doit faire le reste.

Le code présenté dans cet article est disponible dans le repository django-local-ai.