遇到 Claude API 返回的 JSON 格式错误?先别急着换模型。这篇文章整理了一套完整的诊断思路和分层解决方案,帮你快速找到问题出在哪,然后对症下药。

一、先搞清楚:你遇到的是哪类问题?

JSON 输出格式出问题,原因其实就那么几种。花两三分钟对照下面这张表,基本能定位到你的具体情况:

问题类型 典型表现 怎么排查
提示词没说清楚格式 模型有时返回 JSON,有时返回一段自然语言,或者字段结构对不上 翻一下你的 system prompt,有没有明确要求 JSON 格式?
Markdown 混进来了 返回内容被 ```包裹,或者 JSON 里的引号、换行没有正确转义 看看原始响应,JSON 是不是被代码块套着?
请求体本身就有问题 收到 400 错误,提示 “deserialize” 或 “invalid JSON” 检查请求体,重点看特殊字符和 Unicode 编码
输出被截断了 JSON 结构不完整,末尾少了 }] 看看 JSON 是不是在某个奇怪的地方突然断掉了

对应的快速处理思路:

  • 提示词问题 → 用结构化输出 API(最稳),或者在提示词里给出 JSON Schema 示例
  • Markdown 混入 → 启用结构化输出 API,或者在系统提示里明确禁止 markdown 包装
  • 请求体问题 → 升级 SDK,确认编码是 UTF-8,特殊字符记得转义
  • 输出截断 → 把 max_tokens 调大,或者把数据拆成小批次处理在这里插入图片描述

二、四类根源逐一拆解

问题一:提示词没有明确告诉模型你要什么格式

怎么表现出来的: 模型有时给你 JSON,有时给你一段文字;或者虽然是 JSON,但字段名和结构跟你想要的完全不一样。

为什么会这样: Claude 默认会根据上下文自己判断该用什么格式输出。如果你的提示词没说清楚期望的结构,模型就会按自己的理解来,结果自然不稳定。

解决方案,按复杂度分三档:

最简单的做法(快速修复)

在系统提示里加一句明确的指令:

你必须用 JSON 格式回复,不要包含任何其他文本。

这能提高 JSON 输出的概率,但没法保证字段结构每次都一样。

稍微完整一点(给出 Schema 示例)

直接把你想要的结构写出来:

请以以下 JSON 格式回复:
{
  "title": "字符串",
  "summary": "字符串",
  "tags": ["字符串数组"],
  "confidence": 0.0
}
只返回 JSON,不要其他内容。

结构一致性会好很多,但碰到复杂嵌套或字段验证还是可能出问题。

最稳的方案(结构化输出 API)

这是 Claude 官方推荐的做法,下面第三部分会详细说。简单来说,通过 API 层面的 JSON Schema 约束,可以做到格式 100% 一致。


问题二:输出混入了 Markdown,转义也不完整

怎么表现出来的: 响应内容被 ```代码块包着,或者 JSON 里的引号、换行符没有正确转义,直接导致解析失败。

为什么会这样: Claude 在回复时习惯用 markdown 代码块来展示 JSON,尤其是提示词没有明确禁止的时候。另外,如果 JSON 里包含特殊字符,模型也不一定会正确转义。

解决方案 A:结构化输出 API(首选)

这个方案在 Claude 3.5 Sonnet 及更新版本里可用,直接在 API 层面强制模型返回符合 JSON Schema 的纯数据,彻底杜绝 markdown 混入:

import anthropic
import json

client = anthropic.Anthropic(api_key="your_api_key")

schema = {
    "type": "object",
    "properties": {
        "title": {"type": "string"},
        "content": {"type": "string"},
        "tags": {
            "type": "array",
            "items": {"type": "string"}
        }
    },
    "required": ["title", "content", "tags"]
}

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": "请分析这段文本并提取标题、内容摘要和标签。\n文本:[你的内容]"
        }
    ],
    structured_outputs={
        "type": "json_schema",
        "json_schema": {
            "name": "TextAnalysis",
            "schema": schema,
            "strict": True
        }
    }
)

# 直接拿到 JSON 对象,不用手动清洗
result = json.loads(response.content[0].text)
print(result)

这个方案的好处很明显:输出 100% 符合 Schema,没有 markdown 包装,字段类型和必填项自动验证,性能和 token 消耗跟传统方案差不多。

解决方案 B:提示词约束(备选方案)

如果因为模型版本限制用不了结构化输出,可以在提示词里这样写:

你的回复必须满足以下要求:
1. 只返回有效的 JSON,不要任何其他文本
2. 不要用 markdown 代码块包裹 JSON
3. JSON 中的特殊字符(如引号、换行)必须正确转义
4. 不要在 JSON 前后添加任何说明文字

解决方案 C:后处理清洗(应急用)

实在没办法,可以在客户端做一层清洗,但这只是救急,不建议作为常规做法:

import re
import json

