GLM Batch 任务失败怎么排查:从状态字段到错误文件

2026-08-25
站内工具 AI 编程工具报错分诊器 → 把报错原文贴进去,先分清是网络、额度、配置还是上游故障,再决定往哪个方向查。

数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。

GLM 的 Batch 排查,第一步不是看日志,是先确认你说的”失败”到底是哪一层。 官方文档把 Batch 任务的状态定义成一个明确的枚举,其中 failed 只有一个含义——文件没通过验证,任务压根没开始跑;而任务跑完之后个别请求出错,是另一套机制,走的是 Batch 对象里的 error_file_id,任务状态照样是 completed。这两层混在一起看,会出现”任务显示完成但我的结果少了一半”和”任务失败但账单上有消耗”这类看起来矛盾的现象。把状态枚举、request_counts 三个计数、以及 output_file_iderror_file_id 这两个文件分开读,绝大部分 Batch 问题都能定位到具体是哪一行请求、哪一个环节。

先分清两层失败:任务级和请求级

官方文档在”监控任务状态”一节给出了完整的状态表,含义各不相同:

状态官方描述
validating文件正在验证中,Batch 任务未开始
failed文件未通过验证
in_progress文件已成功验证,Batch 任务正在进行中
finalizingBatch 任务已完成,结果正在准备中
completedBatch 任务已完成,结果已准备好
expiredBatch 任务未能完成
cancellingBatch 任务正在取消中
cancelledBatch 任务已取消

这张表是枚举定义,不是行情数字,值得背下来。读它的时候有两个点特别容易看走眼。

一是 failed 的定义窄得超出很多人的预期:它对应的是文件未通过验证。也就是说,你看到 failed,问题几乎一定出在提交的那份 .jsonl 文件本身,而不是模型跑挂了。这时候去翻输出文件是白费力气——任务没进入执行阶段,官方文档也没有提到这种情况会产生输出文件。

二是 expiredcancelled 不是同一回事。cancelled 是你自己调取消接口的结果,expired 按官方原文是”Batch 任务未能完成”,属于系统侧的超期处理,后面单开一节说它的计费口径,因为那里有个容易被忽略的钱的问题。

至于任务顺利跑到 completed、但里面某些请求返回了错误,这一层根本不体现在 status 上。官方文档在下载结果那一步写得很直接:系统会对 Batch 结果文件分开保存output_file_id 保存成功执行请求的输出文件 ID,error_file_id 保存出现错误请求的输出文件 ID。所以”任务成功”和”每条请求都成功”是两件事,判断后者要看文件和计数,不能看状态。

提交前就会被拦下的几种情况

既然 failed 指向文件校验,那把校验规则清点一遍,比事后猜要快得多。官方文档”文件限制”一节列出的约束有这么几条,逐条都可能让文件过不了验证。

每个 batch 文件只能包含对单个模型的请求。 这一条最容易在工程里踩到——很多人写脚本时按业务把请求拼在一起,顺手让长文本走一个模型、短文本走另一个模型,塞进同一份 .jsonl。官方文档明确不允许这么干,要拆成多个文件分别提交。

每个请求必须包含 custom_id 且是唯一的,官方给出的用途是”用来将结果和输入进行匹配”。重复的 custom_id 不只是校验风险,即使通过了,你在结果文件里也没法把某一行对回原始输入。生成 custom_id 时别用可能碰撞的业务字段,序号或 UUID 更稳妥。

单个文件的请求条数和体积都有上限,向量模型的请求条数还有单独的、更严的限制。具体数值以官方文档当前版本为准,这里不抄——它是会调整的。工程上更靠谱的做法是把切分阈值做成配置项,而不是硬编码在打包脚本里。

文件上传时 purpose 必须标记为 batch 官方在创建任务的请求参数说明里写得很清楚:input_file_id 指向的输入文件必须是 .jsonl 格式,并且文件上传时的目的必须标记为 “batch”。Python 示例里对应的就是 client.files.create(file=..., purpose="batch") 这一行。至于 purpose 填错时报错会在哪一步抛出,官方文档里没有找到相关说明,别指望平台一定会在某个固定环节给出明确提示。工程上更稳的做法是把这个值收进上传封装函数里当常量,只留一个入口,不要每处调用各写各的字面量——这类字面量写错,往往是复制粘贴时顺手改错了模板。

endpoint 参数目前支持 /v4/chat/completions 这是创建 Batch 任务时的必填参数,官方参数表对它的说明是”Batch 中所有请求将使用的端点”,并注明目前支持 /v4/chat/completions

