兼容 openai 格式的 api 端点

接入与开发

讲清兼容 OpenAI 格式的端点工程落地:base_url 怎么写、路径怎么拼、超时并发降级日志怎么配,给一份能照着做的清单。

很多人接兼容端点时卡住的不是概念,而是把 base_url 写到哪一层、和谁拼接。写完发现 404、流式断、偶发超时,又不知道从哪查。这篇不讲协议原理,只给工程侧的做法:base_url 的填写与替换判断、请求层参数怎么设、失败怎么退、日志记什么、上线前怎么自测。

接兼容端点的坑,基本都不在协议理解上,而在落到代码那一层:地址写到哪儿、库会不会替你补路径、超时该分几段设、失败之后是重试还是换源。这些点任意一个没对齐,表现就是 404、流式半截断、偶发超时、账单莫名翻倍。这篇只讲工程侧能照着做的做法。

先说清一件事:所谓兼容端点,本质是别人按 OpenAI 那套请求和返回结构实现的接口。结构一样不代表细节一样。地址、鉴权头、参数支持范围、限流口径,各家都会有出入。所以别指望把域名一换就跑通,得按下面这几层逐个确认。

base_url 到底写到哪一层:先分清「域名根」和「带版本前缀」

这是最容易翻车的一步。同一个地址片段,不同客户端处理方式完全不同:有的库会自己补版本前缀和资源路径,有的原样拼接。所以在写入之前,先确认你用的 SDK、框架或可视化工具属于哪一类。判断方法很土但有效——去看它在 debug 日志里实际发出去的完整 URL,别去看文档怎么描述的。

不确定的时候用最保守的写法:只写域名,不加任何版本前缀和斜杠。先跑一次最小请求,报错就逐层往上加。反过来,如果你先写了一个带前缀的地址又跑不通,也要逐层往下减。核心是每次只改一层,别一次动两个变量。

  • 末尾斜杠:有和没有在某些库里是两个地址,报错时优先检查这一位
  • 版本前缀:写不写 /v1 取决于客户端会不会自己补,别两边都写
  • 路径整体替换:换供应商时把整段地址当一个字段替换,不要只改域名尾巴
  • 本地代理场景:如果你中间挂了转发层,转发规则的匹配表达式也要跟着改,否则请求根本没出去
  • 环境变量命名:不同库读的变量名不一样,用错名字会静默走默认地址,这种错最难查

路径拼接对不上会怎样:404 与 405 的分工

这两种错误码看着都是「没找到东西」,但排查方向完全不同。404 基本指向路径本身不对——多了前缀、少了一段、大小写不一致,或者 base_url 已经带了版本前缀,代码里又拼了一次。405 更常见的是地址对了但请求方法或者子路径不匹配,比如该用 POST 用了 GET,或者把对话路径写成了补全路径。

把这两个混成一个方向查,会浪费大量时间。正确顺序是先打印实际发出的完整 URL,再拿它跟供应商文档里的那条路径逐字符比。别靠记忆,逐字符。中间任何一处多一个斜杠、少一个斜杠,都可能让服务端匹配不上路由。

  1. 抓取实际 URL打开客户端 debug 日志或加一个请求拦截器,把 method、完整 URL、请求头键名打出来;这一步不确认,后面全是猜
  2. 复制到调试面板单独跑把完整 URL 和 body、鉴权头原样贴进 curl 或接口调试面板;能通说明是代码拼接问题,不通说明地址或鉴权本身有问题
  3. 逐层删减定位从完整地址往回收:先去版本前缀、再去末尾路径,每删一层跑一次,找到第一个能通的那一层就是正确写法
  4. 固定成模板把验证过的组合写进配置模板并加注释说明「本客户端会/不会自动补前缀」,下次接新端点直接套
现象大概率原因下一步动作
404,最小请求也不通地址层写错,前缀多了或少了打印实际 URL,逐层删减重试
404,只有一个接口 404该子路径拼写或大小写不对对着文档逐字符比路径
405方法或子路径不匹配确认是否 POST,确认是否用错接口
401 / 403鉴权头名字或 Key 本身问题换请求头键名重试,再换一把 Key 交叉验证
200 但返回体结构不对端点兼容程度不足,字段缺失换一个模型名或换端点对比返回结构

