Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.11.0 #64

Merged
merged 4 commits into from
Aug 12, 2024
Merged
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
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,5 @@ Git tags. To create a new release
git push --tags
```

2. Create a new release on GitHub from the tag
3. Upload the nuget package artifact from the GitHub action workflow run triggered by creating the release.
2. Create a new release on GitHub from the tag.
3. The CI uploads a NuGet package artifact after the release.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageProjectUrl>https://github.com/qdrant/qdrant-dotnet</PackageProjectUrl>
<PackageReleaseNotes>https://github.com/qdrant/qdrant-dotnet/releases</PackageReleaseNotes>
<PackageTags>qdrant, database, vector, search</PackageTags>
<QdrantVersion>v1.10.0</QdrantVersion>
<QdrantVersion>v1.11.0</QdrantVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
# .NET SDK for Qdrant vector database
# Qdrant .NET SDK

[![NuGet Release][Qdrant-image]][Qdrant-nuget-url]
[![Build](https://github.com/qdrant/qdrant-dotnet/actions/workflows/test.yml/badge.svg)](https://github.com/qdrant/qdrant-dotnet/actions/workflows/test.yml)

.NET SDK for [Qdrant vector database](https://qdrant.tech/).

## Getting started

### Installing
## 📥 Installation

```sh
dotnet add package Qdrant.Client
```

## 📖 Documentation

Usage examples are available throughout the [Qdrant documentation](https://qdrant.tech/documentation/quick-start/).

## 🔌 Getting started

### Creating a client

A client can be instantiated with
Expand Down
7 changes: 7 additions & 0 deletions src/Qdrant.Client/Grpc/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ public partial class Query
/// <returns>a new instance of <see cref="Query"/></returns>
public static implicit operator Query(OrderBy orderBy) => new() { OrderBy = orderBy };

/// <summary>
/// Implicitly converts a <see cref="Sample"/> to a new instance of <see cref="Query"/>
/// </summary>
/// <param name="sample">An instance of <see cref="Sample"/> </param>
/// <returns>a new instance of <see cref="Query"/></returns>
public static implicit operator Query(Sample sample) => new() { Sample = sample };

/// <summary>
/// Explicitly creates a <see cref="Query"/> to order points by a payload field.
/// </summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Qdrant.Client/LoggingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ internal static partial class LoggingExtensions
[LoggerMessage(3027, LogLevel.Debug, "Query batch on '{collection}'")]
public static partial void QueryBatch(this ILogger logger, string collection);

[LoggerMessage(3028, LogLevel.Debug, "Query groups on '{collection}'")]
public static partial void QueryGroups(this ILogger logger, string collection);

#endregion Point management

#region Snapshot management
Expand Down
102 changes: 102 additions & 0 deletions src/Qdrant.Client/QdrantClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3929,6 +3929,108 @@ public async Task<IReadOnlyList<BatchResult>> QueryBatchAsync(
}
}

/// <summary>
/// Universally query points.
/// Covers all capabilities of search, recommend, discover, filters.
/// Grouped by a payload field.
///
/// </summary>
/// <param name="collectionName">The name of the collection.</param>
/// <param name="groupBy">Payload field to group by, must be a string or number field.</param>
/// <param name="query">Query to perform. If missing, returns points ordered by their IDs.</param>
/// <param name="prefetch">Sub-requests to perform first. If present, the query will be performed on the results of the prefetches.</param>
/// <param name="usingVector">Name of the vector to use for querying. If missing, the default vector is used..</param>
/// <param name="filter">Filter conditions - return only those points that satisfy the specified conditions.</param>
/// <param name="scoreThreshold">Return points with scores better than this threshold.</param>
/// <param name="searchParams">Search config.</param>
/// <param name="limit">Max number of results.</param>
/// <param name="groupSize">Maximum amount of points to return per group.</param>
/// <param name="payloadSelector">Options for specifying which payload to include or not.</param>
/// <param name="vectorsSelector">Options for specifying which vectors to include into the response.</param>
/// <param name="readConsistency">Options for specifying read consistency guarantees.</param>
/// <param name="shardKeySelector">Specify in which shards to look for the points, if not specified - look in all shards.</param>
/// <param name="lookupFrom">The location to use for IDs lookup, if not specified - use the current collection and the 'usingVector' vector</param>
/// <param name="timeout">If set, overrides global timeout setting for this request.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.</param>
public async Task<IReadOnlyList<PointGroup>> QueryGroupsAsync(
string collectionName,
string groupBy,
Query? query = null,
IReadOnlyList<PrefetchQuery>? prefetch = null,
string? usingVector = null,
Filter? filter = null,
float? scoreThreshold = null,
SearchParams? searchParams = null,
ulong limit = 10,
ulong groupSize = 1,
WithPayloadSelector? payloadSelector = null,
WithVectorsSelector? vectorsSelector = null,
ReadConsistency? readConsistency = null,
ShardKeySelector? shardKeySelector = null,
LookupLocation? lookupFrom = null,
TimeSpan? timeout = null,
CancellationToken cancellationToken = default)
{
var request = new QueryPointGroups
{
CollectionName = collectionName,
GroupBy = groupBy,
Limit = limit,
GroupSize = groupSize,
WithPayload = payloadSelector ?? new WithPayloadSelector { Enable = true },
WithVectors = vectorsSelector ?? new WithVectorsSelector { Enable = false }
};

if (query is not null)
request.Query = query;

if (prefetch is not null)
request.Prefetch.AddRange(prefetch);

if (usingVector is not null)
request.Using = usingVector;

if (filter is not null)
request.Filter = filter;

if (scoreThreshold is not null)
request.ScoreThreshold = scoreThreshold.Value;

if (searchParams is not null)
request.Params = searchParams;

if (readConsistency is not null)
request.ReadConsistency = readConsistency;

if (shardKeySelector is not null)
request.ShardKeySelector = shardKeySelector;

if (lookupFrom is not null)
request.LookupFrom = lookupFrom;

if (timeout is not null)
request.Timeout = ConvertTimeout(timeout);

_logger.QueryGroups(collectionName);

try
{
var response = await _pointsClient.QueryGroupsAsync(
request,
deadline: _grpcTimeout == default ? null : DateTime.UtcNow.Add(_grpcTimeout),
cancellationToken: cancellationToken)
.ConfigureAwait(false);

return response.Result.Groups;
}
catch (Exception e)
{
_logger.OperationFailed(nameof(LoggingExtensions.QueryGroups), e);

throw;
}
}

/// <summary>
/// Get list of snapshots for a collection.
/// </summary>
Expand Down
44 changes: 44 additions & 0 deletions tests/Qdrant.Client.Tests/PointTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -512,5 +512,49 @@ public async Task QueryWithPrefetchAndFusion()
Assert.Equal(2, points.Count);
}

[Fact]
public async Task QueryWithSampling()
{
await CreateAndSeedCollection("collection_1");

var points = await _client.QueryAsync(
"collection_1",
query: Sample.Random,
limit: 2
);

Assert.Equal(2, points.Count);
}

[Fact]
public async Task QueryGroups()
{
await CreateAndSeedCollection("collection_1");

await _client.UpsertAsync("collection_1", new[]
{
new PointStruct
{
Id = 10,
Vectors = new[] { 30f, 31f },
Payload = { ["foo"] = "hello" }
}
});

// 3 points in total, 2 with "foo" = "hello" and 1 with "foo" = "goodbye"

var groups = await _client.QueryGroupsAsync(
"collection_1",
"foo",
new[] { 10.4f, 11.4f },
groupSize: 2);

Assert.Equal(2, groups.Count);
// A group 2 hits because of 2 points with "foo" = "hello"
Assert.Single(groups, g => g.Hits.Count == 2);
// A group with 1 hit because of 1 point with "foo" = "goodbye"
Assert.Single(groups, g => g.Hits.Count == 1);
}

public Task DisposeAsync() => Task.CompletedTask;
}