要把它和文件内部每一行的 url 字段区分开:endpoint 是创建任务时传的请求参数,url 写在 .jsonl 的每一行里。官方在”创建 Batch 文件”这一步里除了聊天补全,还单独给了 CogView-3 图像生成和 Embedding 向量化两个单条 JSON 示例,这两个示例里的 url 分别写作 /v4/images/generations/v4/embeddings;而下面那份完整的 .jsonl 文件示例,十行请求的 url 全部是 /v4/chat/completions。至于这两者之间该怎么配合——非聊天类的任务创建 Batch 时 endpoint 该传什么、行内 urlendpoint 是否允许不一致——官方文档里没有找到相关说明。所以照着示例改任务类型之前,先回官方文档当前版本确认你要跑的那类请求还在不在支持范围内,别按自己的推测去拼这两个字段,更不要把推测写进打包脚本的默认值里。

还有一条不在文件校验里、但会更早拦住你的:官方明确写了调用 Batch API 必须实名认证,需要先到实名认证页面完成个人认证或企业认证。如果账号从来没做过这一步,排查文件格式是找错了方向。

任务卡在某个状态不动,看哪几个字段

validatingin_progress 都是正常的中间态,问题在于你怎么知道它是在跑,还是已经僵住了。官方文档给出的 Batch 对象结构里,有一组时间戳字段专门用来回答这个问题:created_at(创建)、in_progress_at(开始处理)、finalizing_at(开始最终处理)、completed_at(完成),以及 failed_atexpired_atcancelling_atcancelled_at。它们都是 Unix 时间戳(秒)。

这组字段的用法是:created_at 有值而 in_progress_at 为空,说明任务还没排到;in_progress_at 有值但迟迟不出现 finalizing_at,说明正在执行。把这两个时间戳记进你自己的监控表,比反复打印 status 字符串信息量大得多。

进度则看 request_counts 这个对象,官方定义了三个计数:total(批处理中的请求总数)、completed(已成功完成的请求数量)、failed(失败的请求数量)。这里有个很实用的用法——total 可以和你本地打包时的行数对一下。两个数对不上,说明文件在打包环节就丢了行,而不是平台的问题。

另外,官方文档提到每个模型的 Batch 有最大排队限制,达到请求队列上限时,需要等待当前任务完成后再提交新任务。具体的队列上限数值以官方文档为准。所以任务长时间停在早期状态,还有一种可能是你自己的存量任务把队列占满了——用列出 Batch 任务的接口翻一遍在途任务,比盯着单个任务看更容易发现这个问题。

值得一提的是,completion_window 这个参数官方已经标为废弃,说明里写的是”原有的时间参数已不再适用,新的任务调度策略将根据系统负载情况自动调整”。如果你的代码还在传这个参数、或者还在按它的值做超时判断,逻辑已经过时了。任务的兜底时限以官方文档当前版本为准,别在代码里写死。

expired 的计费口径:这里最容易吃亏

单独说 expired,因为它牵涉钱。官方原文的说法是:如果 Batch 未能及时完成,该批次将被标记为过期状态;批次中未完成的请求将被取消;对于批次中已完成的请求,用户可以通过文件获取,并且需要支付这些请求消耗的费用

这句话有两层信息,两层都重要。

第一层是:过期不等于白跑一场。已经跑完的那部分结果还在,你依然能通过结果文件把它取回来,别看到 expired 就直接重跑整批——那等于把已经付过钱的部分再付一遍。正确的动作是先下载 output_file_id 对应的文件,用 custom_id 和原始输入做差集,只把差集里的请求重新打包提交。

第二层是:过期批次里已完成的请求要计费。所以如果账单上出现了一笔你以为”任务没成功所以不该收费”的消耗,expired 是排查方向之一。想把账单波动整体理清楚,可以顺着GLM API 报错排查API 成本监控怎么做这两篇一起看。

任务成功但结果不对:错误文件才是现场

拿到 completed 之后,官方给的下载步骤是用 Batch 对象里的 output_file_id 调 Files API 取内容。真正的排查现场在另一个文件里——error_file_id。官方 Python 示例里的写法是先判断 if batch_status.error_file_id: 再去取内容并写入本地,也就是说这个字段可能为空,为空说明没有出错的请求。

排查时按这个顺序走:

第一步,看计数。官方定义的三个计数里,failed 是”批处理中失败的请求数量”,它不为零就说明这一批确实有请求出错,接下来必须去下 error_file_id 那份文件。同时把 total 和你本地打包时的行数对一遍,这一步排的是打包环节丢行。需要说清楚的是:官方文档只定义了 totalcompletedfailed 三个计数各自的含义,并没有说明错误文件的行数与这三个计数之间存在什么数量关系。所以别在解析脚本里硬编码”totalcompleted 就等于错误文件行数”这类等式当断言用,用 failed 这个计数本身作判断依据更稳妥。

