diff --git a/packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/async_qdrant_client_methods.json b/packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/async_qdrant_client_methods.json index ec161a5ee6..28a4b3b20a 100644 --- a/packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/async_qdrant_client_methods.json +++ b/packages/opentelemetry-instrumentation-qdrant/opentelemetry/instrumentation/qdrant/async_qdrant_client_methods.json @@ -44,11 +44,21 @@ "method": "query", "span_name": "qdrant.query" }, + { + "object": "AsyncQdrantClient", + "method": "query_points", + "span_name": "qdrant.query_points" + }, { "object": "AsyncQdrantClient", "method": "query_batch", "span_name": "qdrant.query_batch" }, + { + "object": "AsyncQdrantClient", + "method": "query_batch_points", + "span_name": "qdrant.query_batch_points" + }, { "object": "AsyncQdrantClient", "method": "discover", diff --git a/packages/opentelemetry-instrumentation-qdrant/tests/test_qdrant_instrumentation.py b/packages/opentelemetry-instrumentation-qdrant/tests/test_qdrant_instrumentation.py index 6203b19a08..debf028204 100644 --- a/packages/opentelemetry-instrumentation-qdrant/tests/test_qdrant_instrumentation.py +++ b/packages/opentelemetry-instrumentation-qdrant/tests/test_qdrant_instrumentation.py @@ -1,5 +1,7 @@ +import asyncio + import pytest -from qdrant_client import QdrantClient, models +from qdrant_client import AsyncQdrantClient, QdrantClient, models from opentelemetry.semconv_ai import SpanAttributes @@ -122,3 +124,81 @@ def test_qdrant_search(exporter, qdrant): == COLLECTION_NAME ) assert span.attributes.get(SpanAttributes.QDRANT_SEARCH_BATCH_REQUESTS_COUNT) == 4 + + +# --- AsyncQdrantClient coverage for query_points / query_batch_points --- +# +# The sync client's query_points / query_batch_points are already wrapped, but +# AsyncQdrantClient was missing the equivalents in async_qdrant_client_methods.json, +# so async users got no spans for those calls. These tests pin the parity. + +async def _async_setup(client: AsyncQdrantClient) -> None: + # `AsyncQdrantClient.upload_collection` runs through a ThreadPoolExecutor + # internally and returns None instead of an awaitable, so set up the + # fixture data with `upsert` which is genuinely async. + await client.create_collection( + COLLECTION_NAME, + vectors_config=models.VectorParams( + size=EMBEDDING_DIM, distance=models.Distance.COSINE + ), + ) + await client.upsert( + COLLECTION_NAME, + points=[ + models.PointStruct(id=1, vector=[0.1] * EMBEDDING_DIM, payload={"name": "Paul"}), + models.PointStruct(id=2, vector=[0.2] * EMBEDDING_DIM, payload={"name": "John"}), + models.PointStruct(id=3, vector=[0.3] * EMBEDDING_DIM, payload={}), + ], + ) + + +def test_qdrant_async_query_points(exporter): + exporter.clear() + + async def run() -> None: + client = AsyncQdrantClient(location=":memory:") + await _async_setup(client) + await client.query_points( + collection_name=COLLECTION_NAME, + query=[0.1] * EMBEDDING_DIM, + limit=1, + ) + + asyncio.run(run()) + + spans = exporter.get_finished_spans() + span = next(span for span in spans if span.name == "qdrant.query_points") + + assert ( + span.attributes.get(SpanAttributes.QDRANT_SEARCH_COLLECTION_NAME) + == COLLECTION_NAME + ) + assert span.attributes.get(SpanAttributes.VECTOR_DB_QUERY_TOP_K) == 1 + + +def test_qdrant_async_query_batch_points(exporter): + exporter.clear() + + async def run() -> None: + client = AsyncQdrantClient(location=":memory:") + await _async_setup(client) + await client.query_batch_points( + collection_name=COLLECTION_NAME, + requests=[ + models.QueryRequest(query=[0.1] * EMBEDDING_DIM, limit=10), + models.QueryRequest(query=[0.2] * EMBEDDING_DIM, limit=5), + models.QueryRequest(query=[0.42] * EMBEDDING_DIM, limit=2), + models.QueryRequest(query=[0.21] * EMBEDDING_DIM, limit=1), + ], + ) + + asyncio.run(run()) + + spans = exporter.get_finished_spans() + span = next(span for span in spans if span.name == "qdrant.query_batch_points") + + assert ( + span.attributes.get(SpanAttributes.QDRANT_SEARCH_BATCH_COLLECTION_NAME) + == COLLECTION_NAME + ) + assert span.attributes.get(SpanAttributes.QDRANT_SEARCH_BATCH_REQUESTS_COUNT) == 4