ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Claude API的Conversations与System核心解析:认证备考与工程实践

Claude API的Conversations与System核心解析:认证备考与工程实践 如果你正在准备 Claude Certified Architect你一定看过官方列出的先决知识清单。往届备考的人常告诉新人认证考的不是 API 参数默写而是架构判断。这句话没有错但它掩盖了一个事实——所有架构判断都建立在你对 API 底层机制的真实理解上。尤其是 Conversations 与 System 这两个概念看起来像两个平平无奇的术语实际上决定着你设计的对话系统是否稳定、可扩展、可维护。很多人在第一次使用 Claude API 时都会经历这种挣扎调用messages.create返回了结果就以为已经掌握对话开发了。但一旦要面对真实业务问题比如“在电商客服场景里怎么让模型在整个会话中始终遵守价格规则”“用户聊了 50 轮之后上下文溢出怎么办”“System 里的规则能不能被用户绕过”——很多人就卡住了。这些问题的答案全部藏在 Conversations 和 System 的细节里。这篇文章是 Claude Certified Architect 前置知识系列的第 2 篇。文章不会停留在概念层面而是会把两个概念拆开讲清楚它们分别解决什么问题、在 API 中如何表达、在真实项目中怎么管理并用可运行的 Python 代码演示一套从单轮到多轮对话的完整实现。读完之后你不仅能在备考时拿到这部分知识也能在项目里少走不少弯路。1. 这篇文章真正要解决的问题Claude Certified Architect 的考查方式以方案设计和最佳实践判断为主。它不会直接考你messages.create有多少个参数但会给你一个客户案例要求你判断这个场景应该用多轮对话保存历史还是应该每次按模板拼接请求System Prompt 应该放在哪一层来约束输出当你需要为自己的判断给出理由时只有真正理解 Conversations 与 System 在 API 内部的工作方式才能给出站得住脚的答案。这篇文章要解决的具体问题有三类。第一类API 调用停留在“能跑”阶段。很多人会把 Claude API 理解成“发一条请求、拿一个结果”的简单过程但这忽略了无状态 API 中多轮对话的本质——客户端把历史消息拼好再交给模型。不理解这一点后期做对话系统时会出现上下文错乱、角色丢失的问题。第二类System Prompt 只会简单填写。不少开发者知道 API 里有一个system参数但不知道它和普通用户消息在作用范围与优先级上的本质区别因此会把本应放在应用层的逻辑也塞进 System 里导致规则不稳定、输出不可控。第三类没有成体系的排查思路。当遇到上下文超长、SSL 证书报错、Rate Limit、System 规则被绕过等问题时很多人只会反复重试而没有一个从日志、请求体、环境三个维度去定位问题的框架。本文适合两类读者。第一类是准备 Claude Certified Architect 认证的开发者需要把 API 前置知识补扎实第二类是用 Claude API 做过原型、但尚未上过生产环境的同学想搞清楚对话状态和系统提示到底应该如何管理。2. Claude API 基础架构与 Conversations 模型在深入 Conversations 和 System 之前必须先建立一个总体认识Claude Messages API 是一个典型的无状态 API。所谓无状态是指每一次 API 调用之间没有任何隐式关联。模型不会记得上一次你问过什么也不会记得这次请求之前的任何对话内容。它拿到什么输入就基于这个输入独立生成输出。这个设计和我们日常使用 ChatGPT、Claude 网页版时的体感完全不同。网页版有服务端为你保存会话所以你可以从一个会话中断的地方继续聊。但在 API 层没有这个“会话档案”。所谓多轮对话实际是客户端把整个对话历史作为messages数组传给 API。在 Claude 的 Messages API 中一次完整请求主要由顶层参数组成model模型 ID。messages对话消息数组数组中的每个元素带role和content。system可选的系统提示。max_tokens本次生成的最大 token 数。temperature、top_p等采样参数。其中messages数组就是 Conversations 的载体。它的结构并不复杂但有很多容易被忽略的规则。例如在正常多轮对话中role应当是user和assistant交替出现第一条通常由user发起如果你手动填充assistant的回复要保证这些回复是模型本应生成的文本而不是业务系统写死的文案。从工程视角来看无状态设计带来的最大变化是状态管理责任的转移。你可以通过下面这个对比理解传统有状态会话Claude Messages API服务端保存 session 与历史服务端无状态历史由客户端维护客户端传 sessionId 恢复会话客户端将完整 messages 数组传给 API服务端控制上下文窗口开发者自行决定哪些历史放入请求调试依赖服务端日志请求可在本地完整回放、修改、验证这个设计看似把负担推给了开发者实际上也带来了巨大灵活性。你可以在本地自由裁剪、改写、回放任意一段历史而不受服务端会话过期机制的约束。这对工程测试和问题复现非常有利也是 Claude API 在多轮对话场景中非常可控的原因之一。3. 核心概念一Conversations 对话管理的本质3.1 为什么对话历史必须是数组如果把 Claude 理解成“输入一段文本、输出一段文本”的函数那么messages数组就是这段输入文本的结构化表达。数组里的每一项代表对话历程中的一个发言。模型通过观察这些发言理解当前是在什么样的对话背景下回答你。正因为如此数组顺序非常重要。消息的顺序一旦颠倒模型的语义理解会完全改变。你可以把对话历史想象成一场会议纪要必须按发言时间排列主持人才能知道讨论进行到哪一步。如果突然把后面的发言插到前面整场讨论的逻辑就乱了。在代码层面这个数组就是普通 Python 列表。列表里每一项是一个字典包含role和content。每次发起新请求时把旧的列表原样带上再追加一条新的user消息即可。3.2 role 是对话的骨架messages数组中每个元素都有role字段。在 Claude API 中常见角色包括user代表人类用户或调用方的请求。assistant代表模型之前的回复。tools代表工具调用的结果在 Agent 场景使用。role存在的意义是让模型在推理时区分“哪些话是对方说的、哪些话是我自己说的”。这个区分之所以重要是因为模型在生成本次回复时会参考assistant自己之前的回复以保持风格连续同时会更认真地回应最新的user消息。如果你在构造历史时把role写错模型很容易出现角色混乱。实际操作中一个容易犯的错误是把业务系统的通知文案也塞进user消息里。比如你把“订单状态已更新已发货”这种系统通知放在userrole 中模型可能会把它当成用户提出的话从而在后续回答中产生错误推断。更合理的做法是把这类事实性内容放在assistant的历史回复中或者用 System Prompt 明确告诉模型“系统通知会以 user 消息的形式出现”。3.3 上下文长度与裁剪策略每个模型的上下文窗口都是有限的。Claude 系列模型的上下文较大能容纳很长的历史但长上下文会带来三个实际问题Token 费用随历史长度线性增长。请求处理时间变长。模型在长上下文中对后续指令的注意力可能被稀释。因此工程上几乎必须实现裁剪策略。常见的做法有三种策略实现方式适用场景按轮数裁剪只保留最近 N 轮 user/assistant 消息大多数客服、问答场景按字符/Token 限制裁剪累计长度超限后丢弃最旧消息上下文物料较长的场景语义摘要裁剪先把旧消息压缩成摘要再放入上下文长期会话、需要记忆关键结论的场景前两种简单直接适合大多数场景第三种更适合长期会话但会增加一层摘要调用成本。在实际项目中通常会把三种策略组合使用最近几轮保留原始消息更早的内容压缩成摘要。3.4 两个容易踩的坑第一个坑为了省 Token 只传用户问题不传 assistant 历史。模型并非不能回答但它会失去对“自己之前回答风格”的记忆也很难知道你之前已经纠正过它什么。这会导致多轮对话出现前后矛盾。第二个坑每次都把完整历史原样带上直到突然触发上下文上限。更好的做法是动态维护一个消息池达到阈值后自动丢弃最旧的非关键消息。不要等到 400 错误出现才去处理上下文超长的问题。4. 核心概念二System Prompt 的定位与设计4.1 System 解决什么问题System Prompt也就是 Claude API 中的system参数用来定义模型在整个会话周期内的行为基座。它通常用来做四件事角色设定告诉模型它是客服、代码审查员、翻译还是数据分析师。输出规范规定回答格式比如必须使用 Markdown、必须输出 JSON、不得输出推理过程。边界约束告诉模型不能回答哪些问题、遇到敏感话题时如何拒绝。领域知识补充把业务背景和私有规则放进 System让模型在每一轮都带上这些规则。与messages数组中的消息不同System Prompt 不属于某一条具体发言它更像舞台上的“总导演”贯穿整个演出过程。每一次 API 调用System 都会自动被注入到请求的最前面不需要你在messages中重复拼装。4.2 与 User Message 的差异很多初学者容易把 System Prompt 理解成“写在最前面的一条 user 消息”这是不对的。两者的差异可以用下面的表格来说明维度System PromptUser Message存放位置顶层system参数messages数组内作用范围整个会话周期本条消息及后续上下文典型用途角色、规范、边界、业务规则具体问题、任务、用户输入是否随对话长期保留每次调用自动带上需要客户端在messages中维护对模型行为的影响全局约束局部指令从 API 设计上看System 独立于对话历史之外这意味着它不会和用户消息混在一起也不容易被历史中的某条内容“冲淡”。开发者可以只修改 System而完全不动messages就调整模型的行为。4.3 System 不是安全边界这是一个很重要的判断。很多团队在做企业级应用时把“敏感信息不得外泄”“不要回答竞争对手问题”这类约束只写进 System Prompt认为模型一定会严格遵守。这个假设是有风险的。从原理上看System Prompt 只是模型输入的一部分。模型的目标是生成“概率上最合理的文本”而不是“严格遵守一组外部规则”。虽然现代模型经过对齐训练在绝大多数情况下会遵守 System 中的合理要求但对抗性输入、多轮诱导、注入攻击仍然可能产生影响。在安全相关场景必须把权限控制、数据过滤、内容审核放在应用层而不是只依赖 System。换句话说System Prompt 是行为引导不是访问控制。建设安全体系时要把它当成第一道防线而不是唯一防线。4.4 常见的 System Prompt 设计模式日常开发中比较实用的 System Prompt 结构是“定位 能力 边界”三段式。定位说明模型的角色和任务目标。能力说明模型可以做什么、输出采用什么格式。边界说明模型不可以做什么、遇到越界问题时应如何处理。另一种常用模式是“输出格式约束”。如果希望模型返回 JSON可以在 System 中明确规定“只输出 JSON不输出任何说明”。如果你希望模型在特定场景下给出示例还可以在 System 中加入少量示例引导模型按照示例的格式回答。5. 环境准备API 基础配置与连通性验证在写代码之前需要把环境准备好。本节所有操作都基于 Python 环境建议使用 Python 3.9 及以上版本。第一步安装 Anthropic 官方 SDKpip install anthropic第二步设置环境变量。API Key 属于敏感信息不要直接写在代码仓库里建议通过环境变量注入export ANTHROPIC_API_KEYyour-api-key第三步验证 Python SDK 是否已正确安装# check_sdk.py import anthropic print(anthropic sdk version:, anthropic.__version__)如果安装正常运行后会输出 SDK 版本号。如果报ModuleNotFoundError说明anthropic包没有安装成功需要重新执行安装命令。第四步验证 API 连通性。可以用 curl 直接调用一次最小的 Messages API确认本机能够正常访问 Anthropic 的 API 服务curl -s https://api.anthropic.com/v1/messages \ -H x-api-key: $ANTHROPIC_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d {model:claude-sonnet-4-5,max_tokens:16,messages:[{role:user,content:ping}]}其中anthropic-version是 API 版本标识实际应参考官方最新文档。model字段应填写账号可用的模型 ID不同时期控制台可用的模型列表可能不同。如果这一步返回了 JSON 响应说明网络和 Key 都没有问题。如果出现连接超时、SSL 证书错误或 401原因通常集中在网络环境和 API Key 配置上可以先检查环境变量是否已正确导出。6. 完整示例从单轮到多轮对话实现6.1 最小 System Prompt 调用先写一个最小可运行的示例这个示例包含system参数和单条user消息# demo_basic.py from anthropic import Anthropic client Anthropic(api_keyyour-api-key) resp client.messages.create( modelclaude-sonnet-4-5, # 以控制台实际可用模型为准 max_tokens1024, system你是一名资深后端工程师。回答时要先给结论再给解释。, messages[ {role: user, content: 请说明 API 网关的核心作用。} ], ) print(resp.content[0].text)这段代码的逻辑很简单把system放在顶层参数中messages里只有一条用户提问。模型会按照 System 中设定的“先结论再解释”的方式回答。运行后如果一切正常你会看到一段类似“API 网关的核心作用是统一接入、协议转换、流量控制……”的回答。如果输出风格没有遵循“先结论再解释”说明 System 的约束力度不足可以把要求写得更明确比如“回答第一句必须直接给出结论此后才允许展开”。6.2 多轮对话历史管理真实应用几乎不会只有一轮对话。下面实现一个简单的Conversation类用来维护多轮对话状态# conversation.py class Conversation: def __init__(self, system_prompt): self.system system_prompt self.messages [] def add_user(self, content): self.messages.append({role: user, content: content}) def add_assistant(self, content): self.messages.append({role: assistant, content: content}) def get_payload(self): return { system: self.system, messages: self.messages, }使用这个类发起多轮对话# demo_multi_turn.py from anthropic import Anthropic from conversation import Conversation client Anthropic(api_keyyour-api-key) conv Conversation( system_prompt你是一个项目排期助手。请回答得简洁、具体并给出可执行的时间建议。 ) # 第一轮用户提问 conv.add_user(我们计划下周三发布 v2.0但这周还有三个功能没有完成。怎么办) resp client.messages.create( modelclaude-sonnet-4-5, max_tokens1024, systemconv.system, messagesconv.messages, ) assistant_reply resp.content[0].text print(Assistant:, assistant_reply) conv.add_assistant(assistant_reply) # 第二轮用户追问 conv.add_user(那如果只能砍掉一个功能你会建议砍哪个) resp client.messages.create( modelclaude-sonnet-4-5, max_tokens1024, systemconv.system, messagesconv.messages, ) print(Assistant:, resp.content[0].text) conv.add_assistant(resp.content[0].text)这段代码的核心在于每次发起新请求时都要把conv.messages的完整历史传给 API。模型之所以在第二轮能记住第一轮讨论的内容完全是因为客户端把历史带上去了。如果你把conv.messages替换成只包含最新一条 user 消息模型就会“失忆”。在实际项目中对话历史会越来越长。为了避免上下文超长这里提供一个简单的裁剪函数# trim.py def trim_conversation(messages, max_messages12, max_chars6000): 保留最近的 max_messages 条消息并限制总字符数。 注意这个裁剪策略会丢弃最旧的消息适合普通问答场景。 trimmed [] total_chars 0 for msg in reversed(messages): content msg.get(content, ) length len(content) if total_chars length max_chars: break trimmed.insert(0, msg) total_chars length if len(trimmed) max_messages: break return trimmed裁剪函数在处理超长会话时非常有用。它的思路是从最新消息往前遍历直到累积长度超过阈值或消息条数达到上限然后保留下这些最近的消息。对于还需要保留早期关键信息的场景建议在裁剪之前先对旧消息做一次摘要把摘要作为一条user或assistant消息放在历史最前面。6.3 结构化 JSON 输出在企业应用中经常需要模型输出 JSON 而不是自然语言。System Prompt 在这里可以发挥很大作用# demo_json_output.py import json from anthropic import Anthropic client Anthropic(api_keyyour-api-key) resp client.messages.create( modelclaude-sonnet-4-5, max_tokens1024, system( 你是一个配置解析助手。 只输出 JSON不要输出任何解释性文字。 JSON 必须包含 key、value 和 description 三个字段。 ), messages[ {role: user, content: 请解析这句话服务名 order-service端口 8080启用 SSL。} ], ) text resp.content[0].text.strip() print(Raw output:, text) # 清理可能存在的 Markdown 代码块标记 if text.startswith(): lines text.split(\n) lines [line for line in lines if not line.startswith()] text \n.join(lines).strip() config json.loads(text) print(config)这里真正容易踩的坑是模型可能返回带 json 标记的 Markdown 代码块直接json.loads会报错。所以在解析之前先把外层的代码块标记清理掉。更稳定的做法是在 System 中明确定义“禁止使用 Markdown 代码块直接输出 JSON 对象”并配合代码清理双保险。7. 运行结果与效果验证完成上述示例后可以从三个层面验证结果是否正确。第一层检查 HTTP 状态码。如果请求成功SDK 不会抛出异常resp对象中会包含模型响应。如果失败SDK 会抛出包含错误码的异常常见的错误码包括401、429、400等。第二层检查resp.stop_reason。正常对话结束时stop_reason通常为end_turn。如果出现max_tokens或其他截断原因说明生成结果被提前打断需要调大max_tokens或检查输出长度。第三层验证 System 是否真正生效。这里有一个很值得做的实验把 System 设定为“无论用户问什么都先输出‘收到’”然后把一段历史消息传给模型。如果模型第一句确实输出“收到”说明 System 在起作用如果模型直接忽略了 System则需要检查 System 是否被写错了位置或者用户消息中是否有更强的指令覆盖。对于多轮对话可以这样验证 history 管理是否正确第一轮让模型记住一个关键信息例如“我住在上海喜欢爬山”第二轮提问“我住在哪里喜欢什么运动”。如果模型能正确回答说明messages历史传递成功如果回答错误优先检查conv.messages是否包含了第一轮的 assistant 回复。验证失败时第一步不是改代码而是打印请求体。把system和messages完整打印出来确认 API 实际收到的是什么。大多数对话逻辑错误都能通过检查请求体发现要么是 System 位置不对要么是role写错要么是历史消息缺失。8. 常见问题与排查方法在实际使用 Claude API 时以下问题出现频率较高可以对照排查。问题现象可能原因排查方式解决方案API 返回 401 authentication_errorAPI Key 未设置或无效检查环境变量和请求头重新生成 Key通过环境变量注入请求返回 429 rate_limit_error请求频率或配额超出查看响应头中的 ratelimit 字段退避重试申请更高配额invalid_request_errorroles 不合法messages 数组格式错误连续同 role 或空消息打印 messages 数组检查 role 次序保证 user/assistant 交替删除空内容上下文超长报 token 超限messages 累计 token 超出模型窗口查看错误信息中的 token 统计裁剪历史或先做摘要压缩报错 unable to connect to api: self-signed certificate开发机自建代理、抓包工具替换了 CA 证书检查系统代理和证书链正确配置系统 CA 证书生产环境不应跳过证书校验Claude Code 卡在 waiting for api response网络连接不稳、Key 权限不足或高负载打开 verbose 日志观察请求状态检查网络确认 Key 有对应模型权限重试System 规则被用户绕过只依赖 System 做安全控制用对抗性输入做测试在应用层增加权限、过滤和审核针对两个高频问题这里补充一些说明。429 rate_limit_error通常不是代码错误而是账号配额问题。遇到时不要立刻改代码先查看响应头里的request-id和ratelimit-*字段确认当前剩余额度。连续高频调用时推荐使用指数退避重试而不是固定间隔重试避免加重限流。self-signed certificate错误在开发环境里非常常见通常是本地代理或抓包工具注入了一个不被信任的证书。你可以把SSL_CERT_FILE环境变量指向企业 CA 证书或者在 Python 中显式配置一个合法的httpx.Client。但要注意任何“关闭证书校验”的做法都不应该进入生产环境生产环境应该使用正确的证书链配置。9. 工程最佳实践System 与 Conversations 的落地建议9.1 System Prompt 版本化与测试System Prompt 是模型行为的关键约束应该像代码一样纳入版本管理。建议把它独立成文件例如prompts/order_service_system_v1.md并在代码中按版本引用。每次修改 System都应该配套回归测试用固定用例验证输出是否仍然符合预期。否则很容易出现“改了一行 System线上行为完全变了”的情况。9.2 会话存储与恢复由于 API 是无状态的多轮会话状态必须由业务系统保存。建议为每次会话分配一个conversation_id把messages历史持久化到数据库或 Redis。当用户中途刷新页面或重新发起请求时从存储中读取历史再传给 API。这里要注意不要把system也存进历史记录它是全局配置应该由服务端统一注入。9.3 上下文裁剪与摘要当对话轮数超过阈值时不要简单删掉全部旧消息。更好的做法是保留最近 N 轮原始消息。将更早的消息调用一次模型生成摘要
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进