ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

API 返回 200,为什么业务还是失败?

API 返回 200,为什么业务还是失败? 接口调通的那一刻通常很让人放松。控制台打印出200 OK响应体里也有一段文字。开发环境跑了三次都没报错于是任务被标记为“接入完成”。真正接进业务以后问题才陆续出现。客服机器人返回了半句话文档摘要看起来完整最后两页却没有处理模型说自己已经创建工单工单系统里什么也没有后端要求 JSON偶尔收到一段带代码围栏的 Markdown流式输出显示了大半段连接中断后前端仍然当成成功请求发生了三次自动重试用户只看到一个结果账单却记录了三次调用。这些情况都可能伴随 HTTP 200。HTTP 200 能证明服务器接收并处理了这次 HTTP 请求。它无法替你证明回答符合业务要求更无法证明工具执行、数据写入和后续流程已经完成。一套可上线的验收标准至少要回答七个问题。请求是否到达了正确的接口和模型模型返回的是可用答案、拒答还是一个待执行动作输出有没有被截断或提前结束返回格式能不能被程序稳定解析工具调用是否形成完整闭环流式传输是否完整结束出问题后能不能通过日志找到这一次请求本文把这七项拆成可以执行的测试。你可以拿它验收 Claude API、OpenAI 兼容接口也可以用于 Dify、n8n、Claude Code、自建 Agent 和客服机器人的上线评审。为什么 HTTP 成功不等于业务成功一次大模型调用通常经过四层。层级它回答的问题常见成功信号可能遗漏的问题网络层请求有没有送达DNS、TLS、连接正常网络中途断开、代理缓存异常HTTP 层服务是否接受请求HTTP 200响应体可能拒答、截断或为空模型层模型有没有完成生成有 content、stop reason内容不符合格式或任务目标业务层用户的任务有没有完成工单创建、数据落库、页面更新工具没执行、写入失败、状态没同步很多测试只覆盖了前两层。例如下面这段响应在 HTTP 层没有问题但它并不是一份完成的业务答案。json 复制代码{id:msg_xxx,content:[],stop_reason:refusal,usage:{input_tokens:1210,output_tokens:18}}拒答、达到输出上限、需要调用工具都可以通过正常响应返回。Anthropic 文档要求应用根据stop_reason分别处理end_turn、max_tokens、tool_use和refusal等状态。OpenAI 的 Responses API 也区分completed、incomplete、refusal、工具调用和错误事件。所以验收不能停在response.status_code 200。验收一确认接口、模型和响应身份第一项先排除“请求确实成功了但成功在错误的地方”。常见情况包括Base URL 指向测试环境业务以为自己在测生产环境配置的模型别名被更新返回模型和预期模型不一致fallback 生效后由备用模型完成请求但应用没有记录SDK 自动拼接/v1最终地址多了一层或少了一层多套 Key 混用费用记在了错误的项目或账号下。最小验收记录应该包括text 复制代码environment: staging base_url: https://gw.apito.ai/v1 requested_model:控制台当前可见模型 IDreturned_model:响应中的模型标识request_id:响应头或响应体中的请求 IDapi_key_alias: content-team-staging test_case_id: basic-text-001不要把完整 API Key 写进日志。保留用途别名和末四位已经足够定位配置。通过标准请求环境、Base URL 和 Key 用途明确请求模型与预期一致发生 fallback 时有记录每次测试都能取得可供客服或开发定位的 request ID日志中不保存明文 Key。验收二区分答案、拒答和待执行动作模型的返回内容不一定是普通文本。应用至少要识别三类结果。普通答案模型已经完成本轮生成可以继续进入格式校验和业务校验。拒答或安全回退接口可能返回成功状态但正文是拒答信息或者响应里出现refusal。这种结果不能当成空字符串重试否则容易制造重复请求和无意义账单。工具调用模型没有准备直接回答而是要求应用执行一个函数例如查询订单、创建工单或读取数据库。此时“有响应”只代表规划出了下一步业务动作还没发生。可以先在适配层统一成自己的状态枚举。python 复制代码from enumimportEnum class ResultType(str, Enum): ANSWERanswerREFUSALrefusalTOOL_CALLtool_callINCOMPLETEincompleteERRORerror不要让不同 Provider 的字段名直接散落在业务代码里。适配层负责把stop_reason、finish_reason、status和输出项转换成统一状态业务层只处理上面的五类结果。通过标准拒答能被单独识别并展示适当提示工具调用不会被误当成最终文本空内容会结合状态字段判断不会直接无限重试未知状态进入告警或人工检查而不是默认成功。验收三检查回答是否完整结束一段语法通顺的文字也可能被截断。最常见的信号是Anthropic 格式出现stop_reason: max_tokensOpenAI 兼容格式出现finish_reason: lengthResponses API 的状态为incomplete并在incomplete_details中说明原因预期 10 项实际只返回 7 项JSON 最后缺少右括号长代码停在函数或 Markdown 代码块中间。只检查字符数量不够。不同任务需要不同的完整性契约。任务建议完整性条件分类标签必须来自允许集合且只有一个结果摘要必须包含结论、风险、下一步三个字段列表数量满足约定或明确说明不足原因JSONSchema 校验通过必填字段齐全代码语法检查通过必要测试通过长文章节数量、收尾标记或字数范围符合约定一个简单的检查函数可以先挡住明显截断。python 复制代码def assert_completion(finish_reason: str|None, text: str)-None: incomplete_reasons{length,max_tokens,incomplete}iffinish_reasoninincomplete_reasons: raise ValueError(f模型输出未完成: {finish_reason})ifnot text or not text.strip(): raise ValueError(模型返回空内容)这只是底线。生产环境还要叠加任务自己的结构检查。通过标准截断状态不会进入正常业务流程应用能告诉用户“输出未完成”而不是展示半段结果补全策略有次数上限并保留前一次 request ID长任务有针对任务结构的完整性检查。验收四验证格式而不是相信提示词提示词写了“只返回 JSON”并不能替代 JSON 校验。模型可能返回text 复制代码下面是你需要的 JSON json{priority:high,reason:客户要求退款}人看没有障碍json.loads()会直接失败。另一些回答可以被解析却缺少必填字段或者把数字写成字符串。建议把校验分成三步。语法校验是不是合法 JSONSchema 校验字段、类型、枚举和必填项是否符合约定业务校验金额不能为负、订单号必须存在、分类必须来自当前业务词表。python 复制代码importjson from jsonschemaimportvalidate TICKET_SCHEMA{type:object,required:[category,priority,summary],properties:{category:{type:string,enum:[billing,technical,account,other]},priority:{type:string,enum:[low,medium,high]},summary:{type:string,minLength:1}},additionalProperties:False}def parse_ticket(raw_text: str)-dict: datajson.loads(raw_text)validate(instancedata,schemaTICKET_SCHEMA)returndata如果接口支持结构化输出或 JSON Schema应优先使用对应能力应用侧校验仍然要保留因为网络、适配层和后续代码都可能引入异常。通过标准100 次测试中格式成功率达到项目设定门槛必填字段、枚举和类型均由程序检查校验失败不会把原始模型文本直接写入数据库修复或重试有上限并记录失败样本。验收五工具调用必须走完闭环Agent 场景里最危险的误判是把“模型提出了工具调用”当成“工具已经执行成功”。一次完整工具调用至少有五步。模型返回工具名称和参数应用验证工具是否在允许列表应用校验参数并执行工具工具返回结果应用把结果交回模型模型基于工具结果给出最终回答业务再核对系统状态。任何一步失败HTTP 请求仍可能是 200。例如模型生成了json 复制代码{name:create_refund_ticket,arguments:{order_id:A1024,reason:duplicate_charge}}这段 JSON 只能证明模型希望调用create_refund_ticket。工单是否创建要看工具执行结果和工单系统里的真实记录。工具验收要覆盖下面几组失败样本。不存在的工具名缺少必填参数参数类型错误工具超时工具返回 403、404、409 或 500同一个工具调用被重复执行工具成功但最终回答描述错误高风险动作没有经过人工确认。对于写入、退款、发消息、删除文件等动作应使用幂等键或业务唯一键避免网络重试造成重复执行。通过标准工具名称和参数都有白名单与 Schema 校验工具执行结果和模型最终回答能通过同一 trace 关联写操作具备幂等保护高风险动作有人工确认或明确权限边界工具失败不会被模型包装成“已经完成”。验收六流式输出必须收到完整结束信号流式接口最容易制造一种假象屏幕上已经出现很多字看起来任务成功了。SSE 连接中途断开时前端可能保留已经收到的片段。如果应用没有等待完成事件也没有记录结束原因这段半成品就会被保存或展示。流式验收至少要验证是否收到明确的完成事件chunk 顺序是否正确UTF-8 中文是否出现断字或乱码工具参数分片能否正确拼接客户端主动取消后服务端和账单状态如何记录中途断线时是否把结果标记为 incomplete最终 usage 是否收到没收到时怎样记账。OpenAI 文档特别提醒流式连接中断后客户端可能收不到包含最终 token 用量的尾部数据。Anthropic 的流式响应也会在不同事件阶段逐步提供消息、内容块和停止原因。因此不能把“收到第一个 chunk”当作成功也不能把“连接关闭”一律当作正常完成。建议在前端状态里显式区分text 复制代码idle -connecting -streaming -completed|-incomplete|-cancelled|-failed通过标准只有收到完成事件才把结果标为 completed断线、取消和错误有不同状态工具参数分片能够重组并通过 JSON 校验未取得最终 usage 时有明确标记不伪造 token 数据用户可以重试但系统不会把旧半成品和新结果拼在一起。验收七日志、用量和错误必须能对上前六项解决“结果能不能用”第七项解决“坏了以后能不能查”。一条有排障价值的调用记录至少包含字段用途trace_id串起用户请求、模型调用和工具执行request_id与 API 服务侧记录对应test_case_id知道是哪条验收用例失败requested_model记录应用请求的模型served_model/provider记录实际服务路径若接口提供started_at/completed_at计算总耗时与超时位置first_token_at计算首 Token 延迟finish_reason/status区分正常、截断、拒答和工具调用input/output tokens核对成本与异常输出retry_count识别一次任务背后的重复调用tool_call_count识别 Agent 循环与工具成本validation_result记录格式和业务校验结果日志不要保存完整 Key、身份证号、手机号、客户原文和内部凭证。需要复现输入时可以保存脱敏副本、内容哈希或经过权限控制的短期样本。通过标准任意一条用户反馈都能定位到模型 request ID请求次数、重试次数和账单用量可以核对模型调用、工具调用和业务写入共用 trace日志已脱敏并有保存周期和访问权限告警按失败类型分类不把所有问题都写成“模型异常”。一张可以直接复制的上线验收表下面这张表适合放进 PRD、测试用例或上线评审单。markdown 复制代码# 大模型 API 接入验收单项目 环境开发 / 测试 / 生产 负责人 验收日期 Base URL 请求模型 测试用例版本## 1. 接口与身份-[]Base URL 和环境正确 -[]请求模型与返回模型已记录 -[]Key 使用别名管理日志无明文 Key -[]每次请求可以取得 request ID## 2. 响应语义-[]普通答案可识别 -[]refusal 可识别并给出用户提示 -[]tool call 不会被当成最终答案 -[]未知状态进入告警## 3. 输出完整性-[]length / max_tokens / incomplete 会被拦截 -[]空输出不会进入业务流程 -[]列表、章节或代码有任务级完整性检查 -[]补全与重试次数有上限## 4. 格式校验-[]JSON 语法校验通过 -[]Schema 校验通过 -[]业务字段校验通过 -[]失败样本已归档## 5. 工具调用-[]工具名和参数有白名单 -[]写操作有幂等保护 -[]工具结果与最终回答可以关联 -[]高风险动作需要确认## 6. 流式输出-[]收到完成事件才标记成功 -[]断线、取消、失败分别处理 -[]中文和工具参数分片可以正确重组 -[]缺失最终 usage 时有明确标记## 7. 日志与成本-[]trace_id 串起模型、工具和业务动作 -[]request ID 可用于排障 -[]retry_count 和 tool_call_count 已记录 -[]Token 用量与账单可抽样核对 -[]日志脱敏、权限和保存周期已确认测试时不要只用“你好”一句“你好”适合验证连接不适合验收业务。至少准备下面七类样本每类保留预期结果。测试组示例主要验证项最短正常请求把一句话归类基础响应与模型身份超长输入接近项目允许的上下文上限截断、超时和成本超长输出生成多章节报告max_tokens、流式完整性格式任务严格 JSON、固定枚举Schema 稳定性工具成功查询一条测试订单工具闭环工具失败查询不存在订单、模拟超时错误传递与重试边界请求可能触发拒答的合规测试语句refusal 处理每条用例不要只跑一次。温度、路由、负载和模型更新都可能带来波动。核心用例建议重复执行并记录成功率、P50/P95 延迟和格式通过率。建议采用分层上线而不是一次切满流量验收通过后也不要立即让全部真实请求进入新链路。比较稳妥的上线顺序是影子测试复制脱敏请求到新链路不把结果返回用户内部灰度只对员工或测试账号开放小流量灰度按用户、项目或百分比分流扩大流量观察错误率、格式失败率、工具失败率和成本保留回退达到预设阈值时切回旧链路。回退条件也要写成数字。例如格式通过率低于 99%、工具调用成功率低于 98%、P95 延迟超过业务阈值或单位任务成本超过预算时停止扩量。具体阈值由业务风险决定不能从别人的文章里照抄。结语HTTP 200 是一张网络层回执。用户要的是完整答案、正确数据和已经执行的业务动作。两者之间隔着状态识别、完整性检查、格式验证、工具执行、流式结束和日志追踪。团队把这七项写进验收单以后很多“偶尔失败”“模型又抽风了”“明明调用成功”的模糊问题会变成一条可以定位、复现和修复的测试记录。FAQ1. HTTP 200 到底代表什么它代表这次 HTTP 请求被服务端成功接收并返回响应。它不保证模型答案完整、格式合规、工具已经执行也不保证业务数据库已经写入成功。2. API 返回 200但 content 是空的应该直接重试吗不要立即重试。先检查stop_reason、finish_reason、status和输出项。空内容可能对应拒答、工具调用、输出不完整或接口错误。只有明确判断为可重试故障后再重试并设置次数上限。3.finish_reason: length或stop_reason: max_tokens算成功吗网络请求成功但输出没有完整生成。应用应将其标为 incomplete再根据任务决定提高输出上限、压缩输入、继续生成或提示用户重新执行。4. 提示词要求只返回 JSON还需要 Schema 校验吗需要。提示词是一项生成约束Schema 是程序验收。模型仍可能添加解释文字、漏字段或返回错误类型进入数据库或触发工具前必须校验。5. 模型返回了 tool call能不能认为任务完成不能。tool call 只是模型提出的动作。应用还要校验参数、执行工具、处理工具结果并核对真实业务状态。涉及写入和付款等动作时还要加入幂等和人工确认。6. 流式输出已经显示了一大段为什么还要等待完成事件因为连接可能在任何一个 chunk 后中断。没有完成事件和停止原因应用无法判断收到的是完整答案还是半成品也可能拿不到最终用量数据。参考资料AnthropicHandling stop reasonsAnthropicErrorsAnthropicStreaming MessagesOpenAIStreaming eventsOpenAIFunction callingOpenAIStructured Outputs
返回列表