Skip to content

Commit ceed0ba

Browse files
committed
Add movie agent
1 parent b2358c5 commit ceed0ba

File tree

3 files changed

+291
-6
lines changed

3 files changed

+291
-6
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,3 +177,4 @@ chatbot/tt
177177
lab/t
178178
chatbot/uploads/?*.*
179179
chatbot/t2
180+
agents/m

agents/movie.py

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
#!/usr/bin/python3
2+
"""
3+
Movie Bot - Recommend movies based on user preferences
4+
5+
The job of movie bot is to read the history of previous movies recommend and then suggestion a new
6+
movie based on the user's preferences. The bot will also store the new movie recommendation in the
7+
database for future reference.
8+
9+
* The bot will identify the genre of the movie it recommends from a set of predefined genres from
10+
the database.
11+
* The bot will use the LLM to generate the movie recommendation.
12+
* The bot will store the new movie recommendation in the database.
13+
* The bot will return the movie recommendation to the user along with the genre.
14+
# The bot will attempt to recommend a movie that is not already in the database and
15+
will try to vary the genre of the movie recommendation.
16+
17+
Author: Jason A. Cox
18+
2 Feb 2025
19+
https://github.com/jasonacox/TinyLLM
20+
21+
"""
22+
23+
import openai
24+
import sqlite3
25+
import os
26+
import time
27+
import datetime
28+
import random
29+
30+
# Version
31+
VERSION = "v0.0.1"
32+
33+
# Configuration Settings
34+
api_key = os.environ.get("OPENAI_API_KEY", "open_api_key") # Required, use bogus string for Llama.cpp
35+
api_base = os.environ.get("OPENAI_API_BASE", "http://localhost:8000/v1") # Required, use https://api.openai.com for OpenAI
36+
mymodel = os.environ.get("LLM_MODEL", "models/7B/gguf-model.bin") # Pick model to use e.g. gpt-3.5-turbo for OpenAI
37+
DATABASE = os.environ.get("DATABASE", "moviebot.db") # Database file to store movie data
38+
DEBUG = os.environ.get("DEBUG", "false").lower() == "true" # Set to True to enable debug mode
39+
TEMPERATURE = float(os.environ.get("TEMPERATURE", 0.7)) # LLM temperature
40+
MESSAGE_FILE = os.environ.get("MESSAGE_FILE", "message.txt") # File to store the message
41+
ABOUT_ME = os.environ.get("ABOUT_ME",
42+
"We love movies! Action, adventure, sci-fi and feel good movies are our favorites.") # About me text
43+
44+
#DEBUG = True
45+
46+
# Debugging functions
47+
def log(text):
48+
# Print to console
49+
if DEBUG:
50+
print(f"INFO: {text}")
51+
52+
def error(text):
53+
# Print to console
54+
print(f"ERROR: {text}")
55+
56+
class MovieDatabase:
57+
def __init__(self, db_name=DATABASE):
58+
self.db_name = db_name
59+
self.conn = None
60+
61+
def connect(self):
62+
self.conn = sqlite3.connect(self.db_name)
63+
return self.conn
64+
65+
def create(self):
66+
self.connect()
67+
c = self.conn.cursor()
68+
# Create table to store movies
69+
c.execute('''CREATE TABLE IF NOT EXISTS movies (title text, genre text, date_recommended text)''')
70+
self.conn.commit()
71+
# Create table to store genres (make them unique)
72+
c.execute('''CREATE TABLE IF NOT EXISTS genres (genre text UNIQUE)''')
73+
self.conn.commit()
74+
self.conn.close()
75+
76+
def destroy(self):
77+
self.connect()
78+
c = self.conn.cursor()
79+
# Drop tables
80+
c.execute('''DROP TABLE IF EXISTS movies''')
81+
self.conn.commit()
82+
c.execute('''DROP TABLE IF EXISTS genres''')
83+
self.conn.commit()
84+
self.conn.close()
85+
86+
def insert_movie(self, title, genre, date_recommended=None):
87+
if date_recommended is None:
88+
date_recommended = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
89+
self.connect()
90+
c = self.conn.cursor()
91+
c.execute("INSERT INTO movies VALUES (?, ?, ?)", (title, genre, date_recommended))
92+
self.conn.commit()
93+
self.conn.close()
94+
95+
def select_all_movies(self, sort_by='date_recommended'):
96+
self.connect()
97+
c = self.conn.cursor()
98+
c.execute(f"SELECT * FROM movies ORDER BY {sort_by}")
99+
rows = c.fetchall()
100+
self.conn.close()
101+
# return list of movie titles
102+
m = [row[0] for row in rows]
103+
return m
104+
105+
def select_movies_by_genre(self, genre):
106+
self.connect()
107+
c = self.conn.cursor()
108+
c.execute(f"SELECT * FROM movies WHERE genre = ?", (genre,))
109+
rows = c.fetchall()
110+
self.conn.close()
111+
# return list of movie titles
112+
m = [row[0] for row in rows]
113+
return m
114+
115+
def delete_all_movies(self):
116+
self.connect()
117+
c = self.conn.cursor()
118+
c.execute("DELETE FROM movies")
119+
self.conn.commit()
120+
self.conn.close()
121+
122+
def delete_movie_by_title(self, title):
123+
self.connect()
124+
c = self.conn.cursor()
125+
c.execute("DELETE FROM movies WHERE title = ?", (title,))
126+
self.conn.commit()
127+
self.conn.close()
128+
129+
def insert_genre(self, genre):
130+
self.connect()
131+
c = self.conn.cursor()
132+
c.execute("INSERT OR IGNORE INTO genres VALUES (?)", (genre,))
133+
self.conn.commit()
134+
self.conn.close()
135+
136+
def select_genres(self):
137+
self.connect()
138+
c = self.conn.cursor()
139+
c.execute("SELECT * FROM genres")
140+
rows = c.fetchall()
141+
self.conn.close()
142+
# convert payload into a list of genres
143+
g = [row[0] for row in rows]
144+
return g
145+
146+
# Initialize the database
147+
db = MovieDatabase()
148+
#db.destroy() # Clear the database
149+
db.create() # Set up the database if it doesn't exist
150+
151+
# Load genres
152+
genres = db.select_genres()
153+
if len(genres) == 0:
154+
for genre in ['action', 'fantasy', 'comedy', 'drama', 'horror', 'romance',
155+
'sci-fi', 'christmas', 'musical', 'thriller', 'mystery',
156+
'animated', 'western', 'documentary', 'adventure', 'family']:
157+
db.insert_genre(genre)
158+
genres = db.select_genres()
159+
160+
# Load movies
161+
movies = db.select_all_movies()
162+
if len(movies) == 0:
163+
# Load some sample movies
164+
db.insert_movie("Home Alone", "christmas")
165+
db.insert_movie("Sound of Music", "musical")
166+
db.insert_movie("Die Hard", "action")
167+
db.insert_movie("Sleepless in Seattle", "romance")
168+
db.insert_movie("The Game", "thriller")
169+
db.insert_movie("Toy Story", "animated")
170+
db.insert_movie("Sherlock Holmes", "mystery")
171+
db.insert_movie("Airplane", "comedy")
172+
db.insert_movie("Raiders of the Lost Ark", "action")
173+
db.insert_movie("A Bug's Life", "drama")
174+
175+
# Test LLM
176+
while True:
177+
log("Testing LLM...")
178+
try:
179+
log(f"Using openai library version {openai.__version__}")
180+
log(f"Connecting to OpenAI API at {api_base} using model {mymodel}")
181+
llm = openai.OpenAI(api_key=api_key, base_url=api_base)
182+
# Get models
183+
models = llm.models.list()
184+
# build list of models
185+
model_list = [model.id for model in models.data]
186+
log(f"LLM: Models available: {model_list}")
187+
if len(models.data) == 0:
188+
log("LLM: No models available - check your API key and endpoint.")
189+
raise Exception("No models available")
190+
if not mymodel in model_list:
191+
log(f"LLM: Model {mymodel} not found in models list.")
192+
if len(model_list) == 1:
193+
log("LLM: Switching to default model")
194+
mymodel = model_list[0]
195+
else:
196+
log(f"LLM: Unable to find model {mymodel} in models list.")
197+
raise Exception(f"Model {mymodel} not found")
198+
log(f"LLM: Using model {mymodel}")
199+
# Test LLM
200+
llm.chat.completions.create(
201+
model=mymodel,
202+
stream=False,
203+
temperature=TEMPERATURE,
204+
messages=[{"role": "user", "content": "Hello"}],
205+
)
206+
break
207+
except Exception as e:
208+
log("OpenAI API Error: %s" % e)
209+
log(f"Unable to connect to OpenAI API at {api_base} using model {mymodel}.")
210+
log("Sleeping 10 seconds...")
211+
time.sleep(10)
212+
213+
# LLM Function to ask the chatbot a question
214+
def ask_chatbot(prompt):
215+
log(f"LLM: Asking chatbot: {prompt}")
216+
response = llm.chat.completions.create(
217+
model=mymodel,
218+
stream=False,
219+
temperature=TEMPERATURE,
220+
messages=[{"role": "user", "content": prompt}],
221+
)
222+
log(f"LLM: Response: {response.choices[0].message.content.strip()}\n")
223+
return response.choices[0].message.content.strip()
224+
225+
# Function to recommend a movie
226+
def recommend_movie():
227+
# Get list of movies but limit it to last 12
228+
movies = db.select_all_movies(sort_by='date_recommended')[-12:]
229+
# Get list of genres
230+
genres = db.select_genres()
231+
# Randomly mix up the elements in the list
232+
random.shuffle(genres)
233+
234+
# Get today's date in format: Month Day, Year
235+
today = datetime.datetime.now().strftime("%B %d, %Y")
236+
237+
prompt = f"""You are a expert movie bot that recommends movies based on user preferences.
238+
Today is {today}, in case it is helpful in suggesting a movie.
239+
Provide variety and interest.
240+
Do no repeat previous recommendations.
241+
You have recommended the following movies in the past:
242+
{movies}
243+
You have the following genres available:
244+
{genres}
245+
{ABOUT_ME}
246+
Please recommend a new movie to watch. Give interesting details about the movie.
247+
"""
248+
249+
# Ask the chatbot for a movie recommendation
250+
recommendation = ask_chatbot(prompt).strip()
251+
252+
# Save the movie recommendation to file
253+
with open(MESSAGE_FILE, "w") as f:
254+
f.write(recommendation)
255+
256+
# Ask the chatbot to provide just one move and the title only
257+
prompt = f"""About me: {ABOUT_ME}
258+
259+
Recommendation:
260+
{recommendation}
261+
262+
Based on the above recommendation, select the best movie for me. List the movie title only:
263+
"""
264+
movie_recommendation = ask_chatbot(prompt).strip()
265+
266+
# Ask the chatbot what type of genre the movie is, provide list of genres
267+
prompt = f"""You are a expert movie bot that recommends movies based on user preferences.
268+
You have the following genres available:
269+
{genres}
270+
271+
Based on that list, what genre is the movie "{movie_recommendation}"? List the genre only:
272+
"""
273+
genre = ask_chatbot(prompt).strip().lower()
274+
275+
# Store the movie recommendation
276+
db.insert_movie(movie_recommendation, genre)
277+
return movie_recommendation, genre
278+
279+
# Main
280+
if __name__ == "__main__":
281+
log(f"Movie Bot {VERSION} - Recommends movies based on user preferences and history.")
282+
movie, genre = recommend_movie()
283+
output = f"Movie Bot recommends the {genre} movie: {movie}"
284+
print(output)

