| title | Anime RAG API |
|---|---|
| emoji | 🌸 |
| colorFrom | purple |
| colorTo | pink |
| sdk | docker |
| app_port | 7860 |
| pinned | false |
Raganim is an advanced semantic search and recommendation platform for anime. Rather than performing simple keyword matching, Raganim leverages a modern Retrieval-Augmented Generation (RAG) pipeline. Users can input natural language queries representing moods, settings, characters, or specific plot themes (e.g., "a lone samurai seeking redemption" or "dark futuristic cyberpunk space battles"), and the engine will semantically find the best matches, re-rank them using a Large Language Model, and generate explanations for why each result was recommended.
The backend is built with FastAPI (Python), backed by ChromaDB for vector storage, OpenAI for embedding generation, and Google Gemini for query expansion and re-ranking. The frontend is a clean, glassmorphic modern web interface built with vanilla HTML, CSS, and JavaScript.
When a search request is received by the backend, the RAG engine performs a multi-stage pipeline inside src/core.py:
flowchart TD
RawQuery[1. Raw User Query] --> Rewrite[2. Query Rewriting & Expansion<br>LLM: Gemini]
Rewrite -->|Extracts excluded titles| ExcludeList[Excluded Titles List]
Rewrite -->|Outputs structured query| Embed[3. Vector Embedding<br>OpenAI: text-embedding-3-small]
Embed --> QueryVec[Query Vector]
QueryVec --> Search[4. Vector Database Query<br>ChromaDB Search]
ExcludeList --> Search
Search --> Context[5. Context Builder<br>Format text for prompt]
Context --> Rerank[6. Re-ranking & Reasoning<br>LLM: Gemini]
Rerank --> Output[7. Structured JSON Response]
Rather than converting raw natural language queries (like "find anime action with magic portals similar to Solo Leveling") directly into embeddings, the engine leverages Query Rewriting to match the database's schema:
-
The Problem: Anime records are saved in ChromaDB under a structured schema containing genres, tags, mood, settings, and themes (e.g.
genres: Action \n tags: Magic, Gate \n synopsis: ...). A plain natural language query does not match this structure, which can degrade embedding cosine similarity performance. -
The Solution:
- The raw query is processed by the LLM (
gemma-4-31b-it) using theresponse_mime_type="application/json"config. - The LLM extracts specific attributes:
genres,tags,setting,mood,themes,plot_elements,similar_to, andsynopsis_keywords. - If the query asks for recommendations similar to Anime X, Anime X is parsed and placed in the
excluded_titlesarray so that the input anime itself is filtered out from recommendations. - The code concatenates these attributes using header tags mirroring the database format (e.g.
genres: <value>\ntags: <value>\nsynopsis: <value>). - The resulting structured query string is embedded and searched.
- The raw query is processed by the LLM (
-
Example Output of Query Rewriter: For user query: "dark and gritty cyberpunk with mecha"
genres: Action, Sci-Fi, Drama tags: Cyberpunk, Mecha, Post-Apocalyptic setting: dystopian futuristic city, dark alleys mood: dark and gritty, epic, melancholic themes: survival, technology, existential dread plot_elements: robot pilots, cybernetic enhancements synopsis: futuristic metropolis, giant mechs, dystopian world
The rewritten query is converted into a high-dimensional vector:
- Model: OpenAI's
text-embedding-3-small - Dimensions: 1536
- Role: Captures the deep semantic meaning of the structured query string.
The system queries the local ChromaDB SQLite-backed database (chroma_db):
- Filtering: If
excluded_titleswere parsed (e.g. the user searched "similar to Naruto"), any anime metadata containing "Naruto" will be automatically skipped during retrieval. - Scoring: The database retrieves the top matches based on cosine distance. The system computes a relevance score (
1 - distance) and sorts candidate matches combining the vector similarity with the MyAnimeList popularityscore.
The Context Builder is a key preprocessing step that formats retrieved vector database results into a single clean string injected into the final LLM prompt.
- Is it a prompt? No, the Context Builder itself is not a prompt. Rather, it is a helper method (
_build_contextinsrc/core.py) that gathers raw database data and builds the content context that gets injected into the final prompt template. - How it works:
- It loops through the raw list of dictionaries returned by ChromaDB vector search (
_vector_search). - For each result, it extracts the metadata (
title, MyAnimeListscore, MyAnimeListurl, vectorrelevancedistance) and the raw document body (genres: ... \n tags: ... \n synopsis: ...). - It structures this data into a standardized numbered text format:
[1] Anime Title (MAL score: 8.5 | relevance: 0.95) genres: Action, Fantasy tags: Overpowered Protagonist, Magic synopsis: A story about... URL: https://myanimelist.net/... - It returns a combined multi-line string. This combined context is then formatted directly into the main prompt in
_ask_llm()at the{context}placeholder.
- It loops through the raw list of dictionaries returned by ChromaDB vector search (
- Prompting & System Guidelines: The structured context generated by the Context Builder is combined with the system prompt (
SYSTEM_PROMPT) instructing the model to act as an expert anime recommender. - Reasoning (
why): The LLM compares all retrieved anime directly in-context, sorts them according to how well they align with the user's intent (which can differ from pure vector similarity), and generates a concisewhyexplanation for why that specific anime was chosen. - Output enforcement: The engine enforces strict JSON mode, requesting recommendations of exactly
Nitems (matching the top-k retrieved list) to prevent hallucinations or missing links.
app.py: The entry point for the FastAPI web server. Manages lifespan initialization, CORS settings, Pydantic validation schemas, and REST endpoints.src/core.py: The core RAG pipeline engine implementation (RagEngineclass, LLM prompt templates, embedding and ChromaDB calls).src/config.py: Configuration file loading keys and model configurations from environment variables.frontend/: Client UI assets.frontend/index.html: UI layout, search input, chips, and loading states.frontend/style.css: Premium styled layout (glassmorphism cards, gradients, and custom animations).frontend/script.js: Handles API client connections to the backend, status UI updates, and results rendering.
The application loads settings from the environment. Main configurations include:
EMBED_MODEL: Default istext-embedding-3-small(1536 dimensions).LLM_MODEL: Default isgemma-4-31b-it. Used for final re-ranking and explanation generation.REWRITE_MODEL: Default isgemma-4-31b-it. Used for query restructuring.CHROMA_PATH: Database path, defaults to./chroma_db.COLLECTION: Chroma collection, defaults toanime_collection.TOP_K: Default retrieval count is10.
- Endpoint:
GET /health - Response:
{ "status": "ok", "docs": 25282, "model": "gemma-4-31b-it" }
- Endpoint:
POST /search - Request Payload (
QueryRequest):{ "query": "dark mecha like Evangelion", "top_k": 10 } - Response Payload (
QueryResponse):{ "query": "dark mecha like Evangelion", "rewritten_query": "genres: Sci-Fi, Drama, Mecha\ntags: Psychological, Post-Apocalyptic\nsynopsis: psychological distress, giant robot pilots, existential danger", "excluded_titles": ["evangelion"], "message": "Here are the top dark psychological mecha recommendations tailored for your search.", "recommendations": [ { "rank": 1, "title": "Bokurano", "url": "https://myanimelist.net/anime/2508/Bokurano", "mal_score": 7.7, "why": "Features a dark plot where children pilot a giant mech at the cost of their own lives, heavily matching the existential dread of Evangelion." } ], "all_retrieved": [ { "title": "Bokurano", "url": "https://myanimelist.net/anime/2508/Bokurano", "relevance": 0.8841 } ] }
-
Create a Virtual Environment & Install Dependencies:
python -m venv .venv source .venv/bin/activate # On Windows use: .venv\Scripts\activate pip install -r requirements.txt
-
Configure Environment Variables: Create a
.envfile in the root folder with the following variables:OPENAI_API_KEY="your-openai-api-key" GEMINI_API_KEY="your-gemini-api-key"
-
Start the FastAPI Server:
uvicorn app:app --reload --port 8000
Once started, the API docs are accessible at: http://localhost:8000/docs.
-
Verify Application Health: Send a GET request to
/healthto verify that the RAG engine and database connection are initialized correctly.
To run the frontend client interface locally:
- Open the
frontend/index.htmlfile directly in your browser, or serve it using any simple local static server (e.g., Live Server extension in VS Code or Python's HTTP server):cd frontend python -m http.server 8080 - The frontend expects the backend server to be running on
http://localhost:8000. You can configure the API URL insidefrontend/script.js.