1. 产品源起与架构演进的三次转身
1.1 第一版的本质:一个“能跑通的脚本”
很多人看到AIStarter这个名字,以为是某个开源框架或者商业平台,其实最早它只是一堆Python脚本的集合。
当时的真实场景是:团队里需要对接多个AI服务,把“用户输入→Prompt组装→模型调用→结果解析→入库/通知”这条链路跑通。我最初的做法非常原始,所有逻辑全部堆在一个main.py里,配置靠改代码,流程靠手写 if-else,模型切换靠注释掉几行代码。跑一个固定的任务没问题,但一旦涉及多步骤、多模型、不同用户的个性化输入,代码就开始失控。
这个阶段最典型的痛点有几个:
- 每次调整Prompt模板都要改代码、重新部署,运营同学完全没有自助能力。
- 模型A返回的结构和模型B不一样,解析逻辑写在各个业务分支里,到处是
response["choices"][0]["message"]["content"]这类硬编码。 - 没有日志和追踪,某个环节挂了只能靠 print 输出一点点排查。
- 并发调用时直接撑爆服务,没有限流、没有重试、没有熔断。
现在回头看,这个阶段能撑住,完全是因为使用人数少、场景单一。但它给了我们一个重要启发:AI应用的产品化,第一步不是UI,而是把“分散的调用”收敛成“可描述的流程”。
1.2 模块化与配置驱动:AIStarter从脚本变成引擎
第二次演进是从“能跑”到“能编排”。我们决定把所有AI调用抽象成统一接口,把流程定义从代码里拿出来,改成配置文件。这是一次典型的模块化重构,核心思路是:
- 每个AI服务(大模型、向量库、搜索、图像生成等)封装成一个独立的Provider,通过统一接口暴露能力。
- 流程从代码里剥离,用JSON/YAML描述,支持步骤顺序、变量传递、条件分支和并行执行。
- 增加一个轻量级运行时引擎,负责解析流程定义、调度步骤执行、收集结果。
这个阶段AIStarter才算真正有了“产品”的雏形。它解决的问题是:把业务逻辑和AI能力解耦。运营同学不需要懂代码,只需要在配置文件里填好模型名称、Prompt模板和参数,就能快速组装一个AI应用。
举个例子,一个“文章摘要+关键词提取”的应用,原来的代码需要写几十行调用逻辑,改成配置后大概是这样一个结构:
steps: - name: summarize provider: openai model: gpt-4o-mini input: "{{content}}" prompt_template: "请用200字以内概括以下内容:{{content}}" - name: extract_keywords provider: openai model: gpt-4o-mini input: "{{summary}}" prompt_template: "从以下文本中提取5个关键词,用逗号分隔:{{summary}}"配置驱动的最大好处是可审计、可复用、可版本化。但同时也暴露了一个新问题:配置文件只是静态描述,用户想直观看到流程跑得怎么样、哪个环节贵、哪个环节经常失败,依然非常困难。
1.3 控制面与数据面分离:PanelAI为什么必须独立
第二次演进后,AIStarter已经是一个能执行流程的“运行时引擎”,但产品形态还很“极客”——使用者需要编辑配置文件、命令行执行、翻日志排查。我们当时已经意识到,再往下走必须拆出一个独立的控制面产品,这就是PanelAI的由来。
PanelAI定位是AIStarter的运营控制台,负责三件事:
- 可视化管理:流程配置从YAML变成表单和拖拽式编排。
- 运行监控与复盘:查看每次运行的完整链路,包括耗时、Token消耗、成本、失败原因。
- 用户与权限管理:区分管理员、开发者和普通使用者,不同角色看到不同的功能模块。
这次拆分的核心逻辑是控制面与数据面分离。AIStarter作为数据面,负责执行流程、调用模型、处理数据,保持轻量、稳定、易于嵌入;PanelAI作为控制面,负责配置下发、状态采集、指标展示、参数调整,迭代速度快,UI复杂度高,不影响底层执行引擎。
拆分之后最明显的变化是发版节奏解耦了。以前改一个按钮就要重新发布整个引擎,拆分后PanelAI可以一天发版三次,AIStarter的引擎则可以保持数周稳定不动。这对一个正在快速验证用户需求的产品来说,至关重要。
2. 产品重构逻辑拆解:表面在改架构,底层在换场景
2.1 用户画像变化如何倒逼架构调整
很多人做架构演进只关注技术指标,比如性能、扩展性、可维护性。但复盘这两个产品的演进路径,我最大的体会是:架构的每一次大调整,背后都是用户和使用场景的大变化。
第一阶段用户只有我自己和两名工程师,诉求很单纯:少写重复代码。第二阶段用户扩展到团队里懂点技术的产品经理,他们不看代码,但愿意编辑YAML,诉求变成了“别让开发每次都帮我改Prompt”。第三阶段用户进一步扩大到运营、客服甚至管理层,他们连配置文件都不碰,只关心“今天AI处理了多少任务”“花了多少钱”“有没有出错的”,这就直接催生了PanelAI。
三批用户的差异可以总结成三个关键词:生产能力、自助能力、管理能力。每个阶段都要为下一阶段铺路,架构演进本质上是提前为这些能力变化做好承接。
这个过程给我的经验是:不要为用户还没有的场景过度设计,但要在架构上预留清晰的扩展点。比如我们在模块化阶段并没有急着做完整的管理后台,但所有组件从一开始就要求有唯一的ID、稳定的接口和结构化日志——这些是后来PanelAI能快速搭建的基础。
2.2 功能边界取舍:什么进AIStarter、什么进PanelAI
拆完之后,长期被问的一个问题是:某些功能到底该放在AIStarter还是PanelAI?我踩过几次坑之后总结了一套判断标准:
- 如果是执行链路内必须实时完成的事情,放AIStarter。比如模型调用、重试、限流、缓存、超时熔断。控制台依赖这些能力,但它自己不该执行任何模型请求。
- 如果是事后的分析、展示、配置下发,放PanelAI。比如运行记录、成本统计、错误分布、Prompt版本管理、用户权限配置。
- 如果两边都可能用到,那就单独建一个共享模块,通过API暴露,禁止直接读对方的数据库。
这套标准看起来简单,执行起来很难。最大的坑在于“实时状态查询”这类需求。早期我们为了让面板展示实时进度,让PanelAI直接接AIStarter的运行日志数据库,结果两边的数据模型不完全一致,经常出现面板显示“运行中”但任务实际已经结束的情况。后来统一改成:引擎侧发事件消息到消息队列,PanelAI消费事件更新自己的视图,两边只通过定义好的事件结构通信。
注意:控制面和数据面分离,最难的不是部署上拆开,而是数据交换协议的稳定。事件格式一旦对外承诺,就要保持兼容,新增字段可以,修改语义不行。
2.3 从“一次性启动”到“持续运营”的模型转变
第三个重构逻辑是关于产品心智模型的转变。AIStarter早期解决的痛点是“怎么快速让一个AI流程跑起来”,本质上是一个工具,用完即走。但AI应用真正落地到业务中,一定会进入“持续运营”阶段:流程会反复运行,模型会出错,成本会累积,质量需要关注。
这种心智模型的变化,直接影响了很多设计决策:
- 流程从“脚本”变成“资产”:一个流程有名称、版本、负责人、调用方,需要被管理。
- 单次运行从“日志”变成“记录”:每次运行都关联流程版本、触发方式、入参出参、消耗指标,可重放、可对比、可追溯。
- 成本从“账单”变成“指标”:不只看总额,要看趋势、看分布、看异常峰值,能设置预算告警。
PanelAI的核心价值,恰恰是把“运行”从一次性行为变成了可持续观测的数据资产。这就解释了为什么控制台类产品往往比执行引擎本身更难做——执行引擎只需要对输入负责,控制台必须对全生命周期负责。
3. 关键技术决策与实现细节:复盘里最值得记录的几件事
3.1 任务编排引擎的抽象与状态管理
AIStarter最核心的技术模块是编排引擎。第一版实现很简单,就是遍历流程定义里的步骤,顺序执行。后来加入并行分支和条件分支,立刻发现状态管理变得复杂起来。
- 开始不画DAG(有向无环图),而是沿用“步骤列表+依赖声明”的方式,好处是配置简单、用户容易理解,坏处是没法自动判断环路、拓扑排序、并行优化。
- 每个步骤的运行时状态分为:
pending(等待)、running(执行中)、succeeded(成功)、failed(失败)、skipped(跳过)、retried(重试中)。状态迁移必须由引擎统一管理,不允许业务代码直接改状态。 - 运行实例需要持久化。我们在内存里维护实时状态,同时周期性写入数据库,避免进程崩溃后无法恢复。
- 控制并发的方式:每个流程定义里可以配置
max_concurrency,引擎通过信号量控制同时运行的任务数。全局还有一个总并发限制,防止多个流程互相争抢资源。
一个容易被忽略的细节是变量作用域。用户希望流程里前一步的输出能传给后面用,于是我们搞了一套上下文变量系统。一开始设计得太简单,直接用一个全流程共享的Map存变量,后来出过变量污染的问题:并行分支里两个节点同时写同一个key,互相覆盖。解决方法是把变量分成flow(全局)和node(节点局部)两级,节点写入时默认只用节点局部命名空间,只有显式声明为输出变量时才合并到全局。
实操建议:做编排引擎,上下文设计一定要先定义好作用域和生命周期。宁可多用几层
{namespace}_{key}的结构,也不要贪图方便把变量都塞到同一个字典里。
3.2 多模型接入与统一网关设计
AIStarter要接的模型越来越多:不同服务商的文本模型、向量模型、图像模型、还有开源模型。每个模型的API风格都不一样,字段命名也不一致。如果不做抽象层,整个代码会变成“适配器地狱”。
我们做了一套统一的请求/响应规范,以对话模型为例:
- 请求统一为
messages(消息数组)、model(模型标识)、temperature、max_tokens、tools(可选)。 - 响应统一为
content(文本内容)、tool_calls(工具调用列表)、usage(Token用量)、latency_ms(耗时)。 - 每个Provider提供两个函数:
marshal_request(统一请求) → 厂商请求和unmarshal_response(厂商响应) → 统一响应。
这套设计有几个好处:
- 上层业务只依赖统一接口,不会绑死任何一家模型。
- 模型切换就是改一个配置项,不用改业务代码。
- 换新模型时只需要写新的适配器,其他逻辑完全复用。
真正复杂的不是接口转换,而是面对不同模型的“行为差异”。比如有的模型支持system多轮设定,有的只支持单条;有的模型工具调用格式完全不同,有的返回流式数据。这就要求适配器必须能处理模式差异,而不能只做字段映射。我们最终的方案是定义一个“能力矩阵”,每个Provider声明自己支持哪些特性(如流式输出、工具调用、结构化输出),引擎根据能力矩阵决定是否启用某些功能。
在网关层面,AIStarter内置了一个轻量级路由层,支持按模型名、按成本上限、按优先级、按是否支持某能力来分流请求。分流参数来自流程配置的route字段,例如按用户级别选择不同模型:普通用户走便宜模型,付费用户走更强模型。
3.3 成本与运行监控:PanelAI的看家本领
PanelAI最受用户认可的功能是成本分析。要算清楚一次AI流程花了多少钱,必须解决两个问题:用量如何采集和价格如何计算。
用量采集相对容易。引擎调用完模型后,Provider返回的响应里通常包含input_tokens和output_tokens,我们把这些字段连同模型名一起写入结构化日志,再上报给PanelAI。但有两个容易漏的地方:
- 流式请求的Token统计:有些模型在流式响应里不返回usage,必须在结束时额外发一次查询或根据回应片段估算。
- 缓存命中的Token:部分服务商有上下文缓存的计费规则,缓存命中的Token价格通常低很多。如果只记录总Token,成本会算高。为了解决这个问题,我们在混合存储层做了分项计数,把缓存命中Token和新增Token分开记录。
价格计算不能写死在代码里,否则每次调价都要发版。PanelAI把价格表做成配置,按“模型名+计费项(输入/输出/缓存命中)+每千Token单价”存好,定时更新,消费者接口直接读取。统计时按时间粒度聚合,支持按项目、按流程、按用户、按模型下钻。
监控报警也踩过坑。早期我们只看错误数和耗时,后来发现真正的成本异常往往来自“某个用户跑了一个超长Prompt”或者“流程里出现无限循环调用”。所以监控配置里一定要加上“单次运行Token消耗上限”和“单用户日消耗上限”两类告警,而且这些告警必须在引擎侧执行,不能只依赖PanelAI做后置分析。
3.4 数据模型演进与兼容策略
最后一个核心细节是数据模型。保存AIStarter和PanelAI之间流转的数据,我们经历过三个版本:
- V1:配置文件里的
name、description、steps数组。 - V2:每个步骤多了
id、type、retry_policy、timeout等字段,步骤间支持depends_on依赖声明。 - V3:增加
flow_version、trigger_type、tags、owner、tenant_id等元信息,运行记录也全面规范化。
每次数据模型变更都是一次迁移工程。经验教训是:
- 迁移前必须先写 backfill 脚本,把旧数据完整转换到新结构,不能只改写接口。
- 旧版本的运行记录必须保留原始快照,因为PanelAI有“回放”功能,需要还原当时的流程定义、参数和结果,而不是看最新版本。
- 所有新增字段都必须带默认值,不能假设旧数据天然有该字段。
- 枚举值只增不改,旧状态名如果语义变化,用新字段表达,而不是直接修改旧字段。
V2到V3迁移的时候,我们犯过一个错误:直接修改了运行记录里status字段的枚举值,结果导致旧记录回放到一半解析失败。后来只能做双重兼容——读数据时先判断版本号,再决定走哪条解析逻辑。这个教训让团队之后把所有数据结构变更都当成“多版本并行”来处理,不再追求原地修改。
4. 复盘实录:踩过的坑、弯路与值得保留的决策
4.1 第一批抽象就过度设计,后果很尴尬
现在能熟练地讲架构演进逻辑,但一开始并不是顺风顺水。第一次大的重构,我们就因为过度设计栽了跟头。
当时团队提出要做“模型能力抽象层”,方向是对的,但设计得过于宏大:不仅定义了统一接口,还设计了完整的插件注册机制、动态热加载框架、基于SPI的扩展点,甚至连“未来的自定义算子”都预留了接口。结果就是,开发周期拉长了一倍,而实际用到扩展点的场景少得可怜。理解起来也非常费劲,新成员接手时花了两周才搞清楚一个简单的Prompt调用应该怎么加。
那次之后我形成了两个判断:
- 第一个用户出现之前,不要做通用抽象。如果你的抽象只服务自己一个场景,不如直接用最简单的方式。
- 抽象的价值用“重复代码减少量”来衡量,不是用“设计模式用了几个”来衡量。
后来我们把过度设计的插件机制拿掉,只保留适配器模式和一个统一的接口注册表,代码量砍了三分之一,可维护性反而更好。
4.2 运行中改配置引发的状态错乱事故
第三阶段尝试支持“热更新配置”——流程运行时,如果管理员修改了某个Prompt模板,下次运行就生效。听起来很美好,但落地时翻车了。
原因在于引擎执行一个流程实例时,会先读取流程定义快照,然后按照快照执行。快照本身没问题,问题出在“正在运行的任务”和“新配置”之间的同步上。场景是这样的:
- 管理员修改了步骤B的Prompt模板。
- 某个正在运行的任务正好执行完步骤A,准备读取步骤B的配置。
- 引擎拿到的是新配置,但这个任务记录里保存的是旧流程版本。
- 运行记录回放时,发现步骤B的入参格式和新配置对不上,解析失败。
根本原因是流程执行没有和配置版本绑定。修复方案很直接:引擎在任务开始时锁定完整的流程版本号,执行过程中任何配置修改都只对后续新任务生效,正在运行的任务继续使用旧版配置。
这个原则后来被引到了其他场景里:所有长时运行的操作,都不能在运行中途依赖可变配置。要么快照,要么不要改。
4.3 模块归属混乱给团队协作带来的隐性成本
AIStarter和PanelAI拆分后,出现了一个很有意思的组织性问题。代码仓库是两个,但团队成员的心里往往没有清晰的边界。FlowRunner(引擎侧)和 PanelAPI(控制台侧)之间,经常出现“顺手改一下对方的代码”的情况。表面上看效率高,实际上埋了很多雷:
- 引擎团队改了一个内部函数入参,没同步更新控制台侧调用点,构建时才发现。
- 控制台团队为了显示一些引擎内部指标,直接查询引擎的数据库表,形成了隐式依赖。
- 两边对同一个概念(比如“运行状态”)的理解不完全一致,导致事件消息里字段含义被双方各自解释。
我们用了几个办法缓解:
- 明确仓库所有权:引擎团队只允许通过接口访问PanelAPI的数据,控制台团队只允许订阅引擎发布的事件。
- 接口文档写入代码注解,并用协议校验工具在发布时自动比对,字段不兼容直接拦截发版。
- 概念统一表挂在文档中心:状态枚举、事件类型、字段定义必须共用同一套命名。
经验:技术架构的边界,如果不翻译成团队协作的边界,就会变成“两把钥匙开同一扇门”的局面。谁都改得了,最后问题谁都不认领。
4.4 复盘后留下的三条关键经验与未来的扩展方向
复盘整个演进过程,如果让我提炼三条最关键的经验:
- 架构演进节奏应该跟着用户场景走,而不是跟着技术风潮走。每一次重构的驱动力,本质上都来自新用户角色的加入和对“可运营性”的要求提升。
- 控制面与数据面分离,越早越好。一旦产品开始有“配置→执行→观测→调优”的循环,执行引擎和管控制台的边界就必须存在,哪怕产品还很小。
- 数据兼容是数据型产品最重要的隐形工程。运行记录、流程版本、指标数据都是长期资产,一次不合格的迁移会毁掉整个可观测体系。
至于未来的扩展方向,从目前的发展趋势看,有几个点是我正在关注的:
- 从“按配置执行”走向“按目标执行”:用户可以只描述“我要什么”,AIStarter负责拆解步骤、选模型、编排流程,甚至自动生成Prompt。这个方向会把编排引擎的抽象层级提升一大截。
- 事件驱动架构的深化:流程的触发从手动执行、定时执行,逐步扩展到业务系统的事件触发、Agent自主决策触发。这需要引擎侧有更强的消息能力和状态协调能力。
- 多租户与成本治理的精细化:当同一个平台承载多个业务线和多个客户时,租户隔离、配额管理、成本分摊会从辅助功能变成核心功能。
复盘做多了之后,我个人的最大体会是:好架构不是设计出来的,是演进出来的。AIStarter和PanelAI的拆分、重构、打磨,每一步都是被真实使用场景逼出来的选择。与其追求一步到位的架构模型,不如把产品的核心链路跑通,让架构跟着业务一起长。