chatbot/README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,25 +162,25 @@ The examples below use a Llama 2 7B model served up with the OpenAI API compatib
162162

163163
Open http://127.0.0.1:5000 - Example session:
164164

165-
<img width="946" alt="image" src="https://github.com/jasonacox/TinyLLM/assets/836718/08097e39-9c00-4f75-8c9a-d329c886b148">
165+
<img width="800" alt="image" src="https://github.com/jasonacox/TinyLLM/assets/836718/08097e39-9c00-4f75-8c9a-d329c886b148">
166166

167167
#### Read URL
168168

169169
If a URL is pasted in the text box, the chatbot will read and summarize it.
170170

171-
<img width="810" alt="image" src="https://github.com/jasonacox/TinyLLM/assets/836718/44d8a2f7-54c1-4b1c-8471-fdf13439be3b">
171+
<img width="800" alt="image" src="https://github.com/jasonacox/TinyLLM/assets/836718/44d8a2f7-54c1-4b1c-8471-fdf13439be3b">
172172

173173
#### Current News
174174

175175
The `/news` command will fetch the latest news and have the LLM summarize the top ten headlines. It will store the raw feed in the context prompt to allow follow-up questions.
176176

177-
<img width="930" alt="image" src="https://github.com/jasonacox/TinyLLM/assets/836718/2732fe07-99ee-4795-a8ac-42d9a9712f6b">
177+
<img width="800" alt="image" src="https://github.com/jasonacox/TinyLLM/assets/836718/2732fe07-99ee-4795-a8ac-42d9a9712f6b">
178178

