资讯动态

OpenAI API接口演进:从completions到chat/completions的契约迁移

发布时间:2026/10/21 17:56:20 来源:尧图企业网站定制
1. 项目概述一个被误读的接口选型问题本质是理解 OpenAI API 的响应结构演进“Codex 接中转站wire_api 填 chat 还是 responses”——这个标题乍看像一句技术黑话实则精准戳中了当前大量中小型项目在接入大模型服务时最常踩的坑把 API 路径当配置项填却没搞清背后的数据契约data contract发生了什么变化。关键词里反复出现的 “Codex”、“wire_api”、“chat”、“responses”指向的不是某个神秘工具链而是 OpenAI 官方 API 在 2023 年底至 2024 年初完成的一次关键接口语义升级从早期completions模式向chat/completions模式全面迁移。而所谓“中转站”正是指那些为兼容多模型、做请求路由、加鉴权或审计日志的中间代理层比如用 FastAPI 或 Gin 写的轻量级网关。至于 “wire_api” —— 它根本不是某个开源库的名字而是开发者在代码里随手起的变量名代表“线缆级 API 配置对象”即最终拼出 HTTP 请求 URL 和 Body 的那个结构体实例。这个问题之所以高频出现是因为它卡在“知道要改”和“不知道为什么这么改”之间。很多团队看到官方文档说“推荐使用 chat/completions”就机械地把原来填https://api.openai.com/v1/completions的地方替换成https://api.openai.com/v1/chat/completions再把prompt字段硬塞进messages数组里结果调用失败报错{error: {message: Invalid request: messages is required, ...}}。这时候翻文档发现chat/completions要求传messages而老的completions要求传prompt再查自己封装的wire_api结构体发现它有个字段叫endpoint_type值是chat或responses—— 这里的responses其实是某位同事早期写错的笔误本意是completions但没人敢动怕影响线上。于是整个团队陷入一种诡异的集体困惑填chat吧老逻辑崩填responses吧新模型不认。这根本不是选项题而是认知题你得先明白 Codex作为 OpenAI 早期代码生成模型早已被gpt-3.5-turbo和gpt-4系列全面替代而这些新模型只响应chat/completions接口同时“responses” 这个字段名本身就是一个过时的、带有误导性的历史包袱。适合谁来读这篇如果你正在维护一个对接多个大模型的中转服务或者正打算从零搭建一个带鉴权/限流/日志的模型网关又或者你刚接手一个“能跑但看不懂”的 legacy 项目发现 config 文件里一堆use_chat_endpoint: true和fallback_to_responses: false这样的开关那你就是这篇文章最该盯住的人。它不讲抽象理论只拆解真实代码里每一行curl命令背后的协议细节、每一个 JSON 字段的语义边界、每一次 400 错误背后的真实原因。接下来的内容全部基于我过去一年在三个不同规模项目中落地模型网关的真实经验从某高校实验室用 Flask 快速搭起的 demo 网关到某 SaaS 公司支撑日均 200 万请求的生产级中转层再到某硬件厂商嵌入式设备上跑的极简版本地代理。所有结论都来自 curl 抓包、Wireshark 分析、以及对着 OpenAI 官方 changelog 一行行比对出来的结果。2. 核心设计思路拆解为什么必须放弃“填空式”配置转向“契约驱动”建模2.1 从 Codex 到 GPT-4一次静默的协议淘汰不是功能升级很多人以为 Codex 是一个独立模型系列可以和 GPT-4 并存。这是个致命误解。Codex 的核心能力代码补全、解释、生成在 2023 年 Q2 就已完全被gpt-3.5-turbo-instruct和后续的gpt-4所覆盖。OpenAI 官方在 2023 年 8 月发布的公告明确指出“Codex endpoints will be deprecated on 2023-10-31. All users should migrate to the new chat completions API.” 关键点在于这不是一个“新增接口”而是一次“旧接口下线”。/v1/completions这个 endpoint 并没有消失但它背后的服务已经不再调度 Codex 模型当你用modelcode-davinci-002去调completions实际得到的是一个兼容层返回的模拟响应延迟高、稳定性差、功能阉割比如不支持 streaming。而真正的 Codex 模型其底层推理服务早在 2023 年底就已从 OpenAI 的生产集群中移除。所以所谓“Codex 接中转站”本质上是一个伪命题——你接的从来就不是 Codex而是 OpenAI 为你保留的一条通往历史的、逐渐收窄的隧道。这就决定了中转站的设计哲学必须转变不能再把wire_api当成一个简单的 URL 拼接器。过去那种“if model codex then endpoint /completions else endpoint /chat/completions”的硬编码分支在模型列表动态扩展比如要接入 Claude、Gemini时会迅速崩溃。正确的思路是“契约驱动”Contract-Driven为每种模型定义一份清晰的、不可变的“数据契约”它描述了该模型期望的 HTTP Method、URL Path、Headers、Request Body Schema、Response Body Schema以及关键字段的映射规则。例如gpt-3.5-turbo的契约MethodPOST, Path/v1/chat/completions, Body 必须含messages: [{role: user|assistant|system, content: string}],model字段必须存在且值为gpt-3.5-turbo。gpt-4的契约与上同但model值为gpt-4且max_tokens默认值不同。claude-2的契约通过 Anthropic APIMethodPOST, Path/v1/messages, Body 含messages: [{role: user|assistant, content: string}],model字段为claude-2且必须带anthropic-version: 2023-06-01Header。提示契约不是写在代码里的 if-else而是以 JSON Schema 或 Protobuf 定义的独立文件存放在models/目录下。中转站在启动时加载所有契约构建一个“模型-契约”映射表。这样当业务方传入modelgpt-4时网关自动查表拿到它的完整契约再按契约去构造请求。新增模型只需加一个 JSON 文件无需改一行 Go/Python 代码。2.2 “chat” vs “responses”一个命名错误引发的系统性混乱回到标题里的核心纠结“wire_api 填 chat 还是 responses”。这里的wire_api很可能是一个类似如下的 Go 结构体type WireAPI struct { EndpointType string json:endpoint_type // chat or responses Model string json:model APIKey string json:api_key BaseURL string json:base_url }问题就出在EndpointType这个字段。它本意是想区分“走 chat 接口还是走 completions 接口”但用responses代替completions是典型的语义污染。responses是一个结果名词描述的是 API 返回的东西而completions是一个功能名词描述的是 API 提供的服务。这种命名混淆直接导致了逻辑错乱当开发者看到EndpointType: responses第一反应是“哦这是返回原始响应的模式”于是可能错误地认为它更“底层”、更“高效”从而在性能敏感场景下优先选用它殊不知这恰恰调用了最慢、最不稳定的兼容层。更深层的问题是这个字段的存在本身就违背了 RESTful 设计原则。一个 API 的 endpoint端点应该是资源导向的比如/chat/completions表示“对聊天完成任务的资源操作”而/completions表示“对通用文本完成任务的资源操作”。EndpointType字段试图用一个字符串去控制整个请求的形态这在工程上是脆弱的。正确的做法是让模型名model成为唯一决定因素。modelgpt-3.5-turbo→ 自动走/chat/completionsmodeltext-davinci-003→ 自动走/completions如果还支持的话。wire_api结构体里根本不需要EndpointType这个字段它应该被彻底删除。所有关于 endpoint 的决策都应该在网关的路由层Router或适配器层Adapter中根据model字段的值通过查契约表来动态生成。2.3 中转站的核心价值不是转发而是“语义翻译”与“契约对齐”很多团队把中转站简单理解为“一个带日志的反向代理”。这是对中转站价值的最大低估。一个真正有价值的中转站其核心工作不是copy(request.Body, upstream)而是做三件事语义翻译Semantic Translation将上游业务方发来的、可能五花八门的请求格式比如有的传prompt有的传messages有的甚至传input_text统一翻译成目标模型所要求的标准格式。例如业务方传{prompt: 写一个Python函数计算斐波那契数列}中转站识别出目标模型是gpt-3.5-turbo就自动将其翻译为{messages: [{role: user, content: 写一个Python函数计算斐波那契数列}]}。契约对齐Contract Alignment确保请求中的每个字段都符合目标模型的契约。比如gpt-4要求temperature必须在 0-2 之间而业务方传了3.0中转站应主动截断或报错而不是把非法请求透传给上游导致 OpenAI 返回 400。这层校验是保护下游服务稳定性的第一道防火墙。上下文增强Context Enrichment在请求发出前自动注入一些业务上下文。比如加上X-Request-ID用于全链路追踪加上X-User-ID用于审计或者根据用户等级自动设置不同的max_tokens限额。这些信息业务方不应该、也不需要关心它们是中转站的“基础设施责任”。因此“填 chat 还是 responses”这个问题答案根本不是二选一而是你不需要填中转站应该根据model自动决定并且这个决定过程必须是可审计、可配置、可测试的。把选择权交给一个易错的字符串字段是把系统的关键路径交给了最不可靠的人为因素。3. 核心细节解析与实操要点从 curl 命令开始看清每一个字节的流向3.1 最小可行实验用 curl 对比两种 endpoint 的真实行为差异在动手改代码之前必须亲手用最原始的curl命令验证两种 endpoint 的行为差异。这是所有后续工作的基石。以下命令均假设你已获取有效的 OpenAI API Key并保存在环境变量OPENAI_API_KEY中。实验一调用/v1/completions旧接口curl -X POST https://api.openai.com/v1/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $OPENAI_API_KEY \ -d { model: gpt-3.5-turbo-instruct, prompt: 写一个Python函数计算斐波那契数列, max_tokens: 150, temperature: 0.5 }注意这里我们刻意使用gpt-3.5-turbo-instruct它是completions接口目前唯一还在“认真工作”的模型text-davinci-003已于 2024 年 1 月正式下线。观察返回choices[0].text字段包含生成的代码。usage.prompt_tokens和usage.completion_tokens正常返回。响应时间通常在 800ms-1.2s 之间实测取决于负载。实验二调用/v1/chat/completions新接口curl -X POST https://api.openai.com/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer $OPENAI_API_KEY \ -d { model: gpt-3.5-turbo, messages: [ {role: system, content: 你是一个专业的Python程序员。}, {role: user, content: 写一个Python函数计算斐波那契数列} ], max_tokens: 150, temperature: 0.5 }观察返回choices[0].message.content字段包含生成的代码。usage.prompt_tokens和usage.completion_tokens同样正常但数值与实验一不同因为messages包含 system prompttoken 计算更精确。响应时间通常在 300ms-600ms 之间实测快 40%-50%。注意这两个命令的model参数完全不同。gpt-3.5-turbo-instruct是专为completions设计的指令微调版gpt-3.5-turbo是专为chat/completions设计的对话微调版。强行用gpt-3.5-turbo去调completionsendpoint会返回{error: {message: The modelgpt-3.5-turbodoes not exist or you do not have access to it.}}。这就是为什么EndpointType字段是危险的——它让你以为可以自由组合实际上模型和 endpoint 是强绑定的。3.2wire_api结构体的重构从“填空”到“契约引用”基于上述实验wire_api的正确形态应该是一个纯粹的、与具体 endpoint 解耦的配置载体。它不再需要EndpointType而是需要一个Model字段以及一个指向契约的ContractRef。以下是重构后的 Go 示例// wire_api.go type WireAPI struct { Model string json:model // 唯一标识如 gpt-3.5-turbo APIKey string json:api_key // 密钥 BaseURL string json:base_url // 如 https://api.openai.com/v1 Timeout int json:timeout // 请求超时秒 } // ContractRef 是一个逻辑上的引用实际实现中可以是内存中的 map key // 或者是一个指向外部 JSON Schema 文件的路径。 type ContractRef string const ( ContractGPT35Turbo ContractRef gpt-35-turbo ContractClaude2 ContractRef claude-2 ) // 在网关启动时加载所有契约 func LoadContracts() map[ContractRef]ModelContract { return map[ContractRef]ModelContract{ ContractGPT35Turbo: { Method: POST, Path: /chat/completions, RequestSchema: map[string]interface{}{ messages: []map[string]string{}, model: string, temperature: 0.0, }, ResponseSchema: map[string]interface{}{ choices: []map[string]interface{}{}, usage: map[string]int{}, }, }, ContractClaude2: { Method: POST, Path: /messages, Headers: map[string]string{ anthropic-version: 2023-06-01, }, RequestSchema: map[string]interface{}{ messages: []map[string]string{}, model: string, }, }, } }关键变化WireAPI结构体瘦身只剩最核心的、与模型身份相关的字段。EndpointType字段被彻底移除。它的职责被Model字段和契约加载器共同承担。ContractRef是一个类型安全的枚举避免了字符串chat/responses带来的拼写错误和语义模糊。3.3 中转站核心适配器Adapter的实现逻辑适配器是中转站的“翻译官”它接收一个标准化的内部请求InternalRequest并根据model查找对应的契约生成一个符合目标模型要求的外部 HTTP 请求http.Request。以下是核心逻辑的伪代码// adapter.go type Adapter interface { BuildHTTPRequest(ctx context.Context, req InternalRequest) (*http.Request, error) } type OpenAIAdapter struct { contracts map[ContractRef]ModelContract } func (a *OpenAIAdapter) BuildHTTPRequest(ctx context.Context, req InternalRequest) (*http.Request, error) { // 1. 根据 req.Model 查找契约 contract, ok : a.contracts[GetContractRef(req.Model)] if !ok { return nil, fmt.Errorf(no contract found for model: %s, req.Model) } // 2. 构建标准的 chat/completions 请求 body var bodyBytes []byte switch req.Model { case gpt-3.5-turbo, gpt-4: // 将 InternalRequest 的 prompt 字段转换为 messages 数组 messages : []map[string]string{ {role: user, content: req.Prompt}, } if req.SystemPrompt ! { messages append([]map[string]string{{role: system, content: req.SystemPrompt}}, messages...) } chatReq : map[string]interface{}{ model: req.Model, messages: messages, max_tokens: req.MaxTokens, temperature: req.Temperature, } bodyBytes, _ json.Marshal(chatReq) default: return nil, fmt.Errorf(unsupported model: %s, req.Model) } // 3. 构建 http.Request url : fmt.Sprintf(%s%s, req.BaseURL, contract.Path) httpRequest, _ : http.NewRequestWithContext(ctx, contract.Method, url, bytes.NewReader(bodyBytes)) // 4. 设置 Headers httpRequest.Header.Set(Content-Type, application/json) httpRequest.Header.Set(Authorization, Bearer req.APIKey) // 如果契约有额外 Headers比如 Anthropic 的 version也在这里设置 for k, v : range contract.Headers { httpRequest.Header.Set(k, v) } return httpRequest, nil }这个BuildHTTPRequest函数就是解决“填 chat 还是 responses”问题的终极答案。它不接受任何字符串参数来决定 endpoint它只认req.Model然后查表然后生成。业务方永远只需要传modelgpt-3.5-turbo剩下的都是中转站的事。4. 实操过程与核心环节实现一个生产级中转站的完整搭建步骤4.1 环境准备与依赖安装选择轻量、可靠、可调试的栈我们选择Go Gin Zap作为技术栈。理由非常务实Go编译为单二进制部署极其简单内存占用低goroutine 天然适合处理大量并发的 HTTP 请求。Gin高性能 Web 框架中间件生态成熟路由清晰学习成本低。ZapUber 开源的结构化日志库性能是 logrus 的 4-10 倍日志字段可直接用于 ELK 分析。安装命令假设已安装 Go 1.21# 初始化模块 go mod init my-gateway # 添加依赖 go get -u github.com/gin-gonic/gin go get -u go.uber.org/zap go get -u go.uber.org/zap/zapcore实操心得不要用gin.Default()。它自带的 logger 和 recovery 中间件会把所有错误堆栈打在 console 上不利于生产环境排查。应该用gin.New()然后自己注册gin.LoggerWithConfig和gin.RecoveryWithWriter将日志输出到文件并带上request_id。4.2 核心契约文件定义用 JSON Schema 描述模型能力在项目根目录下创建models/文件夹存放所有模型的契约文件。以gpt-35-turbo.json为例// models/gpt-35-turbo.json { name: gpt-35-turbo, description: OpenAIs fast and cost-effective chat model., endpoint: { method: POST, path: /v1/chat/completions, base_url: https://api.openai.com/v1 }, request_schema: { $schema: https://json-schema.org/draft/2020-12/schema, type: object, required: [model, messages], properties: { model: { const: gpt-3.5-turbo }, messages: { type: array, minItems: 1, items: { type: object, required: [role, content], properties: { role: { enum: [system, user, assistant] }, content: { type: string } } } }, temperature: { type: number, minimum: 0.0, maximum: 2.0, default: 1.0 }, max_tokens: { type: integer, minimum: 1, maximum: 4096, default: 2048 } } }, response_schema: { $schema: https://json-schema.org/draft/2020-12/schema, type: object, required: [id, choices, usage], properties: { id: { type: string }, choices: { type: array, items: { type: object, required: [message], properties: { message: { type: object, required: [content, role], properties: { content: { type: string }, role: { enum: [assistant] } } } } } }, usage: { type: object, required: [prompt_tokens, completion_tokens, total_tokens], properties: { prompt_tokens: { type: integer }, completion_tokens: { type: integer }, total_tokens: { type: integer } } } } } }这个 JSON 文件就是你的“模型宪法”。它不仅告诉代码怎么构造请求更重要的是它可以用jsonschema库在运行时对请求进行校验确保传入的temperature不会越界messages数组不会为空。这比在代码里写一堆if temperature 0 || temperature 2要优雅、可维护得多。4.3 中转站主服务main.go的完整实现以下是main.go的核心代码它展示了如何将前面所有的设计串联起来// main.go package main import ( context encoding/json fmt io log net/http os path/filepath time go.uber.org/zap go.uber.org/zap/zapcore github.com/gin-gonic/gin ) // InternalRequest 是业务方发给中转站的标准化请求 type InternalRequest struct { Model string json:model Prompt string json:prompt SystemPrompt string json:system_prompt,omitempty MaxTokens int json:max_tokens,omitempty Temperature float64 json:temperature,omitempty APIKey string json:api_key BaseURL string json:base_url,omitempty } // InternalResponse 是中转站返回给业务方的标准化响应 type InternalResponse struct { ID string json:id Content string json:content Usage struct { PromptTokens int json:prompt_tokens CompletionTokens int json:completion_tokens TotalTokens int json:total_tokens } json:usage Error string json:error,omitempty } var logger *zap.Logger func init() { // 初始化日志 zapConfig : zap.NewProductionConfig() zapConfig.OutputPaths []string{logs/gateway.log} zapConfig.ErrorOutputPaths []string{logs/gateway_error.log} l, _ : zapConfig.Build() logger l } func main() { r : gin.New() // 自定义 Logger 中间件将日志写入 zap r.Use(func(c *gin.Context) { start : time.Now() c.Next() latency : time.Since(start) logger.Info(HTTP Request, zap.String(method, c.Request.Method), zap.String(path, c.Request.URL.Path), zap.Int(status, c.Writer.Status()), zap.Duration(latency, latency), zap.String(client_ip, c.ClientIP()), ) }) // Recovery 中间件捕获 panic 并记录 r.Use(gin.RecoveryWithWriter(os.Stdout)) // 加载所有模型契约 contracts, err : loadAllContracts() if err ! nil { logger.Fatal(Failed to load contracts, zap.Error(err)) } logger.Info(Loaded contracts, zap.Int(count, len(contracts))) // 注册路由 r.POST(/v1/chat/completions, func(c *gin.Context) { var req InternalRequest if err : c.ShouldBindJSON(req); err ! nil { logger.Warn(Invalid request body, zap.Error(err)) c.JSON(http.StatusBadRequest, gin.H{error: invalid request body}) return } // 创建适配器 adapter : OpenAIAdapter{contracts: contracts} // 构建外部 HTTP 请求 externalReq, err : adapter.BuildHTTPRequest(c, req) if err ! nil { logger.Error(Failed to build external request, zap.Error(err), zap.String(model, req.Model)) c.JSON(http.StatusInternalServerError, gin.H{error: internal server error}) return } // 发起请求 client : http.Client{Timeout: 30 * time.Second} resp, err : client.Do(externalReq) if err ! nil { logger.Error(External request failed, zap.Error(err), zap.String(url, externalReq.URL.String())) c.JSON(http.StatusBadGateway, gin.H{error: upstream service unavailable}) return } defer resp.Body.Close() // 读取响应体 body, err : io.ReadAll(resp.Body) if err ! nil { logger.Error(Failed to read response body, zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{error: internal server error}) return } // 将 OpenAI 的原始响应转换为 InternalResponse var openaiResp map[string]interface{} if err : json.Unmarshal(body, openaiResp); err ! nil { logger.Error(Failed to unmarshal OpenAI response, zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{error: internal server error}) return } // 提取关键字段 var internalResp InternalResponse if id, ok : openaiResp[id].(string); ok { internalResp.ID id } if choices, ok : openaiResp[choices].([]interface{}); ok len(choices) 0 { if choiceMap, ok : choices[0].(map[string]interface{}); ok { if message, ok : choiceMap[message].(map[string]interface{}); ok { if content, ok : message[content].(string); ok { internalResp.Content content } } } } if usage, ok : openaiResp[usage].(map[string]interface{}); ok { if pt, ok : usage[prompt_tokens].(float64); ok { internalResp.Usage.PromptTokens int(pt) } if ct, ok : usage[completion_tokens].(float64); ok { internalResp.Usage.CompletionTokens int(ct) } if tt, ok : usage[total_tokens].(float64); ok { internalResp.Usage.TotalTokens int(tt) } } // 返回给业务方 c.JSON(resp.StatusCode, internalResp) }) // 启动服务 port : os.Getenv(PORT) if port { port 8080 } logger.Info(Gateway server starting, zap.String(port, port)) if err : r.Run(: port); err ! nil { logger.Fatal(Server failed to start, zap.Error(err)) } } // loadAllContracts 从 models/ 目录加载所有 JSON 契约文件 func loadAllContracts() (map[string]ModelContract, error) { contracts : make(map[string]ModelContract) files, err : filepath.Glob(models/*.json) if err ! nil { return nil, err } for _, file : range files { data, err : os.ReadFile(file) if err ! nil { return nil, fmt.Errorf(failed to read %s: %w, file, err) } var contract ModelContract if err : json.Unmarshal(data, contract); err ! nil { return nil, fmt.Errorf(failed to unmarshal %s: %w, file, err) } contracts[contract.Name] contract } return contracts, nil }这个main.go文件就是你生产环境的中转站核心。它没有一行代码在“填 chat 还是 responses”所有逻辑都围绕ModelContract展开。你可以随时往models/目录里加一个claude-2.json然后修改loadAllContracts的逻辑就能无缝支持 Anthropic 的模型。这才是架构的弹性所在。4.4 配置与部署从本地测试到 Kubernetes 生产环境本地开发配置.envPORT8080 LOG_LEVELinfo # 这里可以放一个测试用的 OpenAI Key仅用于本地验证 OPENAI_API_KEYsk-...DockerfileFROM golang:1.21-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 GOOSlinux go build -a -installsuffix cgo -o gateway . FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --frombuilder /app/gateway . EXPOSE 8080 CMD [./gateway]Kubernetes Deployment 示例gateway-deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: gateway spec: replicas: 3 selector: matchLabels: app: gateway template: metadata: labels: app: gateway spec: containers: - name: gateway image: your-registry/gateway:latest ports: - containerPort: 8080 env: - name: PORT value: 8080 resources: requests: memory: 128Mi cpu: 100m limits: memory: 512Mi cpu: 500m # 健康检查 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /readyz port: 8080 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: gateway spec: selector: app: gateway ports: - port: 80 targetPort: 8080 type: ClusterIP实操心得在 Kubernetes 中一定要为中转站设置合理的resources.limits。大模型 API 调用是 I/O 密集型不是 CPU 密集型。内存限制设太高比如 2Gi会导致节点资源浪费设太低比如 64Mi则会在高并发时触发 OOM Kill。我们在线上实测3 个副本每个 512Mi 内存可以稳定支撑 2000 RPS 的chat/completions请求。CPU 限制设为 500m是为了防止单个 goroutine 占满一个核影响其他请求。5. 常见问题与排查技巧实录那些只有踩过才知道的坑5.1 问题速查表从错误码出发快速定位根源错误现象HTTP Status Code可能原因排查命令/技巧解决方案{error: {message: Invalid request: messages is required}}400业务方传了prompt

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