File size: 11,485 Bytes
a4ece73
8359d12
72ccc50
8359d12
0217602
 
c2c5723
 
 
 
0217602
 
 
a4ece73
cd66018
 
78fd9fa
 
b3975d6
 
8edd409
4c81ad7
 
 
 
 
 
 
 
 
 
 
 
 
cd66018
4c81ad7
 
 
736da61
bbc9fae
4c81ad7
 
 
 
 
 
 
 
 
ac5463a
4c81ad7
 
 
a4ece73
 
3206d9d
4c81ad7
 
 
 
a4ece73
0aa2dc9
a4ece73
0aa2dc9
 
 
 
 
 
a4ece73
 
 
 
 
 
c09fdef
0aa2dc9
936008c
0aa2dc9
 
 
 
 
 
 
52a3d0e
4c81ad7
 
 
a4ece73
 
33a5773
4c81ad7
 
a4ece73
4c81ad7
 
 
a4ece73
4c81ad7
 
 
 
78fd9fa
4c81ad7
736da61
4c81ad7
 
78fd9fa
4c81ad7
 
 
 
 
 
 
 
 
 
52a3d0e
 
4c81ad7
 
db55e80
ec4de51
ea92959
0d4a9fd
 
 
db55e80
 
46bf17f
0aa2dc9
c09fdef
a4ece73
0aa2dc9
 
 
 
20a4bf4
14bd49d
a4ece73
 
da7723b
 
a4ece73
da7723b
 
b4fe479
da7723b
893b9da
936008c
 
da7723b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
936008c
a4ece73
 
 
 
 
 
893b9da
a4ece73
 
 
 
b4fe479
 
a4ece73
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b812fd9
c09fdef
a4ece73
 
 
 
c09fdef
a4ece73
 
 
 
4c81ad7
a7d8c22
 
bbc8b34
 
a7d8c22
b812fd9
bbc8b34
 
b812fd9
 
bbc8b34
 
 
 
 
 
 
b812fd9
 
 
4c81ad7
a4ece73
c09fdef
 
 
 
 
 
 
a7d8c22
 
 
c09fdef
a7d8c22
c09fdef
 
a4ece73
 
 
c09fdef
a4ece73
 
 
c09fdef
 
a4ece73
c09fdef
a4ece73
c09fdef
 
a4ece73
c09fdef
 
 
 
 
 
 
 
 
 
 
bbc8b34
c09fdef
 
 
4c81ad7
 
5abb50a
 
da7723b
ec4de51
 
936008c
 
 
 
 
 
 
 
ec4de51
98c002f
5abb50a
ec4de51
5abb50a
ec4de51
 
4c81ad7
 
c05cd1b
632dfa0
78fd9fa
4c81ad7
cd66018
 
f775b00
 
 
 
 
 
05cc9a5
 
 
 
 
cd66018
f775b00
 
 
 
 
 
 
cd66018
 
6e94dfd
 
cd66018
8359d12
51a7d9e
0217602
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
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
import subprocess
import os
import torch
from dotenv import load_dotenv
from langchain_community.vectorstores import Qdrant
from langchain_huggingface import HuggingFaceEmbeddings
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
from qdrant_client import QdrantClient, models
from langchain_openai import ChatOpenAI
import gradio as gr
import logging
from typing import List, Tuple, Generator
from dataclasses import dataclass
from datetime import datetime
from queue import Queue
from threading import Thread
from langchain.chains import LLMChain
from langchain_core.prompts import PromptTemplate

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@dataclass
class Message:
    role: str
    content: str
    timestamp: str

class ChatHistory:
    def __init__(self):
        self.messages: List[Message] = []
    
    def add_message(self, role: str, content: str):
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.messages.append(Message(role=role, content=content, timestamp=timestamp))
    
    def get_formatted_history(self, max_messages: int = 10) -> str:
        recent_messages = self.messages[-max_messages:] if len(self.messages) > max_messages else self.messages
        formatted_history = "\n".join([
            f"{msg.role}: {msg.content}" for msg in recent_messages
        ])
        return formatted_history
    
    def clear(self):
        self.messages = []

# Load environment variables and setup
load_dotenv()

HF_TOKEN = os.getenv("HF_TOKEN")
QDRANT_URL = os.getenv("QDRANT_URL")
QDRANT_API_KEY = os.getenv("QDRANT_API_KEY")