def extract_json_from_markdown(response_text):
    # 先尝试提取 markdown 代码块里的 JSON
    match = re.search(r'```(?:json)?\s*([\s\S]*?)\s*```', response_text)
    if match:
        json_str = match.group(1)
    else:
        json_str = response_text
    
    try:
        return json.loads(json_str)
    except json.JSONDecodeError as e:
        print(f"JSON 解析失败: {e}")
        return None

result = extract_json_from_markdown(response.content[0].text)

问题三:请求体本身有问题(Unicode 或转义错误)

怎么表现出来的: 发请求时直接收到 400 错误,提示 “Failed to deserialize” 或 “invalid JSON”。

常见原因有这几个: 请求体里的字符串没有正确转义(尤其是包含引号、反斜杠、换行的内容);特殊 Unicode 字符编码不对;SDK 版本太旧,对某些字符的处理有 bug。

排查步骤:

首先检查 SDK 版本够不够新:

# Python
pip show anthropic
# 版本需要 >= 0.7.0

# Node.js
npm list @anthropic-ai/sdk
# 版本需要 >= 0.9.0

然后确认字符编码是 UTF-8:

import sys
print(sys.getdefaultencoding())  # 应该输出 'utf-8'

再看看特殊字符的处理方式。其实用 SDK 的话,它会自动处理转义,不需要你手动操心:

# 直接传字符串给 SDK 就好,它会自动转义
content = 'Please return {"name": "value"}'

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    messages=[{"role": "user", "content": content}]
)

如果问题还在,直接升级 SDK:

pip install --upgrade anthropic

问题四:输出被截断(token 不够用)

怎么表现出来的: 返回的 JSON 结构不完整,末尾少了闭合的 }]

怎么确认: 看 API 响应里的 stop_reason 字段,如果是 "max_tokens",那就是 token 不够导致截断了。

两种处理方式:

直接调大 max_tokens:

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=4096,  # 根据实际输出大小调整
    messages=[{"role": "user", "content": "..."}]
)

数据量太大的话,拆成小批次处理:

def process_large_data(items, batch_size=10):
    results = []
    for i in range(0, len(items), batch_size):
        batch = items[i:i+batch_size]
        response = client.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=2048,
            messages=[{
                "role": "user",
                "content": f"处理这些项目:{json.dumps(batch)}"
            }]
        )
        results.extend(json.loads(response.content[0].text))
    return results

三、结构化输出 API 完全指南

结构化输出是目前最稳的 JSON 格式保证方案,Claude 3.5 Sonnet 及更新版本都支持。

它到底是什么

简单说,就是在 API 请求里附上一个 JSON Schema,然后模型会被强制返回符合这个 Schema 的纯 JSON 数据。跟"在提示词里要求 JSON"不同,这是在 API 层面做的约束,所以:

  • 格式 100% 一致
  • 字段类型、必填项自动验证
  • 不会有 markdown 包装
  • 性能开销很小

什么情况下用

  • 必须用的场景: 需要稳定 JSON 输出、字段结构固定、生产环境关键业务
  • 推荐用的场景: 数据提取、内容分类、表单验证、API 集成
  • 可以用也可以不用: 简单的 JSON 输出,对格式容错度比较高

Schema 怎么写

来看一个比较完整的例子:

schema = {
    "type": "object",
    "properties": {
        # 字符串字段
        "title": {
            "type": "string",
            "description": "内容标题"
        },
        # 数字字段,带范围限制
        "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "description": "评分(0-100)"
        },
        # 枚举字段,限定可选值
        "category": {
            "type": "string",
            "enum": ["技术", "生活", "娱乐"],
            "description": "内容分类"
        },
        # 数组字段
        "tags": {
            "type": "array",
            "items": {"type": "string"},
            "description": "标签列表"
        },
        # 嵌套对象
        "author": {
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "email": {"type": "string"}
            },
            "required": ["name"]
        }
    },
    "required": ["title", "category"]
}

几个关键字段说明一下:type 指定字段类型(string、number、integer、boolean、array、object 都支持);required 列出必填字段;enum 限定枚举值;minimum/maximum 设置数字范围;description 写字段说明,模型会参考这个来理解你的需求,建议写清楚一点。

完整代码示例

import anthropic
import json

def extract_article_info(article_text):
    client = anthropic.Anthropic(api_key="your_api_key")
    
    schema = {
        "type": "object",
        "properties": {
            "title": {"type": "string", "description": "文章标题"},
            "summary": {"type": "string", "description": "100字以内的摘要"},
            "main_points": {
                "type": "array",
                "items": {"type": "string"},
                "description": "3-5个主要观点"
            },
            "sentiment": {
                "type": "string",
                "enum": ["positive", "neutral", "negative"],
                "description": "情感倾向"
            },
            "confidence": {
                "type": "number",
                "minimum": 0,
                "maximum": 1,
                "description": "分析置信度"
            }
        },
        "required": ["title", "summary", "main_points", "sentiment"]
    }
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        messages=[
            {
                "role": "user",
                "content": f"请分析以下文章:\n\n{article_text}"
            }
        ],
        structured_outputs={
            "type": "json_schema",
            "json_schema": {
                "name": "ArticleAnalysis",
                "schema": schema,
                "strict": True
            }
        }
    )
    
    result = json.loads(response.content[0].text)
    return result

