在这里插入图片描述

一、JSON:我们熟悉的"完整对象"

JSON(JavaScript Object Notation)是过去二十年里最成功的数据交换格式。一个典型的 JSON 文档长这样:

{
  "messages": [
    {"role": "user", "content": "你好"},
    {"role": "assistant", "content": "你好!有什么可以帮你的?"},
    {"role": "user", "content": "今天天气怎么样?"}
  ],
  "model": "claude-fable-5",
  "temperature": 0.7
}

JSON 的核心特征:完整、闭合、一次性解析。

  • 整个文件是一个合法的 JSON 值(对象、数组、字符串等)
  • 解析器必须读取全部内容才能得到完整的数据结构
  • 在内存中,它就是一棵完整的树

这对"配置"和"请求-响应"场景是天作之合——配置文件、API 请求体、一次性的结构化数据传递,JSON 都是不二之选。

但当一个 Agent 系统开始 “边跑边想、边说边做” 时,JSON 的"完整性约束"反而成了瓶颈。


二、JSONL:行级独立的流式格式

JSONL(JSON Lines)的规则简单得惊人:一行一个合法的 JSON 对象,行与行之间互不依赖。

{"type": "tool_use", "id": "toolu_01", "name": "read_file", "input": {"path": "config.yaml"}}
{"type": "tool_result", "id": "toolu_01", "content": "server: {port: 8080}"}
{"type": "thinking", "content": "配置文件读取成功,现在我需要解析端口号……"}
{"type": "text", "content": "你的应用监听在 8080 端口。"}

JSON vs JSONL 核心对比

维度 JSON JSONL
结构 单个完整的 JSON 值 每行一个独立 JSON 对象
解析方式 一次性全文解析 逐行解析,可流式处理
追加写入 需要重写整个文件 append 即可,无需改写已有内容
部分读取 不行——不完整则非法 可以随时截断,不破坏前面的行
内存占用 与文件大小成正比 只需当前行的内存
合法性验证 整体合法才算合法 逐行合法,坏行不影响好行
典型场景 API 请求/响应、配置文件 流式输出、日志、数据集、Agent 消息流

为什么 JSONL 特别适合"流"?

想象你在看直播弹幕——你不会等主播下播后才一次性收到所有弹幕,而是来一条看一条。Agent 的思考过程和工具调用也是如此:用户不应该等 Agent 完全跑完才看到结果,而应该实时看到每一步推理和行动

JSONL 就是为这种"来一条消费一条"的模式而生的。


三、Agent 场景中的 JSONL 实战

目前主流 AI Agent 系统对 JSONL 的使用已经渗透到几乎每一个环节。

3.1 Streaming API:一个请求,多条返回

当你调用 LLM 的 streaming API 时,服务端返回的不是一个完整的 JSON 响应,而是一系列 Server-Sent Events,每个 event 内部的数据就是一行"事实上的 JSONL"(通常是 data: {"choices": [...]} 的形式)。

OpenAI / Anthropic 的流式响应本质上就是 JSONL:

data: {"type": "text", "content": "我来"}
data: {"type": "text", "content": "帮你"}
data: {"type": "text", "content": "查一下。"}
data: {"type": "tool_use", "name": "search", "input": {"query": "..."}}
data: [DONE]

没有 JSONL 会怎样? 那你只能等模型完整生成完毕后才能看到第一个字——用户体验回到拨号上网时代。

3.2 工具调用追踪:Agent 的"黑匣子"

一个多步 Agent 的执行过程会产生大量事件:

type: "thinking"       → 模型在思考
type: "tool_use"       → 模型决定调用工具
type: "tool_result"    → 工具返回结果
type: "thinking"       → 模型分析工具结果
type: "tool_use"       → 再次调用工具
type: "text"           → 最终回复

这些事件天然就是 JSONL——时间有序、独立可读、增量产生

每个事件带上 timestampid,整个 Agent 执行过程就有了完整的审计日志:

{"ts": "2026-07-18T10:23:01.001Z", "type": "start", "agent": "research-agent", "task": "调研竞品定价"}
{"ts": "2026-07-18T10:23:01.500Z", "type": "tool_use", "tool": "web_search", "input": {"q": "SaaS 定价策略 2026"}}
{"ts": "2026-07-18T10:23:03.200Z", "type": "tool_result", "tool": "web_search", "output": "3 条结果", "tokens": 450}
{"ts": "2026-07-18T10:23:05.100Z", "type": "tool_use", "tool": "deep_read", "input": {"url": "https://..."}}
{"ts": "2026-07-18T10:23:08.300Z", "type": "tool_result", "tool": "deep_read", "output": "..."}

