-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathchatbot.py
More file actions
408 lines (317 loc) · 18.6 KB
/
chatbot.py
File metadata and controls
408 lines (317 loc) · 18.6 KB
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
# PA7, CS124, Stanford
# v.1.1.0
#
# Original Python code by Ignacio Cases (@cases)
# Update: 2024-01: Added the ability to run the chatbot as LLM interface (@mryan0)
# Update: 2025-01 for Winter 2025 (Xuheng Cai)
######################################################################
import util
from pydantic import BaseModel, Field
import numpy as np
# noinspection PyMethodMayBeStatic
class Chatbot:
"""Simple class to implement the chatbot for PA 7."""
def __init__(self, llm_enabled=False):
# The chatbot's default name is `moviebot`.
# TODO: Give your chatbot a new name.
self.name = 'moviebot'
self.llm_enabled = llm_enabled
# This matrix has the following shape: num_movies x num_users
# The values stored in each row i and column j is the rating for
# movie i by user j
self.titles, ratings = util.load_ratings('data/ratings.txt')
self.sentiment = util.load_sentiment_dictionary('data/sentiment.txt')
########################################################################
# TODO: Binarize the movie ratings matrix. #
########################################################################
# Binarize the movie ratings before storing the binarized matrix.
self.ratings = ratings
########################################################################
# END OF YOUR CODE #
########################################################################
############################################################################
# 1. WARM UP REPL #
############################################################################
def greeting(self):
"""Return a message that the chatbot uses to greet the user."""
########################################################################
# TODO: Write a short greeting message #
########################################################################
greeting_message = "How can I help you?"
########################################################################
# END OF YOUR CODE #
########################################################################
return greeting_message
def goodbye(self):
"""
Return a message that the chatbot uses to bid farewell to the user.
"""
########################################################################
# TODO: Write a short farewell message #
########################################################################
goodbye_message = "Have a nice day!"
########################################################################
# END OF YOUR CODE #
########################################################################
return goodbye_message
############################################################################
# 2. Modules 2 and 3: extraction and transformation #
############################################################################
def process(self, line):
"""Process a line of input from the REPL and generate a response.
This is the method that is called by the REPL loop directly with user
input.
You should delegate most of the work of processing the user's input to
the helper functions you write later in this class.
Takes the input string from the REPL and call delegated functions that
1) extract the relevant information, and
2) transform the information into a response to the user.
Example:
resp = chatbot.process('I loved "The Notebook" so much!!')
print(resp) // prints 'So you loved "The Notebook", huh?'
:param line: a user-supplied line of text
:returns: a string containing the chatbot's response to the user input
"""
########################################################################
# TODO: Implement the extraction and transformation in this method, #
# possibly calling other functions. Although your code is not graded #
# directly based on how modular it is, we highly recommended writing #
# code in a modular fashion to make it easier to improve and debug. #
########################################################################
if self.llm_enabled:
response = "I processed {} in LLM Programming mode!!".format(line)
else:
response = "I processed {} in Starter (GUS) mode!!".format(line)
########################################################################
# END OF YOUR CODE #
########################################################################
return response
@staticmethod
def preprocess(text):
"""Do any general-purpose pre-processing before extracting information
from a line of text.
Given an input line of text, this method should do any general
pre-processing and return the pre-processed string. The outputs of this
method will be used as inputs (instead of the original raw text) for the
extract_titles, extract_sentiment, extract_sentiment_for_movies, and
extract_emotion methods.
Note that this method is intentially made static, as you shouldn't need
to use any attributes of Chatbot in this method.
:param text: a user-supplied line of text
:returns: the same text, pre-processed
"""
########################################################################
# TODO: Preprocess the text into a desired format. #
# NOTE: This method is completely OPTIONAL. If it is not helpful to #
# your implementation to do any generic preprocessing, feel free to #
# leave this method unmodified. #
########################################################################
########################################################################
# END OF YOUR CODE #
########################################################################
return text
def extract_titles(self, preprocessed_input):
"""Extract potential movie titles from a line of pre-processed text.
Given an input text which has been pre-processed with preprocess(),
this method should return a list of movie titles that are potentially
in the text.
- If there are no movie titles in the text, return an empty list.
- If there is exactly one movie title in the text, return a list
containing just that one movie title.
- If there are multiple movie titles in the text, return a list
of all movie titles you've extracted from the text.
Example:
potential_titles = chatbot.extract_titles(chatbot.preprocess(
'I liked "The Notebook" a lot.'))
print(potential_titles) // prints ["The Notebook"]
:param preprocessed_input: a user-supplied line of text that has been
pre-processed with preprocess()
:returns: list of movie titles that are potentially in the text
"""
return []
def find_movies_by_title(self, title):
""" Given a movie title, return a list of indices of matching movies.
- If no movies are found that match the given title, return an empty
list.
- If multiple movies are found that match the given title, return a list
containing all of the indices of these matching movies.
- If exactly one movie is found that matches the given title, return a
list
that contains the index of that matching movie.
Example:
ids = chatbot.find_movies_by_title('Titanic')
print(ids) // prints [1359, 2716]
:param title: a string containing a movie title
:returns: a list of indices of matching movies
"""
return []
def extract_sentiment(self, preprocessed_input):
"""Extract a sentiment rating from a line of pre-processed text.
You should return -1 if the sentiment of the text is negative, 0 if the
sentiment of the text is neutral (no sentiment detected), or +1 if the
sentiment of the text is positive.
Example:
sentiment = chatbot.extract_sentiment(chatbot.preprocess(
'I liked "The Titanic"'))
print(sentiment) // prints 1
:param preprocessed_input: a user-supplied line of text that has been
pre-processed with preprocess()
:returns: a numerical value for the sentiment of the text
"""
return 0
############################################################################
# 3. Movie Recommendation helper functions #
############################################################################
@staticmethod
def binarize(ratings, threshold=2.5):
"""Return a binarized version of the given matrix.
To binarize a matrix, replace all entries above the threshold with 1.
and replace all entries at or below the threshold with a -1.
Entries whose values are 0 represent null values and should remain at 0.
Note that this method is intentionally made static, as you shouldn't use
any attributes of Chatbot like self.ratings in this method.
:param ratings: a (num_movies x num_users) matrix of user ratings, from
0.5 to 5.0
:param threshold: Numerical rating above which ratings are considered
positive
:returns: a binarized version of the movie-rating matrix
"""
########################################################################
# TODO: Binarize the supplied ratings matrix. #
# #
# WARNING: Do not use self.ratings directly in this function. #
########################################################################
# The starter code returns a new matrix shaped like ratings but full of
# zeros.
binarized_ratings = np.zeros_like(ratings)
########################################################################
# END OF YOUR CODE #
########################################################################
return binarized_ratings
def similarity(self, u, v):
"""Calculate the cosine similarity between two vectors.
You may assume that the two arguments have the same shape.
:param u: one vector, as a 1D numpy array
:param v: another vector, as a 1D numpy array
:returns: the cosine similarity between the two vectors
"""
########################################################################
# TODO: Compute cosine similarity between the two vectors. #
########################################################################
similarity = 0
########################################################################
# END OF YOUR CODE #
########################################################################
return similarity
def recommend(self, user_ratings, ratings_matrix, k=10, llm_enabled=False):
"""Generate a list of indices of movies to recommend using collaborative
filtering.
You should return a collection of `k` indices of movies recommendations.
As a precondition, user_ratings and ratings_matrix are both binarized.
Remember to exclude movies the user has already rated!
Please do not use self.ratings directly in this method.
:param user_ratings: a binarized 1D numpy array of the user's movie
ratings
:param ratings_matrix: a binarized 2D numpy matrix of all ratings, where
`ratings_matrix[i, j]` is the rating for movie i by user j
:param k: the number of recommendations to generate
:param llm_enabled: whether the chatbot is in llm programming mode
:returns: a list of k movie indices corresponding to movies in
ratings_matrix, in descending order of recommendation.
"""
########################################################################
# TODO: Implement a recommendation function that takes a vector #
# user_ratings and matrix ratings_matrix and outputs a list of movies #
# recommended by the chatbot. #
# #
# WARNING: Do not use the self.ratings matrix directly in this #
# function. #
# #
# For GUS mode, you should use item-item collaborative filtering with #
# cosine similarity, no mean-centering, and no normalization of #
# scores. #
########################################################################
# Populate this list with k movie indices to recommend to the user.
recommendations = []
########################################################################
# END OF YOUR CODE #
########################################################################
return recommendations
############################################################################
# 4. PART 2: LLM Prompting Mode #
############################################################################
def llm_system_prompt(self):
"""
Return the system prompt used to guide the LLM chatbot conversation.
NOTE: This is only for LLM Mode! In LLM Programming mode you will define
the system prompt for each individual call to the LLM.
"""
########################################################################
# TODO: Write a system prompt message for the LLM chatbot #
########################################################################
system_prompt = """Your name is moviebot. You are a movie recommender chatbot. """ +\
"""You can help users find movies they like and provide information about movies."""
########################################################################
# END OF YOUR CODE #
########################################################################
return system_prompt
############################################################################
# 5. PART 3: LLM Programming Mode (also need to modify functions above!) #
############################################################################
def extract_emotion(self, preprocessed_input):
"""LLM PROGRAMMING MODE: Extract an emotion from a line of pre-processed text.
Given an input text which has been pre-processed with preprocess(),
this method should return a list representing the emotion in the text.
We use the following emotions for simplicity:
Anger, Disgust, Fear, Happiness, Sadness and Surprise
based on early emotion research from Paul Ekman. Note that Ekman's
research was focused on facial expressions, but the simple emotion
categories are useful for our purposes.
Example Inputs:
Input: "Your recommendations are making me so frustrated!"
Output: ["Anger"]
Input: "Wow! That was not a recommendation I expected!"
Output: ["Surprise"]
Input: "Ugh that movie was so gruesome! Stop making stupid recommendations!"
Output: ["Disgust", "Anger"]
Example Usage:
emotion = chatbot.extract_emotion(chatbot.preprocess(
"Your recommendations are making me so frustrated!"))
print(emotion) # prints ["Anger"]
:param preprocessed_input: a user-supplied line of text that has been
pre-processed with preprocess()
:returns: a list of emotions in the text or an empty list if no emotions found.
Possible emotions are: "Anger", "Disgust", "Fear", "Happiness", "Sadness", "Surprise"
"""
return []
############################################################################
# 6. Debug info #
############################################################################
def debug(self, line):
"""
Return debug information as a string for the line string from the REPL
NOTE: Pass the debug information that you may think is important for
your evaluators.
"""
debug_info = 'debug info'
return debug_info
############################################################################
# 7. Write a description for your chatbot here! #
############################################################################
def intro(self):
"""Return a string to use as your chatbot's description for the user.
Consider adding to this description any information about what your
chatbot can do and how the user can interact with it.
NOTE: This string will not be shown to the LLM in llm mode, this is just for the user
"""
return """
Your task is to implement the chatbot as detailed in the PA7
instructions.
Remember: in the GUS mode, movie names will come in quotation marks
and expressions of sentiment will be simple!
TODO: Write here the description for your own chatbot!
"""
if __name__ == '__main__':
print('To run your chatbot in an interactive loop from the command line, '
'run:')
print(' python3 repl.py')