File size: 16,000 Bytes
fc3ddc9
 
 
 
 
 
f25e2d7
a566f09
f25e2d7
fc3ddc9
 
207b9f8
fc3ddc9
a566f09
fc3ddc9
fe6e823
7a1339e
fc3ddc9
 
 
 
e87e726
a566f09
fc3ddc9
 
e87e726
fc3ddc9
 
 
a566f09
 
 
 
 
 
 
 
fc3ddc9
a566f09
 
 
 
 
 
fc3ddc9
 
 
 
 
 
 
 
e87e726
fc3ddc9
9697155
fc3ddc9
 
 
 
bcf1ca1
 
fc3ddc9
a566f09
 
fc3ddc9
f25e2d7
a566f09
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f25e2d7
a566f09
 
f25e2d7
 
 
 
fc3ddc9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f25e2d7
fc3ddc9
0d1a7d8
 
 
 
 
 
 
 
 
 
 
fc3ddc9
f25e2d7
a566f09
f25e2d7
a566f09
 
fc3ddc9
a566f09
fc3ddc9
f25e2d7
 
 
 
 
 
 
a566f09
207b9f8
f25e2d7
 
207b9f8
f25e2d7
 
207b9f8
f25e2d7
 
207b9f8
 
 
f25e2d7
 
 
e87e726
f25e2d7
e87e726
207b9f8
 
f25e2d7
 
 
 
207b9f8
fc3ddc9
a566f09
 
fc3ddc9
 
 
 
 
 
 
 
e87e726
 
fc3ddc9
e87e726
 
fc3ddc9
e87e726
fc3ddc9
 
 
 
a566f09
f25e2d7
 
a566f09
e87e726
f25e2d7
a566f09
f25e2d7
 
 
fc3ddc9
e87e726
f25e2d7
 
 
 
 
e87e726
f25e2d7
fc3ddc9
 
f25e2d7
 
 
 
e87e726
f25e2d7
a566f09
e87e726
f25e2d7
 
e87e726
f25e2d7
e87e726
 
f25e2d7
 
 
 
fc3ddc9
 
a566f09
fc3ddc9
 
 
a566f09
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fc3ddc9
e87e726
fc3ddc9
e87e726
fc3ddc9
a566f09
 
 
 
 
 
 
f25e2d7
a566f09
 
 
 
f25e2d7
 
a566f09
 
 
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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
import streamlit as st
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import re
from huggingface_hub import InferenceClient
import os
import numpy as np
from openai import OpenAI





st.set_page_config(layout="wide")

with open("/home/user/app/labeled_text_small.txt", "r", encoding="utf-8") as f:
    my_initial_rag_text = f.read()

# Check if the LLM model is not already in the session state
if "my_llm_model" not in st.session_state:
    # Set the default LLM model to "mistralai/Mistral-7B-Instruct-v0.3"
    st.session_state['my_llm_model'] = "mistralai/Mistral-7B-Instruct-v0.3"

# Check if the SPACE_ID environment variable is not already in the session state
if "my_space" not in st.session_state:
    st.session_state['my_space'] = os.environ.get("SPACE_ID") 

# Function to update the LLM model client
def update_llm_model():
    if st.session_state['my_llm_model'].startswith("gemini-"):
        # Initialize the client for gemini models. We use the OpenAI API to interact with gemini models.
        st.session_state['client'] = OpenAI(api_key = os.getenv("GOOGLE_API_KEY"),
                                            base_url = "https://generativelanguage.googleapis.com/v1beta/openai/")
    elif st.session_state['my_llm_model'].startswith("gpt-"):
        # Initialize the client for openai models
        st.session_state['client'] = OpenAI(api_key = os.getenv("OPENAI_API_KEY"))
                                            # ,base_url = "https://eu.api.openai.com/" # gives error
    else:
        if st.session_state['my_space']:
            # Initialize the client with the model if SPACE_ID is available
            st.session_state['client'] = InferenceClient(st.session_state['my_llm_model'])
        else:
            # Initialize the client with the model and token if SPACE_ID is not available
            st.session_state['client'] = InferenceClient(st.session_state['my_llm_model'], token=os.getenv("HF_TOKEN"))
        
