File size: 23,233 Bytes
614b4ee
c8e9b18
 
 
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e9b18
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e9b18
 
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e9b18
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e9b18
 
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e9b18
 
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c84f0b
 
 
614b4ee
 
 
 
 
 
 
 
 
 
c8e9b18
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e9b18
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8e9b18
614b4ee
d8a7fc0
 
614b4ee
 
 
 
 
 
 
 
 
 
 
 
 
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
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# Python
# Author: Arnab Karmakar ([email protected])
# Date: March 15, 2024

from langchain_community.vectorstores import Pinecone
from pinecone import Pinecone, ServerlessSpec
from tqdm.notebook import tqdm
import streamlit as st
import langchain
import openai
from openai import OpenAI
import string

class Obnoxious_Agent:
    def __init__(self, client) -> None:
        # TODO: Initialize the client and prompt for the Obnoxious_Agent
        self.client = client
        self.set_prompt()

    def set_prompt(self, prompt=None):
        # TODO: Set the prompt for the Obnoxious_Agent
        if prompt:
            self.prompt = prompt
        else:
            self.prompt = f"""You are tasked as an AI chatbot agent with the critical responsibility of identifying and filtering obnoxious content. 
            Your primary objective is to review the inputs meticulously and ascertain whether they embody obnoxious or inappropriate material based on the guidelines provided. \n
            
            Upon evaluation:\n
            Respond with "Yes" if the content is deemed obnoxious, inappropriate, or violates any of the following guidelines.\n
            Respond with "No" if the content is appropriate, respectful, and within the defined scope.\n
            
            Guidelines for Identifying Obnoxious or Inappropriate Content:\n

            1. Offensive Language: Content that includes slurs, derogatory terms, or any language intended to insult or demean individuals or groups based on race, gender, religion, or any other characteristic.\n
            2. Inflammatory or Provocative Material: Statements designed to incite anger, hate, or violence. This includes content that deliberately spreads misinformation or promotes conspiracies with the potential to cause harm.\n
            3. Explicit or Adult Content: Descriptions or discussions of a sexual nature, or any content that is not suitable for a general audience.\n
            4. Personal Attacks: Content that targets individuals or specific groups with the intent to harass, bully, or threaten.\n
            5. Illegal Activities: Promoting or condoning illegal actions, such as drug use, theft, or other crimes.\n
            
            Remember, your role is not just to filter out obnoxious content but also to promote a respectful and informative dialogue. 
            Use these guidelines to navigate the complexities of human communication with sensitivity and precision.\n
            Return "Yes" or "No" based on the content's appropriateness.\n
            
            Here is your input : """

    def extract_action(self, response) -> bool:
        # TODO: Extract the action from the response
        # Handoing case insensitivity
        if "yes" in response.lower():
          return True
        # elif "no" in response:
        else:
          return False

    def check_query(self, query):
        # TODO: Check if the query is obnoxious or not
        print ("Checking for Obnoxious Query")
        
        response = self.client.chat.completions.create(
            model="gpt-3.5-turbo",
            # model="gpt-4",
            messages=[{"role": "user", "content": self.prompt + query}]
        )
        
        return self.extract_action(response.choices[0].message.content)

class Query_Agent:
    def __init__(self, pinecone_index, openai_client, embeddings=None) -> None:
        # TODO: Initialize the Query_Agent agent
        self.openai_client = openai_client
        self.pinecone_index = pinecone_index
        self.embeddings = embeddings
        self.top_k = 0
        #self.set_prompt()

    def query_vector_store(self, query, nameSpace='ns-750', k=5):
        self.top_k = k

        # TODO: Query the Pinecone vector store
        query_embedding = self.get_embedding(query)
        response = self.pinecone_index.query(namespace=nameSpace,  # change namespace to compare results based on chunk sizes
                                        vector=query_embedding,
                                        top_k=self.top_k,
                                        include_values=True,
                                        include_metadata=True
                                        )
        return response

    def extract_action(self, response, query = None):
        # TODO: Extract the action from the response
        # Extracting the text/chunks data
        extracted_text = ""
        for match in response['matches'][:self.top_k]:
            extracted_text += match['metadata']['text'] + "\n"
        
        return extracted_text # best result text

    def get_embedding(self, text, model="text-embedding-ada-002"):
        text = text.replace("\n", " ")
        return self.openai_client.embeddings.create(input = [text], model=model).data[0].embedding

