近两年来,Claude 系列模型在对话理解、长文本处理和工具调用上表现越来越强,很多开发者开始系统学习 Claude API 的使用方式。不过很多人在入门时都会遇到一个尴尬:官方文档里频繁出现 XML 标签,示例代码里到处都是<context>、<thinking>、<output>之类的符号,一时间搞不懂它们到底是模板语法,还是必须遵守的格式规范。
这篇文章是 Claude Certified Architect 前置学习系列的第 9 篇,专门把 Claude API 中的 XML 相关内容拆开讲清楚。我会从“为什么要用 XML”“XML 在 API 调用中的位置”“实际项目怎么组织 XML”“常见的解析与报错问题”几个角度展开。适合已经在用 Claude API 做应用开发、或者正在准备官方架构师认证的读者;零基础也没关系,文章会从最简单的概念入手。
读完之后,你能掌握三件事:第一,在 prompt 中合理使用 XML 标签来划分上下文和指令;第二,让模型以 XML 结构返回内容,并用 Python 可靠解析;第三,遇到常见的 API 报错时,知道从哪些方向排查。
1. 为什么 Claude API 如此看重 XML
1.1 从一段提示词说起
先看一个最常见的例子。假设你希望 Claude 根据公司政策判断一段文本是否合规,普通写法可能是:
请根据以下政策信息,判断用户提交的内容是否合规: 政策:员工不得在公共网络传输客户隐私数据。 内容:小明把客户电话号码发到了公共网盘。 请给出结论和理由。这种写法模型能理解,但在复杂场景下容易出现上下文错乱:政策、待审核内容、审核指令混在一起,模型可能会把“待审核内容”当成“政策”的一部分,也可能输出格式不稳定。
如果改用 XML 标签来划分,效果会清晰很多:
<review_task> <policy> 员工不得在公共网络传输客户隐私数据。 </policy> <document> 小明把客户电话号码发到了公共网盘。 </document> <instructions> 请判断上述文档是否违反政策,先给出结论,再说明理由。 </instructions> </review_task>这里没有任何魔法,XML 只是普通的文本标记语言。但 Claude 在训练阶段就对 XML 结构的文本有较好的理解能力,官方也明确建议用 XML 标签来组织提示词中的不同区块。换句话说:XML 是 Claude 翻译“结构信息”时比较擅长的一种格式。
1.2 XML 是什么,为什么不是 JSON
XML 全称是可扩展标记语言(Extensible Markup Language),和 JSON 一样都是数据交换格式。JSON 的结构是“键值对 + 数组”,XML 的结构是“标签 + 属性 + 层级”。
在实际开发中,JSON 在前后端数据交互中占据了绝大多数场景,那么 Claude API 为什么偏爱 XML?这其实和模型的学习方式、提示词的阅读顺序有关。
第一,XML 的闭合标签天然适合长文本分段。<policy>...</policy>这种成对出现的形式,让模型在长上下文中更容易定位边界。即便中间有大量文本,也很少出现“读到一半忘记在哪一层”的情况。
第二,XML 允许同名标签区分不同类型的信息。比如多个<example>标签可以各自独立,模型能通过标签位置理解它们之间的关系。JSON 要实现类似效果,通常需要包一层数组,阅读成本反而更高。
第三,Claude 在训练数据中大量接触过 XML 文档,对标签语义有稳定的先验理解。这不是说 JSON 不行,而是在提示词工程这个具体场景下,XML 往往是官方推荐、模型响应更稳定的选择。
1.3 XML 在 Claude API 中的三大典型场景
总结起来,XML 在 Claude API 开发中主要出现在三个位置:
| 场景 | 作用 | 示例 |
|---|---|---|
| 提示词结构化 | 把系统指令、参考文档、用户输入区隔开 | <policy>、<document>、<instructions> |
| 输出格式约束 | 要求模型以指定 XML 结构返回结果 | <result><summary>...</summary></result> |
| 工具调用参数 | 理解 API 返回的工具调用数据结构 | tool_use 中的 input 字段结构 |
接下来分别看看这三个场景里有哪些具体的写法和注意点。
2. 环境准备与基础 API 调用
开始写代码之前,先把实验环境准备好。本文所有示例以 Python 为例,因为 Claude 官方 SDK 对 Python 的支持比较完善,代码也简洁。
2.1 安装 SDK 与设置密钥
建议使用 Python 3.9 及以上版本。官方 Python SDK 的包名是anthropic,用 pip 安装即可:
pip install anthropic安装完成后,需要配置 API 密钥。密钥不要直接写死在代码里,推荐通过环境变量读取:
export ANTHROPIC_API_KEY="sk-ant-..."在 Windows PowerShell 中则使用:
$env:ANTHROPIC_API_KEY="sk-ant-..."注意,密钥属于敏感信息。哪怕只是个人学习项目,也不要把密钥提交到 Git 仓库。一旦泄露,别人就可以用你的配额调用接口,产生不必要的费用。
2.2 第一个请求
创建claude_xml_demo.py,写下第一个调用:
import anthropic client = anthropic.Anthropic() # 默认读取 ANTHROPIC_API_KEY 环境变量 response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, messages=[ { "role": "user", "content": "用一句话解释 XML 在提示词工程中的作用。" } ] ) print(response.content[0].text)如果一切正常,你会看到模型返回一段解释文本。这里暂时没有用到 XML,只是确认 SDK 安装和密钥配置没有问题。
需要提醒的是,模型名称会随官方版本不断更新。本文示例中的模型名只是演示,实际使用时请以官方文档列出的可用模型为准。建议把模型名单独抽成配置项,方便后续切换。
2.3 版本差异与兼容性说明
Claude API 的模型家族更新迭代比较快,不同模型的上下文长度、函数调用能力、XML 理解能力可能存在差异。写代码时要注意两点:
第一,max_tokens参数是必填项,如果不设置,部分模型会直接报错。第二,不同模型的上下文窗口长度不一样,有些模型上下文足够长,但如果你在 prompt 中塞入了超大 XML 文档,仍然可能触发 400 错误(maximum context length)。后面第 5 章会专门说这个报错。
3. XML 在 Claude API 中的核心写法
3.1 用 XML 标签组织提示词
这是 XML 在 Claude API 中最重要、也最常用的用途:把不同语义的内容用标签包裹起来。
先看一个带系统提示词的完整例子:
import anthropic client = anthropic.Anthropic() system_prompt = """ 你是一个合同审查助手。你会收到一份<contract>合同文本和一组<rules>审查规则。 请逐条检查合同是否违反规则。 """ user_content = """ <contract> 甲方应在收到乙方发票后30日内支付全部款项。 若甲方逾期支付,需按日支付0.05%的违约金。 </contract> <rules> 1. 付款周期不得超过30天。 2. 违约金比例不得超过0.03%。 </rules> 请输出审查结果。 """ response = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1024, system=system_prompt, messages=[ {"role": "user", "content": user_content} ] ) print(response.content[0].text)这段代码的关键点在于:
<contract>和<rules>是自定义标签,模型不会因为“不认识”就不处理。- 系统提示词和用户消息分开传,系统部分描述角色和任务,用户部分携带数据和问题。
- 标签名称应该简洁、语义明确。不要用没有意义的
<tag1>、<tag2>。
实际项目中,合同文本、政策文档、用户输入往往是动态拼接的,注意在拼进 XML 之前做好安全转义,否则文本里自带的<或>会破坏标签结构。
3.2 保留标签与去除标签
有读者会问:模型返回内容里如果带 XML 标签,我们是该保留还是该去掉?
这取决于使用场景。如果你需要把结果展示给最终用户,通常要提取纯文本,去掉标签;如果你需要把结果交给另一个程序处理,则应该保留 XML 结构,方便解析。比如:
请用以下 XML 格式返回: <review_result> <conclusion>合规/不合规</conclusion> <reason>理由</reason> </review_result>模型返回的内容可能是:
<review_result> <conclusion>不合规</conclusion> <reason>付款周期和违约金比例均超出规则限制。</reason> </review_result>此时你可以在 Python 中解析这段 XML,提取结论和理由,再决定如何展示。
3.3 在工具调用中理解 XML 结构
Claude API 支持工具调用(tool use)。当我们定义工具时,参数结构通常使用 JSON Schema。API 返回的响应中会包含tool_use类型的 content block,其中input字段是模型根据工具定义生成的参数。
有开发者会疑惑:工具调用不是用 JSON 吗,跟 XML 有什么关系?其实这里的核心是数据格式不同,但理解层级结构的思想一致。XML 标签的嵌套思想和 JSON Schema 的嵌套结构是相通的。掌握了 XML 的层级思维,理解工具参数的结构会更容易。
一个简单工具定义示例:
tools = [ { "name": "check_contract", "description": "检查合同条款是否合规", "input_schema": { "type": "object", "properties": { "clause": {"type": "string", "description": "合同条款文本"}, "rule_ids": { "type": "array", "items": {"type": "integer"}, "description": "需要匹配的规则编号列表" } }, "required": ["clause", "rule_ids"] } } ]这里clause和rule_ids就相当于 XML 中的两个子标签,input_schema相当于父标签。API 返回的tool_use.input会是:
{ "clause": "甲方应在收到乙方发票后30日内支付全部款项。", "rule_ids": [1, 2] }理解这个对应关系后,你会发现 XML 和 JSON 只是不同表现形式,核心还是“层级化组织信息”。
4. 完整实战:构建一个 XML 驱动的合同审查助手
为了把前面的知识串起来,这里实现一个比较完整的示例:用 Claude API 审查合同,要求模型以 XML 格式返回结构化结果,再用 Python 解析 XML 并输出格式化报告。
4.1 项目结构
claude-xml-review/ ├── review.py ├── contract.txt └── README.md实际开发中建议拆分成多个模块,这里为了演示,核心逻辑集中在一个review.py里。
4.2 编写完整代码
# -*- coding: utf-8 -*- # 文件路径:claude-xml-review/review.py import os import re import xml.etree.ElementTree as ET import anthropic # 模型名称建议配置化,方便切换 MODEL_NAME = "claude-sonnet-4-20250514" SYSTEM_PROMPT = """ 你是一个严谨的合同审查助手。 你收到合同文本和政策规则后,需要逐条比对。 输出必须严格遵循 XML 结构,不要输出 XML 以外的内容。 """ def read_contract(path: str) -> str: """读取合同文本文件。""" with open(path, "r", encoding="utf-8") as f: return f.read() def build_prompt(contract: str, rules: list[str]) -> str: """用 XML 标签组装用户消息。""" rules_xml = "\n".join( f"<rule id=\"{i + 1}\">{rule}</rule>" for i, rule in enumerate(rules) ) return f""" <contract> {contract} </contract> <rules> {rules_xml} </rules> 请按照如下 XML 结构返回审查结果: <review_result> <summary>总体结论</summary> <issues> <issue> <rule_id>相关规则编号</rule_id> <clause>相关合同条款</clause> <reason>违规原因</reason> </issue> </issues> </review_result> """ def call_claude(prompt: str) -> str: """调用 Claude API,返回模型输出文本。""" client = anthropic.Anthropic() response = client.messages.create( model=MODEL_NAME, max_tokens=2048, system=SYSTEM_PROMPT, messages=[ {"role": "user", "content": prompt} ], ) return response.content[0].text def parse_xml(text: str) -> ET.Element: """解析模型返回的 XML。""" # 模型有时会在 XML 前后添加 Markdown 代码块标记,需要清理 text = text.strip() text = re.sub(r"^```(?:xml)?\s*", "", text) text = re.sub(r"\s*```$", "", text) return ET.fromstring(text) def format_report(root: ET.Element) -> str: """把解析后的 XML 转成可读报告。""" summary = root.findtext("summary", default="无") lines = [f"审查结论:{summary}", ""] issues_node = root.find("issues") if issues_node is None: lines.append("未发现违规条款。") return "\n".join(lines) for issue in issues_node.findall("issue"): rule_id = issue.findtext("rule_id", default="未知") clause = issue.findtext("clause", default="未知") reason = issue.findtext("reason", default="未知") lines.append(f"- 规则 {rule_id}: 违反") lines.append(f" 条款:{clause}") lines.append(f" 原因:{reason}") lines.append("") return "\n".join(lines) def main(): contract = read_contract("contract.txt") rules = [ "付款周期不得超过 30 天。", "违约金比例不得超过 0.03%。", "合同需明确争议解决方式。", ] prompt = build_prompt(contract, rules) raw_output = call_claude(prompt) print("===== 模型原始返回 =====") print(raw_output) print("========================\n") root = parse_xml(raw_output) report = format_report(root) print("===== 格式化报告 =====") print(report) if __name__ == "__main__": main()4.3 准备测试合同
contract.txt内容如下:
甲方(采购方)与乙方(供应商)于2025年3月1日签订本合同。 合同约定:甲方应在收到乙方发票后30日内支付全部款项。 若甲方逾期支付,需按日支付0.05%的违约金。 双方发生争议时,应通过友好协商解决。4.4 运行与预期结果
在项目目录下执行:
python review.py预期模型返回类似:
<review_result> <summary>合同存在 1 处违规条款</summary> <issues> <issue> <rule_id>2</rule_id> <clause>若甲方逾期支付,需按日支付0.05%的违约金。</clause> <reason>违约金比例 0.05% 超出规则允许的 0.03% 上限。</reason> </issue> </issues> </review_result>程序解析后输出的报告为:
审查结论:合同存在 1 处违规条款 - 规则 2: 违反 条款:若甲方逾期支付,需按日支付0.05%的违约金。 原因:违约金比例 0.05% 超出规则允许的 0.03% 上限。这个示例的核心不是合同审查本身,而是演示了“XML 标签构建 prompt → 模型按 XML 结构返回 → Python 解析 XML → 格式化输出”的完整闭环。实际项目中可以替换成智能客服、文档整理、信息提取等任务,思路完全一样。
4.5 解析 XML 时的防御性处理
模型返回的 XML 并不总是严格的。可能出现的变体包括:
- 标签前后有 ```xml 代码块标记。
- 标签闭合顺序与预期不一致。
- 中文文本中混入特殊字符。
因此在parse_xml函数中做了两层防御:第一步去掉 Markdown 代码块标记,第二步用正则规整首尾空白。即便如此,仍然建议在解析外面加异常处理,防止内容无法解析时程序崩溃。
更好的做法是让模型在无法判断时输出一个固定的错误标签,比如:
<review_result> <error>无法生成审查结果</error> </review_result>这样解析层可以根据是否存在<error>标签来决定后续处理。
5. 常见问题与排查思路
在实际使用 Claude API 和 XML 时,大家会遇到一些重复率很高的问题。这一节整理了典型现象和排查方向。
5.1 API 返回 529 错误
错误示例:
api error: 529 overloaded. this is a server-side issue, usually temporary529表示服务端过载,属于临时性错误。通常是当前请求量过大,官方服务端暂时无法处理。
排查与解决:
- 不要频繁重试,建议退避重试(指数退避)。
- 检查是否并发过高,适当降低并发数。
- 确认自己的 API 账户状态是否正常。
- 如果是长时间持续报错,关注官方状态页面。
简单重试代码片段:
import time def call_with_retry(prompt, max_retries=3): for i in range(max_retries): try: return call_claude(prompt) except anthropic.APIStatusError as e: if e.status_code == 529: wait = 2 ** i print(f"服务过载,{wait} 秒后重试") time.sleep(wait) continue raise raise RuntimeError("多次重试仍然失败")5.2 400 错误:超过最大上下文长度
错误示例:
api error: 400 this model's maximum context length is 1048576 tokens. however...这个报错说明 prompt 和补全内容的总长度超过了模型的上下文窗口。常见原因包括 XML 文档过大、历史消息累积过多、输出max_tokens设置过大。
排查路径:
- 压缩输入 XML:只保留必要字段,去掉无关段落。
- 将长文档拆分成多段分批处理。
- 减少历史消息数量,或用摘要替代完整历史。
- 如果逻辑允许,降低
max_tokens值。
从工程角度看,长文档处理最稳妥的方式是“先分段,再汇总”。比如把 100 页合同拆成 10 段,逐段让模型提取重点,最后再汇总。
5.3 浏览器打开模型返回的 XML 报“no style information”
有开发者把模型生成的文本保存为.xml文件并用浏览器打开,会看到:
This XML file does not appear to have any style information associated with the document tree.这只是浏览器提示“该 XML 没有关联样式表”,并不是错误。你可以直接查看 XML 的树状结构,或者用编辑器打开。重点是:模型输出本质是普通文本,只有当你把它当成 XML 解析时,才需要关注语法是否严格。
5.4 Python 解析 XML 失败:invalid XML content
错误示例:
xml.etree.ElementTree.ParseError: invalid XML content常见原因是文本中包含了未转义的特殊字符,例如&、<、>。XML 规范要求这些字符必须转义:
| 字符 | 转义后 |
|---|---|
| & | & |
| < | < |
| > | > |
| " | " |
| ' | ' |
如果合同文本或规则里包含这些字符,拼接 XML 前需要先转义。Python 中可以用xml.sax.saxutils.escape:
from xml.sax.saxutils import escape safe_text = escape("违约金比例 > 0.03% & 小于 5%") print(safe_text)输出:
违约金比例 > 0.03% & 小于 5%转义后的内容再由模型处理,可以避免解析阶段崩溃。
5.5 模型没有严格按照要求的 XML 返回
有时模型会“加班”,额外输出解释性文字,破坏了 XML 的完整性。这是提示词工程里很常见的问题。
缓解方法:
- 在 system prompt 里写明“只输出 XML,不要任何解释”。
- 在 user message 里重复强调输出格式,并给一个最小示例。
- 增加 few-shot 示例,让模型照着格式输出。
- 在代码里做容错:用正则提取第一个
<review_result>到最后一个</review_result>之间的内容。
示例提取代码:
def extract_xml_block(text: str) -> str: start = text.find("<review_result>") end = text.rfind("</review_result>") if start == -1 or end == -1: raise ValueError("未找到完整的 XML 块") return text[start:end + len("</review_result>")]6. 最佳实践与工程建议
6.1 提示词方面的建议
XML 标签命名要语义化。不要使用<a>、<b>这类毫无信息量的标签。Claude 对语义明确的标签理解更好,比如<contract>、<policy>、<rules>、<output>。
标签数量要克制。一个 prompt 里嵌套七八层 XML,反而会增加模型的理解负担。尽量控制在三层以内,保持扁平。如果任务确实复杂,可以考虑拆成多个 API 调用。
给模型一个输出示例。在 prompt 中给出输出标签的最小模板,模型返回结果会更稳定。这里只需要给出结构模板,不需要填真实数据。
6.2 代码层面的建议
解析模型返回的 XML 时,必定要加异常处理。模型输出本质上是生成式内容,无法保证 100% 符合预期。建议的代码骨架:
try: root = parse_xml(raw_output) except ET.ParseError: # 退而求其次:记录原始输出,人工介入或重新请求 log.error("XML 解析失败,原始输出:%s", raw_output) raiseAPI 调用要做重试和超时控制。网络波动、服务端过载都可能导致请求失败。重试策略使用指数退避,同时设置合理的总超时时间,避免长时间阻塞业务。
6.3 敏感信息与合规边界
如果把真实合同、用户隐私数据传给 API,要先确认数据使用政策是否符合企业内部安全要求。生产环境建议:
- 对敏感字段做脱敏处理。
- 不在日志中打印完整 prompt 和完整响应。
- 控制数据留存周期。
- 遵守最小权限原则:API 密钥只配置在服务端,不写入前端代码。
6.4 关于 Claude Code 与官方工具
目前 Claude 的官方工具链里,Claude Code 也能处理 XML 相关任务,安装和配置方式会随时间变化。如果你在环境中执行claude命令报“无法识别”,通常是工具未安装或 PATH 未配置。
对于这个系列的学习目标(Claude Certified Architect),重点仍然是掌握 API 本身的调用逻辑、提示词组织和数据处理方法。命令行工具只是辅助手段,不建议在还没搞懂 API 基础时优先折腾环境。
7. 总结:从 XML 到更扎实的提示词工程
这一篇围绕 Claude API 中的 XML 展开了系统梳理。你至少应该掌握以下几点:
- XML 标签是组织提示词结构的有效方式,Claude 模型对 XML 语义有较好的理解能力。
- 在 prompt 中用
<contract>、<rules>这种语义标签,能显著降低模型混淆上下文的概率。 - 通过 XML 结构要求模型输出,可以让结果更规整,方便程序解析。
- 解析模型返回的 XML 必须做防御性处理,包括代码块清理、特殊字符转义和异常捕获。
- 工程落地时要考虑重试、超时、脱敏和日志记录。
下一步可以继续学习两个方向:一是 Claude API 的 tool use 机制,把 XML 提示词能力和函数调用结合起来;二是复杂任务的多轮拆解,把长文本、多步骤任务拆成多个带 XML 结构的子任务。这两个方向都是架构师路线中很常见的考点。
希望这篇文章对你有帮助,建议打开编辑器,把第 4 章的代码完整跑一遍,改一改规则和合同文本,观察模型输出和解析结果的变化。真正动手之后,你对 XML 在 Claude API 中的作用会有更直观的感受。如果遇到问题,欢迎在评论区讨论交流,也可以把文章收藏起来,后续需要排查时随时翻看。