# Check if the client is not already in the session state
if "client" not in st.session_state:
    update_llm_model()

# Check if the embeddings model is not already in the session state
if "embeddings_model" not in st.session_state:
    # We will use the all-MiniLM-L6-v2 model for embeddings
    st.session_state['embeddings_model'] = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

my_system_instructions = "You are a helpful assistant. Be brief and concise. ALWAYS clasify given text as FAKE or REAL based on the given context using 100 words or less."

first_message = "Hello, how can I help you today?"

def delete_chat_messages():
    for key in list(st.session_state.keys()):
        if key not in {"my_rag_text", "my_system_instructions", "my_llm_model", "my_space"}:
            del st.session_state[key]
    update_llm_model()
            

def create_sentences_rag():
    with rag_status_placeholder:
        # The pattern splits text at any of the punctuation marks .?!;: followed by one or more spaces, or at a newline character
        pattern = r'(?<=[.?!;:])\s+|\n'
        st.session_state['my_sentences'] = [sentence.strip() for sentence in re.split(pattern, st.session_state['my_rag_text']) if sentence.strip()]
        with st.spinner(f"Encoding {len(st.session_state['my_sentences'])} sentences..."):
            sentences_ids = [i for i in range(len(st.session_state['my_sentences']))]
            # Rolling window: include partial windows at end
            st.session_state['my_sentences_rag_ids'] = []
            st.session_state['my_sentences_rag'] = []
            for rolling_window_size in range(st.session_state['min_window_size'], st.session_state['max_window_size']+1):
                for i in range(0, len(st.session_state['my_sentences'])-rolling_window_size+1):
                    chunk = " ".join(st.session_state['my_sentences'][i:i+rolling_window_size]).strip()
                    if chunk:   
                        st.session_state['my_sentences_rag'].append(chunk)
                        st.session_state['my_sentences_rag_ids'].append(sentences_ids[i:i+rolling_window_size])
                        # print(f"*****{chunk}*****\n")

            st.session_state['my_embeddings'] = st.session_state['embeddings_model'].encode(st.session_state['my_sentences_rag'])
        st.success(f"{len(st.session_state['my_sentences_rag'])} chunks have been encoded!")




# Create two columns with a 1:2 ratio
column_1, column_2 = st.columns([1, 2])

# In the first column
with column_1:
    # Display a disclaimer about the potential inaccuracies of Large Language Models
    st.expander("Disclaimer", expanded=False).markdown("""This application and code (hereafter referred to as the 'Software') is a proof of concept at an experimental stage and is not intended to be used as a production environment. The Software is provided as is, wihtout any warranties of any kind, expressed or implied and the user assumes full responsibility for its use, implementation, and legal compliance.
                                                       
The developers of the Software shall not be liable for any damages, losses, claims, or liabilities arising from the Software, including but not limited to the usage of artificial intelligence and machine learning, related errors, third-party tool failures, security breaches, intellectual property violations, legal or regulatory non-compliance, deployment risks, or any indirect, incidental, or consequential damages.
                                                       
Large Language Models may provide wrong answers. Please verify the answers and comply with applicable laws and regulations.
                                                       
The user agrees to indemnify and hold harmless the developers of the Software from any related claims or disputes arising from the utilization of the Software by the user.

By using the Software, you agree to the terms and conditions of the disclaimer.""")

    # Add a selectbox for model selection
    # model_list_all = [  'mistralai/Mistral-7B-Instruct-v0.3',
    #                     'Qwen/Qwen2.5-72B-Instruct', 
    #                     'HuggingFaceH4/zephyr-7b-beta']
    # if os.getenv("GOOGLE_API_KEY"):
    #     model_list_all.append('gemini-2.5-flash-preview-05-20')
    # if os.getenv("OPENAI_API_KEY"):
    #     model_list_all.append('gpt-4.1-nano-2025-04-14')
    # st.selectbox("Select the model to use:",
    #             model_list_all, 
    #             key="my_llm_model", 
    #             on_change=update_llm_model)
    
    # Add a text are for the system instructions
    st.text_area(label="Please enter your system instructions here:", value=my_system_instructions, height=80, key="my_system_instructions", on_change=delete_chat_messages)
    
    # Placeholder right after text_area
    rag_status_placeholder = st.empty()
    # Add a text area for RAG text input
    st.text_area(label="Please enter your RAG text here:", value=my_initial_rag_text, height=200, key="my_rag_text", on_change=delete_chat_messages)

    # Add a slider for minimum window size
    st.slider("Minimum window size in original sentences", min_value=1, max_value=20, value=5, step=1, key="min_window_size", on_change=create_sentences_rag)

    # Add a slider for maximum window size
    st.slider("Maximum window size in original sentences", min_value=1, max_value=20, value=10, step=1, key="max_window_size", on_change=create_sentences_rag)
    
    # Add a slider for the similarity threshold
    st.slider("Similarity threshold", min_value=0.0, max_value=1.0, value=0.2, step=0.01, key="my_similarity_threshold")

    # Add a slider for the number of sentences to keep
    st.slider("Number of original chunks to keep", min_value=1, max_value=50, value=20, step=1, key="nof_keep_sentences")

    # Add a slider for the number of minimum sub prompts
    st.slider("Minimum number of words in sub prompt split", min_value=1, max_value=10, value=1, step=1, key="nof_min_sub_prompts")

    # Add a slider for the number of maximum sub prompts
    st.slider("Maximum number of words in sub prompt split", min_value=1, max_value=10, value=5, step=1, key="nof_max_sub_prompts")



