模型答到一半就停了:输出被截断的最小复现

发布时间:2026/10/14 11:48:29

模型答到一半就停了:输出被截断的最小复现 版权与内容来源声明本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容均在附表 A 中标注来源引用官方原文保持原样不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式也不对任何收益结果作承诺。转载请注明出处。一个做订单摘要的接口上线第二周开始出现一种很别扭的失败HTTP 状态码一直是 200可返回体里最后那个右大括号时不时就没了下游一解析整条链路断掉换个模型、把提示词重写三遍还是隔一阵少一个括号。翻日志翻不出东西因为从头到尾就没有一条报错——模型不是报错它是被砍断了而且是「正常返回」着被砍断的。这篇不复述原理。先用一段能跑的最小代码把「到底有没有被截断」这件事判出来再解释模型为什么不会主动告诉你它没写完最后给一组改了预算口径前后的对照外加一条什么时候该扩预算、什么时候该停手的线。一、先把一段正常回答喂进去看它在哪一步被砍断被砍断的位置不在你的解析层而在服务端的解码循环最后一步——你看到半个 JSON是这一步的结果不是它的原因。所以先别急着改提示词先学会问一句「它是怎么停的」。1.1 现场状态码 200返回体里少一个右大括号把同一段「把这张订单整理成 JSON」的输入反复打过去约十分之一的请求会返回一份体面的半成品字段排得整整齐齐items数组也开好了就是最后一个大括号没合上。注意它的形态——它不是乱码不是空字符串也不是明显的报错而是一段「看起来快写完了」的文本。这种失败最难查因为它长得太像成功。1.2 这一刀砍在生成不在传输第一反应通常是「网络断了」或「网关截断了」。但把同一请求换成长连接、缩短超时、去掉中间代理现象一样。真正的原因在于生成是一步一步往外吐 token 的服务端在吐到某个上限时把循环停掉然后把「已经吐出来的部分」当成一次成功响应返回。传输没丢东西是源头就少了一截。1.3 「回答多长」和「句子通不通」都当不了判据很多人第一反应是看长度短了就是被截断。这条判据立刻就会失效——有些任务本来就只要两行也有些被砍断的回答断在了一个完整的句号后面读起来毫无破绽。句子通顺就更不可靠了模型完全可能在一个漂亮的逗号处被停下。两个直觉判据都不成立所以下面的代码不看它们只看一个字段。二、把「是否被截断」这个判据跑成一段代码判据只有一句话看结束原因字段不看回答本身。OpenAI 的 Chat Completions 把它放在finish_reasonAnthropic 的 Messages 把它放在stop_reason两者语义一一对应。2.1 官方给的取值与语义先钉死OpenAI 官方 API 参考对finish_reason的定义是逐字这样的The reason the model stopped generating tokens. This will be stop if the model hit a natural stop point or a provided stop sequence, length if the maximum number of tokens specified in the request was reached, content_filter if content was omitted due to a flag from our content filters, tool_calls if the model called a tool, or function_call (deprecated) if the model called a function.这句话里最关键的是length那一支它的触发条件不是「回答太长」而是「达到了请求里指定的最大 token 数」。也就是说是不是被截断和你主观觉得长不长无关和你在请求里填了多少有关。2.2 一张表看两边口径 实测环境数据取自官方 API 参考文档截至 2026-10-14为文档核对而非运行时数据无本地依赖协议字段取值官方语义转述本文处置finish_reasonOpenAIstop命中自然停止点或给定的停止串可放行finish_reasonOpenAIlength达到请求里指定的最大 token 数需重试提高上限或分段finish_reasonOpenAItool_calls模型调用了工具需继续执行工具后再发一次finish_reasonOpenAIcontent_filter内容因过滤被省略按内容策略处理finish_reasonOpenAIfunction_call模型调用了函数已弃用需继续stop_reasonAnthropicend_turn模型自然完成响应可放行stop_reasonAnthropicmax_tokens达到你设的max_tokens上限提高上限或继续生成stop_reasonAnthropicstop_sequence命中了自定义停止串按约定处理stop_reasonAnthropictool_use模型正在调用工具需继续stop_reasonAnthropicpause_turn服务端工具循环到达迭代上限把助手内容发回以继续stop_reasonAnthropicrefusal模型拒绝响应读拒绝详情并在回退模型重试stop_reasonAnthropicmodel_context_window_exceeded填满了模型的上下文窗口视为已截断需重试出处OpenAI 开发者文档 Chat 参考与 Create chat completion、Anthropic《Stop reasons and fallback》链接见附表 A。取数日期2026-10-14。口径说明两边字段名不同、取值个数不同但「正常收尾」「触达上限」「要调工具」这三类控制流是齐的——这也是下面这段判定函数能同时吃两种返回体的原因。2.3 十几行代码把判决跑出来判定只做一件事把结束原因映射成一个控制流动作。字段缺失时一律不放过——因为「查不到原因」和「原因是正常」是两码事。DECISION{# 结束原因 → 该做什么length:需重试提高上限或分段禁止把半截结果写进下游,max_tokens:需重试提高上限或继续生成禁止把半截结果写进下游,stop:可放行正常收尾,end_turn:可放行正常收尾,stop_sequence:可放行命中自定义停止串按约定处理,tool_calls:需继续执行工具后把结果回填再发一次请求,tool_use:需继续执行工具后把结果回填再发一次请求,}defjudge(rec):rrec.get(finish_reason)orrec.get(stop_reason)ifnotr:# 字段缺失 ≠ 正常收尾return需重试结束原因字段缺失不能当作正常收尾returnDECISION.get(r,f需人工确认未知结束原因{r!r})forrecin[{id:r1,finish_reason:length},{id:r2,finish_reason:stop},{id:r3,finish_reason:tool_calls},{id:r4,stop_reason:max_tokens},{id:r5}]:print(f记录{rec[id]}结束原因{rec.get(finish_reason)orrec.get(stop_reason)!r}判决{judge(rec)}) 实测环境Python 3.13.12 / macOS / 仅标准库脚本 work/exp1014/exp01b.py真实输出原样粘贴未做任何修饰记录r1 结束原因length 判决需重试提高上限或分段禁止把半截结果写进下游 记录r2 结束原因stop 判决可放行正常收尾 记录r3 结束原因tool_calls 判决需继续执行工具后把结果回填再发一次请求 记录r4 结束原因max_tokens 判决需重试提高上限或继续生成禁止把半截结果写进下游 记录r5 结束原因None 判决需重试结束原因字段缺失不能当作正常收尾r1 和 r4 就是线上那个 bug 的两种拼法一个是 OpenAI 口径的length一个是 Anthropic 口径的max_tokens形态都是「半截 JSON」。判定函数把它们都归到「需重试」于是那半截东西根本进不了下游解析器。三、模型为什么不会告诉你它没写完模型没有「我还没写完」这个内部状态可上报——它每一刻做的事只有一件根据前面的 token 猜下一个 token。「到没到头」是外部循环决定的不是它的自我认知。3.1 停下来的原因是接口层记的账模型自己不记账接口层记。所以「为什么停」这件事被单独抽成一个字段由服务端填。Anthropic 的官方说明里讲得很直白成功的响应都会带stop_reason它和「请求处理失败」的错误不是一回事——错误表示这次请求没处理成而stop_reason是在告诉你「响应是怎么结束的」。3.2 状态码 200 只说明请求被处理了这就是那个接口能一直「绿」着的原因HTTP 200 描述的是请求侧的成败它完全不承诺生成侧是自然收尾还是被砍断。一条被截断的响应仍然是一次成功的调用。把 200 当通关信号等于把「链路上线了」当成「内容写完了」。3.3 更会骗人的是「空回答也可能是正常收尾」Anthropic 文档里还提到一种情况有时会返回一段几乎没有内容的响应文档描述为只有两个到三个 token而它的结束原因仍然是end_turn。反过来说end_turn不等于「一定给了完整内容」它只等于「模型认为自己这一轮说完了」。所以判据要按控制流用别顺势把它当成质量分。四、改掉预算口径前后两组对照实测口径错数字算得再准也没用。旧口径只盯着「正文要多少 token」忘了两件事结构化输出的收尾要单独留量输入会把输出额度一起吃掉。4.1 先算清一件事输入和输出共用同一个窗口上下文窗口是共享的输入多占一点留给输出的就少一点。所以正确的顺序是先量输入再用「窗口减输入」得到剩余额度最后一件事才是定输出预算。用官方分词器把三段不同文本量一遍这个账立刻显形。 实测环境Python 3.13.12 / macOS / tiktoken 0.14.0cl100k_base 编码脚本 work/exp1014/exp01a.py下列可独立运行importtiktoken enctiktoken.get_encoding(cl100k_base)# 官方 BPE 分词器WINDOW,NEED_OUT8192,2048# 上下文窗口 / 本轮需要的输出预算A(把一段中文技术说明喂给模型时字符数和 token 数完全不是一回事。一个汉字往往要占掉一到两个 token标点、换行、空格也都各自记账。如果按字符数去估输出长度预算会算少一大截。)B{name:张三,age:32,skills:[检索,重排,评测],note:这是结构化输出的一段示例}Cdef truncate(records):\n return [r for r in records if r[finish_reason] ! length]\nnlambdas:len(enc.encode(s))fork,sin((A,A),(B,B),(C,C)):print(f编码器cl100k_base 文本{k}字符数{len(s)}tokens{n(s)}剩余输出额度{WINDOW-n(s)})print(-*60)forcopiesin(60,72,73,90):tn(A*copies)print(f编码器cl100k_base 输入文本A×{copies}tokens{t}剩余输出额度{WINDOW-t}f需要输出{NEED_OUT}判定{够用ifWINDOW-tNEED_OUTelse溢出})真实输出原样粘贴未做任何修饰编码器cl100k_base 文本A 字符数92 tokens85 剩余输出额度8107 编码器cl100k_base 文本B 字符数70 tokens36 剩余输出额度8156 编码器cl100k_base 文本C 字符数89 tokens22 剩余输出额度8170 ------------------------------------------------------------ 编码器cl100k_base 输入文本A×60 tokens5100 剩余输出额度3092 需要输出2048 判定够用 编码器cl100k_base 输入文本A×72 tokens6120 剩余输出额度2072 需要输出2048 判定够用 编码器cl100k_base 输入文本A×73 tokens6205 剩余输出额度1987 需要输出2048 判定溢出 编码器cl100k_base 输入文本A×90 tokens7650 剩余输出额度542 需要输出2048 判定溢出三个数要盯住中文段落 92 个字符量出 85 个 tokenJSON 片段 70 个字符只有 36 个 token——字符数不是 token 数两者的比例随文本类型变。更关键的是下半段输出需求一直是 2048输入从 6120 涨到 6205判决就从「够用」翻成了「溢出」。这就是旧口径漏掉的那一刀你什么都没改只是输入长了 85 个 token输出就被挤没了。4.2 换个编码器别拿字符数当刻度官方分词器仓库 README 对这件事说得很朴素tiktoken 是面向 OpenAI 模型的快速 BPE 分词器BPE 是一种把文本转成 token 的方式并且给了一个经验刻度——「On average, in practice, each token corresponds to about 4 bytes.」注意这是平均意义上的约数不是换算公式中文、JSON、代码各走各的比例实测那三行就是证据。所以别再用「1 个字符≈1 个 token」这种刻度去估预算。4.3 两组口径摆在一起看口径max_tokens怎么定漏掉了什么同一段 JSON 任务的结果口径一只按正文估等于正文预期 token 数没给结构收尾留量右大括号被砍得到非法 JSON口径二正文估加固定余量但不管输入正文 200没校验「输入 输出 ≤ 窗口」输入一涨剩余额度不够偶发截断口径三本文建议预期正文上限 × 1.3 结构收尾预留两个约束都覆盖溢出在请求前就算出来不必等线上翻车口径三里的「×1.3」是给正文波动的缓冲「结构收尾预留」是给 JSON 的收尾括号、给 Markdown 表格的最后一行这种必须吐完才合法的尾部留的额度。结构化输出尤其不能省这一块正文少一句还能看JSON 少一个括号直接不可解析。而这一切之前先做一次减法输入 token 数 输出预算 ≤ 上下文窗口不满足就别发请求。这份资料里有什么如果你正在把这类生成接口接进业务链路需要一份讲清上下文预算与失败判定的参考配套资料里有 Agent 与 RAG 的完整梳理。放在资料包里扫码即可获取五、什么时候该扩预算、什么时候该停手结论先给触达上限就重试但重试前必须先算窗口否则你只是把溢出挪到下一次。5.1 判据与动作一张表收口现象判据动作结束原因是触达上限且「输入 新预算 ≤ 窗口」还有空间提高上限重试一次结束原因是触达上限但输入已占掉大半窗口空间不够分段拆成多次生成或先压缩输入反复提高上限结束原因仍是触达上限任务本身超过窗口停手改成流式续写或预处理分段别再加大数字正常收尾但正文比预期短很多不是截断别动预算问题在提示词或采样结束原因字段缺失无法判定按不可放行处理先补齐字段再谈优化这张表里有两条是最容易被写错的。第一条输入占掉大半窗口时继续调大max_tokens是无效动作因为剩余额度已经被输入锁定怎么加都超窗正确动作是分段或压缩输入。第二条反复触达上限说明任务粒度本身就不对这时候该停手改架构而不是把数字往上堆。5.2 一条能直接抄进代码的线把判定放进统一出口结束原因不是「正常收尾」也不是「继续生成」时一律不许把结果写进下游触达上限的记录进重试队列并且重试时按口径三重算一次预算。这条线的好处是它不依赖你对长文/短文的直觉判断只看字段所以不会因为换模型、换任务而失效。5.3 收尾先看字段再谈长度回到开头那个少一个括号的接口。它的问题从来不是提示词而是没人看那个结束原因字段——200 一路放行半截 JSON 一路往下流。给这件事一个可执行的判断只要结束原因是触达上限就必须重试并提高上限或分段只要字段缺失就必须按不可放行处理。至于「扩到多大」先算减法再定输入 token 数 输出预算 ≤ 上下文窗口算得过再发算不过先削输入别削收尾预留。这份资料里有什么本文的截断判定函数、分词预算脚本和两张官方取值表都归到了这套配套里。放在资料包里扫码即可获取附表 A本文引用事实与出处对照表事实出处本文位置finish_reason的取值与语义含length表示达到请求里指定的最大 token 数原文为「The reason the model stopped generating tokens. This will be stop if the model hit a natural stop point or a provided stop sequence, length if the maximum number of tokens specified in the request was reached…」OpenAI 开发者文档 Chat 参考developers.openai.com/api/reference/resources/chat原 platform.openai.com 地址 301 跳转至此第 2.1、2.2 节max_tokens是「chat completion 中可生成的最大 token 数」已弃用、改用max_completion_tokens与 o 系列模型不兼容max_completion_tokens是「一次补全可生成的 token 数上界包含可见输出与推理 token」OpenAI 开发者文档 Create chat completiondevelopers.openai.com/api/reference/resources/chat/subresources/completions/methods/create第 2.2 节、第 4.3 节stop_reason取值与语义end_turn、max_tokens、stop_sequence、tool_use、pause_turn、refusal、model_context_window_exceeded其中model_context_window_exceeded的处置是「将响应视为已截断」Anthropic 官方《Stop reasons and fallback》platform.claude.com/docs/en/build-with-claude/handling-stop-reasons第 2.2 节max_tokens原文为「The maximum number of tokens to generate before stopping. Note that our models may stop before reaching this maximum.」——只规定绝对上限模型可能提前停Anthropic Messages API 参考platform.claude.com/docs/en/api/messages第 2.2 节、第 4.3 节成功响应都带stop_reason它与「请求处理失败」的错误不是一回事Anthropic 官方《Stop reasons and fallback》同链接第 3.1 节有时会返回几乎无内容的响应文档描述为只有两个到三个 token结束原因仍为end_turnAnthropic 官方《Stop reasons and fallback》同链接第 3.3 节tiktoken 是面向 OpenAI 模型的快速 BPE 分词器原文含「On average, in practice, each token corresponds to about 4 bytes.」开源仓库 openai/tiktoken 的 READMEgithub.com/openai/tiktoken第 4.2 节本文实测92 字符中文段落 85 token70 字符 JSON 36 token89 字符代码 22 token窗口 8192 下输入 6120 token 时剩余 2072 尚可、输入 6205 token 时剩余 1987 对 2048 的输出需求即溢出本文实测Python 3.13.12 / macOS / tiktoken 0.14.0 / cl100k_base取数日期 2026-10-14第 4.1 节写在最后这篇用到的资料写这篇文章时把相关的官方文档和源码又翻了一遍顺手也整理了几份配套的东西大模型学习路线图从零基础到能自己动手做 Agent按阶段说明每一步该学什么、哪些可以先跳过《LangChain LangGraph MCP 智能体开发实战》视频课7 个模块从私有化部署、EmbeddingRAG 到 MCPAgent 全流程AI 大模型知识库在线可查Agent Skills 从入门到落地、Claude Skills 完全指南等专题按目录浏览即可640 套 AI 大模型行业报告 经典 PDF 书籍看行业落地案例和别人怎么做的时候用得上大模型零基础到精通教学视频跟着敲一遍比只读文档快得多资料是我自己整理的放在下面这个码上扫码即可获取添加时备注「AI」优先通过。资料按「先路线、再动手、最后查漏」的顺序整理好了建议先看学习路线那一份照着它挑一条适合自己当前基础的路径再往下看。
延伸阅读

更多相关文章

2026/10/14 11:48:29

SpringBoot情绪宣泄平台:技术实现与心理关怀结合

1. 项目概述:情绪宣泄平台的现实需求与技术选型 当代社会快节奏生活带来的心理压力问题日益突出,但传统心理咨询服务存在预约难、费用高、隐私顾虑等痛点。这个基于SpringBoot的情绪宣泄平台正是为解决这些痛点而生——它为用户提供了一个随时可访问的匿…

2026/10/14 11:48:29

芒格思维如何重塑AI安全部署:从认知冗余到逆向验证

1. 为什么“查理芒格之道”突然成了AI安全部署的隐性标尺? 最近在几个技术闭门会上,我听到不止一位架构师在讨论大模型上线前的风险评审时脱口而出:“这个得按查理芒格的标准过一遍。”起初我以为是调侃——毕竟芒格老爷子以投资哲学闻名&…

2026/10/14 11:43:26

Java集成阿里云短信服务实战指南

1. 项目概述在当今企业级应用开发中,短信验证码和通知功能已成为标配需求。作为Java开发者,我们经常需要在项目中快速集成短信发送能力。本教程将带你从零开始,通过阿里云短信服务API示例,完整实现Java项目的短信功能集成。我经历…

2026/10/14 13:09:11

OpenCV全景图片拼接实战:从特征匹配到单应矩阵的完整流程

简介:这是一份面向Python与OpenCV学习者的全景图片拼接实战项目包,解决多张连续图片合成宽视角全景图的问题。项目围绕特征检测、关键点匹配、透视校正与无缝融合等核心环节展开,适合计算机视觉入门和图像处理实操训练。压缩包共8个文件&…

2026/10/14 13:09:11

SpringBoot校园代购平台开发实战与架构设计

1. 项目背景与核心价值去年帮学弟调试毕业设计时,发现校园代购需求比想象中旺盛。特别是疫情期间,封闭管理的校区里经常能看到学生在群里求代购药品、日用品。这个基于SpringBoot的爱心互助代购网站,本质上构建了一个校园内的C2C服务交易平台…

2026/10/14 13:09:11

综合能源系统两阶段优化调度:分时电价与需求响应的MATLAB实现

1. 项目概述与核心问题解析1.1 标题背后到底在解决什么问题这个题目信息量很大。先说结论:它解决的核心问题,是在电价波动的基础上,让一个包含电、气、热等多种能源形式的系统,在保证安全稳定运行的前提下,通过两阶段优…

2026/10/14 13:09:11

多目标粒子群算法在冷热电联供系统优化中的应用

1. 项目背景与核心价值冷热电联供型综合能源系统(CCHP)是当前能源领域的热门研究方向。这类系统通过整合发电、制冷和供热功能,实现能源的梯级利用,能效通常比传统分供系统高出20%-30%。但在实际运行中,如何平衡经济性…

2026/10/14 13:09:11

机器人控制板PCBA厂怎么选?2026年高可靠追溯能力成核心门槛

1. 为什么“机器人控制板PCBA厂怎么选”这个问题在2026年突然变重了?去年底,我帮某高校实验室对接一款双臂协作机器人样机的量产转化,核心控制板是基于Xilinx Zynq UltraScale MPSoC设计的8层HDI板,带高速SerDes通道、多路千兆以太…

2026/10/14 13:04:10

开源数据恢复工具实战:从误删原理到TestDisk/PhotoRec/ddrescue操作指南

简介:这是一份完整的开源数据恢复软件源码包,面向数据恢复开发者、安全研究人员及希望深入学习底层恢复机制的进阶用户。资源聚焦误删恢复、分区修复、坏道读取等场景,清晰展示了存储介质扫描、文件系统分析、数据预览与安全恢复的完整流程。…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/13 17:09:18

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/14 2:32:55

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/14 0:00:33

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:33

macOS/iOS高精度计时:mach_absolute_time原理与实战

1. 项目概述:为什么mach_absolute_time是 macOS/iOS 时间测量的“黄金标准”在 macOS 和 iOS 开发中,我见过太多人用CFAbsoluteTimeGetCurrent()或NSDate.timeIntervalSinceReferenceDate做性能打点,结果在高精度场景下被系统时钟跳变、NTP 同…

2026/10/14 0:00:33

SCORM课件演示与测试全解析:从API通信到自动化验证

简介:这是一套遵循SCORM标准的交互式课件演示与测试资源,面向E-Learning课程开发者、LMS平台测试人员及在线教学从业者,用于验证课件在不同学习管理系统中的识别、传递与跟踪能力。压缩包共28个文件,约12.07MB,以swf动…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