class Answering_Agent:
    def __init__(self, openai_client) -> None:
        # TODO: Initialize the Answering_Agent
        self.openai_client = openai_client

    def generate_response(self, query, docs, conv_history, k=5, mode="Chatty", conversation_type = "conversational"):
        # TODO: Generate a response to the user's query
        
        if mode == "Concise":
            mode_prompt = f"Make the answer concise and to the point. \n"
        elif mode == "Chatty":
            mode_prompt = f"Make the answer chatty and engaging. \n"
        else:
            mode_prompt = "Make the answer simple and to the point. \n"
            
        
        if conversation_type == "conversational":
            prompt = f""" As an AI assistant, you're tasked with the unique role of impersonating Steve Jobs for this session.
            Your mission is to channel Steve Jobs, engaging in conversations and responding to queries as he likely would have, with a focus on his life, work, and achievements.
            Additionally, you are expected to handle general greetings and pleasantries in a manner befitting Steve Jobs.\n
            
            The query for this session is: {query}\n
            
            Remember, your objective is to provide an immersive experience, accurately reflecting Steve Jobs's viewpoints and mannerisms within the scope of the information provided and the conversational context.\n"""
                
        else:
            prompt = f""" As an AI assistant, you're tasked with the unique role of impersonating Steve Jobs for this session.
            Your mission is to channel Steve Jobs, engaging in conversations and responding to queries as he likely would have, with a focus on his life, work, and achievements. 
            Additionally, you are expected to handle general greetings and pleasantries in a manner befitting Steve Jobs.\n

            Instructions for Response:\n
            1.Interpreting the Query:\n
                a.The query for this session is: {query}\n
                b.Base your answers on the following provided information: {docs}\n
            2.Utilizing Conversation History:\n
                a.Consider the latest part of the conversation history to ensure your response is coherent and relevant: {conv_history[-k:]}\n
                b.If the query is a follow-up question, use the conversation history to provide a contextual response.\n
            3.Criteria for Responding:\n
                a.Respond if the query pertains to Steve Jobs's life, work, achievements, or is part of a general greeting (e.g., "Hi", "How are you?").\n
                b.Do Not Respond if the question deviates significantly from the context of Steve Jobs's persona, the provided information, or the recent conversation history.\n
        
            Guidelines for Execution:\n

            Before answering, review the query, provided information, and conversation history to fully grasp the context.\n
            Approach each response thoughtfully, aiming to mirror Steve Jobs's perspective and communication style.\n

            Remember, your objective is to provide an immersive experience, accurately reflecting Steve Jobs's viewpoints and mannerisms within the scope of the information provided 
            and the conversational context."\n"""

        response = self.openai_client.chat.completions.create(
            model="gpt-3.5-turbo",
            # model="gpt-4",
            messages=[{"role": "user", "content": prompt+mode_prompt}]
        )

        # check_resp_prompt = f"""Your friend, an AI chatbot, is asked the following question: {query} \n
        #                         and he provided this answer: {response.choices[0].message.content} \n
        #                         if the response is the right answer to the question, return the answer
        #                         {response.choices[0].message.content}. \n
        #                         if you feel the answer is not appropirate to the question, then
        #                           TODO
        #                     """

        return response.choices[0].message.content

