架构是长出来的:AI平台从脚本到Agent的三次演进实录

发布时间:2026/9/5 8:54:39
架构是长出来的:AI平台从脚本到Agent的三次演进实录 架构这个词在公司里被误用得比“赋能”还狠。每个新来的技术负责人都会带一张画得极其饱满的架构图从接入层画到数据层中间挂满网关、熔断、消息队列、权限中心看上去只需要三个月就能撑起一家千人公司的技术底座。但作为经历了同一个AI平台从零到三落地的工程师我越来越确信一句话架构不是设计出来的是长出来的。真正能活下来并被业务天天使用的架构往往不是规划稿上那版而是被需求、事故、组织变化一轮一轮催生出来的形态。这篇文章想聊的就是这个平台在三次落地过程中架构怎么一步步从单机脚本扭曲成平台又从平台被迫重构出自己的编排层和Agent能力层——以及在这期间我们如何克制住“想要一步到位设计完美架构”的冲动。如果你是平台工程师、AI Infra的负责人或者正在犹豫手里的AI平台要不要上微服务、要不要做Agent化改造这篇文章里的场景和经验也许能让你少走半年弯路。它既不是教科书式的顶层设计教程也不是纯粹的踩坑记录而是一个普通技术团队在业务压力下如何识别架构真正的生长点、又如何在这些生长点上做有限干预的实践总结。1. 第一次落地被业务追着跑的MVP长出了整个平台的第一根骨架1.1 从“先搭框架”到“先跑通流水线”主动放弃前期架构设计先交代一下背景。当时我们组接到的任务是把公司内部已经存在的OCR识别、文本分类、文档抽取等几个零散模型能力整合成一条可供某个业务方直接调用的处理流水线。第一版需求并不复杂一份文档传过来系统先做图像预处理再跑OCR然后把识别结果送入后续的规则引擎和分类模型最终输出一个结构化结果供质检人员人工复核。按教科书流程我应该先梳理领域模型画出分层架构图确定好每个模型的输入输出协议再讨论要不要引入消息队列。但当时的实际情况是业务方只给了我们两周时间要的只是“能跑通的端到端验证”。所以我们做了一个在正规评审里一定会被打回的决定——不搞微服务不引入消息队列先用一台服务器上的Python脚本串起整条链路中间通过共享目录和Redis列表传递结果。这个决定在今天看来依然是对的。因为那时候我们连“这份文档的格式到底有多少种”“有些字段识别不出来时下游能不能容忍”都没搞清楚任何先验架构都是拍脑袋。整个流水线只有一条简单规则上游做完了就把结果写到一个约定好的文件目录里下游轮询到这个新文件就接着处理。没有分布式事务、没有服务注册发现甚至连RESTful接口都是后来才包的壳。两周后流水线居然真的跑起来了速度快得超乎预期。但这套所谓MVP里早就埋下了后来所有架构演化的种子——任务对象、失败重试、中间状态持久化。这些东西当时只体现在Python脚本里的几张表和几个循环里根本没资格叫“架构”但凡是认真跑过真实业务的人都会承认平台后续长出的每一个重要模块几乎都能在这份临时脚本里找到原型。1.2 早期临时方案里的三个注定迁移件所有“生出来”的东西都有自己注定被替换的命运。这里想复盘一下第一版验证系统里最明显的三个短命设计以及它们是什么时候、被什么逼着替换掉的。第一个是共享目录作为任务传递介质。文件系统当消息中间件用好处是直观、好排查坏处是一旦任务量上来多进程同时扫目录就容易出问题文件没写完整就被下游读走、清理线程误删了还没消费的任务、任务积压时目录列表长到让os.listdir直接卡住。我们最开始还靠文件名后缀来区分状态比如.part表示写了一半、.done表示可消费后来才发现状态这种东西天生就该放在数据库里而不是靠文件改名来表示。这个迁移件最终被一张任务表替代但共享目录在人工排查问题时依然被保留了下来用作中间产物的可视化检查。第二个注定被替换的是那套把所有业务参数揉在一个YAML配置里的做法。一开始只有四五类任务参数就那么几个写在同一个配置里还能忍。当第二个业务方带着一批完全不同的字段命名习惯接入时同一个配置项开始被迫承担多种含义改一处崩三处的情况频繁出现。我们逐渐把它拆成“任务定义”和“任务实例”两层前者描述这类任务跑起来需要哪些参数后者记录某一次具体执行时参数的实际取值。这个拆分不是来自什么高深的配置中心理论纯粹是因为出过两次把生产任务参数改错的事故。第三个注定长歪的组件是重试逻辑。第一版里每台机器上的工作进程对失败任务的唯一处理就是记日志重试靠运维手动筛出失败记录再重新触发。有一回夜晚一个批量任务因为第三方模型接口超时将近两千条子任务全部失败第二天早上业务方直接炸了。我们才把那套“睡着的进程”升级成“会哭的叫醒机制”——把失败任务写进专门的重试队列由一个调度进程按指数退避策略重新投递。这三个迁移件有一个共同特点它们都不是因为架构评审说“这里不规范”才被改掉的而是业务真实运行到某个临界点后旧方案自然失去承载能力才被新模块取代。1.3 那条“看不见边界的用户”如何逼出鉴权、配额与审计真正让这套零散脚本第一次意识到“自己已经是个平台”的标志性事件不是代码量涨了而是突然出现了第二个使用方。原本这个流水线只为一条业务线服务所有任务都是内部某个系统通过定时任务触发的不需要什么鉴权。某天另一个业务团队听说我们能做文档识别找过来希望我们也帮他们跑一批历史数据的批量抽取。就这么一个“顺手帮一下”的需求让那台服务器彻底暴露出了问题对方到底能提交什么任务能读哪个目录下的结果如果他们误操作把一个大任务提交上来会不会把正在跑的核心业务资源全部挤占这些疑问以前从没遇到过因为唯一的调用方就是自己的脚本根本不存在“不可信输入”的概念。于是平台开始长出第一批“边界防护”性质的组件一个极简的Token鉴权、一个按业务线划分的任务分组、一个粗糙的请求配额——每个业务方的在飞任务数不能超过N条超出的排队等待。这些组件的代码量并不多但它们彻底改变了平台架构的形态从“一条内部流水线”变成了“一个有多租户雏形的开放系统”。在这个节点上我们才真正开始给代码分层底层的模型调用和上层业务编排开始解耦中间抽象出一个面向任务的API层。我经常和团队说架构里最先长出来的部分往往不是业务能力而是防御能力。平台是自己内部自己用还是对外开放其对架构的塑造差异远超任何技术选型文档的描述。第一次落地结束了平台能跑了也有了边界、配额和审计日志。我们以为自己已经做成一个AI平台了后来才发现这只不过是从“能跑”到“能用”之间迈出的第一步。2. 第二次落地当脚本长成平台哪些模块是“先长出来”再回填设计的2.1 任务并发的质变点系统开始需要真正的调度器第一次落地后半年平台接入的业务方从一个变成四个在飞任务峰值从每小时几百条涨到日均几十万条。并发量其实绝对值并不高但架构暴露出来的问题已经不再是“跑不跑得完”而是“同一类任务的重叠执行开始互相踩踏”。用户发起了一个批量审核任务底层需要按文档分页拆成上千个子任务。旧流程就是把子任务全部丢进线程池谁先跑完谁先写结果。于是事故接踵而来同一份文档被重复提交时两个子任务同时更新同一条记录某个模型服务升级期间正在跑的任务和新提交的任务混在一起结果方差大得没法看更痛苦的是当任务失败后想只重跑失败的那一部分系统根本没有“部分重启”的概念只能整个任务作废重来。那时候我们意识到一个AI平台如果只能单体顺序地处理任务那它本质上还只是一段稍微复杂点的脚本。要支撑多业务方、多任务类型、可重试可恢复的并发处理就必须有一个真正的调度层。但怎么设计这个调度层我们没敢直接上现成的分布式工作流引擎。理由是平台当时对“任务类型”的定义还在剧烈变化OCR类任务和文本生成类任务的执行方式、资源消耗、失败语义都相差过大强行套一个统一引擎反而会限制后续的落地形态。我们选择自研一个非常朴素的调度器任务表里增加状态机字段待调度、运行中、成功、失败、人工暂停每个工作节点通过数据库行锁抢占任务抢占成功后执行再回调结果。这套自研调度器在性能上远不如那些成熟的分布式任务框架但它给了我们一个极好的副产品所有任务的状态流转和结果数据都落在库里天然形成了后续审计、统计、对账的基础。后来我们集成外部开源执行引擎时任务状态的表结构几乎没做任何调整直接平滑迁移。也就是说自研不是为了重复造轮子而是为了让平台先长出存储、状态、重试这些真正核心的语义执行引擎反而成了可以替换的后端。2.2 管理后台、文档与人工复核不是规划出来的业务闭环第二阶段最容易被低估的架构组件居然是管理后台和与人工有关的业务流程。业务方反馈说模型对某些版式的识别结果经常是错的但又不能立刻改模型需要一个让质检人员人工修正结果、再把修正后的数据回传的通道。我们最初觉得这只是个“小功能”加几张表、写几个接口就行。但真正落地时才发现人工复核一旦介入任务流程就再也不是单纯的线性和自动了。人工复核人员从任务列表认领一个实例后系统要能把这个实例“暂停”住保证调度器不会重复派发复核人员修改完结果点提交系统要能记录操作痕迹要能触发下游步骤继续执行如果复核中发现平台处理逻辑有bug还要支持把任务“打回”重跑而不是让用户自己到库里改状态。这些围绕“人的参与”生长出来的状态流转逻辑实际上慢慢勾勒出一个成熟的业务编排引擎的核心——人工任务节点。管理后台也在同一时期被逼着成型。业务方不想每次调整模型阈值都找我们改配置文件他们需要一个界面能自己修改参数、查看执行日志、导出报表。我们最初只想做几个查询页最后不得不把权限模型、配置下发、审计追踪都补齐。这段经历让我总结出一个至今仍觉得有效判断标准当平台开始向非开发角色提供能力时它的架构才真正算长出了平台该有的形态。所谓平台化不是代码能跑通多少功能而是有多少业务人员能在无需研发介入的情况下独立完成操作闭环。2.3 从单一任务到可复用的能力单元能力网关与内部标准第二次落地的后半段我遇到一个典型的平台膨胀难题四个业务方各自要调用的模型不完全一样但任务执行链路又共享公共能力比如图片压缩、文档格式转换、文本去敏、结果合并。如果每个业务方各自实现这些能力平台会迅速被重复代码淹没如果把所有能力都硬编码进核心流程那“平台”就会变成一个谁也改不动的大泥球。这时候架构“生长”的方向变成了一张天然的能力清单。我们给每一个可被多个任务复用的子功能抽象出统一的调用接口把输入输出定义明确再通过一个轻量级能力网关把子功能注册进来。这样上游任务编排只需要知道自己要调用的能力名和入参而不需要关心这个能力具体由哪个模块提供、部署在哪台机器上。拿文本去敏打个比方旧架构里它只是OCR后处理流程里的一个函数能力化之后它变成了一项可通过接口独立调用的服务任何任务只要有需要就能挂上。这种“技能化”或“工具化”的过程现在来看就是后来Agent平台上工具调用的雏形。可当时我们还没引入大模型思路完全是被业务逼出来的——不去抽象每个业务方的重复开发成本都会增长到无法接受的地步。在这一阶段临近结束时平台的整体架构已经和第一版脚本形态截然不同但它的演化逻辑一直很清晰不是想到了什么职能模块才去设立而是真实业务运行里某个痛点反复出现才把它从代码深处抽出来变成平台上的一等公民。3. 第三次落地Agent化重绘了原有边界架构在演进中再次“长出”新的器官3.1 当输入从流程模板变为自然语言任务时最先不适配的其实是运行记录第三次落地的触发点和前两次都不一样。前两次是业务方增多、并发量上涨而这次是LLM时代下使用平台的方式发生了根本变化——业务方不再愿意按我们预设的流程模板来编排任务而是希望用一句自然语言描述他们想做什么由Agent自己拆解为任务计划并调用平台的能力去完成。听起来很酷但真实平台改造的起点其实狼狈得多。我们第一次把Agent接到平台上的时候还是用旧的“任务-子任务”模型来承载Agent生成的计划被翻译成多个传统任务提交给执行引擎。结果没过多久就出问题了Agent的决策依赖大量的上一步执行反馈而旧的执行框架只能返回“任务成功了”这种粗粒度状态无法细粒度返回中间步骤的详细输出比如模型调用耗时、工具参数、上下文命中情况。这直接导致Agent在一次需要多轮推理的任务中变成瞎子在黑暗中摸索。当时我们面临两种选择一种是继续把Agent当成一个“比较复杂的消费者”让规划能力去适应现有平台的粗粒度执行模型另一种是为Agent的动态执行过程单独设计一套运行记录把一次Agent对话里的每一步思考、每次工具调用、每个中间结果都记录下来。我毅然选择了后者因为复盘旧平台时发现平台每次进化到新阶段真正拉动架构的总是对运行状态“可观察”的渴求而不是模型本身有多强。于是在新的Agent执行层里长出了第一张核心表agent_run记录一次Agent会话的完整轨迹。每个步骤的输入输出、调用的工具、模型的thinking过程、消耗的Token数、触发的人工确认节点全部以结构化的方式落到这张表。后续基于它长出了Agent效果评测、成本统计和回放排查系统。这套架构在后来Agent真正接入生产时被证明是决定性的一旦线上出现“Agent做错了决策”的事故能不能快速定位是哪一步错、为什么错直接决定了运营团队愿不愿意信任这套新系统。3.2 模型路由、上下文记忆与工具注册模块生长的真实次序很多讲Agent架构的文章会给你画一张很完整的图感知层、记忆层、规划层、工具层、执行层好像Agent化改造第一天就需要这些模块。而我们在真实改造中模块并不是同时长出来的它有非常明确的先后顺序违背这个顺序就会走弯路。最先长出来的是模型路由层。原因很现实不同业务的请求对模型能力的要求差距太大简单分类任务没必要每次都调用最大的模型而复杂推理任务用小模型又会频繁出错。平台需要一个路由模块根据请求的复杂度预估和业务需求把每一次执行转发到不同的模型服务上。这个模块还有一个关键职责——故障转移当某个模型服务不稳定时自动降级到备用模型保证Agent任务的可用性不受单点故障影响。第二个长出来的是上下文记忆管理。Agent要做到多轮任务中的连贯执行经常需要跨会话保留背景信息。最初我们简单地把所有上下文拼进Prompt里很快token消耗和响应延迟双双爆表。这一层最终演化成了独立的记忆服务按会话、任务、业务域分层管理短期和长期记忆再通过检索增强把最相关的历史片段注入给Agent。第三个长出来的是工具注册中心。虽然平台已有的能力网关已经能承载部分工具调用但Agent场景下的工具调用需要携带更丰富的描述信息这个工具是干什么的、参数类型是什么、在什么情况下适合调用Agent才能做出准确的工具选择。于是我们给网关加了一层语义描述使之升维成真正Agent可发现、可调用的工具注册中心。第一次Agent改造时这几个模块是先后出现的而不是同时设计的但它们一起构成了Agent平台的核心骨架。我分享这个细节是因为我见过太多团队拿到成熟的Agent框架直接开改结果第一步就陷入“并行地跑通了Demo却在真实多租户场景里被各种上下文隔离、工具权限、模型成本纠缠成一团”的泥潭。生长顺序本身就是一种架构决策它决定了你能多快暴露下一个必须解决的问题。3.3 编排层比模型层更重要支撑业务治理的神经系统做第三次落地后我更加确定一个观察对AI平台架构而言模型本身常常不是瓶颈围绕模型动态行为构建的编排与治理层才是平台能否被业务方长期依赖的关键。举个例子旧平台的流程引擎是基于时下流行的DAG有向无环图设计的——任务节点之间的先后关系是预先画好的调度器按确定图去执行。Agent化之后任务图不再固定Agent会根据当前环境动态决定下一步调用哪个工具甚至可能为了某个目标循环尝试多次。但它真就成了无规则吗完全不是。在业务治理层面我们需要它遵守统一的底线。比如对涉及对外发送通知或修改核心系统数据的动作业务方依然要求必须经过人工审批每次Agent调用外部工具都希望有审计。这意味着动态决策必须被约束在业务允许的轨迹里。这个矛盾让我重新思考编排层在整个AI平台中的位置。编排层不再只是执行预先编排好的流程而是变成了一种“约束底线的协调器”Agent可以自由探索但跑不出边界。同时编排层还要负责把Agent的执行路径统一规范化——无论动态规划在某次具体运行中如何选择最终产出的元数据都要满足“可追踪、可重放、可审计”这些基础设施级要求。后来团队内部形成过一个共识Agent架构的落地核心不是让业务方看到一个能自己规划步骤的智能体而是让他们看到一个即使规划错了也能安全回滚、可以复盘、可以逐渐优化的“神经系统”。让模型去涌现把可控性留给编排层。4. 长出来不等于放任自流每一次演化背后需要的三条治理线4.1 识别重构窗口什么时候必须停下补“架构债”每次架构生长的转折点都伴随着大量的临时设计和短期妥协。但这些债务如果不及时清理过一段时间就会让整个平台寸步难行。关键在于不能靠“项目不忙了再重构”这种没有纪律的方式而要建立明确的债务窗口感知机制。我习惯把需要停下来补架构债的信号分成三类。第一类是“代码重复率型”同一个能力逻辑出现在第三个不同业务模块中这时候就不该再靠复制粘贴堆功能而是主动把能力抽出平台化。第二类是“责任归属型”开始有人问“这台机器上跑的到底是什么服务”“这个告警该找谁处理”意味着服务的边界和组织边界出现了错位该重构出清晰的模块归属。第三类是“变更恐惧型”团队对某个模块做任何小改动都需要长时间回归测试没人敢动这块代码说明它已经腐化到必须调整结构的程度。这套信号线在第三次Agent改造时救了我们。假如不做这轮主动调整新功能继续在这个旧的全量模型方法上叠加可能一两个月内平台性能衰减到业务方不可接受的程度。因此在发现延迟增长曲线已经无法归因到单一模型时我们果断暂停了新功能开发花了两周专门做路由和并发控制层面的重构。补架构债的过程中还有一个原则不能一次性做完整的重构——理想是先稳定数据层把任务状态的语义固定下来再收敛API接口层最后才调整内部的执行引擎。每一层的替换都要保证对外行为不变让底层在用户的感知中平稳过渡。可以的话多花一点时间在兼容层上让旧任务能平滑迁移是非常值得的。4.2 三轮改造中为了避免误操作如何让改动面变成“很无聊”的地带架构演进中最容易翻车的场景不是新模块写不出来而是重构过程中把原有仍然正确的逻辑也改坏了。解决这个问题的办法不在写代码时的谨慎而在于刻意让某些区域保持“无聊”。我们在第二次落地开始就做了个决定对于模型输入输出协议、任务状态定义、结果数据字段加入版本化约束并做向后兼容。任何能力模块的改动都必须遵守“加新字段时旧字段保留默认值不改变语义改旧字段时必须提供双写过渡期”的约定。这意味着这些区域虽然频繁演进但对使用者来说永远保持稳定。一个特别有用的操作是给每一个能力单元建立“契约测试”用固定的输入样本和预期的输出快照去校验它是否发生对外可见的变化。重构过程中跑一遍契约测试就像有了一根锚能保证在改了内部实现后外部感知的行为纹丝不动。这种做法让“重构”变得没那么刺激正因为不刺激才敢放心地频繁重构。人总是倾向在改动中顺手添加新想法但当一次任务的目标是“保持行为不变”时就应该刻意压制这种冲动。所谓很无聊的地带是为了防止在推进架构演进时因为夹带各种私货而模糊了判断的基准。4.3 失败的三类提前设计过早抽象、过早平台化、过早规范讲了这么多生长的过程有对比才有尽头。三次落地里我们也犯过不少“过度设计”的错把它总结下来是三条最贵的弯路。过早抽象发生在第一次落地的后期。当时因为那套YAML配置实在难维护我就提前设计了一套“通用规则引擎”——试图让业务方用XML声明式地配置处理链路无需写代码。结果设计得太抽象业务方很难理解后期维护成本远大于直接写代码最后这层抽象被废弃了。真实的架构演化中抽象应该是从多个具体案例里归纳出来的而不是在只有一个具体案例时就先想好它是“所有可能案例的父集”。过早平台化则出现在第二次落地后。那时候我们看到很多团队都把AI平台做成中台于是规划了几个通用组件想提供给全公司其他业务线使用。但实际上除了现有两条业务线还没有人明确需要用我们平台处理任务。当时耗费大量人力去完善API文档、权限系统和多租户隔离结果在无人接入的状态下空闲了大半年很多设计都是闭门造车。后来我定了一条规则通用能力只有在出现第三个可能的调用方时才开始打造平台级功能前两个用户都值得用最直接的定制方式快速满足——当你前面只有一两个客户时你不需要考虑开酒店的问题民宿式的定制才最高效。过早规范也有过教训。平台全盛期我们把代码规范、Commit格式、分支模型都定得非常完备完备到每一个新增模块都要填一堆模板。这导致新业务尝试接入一个新型任务的周期从一天拖到一周。后来很多团队实际开发走快捷分支规范被架空。规范是为了保证质量与速度的平衡不是为了像装修标准一样能看着舒服。一旦规范妨碍新形态浮现它应当先退出而不是让新形态去适配规范。刻意保留错误的空间恰恰是架构能长期健康演化的第一条准则。一味追求整洁的设计图纸反而可能把这套系统逼进一个僵化的未来。5. 从三次落地反推给AI平台架构演化者的实用检查清单与经验沉淀5.1 判断一个新模块是否值得“生长”出来的四个条件经历三轮落地团队内部沉淀出一份用来判断“要不要为某个需求造一个新架构模块”的检查清单我在这里分享出来。第一这个模块至少要有两个以上场景或用户同时需要。为了一个用户或一个场景去建造通用模块资源浪费概率极高。第二这个模块的失败必须能被安全观测和回溯。如果引入某组件后出了问题连“谁在什么条件下调的、结果长什么样”都说不清它就会被业务方拉黑。第三它的接口和底层实现之间要能接受单向依赖——也就是调用方只依赖它提供的能力描述不依赖实现细节后续替换实现时才能不牵一发动全身。第四它要有可度量的成功与失败指标。没有指标就谈不上优化更谈不上决定是否要在这个模块上投入资源去继续发展。 这四条同时满足时我们才考虑抽出一个正式模块否则宁可用更多地方临时的方案来做。5.2 在研发节奏中给技术债预留固定“窗口期”平台类项目最怕的是需求永远排满架构治理永远没时间。真实的三次落地里我学到最实用的一个机制是每个季度固定预留两周左右的技术债清理窗口。这两周不接任何新需求只处理前面季度积累的重复代码、卡顿模块和可观测性缺陷。这几周做起来有时候不一定有立竿见影的特性产出容易被管理层质疑但如果没有这些窗口所谓的架构演进就只会出现在事故爆发后的应急加班里。提前规划比应急从容而且风险低得多。在窗口期内我们最常做的事包括删除那些已经没人调用却还在跑着消耗资源的服务把三个类似模块合并成一个把原先写在脚本里的临时处理逻辑抽进正式平台组件。做完这些事情新业务的接入速度往往会明显提升这个信号比代码整洁度更能说服业务方。5.3 打样团队的架构视野直接决定平台能够走多远最后一条经验可能有点反直觉但在我经历的三个落地阶段里一次次被验证。平台的前三版架构几乎完全是由最初那个两三个人的打样团队的认知水平决定的。如果打样团队迷信大规模分布式第一版就会被迫背上无法支撑的运维复杂度如果打样团队只是追求“能跑就行”平台后续改良的难度会非常巨大。而一个懂得“用最简单结构跑通业务、留下清楚扩展点、需要时正确生长”的小团队其对演进节奏的把控能力几乎直接决定了这个平台最终会不会陷入系统性僵化。所以如果你正在从零搭建AI平台与其问“用哪个开源框架”不如先问自己的核心小团队有没有经历过业务从0到1演进的训练对架构生成周期是否有感觉。框架早晚会被替换架构眼光才是平台成长的天花板。写这篇的时候我又翻了一遍第三次落地时关于Agent运行轨迹和工具注册中心的评审记录每个模块都带着明显的“长出来”痕迹——有迭代的犹豫有临时妥协的补丁也有看清方向后的果断回填。架构这棵树只要始终在真实业务中生长就总能找到属于自己的那条向上路径。