ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

用Claude Opus5构建大模型中转应用平台:架构设计与踩坑复盘

用Claude Opus5构建大模型中转应用平台:架构设计与踩坑复盘 做过不少技术项目但像Claude Opus5开发中转应用平台这样从立项到交付完整走下来还把项目文档写到接近5万字的真不算多。先解释一下背景这既不是单纯的模型调用示例也不是普通接口开发任务而是一个面向企业内部多个AI应用的大模型统一接入平台。我们叫它中转应用平台核心职责是把各类模型接口包括Claude Opus5收敛成一套统一API帮后端业务系统屏蔽掉协议差异、密钥管理、计费统计这些脏活累活。如果你正准备搭建类似的东西或者想看看用Claude Opus5配合大项目开发到底靠不靠谱那这篇内容应该能给你省下不少弯路。我会把这份项目文档里最重要的设计决策、模块实现、踩坑经过和工具使用心得全部拆开讲尽量做到可以照着落地。当年我接手时公司已经在用多个AI大模型的服务但每个应用的接入方式都不一样。有人直接用了模型官方SDK有人自己封装HTTP请求每个月对账时各个部门报上来的调用量和费用口径根本对不上。更头痛的是某个模型服务出现波动时完全没有备用路由业务直接被拖死。所以这个项目的目标从一开始就很明确做一个统一的模型调用中间层让业务方只对接我们这一个平台剩下的路由策略、密钥隔离、配额限制、日志审计、成本分摊全部在平台内解决。在正式写方案之前我带着团队做了一轮需求访谈发现这个中转平台其实要同时满足三类人的需求。对开发同学来说它必须稳定、快速、好用最好能兼容OpenAI的消息格式这样他们原有的代码改动最小。对运维同学来说它要有足够的可观测性出了故障能快速定位是网络问题、模型问题还是业务调用姿势问题。对管理层来说它要能回答一个很核心的问题每个月花了多少钱花在了谁身上值不值。带着这些约束去设计架构才不会做出一堆没人用的API。1. 项目定位与整体设计思路1.1 中转应用平台到底解决什么问题很多第一次接触这个场景的人会问直接接模型官方的SDK不好吗为什么非要搞一个中转层一次真实的故障就把这个问题说明白了。当时我们有个模型服务因为并发过高开始出现大量429限流错误业务侧又没有统一的熔断机制结果半个平台的功能都受了影响。如果当时有一个中间层就能在限流刚冒头的时候自动切换到其他可用模型或者进入降级缓存模式用户基本无感知。中转应用平台的本质是一个面向大模型调用的API网关但比传统API网关多做几件事。传统网关处理的是结构固定的HTTP接口而大模型平台要面对的是流式响应、动态token数量、不同的消息格式、多租户配额等一系列偏业务的概念。它不是简单把请求转发出去而是要理解这个请求在调用什么能力需要什么样的上下文窗口该如何计费以及如何在模型之间的切换过程中保持会话状态一致。从公司治理角度看统一接入还有一个隐形收益密钥安全。以前每个业务组各自保存一套模型API密钥密钥散落在代码仓库、配置文件、甚至是前端页面里。平台建成之后模型服务的上游密钥只存在于平台的配置中心业务侧拿到的都是平台签发的次级密钥哪怕泄露也只是某个租户的权限能被快速吊销不会波及整个账号。这一条在我们最终的安全审计里加分不少。1.2 为什么用Claude Opus5辅助这套平台的开发这其实是一个工具选型问题。项目刚启动时真正要干的活不止是写代码还包括写清楚一堆文档需求说明书、架构设计、编码规范、接口定义、部署手册、验收标准。一个大项目的文档如果全靠人写相当耗时。我当时就做了一次实验性质的选择让Claude Opus5作为主力的协作模型参与平台开发过程中的文档生成与代码评审看它能不能把整理资料、起草方案、生成代码雏形的效率拉起来。实际用下来Claude Opus5对长文本的理解和处理能力确实派上了大用场。最明显的一点是它可以一次处理超大段上下文不会聊到一半就忘掉前面的约束。我们的平台本身要适配多个模型协议各种参数细节非常多平时给普通对话模型发一段需求后常常会生成一套逻辑上自洽但和实际接口对不上的代码但用Claude Opus5时我习惯把官方接口文档的最新片段直接粘贴进去它能很快意识到哪些字段是流程上的必要参数哪些是不影响主链路但会影响计费的细节参数。这一点到了整理项目文档阶段尤其重要因为它能把零散的设计会议记录整理成结构化的章节甚至能指出前后文里互相矛盾的技术决策。有人问我用这么强的模型去做文档整理和基础代码生成是不是大材小用我的回答是在当前这个项目周期里一点都不浪费。平台这种系统性的工程瓶颈往往不是能写多少行代码而是团队能不能在同一个清晰的技术蓝图下协作。Claude Opus5在这里扮演了一个很好的文档合伙人角色它能把一些重复劳动消化掉让人专注去处理业务判断和架构决策。2. 核心模块拆解与技术方案2.1 统一网关与动态路由设计平台的第一个核心模块是请求网关负责接收外部应用发来的所有模型调用请求。在接口协议的选型上我们没有搞一套全新的自定义格式而是选择了兼容当前生态最广泛的OpenAI消息格式。这样做的好处很直接业务方原来可能写的是某个模型厂商的SDK迁移到我们平台时只需要把base_url改成平台网关地址把API key改成平台签发的key大部分代码可以原样跑起来。协议兼容只是第一步真正的技术难点在路由层。同一个请求进来之后系统要根据业务方指定的模型名找到一条可用的上游链路。这里的路由并不是简单的映射表而是包含优先级、权重、健康状态、成本优化等多重策略。举个例子业务方如果请求opus5-chat这个逻辑模型名运维侧可以在路由配置里把80%的流量指向高性能的主用节点另外20%指向成本更低的备用节点同时设定当主用节点连续返回错误时自动切换备用。实现上这里用了基于一致性哈希和健康检查相结合的路由算法既保证同一会话尽量打到同一个上游模型以维持上下文连续性又能在故障时快速摘除异常节点。这里我建议把模型名和实际提供者做两层配置隔离。简单说平台里要有一个逻辑模型名以及一个实际提供者实例列表。业务方永远只感知逻辑模型名后端的模型服务地址、模型版本升级、甚至从一家模型服务商换到另一家都可以在后台动态调整不打扰业务方。这也是中转平台相比直连模型最有价值的地方之一。2.2 多租户密钥管理与安全控制中转平台的用户不是自然人大C而是多个内部业务系统。每个业务系统就是一个租户。租户管理模块负责维护这些系统的身份信息和资源配额。最初我们用的是最简单的共享密钥方式也就是所有业务系统共用一个平台级API key开发流程是简单了但一旦某个调用方出现了超频、误调用甚至是代码泄露谁也说不清请求是从哪个系统发出来的。后来我们把密钥模型重新设计了一把。平台会给每个租户签发一对或多对API key这对key对应唯一的租户IDtenant_id。请求进来后网关会先解析API key得到租户信息再走后面的配额校验和路由逻辑。这里有一个容易忽略的点入库的API key不能明文存储。我们用的是加盐哈希存储的方式只存密钥摘要用户创建密钥时展示一次明文之后就再也查不到了。这样就算数据库被拖库攻击者也拿不到真实密钥。更细一点安全控制还做了双维度限制。第一维度是身份识别也就是这个请求有没有权限调用平台。第二维度是资源限制包括每分钟请求数、每分钟token数、单个模型的最大并发数、单个月的费用上限。在限流算法上我们最终没有只依赖简单的令牌桶而是结合了滑动窗口计数因为大模型请求的token消耗差异巨大一个请求可能消耗几百token另一个请求可能消耗十几万token按请求次数限流根本防不住成本失控。2.3 计费与配额系统为什么必须自研计费系统的复杂度在第一版迭代时被严重低估了。最开始我甚至考虑过直接对接模型官网后台的账单然后手工按租户分摊。后来发现根本行不通因为不同业务方走的是同一个上游账号平台如果不去记录每个请求的实际token消耗后续根本没有办法做内部结算。最终还是决定在平台内自研一套计费模块。它做的事情是在每次模型调用完成后把上游返回的usage信息包括输入token数、输出token数、缓存命中token数和本次请求对应的租户、逻辑模型名记录下来再套用每个租户的计费规则生成费用明细。这里要注意的是不要以为所有模型返回的usage字段都一样不同来源的接口有的返回prompt_tokens/completion_tokens有的返回input_tokens/output_tokens还有的会有一个单独的缓存读取token字段如果不做字段归一化后续数据统计一定会出乱子。配额控制也并到计费模块里一起实现。我们给每个租户设置了一组配额维度日调用次数上限、日token上限、月费用上限。任何一个配额被触达时平台返回特定的错误码然后由业务方决定是提示用户还是调整配额。在实际开发中有个棘手的地方是费用核查的时效性因为模型调用是异步的token统计信息往往要在流式结束之后才能拿到。所以我们做了一版在途额度预占 最终按实结算的机制预占时不扣死额度只冻结一个预估费用响应完成后再用准确数字结算并退还差额。这个机制极大减少了超额调用的风险但也给账务模块带来了状态管理的复杂度。2.4 缓存层与流式转发实现缓存不是一个所有请求都能走的路径但对于一些高度重复的请求它可以明显降低成本和延迟。我们的平台主要缓存两种东西一种是非流式请求的完整响应比如一些固定Prompt的标签分类请求可以用Prompt作为缓存key的一部分命中后直接返回历史结果另一种是语义层面的相似问题检索需要把用户的输入向量化后去向量数据库做近似匹配这一步成本太高所以只对部分场景开放。流式转发是整个平台技术含量最高的一部分。大模型接口为了降低首字延迟普遍使用SSEServer-Sent Events的方式把结果一个个吐出来。中转平台在中间承接时必须保证客户端收到的也是一个实时的流而不能等上游全部响应完再一次性吐给客户端。这是很多初版实现会踩坑的点因为中间任何一个环节开启了缓冲代理或者代码里用了非流式的HTTP客户端发起上游请求都会导致首字延迟暴增。正确做法是用异步HTTP客户端逐块读取上游数据然后把数据块通过同步流的方式写给下游客户端中间不做累积缓冲只做格式微调。3. 实操过程与关键实现细节3.1 开发环境搭建与技术栈选择技术栈的选择上我们用的是Python FastAPI作为网关主体搭配httpx的AsyncClient做上游调用再加PostgreSQL存储租户和账单数据Redis做限流计数和缓存。选择FastAPI而没有选用Java或Go主要是考虑到团队在这个项目里要高频处理模型消息结构、JSON Schema、流式响应等动态类型数据Python在这些场景下开发效率最高而且模型相关的生态库通常最先出Python版本。运行环境上没有搞太复杂核心服务拆成了四个进程网关API服务、异步任务worker、定时对账任务、以及管理后台。网关API服务负责所有在线请求worker处理日志落库和计费异步消息对账任务跑在夜间去校准上游用量和本地记录的偏差。开发阶段用Docker Compose把整套依赖拉起来数据库、Redis各占一个容器本地一键启动联调效率非常高。生产部署时则通过K8s进行扩容把网关服务设置成无状态实例水平拓展完全没问题。依赖版本管理这个细节提醒一下。大模型相关SDK更新频繁但有些版本之间的消息结构不兼容。我们不是把官方SDK直接打进平台代码里而是在平台和上游之间保留了一个薄薄的adapter层所有上游服务的调用参数都封装在自己的扩展模块里。这样即使官方SDK版本更新也不至于影响平台入口的稳定性。3.2 使用Claude Opus5组织项目文档的工作流项目进行到第二周我发现文档产出的速度成了瓶颈。设计文档、接口说明、操作手册、答辩PPT每份材料都要翻阅聊天记录和代码注释效率太低了。于是我把工作方式换成了先让Claude Opus5搭框架人工再填细节的模式。具体流程是这样的每一份重要文档开始前先把自己在会议里记录的几条核心结论原文粘贴给Claude Opus5然后要求它列出文档大纲指出哪些决策会影响后续模块。拿到大纲后我会先审核一遍把不准确的地方纠正掉再让它针对每个章节展开写。Claude Opus5有一个优势就是可以一次性给一个很长的指令而不丢失前文约束所以我通常会把平台的整体架构一大段说明全部放在对话开头之后生成出来的内容基本不会跑偏。这里分享一个经验别直接让它从零写一个完整版本那样输出的内容虽然语句通顺但经常出现模棱两可的表述。更好用的方式是先做一轮关键术语表让它把项目里涉及的所有概念下一个精确的定义比如逻辑模型名、实际提供者、租户、配额、在途额度、缓存命中token。术语统一之后后续所有文档都由同一套术语体系衍生出来前后一致性立刻提升了一个档次。这份术语表在最终的项目文档里也独立成了一个章节作为读者理解整个平台的入口。3.3 网关转发核心代码演示这里给出一段简化后的核心转发代码展示平台如何接收一个OpenAI格式的请求经过认证与路由之后转发到上游并以流式方式返回结果。from fastapi import FastAPI, Request from fastapi.responses import StreamingResponse import httpx app FastAPI() UPSTREAM_TIMEOUT httpx.Timeout(connect10.0, read120.0, write30.0, pool30.0) async def fetch_upstream(url: str, headers: dict, payload: dict): async with httpx.AsyncClient(timeoutUPSTREAM_TIMEOUT) as client: async with client.stream(POST, url, jsonpayload, headersheaders) as resp: if resp.status_code ! 200: # 返回结构化错误信息 error_body await resp.aread() yield fdata: {error_body}\n\n return async for line in resp.aiter_lines(): if line: yield line \n\n app.post(/v1/chat/completions) async def chat_completions(request: Request): body await request.json() auth_key request.headers.get(Authorization, ).replace(Bearer , ) # 步骤1鉴权并获取租户信息 tenant await auth_service.authenticate(auth_key) # 步骤2把逻辑模型名映射到具体上游 upstream route_service.select_upstream(tenant, body.get(model)) # 步骤3构造向上游发送的HTTP头 upstream_headers { Authorization: fBearer {upstream.secret_key}, Content-Type: application/json } stream body.get(stream, False) if not stream: async with httpx.AsyncClient(timeoutUPSTREAM_TIMEOUT) as client: resp await client.post(upstream.url, jsonbody, headersupstream_headers) return JSONResponse(resp.json(), status_coderesp.status_code) # 流式响应走这里 sentinel fetch_upstream(upstream.url, upstream_headers, body) return StreamingResponse(sentinel, media_typetext/event-stream)这段代码是平台在线推理链路的骨架但实际生产版本还要加很多逻辑比如上游异常时的自动重试怎么避免重复计费、同一个请求到达上游后拿到的usage如何回传给计费模块、调用链路的traceID如何贯穿全流程等。用一个统一的上游返回协议包裹器可以解决大部分问题在adapter层把所有非标准响应转换为平台标准响应后再向下抛这样入口代码能保持稳定。3.4 流式响应中的SSE数据格式处理SSE格式本身并不复杂关键是不同模型供应商对事件类型的定义和内容字段的命名并不一致。有些供应商会发送注释行做心跳保活有些会把usage放在最后一个事件里还有些在出错时会通过一个data为开头的错误事件来通知客户端。平台要做的就是把这些方言翻译成下游客户端能识别的统一格式。实际处理时我建议逐行读取而不是逐块读取。逐块读取如果触发TCP粘包一个数据块里可能包含多个事件逐行解析更稳妥。代码里用了一个缓冲队列把每次读到的数据块按换行符拆成多行再对以data开头的行做JSON解析。解析完若是正常增量数据直接透传给下游若是结束标记则要把从上游拿到的usage字段暂存到本次请求的上下文对象里异步发给计费模块。这里有一个容易忽略的点不要尝试在流中修改Content-Length因为流式响应的总长度是不可预先确定的正确的做法是把Transfer-Encoding设为chunked并让网络框架自己处理。4. 用Claude Opus5配合工程落地的心得4.1 怎么把大模型当成称职的架构评审员这个点很多团队都没用好。Claude Opus5在代码生成上的能力已经很扎实但它真正值钱的地方在于能扮演挑战者对设计方案进行提问和质疑。我们每次架构方案评审前都会把设计文档发给它然后要求它列出五个技术风险点、三个可能导致返工的隐患并给出建议方案。比如我们的第一版限流方案用的是单一Redis计数器Claude Opus5在评审时提出如果Redis实例发生主从切换计数器的连续性和准确性会受影响建议把限流状态做成持久化加分布式锁的双保险。虽然最终我们没有完全照这个建议去做但它提醒了我们需要给限流模块设计降级开关在Redis异常时至少保证请求不被全部拒绝。这一轮的互动方式非常像真实团队里的高级工程师在做设计评审。用模型做评审有一个前提是它需要足够的上下文。如果你只喂一段代码和一个模糊的问题它往往给不出有洞察的回应。所以我会在评审前准备一份prompt模板里面包含系统整体架构简述、本次改动涉及的模块、核心流程图用文字描述、服务依赖关系、以及最希望让它检查的问题。信息越结构化反馈越能落到点子上。4.2 五万字项目文档的内容架构与沉淀方法最终的项目文档写到接近五万字不是靠写流水账凑出来的而是因为它确实要覆盖平台从设计、开发、测试到运维的全部生命周期。我把文档分成了六大块产品介绍与术语说明、总体架构与设计决策、接口规范与使用指南、数据结构与后台配置说明、运维手册与故障处理、以及项目复盘与后续规划。写文档最大的挑战是保持新鲜度。代码一直在改文档如果跟不上一周就过时了。我们摸索出来的方法是把文档和代码放在同一个Git仓库里每次合并代码时要求提交信息里标注影响到的文档路径然后在CI流程上加了一个检查凡是涉及接口路由或配置文件的代码变更必须有对应的文档变更记录否则流水线会提示但不阻断。这个软约束比所有人工提醒都有效因为它把写文档变成了编码流程的一部分而不是事后工作。到这里我还有一个很强烈的体会五万字不是目标而是认真记录项目过程后自然长成的体积。别为了凑字数去写一堆正确的废话真正有用的文档是那种三个月后自己回来看也能立刻想起来当时为什么这么决策的记录。你在架构评审里经历过的每一次争论、放弃过的每一个替代方案、妥协过的每一个技术细节其实都值得忠实记录下来因为这些才是后来者最需要的项目遗产。5. 常见问题与项目复盘实录5.1 开发与联调阶段的典型故障速查表这里把我们在开发过程中遇到的高频问题整理成一张速查表都是真实踩过的坑后续做类似平台的同学可以直接对照排查。现象根因解决方案流式接口首字延迟很高网络框架开启了缓冲或上游请求误用了非流式模式确认请求体里streamtrue并关闭代理层缓冲请求偶尔返回401多租户鉴权与平台网关鉴权两套逻辑叠加导致Authorization头被二次改写用户发到平台的key与平台发往上游的key必须使用不同字段区分避免覆盖请求量一大就大量超时HTTP连接池尺寸不够每个请求都新建连接导致端口耗尽使用复用连接池并配置HTTP keep-alive账单里token数目的不准没有在上游返回的usage中区分输入与输出token重复统计了缓存命中对usage字段做映射归一化统一记录input/output/cached三类token缓存命中率偏低缓存key里包含了时间戳或无意义的request_id梳理缓存key生成规则只保留模型名、参数和业务语义标注表格只能记录结论真正的解决过程远没有这么丝滑。比如连接池问题我们一开始只发现服务在高峰期频繁超时但看CPU和内存都正常后来追踪到系统层发现可用端口数量降到了零。原因是Python的httpx客户端在每次请求时如果直接初始化底层会创建新的TCP连接四元组数量一旦触顶就会拒绝连接。改成复用全局AsyncClient后连接数量立刻稳定下来超时问题基本消失。5.2 稳定性治理与容量评估心得平台上线前我们做了一轮完整的压测。结论是流式请求的并发指标不能只盯着QPS看因为一次流式请求占用的时间可能是普通请求的几十倍。更好的衡量指标是同时在途请求数和平均首字延迟。压测脚本里我们对这两个指标分别设了阈值比如在途请求超过某个值时平台会主动拒绝新请求而不是全部排队等上游释放连接保证已建立的请求不被拖垮。稳定性治理上我们还做了一件事就是给所有上游模型服务加了一个健康度打分器。它不是简单的心跳检测而是结合了最近一分钟的成功率、平均延迟、429限流比例综合算出一个分数。路由层每次选择上游时会优先挑选健康度最高的节点只有分数低于阈值时才触发切换。这套机制上线后上游模型服务出问题对业务侧的影响被控制在了非常小的范围内很多故障甚至业务都没有感知到。关于容量规划我给一个比较实用的参考生产环境至少要预留2到3倍的buffer给突发流量因为你永远不知道业务方什么时候会上线一个批量调用脚本。早期我们按日常峰值的1.5倍规划结果一个业务线做A/B测试就把并发顶到接近水位线。后来改成了按历史峰值的3倍来控制平台整体的最大并发配额虽然资源成本增加了一些但运维同学的半夜电话明显少了。5.3 从项目文档沉淀到团队能力复用的思考项目结束之后那份近5万字的项目文档实际上变成了团队内部的一本技术手册。新同学入职后不再需要追着老同事问各种上下文直接读第一章术语表、第二章架构决策就能对整个平台有清晰的认知。运维团队在处理线上问题时也把故障处理手册当成了第一查询入口很多问题都有标准的排查路径。我后来做了一次复盘统计项目过程中和Claude Opus5的协作大概让文档撰写的有效时间压缩了四成但这里有个前提条件是人必须持续进行质量把控。模型生成的内容尤其是代码示例和依赖名称有可能会出现旧版本说法不能不做验证就直接拷进正式文档。我的习惯是让模型生成初稿然后由至少一位实际接触过该模块的工程师做一次技术校审把不精确的地方改掉。最后再分享一个我个人的做法项目复盘不是等到全部结束才做而是每个里程碑后花半小时记几条现在最让自己后悔的技术决策。这个习惯看起来简单但它积累下来的内容往往是最有血有肉的素材。很多团队的项目文档写得很干就是因为只看得到最终实现看不到过程中那些本可以更好的节点。把这些真实权衡写进去文档就有了灵魂。这次做Claude Opus5开发中转应用平台最终沉淀下来的不只是那五万字的技术记录还有一套适合后续复制到其他项目的研发方法和人机协作工作流我想这才是长期最有价值的部分。
RELATED READING

延伸阅读

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