Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OPENROUTER_API_KEY=your_key_here
54 changes: 54 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: CI

on:
push:
branches: ["**"]
pull_request:
branches: ["**"]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: |
pip install --upgrade pip
pip install ruff

- name: Lint with ruff
run: ruff check src/ scripts/

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt

- name: Smoke test — import modules
run: |
python -c "from src.hallucination import check_retrieval_quality; print('hallucination OK')"
python -c "from src.llm_utils import llm_call_with_retry; print('llm_utils OK')"

docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Build Docker image
run: docker build -t cinematch:test .
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
data/raw/
.env
__pycache__/
chroma_db/
*.pyc
*.egg-info/
.venv/
venv/
data/logs.db
.DS_Store
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy source code
COPY src/ ./src/
COPY scripts/ ./scripts/
COPY config.yaml prompts.yaml .env.example ./

# Copy data if exists
COPY data/ ./data/

# Expose Streamlit port
EXPOSE 8501

HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost:8501/_stcore/health || exit 1

CMD ["streamlit", "run", "src/app.py", "--server.port=8501", "--server.address=0.0.0.0"]
932 changes: 932 additions & 0 deletions README.md

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
embedding_model: "all-MiniLM-L6-v2"
reranker_model: "cross-encoder/ms-marco-MiniLM-L-6-v2"
llm_model: "nvidia/nemotron-3-super-120b-a12b:free"
fallback_models:
- "deepseek/deepseek-chat-v3-0324:free"
- "google/gemma-3-27b-it:free"
- "meta-llama/llama-4-maverick:free"
openrouter_base_url: "https://openrouter.ai/api/v1"

chroma_db_path: "chroma_db"
chroma_collection: "movies"
chroma_distance: "cosine"

retrieval:
n_results: 20
top_k: 5
min_results_with_filter: 5
similarity_threshold: 0.4

query_analyzer:
max_retries: 2
backoff_base_seconds: 2

generation:
max_retries: 2
backoff_base_seconds: 2
history_turns: 2

data:
raw_path: "data/raw"
processed_path: "data/processed"
movies_file: "data/processed/movies.jsonl"

logging:
db_path: "data/logs.db"
30 changes: 30 additions & 0 deletions data/test_queries.jsonl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{"query": "Хочу что-то как Интерстеллар", "expected_genres": ["Science Fiction", "Drama"], "expected_type": "recommendation"}
{"query": "Страшный фильм до 100 минут", "expected_genres": ["Horror"], "expected_type": "recommendation"}
{"query": "Романтическая комедия для вечера", "expected_genres": ["Romance", "Comedy"], "expected_type": "recommendation"}
{"query": "Эпический фэнтези фильм", "expected_genres": ["Fantasy", "Adventure"], "expected_type": "recommendation"}
{"query": "Триллер с неожиданной концовкой", "expected_genres": ["Thriller"], "expected_type": "recommendation"}
{"query": "Анимационный фильм для всей семьи", "expected_genres": ["Animation", "Family"], "expected_type": "recommendation"}
{"query": "Военный фильм про вторую мировую", "expected_genres": ["War", "Drama"], "expected_type": "recommendation"}
{"query": "Документальный фильм о природе", "expected_genres": ["Documentary"], "expected_type": "recommendation"}
{"query": "Криминальная драма как Крёстный отец", "expected_genres": ["Crime", "Drama"], "expected_type": "recommendation"}
{"query": "Фильм про супергероев", "expected_genres": ["Action", "Science Fiction"], "expected_type": "recommendation"}
{"query": "Лёгкая комедия чтобы посмеяться", "expected_genres": ["Comedy"], "expected_type": "recommendation"}
{"query": "Фильм о путешествии во времени", "expected_genres": ["Science Fiction"], "expected_type": "recommendation"}
{"query": "Мюзикл с хорошими песнями", "expected_genres": ["Music"], "expected_type": "recommendation"}
{"query": "Психологический триллер", "expected_genres": ["Thriller", "Drama"], "expected_type": "recommendation"}
{"query": "Фильм-катастрофа", "expected_genres": ["Action", "Thriller"], "expected_type": "recommendation"}
{"query": "Детективный фильм с загадкой", "expected_genres": ["Mystery", "Crime"], "expected_type": "recommendation"}
{"query": "Спортивная драма", "expected_genres": ["Drama"], "expected_type": "recommendation"}
{"query": "Фильм про космос и инопланетян", "expected_genres": ["Science Fiction"], "expected_type": "recommendation"}
{"query": "Вестерн", "expected_genres": ["Western"], "expected_type": "recommendation"}
{"query": "Фильм нуар", "expected_genres": ["Crime", "Thriller"], "expected_type": "recommendation"}
{"query": "Биографический фильм о музыканте", "expected_genres": ["Drama", "Music"], "expected_type": "recommendation"}
{"query": "Хороший фильм с рейтингом выше 8", "expected_genres": [], "expected_type": "recommendation"}
{"query": "Новый фильм после 2015 года", "expected_genres": [], "expected_type": "recommendation"}
{"query": "Короткий фильм до 90 минут", "expected_genres": [], "expected_type": "recommendation"}
{"query": "Какая сегодня погода?", "expected_genres": [], "expected_type": "off_topic"}
{"query": "Сколько будет 2+2?", "expected_genres": [], "expected_type": "off_topic"}
{"query": "Расскажи анекдот", "expected_genres": [], "expected_type": "off_topic"}
{"query": "Посоветуй что-то весёлое и доброе", "expected_genres": ["Comedy", "Family"], "expected_type": "recommendation"}
{"query": "Мрачный фильм с глубоким смыслом", "expected_genres": ["Drama"], "expected_type": "recommendation"}
{"query": "Приключенческий фильм для подростков", "expected_genres": ["Adventure"], "expected_type": "recommendation"}
Loading
Loading