|
@@ -0,0 +1,276 @@
|
|
|
|
+using HTEXGpt.Models;
|
|
|
|
+using Newtonsoft.Json.Linq;
|
|
|
|
+using System.Diagnostics;
|
|
|
|
+using System.Net.WebSockets;
|
|
|
|
+using System.Text;
|
|
|
|
+using System.Text.Json;
|
|
|
|
+using System.Text.Json.Nodes;
|
|
|
|
+
|
|
|
|
+namespace HTEXGpt.Services
|
|
|
|
+{
|
|
|
|
+ public class SparkDeskServiceImpl : IModelService
|
|
|
|
+ {
|
|
|
|
+ static ClientWebSocket? webSocket0;
|
|
|
|
+ static CancellationToken cancellation;
|
|
|
|
+ private string appId = "33cc7806";
|
|
|
|
+ private string appSecret = "YmVmNWI4YTRmNzFmZDg4MzUyMGM1MzYx";
|
|
|
|
+
|
|
|
|
+ private string appKey = "7f6165ce496c92a7e231e80a87fe62e9";
|
|
|
|
+ private static string HOST_URL = "https://spark-api.xf-yun.com/v3.5/chat";
|
|
|
|
+ private static string domain = "generalv3.5";
|
|
|
|
+
|
|
|
|
+ public SparkDeskServiceImpl(ClientWebSocket webSocket) {
|
|
|
|
+ webSocket0=webSocket;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /// <summary>
|
|
|
|
+ ///
|
|
|
|
+ /// </summary>
|
|
|
|
+ /// <param name="dto"></param>
|
|
|
|
+ /// <returns></returns>
|
|
|
|
+ public async Task<ChatResponse> ChatMessage(ChatRequest dto,HttpContext httpContext ,HttpResponse? response)
|
|
|
|
+ {
|
|
|
|
+ //测试,debug到这里的时候你会发现,协议使用的是HTTP/2. APS.NET Core 2.1以上就默认支持HTTP/2,无需额外的配置。再Windows Server2016/Windows10+会自动提供支持。
|
|
|
|
+ //string requestProtocol = httpContext.Request.Protocol;
|
|
|
|
+ //响应头部添加text/event-stream,这是HTTP/2协议的一部分。
|
|
|
|
+ // response!.Headers.Add("Content-Type", "text/event-stream");
|
|
|
|
+
|
|
|
|
+ ChatResponse chatResponse = new ChatResponse();
|
|
|
|
+ Stopwatch stopwatch = Stopwatch.StartNew();
|
|
|
|
+ // response!.Headers.ContentType="text/event-stream";
|
|
|
|
+ //for (int i = 0; i<100; i++)
|
|
|
|
+ //{
|
|
|
|
+ // // event:ping event是事件字段名,冒号后面是事件名称,不要忘了\n换行符。
|
|
|
|
+ // await HttpContext.Response.WriteAsync($"event:ping\n");
|
|
|
|
+
|
|
|
|
+ // // data: 是数据字段名称,冒号后面是数据字段内容。注意数据内容仅仅支持UTF-8,不支持二进制格式。
|
|
|
|
+ // // data后面的数据当然可以传递JSON String的。
|
|
|
|
+ // await HttpContext.Response.WriteAsync($"data:Controller {i} at {DateTime.Now}\r\r");
|
|
|
|
+
|
|
|
|
+ // // 写入数据到响应后不要忘记 FlushAsync(),因为该api方法是异步的,所以要全程异步,调用同步方法会报错。
|
|
|
|
+ // await HttpContext.Response.Body.FlushAsync();
|
|
|
|
+
|
|
|
|
+ // //模拟一个1秒的延迟。
|
|
|
|
+ // //await Task.Delay(1000);
|
|
|
|
+ //}
|
|
|
|
+ string authUrl = GetAuthUrl();
|
|
|
|
+
|
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
|
+ string url = authUrl.Replace("http://", "ws://").Replace("https://", "wss://");
|
|
|
|
+ //if (webSocket0!=null) {
|
|
|
|
+ // Console.WriteLine();
|
|
|
|
+ //}
|
|
|
|
+ //using (webSocket0 = new ClientWebSocket())
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ await webSocket0!.ConnectAsync(new Uri(url), cancellation);
|
|
|
|
+
|
|
|
|
+ SparkDeskRequest request = new SparkDeskRequest();
|
|
|
|
+ request.header = new SparkDeskHeader()
|
|
|
|
+ {
|
|
|
|
+ app_id = appId,
|
|
|
|
+ uid = dto.uid
|
|
|
|
+ };
|
|
|
|
+ request.parameter = new SparkDeskParameter()
|
|
|
|
+ {
|
|
|
|
+ chat = new SparkDeskChat()
|
|
|
|
+ {
|
|
|
|
+ domain = domain,//模型领域,默认为星火通用大模型
|
|
|
|
+ temperature = dto.@params.temperature,//温度采样阈值,用于控制生成内容的随机性和多样性,值越大多样性越高;范围(0,1)
|
|
|
|
+ //generalv3.5 max_tokens=8192
|
|
|
|
+ max_tokens = 8192,//生成内容的最大长度,范围(0,4096)
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ request.payload = new SparkDeskPayload()
|
|
|
|
+ {
|
|
|
|
+ // new Content() { role = "user", content = "你是谁" },
|
|
|
|
+ // new Content() { role = "assistant", content = "....." }, // AI的历史回答结果,这里省略了具体内容,可以根据需要添加更多历史对话信息和最新问题的内容。
|
|
|
|
+ message = new SparkDeskMessage()
|
|
|
|
+ {
|
|
|
|
+ text=dto.messages
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+ if (null == request.payload.message.text.Find(x=>x.role!.Equals("system")))
|
|
|
|
+ {
|
|
|
|
+ request.payload.message.text.Insert(0, new MessageDTO { role="system", content= dto.system });
|
|
|
|
+ }
|
|
|
|
+ string jsonString = JsonSerializer.Serialize(request);
|
|
|
|
+ //连接成功,开始发送数据
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ var frameData2 = System.Text.Encoding.UTF8.GetBytes(jsonString.ToString());
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ await webSocket0.SendAsync(new ArraySegment<byte>(frameData2), WebSocketMessageType.Text, true, cancellation);
|
|
|
|
+
|
|
|
|
+ // 接收流式返回结果进行解析
|
|
|
|
+ byte[] receiveBuffer = new byte[1024];
|
|
|
|
+ WebSocketReceiveResult result = await webSocket0.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), cancellation);
|
|
|
|
+ // string resp = "";
|
|
|
|
+ while (!result.CloseStatus.HasValue)
|
|
|
|
+ {
|
|
|
|
+ if (result.MessageType == WebSocketMessageType.Text)
|
|
|
|
+ {
|
|
|
|
+ string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, result.Count);
|
|
|
|
+ //将结果构造为json
|
|
|
|
+ var node = JsonNode.Parse(receivedMessage);
|
|
|
|
+ int? code = (int?)node?["header"]?["code"];
|
|
|
|
+ //JObject jsonObj = JObject.Parse(receivedMessage);
|
|
|
|
+ // int code = (int)jsonObj["header"]["code"];
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (0==code)
|
|
|
|
+ {
|
|
|
|
+ // int status = (int)jsonObj["payload"]["choices"]["status"];
|
|
|
|
+ int? status = (int?)node?["payload"]?["choices"]?["status"];
|
|
|
|
+
|
|
|
|
+ //JArray textArray = (JArray)jsonObj["payload"]["choices"]["text"];
|
|
|
|
+ JsonArray? textArray = (JsonArray?)node?["payload"]?["choices"]?["text"];
|
|
|
|
+ string? content = (string?)textArray?[0]?["content"];
|
|
|
|
+ // resp += content;
|
|
|
|
+ sb.Append(content);
|
|
|
|
+ if (status != 2)
|
|
|
|
+ {
|
|
|
|
+
|
|
|
|
+ // await httpContext.Response.WriteAsync($"event:ping\n");
|
|
|
|
+ //await httpContext.Response.WriteAsync($"data:Controller {receivedMessage} at {DateTime.Now}\r\r");
|
|
|
|
+ // await httpContext.Response.Body.FlushAsync();
|
|
|
|
+
|
|
|
|
+ // Console.WriteLine($"已接收到数据: {receivedMessage}");
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine($"最后一帧: {receivedMessage}");
|
|
|
|
+ //int totalTokens = (int)jsonObj["payload"]["usage"]["text"]["total_tokens"];
|
|
|
|
+ int? total_tokens = (int?)node?["payload"]?["usage"]?["text"]?["total_tokens"];
|
|
|
|
+ int? prompt_tokens = (int?)node?["payload"]?["usage"]?["text"]?["prompt_tokens"];
|
|
|
|
+ int? completion_tokens = (int?)node?["payload"]?["usage"]?["text"]?["completion_tokens"];
|
|
|
|
+ if (total_tokens.HasValue) {
|
|
|
|
+ chatResponse.total_tokens = total_tokens.Value;
|
|
|
|
+ }
|
|
|
|
+ if (prompt_tokens.HasValue)
|
|
|
|
+ {
|
|
|
|
+ chatResponse.prompt_tokens = prompt_tokens.Value;
|
|
|
|
+ }
|
|
|
|
+ if (completion_tokens.HasValue)
|
|
|
|
+ {
|
|
|
|
+ chatResponse.completion_tokens = completion_tokens.Value;
|
|
|
|
+ }
|
|
|
|
+ // Console.WriteLine($"整体返回结果: {resp}");
|
|
|
|
+ //Console.WriteLine($"本次消耗token数: {totalTokens}");
|
|
|
|
+ // await httpContext.Response.WriteAsync($"event:ping\n");
|
|
|
|
+ //await httpContext.Response.WriteAsync($"data: {resp} at {DateTime.Now}\r\r");
|
|
|
|
+ // await httpContext.Response.Body.FlushAsync();
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ // Console.WriteLine($"请求报错: {receivedMessage}");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (result.MessageType == WebSocketMessageType.Close)
|
|
|
|
+ {
|
|
|
|
+ // Console.WriteLine("已关闭WebSocket连接");
|
|
|
|
+ //数据发送完毕后关闭连接。
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+ result = await webSocket0.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), cancellation);
|
|
|
|
+ }
|
|
|
|
+ chatResponse.result= sb.ToString();
|
|
|
|
+ chatResponse.statusCode=System.Net.HttpStatusCode.OK;
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ chatResponse.error=$"{e.Message},{e.StackTrace}";
|
|
|
|
+ chatResponse.statusCode=System.Net.HttpStatusCode.InternalServerError;
|
|
|
|
+ //Console.WriteLine($"{e.Message}\n{e.StackTrace}");
|
|
|
|
+ }
|
|
|
|
+ finally {
|
|
|
|
+ //response.Body.Close();
|
|
|
|
+ await webSocket0!.CloseAsync(WebSocketCloseStatus.NormalClosure, "正常关闭", cancellation);
|
|
|
|
+ webSocket0.Dispose();
|
|
|
|
+ stopwatch.Stop();
|
|
|
|
+ chatResponse.time= stopwatch.ElapsedMilliseconds;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ // response.Body.Close();
|
|
|
|
+ return chatResponse;
|
|
|
|
+ }
|
|
|
|
+ // 返回code为错误码时,请查询https://www.xfyun.cn/document/error-code解决方案
|
|
|
|
+ public string GetAuthUrl()
|
|
|
|
+ {
|
|
|
|
+ string date = DateTime.UtcNow.ToString("r");
|
|
|
|
+
|
|
|
|
+ Uri uri = new Uri(HOST_URL);
|
|
|
|
+ StringBuilder builder = new StringBuilder("host: ").Append(uri.Host).Append("\n").//
|
|
|
|
+ Append("date: ").Append(date).Append("\n").//
|
|
|
|
+ Append("GET ").Append(uri.LocalPath).Append(" HTTP/1.1");
|
|
|
|
+
|
|
|
|
+ string sha = HMACsha256(appSecret, builder.ToString());
|
|
|
|
+ string authorization = string.Format("api_key=\"{0}\", algorithm=\"{1}\", headers=\"{2}\", signature=\"{3}\"", appKey, "hmac-sha256", "host date request-line", sha);
|
|
|
|
+ //System.Web.HttpUtility.UrlEncode
|
|
|
|
+
|
|
|
|
+ string NewUrl = "https://" + uri.Host + uri.LocalPath;
|
|
|
|
+
|
|
|
|
+ string path1 = "authorization" + "=" + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(authorization));
|
|
|
|
+ date = date.Replace(" ", "%20").Replace(":", "%3A").Replace(",", "%2C");
|
|
|
|
+ string path2 = "date" + "=" + date;
|
|
|
|
+ string path3 = "host" + "=" + uri.Host;
|
|
|
|
+
|
|
|
|
+ NewUrl = NewUrl + "?" + path1 + "&" + path2 + "&" + path3;
|
|
|
|
+ return NewUrl;
|
|
|
|
+ }
|
|
|
|
+ public static string HMACsha256(string apiSecretIsKey, string buider)
|
|
|
|
+ {
|
|
|
|
+ byte[] bytes = System.Text.Encoding.UTF8.GetBytes(apiSecretIsKey);
|
|
|
|
+ System.Security.Cryptography.HMACSHA256 hMACSHA256 = new System.Security.Cryptography.HMACSHA256(bytes);
|
|
|
|
+ byte[] date = System.Text.Encoding.UTF8.GetBytes(buider);
|
|
|
|
+ date = hMACSHA256.ComputeHash(date);
|
|
|
|
+ hMACSHA256.Clear();
|
|
|
|
+
|
|
|
|
+ return Convert.ToBase64String(date);
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ //构造请求体
|
|
|
|
+ public class SparkDeskRequest
|
|
|
|
+ {
|
|
|
|
+ public SparkDeskHeader header { get; set; }
|
|
|
|
+ public SparkDeskParameter parameter { get; set; }
|
|
|
|
+ public SparkDeskPayload payload { get; set; }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public class SparkDeskHeader
|
|
|
|
+ {
|
|
|
|
+ public string? app_id { get; set; }
|
|
|
|
+ public string? uid { get; set; }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public class SparkDeskParameter
|
|
|
|
+ {
|
|
|
|
+ public SparkDeskChat chat { get; set; }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public class SparkDeskChat
|
|
|
|
+ {
|
|
|
|
+ public string domain { get; set; }
|
|
|
|
+ public double temperature { get; set; } = 0.5;
|
|
|
|
+ public int max_tokens { get; set; }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public class SparkDeskPayload
|
|
|
|
+ {
|
|
|
|
+ public SparkDeskMessage message { get; set; }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ public class SparkDeskMessage
|
|
|
|
+ {
|
|
|
|
+ public List<MessageDTO> text { get; set; } = new List<MessageDTO>();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+}
|