OpenAI 接口接入调试:流式传输优化

OpenAI 接口接入调试记录

完成的功能

  1. 制作基于流式传输的 OpenAI 接口调用工具
  2. 制作关于剧情生成的提示词

过程中遇到的问题

问题一:调用流式传输接口等待时间过长

原因HttpCompletionOption 指示 HttpClient 操作是在响应可利用时立即视为已完成,还是在读取包含上下文的整个答案信息之后才视为已完成。默认为 HttpCompletionOption.ResponseContentRead,这会使在所有响应结束之后才视为完成,而实际只需要在响应可利用时返回,所以需要修改为 HttpCompletionOption.ResponseHeadersRead

//修改前
//此处测试耗时基本在80s以上
var requestTimer = Stopwatch.StartNew();
//PostAsJsonAsync 本身不支持直接传入 HttpCompletionOption
HttpResponseMessage httpResponseMessage = await client.PostAsJsonAsync<AIChat>("", aiChat);
requestTimer.Stop();

//修改后
var requestContent = JsonContent.Create(aiChat, mediaType: new MediaTypeHeaderValue("application/json"));
using var request = new HttpRequestMessage(HttpMethod.Post, "")
{
    Content = requestContent,
    VersionPolicy = HttpVersionPolicy.RequestVersionExact
};
//此时平均响应在1s左右
var sw = Stopwatch.StartNew();
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
sw.Stop();

问题二:流式传输的底层逻辑

所谓流式传输就是 http 建立 socket 协议之后不立即断开连接,而是按照一定的协议,在 socket 断开之前一直保持连接,直到服务端发送完毕,客户端接收完毕,才断开连接,即客户端会收到多次返回消息,需按约定格式一一处理

// 持续读取流数据直到结束
while (!reader.EndOfStream)
{
    // 异步读取一行数据
    var line = await reader.ReadLineAsync();
    
    // 跳过空行处理
    if (string.IsNullOrEmpty(line)) continue;
    
    // 检测流结束标记
    if (line == "data: [DONE]")
    {
        Console.WriteLine("\nStream completed");
        break; // 终止循环
    }

    // 处理有效数据行(服务端返回的SSE格式数据)
    if (line.StartsWith("data: "))
    {
        try
        {
            // 提取有效JSON部分(去除开头的"data: ")
            var json = line["data: ".Length..];
            
            // 反序列化JSON为响应对象
            var responsejson = JsonSerializer.Deserialize<StreamResponse>(json);
            
            // 解析模型思考过程内容(如模型内部推理过程)
            var think = responsejson?.choices.FirstOrDefault()?.delta?.reasoning_content ?? "";
            
            // 处理思考内容输出
            if (!string.IsNullOrEmpty(think))
            {
                // 状态切换:当开始思考时输出提示
                if (!bThinking)
                {
                    fileWriter.WriteLine("\nThinking...");  // 写入思考提示
                    bThinking = !bThinking;                 // 切换状态标志
                }
                fileWriter.Write(think);  // 持续写入思考内容
            }

            // 解析实际响应内容(模型最终输出的对话内容)
            var chunk = responsejson?.choices.FirstOrDefault()?.delta?.content ?? "";
            
            // 处理最终内容输出
            if (!string.IsNullOrEmpty(chunk))
            {
                // 状态切换:当开始输出正式内容时输出提示
                if (bThinking)
                {
                    fileWriter.WriteLine("\nSay:");         // 写入内容提示
                    bThinking = !bThinking;                 // 重置状态标志
                }
                fileWriter.Write(chunk);  // 持续写入对话内容
            }
        }
        catch (Exception ex)
        {
            // 异常处理:输出解析错误信息(保留控制台输出用于调试)
            Console.WriteLine($"Parse error: {ex.Message}");
        }
    }
}

未解决的问题

  1. 模型输出的文本符合 json 格式,但是会在 json 前后添加 markdown 的 json 标记,目前暂无法消除,需要进一步研究
  2. 文档中 response_format 可以指定模型输出 json 格式的内容,但是事实上模型经常输出错误的格式
📥 导出 Markdown