> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zespan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Qdrant

> Auto-trace Qdrant query_points/search and upsert calls with zespan.autopatch() — retrieval spans and RAG context capture with no code changes to your Qdrant client calls.

<Note>
  Available for: **Python** and **TypeScript**.
</Note>

Calling Qdrant directly (no LangChain/LlamaIndex retriever abstraction in between) previously got zero automatic tracing — only the manual `recordRetrieval`/`record_retrieval` helper. Zespan now patches `QdrantClient` so every query and `upsert()` call is captured automatically, the same way LLM provider calls already are.

<Note>
  Patched automatically as part of `zespan.autopatch()`, which both SDKs run on `init()` unless you pass `autopatch: false` (`autopatch=False` in Python). There's no separate opt-in call for vector-DB tracing.
</Note>

## What gets traced

* **Reads** — Python: `query_points()` only, on both `QdrantClient` (sync) and `AsyncQdrantClient`. The Python client's `.search()` method was **removed entirely** in current `qdrant-client` versions, so there's nothing to patch there. Node: **both** `client.search()` and `client.query()` are patched — the JS client still ships both methods, so either style is traced
* **Writes** — `upsert()` (sync and async in Python; single method in Node) emits an `embedding` span

<Tabs>
  <Tab title="Python">
    ### Installation

    ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    pip install zespan qdrant-client
    ```

    ### Usage

    ```python theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    import zespan

    zespan.init(api_key="zsp_your_api_key_here")  # patches Qdrant automatically

    from qdrant_client import QdrantClient

    client = QdrantClient(url="http://localhost:6333")

    # Traced automatically -- emits an "embedding" span
    client.upsert(
        collection_name="docs",
        points=[{"id": "doc1", "vector": doc_embedding, "payload": {"text": "chunk text", "source": "handbook.pdf"}}],
    )

    # Traced automatically -- emits a "retriever" span
    results = client.query_points(collection_name="docs", query=query_embedding, limit=5)
    ```

    <Note>
      `AsyncQdrantClient` is patched the same way — `await client.query_points(...)` and `await client.upsert(...)` are traced identically to the sync calls above.
    </Note>
  </Tab>

  <Tab title="TypeScript">
    ### Installation

    ```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    npm install @zespan/sdk @qdrant/js-client-rest
    ```

    ### Usage

    ```typescript theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    import { zespan } from "@zespan/sdk";

    zespan.init({ apiKey: process.env.ZESPAN_API_KEY! }); // patches Qdrant automatically

    import { QdrantClient } from "@qdrant/js-client-rest";

    const client = new QdrantClient({ url: "http://localhost:6333" });

    // Traced automatically -- emits an "embedding" span
    await client.upsert("docs", {
      points: [{ id: "doc1", vector: docEmbedding, payload: { text: "chunk text", source: "handbook.pdf" } }],
    });

    // Traced automatically -- emits a "retriever" span
    const results = await client.query("docs", { query: queryEmbedding, limit: 5 });

    // Also traced -- client.search() is patched too, for apps still on the older style
    const legacyResults = await client.search("docs", { vector: queryEmbedding, limit: 5 });
    ```
  </Tab>
</Tabs>

## What gets captured

### Read span (`query_points()` / `query()` / `search()`) — `span_kind: "retriever"`, `operation: "vector_search"`

| Field                   | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider`              | `"qdrant"`                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `latency_ms`            | Time spent in the real call                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `status`                | `"success"` or `"error"` (with `error_message`, truncated to 500 characters)                                                                                                                                                                                                                                                                                                                                                                                    |
| `rag_context_count`     | Number of points returned                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `metadata.rag_contexts` | One entry per point: `{ content, document_id, score, source }` — `content` comes from the point's payload (its text field), `document_id` from the point id, `score` from the point's similarity score. Same shape [`recordRetrieval`/`record_retrieval`](/sdk/manual-spans#recording-retrieved-documents) produces, so the trace-detail **Retrieval** panel and the [RAG evaluators](/dashboard/evaluations#evaluating-rag-pipelines) work with no extra setup |
| `metadata.top_k`        | Requested result count, read from the call's `limit` option — only present when you actually pass one, since `limit` is optional in Qdrant's client. Not gated by `storePrompts` when present, since it's a count, not query content. Only observable nested under `metadata` — the ingest pipeline persists the `metadata` object verbatim but drops other unrecognized top-level fields                                                                       |
| `metadata.filter`       | The query's `filter`/`query_filter` option, redacted and stringified — only present when `storePrompts`/`store_prompts` is `true` (its default). Filter values commonly carry user IDs, emails, or other PII, so unlike `top_k` this is dropped entirely, not just redacted, when `storePrompts` is off. (Same `metadata`-only visibility as `top_k` above.)                                                                                                    |

`content` follows your `storePrompts`/`store_prompts` setting and the same redaction rules as prompt text — set `storePrompts: false` and payload text is dropped while `document_id`/`source`/`score` are still kept.

### Write span (`upsert()`) — `span_kind: "embedding"`, `operation: "vector_upsert"`

| Field                   | Details                                                                                                                                                                                      |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `provider`              | `"qdrant"`                                                                                                                                                                                   |
| `metadata.vector_count` | Number of points in the `upsert()` call. Only observable nested under `metadata` — the ingest pipeline persists the `metadata` object verbatim but drops other unrecognized top-level fields |
| `latency_ms`            | Time spent in the real `upsert()` call                                                                                                                                                       |
| `input_tokens`          | Always `0` — the wrapper doesn't generate embeddings, it only records that vectors already computed elsewhere were upserted                                                                  |
| `status`                | `"success"` or `"error"` (with `error_message`)                                                                                                                                              |

<Warning>
  **Linking a Qdrant query to a following LLM call.** Trace context (`trace_id`) is picked up from whatever `with_zespan_context()`/`withZespanContext()` scope is active when the query/`upsert()` runs — the same rule every other Zespan wrapper follows. If your Qdrant call and your LLM call are two independent, unwrapped top-level calls, they land on **two different traces**, and RAG evaluators/the Retrieval panel won't see the connection between them. Wrap both in the same `with_zespan_context()`/`withZespanContext()` block, exactly like the [complete RAG pipeline example](/sdk/manual-spans#complete-rag-pipeline-example) in Manual spans.
</Warning>

## Next steps

* [Manual spans](/sdk/manual-spans) — the `record_vector_search()`/`recordVectorSearch()` helper for pgvector, and the full `with_zespan_context()`/`withZespanContext()` trace-linking pattern
* [Evaluating RAG pipelines](/dashboard/evaluations#evaluating-rag-pipelines) — score retrieval quality once `rag_contexts` is on the trace
* [Pinecone](/sdk/integrations/pinecone), [Chroma](/sdk/integrations/chroma), [Weaviate](/sdk/integrations/weaviate) — the other auto-traced vector DBs
