Project Metadata
Overview of the project context, scope, and tech choices.
Overview
- Role
- AI Engineer & Developer
- Client
- Academic Project
- Type
- AI Agent / RAG Application
- Industry
- Artificial Intelligence / Recommendation Systems
- Platform
- Web
- Duration
- -
Description
A conversational movie recommendation assistant that combines a LangChain agent, semantic retrieval with HuggingFace embeddings, and Qdrant to ground recommendations in an IMDb Top 1000 movie dataset.
Key Features
- • Conversational Movie Recommendations
- • Retrieval-Augmented Generation with Qdrant
- • Semantic Search Across 1,000 Movies
- • Multi-Attribute Movie Search
- • Persistent Chat History and Session Management
- • Token Usage, Cost Tracking, and Embedding Caching
Tech Stack
Access
Built with Python and Streamlit, the Movie Recommendation Agent is a conversational movie discovery application that grounds its responses in a fixed catalog of 1,000 IMDb movies instead of relying solely on an LLM’s general knowledge.
The application brings together an LLM agent, semantic search, vector storage, and a lightweight chat interface. This creates a simple way to turn a structured movie dataset into a conversational search experience.
Use Case
Traditional movie search works well when someone already knows a title, actor, or other exact attribute. Conversational search becomes more useful when the request is expressed as a preference:
“Recommend some highly rated sci-fi movies from the 1990s.”
“What movies in the catalog feature Tom Hanks?”
“I want a drama with a rating above 8.5.”
This application is designed around that interaction. Users describe what they are looking for, while the agent retrieves relevant movies before generating a response.
Retrieval with Qdrant
Movie data comes from imdb_top_1000.csv. Each row is converted into a LangChain Document containing the main attributes of the movie, including title, year, genre, rating, director, cast, runtime, certificate, and overview.
content = f"""Movie Title: {title}Release Year: {year}Genre: {genre}IMDB Rating: {rating}Director: {director}Stars: {stars_str}Runtime: {runtime}Certificate: {certificate}Overview: {overview}""".strip()Those documents are embedded using sentence-transformers/all-MiniLM-L6-v2 and stored in a Qdrant collection named imdb_movies.
When a user submits a query, the retrieval layer performs a similarity search and returns the top ten matching documents:
results = vector_store.similarity_search(query, k=10)Including structured attributes in the embedded content helps retrieval handle queries about names, years, genres, ratings, and other movie metadata, rather than relying only on the description.
Agent and Tool Calling
Movie retrieval is exposed to the LLM through a search_movies tool. This gives the agent control over when to search while keeping the vector database behind a small application-level interface.
LangChain initializes the agent with the configured model, retrieval tool, and system prompt:
self.agent = create_agent( model=self.llm, tools=self.tools, system_prompt=self.system_prompt)A user request is passed to the agent as a regular chat message:
result = self.agent.invoke({ "messages": [HumanMessage(content=question)]})
answer = result["messages"][-1].contentThe system prompt instructs the agent to use search_movies when answering movie-related questions and ground its responses in the retrieved results. This keeps the catalog as the source of movie information rather than treating the model itself as the database.
Application Structure
Responsibility is split across several small modules:
agent.pyhandles agent creation and execution.tools.pyexposes movie retrieval to the agent.services.pyinitializes the LLM, embeddings, and vector store.prompts.pycontains the language-specific system prompts.config.pycentralizes model and environment configuration.main.pyconnects the application flow with the Streamlit UI.
Separating these concerns keeps the interface, agent logic, retrieval layer, and infrastructure from becoming one large module.
Streamlit Experience
A Streamlit-based chat interface provides the main interaction and maintains session history. The interface also exposes token usage, estimated request cost, model configuration, and vector database connection status.
Expensive resources such as embeddings and the vector store are cached so they are not recreated on every Streamlit rerun:
@st.cache_resourcedef initialize(): return HuggingFaceEmbeddings( model_name=Config.EMBEDDING_MODEL, model_kwargs={"device": "cpu"}, encode_kwargs={"normalize_embeddings": True} )English and Indonesian system prompts are supported without changing the underlying retrieval or model configuration.
Usage Tracking
Each request is processed to capture input and output token usage from the model response. From there, the application estimates the request cost in both USD and IDR.
usd_price = ( total_input_tokens * self.input_price + total_output_tokens * self.output_price) / 1_000_000
idr_price = usd_price * self.usd_to_idrTracking these values makes model consumption visible at the application level instead of treating usage as an opaque part of the system.
What I Learned
The most interesting part of this project was connecting the individual components into one working flow. Preparing the dataset for retrieval, generating embeddings, storing them in a vector database, exposing retrieval through a tool, and grounding the model’s responses all have to work together.
The same pattern can extend beyond movie recommendations. A bounded dataset such as a product catalog, documentation library, course collection, or internal knowledge base can use a similar approach to provide a conversational interface over structured information.
Next Steps
One useful next step would be combining semantic retrieval with structured filtering. A query could use vector similarity to understand the user’s preference while applying exact filters for attributes such as year, genre, or rating.
Further iterations could also explore retrieval evaluation, persistent user preferences, reranking, richer tracing, and streaming responses.
