OpenAI 接口

OpenAI 接口经历了 CompletionsChat CompletionsResponses 三个版本,目前主流还是 Chat Completions

OpenAI 的 Responses 接口协议版本出现已经很久了,不过国内很多模型厂商依然还是 Chat Completions 接口格式,需要通过协议转换后才能在 Codex 使用。所以有些新手或非程序员使用国产模型时可能会有困惑,配置到 Codex 会报错,因为目前最新的 Codex 只支持 Responses 接口格式。


第一代 Completions(/v1/completions,传统文本补全)最早 GPT-3 时代主力接口,单轮续写,入参 prompt,无多轮对话结构,现已标记为Legacy 遗留接口,仅兼容 instruct 类老模型(text-davinci 系列)。不建议再使用

第二代:Chat Completions(/v1/chat/completions,对话补全)2023 年推出,适配 GPT-3.5-turbo/GPT-4,用 messages 多轮对话结构,成为行业标准,至今仍可正常使用,目前还是主流。

第三代:Responses(/v1/responses,智能体统一接口)2025 年 3 月发布,定位为下一代统一接口,整合对话、工具调用、联网检索、文件检索、多模态有状态会话,是面向 Agent 场景的升级超集,OpenAI 建议新项目优先使用,计划 2026 中期逐步弱化旧接口。


你可以通过社区一些人整理的 OpenAI 的接口文档,了解接口的详细参数:

https://www.postman.com/devrel/openai/folder/euam005/openai-api-capabilities?sideView=agentMode

https://shalk.github.io/openai-swagger-ui/


下面是 OpenAI-compatible 和 Anthropic-compatible 的能力对比,其中 /v1/message 是 Anthropic 接口格式,其它两个是 OpenAI 格式。

Feature/v1/responses/v1/chat/completions/v1/messages
Streaming
流式输出
Stateful chat
有状态会话
Remote MCPs
远程 MCP 服务
Custom tools
自定义工具(函数调用)
Include assistant messages in the request
请求里带 assistant 历史消息

表格信息来源是:LM Studio 文档。


接口格式对比

下面是最传统的 Completions 接口,接口也是支持流式返回的,主要特点是 /v1/completions 接口只有 prompt 参数,没有 message 参数,所以需要自己拼接上下文到 prompt 字段。

curl --location 'http://127.0.0.1:1234/v1/completions" \
  -H "Authorization: Bearer $NEWAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen/qwen3.5-9b",
    "prompt": "世界七大奇迹.",
    "stream": true
}'

当然,接口是支持流式返回的:

image-20260710093835560


最后正常结束返回的结构:

{
    "id": "cmpl-f02hm1ui4d8q2d36qls9c",
    "object": "text_completion",
    "created": 1783647489,
    "model": "qwen/qwen3.5-9b",
    "choices": [
        {
            "index": 0,
            "text": "",
            "logprobs": null,
            "finish_reason": "stop"
        }
    ],
    "usage": {
        "prompt_tokens": 4,
        "completion_tokens": 524,
        "total_tokens": 528
    },
    "stats": {
        "total_draft_tokens_count": 0,
        "accepted_draft_tokens_count": 0,
        "rejected_draft_tokens_count": 0,
        "ignored_draft_tokens_count": 0
    }
}

目前最常用的 Chat Completions 接口,请求体里用 messages 数组描述多轮对话:

curl --location 'http://127.0.0.1:1234/v1/chat/completions' \
--header 'Authorization: Bearer $NEWAPI_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "model": "qwen/qwen3.5-9b",
    "stream": true,
    "messages": [
        {
            "role": "system",
            "content": "以简短的话回答"
        },
        {
            "role": "user",
            "content": "世界七大奇迹"
        },
        {
            "role": "user",
            "content": "属于中国的"
        }
    ]
}'

返回结构大致如下:

{
    "id": "chatcmpl-2rdnm653viwc3f93ea5i",
    "object": "chat.completion.chunk",
    "created": 1783647927,
    "model": "qwen/qwen3.5-9b",
    "system_fingerprint": "qwen/qwen3.5-9b",
    "choices": [
        {
            "index": 0,
            "delta": {
                "content": "长城"
            },
            "logprobs": null,
            "finish_reason": null
        }
    ]
}