有了这样的 JSONL 日志,你可以:

  • 重放:从任意步骤重新执行 Agent
  • 审计:快速 grep 所有 tool_use 事件看 Agent 调用了哪些外部服务
  • 统计:流水线式分析延迟、Token 消耗、工具成功率——不需要加载整个日志到内存

3.3 微调数据集(Fine-tuning Data)

几乎所有主流 LLM 的微调数据格式都是 JSONL。以 OpenAI 的 fine-tuning 格式为例:

{"messages": [{"role": "system", "content": "你是客服助手"}, {"role": "user", "content": "如何退款?"}, {"role": "assistant", "content": "您可以在订单页面点击..."}]}
{"messages": [{"role": "system", "content": "你是客服助手"}, {"role": "user", "content": "物流多久到?"}, {"role": "assistant", "content": "顺丰发货,通常 2-3 天"}]}

为什么是 JSONL 而不是一个巨大的 JSON 数组?

  1. 数据量大——一个 fine-tuning 数据集可能有几十万到几百万条,单个 JSON 数组会撑爆内存和 IDE
  2. 流式上传和验证——逐行校验,一行格式错误不影响其他行
  3. Shuffle 和分片——可以用 shuf file.jsonl | head -10000 一行命令取子集
  4. 增量追加——新数据直接 >> 追加,不需要反序列化→合并→重新序列化

3.4 多 Agent 通信与消息队列

在复杂的多 Agent 系统中(比如 Claude Code 的 workflow 模式),不同 Agent 之间需要传递结构化消息。JSONL 文件作为轻量级消息队列非常实用:

# Agent A 写入任务
{"event": "task_dispatched", "task_id": "abc", "handler": "code-reviewer", "payload": {...}}

# Agent B 边处理边写结果
{"event": "review_started", "task_id": "abc"}
{"event": "finding", "task_id": "abc", "severity": "high", "description": "..."}
{"event": "review_complete", "task_id": "abc", "score": 0.85}

为什么不用数据库? 对于开发调试阶段和轻量级编排,JSONL 零依赖、零配置,tail -f 就是天然的消费者。生产环境当然要上 RabbitMQ / Kafka,但 JSONL 在原型阶段"不引入新基础设施"的价值不可忽视。

3.5 日志聚合与可观测性

AI Agent 系统天然是异步、多步、长时运行的。JSONL 日志让其可视化变得简单:

# 统计今天各 Agent 的工具调用次数
grep '"type":"tool_use"' agent-2026-07-18.jsonl | jq -r '.tool' | sort | uniq -c | sort -rn

# 找出所有报错的工具调用
grep '"type":"tool_error"' agent.jsonl | jq '{tool, error: .error, ts}'

# 统计 token 总消耗
grep '"type":"token_count"' agent.jsonl | jq '.tokens' | paste -sd+ | bc

Unix 管线和 jq 的生态系统就是 JSONL 最好的处理工具——几千行、几万行、甚至百万行,都可以用这套组合拳流式处理。


四、设计取舍:什么时候该用什么?

选 JSON 当:

  • API 的请求体和响应体(一次性传递完整信息)
  • 配置文件(package.jsontsconfig.jsonclaude_settings.json
  • 需要在对象内部进行复杂嵌套和交叉引用的数据
  • 一次性导出/导入的场景(数据量可控)
  • 需要 schema 校验时——整体校验比逐行校验更符合预期(“要么全对,要么全错”)

选 JSONL 当:

  • 流式输出——LLM 的 token-by-token 生成、Agent 的事件流
  • 大规模数据集——训练数据、评测数据、爬虫结果
  • 日志和审计追踪——Agent 执行过程、用户行为日志
  • 增量写入——需要频繁追加的时序数据
  • 管道处理——需要 grep | jq | awk 组合加工的文本流
  • 多进程协作——多个进程同时写同一个文件(每行写入是原子的)

一条实用原则

如果你的数据是"一张快照"——写完就不再变了——用 JSON。
如果你的数据是"一条河流"——持续产生、增量消费、可能需要随时截断——用 JSONL。


五、一个小技巧:在项目中同时使用两种格式

很多成熟项目是这样玩的:

project/
├── config.json          # 配置:用 JSON,一次性加载
├── prompts/
│   └── system.jsonl     # 提示词模板:用 JSONL,逐条复用
├── data/
│   ├── train.jsonl      # 训练数据:50万条,JSONL
│   └── test.jsonl       # 测试数据:1万条,JSONL
├── logs/
│   └── agent-2026-07-18.jsonl  # 运行时日志:JSONL
└── results/
    └── eval_report.json # 评估报告:用 JSON,方便阅读

规则就是:输出的消费者是谁,就选适合它的格式。

  • 人类阅读的报告 → JSON(或直接 Markdown)
  • 机器逐行消费 → JSONL
  • 一次性加载的元数据 → JSON

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