三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

DeepSeek API response_format: json_object 避坑指南

DeepSeek API response_format: json_object 避坑指南

把 AI 输出接进业务系统,最怕的就是"格式不固定"。DeepSeek 提供了response_format: json_object强制输出 JSON,但真正落地时坑不少。这篇把我踩过的坑一次说清。

一、先跑通:JSON 模式怎么开

curl https://api.deepseek.com/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DEEPSEEK_API_KEY" \ -d '{ "model": "deepseek-chat", "messages": [ {"role": "system", "content": "你是一个输出 JSON 的助手"}, {"role": "user", "content": "分析这句话的情感,输出 json"} ], "response_format": {"type": "json_object"} }'

返回结果里的choices[0].message.content就是一段 JSON 字符串。看着简单,下面每个坑都藏在这里。

二、坑1:prompt 里没有 "json" 这个词,直接翻车

这是 DeepSeek 官方文档特意强调、也最容易踩的坑。

现象:明明设置了response_format: {"type": "json_object"},模型却返回空内容,或者返回一段不带 JSON 的纯文本。

原因:DeepSeek 要求消息里必须出现 "json" 这个单词(不区分大小写),否则即使你声明了 response_format,模型也可能不认。

正例:

{"role": "system", "content": "你是一个输出 JSON 格式的助手"} {"role": "user", "content": "把下面这段文字转成 json 输出"}

反例(会翻车):

{"role": "system", "content": "你是一个结构化输出助手"} {"role": "user", "content": "分析这句话的情感"}

一句话记住:别用"结构化"代替"json",prompt 里老老实实写 json

三、坑2:max_tokens 太小,JSON 被腰斩

现象:返回的 JSON 明显不完整,结尾是{"name": "张三", "tags": ["Java",就没了,解析必抛异常。

原因:JSON 模式默认输出比纯文本长,字段名、引号、逗号、花括号都占 token。之前按纯文本习惯设的 max_tokens 不够。

解决:给足预算。单次结构化输出建议max_tokens至少 1024,字段多、内容长直接给 2048 或 4096。

四、坑3:模型爱给 JSON 套 markdown 代码块

现象content拿回来长这样:

```json {"name": "张三", "age": 30} ```

直接JSON.parse会报错。

解决:解析前先清洗,去掉 ``` 包裹和前后空白:

private String cleanJson(String content) { String s = content.trim(); if (s.startsWith("```")) { s = s.replaceFirst("```[a-zA-Z]*\\s*", ""); s = s.replaceFirst("```\\s*$", ""); } return s.trim(); }

五、坑4:字段类型漂移,数字变字符串

现象:同一个字段,这次返回"count": 3,下次返回"count": "3"。字段缺失也常见,这次有tags,下次没有。

原因:LLM 不保证类型稳定,尤其是没给示例时。

解决两条路:

  1. prompt 里给一个完整的输出示例,模型会照着抄:

输出示例:{"sentiment": "正面", "score": 0.9, "tags": ["服务", "价格"]}
  1. 拿到结果后做类型归一,读值时对类型做兜底处理。

实战建议:示例优先,兜底其次。示例能解决 90% 的类型漂移。

六、坑5:字符串值里夹了未转义的换行和引号

现象:让模型总结一段文本放进 JSON 字段,结果文本里的换行、双引号没转义,产出非法 JSON:

{"summary": "他说:"服务很好"。 体验不错。"}

这个 JSON 直接解析必挂。

原因:模型输出的是"看起来像 JSON"的文本,不是真正经过序列化的 JSON,特殊字符转义不可靠。

解决

  1. prompt 里明确要求:字符串内的换行请用 \n 表示,双引号请转义

  2. 解析失败时重试一次,把报错信息回喂给模型让它修正(这是最有效的兜底)

七、Java 侧稳健封装(可直接抄)

import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; ​ import java.util.HashMap; import java.util.List; import java.util.Map; ​ @Service @Slf4j public class DeepSeekJsonService { ​ private static final String API_URL = "https://api.deepseek.com/chat/completions"; private static final String API_KEY = "sk-xxxxxxxx"; ​ private final RestTemplate restTemplate = new RestTemplate(); ​ /** * 调用 DeepSeek 强制输出 JSON,返回清洗后的合法 JSON 字符串 */ public String chatForJson(String userPrompt) { Map<String, Object> body = new HashMap<>(); body.put("model", "deepseek-chat"); body.put("max_tokens", 2048); body.put("response_format", Map.of("type", "json_object")); body.put("messages", List.of( Map.of("role", "system", "content", "你是 JSON 输出助手,只输出合法 JSON,不要 markdown 代码块"), Map.of("role", "user", "content", userPrompt + " 请用 json 格式输出") )); ​ HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); headers.set("Authorization", "Bearer " + API_KEY); ​ String resp = restTemplate.postForObject( API_URL, new HttpEntity<>(body, headers), String.class); ​ JSONObject json = JSON.parseObject(resp); String content = json.getJSONArray("choices") .getJSONObject(0) .getJSONObject("message") .getString("content"); ​ return cleanJson(content); } ​ private String cleanJson(String content) { String s = content.trim(); if (s.startsWith("```")) { s = s.replaceFirst("```[a-zA-Z]*\\s*", ""); s = s.replaceFirst("```\\s*$", ""); } return s.trim(); } }

要点都封装进去了:

  • system + user 两个消息都带了 "json" 字样

  • max_tokens给到 2048 防截断

  • cleanJson统一去 markdown 包裹

八、总结

一句话解法
prompt 没有 "json"消息里老老实实写 json
JSON 被截断max_tokens 给足 2048
markdown 代码块包裹解析前 cleanJson 清洗
字段类型漂移prompt 给输出示例
特殊字符没转义明确转义要求 + 失败重试

结构化输出不是开了response_format就万事大吉,真正稳的是一套"提示词约束 + 清洗 + 兜底重试"的组合拳。


关于作者

独立开发者,主业 Java 后端。一个人用 SpringBoot + AI 交付过企业级管理平台和微信小程序,业余接外包。

  • 代码和架构图放 Gitee 了:https://gitee.com/yao113088/jiguang-dev

  • 微信/邮箱:luckluffy

顺手推荐

小程序"面试刷题狮"是我用 SpringBoot + DeepSeek 一个人做的 AI 面试刷题工具,本文的response_format: json_object就是它的核心实现。微信搜索"面试刷题狮"就能搜到,免费刷题 + AI 定制面试。

← 返回列表