用 Claude API 做结构化 JSON 输出(10 个 hook) 记录问题原因、修复,和复用的 prompt 模板。
踩坑记录
坑 1:模型不输出纯 JSON,而是包裹中文前言
现象 :即使设置了 output_config.format (json_schema),Claude 仍然返回:
好的,姐妹!以下是为您生成的 10 个 hook...
{"hooks": [...]}
原因:
- Claude 4.7 在中文场景下倾向于"礼貌回复",即使有结构化输出约束也会附上前言
output_config.format是约束而非强制------不能 100% 依赖它- 中文 system prompt + 中文 user message 时,模型更容易"聊天化"
修复(双层防护):
第一层------Prompt 加强,在 system prompt 末尾加:
## 输出格式(极其重要)
你必须只输出一个合法的 JSON 对象,不要加任何前缀说明、后缀总结、markdown 代码块标记。
正确示例:{"hooks":[{"text":"...","style":"悬念式","score":8,"reason":"..."}]}
错误示例:好的,以下是为您生成的...\`\`\`json...\`\`\`
不要输出 "以下是"、"好的"、"为您生成" 这类废话,直接输出 JSON。
- 用"正确示例"和"错误示例"对比,比单纯说"不要"有效得多
- 明确列出常见废话("以下是"、"好的"、"为您生成")
- 放在 prompt 末尾(模型的 recency bias 会让它更重视末尾指令)
第二层------代码容错,在 JSON.parse 前加提取逻辑:
typescript
let rawText = textBlock.text.trim();
// 1. 如果被包裹在 markdown 代码块中,提取出来
const codeBlockMatch = rawText.match(/\`\`\`(?:json)?\s*([\s\S]*?)\`\`\`/);
if (codeBlockMatch) {
rawText = codeBlockMatch[1].trim();
}
// 2. 尝试找到 JSON 对象的起始位置
const jsonStart = rawText.indexOf('{"hooks"');
if (jsonStart > 0) {
rawText = rawText.slice(jsonStart);
}
// 3. 解析
const data = JSON.parse(rawText);
三层防护(markdown 代码块 → 文本中挖 JSON → 报错)保证健壮。
坑 2:prompt 过长导致模型"浏览"而非"精读"
现象:10 种风格 + 5 个平台规则 + 评分标准,prompt 接近 2000 字,模型可能跳读某些约束。
建议:
- 关键约束放末尾(recency bias)
- 用明确的分隔线和层级标题帮助模型定位
- 不要用"请"、"如果可以的话"这类弱化词------用"必须"、"只能"
坑 3:结构化输出 ≠ 100% 保证
经验 :output_config.format (json_schema) 是概率性约束,不是硬保证。永远要:
- Prompt 层面强调格式
- 代码层面做 JSON 提取容错
- 两个路由(generate + regenerate)都要加同样的容错
可复用的 Prompt 模板
结构化 JSON 输出的通用 System Prompt 模板
你是一个[角色描述]。
## 你的任务
[任务描述]
## 规则
[业务规则 1-N]
## 输出格式(最重要)
你必须只输出一个合法的 JSON 对象。
- 不要加任何前缀说明或后缀总结
- 不要用 markdown 代码块包裹
- 不要输出"好的"、"以下是"、"为您生成"等人机对话用语
正确格式示例:
{"key": "value"}
错误格式示例(禁止):
好的,以下是结果... {"key": "value"}
```json
{"key": "value"}
### 关键原则
1. **末尾强调**:格式约束放 prompt 最后一段
2. **正反示例**:同时给出正确和错误的输出示例
3. **明确禁止词**:列出"以下是"、"好的"、"为您生成"、"当然"等常见废话
4. **简短有力**:用"必须"、"禁止"、"只能",不要用"请"、"如果可以的话"
5. **代码兜底**:永远在 `JSON.parse` 前加提取/容错逻辑
---
## 生成类 Prompt 的通用 Checklist
- [ ] 角色定位是否清晰?(一句话,不要多)
- [ ] 输出数量是否明确?("恰好 10 个",不是"10 个左右")
- [ ] 格式约束是否在末尾且醒目?
- [ ] 是否有正反示例对比?
- [ ] 评分/评价标准是否量化?("好奇心缺口 30% + 情感冲击 30%",不是"综合判断")
- [ ] 是否避免了弱化词("请"、"尽量"、"如果可以")?
- [ ] 代码端是否做了 JSON 解析容错?
---
## 编码侧的通用 JSON 提取函数
```typescript
function extractJson<T>(text: string): T {
let raw = text.trim();
// 去掉 markdown 代码块
const codeMatch = raw.match(/```(?:json)?\s*([\s\S]*?)```/);
if (codeMatch) raw = codeMatch[1].trim();
// 找到 JSON 起始
const start = raw.indexOf("{");
const end = raw.lastIndexOf("}");
if (start > -1 && end > start) {
raw = raw.slice(start, end + 1);
}
return JSON.parse(raw) as T;
}
这个函数可以复用到任何需要从 AI 响应中提取 JSON 的项目。