大模型只吐字符串,凭什么你能拿到 JSON 对象?

系列第 1 篇。你可能遇到过这个场景:让大模型"返回 JSON",它确实乖乖回了 {"name":"爱因斯坦",...}------但你在代码里 result.name 却得到 undefined。问题出在哪?

先回答一个最底层的问题:大模型输出的到底是什么?

大模型是"打字机",不是"数据库"

大模型的本质是逐 token 预测 :根据已有文本,按概率预测下一个 token,拼成完整回复。token 解码出来就是字符------也就是字符串。

无论你用的是 OpenAI、DeepSeek 还是 LangChain,接口里那个 message.content 的类型永远是 string:

js 复制代码
// 你以为
response.content = { name: "爱因斯坦", birth_year: 1879 }   // ❌ 不存在

// 实际
response.content = '{\n  "name": "爱因斯坦",\n  "birth_year": 1879\n}'  // ✅ 字符串

模型内存里没有"对象"这个东西 。所谓"让它返回 JSON",只是让它吐出一段"长得像 JSON 的文本"。至于这段文本是不是真的符合 JSON 语法、有没有裹 ```````json````、有没有在开头啰嗦两句------它都不保证。

那为什么必须变成对象?

因为程序不读文本,程序读结构。

一段字符串 '{"name":"爱因斯坦"}' 对代码来说是一个"整体":你不能 content.name,不能遍历 content.fields,不能直接塞给数据库。只有经过 JSON.parse 变成内存里的对象,字段才可访问、可迭代、可被校验、可传走。

js 复制代码
const s = '{"name":"爱因斯坦"}';
s.name;              // undefined ------ 字符串上没有属性
JSON.parse(s).name;  // "爱因斯坦" ------ 对象上才有

"对象"是你和下游代码之间唯一可消费的形态。大模型的"回答"要变成你程序的"数据",中间必然要过一道 parse。

拦路虎:模型总爱把 JSON 裹进 markdown

你光会 JSON.parse 还不够。实际调大模型时,输出经常长这样:

json 复制代码
好的,以下是爱因斯坦的信息:
```json
{"name": "爱因斯坦", "birth_year": 1879}
ini 复制代码
模型把 JSON 裹进了 markdown 代码块,还带一句开场白。直接 `JSON.parse` 会报错。于是最常见的处理是**先用正则把代码块里的 JSON 抠出来再 parse**:

```js
const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/);
const jsonStr = jsonMatch ? jsonMatch[1] : content;  // 剥围栏,没有围栏就用原文
const result = JSON.parse(jsonStr);                  // 再 parse

这套"剥 markdown → JSON.parse"几乎是每次调大模型的固定动作------所以 LangChain 把它封装成了解析器。

JsonOutputParser:把上面的套路封装好

js 复制代码
import { JsonOutputParser } from "@langchain/core/output_parsers";

const parser = new JsonOutputParser();
const result = await parser.parse(response.content);
console.log(result.name);   // "爱因斯坦" ------ 直接可用

它内部做了什么?翻源码(@langchain/core 的 parseJsonMarkdown):

  1. trim 掉首尾空白;
  2. 找到第一个 ```````````,剥掉可选的 json 语言标注;
  3. 截到第二个 ``````````` 之间的内容;
  4. 交给 JSON.parse。

和你手写的正则殊途同归 ,只是它更宽容:不认 json 标注的裸围栏也能剥、只有一个围栏也能处理。而你手写版只认 ```````json```` 这一种。

一个容易踩的坑:它的格式说明是空的

很多教程会让你在 prompt 里拼上 parser.getFormatInstructions():

js 复制代码
const prompt = `
请介绍爱因斯坦的信息,包含 name、birth_year 等字段。
${parser.getFormatInstructions()}
`;

但对 JsonOutputParser 来说,这一行什么都没加 ------它的 getFormatInstructions() 返回的是空字符串 。源码注释说得很直白:JSON 太常见,不需要额外教模型。所以真正约束模型输出格式的,始终是你 prompt 里自己写的那句中文。这个解析器只负责"事后把模型交上来的东西宽容地解析",不负责"事前约束"。

小结

ini 复制代码
你写 prompt(文本)
   ↓
LLM = 打字机 → 永远输出 字符串(可能裹 markdown、夹废话)
   ↓
JsonOutputParser = 剥围栏 + JSON.parse → JS 对象
   ↓
result.name ✅
  • 大模型天生只吐字符串,"JSON"只是内容长得像 JSON 的文本;
  • 变成对象是你的 parse 完成的,不是模型送的;
  • JsonOutputParser = 剥围栏 + JSON.parse 的封装,宽容但不校验。

下一篇我们会看到它的短板:它不校验字段 。模型少给你一个字段、把 birth_year 写成字符串,它都一声不吭。要"讲清楚再验收",就该轮到 StructuredOutputParser 出场了。

相关推荐
1点东西4 天前
做了近两年的Agent开发,其实真正要学的就是这五件事
llm·agent·ai编程
程序猿编码4 天前
告别改源码适配模型:纯 C++ 可配置 LLM 推理引擎,全格式全结构兼容
c++·大模型·llm·推理引擎
小林ixn4 天前
从 LangChain 到 LangGraph:用「网状工作流」解锁多 Agent 协作的正确姿势
langchain·llm·agent
武子康4 天前
自己做一个 Mini Reviewer:让 AI 审到本次准备提交的代码
人工智能·llm·agent
武子康5 天前
GPU Pod 已经 Running,为什么扩容还没变成推理容量?
人工智能·llm·agent
BlackStar_L5 天前
第二章 上下文工程
大模型·llm·agent
Together_CZ5 天前
DeepSeek-V4.1-Flash:Pushing the Limits of KV Cache Compression——推动 KV 缓存压缩的极限
缓存·llm·compression·kv cache·deepseek·v4.1-flash·推动 kv 缓存压缩的极限
桃西西呀5 天前
你拍的一堆硬币,手机怎么一眼数出有几枚?聊聊边缘、轮廓和模板匹配
人工智能·llm·图像识别
slacker-kian5 天前
[实践]-让 SAP 工程 Skill 脱离 opencode跑在自定义Agent 上
ai·llm·sap·agent·abap·adt·opencode
武子康5 天前
CLAUDE.md 越写越长,哪些规则该放到子目录?
人工智能·llm·agent