前言
现在在网上搜索.NET接入大模型的帖子很少,有些官方案例只提供java和python的SDK,所以有了这篇.Net的接入大模型文章,目前仅实现对话模型的调用。

(图片来源网络,侵删)
这里仅举例通义千问,其他模型实现可以参考Gi他Hub 对您有帮助的话帮忙点个star
个人博客:FaceMan’ Blog 。

(图片来源网络,侵删)
Github:FaceMan’ GitHub 。
实现方式
-
创建IModelExtensionsChatCompletionService对话服务,规范对话服务应实现的接口。
public interface IModelExtensionsChatCompletionService { /// /// 对话 /// /// 对话历史 /// 参数配置 /// SK的kernel /// 是否取消 /// Task GetChatMessageContentsAsync(ChatHistory chatHistory, OpenAIPromptExecutionSettings settings = null, Kernel kernel = null, CancellationToken cancellationToken = default); /// /// 流式对话 /// /// 对话历史 /// 参数配置 /// SK的kernel /// 是否取消 /// IAsyncEnumerable GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, OpenAIPromptExecutionSettings settings = null, Kernel kernel = null, CancellationToken cancellationToken = default); }
-
创建ModelClient类做数据解析
public class ModelClient : IDisposable { internal readonly HttpClient HttpClient = null!; public ModelClient(string apiKey, ModelType modelType, HttpClient? httpClient = null) { HttpClient = httpClient ?? new HttpClient(); switch (modelType) { case ModelType.ZhiPu: int expirationInSeconds = 3600; // 设置过期时间为1小时 apiKey = GenerateJwtToken(apiKey, expirationInSeconds); HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); break; case ModelType.QianWen: HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); break; case ModelType.XunFei: break; } QianWen = new QianWenClient(this); ZhiPu = new ZhiPuClient(this); XunFei = new XunFeiClient(this); WenXin = new WenXinClient(this); } public QianWenClient QianWen { get; set; } public ZhiPuClient ZhiPu { get; set; } public XunFeiClient XunFei { get; set; } public WenXinClient WenXin { get; set; } /// /// 处理基础HTTP客户端。 /// public void Dispose() => HttpClient.Dispose(); /// /// 数据流转换器 /// /// /// 响应体 /// /// /// internal static async Task ReadResponse(HttpResponseMessage response, CancellationToken cancellationToken) { if (!response.IsSuccessStatusCode) { throw new Exception(await response.Content.ReadAsStringAsync()); } try { var debug = await response.Content.ReadAsStringAsync(); return (await response.Content.ReadFromJsonAsync(options: null, cancellationToken))!; } catch (Exception e) when (e is NotSupportedException or System.Text.Json.JsonException) { throw new Exception($"未能将以下json转换为: {typeof(T).Name}: {await response.Content.ReadAsStringAsync()}", e); } } /// /// 讯飞星火 数据流转换器 /// /// /// /// public static XunFeiResponseWrapper ReadResponse(string receivedMessage) { XunFeiResponseWrapper response = JsonConvert.DeserializeObject(receivedMessage); return response; } /// /// 智谱生成JWT令牌 /// /// /// /// /// internal string GenerateJwtToken(string apiKey, int expSeconds) { // 分割API Key以获取ID和Secret var parts = apiKey.Split('.'); if (parts.Length != 2) { throw new ArgumentException("Invalid API key format."); } var id = parts[0]; var secret = parts[1]; // 创建Header信息 var header = new JwtHeader(new SigningCredentials( new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)), SecurityAlgorithms.HmacSha256)) { {"sign_type", "SIGN"} }; // 创建Payload信息 long currentMillis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var payload = new JwtPayload { {"api_key", id}, {"exp", currentMillis + expSeconds * 1000}, {"timestamp", currentMillis} }; // 生成JWT Token var token = new JwtSecurityToken(header, payload); return new JwtSecurityTokenHandler().WriteToken(token); } }
-
定义ModelType区分不同模型供应商
public enum ModelType { [Description("通义千问")] [EnumName("通义千问")] QianWen = 1, [Description("智谱AI")] [EnumName("智谱AI")] ZhiPu, [Description("科大讯飞")] [EnumName("科大讯飞")] XunFei, [Description("文心一言")] [EnumName("文心一言")] WenXin, }
-
以通义千问为例,创建QianWenChatCompletionService类继承IModelExtensionsChatCompletionService
public class QianWenChatCompletionService : IModelExtensionsChatCompletionService { private readonly string _apiKey; private readonly string _model; public QianWenChatCompletionService(string key, string model) { _apiKey = key; _model = model; } /// /// 对话 /// /// /// /// /// /// public async Task GetChatMessageContentsAsync(ChatHistory chatHistory, OpenAIPromptExecutionSettings settings = null, Kernel kernel = null, CancellationToken cancellationToken = default) { var histroyList = new List(); ChatParameters chatParameters = null; foreach (var item in chatHistory) { var history = new ChatMessage() { Role = item.Role.Label, Content = item.Content, }; histroyList.Add(history); } if (settings != null) { chatParameters = new ChatParameters() { TopP = settings != null ? (float)settings.TopP : default, MaxTokens = settings != null ? settings.MaxTokens : default, Temperature = settings != null ? (float)settings.Temperature : default, Seed = settings.Seed != null ? (ulong)settings.Seed : default, Stop = settings != null ? settings.StopSequences : default, //RepetitionPenalty = (float)settings.FrequencyPenalty, //TopK = (int)settings.PresencePenalty }; } ModelClient client = new(_apiKey, ModelType.QianWen); QianWenResponseWrapper result = await client.QianWen.GetChatMessageContentsAsync(_model, histroyList, chatParameters, cancellationToken); var message = new ChatMessageContent(AuthorRole.Assistant, result.Output.Text); return message; } /// /// 流式对话 /// /// /// /// /// /// public async IAsyncEnumerable GetStreamingChatMessageContentsAsync(ChatHistory chatHistory, OpenAIPromptExecutionSettings settings = null, Kernel kernel = null, CancellationToken cancellationToken = default) { var histroyList = new List(); ChatParameters chatParameters = null; foreach (var item in chatHistory) { var history = new ChatMessage() { Role = item.Role.Label, Content = item.Content, }; histroyList.Add(history); } if (settings != null) { chatParameters = new ChatParameters() { TopP = settings != null ? (float)settings.TopP : default, MaxTokens = settings != null ? settings.MaxTokens : default, Temperature = settings != null ? (float)settings.Temperature : default, Seed = settings.Seed != null ? (ulong)settings.Seed : default, Stop = settings != null ? settings.StopSequences : default, }; } ModelClient client = new(_apiKey, ModelType.QianWen); await foreach (string item in client.QianWen.GetStreamingChatMessageContentsAsync(_model, histroyList, chatParameters, cancellationToken)) { yield return item; } } }
其中,OpenAIPromptExecutionSettings 和 ChatHistory 来自于SK框架,ChatParameters属于自定义的参数类,因为每家模型供应商都不一样。
-
ChatParameters的代码
public record ChatParameters { /// /// 结果的格式-“text”为旧文本版本,“message”为OpenAI兼容消息。 /// <para>对于语言模型,此字段必须是中的“text”,而不是VL模型中使用的字段 /// [JsonPropertyName("result_format")] public string? ResultFormat { get; set; } /// /// 随机数生成器的种子,用于控制模型生成的随机性。 /// 使用相同的种子允许模型输出的再现性。 /// 此字段为可选字段。默认值为1234。 /// [JsonPropertyName("seed")] public ulong? Seed { get; set; } /// /// 限制要生成的令牌数量。限制设置了最大值,但不能保证 /// 确切地说,将生成那么多令牌。此字段是可选的。 /// qwen turbo和qwen max longcontext的最大值和默认值为1500。 /// qwen max、qwen-max-1201和qwen plus的最大值和默认值为2048。 /// [JsonPropertyName("max_tokens")] public int? MaxTokens { get; set; } /// /// 细胞核取样的概率阈值。以0.8的值为例, /// 仅保留累积概率总和大于或等于0.8的令牌。 /// 取值范围为(0,1.0)。取值越大,随机性越高 /// 值越小,随机性越低。此字段是可选的. /// 默认值为0.8。请注意,该值不应大于或等于1. /// [JsonPropertyName("top_p")] public float? TopP { get; set; } /// /// 要采样的候选集的大小。例如,当设置为50时,只有前50个令牌 /// 将考虑进行采样。此字段是可选的。较大的值会增加随机性; /// 较小的值会增加确定性。注意:如果top_ k为null或大于100, /// 没有使用topk策略,只有topp是有效的。默认值为null。 /// [JsonPropertyName("top_k")] public int? TopK { get; set; } /// /// 为减少模型生成中的冗余而应用重复的惩罚。 /// 值为1.0表示没有惩罚。此字段是可选的。 /// 默认值为1.1。 /// [JsonPropertyName("repetition_penalty")] public float? RepetitionPenalty { get; set; } /// /// 控制文本生成的随机性和多样性程度。 /// 高温度值会降低概率分布的峰值、 /// 允许选择更多低概率词,从而产生更多样化的输出。 /// /// 低温度值会增加峰度,使高概率词更有可能被选中、 /// 从而使输出结果更加确定。此字段为可选项。 /// 数值范围为 [0, 2)。系统默认值为 1.0。 /// /// [JsonPropertyName("temperature")] public float? Temperature { get; set; } /// /// 指定生成后应停止模型进一步输出的内容。 /// 这可以是一个字符串或字符串列表、一个标记 ID 列表或一个标记 ID 列表。 /// 例如,如果将 stop 设置为 "hello",则在生成 "hello "之前停止生成; new() { Model = model ?? throw new ArgumentNullException(nameof(model)), Input = input ?? throw new ArgumentNullException(nameof(input)), Parameters = parameters, }; public static QianWenRequestWrapper Create(string model, TInput inputPrompt) => new() { Model = model ?? throw new ArgumentNullException(nameof(model)), Input = inputPrompt ?? throw new ArgumentNullException(nameof(inputPrompt)), }; } public record QianWenRequestWrapper : QianWenRequestWrapper { [JsonPropertyName("model")] public string Model { get; set; } [JsonPropertyName("input")] public TInput Input { get; init; } [JsonPropertyName("parameters")] public TParameters? Parameters { get; init; } }
- 调用
QianWenChatCompletionService ChatGPT = new("你的key", "模型名称:例如qwen-turbo"); ChatHistory historys = new ChatHistory(); historys.AddSystemMessage("你是一个C#编程高手,你将用代码回答我关于.net编程的技术问题,下面是我的第一个问题:"); historys.AddUserMessage("用c#写一个冒泡排序"); // 流式调用 await foreach (string item in chatgpt.GetStreamingChatMessageContentsAsync(historys)) { Console.Write(item); } //普通调用 var result = await chatgpt.GetChatMessageContentsAsync(historys); Console.WriteLine(result);