File size: 10,241 Bytes
b7f4192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62a503d
b7f4192
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import os
from pathlib import Path
from tempfile import mkdtemp

import gradio as gr
from docling_haystack.converter import ExportType
from dotenv import load_dotenv

from docling_haystack.converter import DoclingConverter
from haystack import Pipeline
from haystack.components.embedders import (
    SentenceTransformersDocumentEmbedder,
    SentenceTransformersTextEmbedder,
)
from haystack.components.preprocessors import DocumentSplitter
from haystack.components.writers import DocumentWriter
from milvus_haystack import MilvusDocumentStore, MilvusEmbeddingRetriever
from docling.chunking import DocChunk, HybridChunker
from haystack.components.builders import AnswerBuilder
from haystack.components.builders.prompt_builder import PromptBuilder
from haystack.components.generators import HuggingFaceAPIGenerator
from haystack.utils import Secret

def _get_env_from_colab_or_os(key):
    try:
        from google.colab import userdata

        try:
            return userdata.get(key)
        except userdata.SecretNotFoundError:
            pass
    except ImportError:
        pass
    return os.getenv(key)

load_dotenv()
HF_TOKEN = _get_env_from_colab_or_os("HF_TOKEN")
PATHS = ["Codex_over_het_welzijn_op_het_werk.pdf"]  # Docling Technical Report
EMBED_MODEL_ID = "intfloat/multilingual-e5-base" # sentence-transformers/all-MiniLM-L6-v2"
EXPORT_TYPE = ExportType.DOC_CHUNKS
TOP_K = 3
MILVUS_URI = str("codex-e5-base.db")

document_store = MilvusDocumentStore(
    connection_args={"uri": MILVUS_URI},
    drop_old=False,
    text_field="txt",  # set for preventing conflict with same-name metadata field
)

prompt_template = """
    Beantwoord de vraag met behulp van volgende documenten.
    Documenten:
    {% for doc in documents %}
        {{ doc.content }}
    {% endfor %}
    Vraag: {{query}}
    Antwoord:
    """

def create_rag_pipeline(model_id):
    rag_pipe = Pipeline()
    rag_pipe.add_component(
        "embedder",
        SentenceTransformersTextEmbedder(model=EMBED_MODEL_ID),
    )
    rag_pipe.add_component(
        "retriever",
        MilvusEmbeddingRetriever(document_store=document_store, top_k=TOP_K),
    )
    rag_pipe.add_component("prompt_builder", PromptBuilder(template=prompt_template))
    rag_pipe.add_component(
        "llm",
        HuggingFaceAPIGenerator(
            api_type="serverless_inference_api",
            api_params={"model": model_id},
            token=Secret.from_token(HF_TOKEN) if HF_TOKEN else None,
        ),
    )
    rag_pipe.add_component("answer_builder", AnswerBuilder())
    rag_pipe.connect("embedder.embedding", "retriever")
    rag_pipe.connect("retriever", "prompt_builder.documents")
    rag_pipe.connect("prompt_builder", "llm")
    rag_pipe.connect("llm.replies", "answer_builder.replies")
    rag_pipe.connect("llm.meta", "answer_builder.meta")
    rag_pipe.connect("retriever", "answer_builder.documents")
    return rag_pipe

def answer_question(question, selected_model_id):
    rag_pipe = create_rag_pipeline(selected_model_id)
    rag_res = rag_pipe.run(
        {
            "embedder": {"text": question},
            "prompt_builder": {"query": question},
            "answer_builder": {"query": question},
        }
    )

    answer = rag_res['answer_builder']['answers'][0].data.strip()
    sources = rag_res["answer_builder"]["answers"][0].documents

    sources_info = []
    for source in sources:
        if EXPORT_TYPE == ExportType.DOC_CHUNKS:
            doc_chunk = DocChunk.model_validate(source.meta["dl_meta"])
            sources_info.append(f"*** text: {doc_chunk.text}")
            if doc_chunk.meta.origin:
                sources_info.append(f"    file: {doc_chunk.meta.origin.filename}")
            if doc_chunk.meta.headings:
                sources_info.append(f"    section: {' / '.join(doc_chunk.meta.headings)}")
            bbox = doc_chunk.meta.doc_items[0].prov[0].bbox
            sources_info.append(
                f"    page: {doc_chunk.meta.doc_items[0].prov[0].page_no}, "
                f"    bounding box: [{int(bbox.l)}, {int(bbox.t)}, {int(bbox.r)}, {int(bbox.b)}]"
            )
        elif EXPORT_TYPE == ExportType.MARKDOWN:
            sources_info.append(repr(source.content))
        else:
            raise ValueError(f"Unexpected export type: {EXPORT_TYPE}")

    return answer, str("\n".join(sources_info))

