保险Agent开发记录

一个域一份 prompt 的 Agent,我做到第二个域就接不下去了

多域 Agent 落地记录 · 01

这是我把手上这个保险 Agent 从设计做到落地的过程记录。走对的和踩坑的都写,不保证更新频率,但会一直写下去。

先说背景,免得看到一半发现不是你要的东西。

我在做保险业务的 Agent,要覆盖四个业务域:财务、续期、保全、消费者保护。不是写个 demo 发朋友圈那种,是真准备给业务组用的。

第一版朴素得有点寒酸:一个 agent loop,一份 system prompt,四个工具。财务域先跑起来了,效果还行,我当时还挺得意。

然后要接续期。我在这卡了两天。

代码在这儿,MIT,拿去改就是: github.com/a2819778558...


第一版我写了什么

大概是这样的:

erlang 复制代码
你是财务助手。
当用户查询欠费时,先调用 queryArrears 拿到欠费期数,
如果期数 > 1,再调用 calcLateFee 计算滞纳金,
把两个结果合并成表格返回。
用户问报销进度就调 queryReimbursement。

看着挺合理对吧。单域场景确实能用,我甚至还给它加了几个边界条件的说明。

当时我的想法是"把流程写清楚,模型就不会乱来"。这个想法在单域的时候是对的。

第一次崩:prompt 越写越死

那天有个测试同事随手问了一句:

我这张保单欠了两期,滞纳金算一下,另外我上个月的报销单什么状态?

模型的回答是:滞纳金算得挺对,报销单没提。

我回去看,问题很清楚------我 prompt 里写的那条流程是"查欠费 → 算滞纳金 → 返回",里面根本没有"顺手回答另一个问题"这一步。模型很听话地走完了我画的路线,然后收了。

我第一次修是加一条规则:"如果用户同时问了多个业务域的问题,要分别处理。"后来发现得加第二条、第三条......最后那份 prompt 变成了一本规则手册,写满了各种 if-else 的自然语言版本。

而且它还是不好用。 规则越多,模型越容易顾此失彼。

第二次崩:加一个域要动内核

这个比上一个严重,因为它不是效果问题,是结构问题。

我要加续期域,实际操作是:

  1. 复制一份写死的 prompt,把"财务"改成"续期"
  2. 复制一套工具注册的代码
  3. 在 agent loop 里加几个 if (domain.equals("renewal"))

加一个域,我得改核心代码。

这意味着后面三个业务组(续期、保全、消保)没办法各自独立地往里填东西------每加一个域都得我来改一遍。四个组排队等我一个人。

到这儿我才决定返工。


我原来把"域"理解错了

返工前我想了很久,发现根源是一个挺蠢的误解。

我一开始默认"域"是执行边界:这个问题属于财务域,就用财务域的工具、跑财务域的提示词。

但用户不按你的组织结构提问。

开头那句话就是例子------「保单下个月到期,想用现金价值续一下,顺便看看有没有欠费」。这一句里有到期和续期(续期域)、现金价值(保全域)、欠费(财务域)。三个域同时在线。

如果域是执行边界,这句话就无处安放。你会被迫选一个"主域",然后另外两个域的能力直接被砍掉。

所以域这个东西,本质上是**"谁负责维护什么"的管理单位,不是"这个问题归谁执行"的边界**。

想通这一点之后,代码改动其实很小:路由的输出从一个域变成一个域集合。

java 复制代码
public record RouteResult(
        Set<String> domains,        // ← 唯一的实质改动:集合,不是单个
        String primaryDomain,
        double confidence,
        boolean needsClarification,
        String reason               // 保险场景要可解释,得写进审计日志
) {
    public static RouteResult askClarification(String reason) {
        return new RouteResult(Set.of(), null, 0.0, true, reason);
    }
}

这句话改完,跨域问题就能装配出多个域的工具并集了。原来那句"顺便看看有没有欠费"从"被丢掉"变成"顺手就答了"。


现在的结构