第二步,把错误文件按纯文本逐行读出来,先确认它长什么样。这里要专门提醒一句:官方文档给出的那份逐行示例是成功结果文件——示例每一行的 status_code 都是 200,每行带着 custom_id 和平台侧的 id错误文件具体是什么结构,官方文档里没有找到相关说明。所以写解析脚本时别预设错误文件的字段名,先原样打印出第一行看清结构,再决定怎么取字段,否则脚本会在取不到的键上直接抛异常,反而盖住了真正的错误信息。

确认结构之后,思路还是用 custom_id 回查。官方对 custom_id 的定义就是”用来将结果和输入进行匹配”,拿它对回原始输入的那一行,看看是不是某一类输入(比如超长文本、含特殊字符的内容)集中出错。集中出错更像数据问题,零散出错才更可能出在调用侧。

第三步,看结果结构。官方给出的成功结果示例里,每行的结构是 response.status_coderesponse.bodybody 里带 usagemodelchoicesrequest_id。所以判断单条请求成功与否,看的是这一行自己的 status_code,不是任务状态。写结果解析脚本时把这一层判断加上,比事后靠肉眼翻 jsonl 强。

还有一类不算”错误”但结果不合用的情况:模型把 JSON 包在代码块里返回。官方的结果示例中,content 字段的值确实带着代码块围栏。解析时要么做好剥离,要么在提交前就用结构化输出的方式约束格式,这方面可以参考GLM 结构化输出怎么保证格式不跑偏

文件层面的坑:删不掉和过期消失

Batch 的文件管理有两条限制,都可能表现成”莫名其妙提交不上去”或者”结果找不到了”。

一是上传文件数量有上限。官方文档在”删除文件”一节写的是:上传 Batch 文件时有次数上限,若任务量巨大,请及时删除已处理完毕的文件,以便继续上传新文件。具体上限数值以官方文档为准。跑长期批处理流水线的话,删除动作要做进流程里,不能等到传不上去了才手工清。删除有两条路径:到控制台的 Batch 数据页面删,或者调接口删,Python 示例是 client.files.delete(file_id=...)

二是数据保留期。官方明确说明系统只保留一段时间,过期后文件将自动删除、无法恢复,具体天数以官方文档当前版本为准。这一条的杀伤力在于它是静默的——你不会收到失败通知,只是某天回头取历史结果时发现文件不在了。结果文件下载后立刻落到自己的存储里,是唯一靠谱的做法。

创建任务时还有个相关参数 auto_delete_input_file,官方说明是”是否自动删除 batch 原始文件”,True 执行自动删除、False 保留原始 batch 文件。它的默认行为以官方文档当前版本为准。这个参数和排查的关系是:如果开了自动删除,任务出问题后你想回头核对当初到底提交了什么内容,平台侧已经没有那份文件了。所以在流水线还没稳定的阶段,本地务必自己留一份提交快照。

metadata 字段也值得用起来。官方定义它是”用于存储与 Batch 相关的数据,如客户 ID、描述或其他任务管理和跟踪所需的额外信息”,可附加的键值对数量、键长和值长都有上限(以官方文档为准)。把批次的业务标识写进去,事后在任务列表里定位会省很多事——列出接口支持 after 游标分页和 limit 控制返回数量,任务一多,没有 metadata 基本靠猜。

排查动作的推荐顺序

把上面几节压成一条可执行的路径:

先看 status。是 failed 就回去查文件——单文件单模型、custom_id 唯一、.jsonl 格式、上传时 purpose 是不是 batchendpoint 传的值对不对,以及账号有没有完成实名认证。是 expired 就先下结果文件把已完成部分捞回来,再做差集重提,不要整批重跑。是 completed 就转去看文件和计数,别再纠结状态。

然后看 request_countstotal 对不上本地行数,是打包环节的问题;failed 不为零,就去下 error_file_id

最后逐行看错误文件,用 custom_id 回查原始输入,判断是数据集中问题还是零散问题。

最容易栽的坑,还是开头那个:把 completed 当成”全部成功”,结果文件只下了 output_file_id 那一份,error_file_id 从来没人碰过。下游看到条数少了,第一反应是模型不稳定,其实错误信息一直躺在那个没人下载的文件里。把这两个文件都拉下来、把 status_code 的逐行判断写进解析脚本,是这件事上性价比最高的一次性投入。想把批量和其他省成本手段放在一起权衡,可以再看看缓存和批量接口怎么选

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。