You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#调用Azure OpenAI扩展API时反序列化响应失败问题

解决方案

问题根源

  1. 响应为SSE分块格式而非纯JSON:你调用的API返回的是Server-Sent Events(SSE)流,每段数据以data: 前缀开头,直接将整个响应读取为字符串后反序列化,会因开头的d字符触发JSON格式错误。
  2. 模型类结构不匹配:自定义的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 05:33:14