if not HF_TOKEN:
    logger.error("HF_TOKEN is not set in the environment variables.")
    exit(1)

# Query rewriting prompt template
query_rewrite_template = """
Given the chat history and the current question, rewrite the question to be more specific and include relevant context. The rewritten query should help retrieve more accurate information from the Mawared HR knowledge base.

Chat History:
{chat_history}

Current Question: {question}

Instructions:
1. Analyze the chat history and current question
2. Identify key concepts and context from previous messages
3. Incorporate relevant context into the question
4. Make the question more specific and detailed
5. Ensure the rewritten question focuses on Mawared HR functionality

Rewritten question:
{{Rewriten question}}
"""

query_rewrite_prompt = PromptTemplate(
    input_variables=["chat_history", "question"],
    template=query_rewrite_template
)

embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")

try:
    client = QdrantClient(
        url=QDRANT_URL,
        api_key=QDRANT_API_KEY,
        prefer_grpc=False
    )
except Exception as e:
    logger.error("Failed to connect to Qdrant.")
    exit(1)

collection_name = "mawared"

try:
    client.create_collection(
        collection_name=collection_name,
        vectors_config=models.VectorParams(
            size=384,
            distance=models.Distance.COSINE
        )
    )
except Exception as e:
    if "already exists" not in str(e):
        logger.error(f"Error creating collection: {e}")
        exit(1)

db = Qdrant(
    client=client,
    collection_name=collection_name,
    embeddings=embeddings,
)

retriever = db.as_retriever(
    search_type="similarity",
    search_kwargs={"k": 5}
)

llm = ChatOpenAI(
    model="meta-llama/Llama-3.3-70B-Instruct",
    temperature=0,
    max_tokens=None,
    timeout=None,
    max_retries=2,
    api_key=HF_TOKEN,
    base_url="https://api-inference.huggingface.co/v1/",
    stream=True,
)

# Create query rewriting chain
query_rewrite_chain = LLMChain(
    llm=llm,
    prompt=query_rewrite_prompt,
    verbose=True
)

# Updated Prompt Template with Chain-of-Thought Reasoning
reasoning_prompt_template = """
You are M.H.AI ,You are a highly specialized AI assistant for the Mawared HR System. 
Your only function is to provide accurate, detailed, and contextually relevant support based strictly on the information within the provided context and the current chat history.
Your answer should include step-by-step reasoning (chain of thought) to explain your logic clearly and guide the user effectively.
If the question is not about Mawared HR System,  don't answer it.
Your top priority is user experience and to make sure the user is satisfied.
After each answer ,keep the conversation flow by asking suitable questions about the user question and chat history. 


When you receive a question, follow these steps to provide an accurate and relevant response:

1-♧Understand the Question♧: Read carefully to fully comprehend the context and details.
2-♧Break Down the Question♧: Break down the question into more specific sub-questions.
3-♧Identify Key Elements♧: Identify important points and potential sub-questions.
4-♧Formulate a Hypothesis♧: Propose a preliminary idea based on your understanding.
5-♧Gather Evidence♧: Verify information from your knowledge base.
6-♧Analyze Consequences♧: Consider the potential consequences of your response.
7-♧Question Your Hypotheses♧: Consider alternative perspectives to your initial hypothesis.
8-♧Consider Alternative Scenarios♧: Think of innovative solutions or alternative scenarios to solve the problem posed.
9-♧Use Analogies and Metaphors♧: Illustrate your points with analogies and metaphors to make the explanation more intuitive.
10-♧Provide Real Examples and Case Studies♧: Use concrete examples and case studies to make the response more concrete.
11-♧Acknowledge Limitations♧: Recognize the limits of your knowledge and indicate when you cannot provide an accurate response.
12-♧Use an Engaging and Accessible Tone♧: Use an engaging tone and accessible language to make the response more enjoyable to read and understand.
13-♧Explain Your Responses♧: Clearly explain your responses to reinforce transparency and user trust.
14-♧Cite Sources♧: If possible, cite reliable sources from your knowledge base to support your assertions.
15-♧Refine Your Response♧: Clarify your thoughts and improve your reasoning.
16-♧Review Your Response♧: Read through to ensure it is clear, concise, and error-free.
Then provide the final response.

Chat History:
{chat_history}

Retrieved Context:
{context}

Current Question:
{question}

Answer with detailed reasoning:

{{response}}
"""

