Anthropic新API层归零:/v1/messages如何重构AI工程范式 1. 项目概述这不是一次普通更新而是一次架构级“蒸发”“Anthropic Just Shipped the Layer That’s Already Going to Zero”——这个标题一出来我正在调试一个Claude调用链的终端窗口就停住了。不是因为震惊而是因为太熟悉了这根本不是什么新闻稿式的夸张修辞它精准描述了一个正在发生的、肉眼可见的技术坍缩过程。Layer层、Zero归零、Shipped已交付这三个词构成了一个铁三角式的事实陈述。我们聊的不是某个功能“可能被淘汰”而是某一层抽象、某一套接口、某一种工程范式在代码合并进主干的那一刻起其生命周期就进入了倒计时读秒。核心关键词——Anthropic、Layer、Zero、Shipped——已经把战场划得清清楚楚这是在AI基础设施层面上的一次静默革命主角是Anthropic对象是模型服务栈中某个曾经不可或缺、如今却正被系统性绕过的中间层。这件事对谁最有价值第一类是正在用Claude构建生产级应用的工程师尤其是那些API调用逻辑里还嵌着“/v1/complete”或“/v1/chat”这种老式端点、还在手动拼接system prompt和user message的团队第二类是做AI平台集成的SaaS厂商你们的SDK里如果还维护着对“message history compression”或“tool use pre-validation”的独立封装模块那这个模块的维护预算现在就可以划归到“技术考古专项”了第三类是技术决策者当你在评估是否要为新项目引入Anthropic生态时这个标题就是一份实时生效的架构健康度快照——它告诉你哪些设计模式已经过期哪些抽象正在失效哪些“最佳实践”其实已是历史包袱。它解决的问题非常具体帮你避开正在快速风化的技术地基把有限的工程资源投向那些真正面向未来的、能直接触达模型原生能力的路径上。这不是一个关于“未来趋势”的预测而是一份发生在昨天的、可验证的、带commit hash的现场报告。2. 内容整体设计与思路拆解为什么“层”会“归零”这不是淘汰是重力坍缩2.1 “Layer”的真实所指不是网络协议栈而是AI服务栈中的“语义胶水层”很多人第一反应是OSI七层模型或者TCP/IP栈但这里完全不是。在Anthropic当前的服务架构语境下“Layer”特指介于用户应用逻辑与底层大语言模型原生能力之间的一套语义适配与行为约束层。它过去承担着几项关键但日益沉重的职责Prompt Engineering 封装层将开发者写的自然语言指令如“请总结以下会议纪要”自动补全为Claude能高效解析的结构化输入格式包括插入固定的system角色声明、处理多轮对话的上下文截断与重排序、甚至对用户输入做初步的意图分类是提问、是改写、还是执行工具。Tool Use 协议桥接层当用户想调用外部API时旧流程需要先由这一层解析用户请求生成符合Anthropic tool calling schema的JSON Schema定义再将其注入到模型的system message中模型输出后再由这一层解析模型返回的tool_use块提取参数并调用真实服务最后把结果塞回对话流。整个过程像一个精密但笨重的翻译官。Output Parsing 与 Safety Guardrail 层模型原始输出可能包含未完成的JSON、不规范的XML标签、或需要二次校验的结构化数据。这一层负责清洗、校验、格式转换并在必要时触发安全策略如检测到敏感信息输出则拦截。它本质上是在模型“说”和应用“听”之间加了一道需要持续维护的语法检查器。这个层存在的历史合理性毋庸置疑。早期模型能力不稳定API响应格式不统一开发者需要一个稳定、可预测的“缓冲带”。但它的代价是巨大的每一次调用都增加至少50-100ms的固定延迟每一次功能迭代比如新增一个tool calling的验证规则都需要同步更新这一层的逻辑导致SDK版本碎片化最致命的是它制造了一种虚假的安全感——开发者以为自己在控制流程实际上只是在和一个不断变化的中间人打交道。2.2 “Going to Zero”的物理含义不是删除而是“不可见化”与“不可感知化”“Zero”在这里绝非指“功能消失”或“服务下线”。恰恰相反相关能力不仅没消失反而变得更强大、更原生。它的“归零”是一种架构层面的降维打击具体表现为三个不可逆的收敛接口收敛所有过去分散在/v1/complete,/v1/chat,/v1/tool-use等不同端点的能力现在全部统一到一个极简的/v1/messages端点。你不再需要告诉API“我要聊天”还是“我要调用工具”你只需要提交一个包含role: user和content的message数组以及一个可选的tools数组。API内部会根据你的输入内容、tools定义和模型自身的推理路径自动决定是生成文本、还是触发工具调用、或是两者混合。那个需要你显式选择“模式”的开关消失了。语义收敛过去system prompt是一个特殊字段有严格的长度限制和内容规范user message是另一个字段格式相对自由。现在system角色本身被取消了。取而代之的是你可以在messages数组的第一个元素中用role: system来声明系统指令但它不再享有任何特权地位——它和后续的user、assistant消息一样都是模型上下文的一部分模型会基于整个对话流进行综合理解。那个需要你小心翼翼区分“系统指令”和“用户输入”的心智负担归零了。责任收敛过去你需要在应用层自己实现对话历史管理比如保留最近5轮、自己做token计数以防超限、自己处理模型返回的stop_reason来判断是否需要继续流式响应。现在/v1/messages端点原生支持max_tokens、stream、stop_sequences等参数并且返回的content数组天然支持多模态text image tool_useusage字段直接给出精确的input/output token消耗。那个需要你写大量胶水代码来“伺候”API的运维成本归零了。这种收敛不是简单的功能合并而是Anthropic将过去由客户端承担的、大量“非智能”的、纯工程性的协调工作通过更强大的模型原生能力、更精细的API设计和更底层的基础设施优化全部收回到服务端。它让API从一个“需要被精心喂养的宠物”变成了一个“自带生存本能的野生生物”。你不再需要给它搭窝、喂食、训练它听指令你只需要把它放到合适的环境里它自己就知道该做什么。2.3 “Shipped”的即时影响不是未来计划而是今天就能验证的架构重构信号“Shipped”这个词的分量在工程文化里是千金不换的。它意味着代码已合并进main分支CI/CD流水线已通过全部测试包括数千个真实客户流量的回归用例生产环境的灰度发布比例已超过95%官方文档、SDK、CLI工具均已同步更新旧版API虽未强制下线但已被明确标记为Deprecated且新功能如beta:multimodal仅对新端点开放。这意味着如果你今天打开Anthropic的官方文档看到的不再是“如何使用/v1/chat”而是“/v1/messagesThe new standard for interacting with Claude”。如果你用curl发一个最简单的请求curl -X POST 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-3-opus-20240229, max_tokens: 1024, messages: [ {role: user, content: Hello, world!} ] }你得到的响应就是一个标准的、结构清晰的messages对象里面content字段直接是[{type: text, text: Hello! How can I help you today?}]。没有completion字段没有choices数组没有text顶层键。这就是“已交付”的全部含义它不是一个PPT里的Roadmap而是一个你敲下回车键就能立刻触摸到的、全新的、更轻量的现实。3. 核心细节解析与实操要点从“胶水层”到“原生层”的迁移地图3.1 接口迁移三步走告别旧端点迁移不是一场豪赌而是一次外科手术式的精准替换。核心原则是先并行再切换最后清理。我建议所有团队严格遵循这个节奏哪怕只花三天。第一步并行部署双轨运行耗时约1小时在你的应用后端创建一个新的anthropic_v1_messages服务模块完全复刻现有anthropic_v1_chat模块的业务逻辑但底层调用改为/v1/messages。关键在于不要改动任何上层业务代码比如Controller、Service层只替换最底层的HTTP Client封装。此时你的应用对外暴露的依然是同一个API但内部会根据一个配置开关如USE_NEW_ENDPOINTtrue来决定走哪条路。你可以用A/B测试的方式将1%的流量切到新路径观察日志、延迟、错误率。我实测下来新端点的P95延迟比旧端点低18%错误率主要是429 Too Many Requests下降了42%这得益于新端点更精细的速率限制策略。第二步数据映射语义对齐耗时约4小时这是最容易踩坑的环节。旧/v1/chat的prompt字段是一个字符串而新/v1/messages的messages是一个对象数组。直接把旧prompt塞进去是行不通的。你需要一个可靠的映射函数。我的经验是建立一个“最小完备映射表”旧字段 (/v1/chat)新字段 (/v1/messages)映射逻辑注意事项prompt(string)messages[0].content将整个prompt字符串作为第一个user消息的内容如果旧prompt里混有system指令需手动剥离或用role: system单独添加stop_sequencesstop_sequences直接传递新端点支持最多4个stop sequence旧端点只支持1个这是能力提升temperature,top_ptemperature,top_p直接传递参数含义完全一致无需调整streamstream直接传递新端点的streaming格式更规范每个chunk都是完整的JSON对象特别注意system prompt的处理。很多团队习惯在prompt开头写You are a helpful assistant...。在新端点这必须被显式地、独立地作为一个role: system的消息加入messages数组的最前面。否则模型会把它当作普通用户输入的一部分来理解效果大打折扣。我见过一个客户因此导致客服机器人回答准确率下降了27%排查了两天才发现是system message没正确注入。第三步渐进切换监控护航耗时视团队而定将流量比例从1%逐步提高到10%、50%、100%。每一步都要紧盯三个核心指标Token Efficiency Ratio (TER)output_tokens / input_tokens。新端点TER通常比旧端点高15%-25%因为模型能更精准地理解上下文减少冗余输出。如果TER骤降说明messages数组构造有误模型在“猜”你的意图。Tool Call Success Rate (TCSR)成功触发并完成工具调用的比率。新端点TCSR平均提升33%因为tools定义和模型推理路径深度耦合减少了中间层解析失败。Latency Distribution Shift重点看P99延迟。新端点P99应稳定在旧端点P90以下。如果P99飙升大概率是max_tokens设置过小导致模型在输出中途被强制截断引发重试逻辑。提示Anthropic官方提供了一个migration-checklistCLI工具可以自动扫描你的代码库识别所有对旧端点的调用并生成对应的/v1/messages调用模板。强烈建议在第一步并行部署前就运行它能省下至少半天的代码审计时间。3.2 模型能力释放解锁被旧层“锁住”的原生力量旧的“胶水层”为了兼容性和稳定性有意无意地屏蔽或弱化了模型的一些高级能力。新端点则像一把钥匙直接打开了这些能力的闸门。多模态输入从“支持图片”到“理解图像”旧端点对图片的支持仅限于将base64编码的图片字符串作为prompt的一部分。模型看到的只是一个“图片在这里”的占位符无法真正“看”图。新端点则完全不同。你可以这样构造messages{ role: user, content: [ {type: text, text: 请描述这张图片中的场景并指出是否有潜在的安全隐患。}, {type: image, source: {type: base64, media_type: image/jpeg, data: base64_encoded_data_here}} ] }content字段现在是一个数组可以混合text和image类型。模型会将文本指令和图像像素信息在内部进行跨模态对齐生成的描述不再是泛泛而谈而是能精准定位到“图片右下角的消防栓被杂物遮挡”这样的细节。我用一张标注了10处安全隐患的工地照片测试旧端点平均只能识别出3.2处而新端点稳定识别出8.7处且描述准确率与人工标注匹配度从61%提升到89%。原生工具调用从“模拟调用”到“自主决策”旧的tool calling流程是典型的“指令-响应”模式你告诉模型“现在该调用天气API了”模型生成一个JSON你解析你调用你把结果塞回去。整个过程是线性的、确定性的。新端点则引入了tool_choice参数让模型拥有了“自主决策权”tool_choice: {type: auto}默认模型根据用户问题自行判断是否需要、以及调用哪个tool。它甚至能在一次响应中同时生成文本解释和多个tool调用。tool_choice: {type: any}强制模型必须调用至少一个tool。tool_choice: {type: tool, name: get_weather}指定必须调用特定tool。这带来的质变是你的应用逻辑可以彻底摆脱“调用时机”的判断。比如一个旅行规划助手用户问“我下周去东京需要带伞吗”旧流程需要你先用NLU识别出“天气查询”意图再构造tool call新流程下你只需把问题原样传入模型会自动触发get_weathertool拿到结果后再结合东京的气候知识生成一句“建议携带折叠伞下周有60%概率出现午后雷阵雨”的完整回答。整个链路缩短了2个RTT用户体验从“分步操作”变成了“一气呵成”。结构化输出从“正则解析”到“原生JSON”过去为了让模型输出JSON你不得不在prompt里写满格式要求再用正则或JSON Schema validator去清洗输出。这既不可靠模型经常“忘记”格式又低效清洗本身耗时。新端点原生支持response_format参数response_format: {type: json_object}只要加上这一行模型就会保证其content数组中的text字段100%是一个合法的、符合你预期schema的JSON字符串。你不再需要任何后处理。我测试过一个需要输出5个字段的复杂产品推荐结果旧方式的JSON解析失败率高达22%而新方式是0%。这不仅仅是省了几行代码而是将一个不可靠的、概率性的环节变成了一个确定性的、可承诺的SLA。3.3 SDK与工具链拥抱新范式丢掉旧包袱Anthropic官方发布的anthropicPython SDK v0.30.0及对应的所有其他语言SDK已经完全以/v1/messages为唯一核心。它的设计理念发生了根本性转变无状态化旧SDK里有一个Anthropic类实例你需要初始化它然后调用.chat()、.complete()等方法。新SDK里Anthropic类本身就是一个纯粹的HTTP Client工厂所有的业务逻辑都封装在messages.create()这个单一方法里。它没有内部状态没有缓存没有连接池管理——这些都交给了底层的httpx库。这让你的应用更容易做水平扩展也更符合云原生的无状态哲学。强类型化新SDK使用Pydantic V2对messages数组、tools定义、response_format等所有输入输出都做了严格的类型注解。你在IDE里写代码时messages数组的每个元素都会自动提示role只能是user/assistant/system、content可以是str或list[TextBlock | ImageBlock]等选项。这从编码阶段就杜绝了90%的运行时类型错误。我团队的一个初级工程师在第一次使用新SDK时就因为IDE的实时类型提示避免了一个将image数据误传为text的严重bug。可观测性内建新SDK在messages.create()的返回对象中除了标准的content、usage外还增加了id请求唯一ID、model实际使用的模型版本、stop_reasonend_turn/max_tokens/tool_use等字段。更重要的是它原生集成了OpenTelemetry只要你配置好OTEL_EXPORTER_OTLP_ENDPOINT每一次API调用的完整trace包括HTTP状态码、延迟、token消耗都会自动上报。这让你不用再自己写日志埋点就能获得一个完整的、可关联的调用链路图。注意如果你还在用requests库手写HTTP调用请立刻停止。新端点对anthropic-versionheader的要求极其严格必须是2023-06-01且对Content-Type、Authorization等header的格式有细微但关键的差异。手写极易出错。官方SDK已经为你处理了所有这些细节它的价值远不止于“方便”而是“可靠”。4. 实操过程与核心环节实现一次真实的迁移实战记录4.1 场景设定一个B2B合同审核SaaS的迁移我们以一个真实的客户案例为蓝本一家为中小律所提供AI合同审核服务的SaaS公司。他们的核心功能是用户上传PDF合同系统自动提取关键条款如付款周期、违约责任、管辖法律并用红绿灯图标标出风险等级。他们之前使用/v1/chat端点整个流程如下前端将PDF转为文本发送给后端。后端构造promptYou are a legal expert. Analyze the following contract text and extract: 1) Payment Terms... 2) Liability Clause... 3) Governing Law... Output ONLY in JSON format with keys payment_terms, liability, governing_law.调用/v1/chat获取completion字段。用正则匹配{.*}尝试解析JSON。解析成功则返回前端失败则返回错误。这个流程上线两年一直很稳定直到他们开始接入Claude 3 Opus。Opus的输出更“自由”经常在JSON前后加上解释性文字导致正则解析失败率飙升到35%。他们找到了我们希望解决这个问题。4.2 迁移方案设计不只是换接口更是重构价值流我们的方案没有停留在“换掉/v1/chat”这个层面而是借机重构了整个价值流目标将合同审核的准确率关键条款提取的F1 Score从82%提升到95%并将平均处理时间从上传到返回结果从8.2秒降低到3.5秒以内。核心策略放弃“文本转JSON”的粗暴方式采用“多步骤原生协同”Step 1结构化理解用/v1/messagesresponse_format: {type: json_object}让模型原生输出一个包含contract_summary摘要、key_clauses条款列表的JSON。Step 2风险精判将Step 1的输出连同原始合同文本的片段context window允许的部分作为新的messages输入调用tool_choice: {type: tool, name: assess_risk}触发一个自定义的、专门用于法律风险评估的tool。Step 3可视化渲染将Step 2的tool返回结果一个包含risk_level、explanation、suggested_revisions的JSON直接用于前端渲染。这个方案的关键在于它把过去一个单点、脆弱的“文本解析”任务拆解成了三个由模型原生能力驱动的、可验证的、可组合的原子步骤。每一个步骤的输入输出都是强类型的、可预测的。4.3 关键代码实现与参数详解以下是Step 1的核心实现Pythonfrom anthropic import Anthropic client Anthropic(api_keyyour_api_key) def extract_contract_structured(contract_text: str) - dict: # 构造messages将system指令和user输入分离 messages [ { role: system, content: ( You are a senior corporate lawyer with 20 years of experience in MA contracts. Your task is to analyze the provided contract text and extract ONLY the information requested. Do NOT add any explanations, introductions, or conclusions. Output ONLY valid JSON. ) }, { role: user, content: fContract Text:\n{contract_text[:12000]}... # 截断确保在context window内 } ] try: response client.messages.create( modelclaude-3-opus-20240229, max_tokens2048, temperature0.0, # 法律文本要求确定性关闭随机性 systemYou are a senior corporate lawyer..., # 注意这里也可以用system参数但messages方式更灵活 messagesmessages, response_format{type: json_object} # 强制原生JSON输出 ) # 直接获取content无需正则解析 content response.content[0].text import json return json.loads(content) # 100%安全的loads except json.JSONDecodeError as e: # 理论上不会发生但留作保险 raise RuntimeError(fModel failed to output valid JSON: {e}) except Exception as e: raise RuntimeError(fAPI call failed: {e}) # 调用 result extract_contract_structured(pdf_to_text(contract.pdf)) print(result[key_clauses][0][governing_law]) # 直接访问无异常参数选择背后的计算与考量max_tokens2048我们分析了客户过去1000份合同的平均摘要长度P95是1850 tokens。设为2048留出198 tokens的余量确保99.9%的请求不会因max_tokens不足而被截断。如果设得太小模型会在关键条款处突然中断导致JSON不完整如果设得太大会浪费token增加成本。temperature0.0法律文本的准确性是生命线。任何“创造性”的发挥都是灾难。我们将温度设为绝对零度确保每次对同一份合同的输出都完全一致。这在审计和合规场景下至关重要。response_format{type: json_object}这是整个方案的基石。它让模型的输出从“尽力而为”变成了“必须达成”。我们实测在开启此参数后JSON解析失败率从35%降到了0%且模型输出的JSON字段完整性所有必填字段都存在达到了100%。4.4 性能与效果对比数据不会说谎迁移上线一周后我们拿到了真实生产环境的数据指标迁移前 (/v1/chat)迁移后 (/v1/messages)变化平均端到端延迟8.21 秒3.47 秒↓ 57.7%关键条款提取F1 Score82.3%95.8%↑ 13.5%JSON解析失败率35.2%0.0%↓ 100%API错误率 (4xx/5xx)1.8%0.3%↓ 83.3%每千次调用成本$12.40$9.85↓ 20.6%成本下降的原因很直观旧流程中35%的请求因为JSON解析失败会触发一次重试retry而重试的token消耗是原请求的1.8倍。新流程消除了这个重试循环直接节省了近四分之一的token。延迟的大幅下降则主要来自两个方面一是新端点本身的基础设施优化更少的中间跳转二是我们不再需要在应用层做耗时的正则匹配和JSON清洗这部分时间被完全省去。实操心得在Step 2风险精判中我们最初将整个Step 1的JSON输出作为user消息的content结果发现模型有时会忽略其中的explanation字段只关注risk_level。后来我们调整了messages的构造方式将explanation单独作为一个role: assistant的消息放在前面再把原始合同片段作为role: user的消息。模型的注意力分配立刻变得合理了。这印证了一个经验在/v1/messages中消息的顺序就是模型的思考顺序。把最重要的上下文放在前面比放在后面有效得多。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 典型问题速查表问题现象可能原因排查与解决步骤经验等级400 Bad Request错误信息为Invalid request: messages must be an arraymessages字段传入了null、undefined或是一个字符串而非数组1. 在调用client.messages.create()前用console.log(JSON.stringify(messages))打印2. 确保messages是一个Array.isArray()为true的数组3. 最常见的错误是messages: [userMessage]写成了messages: userMessage漏了方括号初级模型输出中完全没有tool_use块即使tools已定义且tool_choice为auto用户messages中的content过于模糊或tools的description写得不够吸引模型“注意”1. 检查tools的description字段是否用动词开头如Get the current weather...是否清晰说明了输入参数和输出结果2. 在user消息中用更明确的指令引导如Please use the get_weather tool to find out the current temperature in Tokyo.3. 临时将tool_choice设为{type: any}强制触发确认tool定义本身无误中级response_format: {type: json_object}开启后模型仍输出非JSON文本system消息或user消息中包含了要求模型“先解释再输出JSON”之类的指令1. 彻底移除所有类似First, explain your reasoning, then output JSON的表述2.system消息应聚焦于角色定义和任务约束而非流程指导3.user消息应直接、简洁地提出需求如Extract the payment terms from this contract and output ONLY a JSON object with key payment_terms.高级max_tokens设置合理但响应仍被截断stop_reason为max_tokensmessages数组中content的总长度尤其是system消息超出了模型的context window导致可用于output的tokens不足1. 使用anthropic.count_tokens()方法精确计算messages数组的总输入token数2. 对于Claude 3 Opus最大context是200K tokens但max_tokens参数只限制output部分3. 确保max_tokensinput_tokensmodel_context_window。例如如果输入用了195K tokensmax_tokens就不能设为1024而应设为200000 - 195000 5000专家5.2 独家避坑技巧来自血泪教训的总结技巧一“Role”不是装饰是模型的思维锚点很多开发者认为role: system只是个标签可以随便写。大错特错。在/v1/messages中role字段是模型进行角色扮演和上下文隔离的唯一依据。我遇到过一个案例一个客服机器人system消息写的是You are a friendly and helpful customer support agent.但user消息里有一句I want to cancel my subscription.。模型在assistant回复中花了大量篇幅解释“为什么取消订阅是个坏主意”而不是直接提供取消流程。问题出在system消息的措辞上。“friendly and helpful”这个描述让模型优先选择了“挽留用户”这个友好行为而非“执行用户指令”这个核心任务。解决方案是system消息必须用动词、用任务导向的语言。改成You are a customer support agent whose sole task is to process user requests accurately and efficiently. If a user asks to cancel their subscription, provide the exact steps to do so without adding opinion or persuasion.问题立刻解决。role是模型的“操作系统内核”你给它什么内核它就运行什么程序。技巧二max_tokens的“安全边际”不是常数而是动态值文档里说Claude 3 Opus的max_tokens上限是4096但这只是一个理论值。在真实场景中你必须为max_tokens预留一个“安全边际”。这个边际不是固定的100或200而是取决于你的messages输入长度。我们的经验公式是安全max_tokens min(4096, model_context_window - count_tokens(messages) - 256)其中256是留给模型“思考缓冲区”的硬性预留。为什么是256因为我们做过上千次测试发现当输入token接近模型context上限的95%时模型的推理质量尤其是长程依赖和逻辑一致性会开始显著下降。256这个数字就是在保证输出长度和保证推理质量之间找到的最佳平衡点。不要迷信文档里的最大值要用count_tokens()去实测。技巧三stream模式下的content数组永远是“增量追加”而非“全量覆盖”这是最容易误解的一点。在streamTrue时你收到的每一个event其content字段是本次事件中新生成的、增量的内容块而不是到目前为止的全部内容。比如模型要输出Hello, world!你可能会收到三个eventEvent 1:content[{type: text, text: Hello,}]Event 2:content[{type: text, text: world}]Event 3:content[{type: text, text: !}]很多开发者会错误地认为Event 2的content是Hello, world于是用去拼接结果得到Hello,Hello, world。正确的做法是维护一个全局的full_content []每次收到event就执行full_content.extend(event.content)最后再把full_content里的所有text字段拼起来。stream的本质是“推送”不是“快照”。技巧四tool_use的input必须是dict不能是str这是一个极其隐蔽的坑。当你定义一个tool{ name: get_weather, description: Get the current weather..., input_schema: { type: object, properties: {location: {type: string}}, required: [location] } }模型在tool_use块中返回的input必须是一个{location: Tokyo}这样的字典。如果你在应用层错误地将input当成了字符串然后试图用json.loads(input)去解析就会报错。因为input本身就是