File size: 3,126 Bytes
1e8bb26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0151e19
 
 
 
1e8bb26
0151e19
1e8bb26
 
 
0151e19
1e8bb26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Qdrant vector store: collection management, upsert, and search."""
from __future__ import annotations

import uuid
from functools import lru_cache
from typing import List, Optional

from qdrant_client import QdrantClient
from qdrant_client.http import models as qm

from .config import get_settings


@lru_cache
def get_client() -> QdrantClient:
    settings = get_settings()
    return QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key, timeout=60)


def ensure_collection() -> None:
    """Create the collection if it does not exist."""
    settings = get_settings()
    client = get_client()
    existing = {c.name for c in client.get_collections().collections}
    if settings.qdrant_collection not in existing:
        client.create_collection(
            collection_name=settings.qdrant_collection,
            vectors_config=qm.VectorParams(
                size=settings.embedding_dim, distance=qm.Distance.COSINE
            ),
        )
        # Index on document_id so we can filter / delete by document.
        client.create_payload_index(
            collection_name=settings.qdrant_collection,
            field_name="document_id",
            field_schema=qm.PayloadSchemaType.KEYWORD,
        )


def upsert_chunks(
    document_id: str,
    filename: str,
    chunks: List[str],
    vectors: List[List[float]],
) -> None:
    settings = get_settings()
    client = get_client()
    points = [
        qm.PointStruct(
            id=str(uuid.uuid4()),
            vector=vector,
            payload={
                "document_id": document_id,
                "filename": filename,
                "chunk_index": idx,
                "text": chunk,
            },
        )
        for idx, (chunk, vector) in enumerate(zip(chunks, vectors))
    ]
    client.upsert(collection_name=settings.qdrant_collection, points=points)


def search(
    query_vector: List[float],
    top_k: int,
    document_ids: Optional[List[str]] = None,
):
    settings = get_settings()
    client = get_client()

    query_filter = None
    if document_ids:
        query_filter = qm.Filter(
            must=[qm.FieldCondition(key="document_id", match=qm.MatchAny(any=document_ids))]
        )

    # query_points is the current API (works on qdrant-client >=1.10; the old
    # .search() was removed in 1.18). Returns a QueryResponse; .points is the
    # list of ScoredPoint (each has .payload and .score), matching prior usage.
    return client.query_points(
        collection_name=settings.qdrant_collection,
        query=query_vector,
        limit=top_k,
        query_filter=query_filter,
        with_payload=True,
    ).points


def delete_document(document_id: str) -> None:
    settings = get_settings()
    client = get_client()
    client.delete(
        collection_name=settings.qdrant_collection,
        points_selector=qm.FilterSelector(
            filter=qm.Filter(
                must=[
                    qm.FieldCondition(
                        key="document_id", match=qm.MatchValue(value=document_id)
                    )
                ]
            )
        ),
    )