File size: 16,428 Bytes
c32a46b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
059b43a
65b715e
c32a46b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65b715e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c32a46b
42c4221
c32a46b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65b715e
c32a46b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
059b43a
 
 
 
 
 
 
 
 
 
 
 
 
 
c32a46b
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
import os
os.environ["REQUESTS_CA_BUNDLE"] = ""

from llama_index.core import (
    VectorStoreIndex,
)

import chromadb
from llama_index.llms.mistralai import MistralAI
from llama_index.embeddings.mistralai import MistralAIEmbedding
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core.storage.storage_context import StorageContext
from llama_index.core import ServiceContext
from llama_index.core.retrievers import VectorIndexRetriever

import tenacity
from typing import List, Union
import textwrap
import dsp
import dspy
from dsp.utils import dotdict
from dsp.modules.lm import LM


MISTRAL_API_KEY = "Yb2kAF0DR4Mva5AEmoYFV3kYRAKdXB7i"
EMBEDDING_MODEL_NAME = "mistral-embed"
COMPLETION_MODEL_NAME = "mistral-medium"
PERSISTANT_DB_PATH = "./chroma_db"

def deduplicate_with_sources(seq: list[str], sources: list[str]) -> list[str]:
    final_context = []
    final_sources = []

    for snippet, source in zip(seq, sources):
        if snippet not in final_context:
            final_context.append(snippet)
            final_sources.append(source)

    return final_context, final_sources


class FinalAnswerChainOfThought(dspy.Predict):
    def __init__(self, signature, rationale_type=None, activated=True, **config):
        super().__init__(signature, **config)

        self.activated = activated

        signature = self.signature
        *keys, last_key = signature.kwargs.keys()

        DEFAULT_RATIONALE_TYPE = dsp.Type(
            prefix="Raisonnement: Décompose la requête en sous requêtes, réponds à chacune d'entre elle d'abord avant de",
            desc="produire la ${" + last_key + "}. Nous ...",
        )

        rationale_type = rationale_type or DEFAULT_RATIONALE_TYPE

        extended_kwargs = {key: signature.kwargs[key] for key in keys}
        extended_kwargs.update(
            {"rationale": rationale_type, last_key: signature.kwargs[last_key]}
        )

        self.extended_signature = dsp.Template(
            signature.instructions, **extended_kwargs
        )

    def forward(self, **kwargs):
        new_signature = kwargs.pop("new_signature", None)
        if new_signature is None:
            if self.activated is True or (
                self.activated is None and isinstance(dsp.settings.lm, dsp.GPT3)
            ):
                signature = self.extended_signature
            else:
                signature = self.signature
        else:
            signature = dsp.Template(self.signature.instructions, **new_signature)
        return super().forward(signature=signature, **kwargs)


    def dump_state(self):
        state = super().dump_state()

        # Cache the signature instructions and the last field's name.
        state["extended_signature_instructions"] = self.extended_signature.instructions
        state["extended_signature_prefix"] = self.extended_signature.fields[-1].name

        return state

    def load_state(self, state):
        super().load_state(state)
        
        # Reconstruct the signature.
        if "extended_signature_instructions" in state:
            instructions = state["extended_signature_instructions"]
            self.extended_signature.instructions = instructions
        
        if "extended_signature_prefix" in state:
            prefix = state["extended_signature_prefix"]
            self.extended_signature.fields[-1] = self.extended_signature.fields[-1]._replace(name=prefix)


class GenerateSearchQuery(dspy.Signature):
    """Écris en une phrase une requête de recherche simple qui aidera à répondre à une question complexe. Utilise des termes plus génériques pour faciliter la recherche"""

    contexte = dspy.InputField(desc="peut contenir des faits pertinents")
    question = dspy.InputField()
    requete = dspy.OutputField()


