批量调用OpenAI异步API返回空响应问题排查与重构
问题分析与解决方案
核心问题排查
你的异步等待逻辑是正确的,Task.WhenAll会等待所有异步任务完成,所以不是未正确等待API响应的问题。响应为空的主要可能原因:
- OpenAI API返回成功但内容为空(比如prompt质量问题、模型无法生成有效内容)
- 批量并行调用触发API速率限制,导致部分请求返回空或异常
- 原代码未对API返回的
Choices和Content做空值校验,无法定位具体空值来源
代码重构方案
1. 修复API调用方法的空值校验与日志
修改CallApiAsync方法,增加对返回结果的完整校验,同时补充日志便于排查问题:
public async Task<string> CallApiAsync(string requestPrompt) { try { var completionResult = await _openAiService.ChatCompletion.CreateCompletion(new ChatCompletionCreateRequest { Messages = new List<ChatMessage> { ChatMessage.FromSystem("You are a helpful assistant."), ChatMessage.FromUser(requestPrompt) }, Model = Models.Gpt_3_5_Turbo, }); if (!completionResult.Successful) { _logger.LogError($"API call failed for prompt: {requestPrompt}. Error: {completionResult.Error?.Message}"); return string.Empty; } if (!completionResult.Choices.Any()) { _logger.LogWarning($"API returned successful but no choices for prompt: {requestPrompt}"); return string.Empty; } var responseContent = completionResult.Choices.First().Message.Content?.Trim(); if (string.IsNullOrEmpty(responseContent)) { _logger.LogWarning($"API returned empty content for prompt: {requestPrompt}"); } return responseContent ?? string.Empty; } catch (Exception ex) { _logger.LogError($"Exception calling ChatGPT API for prompt: {requestPrompt}. Message: {ex.Message}", ex); throw; } }
2. 控制并发调用数量,避免速率限制
OpenAI API有严格的调用频率限制,批量并行发起所有请求会触发限制,导致部分请求返回空。使用SemaphoreSlim控制并发数:
// 根据你的OpenAI配额设置并发数,比如免费版建议设为3 var semaphore = new SemaphoreSlim(3); var fetchTasks = videos.Select(async item => { await semaphore.WaitAsync(); try { var formattedItem = new VideoItem { Title = item.Title, Subtitle = _extractService.GetCleanSubtitle(item.Subtitle ?? ""), Description = _extractService.GetCleanSubtitle(item.Description ?? ""), }; if (string.IsNullOrEmpty(formattedItem.Subtitle) && string.IsNullOrEmpty(formattedItem.Description)) { _logger.LogWarning($"Video {item.Id} has empty subtitle and description. Marking as not processed."); item.NotProcess = true; return new { item, processStatus = false, request = string.Empty, response = (string)null }; } var builder = new RequestBuilder(formattedItem).WithYamlFormatPath(yamlPath); var yamlRequest = builder.Build(); _logger.LogInformation($"Built YAML request for video {item.Id}: {yamlRequest}"); var response = await _apiService.CallApiAsync(yamlRequest); _logger.LogInformation($"Response for video {item.Id}: {response ?? "[Empty]"}"); return new { item, processStatus = true, request = yamlRequest, response }; } catch (Exception ex) { _logger.LogError($"Error fetching details for video {item.Id}: {ex.Message}", ex); return null; } finally { semaphore.Release(); } }); var fetchedDetails = (await Task.WhenAll(fetchTasks)).Where(result => result != null);
3. 修复数据库保存代码的错误
原代码中访问item.Id是错误的,匿名类型中的视频对象是item属性,需修正:
foreach (var result in fetchedDetails) { var dbResponse = new Response { Request = result.request, YtVideoId = result.item.Id, Response = result.response }; // 执行数据库保存操作,例如: // await _dbContext.Responses.AddAsync(dbResponse); }
额外建议
- 检查你的OpenAI API密钥配额,确保有足够的调用次数和速率
- 优化prompt内容,明确要求模型返回非空的YAML格式数据,比如在prompt末尾添加:"请务必返回非空的YAML格式数据,不得返回空内容"
- 对空响应的视频项进行重试逻辑,避免直接丢弃
内容的提问来源于stack exchange,提问作者Rasik
相关产品推荐
相关产品推荐