超时与重试:分层设置,别只设一个总超时

只设一个总超时是最常见的偷懒做法,也是流式请求被掐断的头号原因。连接超时和读取超时要分开设。连接阶段超时,通常说明域名解析、网络链路或者网关有问题,这种情况下重试往往有效;读取阶段超时,多半是模型排队或者生成太慢,重试只是把同样的等待再来一遍。

流式请求的总超时要给得比非流式宽。因为流式是把内容分多次吐出来的,任何一段卡住都会累计耗时,设太紧就会在最后一段没吐完的时候直接断掉,用户看到的就是半句话。非流式反而可以设紧一点,早失败早切换,体验更可控。

连接超时
建立连接这一段的上限,通常给得比较短,超了大概率是网络层问题
读取超时
连上之后等响应的上限,流式要放宽,非流式可以收紧
首字节时间
从发出到收到第一个字节的耗时,这个指标最能反映服务端是否在正常排队
重试退避
两次重试之间的等待,固定间隔容易把压力叠在同一时刻,建议加随机抖动
熔断阈值
连续失败到多少就暂停调用,防止一直用坏端点跑出错误结果

重试还有一条底线:只对明确的网络层、网关层失败开重试。参数错、鉴权错、模型名不存在这类业务性报错,重试多少次结果都一样,只会把日志刷烂、把账单堆高。哪些错误码允许重试,建议列一个白名单写在配置里,而不是按「只要失败就重试」处理。

并发控制:先量基线,再定队列

文档里写的上限是理论值,不是你这个账号在当前端点下的可用值。实际稳定并发跟账号等级、端点当前负载、请求长度都有关,只能自己测。测的时候别只压一个并发数,取几档从小往大压,看哪一档开始出现限流响应,比它低一档就是你当前的稳定基线。

定下基线之后再上队列。用信号量或固定大小的线程、协程池把并发压住,超出的进队列等待,而不是直接放出去。队列一定要有长度上限,满了就快速失败并记录,否则请求全堆在内存里,最后内存和账单一起爆。批量任务还要额外留提交间隔,批次大小和间隔同样用小规模压测来定。

  • 队列要有上限,满了返回明确错误,别无限堆积
  • 区分优先级:实时对话和离线批处理走不同队列,别互相挤
  • 按请求长度分池:长文本请求占用连接更久,混在一起会把短请求拖慢
  • 记录排队时长:这个指标能提前告诉你并发是不是快到顶了
  • 限流响应要单独计数,它是调并发数最直接的依据

失败降级与端点切换:把「换一个源」做成可配置

降级能不能用,取决于地址和模型名有没有从代码里抽出来。这两个值写死在代码里,切换就得发版,那降级基本等于没有。正确做法是放进配置,切换时只动配置,业务逻辑一行不改。多端点场景下建议维护一张端点表,而不是在代码里写一堆 if。

降级顺序也要提前定清楚。常见做法是先在同一个端点内换模型,再考虑跨端点换供应商。哪一步先走、什么错误码触发、最多往下走几层,都要写进配置。没有明确顺序的降级,出问题时你会看到请求在几个源之间来回跳,每个都失败,日志还看不出主线。

触发条件先做什么再做什么
单个模型不可用同端点换备用模型仍失败则跨端点
端点整体失败直接跨端点保留原端点探测,恢复后切回
高并发被限流降速并排队仍限流才换源,避免误判
长文本请求超时换承载更稳的端点缩短输入或分批提交
连续失败超阈值熔断并告警,暂停调用人工确认后再恢复

要注意降级不是无限兜底。如果所有源都失败,继续循环切换只会产生更多错误请求。设一个连续失败阈值,到了就熔断一段时间,同时发告警,等人确认。什么时候不适用降级?当错误其实是请求本身有问题——参数拼错、模型名写成字符串常量但拼错了——这种情况下换多少个源都一样。

日志里该记哪几个字段,让一次请求能复盘

