Jev AI决策系统架构设计与生产实践:从编排优先到级联推理
发布时间:2026/11/3 21:41:11 锦皓数字建站

1. 从概念到生产Jev AI决策系统的架构全景与设计哲学1.1 为什么“决策系统”和“模型调用”是两码事很多人第一次听到“Jev AI决策系统”这个词脑子里浮现的画面大概是一个对话框你问一句它答一句。但如果真把这种东西往生产环境里塞你会发现它连最基本的业务闭环都跑不起来。原因很简单问答是单次交互决策是连续博弈。我举个实际场景你就明白了。假设你手上有一个电商风控场景用户下单那一瞬间系统需要在80毫秒内判断这笔订单是放行、拦截还是转人工审核。这个判断不能只看用户当前这一次的行为还要结合他过去30天的退货率、当前设备的指纹特征、收货地址的历史投诉密度、以及此时此刻平台整体的欺诈水位。这些信息分散在四五个不同的数据源里有的走缓存有的走实时流有的要查离线数仓。Jev要做的是在极短的时间窗口内把这些异构信息拉齐、对齐时间戳、做特征交叉然后输出一个带置信度的决策结果并且这个结果还要能被事后审计和回溯。这就是为什么我说“决策系统”和“模型调用”完全是两个物种。模型调用只关心输入输出的正确性决策系统关心的是在资源约束、时间约束和业务规则约束下的最优路径选择。Jev的架构设计从一开始就是冲着后者去的它的核心不是某个单一的大模型而是一套围绕决策生命周期构建的编排层、特征层、推理层和反馈层。1.2 Jev架构的四层拆解从接入到反馈的完整链路我把Jev的架构拆成四层来看这样不管你是做后端还是做算法都能找到自己关心的部分。接入层负责协议适配和流量治理。生产环境里来的请求五花八门有HTTP的、有gRPC的、有消息队列推过来的甚至还有从边缘节点同步过来的批量决策任务。接入层要做的不只是转发它还要做请求的优先级标记、超时预算分配和降级策略触发。比如风控场景里实时交易的优先级永远高于离线批量评分当GPU资源紧张时接入层要能自动把离线任务排到低优先级队列里去。特征层是Jev最容易被低估的部分。很多人以为特征工程就是写SQL跑批但在决策系统里特征层要解决的是在线离线一致性这个老大难问题。训练时用的特征和推理时算出来的特征如果口径不一致模型效果会断崖式下跌。Jev的做法是把特征定义收敛到一套DSL里离线跑Spark任务和在线走Flink任务都从同一份DSL生成执行计划从源头上堵住不一致的漏洞。推理层是大家最关注的部分但我得说句实话在生产环境里推理层的复杂度往往不在模型本身而在多模型编排。一个决策请求可能先过一个轻量级的规则引擎做粗筛再过一个梯度提升树做精排最后过一个深度模型做兜底。这三个模型的延迟预算怎么分配、哪个先跑哪个后跑、串行还是并行这些编排逻辑才是真正吃经验的地方。反馈层是区分“能用”和“好用”的分水岭。决策系统发出去一个结果这个结果对不对用户有没有申诉后续有没有发生反转这些信号要能回流到系统里驱动特征更新和模型迭代。Jev的反馈层设计了一个延迟标注队列把不同时间窗口才能拿到的标签分阶段回填避免因为等标签而阻塞整个训练流水线。1.3 为什么选择“编排优先”而不是“模型优先”市面上很多AI系统的设计思路是“模型优先”先选一个最强的模型然后围绕它搭一套服务。Jev走的是相反的路——编排优先。这个选择背后有很实际的考量。模型是会过时的。今天效果最好的模型三个月后可能就被新架构超越了。如果你的系统跟某个特定模型绑得太死换模型的时候就要伤筋动骨。Jev把模型当成可替换的插件编排层定义的是“什么时候调用什么能力的模型”而不是“调用哪个具体的模型文件”。这样一来算法团队可以独立迭代模型版本工程团队只需要保证编排逻辑的稳定性。另一个考量是成本。不是所有决策都值得用大模型。一个简单的阈值判断能用规则引擎搞定的事情没必要走一遍深度网络。Jev的编排层支持级联推理先走便宜的路径只有置信度不够时才升级到更贵的模型。实测下来这种级联策略能把平均推理成本压到原来的三分之一左右而决策准确率的损失不到两个百分点。注意级联推理的阈值设定是个精细活。阈值太松便宜路径放过了太多难样本兜底模型压力大阈值太紧便宜路径频繁升级省下来的成本又吐回去了。我的经验是先用历史数据做离线模拟画出“成本-准确率”曲线找到拐点再上线。2. 核心细节解析Jev在生产环境中的关键实现2.1 特征管道的在线离线一致性怎么保证前面提到特征层要解决在线离线一致性问题这里展开讲具体怎么做。Jev的特征管道分两条线离线线走Spark每天凌晨跑全量特征回填在线线走Flink消费实时事件流做增量计算。两条线共用同一份特征定义文件这个文件用YAML描述大概长这样feature_name: user_30d_return_rate entity: user_id type: float offline_source: dwd.user_return_log online_source: kafka.user_behavior_topic window: 30d aggregation: sum(return_count) / sum(order_count) default_value: 0.0离线引擎读到这个定义生成对应的Spark SQL在线引擎读到同一个定义生成对应的Flink DataStream算子。两边算出来的结果理论上应该完全一致但实际跑起来总会有偏差。常见的偏差来源有三个时间窗口对齐方式不同、空值处理逻辑不同、数据迟到处理策略不同。时间窗口对齐这块离线通常按自然日切分在线按事件时间滑动。Jev的解决办法是在定义里强制指定window_align: event_time让离线也按事件时间做滑动窗口虽然计算量大了些但一致性有保障。空值处理上离线默认填0在线默认填默认值这个要在定义里显式声明default_value两边都读同一个配置。数据迟到是最麻烦的离线跑批时迟到的数据已经落盘了在线流处理时迟到的数据可能已经被窗口丢弃了。Jev的做法是在在线侧维护一个允许迟到窗口默认设成窗口长度的10%超过这个时间才丢弃。2.2 推理编排的延迟预算分配实战假设一个决策请求的总延迟预算是100毫秒Jev的编排层会这样分配接入层解析和鉴权占5毫秒特征拉取占30毫秒规则引擎粗筛占5毫秒梯度提升树精排占20毫秒深度模型兜底占35毫秒最后5毫秒做结果组装和日志落盘。这个分配不是拍脑袋定的而是根据每个环节的P99延迟实测值反推出来的。但实际运行中某个环节偶尔会超时。比如特征拉取依赖的外部缓存集群发生抖动30毫秒的预算可能突然变成80毫秒。这时候编排层要能动态调整如果特征拉取超时直接跳过精排模型用规则引擎的结果加一个保守的置信度输出。这种降级路径要在系统设计阶段就规划好不能等出了故障再临时想。我踩过的一个坑是降级路径本身也会成为故障点。有一次我们配置的降级规则是“特征超时则走缓存中的历史特征”结果缓存集群和特征集群是同一套硬件一起挂了降级路径直接失效。后来我们把降级依赖拆到不同的可用区虽然成本高了些但可用性上了一个台阶。2.3 反馈回流的延迟标注队列设计决策系统发出去的结果标签往往要等很久才能拿到。比如一个贷款审批决策用户可能三个月后才知道有没有违约。如果等三个月再回填标签模型迭代周期就被拖死了。Jev的延迟标注队列把标签分成几个阶段即时标签几秒内可得如用户是否点击、短期标签几小时内可得如是否发生退款、长期标签几周或几个月后可得如是否违约。每个阶段回填时系统会重新计算受影响样本的损失函数如果损失变化超过阈值就触发一次增量训练。这样模型不用等所有标签到齐就能持续进化。实际跑下来即时标签驱动的增量训练能把模型的小时级效果提升两到三个百分点而长期标签主要用于季度级的大版本迭代。实操心得延迟标注队列的存储设计要用列式存储加时间分区因为回填操作通常是按时间范围批量更新的。我们一开始用行式存储回填一次要扫全表后来换成Parquet按天分区回填效率提升了十几倍。3. 实操过程从零搭建一个Jev决策流水线3.1 环境准备与依赖清单假设你要在本地或测试环境搭一套最小可用的Jev流水线下面是我验证过的依赖清单和版本组合。这套组合在Ubuntu 22.04和CentOS 8上都跑通过Python版本锁定在3.10因为3.11之后有些依赖包的wheel还没跟上。组件版本用途备注Python3.10.x主运行时不建议用3.12部分C扩展编译会报错Redis7.2在线特征缓存开启持久化避免重启后特征全丢Kafka3.6实时事件流单节点即可生产建议三节点起Flink1.18在线特征计算用PyFlink跟Python生态更搭Spark3.5离线特征回填本地模式跑测试生产走YARNPostgreSQL15元数据和决策日志决策日志表要按天分区ONNX Runtime1.17模型推理比直接加载PyTorch快不少安装顺序有讲究先装PostgreSQL和Redis再装Kafka和Flink最后装Spark。因为Flink的Kafka连接器依赖Kafka的客户端库顺序反了容易出类冲突。Python依赖用conda建虚拟环境别用系统自带的pip不然编译PyFlink的时候会缺头文件。3.2 特征定义与管道配置环境准备好之后第一步是定义特征。我拿一个简化的风控场景举例定义三个核心特征用户近7天交易频率、近30天退货率、当前设备是否首次出现。# features/risk_features.yaml features: - name: user_7d_txn_freq entity: user_id type: float offline_source: dwd.txn_log online_source: kafka.txn_topic window: 7d aggregation: count(txn_id) default_value: 0.0 window_align: event_time - name: user_30d_return_rate entity: user_id type: float offline_source: dwd.return_log online_source: kafka.return_topic window: 30d aggregation: sum(return_cnt) / sum(order_cnt) default_value: 0.0 window_align: event_time - name: device_first_seen entity: device_id type: boolean offline_source: dwd.device_registry online_source: kafka.device_topic window: 1d aggregation: min(event_time) now() - 1d default_value: true window_align: processing_time注意第三个特征的window_align用的是processing_time而不是event_time因为“设备是否首次出现”这个判断依赖的是系统当前时间不是事件时间。这种细节在定义阶段就要想清楚不然后面排查一致性问题时会很痛苦。定义写好后用Jev自带的CLI工具生成两套执行计划jev feature compile --config features/risk_features.yaml --target offline --output build/offline_plan.sql jev feature compile --config features/risk_features.yaml --target online --output build/online_plan.py离线计划是一段Spark SQL在线计划是一段PyFlink代码。两套计划都从同一份YAML生成这是保证一致性的关键。3.3 推理服务的编排配置特征管道跑通之后接下来配置推理编排。Jev的编排配置也是YAML定义决策流水线的各个阶段# pipelines/risk_decision.yaml pipeline_name: risk_decision total_latency_budget_ms: 100 stages: - name: rule_filter type: rule_engine latency_budget_ms: 5 rules: - if: user_7d_txn_freq 50 action: reject confidence: 0.95 - if: device_first_seen true and user_30d_return_rate 0.3 action: review confidence: 0.7 fallback: continue - name: gbdt_rank type: onnx_model model_path: models/risk_gbdt_v3.onnx latency_budget_ms: 20 input_features: - user_7d_txn_freq - user_30d_return_rate - device_first_seen fallback: rule_filter_result - name: deep_fallback type: onnx_model model_path: models/risk_deep_v2.onnx latency_budget_ms: 35 trigger_condition: gbdt_rank.confidence 0.8 fallback: gbdt_rank_result这个配置的意思是先走规则引擎如果规则能给出高置信度结论就直接返回否则走GBDT模型如果GBDT的置信度还是不够再走深度模型兜底。每个阶段都有延迟预算和降级路径任何一环超时都不会阻塞整个决策。3.4 上线前的验证清单在把流水线推到生产之前我一般会跑一遍下面这个验证清单。这个清单是踩了无数次坑之后总结出来的少跑一项都可能在上线后出问题。验证项方法通过标准在线离线特征一致性同一批样本分别走离线和在线管道对比特征值差异率低于0.1%推理延迟P99用压测工具打1000 QPS持续10分钟P99低于延迟预算的80%降级路径可用性手动杀掉特征缓存观察系统行为降级后决策成功率高于99%决策日志完整性检查日志表是否有缺失的请求ID缺失率低于0.01%反馈回流延迟模拟标签回填观察增量训练触发回填后5分钟内触发训练注意在线离线一致性验证要在生产数据上做不能用测试数据。测试数据的分布太干净掩盖了很多边界情况。我们有一次用测试数据验证通过上线后发现真实数据里有大量空值离线填了0在线填了默认值两边不一致导致模型效果暴跌。4. 常见问题与排查技巧实录4.1 特征值漂移最难排查的一类问题特征值漂移是决策系统里最隐蔽的故障。模型效果突然下降查日志发现推理正常、延迟正常、没有报错但决策准确率就是掉了。这种情况十有八九是特征值漂移了。排查特征漂移的第一步是对比在线特征和离线特征的分布。Jev的监控面板里有一个“特征分布对比”视图把过去24小时在线特征的分位数和离线回填的分位数画在一起。如果两条线分叉了说明在线离线不一致。常见的分叉原因有上游数据源改了字段类型、Kafka消息格式变了、Flink算子的并行度调整导致窗口计算逻辑变化。我遇到过一次很诡异的情况在线特征的分位数整体偏高但离线特征正常。查了半天发现是Flink的watermark策略配错了导致窗口触发时间比预期晚了5秒把一些本该算进下一个窗口的事件算进了当前窗口。这种问题不看源码根本发现不了后来我们在Flink作业里加了一个窗口触发时间监控每次窗口触发都打一条日志跟预期时间对比偏差超过1秒就告警。4.2 推理超时的级联效应推理超时本身不可怕可怕的是超时引发的级联效应。一个请求超时了编排层触发降级降级路径又超时再触发更底层的降级最后整个流水线雪崩。Jev的编排层有一个降级深度限制默认最多降两级超过两级直接返回一个安全的默认决策不再继续降级。但光有限制还不够还要有熔断机制。如果某个阶段的超时率在1分钟内超过10%编排层自动把这个阶段从流水线里摘掉所有请求直接走下一级。等超时率降下来再自动恢复。这个熔断阈值要根据业务容忍度来调风控场景可以设得激进一些推荐场景可以设得保守一些。4.3 模型版本切换的灰度策略模型迭代是常态但模型切换不能一刀切。Jev支持按流量百分比灰度和按用户分组灰度两种策略。按流量百分比灰度适合验证模型整体效果按用户分组灰度适合验证模型在特定人群上的表现。灰度过程中要盯紧几个指标决策准确率、决策覆盖率有多少请求拿到了非默认决策、平均置信度、以及下游业务的反馈指标。如果灰度组的准确率比对照组低超过1个百分点自动回滚。这个回滚阈值不要设得太松因为模型效果下降往往不是线性的等你看出来的时候已经跌了很多了。实操心得灰度期间不要同时改特征和模型。我们有一次既换了模型版本又加了一个新特征结果效果下降排查了两天才发现是新特征的空值处理有问题跟模型本身没关系。后来定了个规矩特征变更和模型变更必须分开灰度中间至少隔一个完整的反馈周期。4.4 常见问题速查表现象可能原因排查方法解决措施决策准确率突然下降特征漂移对比在线离线特征分布修复特征管道回滚模型推理延迟P99飙升某个阶段超时查看各阶段延迟分位数调整延迟预算或熔断该阶段决策日志缺失日志写入阻塞检查日志表分区和磁盘IO扩容日志存储异步写入反馈回流延迟标签队列积压查看队列深度和消费速率增加消费者优化回填逻辑模型加载失败ONNX版本不兼容检查ONNX Runtime版本统一训练和推理的ONNX版本灰度效果不显著流量分配不均检查灰度分组哈希算法改用一致性哈希确保分组稳定5. 从生产反馈中沉淀的几条硬经验5.1 决策系统的可观测性比模型精度更重要这话听起来有点反直觉但我在生产环境里反复验证过一个精度85%但完全可观测的决策系统比一个精度90%但黑盒的系统更有价值。因为前者你能知道它为什么错能持续优化后者错了你只能干瞪眼。Jev的可观测性建设包括三个层面指标层面每个阶段的延迟、吞吐、错误率、日志层面每个决策请求的完整链路追踪、特征层面每个特征的实时分布和漂移检测。这三层缺一不可。我见过太多团队只做指标监控出了问题查日志发现日志没打全最后只能靠猜。5.2 决策日志的存储成本要提前规划决策日志是排查问题的命根子但它的增长速度往往被低估。一个中等规模的决策系统每天产生几千万条决策记录每条记录包含请求参数、特征快照、模型输出、最终决策压缩后大概2KB。一天就是几十GB一年就是几十TB。如果不做冷热分离存储成本会很快失控。Jev的做法是热数据存30天走SSD支持快速查询温数据存1年走HDD查询慢但便宜冷数据归档到对象存储只保留聚合统计结果。这个分层策略能把存储成本压到全热存储的十分之一左右。5.3 别忽视决策的“可解释性”需求生产环境里的决策系统很多时候不只是要给出结果还要解释为什么。比如风控拒绝了用户的贷款申请用户有权知道原因比如推荐系统给用户推了一个奇怪的东西运营需要知道是哪个特征起了作用。Jev在推理层集成了SHAP值计算对每个决策请求输出特征贡献度。这个计算会增加一些延迟所以只在需要解释的场景下开启比如用户申诉流程。SHAP值的计算成本跟特征数量成正比特征多了延迟会很明显。我们的做法是只对Top 10贡献特征做精确计算其余特征用近似值。实测下来解释延迟能控制在5毫秒以内对主流程几乎没影响。5.4 团队协作的接口约定要写死在代码里决策系统涉及算法、工程、业务三方协作接口约定不清楚是最大的效率杀手。Jev的做法是把所有接口约定写成可执行的契约测试。比如特征定义文件里声明的字段类型和默认值会生成对应的契约测试用例算法团队改了特征定义契约测试会自动跑不通过就合不了代码。这个做法一开始推的时候有阻力算法团队觉得约束太多。但跑了一个季度之后因为接口不一致导致的线上问题降到了零大家就都认了。我的体会是约定要写在代码里不要写在文档里。文档会过时代码不会。5.5 容量规划要留足突发余量决策系统的流量往往有突发性。大促期间的风控请求可能是平时的十倍热点事件期间的推荐请求可能瞬间翻几十倍。Jev的容量规划原则是按峰值流量的1.5倍配置资源按峰值流量的2倍做压测。多出来的0.5倍是安全余量用来应对突发和故障转移。但资源不是越多越好闲置资源也是成本。Jev支持弹性伸缩平时只跑基础容量监控到流量上涨时自动扩容。扩容的触发阈值要设得敏感一些因为决策系统的扩容不像Web服务那么快模型加载和特征预热都需要时间。我们的经验是流量达到基础容量的70%就开始扩容等流量涨到100%的时候新实例已经预热好了。这个内容后续还可以这样扩展把Jev的编排层跟主流的模型服务框架做对比测试看看在不同流量模式下的延迟和成本差异或者把反馈回流机制跟在线学习结合起来做真正的实时模型更新。我目前正在试的是把决策日志喂给一个轻量级的强化学习模块让系统自己学会调整级联推理的阈值初步结果看起来有搞头等跑完一个完整的反馈周期再跟大家分享。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。