File size: 9,277 Bytes
ce7c258
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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

# ==========================
# Imports
# ==========================

import os, sys, random
import gradio as gr
from   langchain_openai  import ChatOpenAI
from   langchain.schema  import AIMessage, HumanMessage
from   langchain.prompts import ChatPromptTemplate
from   langchain.schema.output_parser import StrOutputParser
from   langchain_core.runnables import RunnableLambda
from   langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage, HumanMessageChunk
from   game_info import *

# ==========================
# Constants / Globals
# ==========================

NVIDIA_API_KEY    = os.environ["API_KEY"]                             # API Key for the LLM - Using NVIDIA's NIM to access an 8 Billion parameter Llama3 model

CHATBOT_HEIGHT    = 700                                               # Height of Gradio Chatbot
LINES_PER_BOX     = 20                                                # Number of text lines in a gradio text box
SUSPECTS_PER_PAGE = 10                                                # Number of suspects shown per page

game              = cMystery()                                        # New mystery game imported from game_info

# ==========================
# LLM Functions
# ==========================

# Using NVIDIA's NIM (NVIDIA Inference Microservices) to access an 8 Billion parameter Llama3 model

llm = ChatOpenAI(model    = "meta/llama3-8b-instruct",temperature=0.5,max_tokens=1024,timeout=None,max_retries=2,
                 base_url = "https://integrate.api.nvidia.com/v1",
                 api_key  = NVIDIA_API_KEY)

# ================================
# Custom LangChain Tools and Pipes
# ================================

def output_parser(ai_message: AIMessage) -> str:

    global game

    current_suspect = game.get_current_suspect()
    game.add_note_to_recap(ai_message.content, current_suspect)       # Save a record of the conversation with the suspect name in the recap

    return ai_message.content


# ==========================
# UI Functions
# ==========================


# ============================================
# Feed the chat message to the LLM
# ============================================

def predict(message, history):
    
    global game
    global llm

    current_suspect = game.get_current_suspect()

    if current_suspect is None:
        return "Please select a suspect."

    AI_Instructions = f"You are a suspect in an art theft." + \
                      f"Your name is {game.get_suspect_proper_name(current_suspect)} " + \
                      f"and your background is {game.get_suspect_background(current_suspect)}.  " + \
                      f"Your motive for stealing the painting is {game.get_suspect_motive(current_suspect)}.  " + \
                      f"If you stole the painting do not admit it.  " + \
                      f"Base all conversations on the following JSON text:{game.create_interview(current_suspect)}"

    prompt = ChatPromptTemplate.from_messages([
    ("system", f"{AI_Instructions}"),
    ("human",  f"{message}"),
    ])

    history_langchain_format = []
    history_langchain_format.append(AIMessage(content=AI_Instructions))
    history_langchain_format.append(HumanMessage(content=message))
    
    gpt_response = llm(history_langchain_format)
    output_parser(gpt_response)
    response     = gpt_response.content

    # LCEL configuration for future game expansion / options
    
    # lcel_chain = prompt | llm | output_parser
    # response   = lcel_chain.invoke(message)

    return response

# ==========================
# Generate recap of game
# ==========================

def get_recap():

    global game

    recap  = ""
    recap += "๐Ÿ’ก Hints ๐Ÿ’ก \n\n" + game.get_hints_recap() + "\n"      # Add revealed hints
    recap += "๐Ÿ•ต Suspect Interview Notes ๐Ÿ•ต \n\n"                   # Add notes from interviews
    recap += game.get_crime_recap()      

    return gr.Textbox(lines=LINES_PER_BOX, label="Recap of the Mystery", value=recap)

# ==========================
# Question suspect
# ==========================

def question_suspect(image, name):

    global game

    game.set_current_suspect(name)
    current_suspect = game.get_current_suspect()
    
    return gr.Textbox(lines = LINES_PER_BOX,
                      label = "About the Suspect",
                      value = game.get_suspect_profile(current_suspect))

# ==========================
# Generate game hint
# ==========================

def get_hint():

    global game

    return gr.Textbox(lines = LINES_PER_BOX,
                      label = "Hint About the Mystery",
                      value = game.give_hint())