而第三代 Responses 接口则用 input 而不是 messages,并且原生支持推理强度、状态续接等能力,也就是说服务器会保存对话记录,后续只需要推送当前最新用户问题即可,不需要拼接提交完整的对话历史。

curl --location 'http://127.0.0.1:1234/v1/responses' \
--header 'Authorization: Bearer $NEWAPI_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "model": "qwen/qwen3.5-9b",
    "stream": true,
    "input": "以简短的话回答,世界七大奇迹",
    "reasoning": {
        "effort": "medium"
    }
}'

流式返回第一条对话会带有 responses.id

{
    "type": "response.created",
    "response": {
        "id": "resp_ab7c283675e4e46e3c0c04826db352593b0637bfdc747895",
        "object": "response",
 ...

后续在 previous_response_id 参数贴上 responses.id ,无需拼接完整上下文对话历史。

curl --location 'http://127.0.0.1:1234/v1/responses' \
--header 'Authorization: Bearer $NEWAPI_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
    "model": "qwen/qwen3.5-9b",
    "stream": true,
    "input": "只属于中国的",
    "previous_response_id": "resp_ab7c283675e4e46e3c0c04826db352593b0637bfdc747895",
    "reasoning": {
        "effort": "medium"
    }
}'

或者直接以无状态的形式,将完整上下文内容贴到 input 中,input 既可以单条,也可以设置为对话历史数组,可包含消息、推理、工具调用等不同类型。

{
    "model": "qwen/qwen3.5-9b",
    "stream": true,
    "input": [
        {
            "type": "message",
            "role": "system",
            "content": "简单回答"
        },
        {
            "type": "message",
            "role": "user",
            "content": "世界七大奇迹"
        },
        {
            "type": "message",
            "role": "user",
            "content": "属于中国的"
        }
    ],
    "reasoning": {
        "effort": "medium"
    }
}

Responses 格式的接口,可以更加方便地通过 type 字段和其它字段,有效状态判断 Agent 执行状态和编程逻辑,提升用户交互体验,对开发者更加友好,对于设计联网、检索、多轮工具链、推理等状态和能力交互,也很方便。

image-20260710100518930



对于原生 API 接口的介绍就到此为止,本系列教程主要讲解原理和 Agent 逻辑,后续以 SDK 为主,不详细介绍接口的具体参数和能力,感兴趣的读者请移步到官方文档了解。


SDK 接入准备

本文所有示例基于官方 .NET SDK,SDK 源码和 nuget 地址:

https://github.com/openai/openai-dotnet

https://www.nuget.org/packages/OpenAI

笔者当前使用版本是 OpenAI 2.12.0。

OpenAI 2.13.0 在使用本地模型,如 LM Studio 时,流式输出会有 bug。


先看看 OpenAI 官方的 SDK 有哪些内容:

命名空间客户端类用途
OpenAI.ChatChatClient对话补全(Chat Completions)
OpenAI.ResponsesResponsesClient第三代 Responses 接口、深度推理
OpenAI.EmbeddingsEmbeddingClient向量化
OpenAI.AudioAudioClient语音合成 / 语音识别
OpenAI.ImagesImageClient图像生成
OpenAI.ModelsOpenAIModelClient列出 / 查询模型
OpenAI.FilesOpenAIFileClient文件上传与管理

创建客户端

我们最终用来请求服务器的是 ChatClient、EmbeddingClient 等客户端类型对象,默认内置地址是连到官方 https://api.openai.com 的。

using OpenAI.Chat;

ChatClient client = new(model: "gpt-4o", apiKey: "你的密钥");

如果要自定义服务器地址,就需要通过 OpenAIClientOptions.Endpoint 指定地址:

using OpenAI.Chat;
using System.ClientModel;

ChatClient client = new(
    model: "gpt-4o",
    credential: new ApiKeyCredential("你的密钥"),
    options: new OpenAIClientOptions { Endpoint = new Uri("https://你的服务器地址/v1") });

如果要进行比较多的其它操作,可以使用 OpenAIClient 工厂模式在相同的端点下创建其它类型的客户端,可以同时使用对话、向量化、语音等功能。

using OpenAI;

OpenAIClient factory = new(
    credential: new ApiKeyCredential("你的密钥"),
    options: new OpenAIClientOptions { Endpoint = new Uri("https://你的服务器地址/v1") });

// 工厂方法会自动复用上面的密钥和 Endpoint
ChatClient chat = factory.GetChatClient("qwen/qwen3.5-9b");
EmbeddingClient embedding = factory.GetEmbeddingClient("text-embedding-3-small");

注意 ChatClientEmbeddingClientAudioClientImageClient 在创建时就要绑定模型名称,因为它们的所有请求都会带上这个模型,而 OpenAIModelClientOpenAIFileClient 这类不绑定模型。


记录日志

记录 SDK 的原生 http 请求是非常重要的一个事情,有助于帮助我们理解接口内容和诊断问题,以下 DelegatingHandler 可以拦截 http 请求,输出原始请求和响应内容,但是因为这是拦截,使用时会导致流式变成了同步等待,所以只有在调试情况下建议使用

笔者还没有找到 DelegatingHandler 记录日志还能保持流式返回的方案,如果你有,欢迎微信讨论。

public sealed class LoggingHandler : DelegatingHandler
{
    public LoggingHandler(HttpMessageHandler? inner = null) : base(inner ?? new HttpClientHandler()) { }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken ct)
    {
        if (request.Content != null)
        {
            await request.Content.LoadIntoBufferAsync(ct);
            Console.WriteLine($"--> {request.Method} {request.RequestUri}");
            Console.WriteLine(await request.Content.ReadAsStringAsync(ct));
        }

        var response = await base.SendAsync(request, ct);

        if (response.Content != null)
        {
            var origin = await response.Content.ReadAsStreamAsync(ct);
            var ms = new MemoryStream();
            await origin.CopyToAsync(ms, ct);
            await origin.DisposeAsync();

            Console.WriteLine($"<-- {(int)response.StatusCode}");
            Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));

            ms.Position = 0;
            response.Content = new StreamContent(ms);
            response.Content.Headers.ContentType =
                new MediaTypeHeaderValue(response.Content.Headers.ContentType?.MediaType ?? "application/json");
        }
        return response;
    }
}