graph TB U[用户提问] --> R[路由层<br/>意图 → 域集合] R -->|domains = Set| P[平台层·域无关<br/>AgentLoop · DomainRegistry] P --> A[DomainToolAssembler<br/>装配多域工具并集] A --> D1[财务域] A --> D2[续期域] A --> D3[保全域] A --> D4[消保域] D1 -.依赖只能向上.-> P D2 -.依赖只能向上.-> P D3 -.依赖只能向上.-> P D4 -.依赖只能向上.-> P
复制代码
┌─────────────────────────────────────────┐
│ 平台层(域无关,做一次,四域共用)        │
│ AgentLoop · DomainRegistry · Assembler   │
├─────────────────────────────────────────┤
│ 路由层(意图 → 域集合)                   │
├─────────────────────────────────────────┤
│ 域层(每域一份配置,可插拔)              │
│ 财务 │ 续期 │ 保全 │ 消费者保护           │
└─────────────────────────────────────────┘

每个域都实现同一个接口,这就是"加一个域内核一行都不用改"的全部内容:

java 复制代码
public interface DomainPlugin {

    String name();                              // "finance" / "renewal"
    String displayName();                       // "财务"

    List<IntentSpec> intents();                 // 这个域负责回答哪些意图
    List<ToolSpec> tools();                     // 只放业务动词工具

    String systemPrompt();                      // 只写角色 + 边界

    default PermissionPolicy permissions() {    // 同一域内不同工具可要求不同权限
        return PermissionPolicy.allowAll();
    }

    default FallbackPolicy fallback() {         // 答不了时转给谁
        return FallbackPolicy.toHuman("客服");
    }
}

一个域最少只要交出三样:意图表、工具、系统提示。权限和兜底有默认实现,不写也能跑。

接 Spring 更省事,各域实现类标个 @Component,平台侧注入 List<DomainPlugin> 就自动全收齐了,注册代码都不用写。


跑起来是这样的

我不太喜欢拿单域问答演示 Agent,那看不出结构好坏。跨域问题才是分水岭。

这是真实输出,我直接贴上来的:

ini 复制代码
已注册的域:
  - finance    财务      意图 3 个, 工具 3 个
  - renewal    续期      意图 2 个, 工具 2 个
  - servicing  保全      意图 3 个, 工具 2 个
  - protection 消费者保护  意图 2 个, 工具 1 个

================ 用户:我这保单下个月到期,想用现金价值续一下,顺便看看有没有欠费

路由结果:domains=[servicing, renewal, finance], primary=servicing,
         confidence=1.00, 需澄清=false
决策原因:关键词命中: [servicing, renewal, finance]   (→ 写审计日志)

装配工具(7):[servicing.tryCashValue, servicing.surrender,
             renewal.queryStatus, renewal.calcPremium,
             finance.queryArrears, finance.calcLateFee,
             finance.queryReimbursement]

→ 跨域取数:
   [保全] servicing.tryCashValue  => {现金价值=12450.3, 试算日=2026-10-01}
   [续期] renewal.queryStatus     => {下期到期日=2026-10-20, 宽限期至=2026-12-20}
   [财务] finance.queryArrears    => {欠费期数=2, 欠费金额=3860.0, 状态=已逾期}

一句话命中三个域,装配出 7 个工具的并集,然后跨三个域取数。

说清楚一点,免得有人以为我在吹:上面第 4 步"跨域取数"是写死的占位实现,真实项目这一步由 LLM 决定调哪几个工具。前 3 步(注册 → 路由 → 装配)是真跑的逻辑。


顺带一个踩出来的经验:工具别暴露 CRUD

这条是我实际踩过的,写在 ToolSpec 的注释里:

erlang 复制代码
✅ servicing.surrender      办理犹豫期退保(业务规则封在工具内部)
✅ servicing.tryCashValue   试算现金价值
✅ finance.queryArrears     查询欠费明细
❌ updatePolicyField        改字段
❌ execSql                  裸 SQL

原因很直接:写操作出事就是事故。