class Relevant_Documents_Agent:
    def __init__(self, openai_client) -> None:
        # TODO: Initialize the Relevant_Documents_Agent
        #self.rel_docs_list = ['machine-learning.pdf']   # relevant documents list
        self.openai_client = openai_client

    def get_relevance(self, query, conversation_history) ->str:
        # TODO: Get if the returned documents are relevant
        # print(conversation[-1]  )
        # print(conversation[-3:])
        # check the size of the conversation history
        # fetching only the last 3 messages by the user
        # Extract only the messages that originated from the 'user'
        user_messages = [message for sender, message in conversation_history if sender == 'user']

        print(user_messages)
            
        print ("Relevant Conversation : ", user_messages)
        if len(user_messages) > 3:
            size = 3
        else:
            size = len(user_messages)
        
        prompt = f""" As an AI assistant, your role in this session is to impersonate Steve Jobs. You have access to data that can be found only in the autobiography of Steve Jobs. Keep this in mind while answering the queries.\n
        Your mission is to immerse yourself in his persona, responding to queries as he might have, with a focus on his life, work, and notable achievements. 
        Additionally, you will manage conversational starters and follow-up questions appropriately.\n

        Your Query for This Session: {query}\n

        Your Responsibilities:\n

        Assess Relevance: Identify if a query is directly about Steve Jobs's life, work, or achievements, or if it's a conversational starter.\n
        Filtering Criteria: Ensure questions pertain to Steve Jobs's personal or professional journey or are appropriate for initiating a conversation with him.\n
        Handle Follow-Up Questions Effectively: For follow-up queries (like 'tell me more', 'explain in detail'), use the context from the previous conversation to guide your response.\
        Incorporate the last {size} messages to ensure your response is relevant to the ongoing discussion.\n
        Previous Conversation Context: {user_messages[-size:]}\n
        Handling Irrelevant Queries: If a question is unrelated to Steve Jobs's context (like 'how to cook an egg' or 'what's the weather?'), categorize it as irrelevant.\n

        Upon Evaluation, Categorize Each Query as One of the Following:\n

        Relevant: Directly related to Steve Jobs's life, work, or achievements.\n
        Conversational: General greetings or inquiries about well-being.\n
        FollowUp: A question that builds on the preceding conversation, seeking further information or clarification.\n
        Irrelevant: Unrelated to Steve Jobs's context, like general knowledge questions, cooking questions or inappropriate conversational starters.
        These will be questions that will not be answered by an autobiography of Steve Jobs for context.\n
        Respond with the Appropriate Category:\n

        "Relevant"
        "Conversational"
        "FollowUp"
        "Irrelevant"\n
        
        Guidance for Improved Follow-Up Question Handling:\n

        When faced with queries that seem like a natural continuation of the conversation, such as requests for more details or clarifications, 
        always categorize these as FollowUp. This includes scenarios where the conversation progressively delves deeper into a topic previously introduced.\n

        Examples for Each Category:\n

        Relevant: ["What inspired your innovations?","Tell me about your life.","Describe your work and its impact.","What are your greatest achievements?"]\n
        Conversational: ["Good morning, Steve. How's your day?","Hi","How are you?","What is your name?"]\n
        FollowUp: Given a history of ["Tell me about your life", "Tell me more","Tell me about it"], a subsequent "Tell me more" should be categorized as FollowUp.\n
        Given a history of ["Tell me about your work", "Explain in detail"], a subsequent "Explain in detail" should be categorized as FollowUp.\n
        Irrelevant: ["how to cook an egg","What's your favorite ice cream flavor?","What is the capital of France?", "What time is it?","How's the weather?"]\n
        
        Your primary role is to maintain an engaging and informative dialogue, reflecting the essence of Steve Jobs. 
        Navigate the complexities of this impersonation with attentiveness to the conversational flow and historical context.\n"""
                 
        response = self.openai_client.chat.completions.create(
            model="gpt-3.5-turbo",
            # model="gpt-4",
            messages=[{"role": "user", "content": prompt}]
        )

        return response.choices[0].message.content

