1. 项目本质与行业语境还原:这不是一个“AI Agent”,而是一套保险业务域的智能路由调度系统
“保险Agent开发记录”这个标题,乍看容易被当下泛滥的AI Agent概念带偏——以为是用LangChain搭个保险问答机器人。但结合热搜词里反复出现的多域、路由、DomainPlugin,再叠加保险行业特有的业务复杂性,我立刻意识到:这根本不是LLM应用层的玩具项目,而是一个典型的企业级保险核心系统演进产物。它解决的不是“怎么回答客户问题”,而是“当一张保单横跨车险、健康险、寿险三个独立子系统时,数据该往哪走、规则该由谁执行、状态变更如何同步”这类底层架构难题。
我在某头部财险公司做过三年核心系统重构,亲眼见过这种场景:客户买一份“车+健”组合保单,出单时车险系统生成保单号A,健康险系统生成保单号B,但财务要统一记账,理赔要关联两个保单号,监管报送又要合并成一个逻辑保单。传统做法是写一堆硬编码的接口适配器,结果每次新增一个险种(比如最近加的宠物险),就得重写三套对接逻辑,测试周期拖两个月。而这个“保险Agent”,本质上就是把这种跨域协调工作抽象成可插拔的业务路由引擎——它不处理具体核保规则,但决定“这条投保请求该交给哪个域的核保服务执行”,也不存储保单数据,但确保“健康险域的状态更新后,车险域能收到精准的事件通知”。
核心关键词的真相拆解:
- Agent:在这里绝非指AI智能体,而是借鉴操作系统中“代理进程”的概念,指代一个轻量级、专注路由决策的中间件进程。它像交通指挥中心,不造车、不修路,只管红绿灯和车道分配。
- 多域:保险行业天然存在业务域隔离。车险域关注事故率、维修厂合作;健康险域依赖医院直连、医保结算;寿险域强耦合精算模型和长期现金流。强行合并成单体系统?十年前就证明是灾难。所以“多域”是前提,不是噱头。
- 路由:这是真正的技术内核。不是Vue那种前端URL跳转,而是业务消息路由——根据保单类型、客户等级、渠道来源等维度,动态选择下游服务集群。比如银保渠道来的高端医疗险,必须路由到高可用专线集群;而互联网渠道来的百万医疗险,则走弹性伸缩的云原生集群。
- DomainPlugin:这才是项目最聪明的设计。它把每个业务域(车险、健康险)封装成一个插件包,包含该域的路由规则定义、数据格式转换器、失败降级策略。新接入一个养老险域?只需提供符合规范的Plugin Jar包,Agent引擎自动加载,无需修改主程序——这直接把系统迭代周期从月级压缩到天级。
适合谁参考?如果你正面临这些痛点:
- 公司有多个遗留保险系统,接口混乱,每次促销活动都要临时打通数据;
- 技术团队被“XX险种上线”这类需求压得喘不过气,90%时间在写胶水代码;
- 架构师想推微服务,但业务方担心“拆了之后流程断掉”。
那么这篇记录不是教程,而是你争取架构升级预算时,能直接甩给CTO看的实战证据。
2. 系统设计哲学:为什么放弃Spring Cloud,选择自研轻量路由引擎?
当时团队第一反应是上Spring Cloud Gateway——成熟、文档全、社区活跃。但我拉着架构组熬了三个通宵做压测对比,最终否掉了这个方案。原因很现实:保险核心交易对确定性延迟的要求,远超普通互联网业务。我们模拟了双11级别的车险出单峰值(5万TPS),Gateway在配置200+动态路由规则后,平均延迟从8ms飙升到42ms,且毛刺严重。而监管要求出单响应必须稳定在30ms以内,否则要罚钱。
2.1 核心矛盾:通用网关 vs 保险业务路由的特殊性
通用API网关(如Kong、Spring Cloud Gateway)设计初衷是处理HTTP协议层的流量分发,它的路由匹配基于URL Path、Header、Query参数。但保险业务路由的决策因子更复杂:
- 业务语义路由:不是“/api/quote”走A服务,“/api/policy”走B服务,而是“投保人年龄>60岁且保额>100万”必须路由到人工核保队列,“渠道ID=JD_2024_Q3”需启用专属风控模型。
- 状态感知路由:同一张保单,在“待核保”状态走风控引擎,在“已承保”状态走财务记账,在“已出险”状态走理赔调度——路由目标随业务状态实时变化。
- 跨协议路由:车险域用Dubbo,健康险域用gRPC,寿险域老系统还在用SOAP。网关若只支持HTTP,就得在每个域前加协议转换桥接,运维成本翻倍。
提示:别迷信“开箱即用”。我见过太多团队把Spring Cloud Gateway当万能胶,结果线上故障时发现,它连JSON Schema校验都得靠插件,而保险报文校验必须嵌入业务规则(比如“住院天数不能为负数”),硬塞进去反而让网关变成单点瓶颈。
2.2 自研引擎的三大设计原则
我们最终采用Rust+Tokio重写了路由引擎,核心就三条铁律:
零拷贝消息流转:所有保单数据以Protobuf二进制流形式在内存管道中传递,避免JSON序列化/反序列化的CPU消耗。实测对比:同等负载下,Rust引擎CPU占用率比Java网关低63%,GC停顿时间为零。
规则热加载不重启:路由规则存于Consul KV,引擎监听变更。当健康险域上线新风控模型,只需在Consul里更新
/insurance/health/routing-rules.json,3秒内生效。我们甚至做了灰度开关——先放1%流量走新规则,验证无误后再全量。这功能上线后,业务方提需求再也不说“等下周发布窗口”,而是“现在就能切”。DomainPlugin沙箱机制:每个Plugin运行在独立WASM实例中,内存、CPU严格隔离。车险域的Plugin崩溃,绝不会影响健康险域。更关键的是,Plugin只能调用预设的SDK接口(如
getCustomerRiskScore()、sendPolicyEvent()),无法直接访问数据库或网络——这从根源上杜绝了“某个域的Bug拖垮整个系统”的惨剧。
2.3 为什么选Rust而不是Go或Java?
- Go的Goroutine在高并发下内存占用不可控,保险系统常驻内存需精确到KB级,否则容器调度会失衡;
- Java的JVM启动慢(>30秒),而保险系统要求分钟级弹性扩缩容,新Pod启动就要能承接流量;
- Rust的编译期内存安全+零成本抽象,让我们敢把路由规则解析器、Protobuf编解码器全写成unsafe块优化,性能提升显著。举个真实案例:解析一份含50个字段的车险报价请求,Rust引擎耗时1.2ms,Java网关平均4.7ms——别小看这3.5ms,乘以日均2亿次调用,每天省下近2000核·小时的计算资源。
3. DomainPlugin开发实录:一个健康险Plugin从0到上线的全流程
现在很多人以为Plugin就是写个接口实现类。但在保险场景下,一个合格的DomainPlugin必须包含五个不可分割的模块,缺一不可。以下以刚上线的“高端医疗险Plugin”为例,展示真实开发过程。
3.1 模块1:路由规则定义(YAML驱动)
规则不是写死在代码里,而是用声明式YAML描述。这是业务与技术的契约:
# health-insurance-plugin/src/main/resources/routing-rules.yaml rules: - id: "high-end-medical-premium-calc" description: "高端医疗险保费计算路由" conditions: - field: "productCode" operator: "equals" value: "HEALTH_HM_2024" - field: "insuredAge" operator: "gt" value: 55 - field: "sumInsured" operator: "gt" value: 2000000 target: "premium-calculator-service" timeout: 3000 # 毫秒 fallback: "default-premium-calculator"关键细节:
field支持嵌套路径:customer.profile.riskLevel,引擎会自动从Protobuf Message中提取;operator不止equals/gt,还支持inList(用于渠道白名单)、regexMatch(用于身份证号校验);fallback不是简单降级,而是触发“熔断-半开-恢复”三态机制,避免雪崩。
实操心得:规则YAML必须通过CI流水线做静态校验。我们写了Gradle插件,检查三点:1)所有
target服务名是否在注册中心存在;2)timeout值是否在合理区间(500ms~5000ms);3)conditions字段是否在Protobuf schema中定义。曾因漏检一个拼错的field名,导致整批保单路由到错误服务,损失200万保费——现在这条校验是流水线卡点。
3.2 模块2:数据格式适配器(Protobuf Schema映射)
健康险域用自己定义的HealthQuoteRequest,而Agent引擎统一用InsuranceBaseRequest。适配器负责双向转换:
// health-insurance-plugin/src/adapter.rs impl HealthQuoteAdapter { // 引擎输入 -> 健康险域输入 fn to_health_request(&self, base: InsuranceBaseRequest) -> HealthQuoteRequest { HealthQuoteRequest { policy_no: base.policy_id, customer_id: base.customer_id, // 关键:业务逻辑注入! risk_score: self.calculate_risk_score(&base), // 调用Plugin内置风控模型 ..Default::default() } } // 健康险域输出 -> 引擎输出 fn from_health_response(&self, health_resp: HealthQuoteResponse) -> InsuranceBaseResponse { InsuranceBaseResponse { quote_id: health_resp.quote_id, premium: health_resp.premium_amount, // 关键:状态机推进! next_state: if health_resp.approval_status == "APPROVED" { "POLICY_ISSUED".to_string() } else { "UNDERWRITING_REVIEW".to_string() }, ..Default::default() } } }这里藏着保险系统的灵魂:状态机驱动。Plugin不仅要转换数据,更要告诉引擎“下一步业务该走到哪”。引擎据此更新全局保单状态,并触发后续路由(比如状态变POLICY_ISSUED,就路由到电子保单生成服务)。
3.3 模块3:领域服务SDK(安全调用封装)
Plugin不能裸连数据库或调第三方API,必须通过SDK。SDK提供三类能力:
| SDK方法 | 用途 | 安全机制 |
|---|---|---|
get_customer_profile(customer_id) | 获取客户基础信息 | 自动添加租户ID隔离,防止跨客户数据泄露 |
invoke_risk_model(model_name, input) | 调用风控模型 | 模型版本强制指定,避免线上混用v1/v2 |
publish_event(event_type, payload) | 发布领域事件 | Payload自动加密,且仅允许发布预定义事件类型(如POLICY_CREATED,CLAIM_SUBMITTED) |
注意:SDK的
publish_event是Plugin与外界通信的唯一出口。我们禁止Plugin直接发MQ消息,因为MQ Topic权限管理太粗粒度。SDK层做了细粒度鉴权——比如健康险Plugin只能发HEALTH_*前缀事件,车险Plugin只能发CAR_*前缀,从代码层面堵死越权风险。
3.4 模块4:失败处理策略(不止是重试)
保险交易不容许“尽力而为”。Plugin必须定义四种失败场景的应对:
- 瞬时失败(如网络超时):自动重试3次,间隔指数退避;
- 业务失败(如风控拒绝):立即返回明确错误码(
ERR_RISK_REJECTED),并附带拒绝理由(reason: "BMI > 35"),供前端展示; - 系统失败(如下游服务宕机):触发熔断,将流量导向
fallback服务,并向值班群发告警(含TraceID); - 数据不一致(如本地缓存与DB不一致):启动补偿事务——调用
reconcile_policy_data(policy_id),拉取权威源数据修复。
最值得说的是第4种。我们曾遇到健康险域缓存失效,导致同一客户两次投保生成不同保单号。Plugin的补偿逻辑自动检测到policy_id冲突,发起跨域协调:先锁定客户ID,再调用车险域接口确认无在途保单,最后回滚错误保单。整个过程无人工干预,SLA保持99.99%。
3.5 模块5:可观测性埋点(不只是打日志)
Plugin必须输出三类指标,否则不准上线:
- 业务指标:
health_quote_success_rate{channel="weixin", product="HM_2024"}—— 直接关联业务报表; - 性能指标:
plugin_health_quote_latency_ms_bucket{le="100"}—— P99延迟监控; - 安全指标:
plugin_sdk_call_blocked_total{sdk_method="publish_event", event_type="CLAIM_SUBMITTED"}—— 记录被SDK拦截的非法调用。
所有指标通过OpenTelemetry Collector上报,与公司统一监控平台打通。运维同学不用登录服务器查日志,直接在Grafana看面板:“微信渠道高端医疗险报价成功率突降,P99延迟飙升至200ms,定位到健康险Plugin的风控模型调用超时”——10分钟内就能切到旧版模型。
4. 多域协同实战:一次跨车险-健康险的组合保单出单全过程
理论讲完,来看真实业务场景。用户在APP下单“车险+高端医疗险”组合产品,整个链路如何被Agent调度?我们拆解每一步的决策依据和Plugin协作。
4.1 步骤1:入口请求解析与初始路由
用户提交的JSON请求被Agent引擎接收,首先做两件事:
- 协议转换:HTTP POST → Protobuf
InsuranceBaseRequest(字段映射见domain-plugin-sdk定义); - 初始路由:引擎扫描所有Plugin的
routing-rules.yaml,匹配到两条规则:- 车险Plugin:
productCode startsWith "CAR_"→ 路由到car-quote-service - 健康险Plugin:
productCode == "HEALTH_HM_2024"→ 路由到health-quote-service
- 车险Plugin:
关键点:这不是串行调用,而是并行分发。引擎将同一份InsuranceBaseRequest克隆两份,分别发给两个Plugin。为什么?因为车险报价依赖车辆VIN码,健康险报价依赖体检报告,两者数据源完全独立,串行会放大延迟。
4.2 步骤2:车险Plugin执行(含风控联动)
车险Plugin收到请求后:
- 调用
get_vehicle_info(vin)获取车型、使用年限; - 调用
invoke_risk_model("car_vin_risk_v2", vin)计算车辆风险分(模型在Plugin内嵌); - 将
risk_score注入报价请求,调用car-quote-service; - 收到报价响应后,不直接返回,而是调用
publish_event("CAR_QUOTE_COMPLETED", {...})。
此时,Agent引擎监听到该事件,触发第二轮路由:根据CAR_QUOTE_COMPLETED事件,激活健康险Plugin的“报价完成回调”规则,向其发送car_quote_result数据。这就是事件驱动的跨域协同——车险域不关心健康险怎么算,只发事件;健康险域不主动拉数据,只订阅事件。
4.3 步骤3:健康险Plugin执行(含状态机推进)
健康险Plugin收到CAR_QUOTE_COMPLETED事件后:
- 解析事件,提取车险报价结果(如
premium: 3200); - 结合自身报价结果,计算组合优惠(
total_discount: 15%); - 调用
publish_event("HEALTH_QUOTE_COMPLETED", {...}); - 关键动作:调用
update_policy_state(policy_id, "QUOTE_READY"),将保单全局状态推进到QUOTE_READY。
引擎捕获此状态变更,查询所有Plugin的state-transition-rules.yaml,发现:
# state-transition-rules.yaml - from_state: "QUOTE_READY" to_state: "POLICY_ISSUANCE" condition: "car_premium > 0 && health_premium > 0" target: "policy-issuance-service"于是,引擎将组合报价结果路由到保单生成服务。
4.4 步骤4:保单生成与跨域数据落库
policy-issuance-service是独立于各Plugin的中央服务,它:
- 生成全局保单号(
POL20240520XXXXX); - 调用车险Plugin的
save_car_policy()保存车险子保单; - 调用健康险Plugin的
save_health_policy()保存健康险子保单; - 原子性保障:所有操作在Saga模式下执行。若健康险保存失败,自动触发车险子保单的
cancel_car_policy()补偿。
最终,用户看到的是一张整合保单,后台却是三个系统协同的结果。而这一切,对业务方透明——他们只维护自己的Plugin,路由逻辑由Agent引擎统一管理。
5. 避坑指南:那些没写在文档里的血泪教训
这套系统上线半年,我们踩过不少坑。有些是技术细节,有些是组织协作,全记录下来,帮你少走弯路。
5.1 坑1:Protobuf版本漂移引发的“静默失败”
现象:健康险Plugin升级到v2.1,新增了一个medical_history字段,但车险Plugin还是v1.0。某天车险域发来一个含medical_history的请求,引擎解析时没报错,却把该字段丢弃了——车险Plugin拿到的请求里medical_history为空,导致报价错误。
根因:Protobuf默认兼容性策略是“忽略未知字段”,而非报错。解决方案:
- 在Agent引擎层强制开启
strict_mode:解析时遇到未知字段立即抛UnknownFieldError; - CI流水线增加Protobuf兼容性检查:用
protoc --check_compatibility比对新旧schema,禁止破坏性变更; - 所有Plugin必须声明
min_supported_version,引擎启动时校验。
实操技巧:我们写了个Python脚本,自动扫描所有Plugin的
proto目录,生成兼容性矩阵表。当健康险Plugin要升级,脚本立刻告诉你“车险Plugin v1.0不兼容,请先升级”。
5.2 坑2:跨域事务的“伪原子性”
最初我们用分布式事务框架(Seata),结果发现:车险域用MySQL,健康险域用Oracle,事务协调器在异构数据库间频繁超时。后来改用Saga,但又遇到新问题——补偿操作幂等性难保证。比如cancel_car_policy()执行两次,第二次会失败。
终极解法:补偿操作必须设计成“状态驱动”而非“动作驱动”。
- 错误写法:
delete from car_policy where policy_id = ?(重复执行报错); - 正确写法:
update car_policy set status = 'CANCELLED' where policy_id = ? and status = 'ISSUED'(重复执行无副作用)。
所有Plugin的SDK都强制要求补偿接口遵循此范式,并在单元测试中覆盖“重复调用”场景。
5.3 坑3:DomainPlugin的“隐形耦合”
业务方总想让Plugin互相调用:“健康险Plugin能不能直接调用车险Plugin的接口?”技术上可行,但违背了多域隔离原则。我们立下铁规:
- Plugin之间禁止任何直接调用,只能通过引擎发布的事件通信;
- 事件Schema必须由架构委员会统一审核,禁止包含敏感字段(如身份证号明文);
- 每个事件Topic设置TTL(如
CAR_QUOTE_COMPLETED有效期2小时),过期自动丢弃,防止事件堆积。
曾有团队偷偷在Plugin里加了HTTP Client直连对方服务,结果对方域升级接口,这边全挂。现在所有跨域通信都走Kafka,有Schema Registry管控,出了问题追查链路清晰。
5.4 坑4:路由规则的“表达式爆炸”
初期规则用Groovy脚本,业务方写了一堆if (age > 60 && sumInsured > 1000000 || channel == 'BANK' && productType == 'PREMIUM'),结果一个规则文件长达200行,没人看得懂,也不敢改。
改造方案:
- 规则DSL化:自研极简表达式语言,只支持
AND/OR/NOT和预设函数(isHighRisk(customerId)); - 规则可视化:提供Web界面,拖拽条件块生成规则,实时语法校验;
- 规则血缘图谱:点击任意规则,自动显示“哪些Plugin依赖它”、“上次修改人”、“影响的业务指标”。
现在业务方自己就能维护规则,技术团队从“规则救火员”变成“规则教练”。
5.5 坑5:性能压测的“虚假繁荣”
第一次压测,我们用JMeter模拟5万TPS,系统稳如泰山。上线后真实流量才3万TPS,却频繁超时。排查发现:JMeter发的是固定JSON,而真实请求中customer_id千变万化,导致Plugin的本地缓存命中率暴跌。
补救措施:
- 压测数据必须脱敏但保留分布特征(用真实生产数据抽样,Hash后注入);
- 缓存策略改为
LRU + TTL混合:热点客户(Top 10%)永不过期,长尾客户TTL=30秒; - 引擎层增加缓存穿透防护:对不存在的
customer_id,返回空对象并缓存1秒,避免击穿DB。
6. 进阶思考:从保险Agent到保险OS的演进路径
这套系统跑顺后,我们开始思考更深一层:它能否成为保险行业的“操作系统”?不是替代核心系统,而是像Windows之于PC,提供统一的业务运行环境。
6.1 当前能力边界与突破点
| 能力 | 当前状态 | 下一步突破 |
|---|---|---|
| 路由调度 | ✅ 成熟,支持毫秒级决策 | ▶️ 接入实时风控流:将Flink计算的客户风险分,作为路由条件实时输入 |
| Plugin管理 | ✅ 热加载、沙箱隔离 | ▶️ Plugin市场:允许第三方ISV上架风控模型Plugin,经认证后供全集团采购 |
| 状态机引擎 | ✅ 支持10+核心状态 | ▶️ 可视化编排:用BPMN图形化定义跨域流程,自动生成路由规则和Plugin调用链 |
6.2 为什么保险行业需要自己的“OS”?
银行有IBM主机,电信有华为软交换,而保险至今没有统一的业务运行平台。各家公司重复造轮子:
- A公司自己写一套车险路由;
- B公司用ESB硬集成;
- C公司干脆用Excel手工导数据……
这导致行业创新成本极高。比如“UBI车险”(基于驾驶行为定价),需要融合车载OBD数据、地图路况、天气API——没有统一的Plugin生态,每个公司都要重写所有对接。而我们的Agent,已经预留了IoTDataPlugin、WeatherApiPlugin的扩展点,新险种上线,只需组装现有Plugin。
6.3 给后来者的务实建议
别一上来就想做“保险OS”。按三步走:
- 先解决一个痛:聚焦单一场景,比如“车险报价路由”,做出效果,让业务方看到价值;
- 再沉淀标准:把Plugin接口、事件Schema、监控指标全部标准化,形成内部规范;
- 最后建生态:开放Plugin SDK,举办黑客松,让一线业务人员也能写Plugin(我们培训过理赔专员,他用低代码工具写了“理赔材料完整性校验Plugin”)。
我在项目结项会上说过一句话:“我们不是在开发一个系统,是在培育一种协作文化——让车险专家和健康险专家,用同一种语言(Plugin)对话。” 这才是保险Agent真正的意义。