给模型一个 updatePolicyField,它就会很自信地往一张不该改的表里写值,而且你还拦不住。但如果给的是 servicing.surrender,那么犹豫期校验、状态检查、退款计算这些规则全都封在工具内部,模型只能决定"要不要办退保",决定不了"退保该怎么办"。

粗一点的经验是:读和查询可以粗,写和动作必须细。

还有一个不是代码的事

我做了份 docs/域接入登记表.md,是给业务专家填的,不需要懂代码。

里面最要紧的是意图表:

意图 ID 意图名称 类型 用户可能的问法 需要哪些工具
finance.arrears 查询保单欠费明细 tool 我这张保单有没有欠费;欠了多少钱;还剩几期没交 finance.queryArrears
renewal.premium 计算应缴保费 tool 这一期要交多少钱 renewal.calcPremium
... tool/rag/chat/handoff

这张表同时是三样东西:路由做向量匹配的依据、评测 golden set 的骨架、业务和开发之间的接口。

我最初的版本没有这张表,意图是散在代码和 prompt 里的。后来发现不行------意图表里的 examples 必须是真实用户怎么说话,这东西只有业务专家知道,我和模型都编不出来。


现在什么还不行

  • 路由器是最简的关键词实现。真实项目要换成「规则 → 向量 → LLM 结构化分类 → 澄清」的级联。我正打算换。
  • 没有评测集。意图表里的 examples 只能算骨架,真实问法得几百条才够量准域准确率、跨域召回率和澄清率。
  • 工具并集到 50 个以上就不能这么干了。扩展点想好了(工具 RAG 或渐进加载),但现在还没到那个量级,先不做。
  • Memory 层还没接。跨轮次上下文归属到哪个域,是下一个要处理的。

下一篇要做什么

想通"域是域集合"之后,下一个撞上来的就是路由器。

现在这个 KeywordRouter 是我拿来演示的,就是个关键词 contains 匹配,真实场景跑不了。下一篇打算换成级联实现:规则先挡一批,剩下走向量,向量也不确定的丢给 LLM 做结构化分类,再不确定就直接反问用户澄清。

这里我预计会掉坑:级联每一层的阀值怎么定,没有评测集的话就是拍脑袋。所以再下一篇应该是搭 golden set,拿几百条真实问法把域准确率、跨域召回率、澄清率量出来。

工具并集和 Memory 归属这两个先放着,没到量级。

当前进度

arduino 复制代码
2026-10-03
✅ 域插件契约定稿(DomainPlugin / ToolSpec / IntentSpec)
✅ 路由输出改成域集合,跨域问题装配工具并集
✅ finance 完整样板 + 另三个域骨架
✅ 域接入登记表(给业务专家填的)
⬜ 路由器换级联实现            ← 下一篇
⬜ 建 golden set,量准三个指标
⬜ 工具 RAG / 渐进加载(工具超 50 个再说)
⬜ Memory 层接入
⬜ 逐域接入生产

骨架地址(MIT):github.com/a2819778558... mvn compile exec:java 就能看到上面那段跨域输出。

相关推荐
imDwAaY1 小时前
Redis List 是链表吗?从 Ziplist 到 Quicklist 揭开底层实现
redis·后端
_风不会停息1 小时前
剥开 Agent 开发:参照 pi-agent 实现一个小 Agent
人工智能·后端
sp421 小时前
Java 加解密组件再设计
java·后端
鶴哥只手遮天1 小时前
从零搭建光电仿真引擎(二):场景建模与材质系统
后端
柠檬味拥抱1 小时前
IP102农作物害虫检测数据集 | 4400张YOLO智慧农业数据集
后端
llqbzllll1 小时前
什么是零拷贝?别被“零”字骗了:一次讲透完整链路
后端
llqbzllll1 小时前
为什么 NoSQL 查询更快?答案不在数据库名字里
后端
ZhenYuChen20001 小时前
2026年后端开发进化:告别CRUD内卷,拥抱AI原生架构与服务编排新时代
后端·开源·全栈
llqbzllll1 小时前
SQL 和 NoSQL 到底怎么选?别再把它们当成非此即彼
后端