LLM+ semantic kernel 多用户场景下的Agent如何实现会话与记忆的隔离
其实 面试官抛出这个问题的时候,我心里想的是,这不是很简单嘛,每个用户单独管理数据不就行了?
然后我就把这个答案给说出口了。说出来的那一瞬间,面试官就冷笑了一下,问了句,单独管理?你说的这个"单独",具体是怎么单独法?
我当时就卡壳了,一下子不知道该怎么接。因为"每个用户单独管理"这句话吧,听起来是对的,但你仔细想想,完全没有落地。单独管理的到底是什么东西呢?是当前的对话内容,还是历史的记忆?隔离的边界到底在哪里?用什么样的机制才能保证隔离不会被打破?这道题的真正难点啊,从来就不在"要不要隔离"这个问题上。真正的难点在于,Agent内部至少有两种完全不同性质的状态,需要用两套完全不同的隔离机制去分别管理。
这次被问懵之后,我回去把这个问题给彻底捋了一遍。也趁机分享给同样在准备大厂Agent系统面试的朋友们吧。
举个栗子

数据泄密,员工A知道了员工B的数据!
怎么解决?
使用 Session 隔离 会话
核心组件
🏭 KernelFactory - Kernel 工厂
┌─────────────────────────────────────────────────────────────────┐ │ KernelFactory │ ├─────────────────────────────────────────────────────────────────┤ │ ConcurrentDictionary<string, Kernel> _kernels │ │ │ │ GetOrCreateKernel(sessionKey): │ │ ├─ 检查缓存 → 有则返回 │ │ └─ 无则创建 → 存入缓存 → 返回 │ │ │ │ 核心作用:每个 Session 一个独立的 Kernel 实例 │ └─────────────────────────────────────────────────────────────────┘
核心代码
public class KernelFactory { private readonly ConcurrentDictionary<string, Kernel> _kernels = new(); private readonly string _apiKey; private readonly string _endpoint; private readonly QdrantClient _qdrantClient; private readonly int _vectorSize; public KernelFactory(IConfiguration config) { _apiKey = config["DashScope:ApiKey"]!; _endpoint = config["DashScope:Endpoint"]!; _qdrantClient = new QdrantClient( host: config["Qdrant:Host"]!, port: int.Parse(config["Qdrant:Port"]!), apiKey: config["Qdrant:QdrantApiKey"]!); _vectorSize = int.Parse(config["Qdrant:VectorSize"]!); } public Kernel GetOrCreateKernel(string sessionKey) { return _kernels.GetOrAdd(sessionKey, key => { Console.WriteLine($"[KernelFactory] Creating Kernel for Session: {key}"); var builder = Kernel.CreateBuilder(); builder.AddOpenAIChatCompletion( modelId: "qwen-plus", apiKey: _apiKey, endpoint: new Uri(_endpoint)); var kernel = builder.Build(); kernel.Data["SessionKey"] = key; kernel.Data["MemoryStore"] = new SessionMemoryStore(_qdrantClient, key, _vectorSize); return kernel; }); } public SessionMemoryStore GetMemoryStore(Kernel kernel) { return kernel.Data["MemoryStore"] as SessionMemoryStore ?? throw new InvalidOperationException("MemoryStore not found"); } public void RemoveKernel(string sessionKey) { if (_kernels.TryRemove(sessionKey, out _)) { Console.WriteLine($"[KernelFactory] Removed Kernel for Session: {sessionKey}"); } } }
Session 隔离原理
┌─────────────────────────────────────────────────────────────────────────┐ │ 三层隔离架构 │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ 1️⃣ 计算层隔离 (Kernel Instance) │ │ ┌─────────────┬─────────────┬─────────────┐ │ │ │ Kernel-A │ Kernel-B │ Kernel-C │ │ │ │ user-001 │ user-002 │ user-003 │ │ │ └─────────────┴─────────────┴─────────────┘ │ │ │ │ 2️⃣ 存储层隔离 (Qdrant Collection) │ │ ┌─────────────┬─────────────┬─────────────┐ │ │ │ session_ │ session_ │ session_ │ │ │ │ user_001 │ user_002 │ user_003 │ │ │ └─────────────┴─────────────┴─────────────┘ │ │ │ │ 3️⃣ 并发层隔离 (Task 独立作用域) │ │ ┌─────────────┬─────────────┬─────────────┐ │ │ │ Task-1 │ Task-2 │ Task-3 │ │ │ │ 独立变量 │ 独立变量 │ 独立变量 │ │ │ └─────────────┴─────────────┴─────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────┘
隔离验证:
user-001 只能访问 → session_user_001 ✅
user-002 只能访问 → session_user_002 ✅
user-003 只能访问 → session_user_003 ✅
关键技术点
| 技术 | 作用 | 实现方式 |
|---|---|---|
| ConcurrentDictionary | 线程安全的 Kernel 缓存 | GetOrAdd() 方法 |
| Qdrant Collection | 物理隔离存储 | session_{sessionId} 命名 |
| async/await | 异步并发 | Task.WhenAll() |
| Lambda 闭包 | 任务作用域隔离 | 每个 async lambda 独立状态机 |
| 引用类型 | 对象状态共享 | kernel.Data["MemoryStore"] 存引用 |
| 向量嵌入 | 语义搜索 | DashScope text-embedding-v2 |
✅ 推荐做法
| 场景 | 推荐做法 |
|---|---|
| Session 标识 | 用 GUID 或 用户 ID,确保唯一 |
| 向量生成 | 用"问题 + 回答"一起生成,检索更准 |
| 存储内容 | text=问题,description=回答,天然关联 |
| Collection 命名 | session_{sessionId} 清晰隔离 |
| 异常处理 | Task.WhenAll 内每个任务独立 try-catch |
项目总结
核心成果
| 成果 | 说明 |
|---|---|
| ✅ Session 隔离 | 每个用户独立的 Kernel + Qdrant Collection |
| ✅ 并发安全 | Task.WhenAll + ConcurrentDictionary |
| ✅ 向量存储 | 问题 + 回答整体存储,支持语义检索 |
| ✅ 查询功能 | 按 ID/按向量/按条件/获取全部 |
| ✅ 统计信息 | Collection 状态 + 记录数 |
适用场景
- ✅ 多用户客服系统
- ✅ 个性化 AI 助手
- ✅ 对话历史记录
- ✅ 向量相似度检索
- ✅ Session 数据隔离
项目代码
核心工厂类及Qdrant存储查询
using Google.Protobuf.Collections; using Microsoft.Extensions.Configuration; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Memory; using Polly; using Polly.Retry; using Qdrant.Client; using Qdrant.Client.Grpc; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.RateLimiting; using System.Threading.Tasks; namespace ConsoleApp11 { public class KernelFactory { private readonly ConcurrentDictionary<string, Kernel> _kernels = new(); private readonly string _apiKey; private readonly string _endpoint; private readonly QdrantClient _qdrantClient; private readonly int _vectorSize; public KernelFactory(IConfiguration config) { _apiKey = config["DashScope:ApiKey"]!; _endpoint = config["DashScope:Endpoint"]!; _qdrantClient = new QdrantClient( host: config["Qdrant:Host"]!, port: int.Parse(config["Qdrant:Port"]!), apiKey: config["Qdrant:QdrantApiKey"]!); _vectorSize = int.Parse(config["Qdrant:VectorSize"]!); } public Kernel GetOrCreateKernel(string sessionKey) { return _kernels.GetOrAdd(sessionKey, key => { Console.WriteLine($"[KernelFactory] Creating Kernel for Session: {key}"); var builder = Kernel.CreateBuilder(); builder.AddOpenAIChatCompletion( modelId: "qwen-plus", apiKey: _apiKey, endpoint: new Uri(_endpoint)); var kernel = builder.Build(); kernel.Data["SessionKey"] = key; kernel.Data["MemoryStore"] = new SessionMemoryStore(_qdrantClient, key, _vectorSize); return kernel; }); } public SessionMemoryStore GetMemoryStore(Kernel kernel) { return kernel.Data["MemoryStore"] as SessionMemoryStore ?? throw new InvalidOperationException("MemoryStore not found"); } public void RemoveKernel(string sessionKey) { if (_kernels.TryRemove(sessionKey, out _)) { Console.WriteLine($"[KernelFactory] Removed Kernel for Session: {sessionKey}"); } } } // <summary> /// Session 隔离的 Qdrant 存储 - 每个 Session 一个 Collection /// </summary> public class SessionMemoryStore { private readonly QdrantClient _client; private readonly string _collectionName; private readonly int _vectorSize; private bool _collectionCreated; public SessionMemoryStore(QdrantClient client, string sessionId, int vectorSize) { _client = client; // 核心:Collection 名包含 SessionID,实现隔离 _collectionName = $"session_{sessionId}".Replace(":", "_").Replace("-", "_"); _vectorSize = vectorSize; } /// <summary> /// 确保 Collection 存在 /// </summary> private async Task EnsureCollectionExistsAsync(CancellationToken cancellationToken = default) { if (_collectionCreated) return; var exists = await _client.CollectionExistsAsync(_collectionName, cancellationToken); if (!exists) // { Console.WriteLine($"[SessionMemoryStore] Creating Collection: {_collectionName}"); await _client.CreateCollectionAsync(_collectionName, new VectorParams { Size = (ulong)_vectorSize, Distance = Distance.Cosine }, cancellationToken: cancellationToken); await _client.CreatePayloadIndexAsync(_collectionName, "text", PayloadSchemaType.Text); await _client.CreatePayloadIndexAsync(_collectionName, "description", PayloadSchemaType.Keyword); } _collectionCreated = true; } public async Task<IReadOnlyList<string>> GetCollectionList(CancellationToken cancellationToken = default) { var Collections = await _client.ListCollectionsAsync(cancellationToken); return Collections; } public async Task DeleteCollectionByName(string name) { await _client.DeleteCollectionAsync(name); } /// <summary> /// 插入/更新单条记录 /// </summary> public async Task<Guid> UpsertAsync(Guid id, float[] vector, string text, string description = "", CancellationToken cancellationToken = default) { // ✅ 第 1 步:确保 Collection 存在 await EnsureCollectionExistsAsync(cancellationToken); var point = new PointStruct { Id = new PointId { Uuid = id.ToString() }, Vectors = vector, Payload = { ["id"] = id.ToString(), ["text"] = text, ["description"] = description, ["created_at"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds() } }; var result = await _client.UpsertAsync(_collectionName, new[] { point }, cancellationToken: cancellationToken); Console.WriteLine($"[SessionMemoryStore] Upserted: {id}, Status: {result.Status}"); return id; } /// <summary> /// 批量插入 /// </summary> public async Task<List<Guid>> UpsertBatchAsync(List<(Guid id, float[] vector, string text, string description)> records, CancellationToken cancellationToken = default) { // ✅ 第 1 步:确保 Collection 存在 await EnsureCollectionExistsAsync(cancellationToken); var points = records.Select(r => new PointStruct { Id = new PointId { Uuid = r.id.ToString() }, Vectors = r.vector, Payload = { ["id"] = r.id.ToString(), ["text"] = r.text, ["description"] = r.description, ["created_at"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds() } }).ToList(); var result = await _client.UpsertAsync(_collectionName, points, cancellationToken: cancellationToken); Console.WriteLine($"[SessionMemoryStore] Upserted {points.Count} records, Status: {result.Status}"); return records.Select(r => r.id).ToList(); } public async Task<List<QdrantRecord>> GetAllRecordsAsync(CancellationToken cancellationToken = default) { await EnsureCollectionExistsAsync(cancellationToken); var records = new List<QdrantRecord>(); var offset = 0; const int batchSize = 10; // 滚动获取所有记录 while (true) { var points = await _client.ScrollAsync( collectionName: _collectionName, limit: (uint)batchSize, offset: offset > 0 ? new PointId { Uuid = offset.ToString() } : null, payloadSelector: new WithPayloadSelector { Enable = true }, vectorsSelector: new WithVectorsSelector { Enable = false }, cancellationToken: cancellationToken); if (points.Result.Count == 0) break; foreach (var p in points.Result) { records.Add(new QdrantRecord { Id = p.Id.ToString(), Text = GetPayloadString(p.Payload, "text"), Description = GetPayloadString(p.Payload, "description"), CreatedAt = GetPayloadLong(p.Payload, "created_at") }); } offset += points.Result.Count; if (points.Result.Count < batchSize) break; } return records; } /// <summary> /// 按 ID 查询 /// </summary> public async Task<QdrantRecord?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default) { // ✅ 第 1 步:确保 Collection 存在 await EnsureCollectionExistsAsync(cancellationToken); var points = await _client.RetrieveAsync( collectionName: _collectionName, ids: new[] { new PointId { Uuid = id.ToString() } }, // ✅ 参数名 ids,传数组 withPayload: true, withVectors: false, cancellationToken: cancellationToken); if (points.Count == 0) return null; var p = points[0]; return new QdrantRecord { Id = p.Id.ToString(), Text = GetPayloadString(p.Payload, "text"), Description = GetPayloadString(p.Payload, "description"), CreatedAt = GetPayloadLong(p.Payload, "created_at") }; } /// <summary> /// 向量相似度搜索 /// </summary> public async Task<List<QdrantRecord>> SearchAsync(float[] vector, int limit = 5, double minScore = 0.0, CancellationToken cancellationToken = default) { // ✅ 第 1 步:确保 Collection 存在 await EnsureCollectionExistsAsync(cancellationToken); var results = await _client.SearchAsync( collectionName: _collectionName, vector: vector, limit: (ulong)limit, offset: 0, payloadSelector: new WithPayloadSelector { Enable = true }, vectorsSelector: new WithVectorsSelector { Enable = false }, cancellationToken: cancellationToken); return results .Where(r => r.Score >= minScore) .Select(r => new QdrantRecord { Id = r.Id.ToString(), Score = r.Score, Text = GetPayloadString(r.Payload, "text"), Description = GetPayloadString(r.Payload, "description"), CreatedAt = GetPayloadLong(r.Payload, "created_at") }).ToList(); } /// <summary> /// 按条件过滤搜索 /// </summary> public async Task<List<QdrantRecord>> SearchWithFilterAsync(float[] vector, string? keyword = null, int limit = 5, CancellationToken cancellationToken = default) { // ✅ 第 1 步:确保 Collection 存在 await EnsureCollectionExistsAsync(cancellationToken); var filter = new Filter(); if (!string.IsNullOrEmpty(keyword)) { filter.Must.Add(new Condition { Field = new FieldCondition { Key = "description", Match = new Match { Keyword = keyword } } }); } var results = await _client.SearchAsync( collectionName: _collectionName, vector: vector, filter: filter, limit: (ulong)limit, offset: 0, cancellationToken: cancellationToken, payloadSelector: new WithPayloadSelector { Enable = true }, vectorsSelector: new WithVectorsSelector { Enable = false } ); return results.Select(r => new QdrantRecord { Id = r.Id.ToString(), Score = r.Score, Text = GetPayloadString(r.Payload, "text"), Description = GetPayloadString(r.Payload, "description") }).ToList(); } /// <summary> /// 删除记录 /// </summary> public async Task DeleteAsync(string id, CancellationToken cancellationToken = default) { await EnsureCollectionExistsAsync(cancellationToken); await _client.DeleteAsync(_collectionName, new[] { new PointId { Uuid = id } }, cancellationToken: cancellationToken); Console.WriteLine($"[SessionMemoryStore] Deleted: {id}"); } /// <summary> /// 删除整个 Session(清空 Collection) /// </summary> public async Task DeleteCollectionAsync(CancellationToken cancellationToken = default) { await _client.DeleteCollectionAsync(collectionName: _collectionName,cancellationToken: cancellationToken); _collectionCreated = false; Console.WriteLine($"[SessionMemoryStore] Deleted Collection: {_collectionName}"); } /// <summary> /// 获取统计信息 /// </summary> public async Task<CollectionStats> GetStatsAsync(CancellationToken cancellationToken = default) { await EnsureCollectionExistsAsync(cancellationToken); var info = await _client.GetCollectionInfoAsync(_collectionName, cancellationToken); return new CollectionStats { CollectionName = _collectionName, TotalPoints = info.PointsCount, VectorDimension = info.Config.Params.VectorsConfig.Params.Size, Distance = info.Config.Params.VectorsConfig.Params.Distance.ToString(), Status = info.Status.ToString() }; } #region Helper Methods private static string GetPayloadString(MapField<string, Value> payload, string key) { if (!payload.ContainsKey(key)) return ""; var value = payload[key]; return value.KindCase == Value.KindOneofCase.StringValue ? value.StringValue : ""; } private static long GetPayloadLong(MapField<string, Value> payload, string key) { if (!payload.ContainsKey(key)) return 0; var value = payload[key]; return value.KindCase == Value.KindOneofCase.DoubleValue ? (long)value.DoubleValue : 0; } #endregion } /// <summary> /// Qdrant 记录模型 - 用于封装从 Qdrant 查询返回的单条数据 /// </summary> public class QdrantRecord { /// <summary> /// 记录唯一标识(Qdrant 内部 ID,UUID 格式) /// </summary> public string Id { get; set; } = ""; /// <summary> /// 相似度分数(0.0 ~ 1.0,越高越相似) /// 仅在使用 SearchAsync 向量搜索时有值,GetByIdAsync 精确查询时为 0 /// </summary> public float Score { get; set; } /// <summary> /// 文本内容(通常是用户的问题或对话内容) /// 对应 Qdrant Payload 中的 "text" 字段 /// </summary> public string Text { get; set; } = ""; /// <summary> /// 描述信息(通常是助手的回答或补充说明) /// 对应 Qdrant Payload 中的 "description" 字段 /// </summary> public string Description { get; set; } = ""; /// <summary> /// 创建时间戳(Unix 秒级时间戳) /// 对应 Qdrant Payload 中的 "created_at" 字段 /// 可通过 DateTimeOffset.FromUnixTimeSeconds(CreatedAt) 转换为 DateTime /// </summary> public long CreatedAt { get; set; } } /// <summary> /// 集合统计信息 - 用于展示 Qdrant Collection 的状态和规模 /// </summary> public class CollectionStats { /// <summary> /// Collection 名称(格式:session_{sessionId}) /// 例如:session_user_001 /// </summary> public string CollectionName { get; set; } = ""; /// <summary> /// 总记录数(该 Session 已存储的对话/记忆条数) /// </summary> public ulong TotalPoints { get; set; } /// <summary> /// 向量维度(通常为 1536,对应 text-embedding-v2 模型的输出维度) /// </summary> public ulong VectorDimension { get; set; } /// <summary> /// 距离算法类型 /// Cosine = 余弦相似度(推荐用于文本向量) /// Euclidean = 欧氏距离 /// Dot = 点积 /// </summary> public string Distance { get; set; } = ""; /// <summary> /// Collection 状态 /// green = 健康可用 /// yellow = 部分可用 /// red = 不可用 /// </summary> public string Status { get; set; } = ""; } }
Program.cs
using Google.Protobuf; using Microsoft.Extensions.Configuration; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.ChatCompletion; using System.Text.Json; using System.Text.Json.Serialization; namespace ConsoleApp11 { internal class Program { static async Task Main(string[] args) { var config = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); var kernelFactory = new KernelFactory(config); var embeddingService = new EmbeddingService(config["DashScope:ApiKey"]!); Console.WriteLine("=== Session Isolation Demo ===\n"); // 模拟 3 个不同 Session var sessions = new[] { "user-001", "user-002", "user-003" }; await Task.WhenAll(sessions.Select(async sessionId => { try {// 1. 获取 Session 级别的 Kernel var kernel = kernelFactory.GetOrCreateKernel(sessionId); var memoryStore = kernelFactory.GetMemoryStore(kernel); var chatService = kernel.GetRequiredService<IChatCompletionService>(); // 6. LLM 调用 var sex = new Random().Next(18, 36) % 2 == 0 ? "男" : "女"; var xgl = new Random().Next(18, 36) % 2 == 0 ? "温柔" : "严谨"; var history = $"性别:{sex},性格:{xgl},年龄:{new Random().Next(18,36)},你是编号为:{sessionId}的客服!"; var chatHistory = new ChatHistory(); if (!string.IsNullOrEmpty(history)) { chatHistory.AddSystemMessage(history); } string message = "请如实回答你的客服编号及其他信息?"; chatHistory.AddUserMessage(message); // ✅ 真正调用 LLM var response = await chatService.GetChatMessageContentAsync( chatHistory, cancellationToken: default); //Console.WriteLine($"[Session {sessionId}] LLM Response: {response.Content}"); if (!string.IsNullOrWhiteSpace(response.Content)) { //构建用户提问 和 LLM回答 var resl = $"用户提问:{message},LLM作答:{response.Content}"; var Vectors = await embeddingService.GenerateEmbeddingAsync(resl); //将用户提问 和 LLM 的回答存储到向量数据库 await memoryStore.UpsertAsync(Guid.NewGuid(), Vectors, message, response.Content.ToString()); } //模拟三个用户,应该生成三个集合 var collections = await memoryStore.GetCollectionList(); foreach (var item in collections) { Console.WriteLine("集合名称:"+ item); } //查询客服的历史服务记录 var ChatResult = await memoryStore.GetAllRecordsAsync(); Console.WriteLine($"\n[Session {sessionId}] === 历史服务记录 ==="); Console.WriteLine($"记录总数:{ChatResult.Count}\n"); if (ChatResult.Count == 0) { Console.WriteLine(" (暂无历史记录)"); } else { foreach (var record in ChatResult) { Console.WriteLine($"┌─────────────────────────────────────────────────────────"); Console.WriteLine($"│ ID: {record.Id}"); Console.WriteLine($"│ 时间:{DateTimeOffset.FromUnixTimeSeconds(record.CreatedAt):yyyy-MM-dd HH:mm:ss}"); Console.WriteLine($"│ 用户提问:{record.Text}"); Console.WriteLine($"│ 客服回答:{record.Description}"); Console.WriteLine($"└─────────────────────────────────────────────────────────\n"); } } } catch (Exception ex) { Console.WriteLine($"[Session {sessionId}] Error: {ex.Message}"); } })); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } } public class EmbeddingService { private readonly string _apiKey; private readonly HttpClient _httpClient = new(); public EmbeddingService(string apiKey) { _apiKey = apiKey; } public async Task<float[]> GenerateEmbeddingAsync(string text) { var endpoint = "https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings"; var requestBody = new { model = "text-embedding-v2", input = text }; var json = JsonSerializer.Serialize(requestBody); var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); _httpClient.DefaultRequestHeaders.Clear(); _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}"); var response = await _httpClient.PostAsync(endpoint, content); var responseJson = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) { throw new Exception($"DashScope API 错误:{response.StatusCode}\n{responseJson}"); } using var doc = JsonDocument.Parse(responseJson); var embedding = doc.RootElement.GetProperty("data")[0].GetProperty("embedding"); var vector = new float[embedding.GetArrayLength()]; int idx = 0; foreach (var item in embedding.EnumerateArray()) { vector[idx++] = item.GetSingle(); } return vector; } } }
配置文件-appsettings.json
{ "DashScope": { "ApiKey": "sk-axxxxxxxxxxxxxxxxxxxxx101d", "Endpoint": "https://dashscope.aliyuncs.com/compatible-mode/v1" }, "Qdrant": { "Host": "x.16.9.10", "Port": 6334, "QdrantApiKey": "xxxxxxx", "VectorSize": 1536 } }
项目引用:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.10" /> <PackageReference Include="Microsoft.SemanticKernel" Version="1.78.0" /> <PackageReference Include="Microsoft.SemanticKernel.Plugins.Memory" Version="1.78.0-alpha" /> <PackageReference Include="Qdrant.Client" Version="1.18.1" /> <PackageReference Include="Sdcb.DashScope" Version="2.0.0" /> <PackageReference Include="System.Threading.RateLimiting" Version="8.0.0" /> <PackageReference Include="Polly" Version="8.4.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" /> </ItemGroup> <ItemGroup> <None Update="appsettings.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> </ItemGroup> </Project>

浙公网安备 33010602011771号