article = "这是一篇关于 AI 发展的文章..."
analysis = extract_article_info(article)
print(f"标题: {analysis['title']}")
print(f"情感: {analysis['sentiment']}")

加上重试机制

偶发失败在所难免,建议在生产环境里加上重试逻辑:

def call_with_retry(prompt, schema, max_retries=3):
    client = anthropic.Anthropic()
    
    for attempt in range(max_retries):
        try:
            response = client.messages.create(
                model="claude-3-5-sonnet-20241022",
                max_tokens=1024,
                messages=[{"role": "user", "content": prompt}],
                structured_outputs={
                    "type": "json_schema",
                    "json_schema": {
                        "name": "Output",
                        "schema": schema,
                        "strict": True
                    }
                }
            )
            return json.loads(response.content[0].text)
        except Exception as e:
            if attempt == max_retries - 1:
                raise
            print(f"第 {attempt + 1} 次尝试失败: {e},重试中...")

四、常见错误速查表

错误信息 根本原因 怎么解决
invalid_request_error: Could not deserialize JSON body 请求体 JSON 格式有问题,通常是特殊字符没转义 升级 SDK,检查字符编码,用 SDK 自动转义
400 Bad Request 请求格式错误,可能是 headers、body 或参数问题 确认 Content-Type 是 application/json,检查 API key 格式
stop_reason: "max_tokens" + JSON 不完整 token 不够,输出被截断了 调大 max_tokens,或者拆批处理
JSON 被 markdown 包裹 模型习惯用 ```包装 JSON 用结构化输出 API,或在提示词里明确禁止 markdown
"no low surrogate in string" Unicode 转义错误,代理对处理有问题 确保 UTF-8 编码,升级 SDK
字段类型不匹配 模型理解有偏差,返回了错误类型的数据 在 Schema 的 description 里把字段要求写得更具体

五、工程实践建议

提示词怎么写: 明确说明期望的 JSON 结构(给示例或 Schema 都行),解释每个字段的含义和类型,同时禁止不需要的格式,比如 markdown 或者自然语言混排。

请求体构造注意事项: 用最新版本的 SDK,确保所有字符串是 UTF-8 编码,不要在请求体里手动拼接未转义的 JSON,交给 SDK 处理就好。

响应验证和错误处理: 每次都检查 stop_reason,确认输出是完整的;解析 JSON 之前做格式校验;加上重试机制应对偶发失败。

调试时的日志:

import logging

logging.basicConfig(level=logging.DEBUG)

response = client.messages.create(...)
print(f"Stop reason: {response.stop_reason}")
print(f"Content: {response.content[0].text}")

六、完整实战案例:从用户评论中提取结构化信息

import anthropic
import json

def analyze_user_reviews(reviews):
    client = anthropic.Anthropic()
    
    schema = {
        "type": "object",
        "properties": {
            "reviews": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "original_text": {"type": "string"},
                        "sentiment": {
                            "type": "string",
                            "enum": ["positive", "neutral", "negative"]
                        },
                        "rating": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 5
                        },
                        "key_points": {
                            "type": "array",
                            "items": {"type": "string"}
                        }
                    },
                    "required": ["original_text", "sentiment", "rating"]
                }
            }
        },
        "required": ["reviews"]
    }
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=2048,
        messages=[{
            "role": "user",
            "content": f"请分析以下用户评论,提取情感、评分和关键点:\n{json.dumps(reviews, ensure_ascii=False)}"
        }],
        structured_outputs={
            "type": "json_schema",
            "json_schema": {
                "name": "ReviewAnalysis",
                "schema": schema,
                "strict": True
            }
        }
    )
    
    return json.loads(response.content[0].text)

# 跑一下试试
reviews = [
    "这个产品太棒了,质量很好",
    "一般般,没什么特别的",
    "质量很差,不值得买"
]

result = analyze_user_reviews(reviews)
for review in result["reviews"]:
    print(f"评论: {review['original_text']}")
    print(f"情感: {review['sentiment']},评分: {review['rating']}")

总结

遇到 Claude API JSON 输出格式问题,处理思路其实很清晰:先用第一部分的表格定位是哪类问题,然后选对应的解决方案——结构化输出 API 是最优解,提示词约束是备选;实施的时候参考代码示例,记得检查 stop_reason 和响应格式;后续根据实际情况调整 max_tokens、Schema 定义或提示词。

对于生产环境,强烈建议用 Claude 3.5 Sonnet 及更新版本的结构化输出 API,稳定性和可维护性都是最好的。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