yuanrf преди 2 седмици
родител
ревизия
18b6b1ff9b

+ 14 - 0
OASystem/OASystem.Api/Controllers/GroupsController.cs

@@ -14084,6 +14084,20 @@ FROM
         }
 
 
+        [HttpGet("chat-stream")]
+        public async Task ChatStream([FromQuery] string question)
+        {
+            Response.ContentType = "text/plain; charset=utf-8";
+            Response.Headers.CacheControl = "no-cache";
+
+            await foreach (var chunk in _deepSeekService.ChatStreamAsync(question))
+            {
+                await Response.WriteAsync(chunk);
+                await Response.Body.FlushAsync(); // 尽快把本段发给客户端(视反向代理是否缓冲而定)
+            }
+        }
+
+
         /// <summary>
         /// 商邀Ai行程框架导出
         /// </summary>

+ 88 - 0
OASystem/OASystem.Api/OAMethodLib/DeepSeekAPI/DeepSeekService.cs

@@ -388,6 +388,94 @@ namespace OASystem.API.OAMethodLib.DeepSeekAPI
             }
         }
 
+        /// <summary>
+        /// 流式 chat
+        /// </summary>
+        /// <param name="question">问题</param>
+        /// <param name="model">模型名称</param>
+        /// <param name="temperature">温度参数</param>
+        /// <param name="maxTokens">最大token数</param>
+        /// <returns>聊天响应</returns>
+        public async IAsyncEnumerable<string> ChatStreamAsync(string question, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000)
+        {
+            var messageContent = new List<object>
+            {
+                new { type = "text", text = question }
+            };
+
+            var request = new DeepSeekChatWithFilesRequest
+            {
+                Model = model,
+                Messages = new List<FileMessage>
+                {
+                    new FileMessage
+                    {
+                        Role = "user",
+                        Content = messageContent
+                    }
+                },
+                Stream = true,
+                Temperature = temperature,
+                MaxTokens = maxTokens,
+                TopP = 0.9M,
+                FrequencyPenalty = 0.2M,
+                PresencePenalty = 0.1M,
+            };
+
+            var jsonContent = JsonSerializer.Serialize(request, new JsonSerializerOptions
+            {
+                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
+            });
+
+            _httpClient.Timeout = TimeSpan.FromMinutes(10);
+
+            using var requestMsg = new HttpRequestMessage(HttpMethod.Post, "chat/completions")
+            {
+                Content = new StringContent(jsonContent, Encoding.UTF8, "application/json")
+            };
+
+            using var response = await _httpClient.SendAsync(requestMsg, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
+            response.EnsureSuccessStatusCode();
+
+            using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+            using var reader = new StreamReader(responseStream, Encoding.UTF8);
+
+            string? line;
+            while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null)
+            {
+                if (string.IsNullOrWhiteSpace(line))
+                    continue;
+                if (!line.StartsWith("data: ", StringComparison.Ordinal))
+                    continue;
+
+                var data = line["data: ".Length..];
+                if (data == "[DONE]")
+                    yield break;
+
+                string? deltaText = null;
+                try
+                {
+                    using var jsonDoc = JsonDocument.Parse(data);
+                    if (!jsonDoc.RootElement.TryGetProperty("choices", out var choices) || choices.GetArrayLength() == 0)
+                        continue;
+                    if (!choices[0].TryGetProperty("delta", out var delta))
+                        continue;
+                    if (delta.TryGetProperty("content", out var contentVal))
+                    {
+                        var text = contentVal.GetString();
+                        if (!string.IsNullOrEmpty(text))
+                            deltaText = text;
+                    }
+                }
+                catch (System.Text.Json.JsonException)
+                {
+                }
+
+                if (deltaText != null)
+                    yield return deltaText;
+            }
+        }
+
         /// <summary>
         /// 等待文件处理完成
         /// </summary>

+ 11 - 1
OASystem/OASystem.Api/OAMethodLib/DeepSeekAPI/IDeepSeekService.cs

@@ -1,4 +1,4 @@
-namespace OASystem.API.OAMethodLib.DeepSeekAPI
+namespace OASystem.API.OAMethodLib.DeepSeekAPI
 {
     /// <summary>
     /// DeepSeek API 服务接口
@@ -75,6 +75,16 @@
         /// <returns>聊天响应</returns>
         Task<ApiResponse> ChatAsync(string question, bool stream = false, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000);
 
+        /// <summary>
+        /// 流式 chat
+        /// </summary>
+        /// <param name="question">问题</param>
+        /// <param name="model">模型名称</param>
+        /// <param name="temperature">温度参数</param>
+        /// <param name="maxTokens">最大token数</param>
+        /// <returns>聊天响应</returns>
+        IAsyncEnumerable<string> ChatStreamAsync(string question, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000);
+
         /// <summary>
         /// 等待文件处理完成
         /// </summary>