日志的目的不是「记录发生过什么」,而是「事后能不能还原一次请求的完整链路」。所以关键字段要围绕这个目标来收。最重要的一条:记实际值,不要记配置值。配置热更或者降级切换之后,只有实际发出的地址和模型名能还原当时到底打了哪个源。

还要把耗时拆开记。连一个总耗时,看不出问题出在哪:可能是连接慢、可能是排队久、也可能是生成慢。拆成连接、首字节、总时长三段,看一眼就知道该往哪个方向查。按天汇总失败状态码分布和耗时分位值,比逐条翻日志快得多。

  • 请求 ID:贯穿客户端到服务端,是串联日志的主键
  • 实际完整地址:不是配置里的模板,是真正发出去的那条
  • 模型名与是否流式:两者共同决定预期行为,降级后尤其要记
  • 耗时三段:连接、首字节、总时长,缺一段就不好定位
  • 状态码与错误体摘要:错误体别整段记,摘要加关键字段就够了
  • 用量统计:输入输出各多少,用于对账和排查异常请求

日志量大的时候,别把所有请求都按全字段记。可以对成功请求抽样,失败请求全量记。这样既压得住存储成本,又不丢排查线索。具体保留多久、怎么归档,按你们自己的合规要求定。

接新端点前的一份自检清单

这套清单的顺序是有讲究的:先确认地址层,再确认错误处理,最后才压行为和降级。因为地址层没通,后面所有验证都没有意义;错误处理没做对,压测出来的数据也不可信。

  1. 地址层三连测最小非流式请求、流式请求、长文本请求各跑一次;前两个必须通,第三个要能区分是超时还是被限流,而不是笼统记一个失败
  2. 错误路径故意走一遍故意用错 Key、故意传一个不存在的模型名,各跑一次,确认代码能正确识别这两种错误并且不会盲目重试
  3. 并发摸底从小并发往大压,找到开始出现限流响应的那一档,把稳定基线记进配置
  4. 重试与去重验证构造一次超时重试,确认不会造成重复提交或重复扣费
  5. 手动触发一次降级把主端点配置成不可用,看请求是否按预期切到备用源,日志里能不能看出切了

最后补一句适用范围。上面这套做法针对的是标准对话类请求。如果端点支持的是向量、语音这一类,路径和参数结构会不一样,超时取值和并发表现也完全不同,得单独再摸一遍。别拿对话的基线直接套到别的接口上。

下一步各家端点的实际表现和限流口径不一样,地址模板只是起点。想省下逐个试的时间,可以去站内的端点与接口资料页看当前的对照信息和实测基线,再决定先接哪个。

常见问题

base_url 要不要带结尾的 /v1?

看你的客户端自己补不补。有的库只在 base_url 后面拼 chat/completions,那就必须带 /v1;有的会自己补版本段,带了就变 /v1/v1 直接 404。最稳的做法是先只写域名跑一次,报 404 再逐层加,别一次写死。

换一家供应商,为什么把域名换掉后还是报错?

很可能不是域名问题,而是路径前缀、模型名或鉴权头不一样。先把实际发出去的完整地址打印出来,跟新供应商文档给的路径逐字符对;再确认模型名是否仍存在。这三处对齐之前,换多少次域名都没用。

兼容端点老是偶发超时,该从哪下手?

先分清是连接超时还是读取超时。连接超时多半是网络链路,读取超时多半是排队或生成慢。分开设两个阈值,配合重试;如果集中在高峰时段,那大概率是并发超了,该做的是压并发加队列,而不是一直调大超时。

流式接口和非流式接口在配置上要注意什么不同?

最大的差别是超时。流式的总时长本来就长,阈值设太紧会在最后一段被掐断,看起来像「无故断流」;另外流式对中间代理的缓冲很敏感,如果经过自己的网关,确认它没有把响应整体缓存住。非流式反而可以设短一点,失败快速重试。

怎么判断问题出在我的代码还是端点本身?

用同一把 Key、同一个地址,绕过业务代码直接用命令行或调试面板发一次最小请求。通了说明端点没问题,往代码拼接、中间层和参数上查;不通就换端点或换 Key 再试。这一步能省掉大量来回猜的时间。

本文依据各平台公开文档与官方公告整理,只做中文归纳;具体做法、参数与限制以官方文档为准。