C#调用Azure OpenAI扩展API时反序列化响应失败问题
解决方案
问题根源
- 响应为SSE分块格式而非纯JSON:你调用的API返回的是Server-Sent Events(SSE)流,每段数据以
data:前缀开头,直接将整个响应读取为字符串后反序列化,会因开头的d字符触发JSON格式错误。 - 模型类结构不匹配:自定义的
ChatGPTChoice仅定义了Text字段,但实际响应中choices包含messages、index、finish_reason等结构,字段完全不对应。
解决步骤
1. 正确读取SSE分块响应
需要流式读取响应内容,逐行解析每个data块:
- 使用
StreamReader逐行处理响应流,避免一次性加载所有数据 - 跳过空行和非
data:开头的行 - 移除每行的
data:前缀,提取纯JSON片段 - 遇到
data: [DONE]时停止读取
示例代码:
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); response.EnsureSuccessStatusCode(); using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); using var reader = new StreamReader(stream); List<CompletionChunk> chunks = new List<CompletionChunk>(); while (!reader.EndOfStream) { var line = await reader.ReadLineAsync().ConfigureAwait(false); if (string.IsNullOrWhiteSpace(line)) continue; // 过滤并处理data块 if (line.StartsWith("data: ")) { var jsonContent = line["data: ".Length..]; if (jsonContent == "[DONE]") break; var chunk = JsonSerializer.Deserialize<CompletionChunk>(jsonContent); if (chunk != null) { chunks.Add(chunk); } } }
2. 修正模型类匹配响应结构
根据你提供的SSE响应示例,定义对应的强类型模型:
public class CompletionChunk { [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; [JsonPropertyName("model")] public string Model { get; set; } = string.Empty; [JsonPropertyName("created")] public long Created { get; set; } [JsonPropertyName("object")] public string ObjectType { get; set; } = string.Empty; [JsonPropertyName("choices")] public List<ChatGPTChoiceChunk> Choices { get; set; } = new List<ChatGPTChoiceChunk>(); } public class ChatGPTChoiceChunk { [JsonPropertyName("index")] public int Index { get; set; } [JsonPropertyName("messages")] public List<ChatMessageChunk> Messages { get; set; } = new List<ChatMessageChunk>(); [JsonPropertyName("finish_reason")] public string? FinishReason { get; set; } } public class ChatMessageChunk { [JsonPropertyName("delta")] public ChatMessageDelta Delta { get; set; } = new ChatMessageDelta(); [JsonPropertyName("index")] public int Index { get; set; } [JsonPropertyName("end_turn")] public bool EndTurn { get; set; } } public class ChatMessageDelta { [JsonPropertyName("role")] public string? Role { get; set; } [JsonPropertyName("content")] public string? Content { get; set; } } // 修正原Usage类的字段名拼写错误 public class ChatGPTUsage { [JsonPropertyName("prompt_tokens")] public int PromptTokens { get; set; } [JsonPropertyName("completion_tokens")] // 原代码为completion_token,API返回复数形式 public int CompletionTokens { get; set; } [JsonPropertyName("total_tokens")] public int TotalTokens { get; set; } }
3. 合并分块数据(可选)
如果需要将所有分块合并为完整响应,可以遍历chunks集合,拼接delta中的内容:
StringBuilder fullToolContent = new StringBuilder(); foreach (var chunk in chunks) { foreach (var choice in chunk.Choices) { foreach (var message in choice.Messages) { if (message.Delta.Role == "tool" && !string.IsNullOrEmpty(message.Delta.Content)) { fullToolContent.Append(message.Delta.Content); } } } } // 若需解析citations,可将拼接后的内容反序列化为对应模型 // var citationData = JsonSerializer.Deserialize<CitationModel>(fullToolContent.ToString());
额外注意事项
- 使用
HttpCompletionOption.ResponseHeadersRead确保立即开始读取流,适配SSE分块传输的特性 - 原代码中
ChatGPTUsage的completion_token字段名拼写错误,API实际返回的是completion_tokens(复数),需修正才能正确反序列化使用量数据
内容的提问来源于stack exchange,提问作者RobIreland
相关产品推荐
相关产品推荐