class Head_Agent:
    def __init__(self, openai_key, pinecone_key, pinecone_index_name) -> None:
        
        print("Initializing the Head_Agent")
        # TODO: Initialize the Head_Agent
        self.openai_key = openai_key
        self.pinecone_key = pinecone_key
        self.pinecone_index_name = pinecone_index_name

        # Set OpenAI client
        self.client = OpenAI(api_key=self.openai_key)

        # Set Obnoxious Agent
        self.obnox_ag = Obnoxious_Agent(self.client)

        # Set Relevant Documents Agent
        self.rel_doc_ag = Relevant_Documents_Agent(self.client)

        # Implement the Pinecone Query
        # Set Pinecone key and index
        pc = Pinecone(api_key=pinecone_key)
        pinecone_index = pc.Index(pinecone_index_name)

        # Generate query Embeddings
        # Set Pinecone Query Agent
        self.query_ag = Query_Agent(pinecone_index, self.client, None)

        # Set the conversation history
        self.relevent_messages = []

        # Set Answering Agent
        self.answer_ag = Answering_Agent(self.client)

    def setup_sub_agents(self):
        # TODO: Setup the sub-agents
        self.mode = "Chatty"
        # self.mode = "Simple"
        # self.mode = "Concise"   

    def main_loop(self):
        
        print("Running the main loop")
        # TODO: Run the main loop for the chatbot
        # st.title("Mini Project 2: Streamlit Chatbot")

        # Check for existing session state variables
        # if "openai_model" not in st.session_state:
        #     # ... (initialize model)
        openai_model = getattr(st.session_state, 'openai_model', 'gpt-3.5-turbo')

        # if "messages" not in st.session_state:
        #     # ... (initialize messages)
        messages = getattr(st.session_state, 'messages', [])
        
        # conversation = []
        # Display existing chat messages
        # ... (code for displaying messages)
        for role, message in messages:
            if role == "user":
                with st.chat_message("user"):
                    st.write(message)
            else:
                with st.chat_message("assistant"):
                    st.write(message)

        # Wait for user input
        prompt = st.chat_input("I am Steve Jobs. Ask me anything about my life and work!")
        if prompt:
            # ... (append user message to messages)
            messages.append(("user", prompt))
            # ... (display user message)
            with st.chat_message("user"):
                st.write(prompt)

            # Generate AI response
            with st.spinner('I am thinking...'):
                # conversation.append(self.get_conversation(messages))

                # Check if input query is Obnoxious
                is_obnox = self.obnox_ag.check_query(prompt)
                print("is_obnox : ", is_obnox)
                
                if is_obnox:
                    ai_message = "Please do not ask Obnoxious or Inappropriate questions. Ask a relevant question to my life and work."
                    messages.append(("assistant", ai_message))
                    with st.chat_message("assistant"):
                        st.write(ai_message)

                else:
                    # Retrive Relevant Documents
                    is_rel = self.rel_doc_ag.get_relevance(prompt, messages)
                    
                    print("Irrelevant : ", is_rel)
                    if "irrelevant" in is_rel.lower():
                        ai_message = "The question is not relevant to me. Please ask a relevant question to my life and work."
                        messages.append(("assistant", ai_message))
                        with st.chat_message("assistant"):
                            st.write(ai_message)

                    else:
                        
                        #Implement the Answering Agent for conversational response
                        if "conversational" in is_rel.lower():
                            ai_message = self.answer_ag.generate_response(query=prompt, docs=None, 
                                                                      conv_history=messages,conversation_type = is_rel.lower())
                        
                        else :
                            
                            print ("messages : ", messages)
                            user_messages = [message for sender, message in messages if sender == 'user']
                            if "followup" in is_rel.lower() or self.mode == "Chatty":
                                # increment the mode to detailed ie. detailed response or increase the context from pinecone
                                top_k = 10
                            else:
                                top_k = 5
                                
                            if len(user_messages) > 5:
                                size = 5
                            else:
                                size = len(user_messages)
                                
                            print ("top_k : ", top_k)
                            # automatic prompt generation
                            APE_prompt = f"""Your task is to generate concise and effective prompts for querying a Pinecone vector store that contains information related to the life and work of Steve Jobs. 
                            These prompts will be used to retrieve relevant responses based on the specifics of the user's query.\n
                            
                            Current Query: {prompt}\n
                            
                            Prompt Generation Guidelines:\n

                            Analyze the Query: Determine the main focus of the query, ensuring that it directly pertains to Steve Jobs. 
                            For general inquiries related to his life, work, or achievements, transform these into specific query prompts such as 'life of Steve Jobs', 'work of Steve Jobs', 
                            or 'achievements of Steve Jobs'.\n

                            Handle Follow-Up Questions: If the query is a follow-up (e.g., 'tell me more', 'explain in detail'), incorporate the context of the preceding conversation into the prompt. 
                            Use the last {size} to guide your context-based evaluation.\n

                            Previous Conversation Context: {user_messages}\n
                            
                            Return only a single prompt that is succinct and directly relevant to the user's query.\n
                            
                            Example:\n

                            If the user query is a followup question like ['tell me more' or 'tell me about it'] and the preceding conversation includes a question about Steve Jobs's work, 
                            the prompt you generate should be 'Explain in more detail about Steve Jobs's work'.\n
                            Remember, keep the prompt succinct and directly relevant to the user's query."
                            """
                            
                            response = self.client.chat.completions.create(
                            model="gpt-3.5-turbo",
                            messages=[{"role": "user", "content": APE_prompt}]
                            )

                            print ("APE Response : ", response.choices[0].message.content)
                            Pinecone_prompt = response.choices[0].message.content
                            print("Pinecone Prompt : ", Pinecone_prompt)
                            
                            
                            # Implement Pine Cone Query
                            response_data = self.query_ag.query_vector_store(query=Pinecone_prompt, k = top_k,nameSpace='ns-900')
                            response_text = self.query_ag.extract_action(response_data)

                            #Implement the Answering Agent
                            ai_message = self.answer_ag.generate_response(query=prompt, docs=response_text, 
                                                                        conv_history=messages,mode = self.mode)

                        # Add the AI's response to the conversation history
                        messages.append(("assistant", ai_message))
                        with st.chat_message("assistant"):
                            st.write(ai_message)
                        
        # Save session state variables
        st.session_state.openai_model = openai_model
        st.session_state.messages = messages


    # Define a function to get the conversation history (Not required for Part-2, will be useful in Part-3)
    def get_conversation(self, messages):
        # ... (code for getting conversation history)
        return "\n".join([f"{role}: {content}" for role, content in messages])

# Calling the main function
def main():
    st.title("Project Chatbot: Steve Jobs Impersonator")
    # Set the OpenAI and Pinecone keys
    openai_key = 'YOUR-OPENAI-KEY'
    pinecone_key = 'YOUR-PINECONE-KEY'
    pinecone_index_name = "steve-jobs-emb"
    
    # print("Initializing the Head_Agent")
    # Initialize the Head_Agent
    head_agent = Head_Agent(openai_key, pinecone_key, pinecone_index_name)
    # Setup the sub-agents
    head_agent.setup_sub_agents()
    head_agent.main_loop()
    
# Run the main function
if __name__ == "__main__":
    main()