From 49e9ded36a87d016b990f9af4ca80a485f8d24f3 Mon Sep 17 00:00:00 2001 From: zpc Date: Fri, 12 Jul 2024 16:15:55 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Claude=E5=AF=B9=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Chat/Claude/ClaudeChat.cs | 71 +++++++++++++++++-- .../Chat/Claude/ClaudeChatChatParams.cs | 2 +- .../Chat/Claude/ClaudeChatChatStreamParams.cs | 31 ++++++++ .../Chat/Claude/ClaudeChatInfo.cs | 20 ------ .../Chat/Claude/Model/ClaudeChatContent.cs | 28 ++++++++ .../Chat/Claude/Model/ClaudeChatResponse.cs | 60 ++++++++++++++++ .../Claude/Model/ClaudeChatStreamResponse.cs | 41 +++++++++++ .../Chat/Claude/Model/ClaudeChatUsage.cs | 28 ++++++++ .../Chat/Contract/IChat.cs | 2 +- src/2-api/TextGenerationApi/Program.cs | 2 +- 10 files changed, 256 insertions(+), 29 deletions(-) create mode 100644 src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatChatStreamParams.cs delete mode 100644 src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatInfo.cs create mode 100644 src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatContent.cs create mode 100644 src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatResponse.cs create mode 100644 src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatStreamResponse.cs create mode 100644 src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatUsage.cs diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChat.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChat.cs index 202a568..9fb0e0b 100644 --- a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChat.cs +++ b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChat.cs @@ -13,6 +13,7 @@ using System.Net.Http.Json; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json.Serialization; +using HuanMeng.MiaoYu.Code.Chat.Claude.Model; namespace HuanMeng.MiaoYu.Code.Chat.Claude { @@ -21,6 +22,13 @@ namespace HuanMeng.MiaoYu.Code.Chat.Claude /// public class ClaudeChat(ClaudeChatConfig claudeChatConfig, IHttpClientFactory factory) : IChat { + /// + /// + /// + /// + /// + /// + /// public async Task MessagesAsync(BaseChatParams chatParams) { var claudeChatChatParams = chatParams as ClaudeChatChatParams; @@ -52,20 +60,71 @@ namespace HuanMeng.MiaoYu.Code.Chat.Claude }; string json = JsonConvert.SerializeObject(chatParams, settings); var content = new StringContent(json, Encoding.UTF8, "application/json"); - //HttpClientHandler httpClientHandler = new HttpClientHandler(); - //HttpContent httpContent = new JsonContent(); var response = await httpClient.PostAsync(claudeChatConfig.RequestUrl, content); if (response.IsSuccessStatusCode) { - var chatInfo = response.Content.ReadFromJsonAsync(); + var chatInfo = await response.Content.ReadFromJsonAsync(); + return chatInfo; } } - throw new NotImplementedException(); + return null; } - public IAsyncEnumerable MessagesStreamAsync(BaseChatParams chatParams) + public async IAsyncEnumerable MessagesStreamAsync(BaseChatParams chatParams) { - throw new NotImplementedException(); + var claudeChatChatParams = chatParams as ClaudeChatChatStreamParams; + if (claudeChatChatParams == null) + { + throw new ArgumentException("参数异常"); + } + //添加默认值 + if (claudeChatChatParams.MaxTokens == 0) + { + claudeChatChatParams.MaxTokens = claudeChatConfig.MaxTokens; + } + claudeChatChatParams.Stream = true; + //添加默认值 + if (!string.IsNullOrEmpty(claudeChatChatParams.Model)) + { + claudeChatChatParams.Model = claudeChatChatParams.Model; + } + //去线程池里拿http线程 + using (var httpClient = factory.CreateClient()) + { + // 设置请求头 + httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + httpClient.DefaultRequestHeaders.Add("x-api-key", claudeChatConfig.ApiKey); + httpClient.DefaultRequestHeaders.Add("anthropic-version", claudeChatConfig.AnthropicVersion); + + var settings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + string json = JsonConvert.SerializeObject(chatParams, settings); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await httpClient.PostAsync(claudeChatConfig.RequestUrl, content); + if (response.IsSuccessStatusCode) + { + var chatInfo = await response.Content.ReadFromJsonAsync(); + if (chatInfo?.Type == "message_delta") + { + claudeChatChatParams.OutCharResponse.Usage.OutputTokens = chatInfo.Usage?.OutputTokens ?? 0; + } + else if (chatInfo?.Type == "content_block_start") + { + claudeChatChatParams.OutCharResponse = chatInfo?.Message ?? new ClaudeChatResponse(); + } + else if (chatInfo?.Type == "content_block_delta") + { + var text = chatInfo?.Delta?.Text ?? ""; + if (!string.IsNullOrEmpty(text)) + { + yield return text; + } + } + claudeChatChatParams.OutObj.Add(chatInfo); + } + } } } } diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatChatParams.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatChatParams.cs index a74971c..56e99f7 100644 --- a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatChatParams.cs +++ b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatChatParams.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; namespace HuanMeng.MiaoYu.Code.Chat.Claude { /// - /// Claude3 请求的参数 + /// Claude3 请求的参数 /// public class ClaudeChatChatParams : BaseClaudeChatChatParams { diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatChatStreamParams.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatChatStreamParams.cs new file mode 100644 index 0000000..50edb61 --- /dev/null +++ b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatChatStreamParams.cs @@ -0,0 +1,31 @@ +using HuanMeng.MiaoYu.Code.Chat.Claude.Model; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.MiaoYu.Code.Chat.Claude +{ + /// + /// 实时流 + /// + public class ClaudeChatChatStreamParams : BaseClaudeChatChatParams + { + /// + /// 视频流 + /// + public bool Stream { get; set; } + + /// + /// 返回消息内容 + /// + public ClaudeChatResponse OutCharResponse { get; set; } + + /// + /// + /// + public List OutObj { get; set; } + } +} diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatInfo.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatInfo.cs deleted file mode 100644 index 473a82e..0000000 --- a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/ClaudeChatInfo.cs +++ /dev/null @@ -1,20 +0,0 @@ -using HuanMeng.MiaoYu.Code.Chat.Contract; - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace HuanMeng.MiaoYu.Code.Chat.Claude -{ - /// - /// 返回内容 - /// - public class ClaudeChatInfo : BaseChatInfo - { - - } - - -} diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatContent.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatContent.cs new file mode 100644 index 0000000..f966498 --- /dev/null +++ b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatContent.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.MiaoYu.Code.Chat.Claude.Model +{ + /// + /// 消息内容 + /// + public class ClaudeChatContent + { + /// + /// 内容类型 + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// 文本内容 + /// + [JsonProperty("text")] + public string Text { get; set; } + } +} diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatResponse.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatResponse.cs new file mode 100644 index 0000000..1ab5ee2 --- /dev/null +++ b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatResponse.cs @@ -0,0 +1,60 @@ +using HuanMeng.MiaoYu.Code.Chat.Contract; +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.MiaoYu.Code.Chat.Claude.Model +{ + /// + /// 返回内容 + /// + public class ClaudeChatResponse : BaseChatInfo + { + /// + /// 消息 ID + /// + [JsonProperty("id")] + public string Id { get; set; } + + /// + /// 模型 + /// + [JsonProperty("model")] + public string Model { get; set; } + + /// + /// 消息类型 + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// 角色 + /// + [JsonProperty("role")] + public string Role { get; set; } + + /// + /// 消息内容 + /// + [JsonProperty("content")] + public ClaudeChatContent[] Content { get; set; } + + /// + /// 停止原因 + /// + [JsonProperty("stop_reason")] + public string StopReason { get; set; } + + /// + /// 使用情况 + /// + [JsonProperty("usage")] + public ClaudeChatUsage Usage { get; set; } + } + +} diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatStreamResponse.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatStreamResponse.cs new file mode 100644 index 0000000..1c1359c --- /dev/null +++ b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatStreamResponse.cs @@ -0,0 +1,41 @@ +using HuanMeng.MiaoYu.Code.Chat.Contract; + +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.MiaoYu.Code.Chat.Claude.Model +{ + /// + /// + /// + public class ClaudeChatStreamResponse : BaseChatStream + { + public int? Index { get; set; } + /// + /// 类型 + /// + public string Type { get; set; } + + /// + /// 内容 + /// + public ClaudeChatContent? Delta { get; set; } + /// + /// 结尾 + /// + public ClaudeChatResponse? Message { get; set; } + + /// + /// 使用情况 + /// + [JsonProperty("usage")] + public ClaudeChatUsage Usage { get; set; } + + } + //delta +} diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatUsage.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatUsage.cs new file mode 100644 index 0000000..89d9e76 --- /dev/null +++ b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Claude/Model/ClaudeChatUsage.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.MiaoYu.Code.Chat.Claude.Model +{ + /// + /// 使用情况 + /// + public class ClaudeChatUsage + { + /// + /// 输入的 token 数量 + /// + [JsonProperty("input_tokens")] + public int InputTokens { get; set; } + + /// + /// 输出的 token 数量 + /// + [JsonProperty("output_tokens")] + public int OutputTokens { get; set; } + } +} diff --git a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Contract/IChat.cs b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Contract/IChat.cs index c135c20..b51956c 100644 --- a/src/0-core/HuanMeng.MiaoYu.Code/Chat/Contract/IChat.cs +++ b/src/0-core/HuanMeng.MiaoYu.Code/Chat/Contract/IChat.cs @@ -23,6 +23,6 @@ namespace HuanMeng.MiaoYu.Code.Chat.Contract /// /// /// - IAsyncEnumerable MessagesStreamAsync(BaseChatParams chatParams); + IAsyncEnumerable MessagesStreamAsync(BaseChatParams chatParams); } } diff --git a/src/2-api/TextGenerationApi/Program.cs b/src/2-api/TextGenerationApi/Program.cs index adafb1f..545f55b 100644 --- a/src/2-api/TextGenerationApi/Program.cs +++ b/src/2-api/TextGenerationApi/Program.cs @@ -7,7 +7,7 @@ var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddHttpContextAccessor(); //添加httpContext注入访问 builder.Services.AddControllers(); - +builder.Services.AddHttpClient(); //注入数据库 builder.Services.AddDbContext((options) => {