# Check if the chat messages are not already in the session state
if "my_chat_messages" not in st.session_state:
    # Initialize the chat messages list in the session state
    st.session_state['my_chat_messages'] = []
    # Add the system instructions to the chat messages
    st.session_state['my_chat_messages'].append({"role": "system", "content": st.session_state['my_system_instructions']})


# print(100*"-")
# Check if the sentences are not already in the session state
if "my_sentences_rag" not in st.session_state:
    create_sentences_rag()




with column_2:
    # Create a container for the messages with a specified height
    messages_container = st.container(height=500)
    
    # Display the first message from the assistant
    messages_container.chat_message("ai", avatar=":material/robot_2:").markdown(first_message)
    
    # Iterate through the chat messages stored in the session state
    for message in st.session_state['my_chat_messages']:
        if message['role'] == "user":
            # Display user messages with a specific avatar - https://fonts.google.com/icons
            messages_container.chat_message(message['role'], avatar=":material/psychology_alt:").markdown(message['content'])
        elif message['role'] == "assistant":
            # Display assistant messages with a specific avatar
            messages_container.chat_message(message['role'], avatar=":material/robot_2:").markdown(message['content'])

    # Check if there is a new prompt from the user
    if prompt := st.chat_input("you may ask here your questions"):
        
        # Split the prompt into words
        split_prompt = prompt.split(" ")
        all_sub_prompts = []
        # Generate sub-prompts based on the specified range
        for jj in range(st.session_state['nof_min_sub_prompts'], st.session_state['nof_max_sub_prompts']+1):
            for ii in range(len(split_prompt)):
                # Create sub-prompt by joining words
                i_split = " ".join(split_prompt[ii:ii+jj]).strip()
                if i_split:
                    all_sub_prompts.append(i_split)
        
        similarities_to_question = np.zeros(len(st.session_state['my_embeddings']))
        for sub_prompt in all_sub_prompts:
            # Encode the user's prompt to get its embedding
            my_question_embedding = st.session_state.embeddings_model.encode([sub_prompt])
            
            # Calculate the cosine similarity between the prompt embedding and stored embeddings
            similarities_to_question += cosine_similarity(my_question_embedding, st.session_state['my_embeddings']).flatten()
        similarities_to_question /= len(all_sub_prompts)
        
        # Get the indices of the top similar sentences
        bottom_col1, bottom_col2 = st.columns([1, 1])
        sorted_indices_rag = similarities_to_question.argsort()[::-1]
        sorted_indices_sentences = []
        max_similarity = 0
        # for irag in range(st.session_state['nof_keep_sentences']):
        irag = 0
        while len(set(sorted_indices_sentences))<st.session_state['nof_keep_sentences'] and irag<len(sorted_indices_rag):
            sorted_indices_sentences.extend(st.session_state['my_sentences_rag_ids'][sorted_indices_rag[irag]])
            max_similarity = max(max_similarity, similarities_to_question[sorted_indices_rag[irag]])
            with bottom_col1:
                str_conf = f"Confidence: {similarities_to_question[sorted_indices_rag[irag]]:.5f}, Sentences IDs: {st.session_state['my_sentences_rag_ids'][sorted_indices_rag[irag]]}"
                with st.expander(f"Chunk: {str(irag+1)} {str_conf}"):
                    for idx in st.session_state['my_sentences_rag_ids'][sorted_indices_rag[irag]]:
                        st.write(f"{st.session_state['my_sentences'][idx]}")
            irag += 1

        sorted_indices_sentences = sorted(list(set(sorted_indices_sentences)))

        # Display the user's prompt in the chat container with a specific avatar
        messages_container.chat_message("user", avatar=":material/psychology_alt:").markdown(prompt) 
        
        # Create an empty container for the streaming response from the assistant
        with messages_container.chat_message("ai", avatar=":material/robot_2:"):
            response_placeholder = st.empty()
            if max_similarity > st.session_state['my_similarity_threshold']:
                # Construct the augmented prompt with the similar sentences
                augmented_prompt = "This is my context:" + "\n\n" + 20*"-" + "\n\n" 
                augmented_prompt += "\n".join([st.session_state['my_sentences'][idx] for idx in sorted_indices_sentences])
                augmented_prompt += "\n\n" + 20*"-" + "\n\n" + "If the above context is not relevant to the prompt, ignore the context and reply based only on the prompt."
                augmented_prompt += "\n\n" + 20*"-" + "\n\n" + "If the above context is relevant to the prompt, reply based on the context and the prompt."
                augmented_prompt += "\n\n" + 20*"-" + "\n\n" + "The prompt is:"
                augmented_prompt += "\n\n" + f"\n\n{prompt}"
                # Append the augmented prompt to the chat messages in the session state
                st.session_state['my_chat_messages'].append({"role": "user", "content": augmented_prompt})
                # Stream the response from the assistant and update the placeholder
                response = ""
                for chunk in st.session_state['client'].chat.completions.create(messages = st.session_state['my_chat_messages'], 
                                                                                model = st.session_state['my_llm_model'],
                                                                                stream = True, 
                                                                                max_tokens = 1024):
                    if chunk.choices[0].delta.content:
                        response += chunk.choices[0].delta.content
                        # Use markdown to update the response placeholder with the streamed content
                        response_placeholder.markdown(response)
                # Remove the last message from the chat messages in the session state
                st.session_state['my_chat_messages'].pop()
            else:
                augmented_prompt = ""
                response = f"I do not have enough information to reply. The maximum similarity found in the context is: {100*max_similarity:.2f}%."
                response_placeholder.markdown(response)

        # Append the user's original prompt to the chat messages in the session state
        st.session_state['my_chat_messages'].append({"role": "user", "content": prompt})
        # Append the assistant's response to the chat messages in the session state
        st.session_state['my_chat_messages'].append({"role": "assistant", "content": response})


        if len(st.session_state['my_chat_messages'])>10:
            # Keep the first message which is the system instructions, remove the 2nd and 3rd messages which are the first user and assistant messages
            st.session_state['my_chat_messages'] = st.session_state['my_chat_messages'][:1] + st.session_state['my_chat_messages'][3:]
    
            

        with bottom_col2:
            # Display the augmented prompt used for generating the response
            st.write("Augmented prompt:")
            st.json({"max_similarity": max_similarity, "augmented_prompt": augmented_prompt}, expanded=False)

            # Display the chat messages history
            st.write("Messages History All:")
            st.json(st.session_state['my_chat_messages'], expanded=False)