class GenerateAnswer(dspy.Signature):
    """Fournis une réponse concise et factuelle en faisant référence au contexte"""

    contexte = dspy.InputField(desc="peut contenir des faits pertinents")
    question = dspy.InputField()
    reponse = dspy.OutputField(desc="contient obligatoirement des références du contexte ([numéro de référence dans le contexte])")


class SimplifiedBaleen(dspy.Module):
    def __init__(self, passages_per_hop=5, max_hops=2):
        super().__init__()

        self.generate_query = [dspy.ChainOfThought(GenerateSearchQuery) for _ in range(max_hops)]
        self.retrieve = dspy.Retrieve(k=passages_per_hop)
        self.generate_answer = FinalAnswerChainOfThought(GenerateAnswer)
        self.max_hops = max_hops
    
    @tenacity.retry(
            stop=tenacity.stop_after_attempt(3),
            wait=tenacity.wait_fixed(10)
    )
    def forward(self, question):
        context = []
        sources = []

        for hop in range(self.max_hops):
            query = self.generate_query[hop](contexte=context, question=question).requete
            retriever_output = self.retrieve(query)
            passages, current_sources = retriever_output
            passages = passages.passages
            current_sources = current_sources.sources
            context, sources = deduplicate_with_sources(context + passages, sources + current_sources)
        print("\nFinal context:")
        for idx, (elem, source) in enumerate(zip(context, sources)):
            print(f"[{idx + 1}] : {elem} ==> {source}")
        pred = self.generate_answer(contexte=context, question=question)
        return dspy.Prediction(contexte=context, answer=pred.reponse), context, sources
    
class ChromaDBRetriever(dspy.Retrieve):
    def __init__(self, vector_store_index:VectorStoreIndex, *args, **kwargs):
        self.vector_store_index = vector_store_index
        # self.similarity_top_k = kwargs.get("k", 2)
    
    def forward(self, query_or_queries: Union[str, List[str]], *args, **kwargs) -> dspy.Prediction:
        # query_engine = self.vector_store_index.as_retriever(similarity_top_k=kwargs.get("k"), vector_store_query_mode="text_search")

        query_engine = VectorIndexRetriever(
            index=self.vector_store_index,
            similarity_top_k=kwargs.get("k"),
        )

        top_k = query_engine.retrieve(query_or_queries)# k=kwargs.get("k", 1))
        passages = [dotdict({"long_text": doc.text, "source_name": doc.metadata["source_name"]}) for doc in top_k]

        return passages
    
class DspyMistralWrapper(LM):
    def __init__(self, llm_model: MistralAI):
        super().__init__(model="mistral")
        self.llm_model = llm_model
        self.history = []

    def basic_request(self, prompt, **kwargs):
        response = self.llm_model.complete(prompt)
        # res_data = response
        history = {
            "prompt": prompt,
            "response": response.text,
            "kwargs": kwargs,
            "raw_kwargs": {},
        }
        self.history.append(history)
        return response.text
    
    def __call__(self, prompt, only_completed=True, return_sorted=False, **kwargs):
        return [self.basic_request(prompt=prompt)]

def init_llm(persistant_db_path=PERSISTANT_DB_PATH):
    llm = MistralAI(api_key=MISTRAL_API_KEY, model=COMPLETION_MODEL_NAME, temperature=0, max_tokens=10000)
    embed_model = MistralAIEmbedding(model_name=EMBEDDING_MODEL_NAME, api_key=MISTRAL_API_KEY)

    db = chromadb.PersistentClient(path=persistant_db_path)
    chroma_collection = db.get_or_create_collection("quickstart")

    # set up ChromaVectorStore and load in data
    vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
    storage_context = StorageContext.from_defaults(vector_store=vector_store)
    service_context = ServiceContext.from_defaults(
        chunk_size=1024, llm=llm, embed_model=embed_model
    )
    index = VectorStoreIndex(
        [], service_context=service_context, storage_context=storage_context
    )

    retriever = ChromaDBRetriever(vector_store_index=index)

    dspy_llm = DspyMistralWrapper(llm_model=llm)
    dspy.settings.configure(lm=dspy_llm, rm=retriever)
    dspy.settings.configure(lm=dspy_llm, rm=retriever)