使用拦截器:

OpenAIClient factory = new(
    credential: new ApiKeyCredential("1234"),
    options: new OpenAIClientOptions
    {
        Endpoint = new Uri("http://127.0.0.1:1234/v1"),
        Transport = new HttpClientPipelineTransport(new HttpClient(new LoggingHandler()))
    });

列出所有模型

GET /v1/models 用来查询当前账号可用哪些模型。它返回一个 data 数组,每项包含模型 id、创建时间和归属:

curl "https://你的服务器地址/v1/models" \
  -H "Authorization: Bearer $NEWAPI_API_KEY"

返回示例:

{
    "data": [
        {
            "id": "qwen/qwen3.5-9b",
            "object": "model",
            "owned_by": "organization_owner"
        },
        {
            "id": "google/gemma-4-12b",
            "object": "model",
            "owned_by": "organization_owner"
        }
            ],
    "object": "list"
}

image-20260710110453958


之所以介绍这个接口是有原因的,因为我们在设计 Agent 平台时,往往会碰到对接不同厂商渠道和模型,如果让用户手动填写模型名称,那就会很麻烦,所以 MaomiAgent 做了一个很好用的接入模型功能。

faxianmoxin


对应到 SDK,使用 OpenAIModelClient 即可,它返回的 OpenAIModelCollection 本身是一个只读集合,可以直接 foreach 遍历。

using OpenAI;
using OpenAI.Models;

OpenAIModelClient client = new(
    credential: new System.ClientModel.ApiKeyCredential("你的密钥"),
    options: new OpenAIClientOptions { Endpoint = new Uri("http://127.0.0.1:1234/v1") });

OpenAIModelCollection models = client.GetModels();

foreach (OpenAIModel model in models)
{
    Console.WriteLine($"模型: {model.Id}    归属: {model.OwnedBy}    创建: {model.CreatedAt:yyyy-MM-dd}");
}

image-20260710112839239


至于嵌入、图片生成这些接口,这里就不再讨论。