# ===============================
# Reset game for a new mystery
# ===============================

def new_crime():

    global game

    game = cMystery()
    game.print_game()

    return [gr.Image  (value = game.get_stolen_painting(), height=500, width=500, label = game.get_stolen_painting_name(), interactive=False),
            gr.Textbox(lines = LINES_PER_BOX, label="A Crime has been Committed", value = game.get_crime_text()), 
            gr.Image  (value = "Question_Mark.jpg",        height=500, width=500, label = "Suspect", interactive=False),
            gr.Textbox(lines = LINES_PER_BOX, label="About the Suspect",          value = ""),]

# ============================================
# Arrest attempt - Are you correct ?
# ============================================

def arrest():

    global game
    
    current_suspect = game.get_current_suspect()
    guilty_suspect  = game.get_guilty_suspect_name()

    if current_suspect is None:
        arrest_results = "Please select a suspect on the right to arrest."
        
    elif current_suspect == guilty_suspect:
        arrest_results = "๐Ÿ‘๐Ÿ‘ Congratulations ๐Ÿ‘๐Ÿ‘ \n\nYou caught " + guilty_suspect + " and recovered the painting.\n\n" + \
                          guilty_suspect + " confessed why: \n\n'"  + game.get_suspect_motive(guilty_suspect) + "'" + \
                          "and said 'I would have gotten away with it too if it weren't for you meddling detectives'" 
                          
    else:
        arrest_results = "โŒโŒโŒ You Failed Detective โŒโŒโŒ\n\n" + guilty_suspect + " stole the painting and snuck away while you were arresting the wrong person"

    return gr.Textbox(lines = LINES_PER_BOX,
                      label = "Results of Your Arrest",
                      value = arrest_results)


# ================================
# Main Code - Draw Gradio UI
# ================================

game.print_game()                                             # DEBUG - Print out the game settings

with gr.Blocks() as demo:
        
    image_manor           = gr.Image(value = "mystery_manor.jpg", width=2400, interactive=False)

    with gr.Row():     

        with gr.Column():
            painting      = gr.Image  (value = game.get_stolen_painting(), height=500, width=500, label = game.get_stolen_painting_name(), interactive=False)
            crime_desc    = gr.Textbox(lines = LINES_PER_BOX, label="A Crime has been Committed", value = game.get_crime_text() )
            with gr.Row():
                btn_new_crime = gr.Button("โšก New Crime โšก")
                btn_recap     = gr.Button("๐Ÿ’ก Recap ๐Ÿ’ก")
                
        with gr.Column():            
            suspect_image = gr.Image  (value = "Question_Mark.jpg", height=500,width=500, label="Suspect", interactive=False)
            suspect_desc  = gr.Textbox(lines = LINES_PER_BOX,       label = "About the Suspect", value="")
            with gr.Row():
                btn_arrest= gr.Button("๐Ÿ‘ฎ Arrest ๐Ÿ‘ฎ")
                btn_hint  = gr.Button("๐Ÿ•ต Clue ๐Ÿ•ต")

        with gr.Column():                             
            suspect_list  = gr.Examples(examples = game.get_suspect_images(), inputs = [suspect_image,suspect_desc], outputs=[suspect_desc], examples_per_page=SUSPECTS_PER_PAGE, fn=question_suspect, run_on_click=True, label="Suspect List", cache_examples=False)

        with gr.Column():                        
            QnA           = gr.ChatInterface(fn=predict, examples=game.get_sample_questions(), title="โ“"+" Question the Suspect "+"โ“", fill_height=False, retry_btn=None, undo_btn=None, cache_examples=False)

    btn_new_crime.click(new_crime, inputs=None, outputs=[painting, crime_desc, suspect_image, suspect_desc])                               # Resets game to new one
    btn_recap.click    (get_recap, inputs=None, outputs=[crime_desc])                                                                      # Prints out summary of notes
    btn_arrest.click   (arrest,    inputs=None, outputs=[suspect_desc])                                                                    # Arrest selected subject 
    btn_hint.click     (get_hint,  inputs=None, outputs=[suspect_desc])                                                                    # Ask for hint/clue
    

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