def invoke(input_prompt:str):
    uncompiled_baleen = SimplifiedBaleen(passages_per_hop=5, max_hops=2)
    predictions, justifications, sources = uncompiled_baleen(input_prompt)
    content = " ".join(textwrap.wrap(predictions.answer))
    # import time
    # time.sleep(0.2)
    # content = "Le numéro d'identification d'un bovin en France est composé de 12 chiffres. Les deux premiers chiffres représentent le code pays "FR" ([1]). Les deux chiffres suivants correspondent au numéro de code INSEE du département où se trouve l'animal au moment de son identification ([2]). Les huit chiffres restants sont attribués sous la responsabilité de l'établissement de l'élevage ([2]). Pour les bovins identifiés en France après le 1er septembre 1998, le numéro de travail est composé des 4 derniers chiffres du numéro national ([1])."
    # justifications = [
    #     "Page 20 sur 47  IDENTIFICATION Vv. BOVINE  Annexe de l’arrêté du 6 août 2013 relatif à l’identification des animaux de l’espèce bovine  Notification des informations  10. Notifier chaque événement dans les sept jours, à (nom du maître d'oeuvre de l'identification) : – soit en transmettant l’exemplaire du « DOCUMENT DE NOTIFICATION – REGISTRE BOVIN » prévu à cet effet ; – soit en transmettant ces informations par voie électronique, selon les modalités techniques définies par (nom du maître d'oeuvre de l'identification).  11. Notifier toute anomalie constatée sur tout document (passeport, livre des bovins, document de notification….) à (nom du maître d'oeuvre de l'identification).  Pertes de marques auriculaires agréées numérotées  12. En cas de perte, de détérioration ou d’illisibilité d'une seule marque auriculaire agréée, commander à (nom du maître d'oeuvre de l'identification) une marque auriculaire agréée permettant d'avoir toujours le même numéro national sur ce bovin et l’apposer au plus vite, dans un délai maximum de trente jours après la livraison.  13. En cas de perte de détérioration ou d’illisibilité de deux marques auriculaires agréées, isoler l'animal et faire appel à (nom du maître d'oeuvre de l'identification) pour la vérification de l’identité de l'animal et le remplacement éventuel de ses marques auriculaires agréées à l'identique. En cas d'impossibilité de reconnaissance de l’identité de l'animal, ce dernier pourra être détruit sans compensation financière, conformément à la réglementation communautaire en vigueur.  Circulation des animaux  14. Ne laisser entrer dans mon exploitation un bovin, ou en sortir, que correctement identifié (deux marques auriculaires agréées numérotées, passeport correctement renseigné et correspondant aux caractéristiques de l'animal).  Cessation d’activité  15. Informer (nom du maître d'oeuvre de l'identification) de ma cessation d'activité.  Restitution du matériel d'identification  16. Restituer à (nom du maître d'oeuvre de l'identification), en cas de cessation d'activité, ou à sa demande, la totalité des marques d'identification et des passeports dont je dispose.  Dispositions générales  17. Sur demande d'un agent mandaté par l'établissement de l'élevage ou par (nom du maître d'oeuvre de l'identification) le cas échéant ou de tout agent mandaté de la direction départementale en charge de la protection de la population ou de la direction départementale des territoires, communiquer toute information utile et présenter tous mes animaux, toutes les marques auriculaires agréées en stock ainsi que tous les documents d'identification dont je dispose.  18. En cas d’intervention de ces agents, faciliter l'accès à mes animaux en assurant notamment leur contention.  19. Payer à (nom du maître d'oeuvre de l'identification) les sommes dont je suis redevable pour les opérations d'identification. En cas de non-paiement, (nom du maître d’oeuvre de l’identification) peut me refuser la délivrance des passeports.  20. En cas de non-respect de mes obligations, je dois avoir recours à un agent mandaté par (nom du maître d'oeuvre de l'identification) à mes frais, pour la réalisation de l'identification des animaux de mon exploitation.  21. Je suis informé que le non-respect de mes obligations peut se traduire par la perte des primes, voire l'obligation de paiement de pénalités financières complémentaires.  Date et signature.  Vu le détenteur.  un exemplaire signé est retourné à (nom du maître d'oeuvre de l'identification) un exemplaire est conservé par le détenteur",
    #     "Page 21 sur 47  IDENTIFICATION Vv. BOVINE  Annexe de l’arrêté du 6 août 2013 relatif à l’identification des animaux de l’espèce bovine  7.1.2  I-B. - Déclaration du détenteur-opérateur commercial auprès de l'établissement départemental/interdépartemental de l'élevage  *** Préciser dans le texte l'organisme qui assure la maîtrise d'oeuvre de l'identification***  Je soussigné, M. ..., détenteur, agissant en mon nom propre / agissant en tant que responsable de (nom de la personne morale)……………  déclare avoir pris connaissance de l'obligation qui m'est faite d'accomplir les opérations d'identification des bovins, détenus sur mon exploitation n° ……....., telles que prévues par la réglementation communautaire et nationale en vigueur.  Ces obligations portent plus particulièrement sur les points suivants :  Gestion des marques auriculaires agréées numérotées et des documents d'identification  1. Notifier à (nom du maître d'oeuvre de l'identification) toute perte de documents d'identification.  2. Ne déboucler sous aucun prétexte quelque animal que ce soit.  Tenue du registre  3. Inscrire sur le registre des bovins chaque événement : naissance, entrée, sortie ou mort. Remarque : dans le cas particulier d’une naissance, l’apposition des marques auriculaires sur l’animal, réalisée par l’EdE, s’effectue avant l’enregistrement de la naissance sur le registre. Le registre des bovins peut être tenu : – ou en utilisant les documents de notification mis à disposition par (nom du maître d'oeuvre de l'identification) ; – ou en utilisant le registre fiscal, – ou sur support électronique. Dans ce dernier cas, le registre doit pouvoir être présenté sur demande d’un agent identificateur désigné par l’EdE, d’un agent mandaté par la direction départementale de la protection de la population ou d’un agent mandaté par la direction départementale des territoires.  4. Vérifier que le registre des bovins contient l'ensemble des informations d'identification, tenues à jour, concernant mon exploitation.  5. Conserver dans le registre des bovins au minimum, les informations des trois dernières années.  Notification des informations  6. Notifier chaque événement dans les sept jours : – soit en transmettant à (nom du maître d'oeuvre de l'identification) un exemplaire du support papier du registre ; – soit en transmettant ces informations par voie électronique, selon les modalités techniques définies par (nom du maître d'oeuvre de l'identification).  7. Notifier toute anomalie constatée sur tout document (passeport, livre des bovins, document de notification….) à (nom du maître d'oeuvre de l'identification).  Pertes de marques auriculaires agréées numérotées  8. Notifier toute perte, détérioration ou illisibilité de marques auriculaires agréées à (nom du maître d'oeuvre de l'identification). Un agent identificateur habilité vérifiera l'identité de l'animal et effectuera le remplacement éventuel des marques auriculaires agréées à l'identique. En cas d'impossibilité de reconnaissance de l'identité de l'animal, ce dernier pourra être détruit sans compensation financière, conformément à la réglementation communautaire en vigueur.      z- | Liberd + Egalté » Fraternlté REPUBLIQUE FRANCAISE  Annexe v1.2"
    # ]
    # sources = [
    #     "Dossier-PAC-2022_notice_ICHN.pdfPAGENUMBER2",
    #     "lien2.html",
    # ]
    if not len(justifications) == len(sources):
        raise RuntimeError(f"Justifications ({len(justifications)}) and sources ({len(sources)}) does not have the same size")
    return {
        "content": content,
        "justifications": justifications,
        "sources": sources,
    }