把 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 交付过企业级管理平台和微信小程序,业余接外包。

顺手推荐

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

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