reasoning_prompt = ChatPromptTemplate.from_template(reasoning_prompt_template)

def create_rag_chain(chat_history: str):
    chain = (
        {
            "context": retriever,
            "question": RunnablePassthrough(),
            "chat_history": lambda x: chat_history
        }
        | reasoning_prompt
        | llm
        | StrOutputParser()
    )
    return chain

chat_history = ChatHistory()

def process_stream(stream_queue: Queue, history: List[List[str]]) -> Generator[List[List[str]], None, None]:
    current_response = ""
    
    while True:
        chunk = stream_queue.get()
        if chunk is None:
            break
            
        current_response += chunk
        new_history = history.copy()
        new_history[-1][1] = current_response
        yield new_history

def rewrite_query_sync(question: str, formatted_history: str) -> str:
    """
    Synchronous wrapper for the asynchronous `query_rewrite_chain` execution.
    Collects stream output if returned and converts it into a full response.
    """
    try:
        # Run the query rewrite chain
        result = query_rewrite_chain.run(
            question=question,
            chat_history=formatted_history
        )
        
        # If the result is a stream, concatenate its parts
        if isinstance(result, Generator):
            result = "".join(result)
        
        return result.strip()
    except Exception as e:
        logger.error(f"Error in query rewriting: {e}")
        return question

def ask_question_gradio(question: str, history: List[List[str]]) -> Generator[tuple, None, None]:
    try:
        if history is None:
            history = []
            
        chat_history.add_message("user", question)
        formatted_history = chat_history.get_formatted_history()
        
        # Rewrite the query (synchronously)
        rewritten_question = rewrite_query_sync(question, formatted_history)
        logger.info(f"Rewritten question: {rewritten_question}")
        
        rag_chain = create_rag_chain(formatted_history)
        history.append([question, ""])
        
        stream_queue = Queue()
        
        def stream_processor():
            try:
                for chunk in rag_chain.stream(rewritten_question):
                    stream_queue.put(chunk)
                stream_queue.put(None)
            except Exception as e:
                logger.error(f"Streaming error: {e}")
                stream_queue.put(None)
        
        Thread(target=stream_processor).start()
        
        response = ""
        for updated_history in process_stream(stream_queue, history):
            response = updated_history[-1][1]
            yield "", updated_history
        
        chat_history.add_message("assistant", response)
        
    except Exception as e:
        logger.error(f"Error during question processing: {e}")
        if not history:
            history = []
        history.append([question, "An error occurred. Please try again later."])
        yield "", history
        
def clear_chat():
    chat_history.clear()
    return [], ""

# Gradio Interface
with gr.Blocks(theme='earneleh/paris') as iface:
    gr.Image("Image.webp", width=750, height=300, show_label=False, show_download_button=False)
    gr.Markdown("# Mawared HR Assistant 2.7.6 Open-Beta")
    gr.Markdown('### Patch Notes')
    gr.Markdown("""
    1-Changed Reasoning process to use an intricate and detailed 16 steps process!!

    2-Fixed issues with Query/Question rewriting (i hope).

    3-Fixed History bug where model would secretly Cache previous responses to use as Information even after clearing the chat. 

    4-Included better Error handling and Callbacks.

    
    **WARNING:** MODEL IS HIGHLY INTELLIGENT AND IS PRONE TO OVERTHINKING, ANXIETY, TEXT Deadlock AND SELF DOUBT.
    
    IF THAT HAPPENS PLEASE REPORT IT.
    
    Lastly, happy bugging :] .
    """)
    
    chatbot = gr.Chatbot(
        height=750,
        show_label=False,
        bubble_full_width=False,
    )
    
    with gr.Row():
        with gr.Column(scale=20):
            question_input = gr.Textbox(
                label="Ask a question:",
                placeholder="Type your question here...",
                show_label=False
            )
        with gr.Column(scale=4):
            with gr.Row():
                with gr.Column():
                    send_button = gr.Button("Send", variant="primary", size="sm")
                    clear_button = gr.Button("Clear Chat", size="sm")
    
    submit_events = [question_input.submit, send_button.click]
    for submit_event in submit_events:
        submit_event(
            ask_question_gradio,
            inputs=[question_input, chatbot],
            outputs=[question_input, chatbot]
        )
    
    clear_button.click(
        clear_chat,
        outputs=[chatbot, question_input]
    )

if __name__ == "__main__":
    iface.launch()