尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Bug已解决】Expected file to have JSONL format, where every line is a JSON dictionary. openai…

【Bug已解决】Expected file to have JSONL format, where every line is a JSON dictionary. openai… 【Bug已解决】Expected file to have JSONL format, where every line is a JSON dictionary. openai createFile for fine tune 解决方案一、现象长什么样用 OpenAI 做微调时先把训练集上传到平台from openai import OpenAI client OpenAI() client.files.create(fileopen(train.jsonl, rb), purposefine-tune)结果立刻被服务端拒回Invalid file format. Expected file to have JSONL format, where every line is a JSON dictionary.或者更模糊一点openai.BadRequestError: ... expected each line to be a JSON object ...让人困惑的是文件明明是.jsonl后缀用文本编辑器看每行也像 JSON为什么 OpenAI 说不是每行都是 JSON 字典这背后的真正原因往往藏在肉眼看不见的细节里——末尾空行、字段里的真实换行、或是把整个文件写成了一个 JSON 数组。二、背景OpenAI 微调要求训练文件是JSONLJSON Lines即每行一个独立的 JSON 对象行与行之间用换行分隔整个文件不是一个大 JSON。服务端在上传后会逐行json.loads一旦某行解析失败或不是对象dict就抛出上面的错误。典型的合规单行长这样chat 微调格式{messages: [{role: system, content: 你是一个客服}, {role: user, content: 怎么退款}, {role: assistant, content: 请在订单页点击申请退款}]}注意三点整行必须能被json.loads解析成 dict不是一个 list也不是嵌套在数组里。字符串字段里不能出现真实的换行符。JSON 规范中字符串内的换行必须写成转义形式\n。如果你在 Python 里写line1\nline2然后手工拼字符串真实的换行会让这一行变成两行破坏 JSONL 结构。文件不能有末尾空行。很多人用write循环每行加\n最后多出一个空行那一行json.loads()直接失败。这三点是九成 Expected JSONL 报错的根。三、根因根因 A整个文件是 JSON 数组而非 JSONL。有人写出[{...},{...}]然后存成train.jsonl。服务端逐行解析第一行[不是合法 JSON 对象或把整个数组当一行但它是 list 不是 dict直接拒。根因 B某行字段里含有真实换行符。例如content来自一段多行文案里面是真\n0x0A 字符导致该行在物理上被拆成多行其中某一物理行不再是完整 JSON 对象解析失败。根因 C末尾空行 / 中间空行。文件最后多一个\n产生空行或数据里有空行json.loads()抛错。根因 D手动字符串拼接而非json.dumps。用 f-string 拼 JSON{messages: [...]}极易漏转义引号、换行得到非法 JSON。正确做法是用json.dumps它会自动转义换行和引号。根因 E编码 / BOM 问题。UTF-8 BOM 会让第一行开头多\ufeff偶尔干扰解析或文件不是 UTF-8。四、最小可运行复现复现末尾空行导致失败import json, io, tempfile, os # 错误写法每行加 \n最后多一个空行 bad for obj in [{messages: [{role: user, content: hi}]}]: bad json.dumps(obj, ensure_asciiFalse) \n bad \n # 末尾空行 path tempfile.mktemp(suffix.jsonl) with open(path, w, encodingutf-8) as f: f.write(bad) # 模拟服务端逐行校验 with open(path, encodingutf-8) as f: for i, line in enumerate(f, 1): line line.strip() if not line: print(f第 {i} 行是空行 - 触发 Expected JSONL 错误) continue try: json.loads(line) except json.JSONDecodeError as e: print(f第 {i} 行解析失败: {e}) os.remove(path)复现字段含真实换行raw 第一行\n第二行 # 真实换行字符 # 错误手工拼字符串换行未被转义 bad_line {content: raw } print(json.loads(bad_line)) # JSONDecodeError: Invalid control character五、解决方案第一层最小直接修复第一招用json.dumps生成每行绝不手工拼。import json records [ {messages: [ {role: system, content: 你是一个客服助手}, {role: user, content: 怎么退款}, {role: assistant, content: 请在订单页点击申请退款}, ]}, {messages: [ {role: user, content: 营业时间}, {role: assistant, content: 每天 9:00-21:00}, ]}, ] with open(train.jsonl, w, encodingutf-8) as f: for r in records: # json.dumps 会自动把字段里的真实换行转成 \n保证整行合法 f.write(json.dumps(r, ensure_asciiFalse) \n)json.dumps会替你转义所有内部换行和引号这是避免真实换行破坏 JSONL的根本手段。第二招去掉末尾空行。上面的写法每行都以\n结尾但最后没有额外的空行符合要求。如果你用列表writelines注意别多写一个空字符串。第三招上传前本地校验。def validate_jsonl(path): with open(path, encodingutf-8) as f: for i, line in enumerate(f, 1): s line.strip() if not s: raise ValueError(f第 {i} 行是空行) obj json.loads(s) # 解析失败会抛 JSONDecodeError if not isinstance(obj, dict): raise ValueError(f第 {i} 行不是 JSON 对象而是 {type(obj)}) validate_jsonl(train.jsonl) print(校验通过可以上传)第四招上传。from openai import OpenAI client OpenAI() resp client.files.create(fileopen(train.jsonl, rb), purposefine-tune) print(file_id:, resp.id)六、解决方案第二层结构化改进把记录结构、序列化、校验、路径收口成一个配置对象避免每次手工拼 JSON。from dataclasses import dataclass, field from typing import List, Dict, Any dataclass class OpenAIFineTuneFilePolicy: path: str train.jsonl encoding: str utf-8 ensure_ascii: bool False def make_messages(self, system: str, user: str, assistant: str) - Dict[str, Any]: return {messages: [ {role: system, content: system}, {role: user, content: user}, {role: assistant, content: assistant}, ]} def write(self, records: List[Dict[str, Any]]): with open(self.path, w, encodingself.encoding) as f: for r in records: # 唯一序列化入口杜绝手工拼接 f.write(json.dumps(r, ensure_asciiself.ensure_ascii) \n) def validate(self): import json as _json with open(self.path, encodingself.encoding) as f: for i, line in enumerate(f, 1): s line.strip() if not s: raise ValueError(f第 {i} 行空行) obj _json.loads(s) if not isinstance(obj, dict): raise ValueError(f第 {i} 行不是 dict) def upload(self): from openai import OpenAI client OpenAI() return client.files.create(fileopen(self.path, rb), purposefine-tune)用法policy OpenAIFineTuneFilePolicy(pathtrain.jsonl) policy.write([ policy.make_messages(客服, 怎么退款, 订单页点申请退款), policy.make_messages(客服, 营业时间, 9:00-21:00), ]) policy.validate() # 上传前本地把关 policy.upload()write是唯一序列化入口保证每行都经json.dumps转义validate在上传前复刻服务端校验逻辑把错误挡在createFile之前。七、解决方案第三层断言 / CI 守护把JSONL 合法性做成 pytest 断言集成到数据管线 CI提交训练数据前自动拦截。import json import pytest def _load_lines(path): with open(path, encodingutf-8) as f: return [ln.strip() for ln in f if ln.strip()] def test_no_blank_lines(policy): policy.write([policy.make_messages(s, u, a)]) lines _load_lines(policy.path) assert all(ln for ln in lines), 存在空行会触发 Expected JSONL 错误 def test_every_line_is_json_dict(policy): policy.write([policy.make_messages(s, u, a), policy.make_messages(s, u, b)]) for ln in _load_lines(policy.path): obj json.loads(ln) # 解析不出的行会抛错 assert isinstance(obj, dict) def test_inner_newline_escaped(policy): # 含真实换行的文本也必须能安全序列化且每行合法 rec policy.make_messages(s, 多行\n文本, 回复) policy.write([rec]) for ln in _load_lines(policy.path): obj json.loads(ln) # 若未转义这里会 JSONDecodeError assert \n not in ln[:-1] or \\n in ln # 真实换行必须被转义 def test_not_a_json_array(policy, tmp_path): # 反例整文件是数组必须被校验拒绝 bad tmp_path / bad.jsonl bad.write_text(json.dumps([{messages: []}]), encodingutf-8) with pytest.raises(json.JSONDecodeError): # 逐行解析时第一行 [ 不是合法对象 json.loads(bad.read_text(encodingutf-8).splitlines()[0])把这些断言挂到数据生成后的 CI 步骤能在调用client.files.create之前就发现空行、数组格式、未转义换行等问题。八、排查清单遇到 Expected file to have JSONL format, where every line is a JSON dictionary确认是 JSONL 不是 JSON 数组文件应是每行一个对象不要写成[{...},{...}]。用json.dumps写每行别手工 f-string 拼接它会自动转义内部换行和引号。检查末尾/中间空行多出的\n产生空行json.loads()失败删掉即可。字段里的真实换行必须转义多行文案里的\n让物理行数变多破坏 JSONL靠json.dumps解决。上传前本地复刻校验逐行strip()json.loadsisinstance(obj, dict)提前拦截。确认 UTF-8 无 BOMBOM 会污染第一行保存时选 UTF-8。用OpenAIFineTuneFilePolicy统一生成与校验从源头消除格式错误。九、小结OpenAI 微调上传文件报 Expected file to have JSONL format, where every line is a JSON dictionary根因几乎都是格式细节文件是 JSON 数组而非逐行对象、字段里藏了未转义的真实换行、或末尾多了一个空行。json.dumps是银弹——它自动转义换行与引号保证每行都是合法 JSON 对象再配上上传前逐行strip json.loads isinstance dict的本地校验就能在client.files.create之前拦下所有格式问题。用OpenAIFineTuneFilePolicy把序列化和校验收口为唯一入口这类报错从根上消失。至此 1000 篇任务收官。
返回列表