File size: 4,634 Bytes
41604f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
889b393
 
 
 
 
 
 
 
 
 
41604f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Rebuild the Qdrant ``interview_questions`` collection from the local dataset.

This recreates, byte-for-byte, the vector database the original app used:
  * collection name : interview_questions
  * vector size     : 384  (all-MiniLM-L6-v2)
  * distance        : COSINE
  * payload         : {"job_role": <lower>, "question": ..., "answer": ...}

The original cluster was deleted after inactivity, but every question is
preserved in ``data/shuffled_questions.json`` (4233 Q&A pairs, 26 roles),
which is exactly what populated Qdrant in the first place.

Usage:
    export QDRANT_API_URL="https://<your-cluster>.qdrant.io:6333"
    export QDRANT_API_KEY="<your-key>"
    python scripts/rebuild_qdrant.py

It is safe to re-run: the collection is recreated from scratch each time.
"""

import json
import logging
import os
import sys

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

COLLECTION_NAME = "interview_questions"
VECTOR_SIZE = 384
DATA_FILE = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
    "data",
    "shuffled_questions.json",
)


def main() -> int:
    url = os.getenv("QDRANT_API_URL")
    key = os.getenv("QDRANT_API_KEY")
    if not url or not key:
        logging.error(
            "Set QDRANT_API_URL and QDRANT_API_KEY environment variables first."
        )
        return 1

    # Qdrant cloud URLs need the :6333 REST port; add it if the user pasted
    # the bare hostname from the dashboard.
    if url.endswith("/"):
        url = url[:-1]
    if ".qdrant.io" in url and not url.rsplit(":", 1)[-1].isdigit():
        url = url + ":6333"

    from qdrant_client import QdrantClient
    from qdrant_client.http.models import Distance, PointStruct, VectorParams
    from sentence_transformers import SentenceTransformer

    logging.info("Loading dataset from %s", DATA_FILE)
    with open(DATA_FILE, "r", encoding="utf-8") as f:
        rows = json.load(f)
    logging.info("Loaded %d Q&A rows", len(rows))

    logging.info("Loading embedding model all-MiniLM-L6-v2 (first run downloads it)")
    model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")

    client = QdrantClient(url=url, api_key=key, check_compatibility=False, timeout=120)

    logging.info("Recreating collection '%s' (size=%d, COSINE)", COLLECTION_NAME, VECTOR_SIZE)
    client.recreate_collection(
        collection_name=COLLECTION_NAME,
        vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE),
    )

    # The app filters questions by job_role, which requires a keyword payload
    # index — without it Qdrant returns HTTP 400 and the interview silently
    # falls back to generic default questions.
    client.create_payload_index(
        collection_name=COLLECTION_NAME,
        field_name="job_role",
        field_schema="keyword",
    )
    logging.info("Created keyword payload index on 'job_role'")

    # Build the points. We embed the QUESTION text, exactly like the original
    # notebook, and store role/question/answer in the payload.
    questions, payloads = [], []
    for item in rows:
        try:
            role = item["Job Role"].lower().strip()
            question = item["Questions"].strip()
            answer = item["Answers"].strip()
        except (KeyError, AttributeError):
            continue
        if not question:
            continue
        questions.append(question)
        payloads.append({"job_role": role, "question": question, "answer": answer})

    logging.info("Embedding %d questions...", len(questions))
    vectors = model.encode(questions, batch_size=128, show_progress_bar=True)

    batch_size = 64
    total = len(questions)
    for start in range(0, total, batch_size):
        end = min(start + batch_size, total)
        points = [
            PointStruct(id=i, vector=vectors[i].tolist(), payload=payloads[i])
            for i in range(start, end)
        ]
        for attempt in range(1, 4):
            try:
                client.upsert(collection_name=COLLECTION_NAME, points=points, wait=True)
                break
            except Exception as exc:
                logging.warning("Batch %d-%d attempt %d failed: %s", start, end, attempt, exc)
                if attempt == 3:
                    raise
        logging.info("Uploaded %d/%d", end, total)

    info = client.get_collection(COLLECTION_NAME)
    logging.info(
        "Done. Collection '%s' now has %s points (distance=%s).",
        COLLECTION_NAME,
        info.points_count,
        info.config.params.vectors.distance,
    )
    return 0


if __name__ == "__main__":
    sys.exit(main())