You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel.Qdrant/QdrantClient.cs

301 lines
14 KiB

using Newtonsoft.Json;
using System;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using ZeroLevel.Models;
using ZeroLevel.Qdrant.Models.Filters;
using ZeroLevel.Qdrant.Models.Requests;
using ZeroLevel.Qdrant.Models.Responces;
namespace ZeroLevel.Qdrant
{
/*
https://qdrant.github.io/qdrant/redoc/index.html#operation/search_points
https://qdrant.tech/documentation/search/
*/
/// <summary>
/// Client for Qdrant API
/// </summary>
public class QdrantClient
{
private HttpClient CreateClient()
{
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => { return true; }
};
handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
return new HttpClient(handler)
{
BaseAddress = _serverUri,
Timeout = TimeSpan.FromMinutes(5)
};
}
private readonly Uri _serverUri;
public QdrantClient(string host = "localhost", int port = 6333)
{
_serverUri = new Uri($"{host}:{port}");
}
#region API
#region Collection https://qdrant.tech/documentation/collections/
/// <summary>
/// Create collection
/// </summary>
/// <param name="name">Collection name</param>
/// <param name="distance">Cosine or Dot or Euclid</param>
/// <param name="vector_size">Count of elements in vectors</param>
/// <returns></returns>
public async Task<InvokeResult<OperationResponse>> CreateCollection(string name, string distance, int vector_size)
{
try
{
var collection = new CreateCollectionReqeust(name, distance, vector_size);
var json = JsonConvert.SerializeObject(collection);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"/collections";
var response = await _request<OperationResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<OperationResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.CreateCollection] Name: {name}. Distance: {distance}. Vector size: {vector_size}");
return InvokeResult.Fault<OperationResponse>($"[QdrantClient.CreateCollection] Name: {name}\r\n{ex.ToString()}");
}
}
/// <summary>
/// Delete collection by name
/// </summary>
/// <param name="name">Collection name</param>
public async Task<InvokeResult<OperationResponse>> DeleteCollection(string name)
{
try
{
var collection = new DeleteCollectionRequest(name);
var json = JsonConvert.SerializeObject(collection);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"/collections";
var response = await _request<OperationResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<OperationResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.DeleteCollection] Name: {name}.");
return InvokeResult.Fault<OperationResponse>($"[QdrantClient.DeleteCollection] Name: {name}\r\n{ex.ToString()}");
}
}
#endregion
#region Indexes https://qdrant.tech/documentation/indexing/
/// <summary>
/// For indexing, it is recommended to choose the field that limits the search result the most. As a rule, the more different values a payload value has, the more efficient the index will be used. You should not create an index for Boolean fields and fields with only a few possible values.
/// </summary>
public async Task<InvokeResult<CreateIndexResponse>> CreateIndex(string collection_name, string field_name)
{
try
{
var index = new CreateIndexRequest(field_name);
var json = JsonConvert.SerializeObject(index);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"/collections/{collection_name}";
var response = await _request<CreateIndexResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<CreateIndexResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.CreateIndex] Collection name: {collection_name}. Field name: {field_name}");
return InvokeResult.Fault<CreateIndexResponse>($"[QdrantClient.CreateIndex] Collection name: {collection_name}. Field name: {field_name}\r\n{ex.ToString()}");
}
}
#endregion
#region Search https://qdrant.tech/documentation/search/
/// <summary>
/// Searching for the nearest vectors
/// </summary>
public async Task<InvokeResult<SearchResponse>> Search(string collection_name, double[] vector, uint top, Filter filter = null)
{
try
{
var search = new SearchRequest { FloatVector = vector, Top = top, Filter = filter };
var json = search.ToJson();
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"/collections/{collection_name}/points/search";
var response = await _request<SearchResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<SearchResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.Search] Collection name: {collection_name}.");
return InvokeResult.Fault<SearchResponse>($"[QdrantClient.Search] Collection name: {collection_name}.\r\n{ex.ToString()}");
}
}
/// <summary>
/// Searching for the nearest vectors
/// </summary>
public async Task<InvokeResult<SearchResponse>> Search(string collection_name, long[] vector, uint top, Filter filter = null)
{
try
{
var search = new SearchRequest { IntegerVector = vector, Top = top, Filter = filter };
var json = search.ToJson();
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"/collections/{collection_name}/points/search";
var response = await _request<SearchResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<SearchResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.Search] Collection name: {collection_name}.");
return InvokeResult.Fault<SearchResponse>($"[QdrantClient.Search] Collection name: {collection_name}.\r\n{ex.ToString()}");
}
}
#endregion
#region Points https://qdrant.tech/documentation/points/
/// <summary>
/// There is a method for retrieving points by their ids.
/// </summary>
public async Task<InvokeResult<PointResponse>> Points(string collection_name, long[] ids)
{
try
{
var points = new PointsRequest { ids = ids };
var json = JsonConvert.SerializeObject(points);
var data = new StringContent(json, Encoding.UTF8, "application/json");
string url = $"/collections/{collection_name}/points";
var response = await _request<PointResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<PointResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.Points] Collection name: {collection_name}.");
return InvokeResult.Fault<PointResponse>($"[QdrantClient.Points] Collection name: {collection_name}.\r\n{ex.ToString()}");
}
}
/// <summary>
/// There is a method for retrieving points by their ids.
/// </summary>
public async Task<InvokeResult<ScrollResponse>> Scroll(string collection_name, Filter filter, long limit, long offset = 0, bool with_vector = true, bool with_payload = true)
{
try
{
var scroll = new ScrollRequest { Filter = filter, Limit = limit, Offset = offset, WithPayload = with_payload, WithVector = with_vector };
var json = scroll.ToJson();
var data = new StringContent(json, Encoding.UTF8, "application/json");
string url = url = $"/collections/{collection_name}/points/scroll";
var response = await _request<ScrollResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<ScrollResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.Scroll] Collection name: {collection_name}.");
return InvokeResult.Fault<ScrollResponse>($"[QdrantClient.Scroll] Collection name: {collection_name}.\r\n{ex.ToString()}");
}
}
/// <summary>
/// Record-oriented of creating batches
/// </summary>
public async Task<InvokeResult<PointsOperationResponse>> PointsUpload<T>(string collection_name, UpsertPoint<T>[] points)
{
try
{
var points_request = new PointsUploadRequest<T> { upsert_points = new UpsertPoints<T> { points = points } };
var json = points_request.ToJSON();
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"/collections/{collection_name}";
var response = await _request<PointsOperationResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<PointsOperationResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.Points] Collection name: {collection_name}.");
return InvokeResult.Fault<PointsOperationResponse>($"[QdrantClient.Points] Collection name: {collection_name}.\r\n{ex.ToString()}");
}
}
/// <summary>
/// Column-oriented of creating batches
/// </summary>
public async Task<InvokeResult<PointsOperationResponse>> PointsColumnUpload<T>(string collection_name, ColumnPoints<T> points)
{
try
{
var points_request = new PointsColumnUploadRequest<T> { upsert_points = new UpsertColumnPoints<T> { batch = points } };
var json = points_request.ToJSON();
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"/collections/{collection_name}";
var response = await _request<PointsOperationResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<PointsOperationResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.Points] Collection name: {collection_name}.");
return InvokeResult.Fault<PointsOperationResponse>($"[QdrantClient.Points] Collection name: {collection_name}.\r\n{ex.ToString()}");
}
}
/// <summary>
/// Delete points by their ids.
/// </summary>
public async Task<InvokeResult<PointsOperationResponse>> DeletePoints(string collection_name, long[] ids)
{
try
{
var points = new DeletePointsRequest { delete_points = new DeletePoints { ids = ids } };
var json = JsonConvert.SerializeObject(points);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var url = $"/collections/{collection_name}";
var response = await _request<PointsOperationResponse>(url, new HttpMethod("POST"), data);
return InvokeResult.Succeeding<PointsOperationResponse>(response);
}
catch (Exception ex)
{
Log.Error(ex, $"[QdrantClient.DeleteCollection] Name: {collection_name}.");
return InvokeResult.Fault<PointsOperationResponse>($"[QdrantClient.DeleteCollection] Name: {collection_name}\r\n{ex.ToString()}");
}
}
#endregion
#endregion
#region Private
private async Task<T> _request<T>(string url, HttpMethod method, HttpContent content = null)
{
var json = await _request(url, method, content);
return JsonConvert.DeserializeObject<T>(json);
}
private async Task<string> _request(string url, HttpMethod method, HttpContent content = null)
{
var fullUrl = new Uri(_serverUri, url);
var message = new HttpRequestMessage(method, fullUrl) { Content = content };
using (var client = CreateClient())
{
var response = await client.SendAsync(message);
var result = await response.Content.ReadAsStringAsync();
var jsonPrint = result?.Length >= 5000 ? "<BIG CONTENT>" : result;
if (response.IsSuccessStatusCode == false)
{
throw new Exception($"Not SuccessStatusCode {method} {fullUrl}. Status: {response.StatusCode} {response.ReasonPhrase}. Content: {jsonPrint}");
}
return result;
}
}
#endregion
}
}

Powered by TurnKey Linux.