def main():
    with gr.Blocks() as demo:
        gr.Markdown('''<sub><sup>(english explanation below)</sup></sub>

**RAG met codex welzijn op het werk**

Deze app biedt een slimme manier om specifieke vragen te stellen over de  **[Codex over het welzijn op het werk](https://huggingface.co/spaces/to-be/chat_met_codex_over_het_welzijn_op_het_werk/resolve/main/Codex_over_het_welzijn_op_het_werk.pdf)**, een Belgische wetgeving die de veiligheid, gezondheid en het welzijn van werknemers regelt. De codex bestaat uit 10 boeken die diverse aspecten van arbeidsomstandigheden behandelen, zoals preventie van psychosociale risico's, gezondheidstoezicht en eerste hulp. Dit kan een hulp zijn voor preventieadviseurs en werkpleksveiligheidsdeskundigen.

''')
        
        with gr.Row():
            question_input = gr.Textbox(label="Vraag", value="Mag een werkgever zelf een asbestinventaris maken voor zijn bedrijf?")
        with gr.Row():
            sample_questions = gr.Dropdown(
                label="Voorbeeldvragen",
                choices=[
                    "Mag een werkgever zelf een asbestinventaris maken voor zijn bedrijf?",
                    "Rangschik volgende beschermingsmaatregelen volgens prioriteit:\n A. Organisatorische maatregelen: opleidingen, procedures, werkvergunningen, …\n B. Risico’s verminderen met collectieve beschermingsmiddelen zoals relingen\n C. Substitutie van het gevaar: bv. een gevaarlijk chemisch product vervangen door een minder gevaarlijke stof\n D. Persoonlijke beschermingsmiddelen: denk aan een veiligheidsharnas",
                    "Wat is geen taak van de Interne Dienst voor Preventie en Bescherming op het Werk (IDPBW)? Kies 1 van volgende opties:\nA: Risico's onderzoeken en advies geven over de risico-evaluatie\nB: Advies geven over arbeidshygiëne\nC: Werk-privébalans voor werknemers bewaken\nD: Policies rond telewerken goedkeuren\nE: Werkinstructies opstellen"
                ],
                value="Mag een werkgever zelf een asbestinventaris maken voor zijn bedrijf?"
            )
        with gr.Row(equal_height=True):
            model_dropdown = gr.Dropdown(
                label="Selecteer generatie model",
                choices=[
                    "01-ai/Yi-1.5-34B-Chat",
                    "mistralai/Mixtral-8x7B-Instruct-v0.1",
                    "mistralai/Mistral-Nemo-Instruct-2407",
                    "Qwen/Qwen2.5-72B-Instruct",
                    "microsoft/Phi-3.5-mini-instruct"
                ],
                value="microsoft/Phi-3.5-mini-instruct"
            )
            send_button = gr.Button("Send")
        with gr.Row():
            answer_output = gr.Textbox(label="Antwoord")
            sources_output = gr.Textbox(label="Bronnen")

        gr.Markdown("<sub><sup>LLM kan fouten maken. Dubbelcheck belangrijke informatie</sup></sub>")
        gr.Markdown('''


**Hoe werkt de app?**

De app maakt gebruik van  **Retrieval-Augmented Generation (RAG)**  om specifieke vragen te beantwoorden op basis van de inhoud van de codex. De gebruiker kan een vraag stellen via een eenvoudige interface en krijgt een antwoord met **relevante bronnen** uit de codex.


**Technologieën die zijn gebruikt:**

-   **Docling**: Voor het converteren en indexeren van de documenten.
-   **Haystack**: Voor het bouwen van de RAG-pipeline.
-   **Hugging Face Inference API**: Voor het genereren van antwoorden via AI.
-   **Gradio**: Voor het bouwen van de gebruikersinterface.

**Verbeteringen:**

-   Het embedding-model is gewijzigd van  `sentence-transformers/all-MiniLM-L6-v2`  naar  `intfloat/multilingual-e5-base`  vanwege betere prestaties in multilinguïsme, vooral voor het Nederlands.
-   Je hebt keuze tussen verschillende LLM's, met persoonlijke voorkeur voor  **Phi-3.5-mini-instruct.** 
        
This demo provides a smart way to ask specific questions about the  **Codex on Well-Being at Work**, a Belgian legislation that regulates the safety, health, and well-being of employees. The codex consists of 10 books that cover various aspects of working conditions, such as prevention of psychosocial risks, health monitoring, and first aid. This can be a help for prevention advisors and workplace safety professionals.

**How does the app work?**

The app uses  **Retrieval-Augmented Generation (RAG)**  to answer specific questions based on the content of the codex. The user can ask a question through a simple interface and receives an answer with  **relevant sources**  from the codex.

**Technologies used:**

-   **Docling**: For converting and indexing the documents.
-   **Haystack**: For building the RAG pipeline.
-   **Hugging Face Inference API**: For generating answers via AI.
-   **Gradio**: For building the user interface.

**Improvements:**

-   The embedding model was changed from  `sentence-transformers/all-MiniLM-L6-v2`  to  `intfloat/multilingual-e5-base`  for better performance in multilingualism, especially for Dutch.
-   You have the option to choose between different LLMs, with a personal preference for  **Phi-3.5-mini-instruct**.


Comments / questions can be directed to [toon@neontreebot.be](mailto:toon@neontreebot.be?subject=to-be/chat_met_codex_over_het_welzijn_op_het_werk)

''')
        gr.HTML('''<a href="https://visitorbadge.io/status?path=chat_met_codex_over_het_welzijn_op_het_werk"><img src="https://api.visitorbadge.io/api/combined?path=chat_met_codex_over_het_welzijn_op_het_werk&countColor=%23263759" /></a>''')
        sample_questions.change(lambda x: x, inputs=sample_questions, outputs=question_input)
        send_button.click(answer_question, inputs=[question_input, model_dropdown], outputs=[answer_output, sources_output])
        question_input.submit(answer_question, inputs=[question_input, model_dropdown], outputs=[answer_output, sources_output])

    demo.launch()

if __name__ == "__main__":
    main()