using System.Net.Http; using System.Net.Http.Headers; using System.Text.Json; using System.Text.Json.Serialization; using JsonSerializer = System.Text.Json.JsonSerializer; using System.IO; namespace OASystem.API.OAMethodLib.DeepSeekAPI { /// /// DeepSeek API 服务实现 /// public class DeepSeekService : IDeepSeekService { private readonly HttpClient _httpClient; private readonly ILogger _logger; private readonly IHostEnvironment _hostEnvironment; /// /// 配置文件 /// private DeepSeek DeepSeek { get; set; } /// /// 构造函数 /// public DeepSeekService(IHostEnvironment hostEnvironment, ILogger logger, HttpClient httpClient) { _hostEnvironment = hostEnvironment; _httpClient = httpClient; _logger = logger; DeepSeek = AutofacIocManager.Instance.GetService(); // 设置基础地址和认证头 _httpClient.BaseAddress = new Uri(DeepSeek.BaseAddress ?? "https://api.deepseek.com/v1/"); _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", DeepSeek.ApiKey); _httpClient.DefaultRequestHeaders.Add("Accept", "application/json"); } /// /// 测试API连通性 /// /// public async Task TestApiConnectivityAsync() { try { var response = await _httpClient.GetAsync("models"); return response.IsSuccessStatusCode; } catch { return false; } } /// /// 检查可用端点 /// /// public async Task> DiscoverAvailableEndpointsAsync() { var endpoints = new List { "models", "chat/completions", "files", "uploads", "documents", "assistants" }; var availableEndpoints = new List(); foreach (var endpoint in endpoints) { try { var response = await _httpClient.GetAsync(endpoint); if (response.IsSuccessStatusCode) { availableEndpoints.Add(endpoint); Console.WriteLine($"✅ 端点可用: {endpoint}"); } else { Console.WriteLine($"❌ 端点不可用: {endpoint} - {response.StatusCode}"); } } catch (Exception ex) { Console.WriteLine($"⚠️ 端点检查错误: {endpoint} - {ex.Message}"); } } return availableEndpoints; } /// /// 上传文件到DeepSeek API /// public async Task UploadFileAsync(IFormFile file, string purpose = "assistants") { try { _logger.LogInformation("开始上传文件: {FileName}, 大小: {Size} bytes", file.FileName, file.Length); // 检查文件大小 if (file.Length > 512 * 1024 * 1024) // 512MB限制 { throw new Exception($"文件大小超过限制: {file.Length} bytes"); } using var content = new MultipartFormDataContent(); using var fileStream = file.OpenReadStream(); var fileContent = new StreamContent(fileStream); fileContent.Headers.ContentType = new MediaTypeHeaderValue(GetContentType(file.FileName)); content.Add(fileContent, "file", file.FileName); content.Add(new StringContent(purpose), "purpose"); var response = await _httpClient.PostAsync("files", content); _logger.LogInformation("文件上传路径:{filePath}", response.RequestMessage.RequestUri); response.EnsureSuccessStatusCode(); var responseContent = await response.Content.ReadAsStringAsync(); var result = JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); _logger.LogInformation("文件上传成功: {FileName}, FileId: {FileId}", file.FileName, result.Id); return result; } catch (Exception ex) { _logger.LogError(ex, "文件上传失败: {FileName}", file.FileName); throw; } } /// /// 批量上传文件 /// public async Task> UploadFilesAsync(List files, string purpose = "assistants") { var results = new List(); foreach (var file in files) { var result = new FileUploadResult { FileName = file.FileName, FileSize = file.Length }; try { var uploadResponse = await UploadFileAsync(file, purpose); result.FileId = uploadResponse.Id; result.Success = true; result.Status = uploadResponse.Status; result.Message = "上传成功"; } catch (Exception ex) { result.Success = false; result.Message = $"上传失败: {ex.Message}"; result.Status = "error"; } results.Add(result); } return results; } /// /// 获取文件列表 /// public async Task ListFilesAsync() { try { var response = await _httpClient.GetAsync("files"); response.EnsureSuccessStatusCode(); var responseContent = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } catch (Exception ex) { _logger.LogError(ex, "获取文件列表失败"); throw; } } /// /// 获取文件信息 /// public async Task GetFileInfoAsync(string fileId) { try { var response = await _httpClient.GetAsync($"files/{fileId}"); response.EnsureSuccessStatusCode(); var responseContent = await response.Content.ReadAsStringAsync(); return JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } catch (Exception ex) { _logger.LogError(ex, "获取文件信息失败: {FileId}", fileId); throw; } } /// /// 删除文件 /// public async Task DeleteFileAsync(string fileId) { try { var response = await _httpClient.DeleteAsync($"files/{fileId}"); response.EnsureSuccessStatusCode(); _logger.LogInformation("文件删除成功: {FileId}", fileId); return true; } catch (Exception ex) { _logger.LogError(ex, "文件删除失败: {FileId}", fileId); return false; } } /// /// 使用已上传的文件进行聊天 /// public async Task ChatWithFilesAsync(List fileIds, string question, string model = "deepseek-chat", float temperature = 0.7f, int maxTokens = 4000) { try { // 等待所有文件处理完成 var processedFiles = new List(); foreach (var fileId in fileIds) { var fileInfo = await WaitForFileProcessingAsync(fileId); processedFiles.Add(fileInfo); } // 构建消息内容 var messageContent = new List { new { type = "text", text = question } }; // 添加文件引用 foreach (var file in processedFiles) { messageContent.Add(new { type = "file", file_id = file.Id }); } var request = new DeepSeekChatWithFilesRequest { Model = model, Messages = new List { new FileMessage { Role = "user", Content = messageContent } }, Temperature = temperature, MaxTokens = maxTokens }; var jsonContent = JsonSerializer.Serialize(request, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync("chat/completions", httpContent); response.EnsureSuccessStatusCode(); var responseContent = await response.Content.ReadAsStringAsync(); var chatResponse = JsonSerializer.Deserialize(responseContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); return new ApiResponse { Success = true, Message = "聊天成功", Answer = chatResponse.Choices[0].Message.Content, TokensUsed = chatResponse.Usage.TotalTokens }; } catch (Exception ex) { _logger.LogError(ex, "使用文件聊天失败"); throw; } } /// /// 等待文件处理完成 /// public async Task WaitForFileProcessingAsync(string fileId, int timeoutSeconds = 60) { var startTime = DateTime.UtcNow; var timeout = TimeSpan.FromSeconds(timeoutSeconds); while (DateTime.UtcNow - startTime < timeout) { var fileInfo = await GetFileInfoAsync(fileId); if (fileInfo.Status == "processed") { _logger.LogInformation("文件处理完成: {FileId}", fileId); return fileInfo; } else if (fileInfo.Status == "error") { throw new Exception($"文件处理失败: {fileId}"); } // 等待2秒后重试 await Task.Delay(2000); } throw new TimeoutException($"文件处理超时: {fileId}"); } /// /// 根据文件名获取Content-Type /// private static string GetContentType(string fileName) { var extension = Path.GetExtension(fileName).ToLower(); return extension switch { ".txt" => "text/plain", ".pdf" => "application/pdf", ".json" => "application/json", ".csv" => "text/csv", ".html" => "text/html", ".htm" => "text/html", ".md" => "text/markdown", _ => "application/octet-stream" }; } #region 项目相关 /// /// 读取项目内的指定文件并转换为IFormFile /// public async Task ReadProjectFilesAsync(List relativePaths) { var response = new ProjectFileReadResponse(); var projectRoot = GetProjectRootPath(); if (projectRoot.Contains("OASystem.Api")) { projectRoot = projectRoot.Replace("OASystem.Api", ""); } _logger.LogInformation("开始读取项目文件,数量: {Count}", relativePaths.Count); foreach (var relativePath in relativePaths) { var fileInfo = new ProjectFileInfo { RelativePath = relativePath }; try { var fullPath = Path.Combine(projectRoot, relativePath); fileInfo.FullPath = fullPath; if (!System.IO.File.Exists(fullPath)) { throw new FileNotFoundException($"文件不存在: {relativePath}"); } var fileInfoObj = new FileInfo(fullPath); fileInfo.FileSize = fileInfoObj.Length; fileInfo.LastModified = fileInfoObj.LastWriteTime; // 转换为IFormFile fileInfo.FormFile = await ConvertToFormFileAsync(fullPath); fileInfo.Success = true; _logger.LogInformation("文件读取成功: {FileName}, 大小: {Size} bytes", relativePath, fileInfo.FileSize); } catch (Exception ex) { fileInfo.Success = false; fileInfo.ErrorMessage = ex.Message; _logger.LogError(ex, "文件读取失败: {FileName}", relativePath); } response.FileInfos.Add(fileInfo); } response.Success = response.SuccessCount > 0; response.Message = $"成功读取 {response.SuccessCount} 个文件,失败 {response.FailureCount} 个"; return response; } /// /// 读取项目内指定目录的文件 /// public async Task ReadProjectDirectoryAsync(string directoryPath, string searchPattern = "*.cs", bool recursive = false) { var response = new ProjectFileReadResponse(); var projectRoot = GetProjectRootPath(); var fullDirectoryPath = Path.Combine(projectRoot, directoryPath); _logger.LogInformation("开始读取目录文件: {Directory}, 模式: {Pattern}", directoryPath, searchPattern); if (!Directory.Exists(fullDirectoryPath)) { response.Success = false; response.Message = $"目录不存在: {directoryPath}"; return response; } var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var files = Directory.GetFiles(fullDirectoryPath, searchPattern, searchOption); _logger.LogInformation("找到 {FileCount} 个文件", files.Length); foreach (var filePath in files) { var relativePath = Path.GetRelativePath(projectRoot, filePath); var fileInfo = new ProjectFileInfo { RelativePath = relativePath, FullPath = filePath }; try { var fileInfoObj = new FileInfo(filePath); fileInfo.FileSize = fileInfoObj.Length; fileInfo.LastModified = fileInfoObj.LastWriteTime; // 转换为IFormFile fileInfo.FormFile = await ConvertToFormFileAsync(filePath); fileInfo.Success = true; _logger.LogDebug("文件读取成功: {FileName}", relativePath); } catch (Exception ex) { fileInfo.Success = false; fileInfo.ErrorMessage = ex.Message; _logger.LogError(ex, "文件读取失败: {FileName}", relativePath); } response.FileInfos.Add(fileInfo); } response.Success = response.SuccessCount > 0; response.Message = $"成功读取 {response.SuccessCount} 个文件,失败 {response.FailureCount} 个"; return response; } /// /// 获取项目根目录路径 /// public string GetProjectRootPath() { // 在开发环境中使用ContentRootPath,在生产环境中可能需要调整 return _hostEnvironment.ContentRootPath; } /// /// 检查文件是否存在 /// public bool FileExists(string relativePath) { var fullPath = Path.Combine(GetProjectRootPath(), relativePath); return System.IO.File.Exists(fullPath); } /// /// 将物理文件转换为IFormFile /// public async Task ConvertToFormFileAsync(string filePath) { var fileInfo = new FileInfo(filePath); var fileName = Path.GetFileName(filePath); // 读取文件内容 byte[] fileBytes; using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) { fileBytes = new byte[fileStream.Length]; await fileStream.ReadAsync(fileBytes, 0, (int)fileStream.Length); } // 创建IFormFile return new FormFile( new MemoryStream(fileBytes), 0, fileBytes.Length, "file", fileName) { Headers = new HeaderDictionary(), ContentType = GetFileContentType(fileName) }; } /// /// 根据文件名获取Content-Type /// private string GetFileContentType(string fileName) { var extension = Path.GetExtension(fileName).ToLower(); return extension switch { ".cs" => "text/plain", ".txt" => "text/plain", ".json" => "application/json", ".xml" => "application/xml", ".html" => "text/html", ".htm" => "text/html", ".css" => "text/css", ".js" => "application/javascript", ".ts" => "application/typescript", ".py" => "text/x-python", ".java" => "text/x-java", ".cpp" => "text/x-c++", ".c" => "text/x-c", ".h" => "text/x-c", ".md" => "text/markdown", _ => "application/octet-stream" }; } /// /// 读取特定的签证申请表单文件 /// /// /// public async Task ReadVisaFormFileAsync(string fileName) { var specificPath = $@"OASystem\OASystem.Domain\ViewModels\VisaFormDetails\{fileName}"; return await ReadProjectFilesAsync(new List { specificPath }); } /// /// 读取OASystem项目中的所有CS文件 /// public async Task ReadOASystemFilesAsync(string searchPattern = "*.cs", bool recursive = true) { return await ReadProjectDirectoryAsync(@"OASystem", searchPattern, recursive); } /// /// 读取指定域模型中的CS文件 /// public async Task ReadDomainViewModelsAsync(string searchPattern = "*.cs", bool recursive = true) { return await ReadProjectDirectoryAsync(@"OASystem\OASystem.Domain\ViewModels", searchPattern, recursive); } /// /// 读取签证表单相关的所有CS文件 /// public async Task ReadVisaFormFilesAsync(string searchPattern = "*.cs", bool recursive = true) { return await ReadProjectDirectoryAsync(@"OASystem\OASystem.Domain\ViewModels\VisaFormDetails", searchPattern, recursive); } #endregion } #region 私有实体类 /// /// DeepSeek 聊天响应(用于反序列化) /// internal class DeepSeekResponse { [JsonPropertyName("choices")] public List Choices { get; set; } [JsonPropertyName("usage")] public Usage Usage { get; set; } } internal class Choice { [JsonPropertyName("message")] public Message Message { get; set; } } internal class Message { [JsonPropertyName("content")] public string Content { get; set; } } internal class Usage { [JsonPropertyName("total_tokens")] public int TotalTokens { get; set; } } #endregion }