LLM+ semantic kernel 多用户场景下的Agent如何实现会话与记忆的隔离

其实 面试官抛出这个问题的时候,我心里想的是,这不是很简单嘛,每个用户单独管理数据不就行了?

然后我就把这个答案给说出口了。说出来的那一瞬间,面试官就冷笑了一下,问了句,单独管理?你说的这个"单独",具体是怎么单独法?

我当时就卡壳了,一下子不知道该怎么接。因为"每个用户单独管理"这句话吧,听起来是对的,但你仔细想想,完全没有落地。单独管理的到底是什么东西呢?是当前的对话内容,还是历史的记忆?隔离的边界到底在哪里?用什么样的机制才能保证隔离不会被打破?这道题的真正难点啊,从来就不在"要不要隔离"这个问题上。真正的难点在于,Agent内部至少有两种完全不同性质的状态,需要用两套完全不同的隔离机制去分别管理。

这次被问懵之后,我回去把这个问题给彻底捋了一遍。也趁机分享给同样在准备大厂Agent系统面试的朋友们吧。

举个栗子

image

 数据泄密,员工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; } = "";
    }
}
View Code

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;
        }
    }
}
View Code

配置文件-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>

 

posted @ 2026-08-14 15:10  天才卧龙  阅读(7)  评论(0)    收藏  举报