diff --git a/DashScope.net.sln b/DashScope.net.sln
index 760dd0f..97f305d 100644
--- a/DashScope.net.sln
+++ b/DashScope.net.sln
@@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
+ .github\workflows\nuget-build.yml = .github\workflows\nuget-build.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DashScope.UnitTests", "tests\DashScope.UnitTests\DashScope.UnitTests.csproj", "{837CD31F-A2EA-4379-B325-3A20285F7577}"
@@ -38,6 +39,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DashVector.UnitTests", "tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DashVector.Sample", "samples\DashVector.Sample\DashVector.Sample.csproj", "{0B09CBA0-491C-49B5-A498-940AEB7210DF}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DashVector.SemanticKernel", "src\DashVector.SemanticKernel\DashVector.SemanticKernel.csproj", "{648ABFAD-C62C-4D58-B968-91F6BEF0831D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -84,6 +87,10 @@ Global
{0B09CBA0-491C-49B5-A498-940AEB7210DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B09CBA0-491C-49B5-A498-940AEB7210DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B09CBA0-491C-49B5-A498-940AEB7210DF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {648ABFAD-C62C-4D58-B968-91F6BEF0831D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {648ABFAD-C62C-4D58-B968-91F6BEF0831D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {648ABFAD-C62C-4D58-B968-91F6BEF0831D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {648ABFAD-C62C-4D58-B968-91F6BEF0831D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -99,6 +106,7 @@ Global
{580E981D-49BB-4C34-B4BA-2A1C85B6016D} = {BB7B834A-464F-4F81-A649-E63AA0C53C24}
{FFF43DC0-8591-4817-ABEF-C11598BE6D24} = {ED489DAA-351A-43F9-A3DF-7DBEBF7677CD}
{0B09CBA0-491C-49B5-A498-940AEB7210DF} = {2FDFB5A9-FE06-4822-933E-5D1C6D9160C9}
+ {648ABFAD-C62C-4D58-B968-91F6BEF0831D} = {BB7B834A-464F-4F81-A649-E63AA0C53C24}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {4DB8E739-7470-4879-9DCE-E9BACBA58715}
diff --git a/Directory.Packages.props b/Directory.Packages.props
index b66aa68..d4c9863 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -10,6 +10,7 @@
+
diff --git a/samples/SK-DashScope.Sample/Controllers/ApiController.cs b/samples/SK-DashScope.Sample/Controllers/ApiController.cs
index 38763e2..e31b709 100644
--- a/samples/SK-DashScope.Sample/Controllers/ApiController.cs
+++ b/samples/SK-DashScope.Sample/Controllers/ApiController.cs
@@ -6,6 +6,7 @@
using System.Text;
using DashScope.SemanticKernel;
using Microsoft.SemanticKernel.TextGeneration;
+using Microsoft.SemanticKernel.Memory;
namespace SK_DashScope.Sample.Controllers
{
@@ -14,10 +15,13 @@ namespace SK_DashScope.Sample.Controllers
public class ApiController : ControllerBase
{
private readonly Kernel kernel;
+ private readonly ISemanticTextMemory memory;
+ const string CollectionName = "DashScopeMemoryStore";
- public ApiController(Kernel kernel)
+ public ApiController(Kernel kernel, ISemanticTextMemory memory)
{
this.kernel = kernel;
+ this.memory = memory;
}
[HttpPost]
@@ -151,5 +155,49 @@ public async Task SemanticAsync([FromBody] UserInput input, Cance
return Ok(new { value, usage });
}
+
+ [HttpPost("save_memory")]
+ public async Task SaveMemory([FromBody] UserInput input, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(input.Text))
+ {
+ return string.Empty;
+ }
+ var id = Guid.NewGuid().ToString("N");
+
+ return await memory.SaveInformationAsync(CollectionName, input.Text, id, cancellationToken: cancellationToken);
+ }
+
+ [HttpPost("get_memory")]
+ public async Task GetMemory([FromBody] UserInput input, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(input.Text))
+ {
+ return null;
+ }
+
+ var id = input.Text;
+ return await memory.GetAsync(CollectionName, id, true, cancellationToken: cancellationToken);
+ }
+
+ [HttpPost("query_memory")]
+ public async Task> QueryMemory([FromBody] UserInput input, CancellationToken cancellationToken)
+ {
+ if (string.IsNullOrWhiteSpace(input.Text))
+ {
+ return Array.Empty();
+ }
+
+ var query = input.Text;
+ var records = memory.SearchAsync(CollectionName, query, 10, 0, true, cancellationToken: cancellationToken);
+
+ var results = new List();
+ await foreach (var record in records)
+ {
+ results.Add(record);
+ }
+
+ return results;
+ }
}
}
\ No newline at end of file
diff --git a/samples/SK-DashScope.Sample/Program.cs b/samples/SK-DashScope.Sample/Program.cs
index 977c987..e85002d 100644
--- a/samples/SK-DashScope.Sample/Program.cs
+++ b/samples/SK-DashScope.Sample/Program.cs
@@ -1,3 +1,5 @@
+using DashVector;
+using DashVector.SemanticKernel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Memory;
@@ -23,6 +25,15 @@
{
return new MemoryBuilder()
.WithDashScopeTextEmbeddingGenerationService(builder.Configuration["DashScope:ApiKey"]!)
+ .WithMemoryStore(factory =>
+ {
+ var dashVectorClient = new DashVectorClient(builder.Configuration["DashVector:ApiKey"]!,
+ builder.Configuration["DashVector:Endpoint"]!);
+ return new DashVectorMemoryStore(dashVectorClient, new DashVectorCollectionOptions()
+ {
+ Dimension = 1536
+ });
+ })
.Build();
});
diff --git a/samples/SK-DashScope.Sample/SK-DashScope.Sample.csproj b/samples/SK-DashScope.Sample/SK-DashScope.Sample.csproj
index b1840c8..d91932e 100644
--- a/samples/SK-DashScope.Sample/SK-DashScope.Sample.csproj
+++ b/samples/SK-DashScope.Sample/SK-DashScope.Sample.csproj
@@ -17,6 +17,7 @@
+
diff --git a/src/DashVector.SemanticKernel/DashVector.SemanticKernel.csproj b/src/DashVector.SemanticKernel/DashVector.SemanticKernel.csproj
new file mode 100644
index 0000000..0ed682c
--- /dev/null
+++ b/src/DashVector.SemanticKernel/DashVector.SemanticKernel.csproj
@@ -0,0 +1,16 @@
+
+
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DashVector.SemanticKernel/DashVectorMemroyStore.cs b/src/DashVector.SemanticKernel/DashVectorMemroyStore.cs
new file mode 100644
index 0000000..2bcc05b
--- /dev/null
+++ b/src/DashVector.SemanticKernel/DashVectorMemroyStore.cs
@@ -0,0 +1,272 @@
+using DashVector.Enums;
+using DashVector.Models;
+using Microsoft.SemanticKernel.Memory;
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Text.Json.Serialization;
+
+namespace DashVector.SemanticKernel
+{
+ ///
+ /// An IMemoryStore implementation that uses DashVector as the underlying storage.
+ ///
+ ///
+ public class DashVectorMemoryStore : IMemoryStore
+ {
+ static readonly Dictionary FieldsSchema = new()
+ {
+ [nameof(MemoryRecordMetadata.IsReference)] = FieldType.BOOL,
+ [nameof(MemoryRecordMetadata.ExternalSourceName)] = FieldType.STRING,
+ [nameof(MemoryRecordMetadata.Id)] = FieldType.STRING,
+ [nameof(MemoryRecordMetadata.Description)] = FieldType.STRING,
+ [nameof(MemoryRecordMetadata.Text)] = FieldType.STRING,
+ [nameof(MemoryRecordMetadata.AdditionalMetadata)] = FieldType.STRING,
+ };
+
+ static MemoryRecordMetadata ToMetaData(Dictionary fields)
+ {
+ return new MemoryRecordMetadata(
+ fields[nameof(MemoryRecordMetadata.IsReference)].GetValue(),
+ fields[nameof(MemoryRecordMetadata.Id)].GetValue(),
+ fields[nameof(MemoryRecordMetadata.Text)].GetValue(),
+ fields[nameof(MemoryRecordMetadata.Description)].GetValue(),
+ fields[nameof(MemoryRecordMetadata.ExternalSourceName)].GetValue(),
+ fields[nameof(MemoryRecordMetadata.AdditionalMetadata)].GetValue()
+ );
+ }
+ static Dictionary ToFieldValues(MemoryRecordMetadata metadata)
+ {
+ return new Dictionary()
+ {
+ [nameof(MemoryRecordMetadata.IsReference)] = metadata.IsReference,
+ [nameof(MemoryRecordMetadata.ExternalSourceName)] = metadata.ExternalSourceName,
+ [nameof(MemoryRecordMetadata.Id)] = metadata.Id,
+ [nameof(MemoryRecordMetadata.Description)] = metadata.Description,
+ [nameof(MemoryRecordMetadata.Text)] = metadata.Text,
+ [nameof(MemoryRecordMetadata.AdditionalMetadata)] = metadata.AdditionalMetadata,
+ };
+ }
+
+
+
+ private readonly DashVectorClient client;
+ private readonly DashVectorCollectionOptions options;
+
+ public DashVectorMemoryStore(DashVectorClient client, DashVectorCollectionOptions options)
+ {
+ this.client = client;
+ this.options = options;
+ }
+
+ ///
+ public async Task CreateCollectionAsync(string collectionName, CancellationToken cancellationToken = default)
+ {
+ await client.CreateCollectionAsync(new Models.Requests.CreateCollectionRequest()
+ {
+ Name = collectionName,
+ DataType = options.DataType,
+ Dimension = options.Dimension,
+ Metric = options.Metric,
+ ExtraParams = options.ExtraParams,
+ FieldsSchema = FieldsSchema,
+ }, cancellationToken);
+ }
+
+ ///
+ public async Task DeleteCollectionAsync(string collectionName, CancellationToken cancellationToken = default)
+ {
+ await client.DeleteCollectionAsync(collectionName, cancellationToken);
+ }
+
+ ///
+ public async Task DoesCollectionExistAsync(string collectionName, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ var result = await client.DescribeCollectionAsync(collectionName, cancellationToken);
+
+ return result.OutPut?.Status == CollectionStatus.SERVING;
+ }
+ catch { }
+ return false;
+ }
+
+ ///
+ public async Task GetAsync(string collectionName, string key, bool withEmbedding = false, CancellationToken cancellationToken = default)
+ {
+ var results = await client.FetchDocAsync(new Models.Requests.FetchDocRequest()
+ {
+ Ids = [key]
+ }, collectionName, cancellationToken);
+
+ if (results.Code == 0 && results.OutPut?.Count == 1)
+ {
+ var doc = results.OutPut.First().Value;
+ var metaData = ToMetaData(doc.Fields!);
+
+ return MemoryRecord.FromMetadata(metaData, doc.Vector, doc.Id);
+ }
+ return null;
+ }
+
+ ///
+ public async IAsyncEnumerable GetBatchAsync(string collectionName, IEnumerable keys, bool withEmbeddings = false, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var results = await client.FetchDocAsync(new Models.Requests.FetchDocRequest()
+ {
+ Ids = keys.ToList()
+ }, collectionName, cancellationToken);
+
+ if (results.OutPut != null)
+ {
+ foreach (var (id, doc) in results.OutPut)
+ {
+ var metaData = ToMetaData(doc.Fields!);
+
+ yield return MemoryRecord.FromMetadata(metaData, doc.Vector, doc.Id);
+ }
+ }
+ }
+
+ ///
+ public async IAsyncEnumerable GetCollectionsAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var collections = await client.GetCollectionListAsync(cancellationToken);
+
+ if (collections.OutPut != null)
+ {
+ foreach (var collection in collections.OutPut)
+ {
+ yield return collection;
+ }
+ }
+ }
+
+ ///
+ public async Task<(MemoryRecord, double)?> GetNearestMatchAsync(string collectionName, ReadOnlyMemory embedding, double minRelevanceScore = 0, bool withEmbedding = false, CancellationToken cancellationToken = default)
+ {
+ var result = await client.QueryDocAsync(new Models.Requests.QueryDocRequest()
+ {
+ IncludeVector = withEmbedding,
+ Vector = embedding.ToArray(),
+ TopK = 1,
+ }, collectionName, cancellationToken);
+
+ if (result.OutPut?.Count != 1)
+ {
+ return null;
+ }
+
+ var doc = result.OutPut[0];
+ if (doc.Score < minRelevanceScore)
+ {
+ return null;
+ }
+
+ var metadata = ToMetaData(doc.Fields!);
+ return (MemoryRecord.FromMetadata(metadata, doc.Vector, doc.Id),
+ doc.Score);
+
+ }
+
+ ///
+ public async IAsyncEnumerable<(MemoryRecord, double)> GetNearestMatchesAsync(string collectionName, ReadOnlyMemory embedding, int limit, double minRelevanceScore = 0, bool withEmbeddings = false,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var results = await client.QueryDocAsync(new Models.Requests.QueryDocRequest()
+ {
+ IncludeVector = withEmbeddings,
+ Vector = embedding.ToArray(),
+ TopK = limit,
+ }, collectionName, cancellationToken);
+
+ if (results.OutPut?.Count > 0)
+ {
+ foreach (var doc in results.OutPut)
+ {
+ if (doc.Score < minRelevanceScore)
+ {
+ break;
+ }
+
+ var metadata = ToMetaData(doc.Fields!);
+ yield return (MemoryRecord.FromMetadata(metadata, doc.Vector, doc.Id),
+ doc.Score);
+ }
+ }
+ }
+
+ ///
+ public async Task RemoveAsync(string collectionName, string key, CancellationToken cancellationToken = default)
+ {
+ await client.DeleteDocAsync(new Models.Requests.DeleteDocRequest()
+ {
+ Ids = [key]
+ }, collectionName, cancellationToken);
+ }
+
+ ///
+ public async Task RemoveBatchAsync(string collectionName, IEnumerable keys, CancellationToken cancellationToken = default)
+ {
+ await client.DeleteDocAsync(new Models.Requests.DeleteDocRequest()
+ {
+ Ids = keys.ToList()
+ }, collectionName, cancellationToken);
+ }
+
+ ///
+ public async Task UpsertAsync(string collectionName, MemoryRecord record, CancellationToken cancellationToken = default)
+ {
+ var id = string.IsNullOrEmpty(record.Key) ? Guid.NewGuid().ToString("N") : record.Key;
+ await client.UpsertDocAsync(new Models.Requests.UpsertDocRequest()
+ {
+ Docs = [
+ new Doc()
+ {
+ Id = id,
+ Fields = ToFieldValues(record.Metadata),
+ Vector = record.Embedding.ToArray()
+ }
+ ]
+ }, collectionName, cancellationToken);
+ return id;
+ }
+
+ ///
+ public async IAsyncEnumerable UpsertBatchAsync(string collectionName, IEnumerable records, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var docs = new List();
+
+ foreach (var record in records)
+ {
+ var id = string.IsNullOrEmpty(record.Key) ? Guid.NewGuid().ToString("N") : record.Key;
+
+ docs.Add(new Doc()
+ {
+ Id = id,
+ Fields = ToFieldValues(record.Metadata),
+ Vector = record.Embedding.ToArray()
+ });
+ }
+
+ await client.UpsertDocAsync(new Models.Requests.UpsertDocRequest()
+ {
+ Docs = docs,
+ }, collectionName, cancellationToken);
+ foreach (var doc in docs)
+ {
+ yield return doc.Id;
+ }
+ }
+ }
+
+ public class DashVectorCollectionOptions
+ {
+ public int Dimension { get; set; }
+ public DataType DataType { get; set; } = DataType.FLOAT;
+ public string Metric { get; set; } = CollectionInfo.Metric.Cosine;
+ public Dictionary? ExtraParams { get; set; }
+ }
+}
diff --git a/src/DashVector.SemanticKernel/readme.md b/src/DashVector.SemanticKernel/readme.md
new file mode 100644
index 0000000..96a3c54
--- /dev/null
+++ b/src/DashVector.SemanticKernel/readme.md
@@ -0,0 +1 @@
+# DashVector.SemanticKernel
\ No newline at end of file
diff --git a/src/DashVector/DashVector.csproj b/src/DashVector/DashVector.csproj
index 132c02c..f80c10e 100644
--- a/src/DashVector/DashVector.csproj
+++ b/src/DashVector/DashVector.csproj
@@ -1,9 +1,14 @@
-
+
- net6.0
enable
enable
+ e49d7ca5-270c-4339-8bc1-f8c4fe66a275
+
+
+
+
+
diff --git a/src/DashVector/Enums/CollectionStatus.cs b/src/DashVector/Enums/CollectionStatus.cs
index 485439c..7dc1274 100644
--- a/src/DashVector/Enums/CollectionStatus.cs
+++ b/src/DashVector/Enums/CollectionStatus.cs
@@ -2,10 +2,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace DashVector.Enums
{
+ [JsonConverter(typeof(JsonStringEnumConverter))]
public enum CollectionStatus
{
INITIALIZED,
diff --git a/src/DashVector/Models/CollectionMeta.cs b/src/DashVector/Models/CollectionMeta.cs
index d68640e..250e9e1 100644
--- a/src/DashVector/Models/CollectionMeta.cs
+++ b/src/DashVector/Models/CollectionMeta.cs
@@ -45,7 +45,7 @@ public class CollectionMeta
/// Fields, value: Float/Bool/INT/String
///
[JsonPropertyName("fields_schema")]
- public Dictionary FiledSchema { get; set; }
+ public Dictionary FiledSchema { get; set; } = [];
///
/// PartitionName information
diff --git a/src/DashVector/Models/CollectionStats.cs b/src/DashVector/Models/CollectionStats.cs
index 654a0c4..e32d366 100644
--- a/src/DashVector/Models/CollectionStats.cs
+++ b/src/DashVector/Models/CollectionStats.cs
@@ -10,12 +10,13 @@ namespace DashVector.Models
public class CollectionStats
{
[JsonPropertyName("total_doc_count")]
- public string TotalDocCount { get; set; }
+ [JsonConverter(typeof(LongToStringConverter))]
+ public long TotalDocCount { get; set; }
[JsonPropertyName("index_completeness")]
public float IndexCompleteness { get; set; }
[JsonPropertyName("partitions")]
- public Dictionary Partitions { get; set; }
+ public Dictionary? Partitions { get; set; }
}
}
diff --git a/src/DashVector/Models/Doc.cs b/src/DashVector/Models/Doc.cs
index 129ef65..128db2d 100644
--- a/src/DashVector/Models/Doc.cs
+++ b/src/DashVector/Models/Doc.cs
@@ -21,7 +21,7 @@ public class Doc
/// vector data
///
[JsonPropertyName("vector")]
- public List Vector { get; set; }
+ public float[] Vector { get; set; } = [];
///
/// sparse vector data
diff --git a/src/DashVector/Models/Requests/QueryDocRequest.cs b/src/DashVector/Models/Requests/QueryDocRequest.cs
index 8f696b2..2d57f49 100644
--- a/src/DashVector/Models/Requests/QueryDocRequest.cs
+++ b/src/DashVector/Models/Requests/QueryDocRequest.cs
@@ -12,7 +12,7 @@ public class QueryDocRequest
{
[JsonPropertyName("vector")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
- public List? vector { get; set; }
+ public float[]? Vector { get; set; }
[JsonPropertyName("sparse_vector")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
diff --git a/src/DashVector/Models/Responses/ErrorResponse.cs b/src/DashVector/Models/Responses/ErrorResponse.cs
deleted file mode 100644
index aca03f1..0000000
--- a/src/DashVector/Models/Responses/ErrorResponse.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Text.Json.Serialization;
-using System.Threading.Tasks;
-
-namespace DashVector.Models.Responses
-{
- public class ErrorResponse : ResponseBase
- {
- [JsonPropertyName("success")]
- public bool Success { get; set; }
-
- [JsonPropertyName("httpStatusCode")]
- public int HttpStatusCode { get; set; }
-
- [JsonPropertyName("requestId")]
- public string RequestId { get; set; }
-
- [JsonPropertyName("accessDeniedDetail")]
- public string AccessDeniedDetail { get; set; }
- }
-}