Claude JSON 稳定输出:Schema 校验与修复回路(Kotlin)

1)先立规矩:你要的不是"像 JSON",而是"只输出 JSON"

最容易失败的提示词长这样:

请用 JSON 输出,并解释一下原因

这会让模型同时做两件事:结构化 + 自由文本。线上一定会有人撞坑。

建议你把底线写死(短、硬、可检查):

  • 只允许输出一个 JSON 对象(或数组)
  • 不允许 Markdown 代码块
  • 不允许额外文本
  • 信息不足时输出 { "error": "...", "need": [...] }

2)提示词模板:schema + 规则 + 唯一出口

2.1 最小模板(低风险任务)

text 复制代码
你必须只输出一个 JSON 对象,不要输出任何额外文本,也不要使用 Markdown。
信息不足的字段写 null。

schema:
{
  "intent": "string",
  "priority": "low|medium|high",
  "summary": "string",
  "tags": ["string"]
}

2.2 更稳模板(推荐):加规则 + 唯一出口

text 复制代码
你必须只输出 JSON(不要 Markdown,不要解释)。
必须满足:
1) 必须包含字段:intent, priority, summary, tags
2) priority 只能是 low/medium/high
3) tags 必须是数组,最多 5 个
4) summary <= 60 字

如果无法满足规则,请只输出:
{ "error": "原因", "need": ["缺少的信息"] }

你会发现:"唯一出口"非常重要。它能把模型想说的废话收敛到结构里。


3)修复回路:解析失败别硬扛,给自己一个稳定的 fallback

生产里我一般按三层兜底:

  1. 直接解析(成功就返回)
  2. 轻量净化 (截取第一个 { 到最后一个 }
  3. 二次修复(把失败样本喂回模型,让它只输出最终 JSON)

如果你没有第 3 步,线上总会出现"某些输入必炸"的角落案例。


4)Kotlin 实战:kotlinx.serialization + 规则校验 + 净化截取

4.1 Gradle 依赖

kotlin 复制代码
dependencies {
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
}

4.2 数据模型(包含 error/need 兜底字段)

kotlin 复制代码
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json

@Serializable
data class Ticket(
  val intent: String? = null,
  val priority: Priority? = null,
  val summary: String? = null,
  val tags: List<String> = emptyList(),
  val error: String? = null,
  val need: List<String> = emptyList()
)

@Serializable
enum class Priority {
  @SerialName("low") LOW,
  @SerialName("medium") MEDIUM,
  @SerialName("high") HIGH
}

private val json = Json {
  ignoreUnknownKeys = true
  isLenient = true
  explicitNulls = false
}

4.3 净化:截取 JSON 主体

kotlin 复制代码
fun sanitizeToJsonObject(raw: String): String {
  val start = raw.indexOf('{')
  val end = raw.lastIndexOf('}')
  if (start < 0 || end <= start) return raw.trim()
  return raw.substring(start, end + 1).trim()
}

4.4 解析 + 规则校验

kotlin 复制代码
data class Validation(val ok: Boolean, val message: String)

fun validate(t: Ticket): Validation {
  if (t.error != null) return Validation(true, "error-mode")
  if (t.intent.isNullOrBlank()) return Validation(false, "intent missing")
  if (t.priority == null) return Validation(false, "priority missing")
  if (t.summary.isNullOrBlank()) return Validation(false, "summary missing")
  if (t.tags.size > 5) return Validation(false, "too many tags")
  if (t.summary.length > 60) return Validation(false, "summary too long")
  return Validation(true, "ok")
}

fun parseTicket(raw: String): Ticket? =
  runCatching { json.decodeFromString(Ticket.serializer(), raw.trim()) }.getOrNull()

fun parseWithFallback(raw: String): Ticket? {
  parseTicket(raw)?.let { return it }
  val sanitized = sanitizeToJsonObject(raw)
  return parseTicket(sanitized)
}

你现在已经能处理 80% 的"夹文本/代码块"问题。剩下那 20%,需要二次修复回路。


5)二次修复回路(推荐):把失败样本再喂回 Claude

思路很简单:当 parseWithFallback() 仍失败时,触发一次修复调用:

修复提示词(示例):

text 复制代码
下面是一段"可能包含多余文字"的输出。请你只输出一个合法 JSON,并且必须符合 schema:
{ ... }
禁止 Markdown,禁止解释。

原始输出:
<<<
{原文粘贴}
>>>

这一轮通常用 Sonnet 就够了,不要用 Opus 硬抬成本。


6)把 schema/校验/修复策略集中管理(147api)

结构化输出最怕的是:每个服务都写一套 schema,每次改字段都要全量发版。

更合理的做法是把这些能力集中到统一接入层:

  • schema 版本化(v1/v2)
  • 校验与修复回路统一实现
  • 按项目/用户做限流与配额,避免修复回路把 429 撞爆

如果你不想自建网关,可以了解下 147api:按其介绍,它提供 OpenAI 风格的聚合管理与 key 管理能力。把接入层工程收口后,业务侧就只需要调用一个稳定接口。


一页清单

  • 提示词:只输出 JSON,禁止 Markdown/解释
  • schema:字段/类型/枚举写清楚
  • 规则:写成可检查项(长度、必填、数组上限)
  • 唯一出口:{error, need} 兜底
  • 解析:反序列化失败要有 fallback(净化/修复回路)
  • 观测:记录失败样本,反推 schema 与规则
相关推荐
海盗12341 天前
C#在Distinct()中使用IEqualityComparer<T>
开发语言·c#
Vertira1 天前
python 配置PostgreSQL 数据库
开发语言·python
Highcharts.js1 天前
Highcharts 纯 JavaScript 图表库深度使用评测
开发语言·前端·javascript·功能测试·ecmascript·highcharts·技术评测
瑶池酒剑仙1 天前
C++类和对象完全指南:从封装继承多态到内存布局的面向对象宝典(雨夜论道)
c语言·开发语言·c++·visual studio
三品吉他手会点灯1 天前
C语言学习笔记 - 27.C编程预备计算机专业知识 - 什么是字节
c语言·开发语言·笔记·学习
许彰午1 天前
政务远程帮办部署踩坑实录——从互联网到政务外网
开发语言·网络·政务
kartjim1 天前
离谱!Claude Desktop 现在要求模型名必须像 Claude
claude
却尘1 天前
Tool Use 到底能保证什么?搞懂这条边界,你的 LLM 提取再也不会"格式炸了"
gpt·chatgpt·claude
却尘1 天前
你的 LLM 系统在偷偷出错,季度审计才发现:Stratified Sampling 这把刀治到根上
claude