179179
#### Model Selection
180180

181181
The `/model` command will popup the list of available models. Use the dropdown to select your model. Alternatively, specify the model with the command (e.g. `/model mixtral`) to select it immediately without the popup.
182182

183-
<img width="1168" alt="image" src="https://github.com/user-attachments/assets/3b5740cb-e768-4174-aaf6-118f4080f68e" />
183+
<img width="800" alt="image" src="https://github.com/user-attachments/assets/e21ad350-6ae0-47de-b7ee-135176d66fe7" />
184184

185185
## Document Manager (Weaviate)
186186

@@ -229,9 +229,9 @@ Note - You can restrict collections by providing the environmental variable `COL
229229

230230
You can now create collections (libraries of content) and upload files and URLs to be stored into the vector database for the Chatbot to reference.
231231

232-
<img width="1035" alt="image" src="https://github.com/user-attachments/assets/544c75d4-a1a3-4c32-a95f-7f12ff11a450">
232+
<img width="800" alt="image" src="https://github.com/user-attachments/assets/544c75d4-a1a3-4c32-a95f-7f12ff11a450">
233233

234-
<img width="1035" alt="image" src="https://github.com/user-attachments/assets/4b15ef87-8f25-4d29-9214-801a326b406f">
234+
<img width="800" alt="image" src="https://github.com/user-attachments/assets/4b15ef87-8f25-4d29-9214-801a326b406f">
235235

236236
The Chatbot can use this information if you send the prompt command:
237237

0 commit comments

Comments
 (0)