引导式对话系统(上):应用场景、业务模式与技术架构引导式对话系统(下):槽位抽取、Function Calling 与流程编排怎么落地? 两篇文章详细介绍了引导式对话的技术架构以及各种实现方案。

本文介绍一种面向保险/金融咨询场景的智能对话方案:通过一次大模型调用完成槽位抽取,再通过策略编排与话术模板完成总结与追问,从而避免「总结 + 追问」的二次 LLM 调用,在保证信息收集完整、流程可控的前提下,显著降低延迟与成本。

代码以及文档参考:

关键要点

  • 一次 LLM + 策略编排:每轮仅一次大模型调用做信息抽取,总结与追问全部由模板与策略函数完成,无二次 LLM。
  • 六大主题有序收集:投保人基本信息、家庭结构、需求与偏好、已有保障、健康告知、补充说明,按固定顺序逐主题推进,用户可跨主题一次性回答。
  • 缺失槽位递归查找:纯逻辑判断当前缺失槽位与下一主题,决定是「模板化反问」还是「总结 + 开启下一主题」或结束语。
  • 槽位持久化:基于 SQLite 按 group_id 持久化槽位,多轮对话可续写与修正,支持跨会话恢复。
  • 流程可控、易扩展:话术与流程由配置与代码驱动,便于维护;可叠加提示词/正则等补漏机制提升抽取稳定性。

1. 项目背景与目标

1.1 项目背景

  • 保险/金融产品咨询量大,机构需大量售前人员,人工成本高。
  • 需先摸清客户情况(年龄、收入、家庭、需求、健康等)才能推荐产品,线上咨询成单率低但咨询量巨大。

1.2 项目目标

开发一款智能对话机器人,实现售前接待 + 信息引导收集,替代或前置人工客服,降低运营成本,收齐信息后做产品匹配或转人工。

1.3 产品交互要求

要求 实现方式
主动引导 通过缺失槽位查找函数与话术模板自动追问
按主题推进 一次询问一个主题的所有子问题,收集完总结并二次确认后开启下一主题
灵活对话 用户主动提及某主题时,优先追问该主题下的其他问题

2. 项目代码结构介绍

本节对项目代码结构与主流程做简要说明;更详细的文件说明、流程图与源码调用追踪见 README_运行与架构.md

2.1 主要文件与职责

文件 说明
main.py Gradio 网页对话入口,在浏览器中与保险咨询助手聊天
server_api_prod.py FastAPI 服务入口,提供 POST /insurance_chatbot 流式接口
server_post_prod.py 示例客户端,用于测试 API 服务
chat_process_client.py 核心:对话主流程(首轮引导、LLM 调用、工具解析、槽位更新、模板反问/总结)
function_utils.py 槽位排序、缺失槽位查找 get_next_message_by_slots、总结模板类 FunctionCallSummaryConfig
tools_definition.py 工具定义(Function Calling 的 tools 描述、槽位与话术配置)
config_parser.py API、数据库等配置

2.2 主流程概览

用户输入后,先读取历史槽位;若为首轮则直接发送固定引导语并流式输出;非首轮则进入 LLM 流式调用。若 LLM 返回普通回复(finish_reason=stop),则直接流式返回;若触发工具调用(tool_calls),则解析并执行工具、更新 all_filled_slots 并写库,再调用 get_next_message_by_slots:当前主题有缺失则模板化反问,无缺失且有下一主题则总结并开启下一主题,全部完成则发送结束语,无缺失且槽位未变则走 LLM 自由对话。详细文字流程与 Mermaid 图见 保险/金融咨询大模型智能办理项目代码——运行说明与项目架构

3. 结果展示

当我们根据 保险/金融咨询大模型智能办理项目代码——运行说明与项目架构 在本地创建了虚拟环境,安装了依赖,添加了大模型的Base URL,API等,以及开始运行。我们通过 http://127.0.0.1:7860 在本地的浏览器打开前端,可以看到界面如下:

请添加图片描述

这个界面由三部分组成:

  • chatbot对话框;
  • 当前槽位信息展示;
  • 槽位重置。

当我们在对话框进行对话,我们会发现,大模型会有意引导用户去填充槽位。而在 当前槽位信息展示 栏,我们也可以查看当前槽位的填充情况。

比如,如果一开始,用户问了一句 你好。自然的,不会有任何槽位被填充。我们看一下大模型的回复,以及槽位填充栏的结果:

请添加图片描述

我们可以看到,系统对于用户的 你好 回复了 欢迎咨询保险服务,我是您的智能顾问。请问您的年龄、职业和大概年收入区间?

如果这个时候用户说 我今年40岁,这里有一个槽位会被填充:年龄。我们看一下大模型的回复,以及槽位填充栏更新后的结果:

请添加图片描述

我们看到,系统回复 收到反馈:年龄:40岁。还得辛苦您补充一下: 职业,年收入区间。相当于,系统会先和你确认一下用户刚才说的信息,然后再引导用户进一步填充其他槽位。

如果用户输入的回复包含了多个槽位信息,比如 我是一名教师,现在年收入在20万元左右。。这时,大模型会提取这两个槽位,然后再引导用户填充其他槽位。如下图:

请添加图片描述

这时候,用户突然想到,他之前输入的信息有误,比如,用户的年收入不是20万,而是10万。我们来看看大模型会怎么回复:

请添加图片描述

我们看到,对于用户的问题 哦不对,我搞错了,我现在的年收入只有10万元不到。,系统回答 已收到您的反馈:年龄:40岁 职业:教师 年收入区间:10万以下您是否是家里主要经济支柱?目前有几个孩子?。我们看到,槽位 年收入区间从 20万元左右跟新为了10万元以下。并且,大模型继续引导用户继续填充其他槽位。

如果用户这时候又输入了一个无关紧要的回答,比如 我也不清楚,我们来看看系统怎么回答:

请添加图片描述

槽位并没有任何改变,这也很合理,大模型进一步引导用户先填充其他槽位:

没关系,我来帮您梳理一下:

✅ 重疾险:覆盖大病治疗费用+收入损失补偿(适合家庭支柱)
✅ 寿险:保障家人生活、房贷/教育金等责任(尤其推荐主要养家者)
✅ 意外险:杠杆高、价格低,应对突发意外风险
✅ 年金险:为养老或子女教育做长期储蓄规划

您可以先告诉我:
🔹 最担心什么风险?(如生病花大钱、意外伤残、孩子教育费、退休后没钱花等)
🔹 或者有没有已经买过的保险?比如公司团险、之前买的重疾险等?

最后,当我们点击 重置槽位 按钮并确认后,系统将清空槽位所有信息,并且对话系统重置,如下图:

请添加图片描述


4. 方案核心逻辑

本方案的核心是**「一次大模型调用 + 策略编排驱动话术」**。

4.1 Agent 层(一次 LLM 调用)

  • 仅通过一次大模型调用,理解用户本轮输入,通过 Function Calling 将用户自然语言中的信息结构化到预定义的业务槽位中。
  • 关键优化:省略传统流程中「总结信息 + 追问下一个问题」的二次 LLM 调用,完全由话术模板和策略函数替代。

4.2 策略编排层(无 LLM 调用)

  • 缺失槽位递归查找函数:遍历所有主题和槽位,识别当前缺失的信息,决定下一轮需要询问的内容。
  • 话术模板生成:整合本轮收集到的信息与历史信息,通过固定模板生成自然语言回复,完全替代二次 LLM 调用。

5. 信息收集设计

5.1 六大信息主题与对应槽位

主题 函数名 核心槽位
1. 投保人基本信息 get_policyholder_basic 年龄 (age)、职业 (occupation)、年收入区间 (income_range)
2. 家庭结构 get_family_info 是否主要养家 (is_breadwinner)、子女数 (children_count)
3. 需求与偏好 get_insurance_demand 险种意向 (product_types)、保额预期 (coverage_expectation)、缴费预算 (premium_budget)
4. 已有保障 get_existing_insurance 已有保险 (existing_insurance)
5. 健康告知 get_health_info 健康状况简述 (health_summary)
6. 补充说明 get_other_info 特殊需求及其他 (special_needs)

5.2 槽位初始化结构

slot_dict = {
    "get_policyholder_basic": {"age": "", "occupation": "", "income_range": ""},
    "get_family_info": {"is_breadwinner": "", "children_count": ""},
    "get_insurance_demand": {"product_types": "", "coverage_expectation": "", "premium_budget": ""},
    "get_existing_insurance": {"existing_insurance": ""},
    "get_health_info": {"health_summary": ""},
    "get_other_info": {"special_needs": ""}
}

5.3 主题询问话术模板

main_question_schema = {
    "get_policyholder_basic": "请问您的年龄、职业和大概年收入区间?",
    "get_family_info": "您是否是家里主要经济支柱?目前有几个孩子?",
    "get_insurance_demand": "您想了解哪类保险?重疾、寿险、年金还是意外?期望保额和年缴费预算大概多少?",
    "get_existing_insurance": "您目前是否已有其他保险或保障?",
    "get_health_info": "方便简单说一下您的健康状况吗?用于后续方案参考。",
    "get_other_info": "还有什么需要补充说明的吗?例如海外就医等特殊需求。",
}

5.4 槽位中文映射(用于反问)

params_schema = {
    "age": "年龄",
    "occupation": "职业",
    "income_range": "年收入区间",
    "is_breadwinner": "是否主要养家",
    "children_count": "子女数",
    "product_types": "险种意向",
    "coverage_expectation": "保额预期",
    "premium_budget": "缴费预算",
    "existing_insurance": "已有保险",
    "health_summary": "健康状况",
    "special_needs": "其他补充",
}

6. 核心技术实现

6.1 Function Calling 调整方案

  • LLM 职责:仅负责信息抽取,通过 Function Calling 将用户输入结构化到槽位中。
  • 关键优化:省略传统流程中「总结信息 + 追问下一个问题」的二次 LLM 调用,完全由话术模板和策略函数替代,大幅提升响应速度。

6.2 话术模板的实现

  • 总结信息:当一个主题的所有槽位都收集完成后,调用对应的模板函数生成总结。
def get_policyholder_basic(age, occupation, income_range):
    return f"年龄:{age} 职业:{occupation} 年收入区间:{income_range}"
  • 追问缺失槽位:根据缺失槽位查找结果,用模板拼接出下一轮问题。
    • 场景一(当前主题完成):开启下一个主题。

      收到反馈:年龄:40岁,职业:教师,年收入区间:10万以下。
      后续我还想了解一下:您是否是家里主要经济支柱?目前有几个孩子?

    • 场景二(当前主题缺失):继续追问当前主题缺失的槽位。

      您已告知年龄 40 岁、职业是教师,
      那么我还想知道:您的年收入区间大概是多少呢?

6.3 缺失槽位递归查找函数

def get_next_message_by_slots(filled_slots):
    filled_slots = sort_nested_dict(input_dict=filled_slots)
    next_collect_slot_list = []
    for theme, slot_value_dict in filled_slots.items():
        for slot, value_elem in slot_value_dict.items():
            if value_elem == "":
                next_collect_slot_list.append(slot)
        if next_collect_slot_list:
            if len(next_collect_slot_list) == len(slot_value_dict):
                is_all_field_missing = True
            else:
                is_all_field_missing = False
            return next_collect_slot_list, is_all_field_missing, theme
    return [], False, ''

返回值含义:

  • next_collect_slot_list:下一轮要收集的槽位列表
  • is_all_field_missing:当前主题是否所有槽位都缺失
  • theme:当前要处理的主题

6.4 流式输出

  • 模板话术通过 stream_string() 按字符块流式输出
  • LLM 自由回复走聊天补全的流式接口

6.5 历史槽位增补与修正

  • 历史有值、本轮为空或「先空着」等:保留历史值
  • 新旧值不同:更新槽位,实现增补和修正

7. 用户信息持久化实现方案

7.1 核心需求

不同用户多轮对话的槽位信息搜集进度需要被记录,防止信息混淆或丢失。

7.2 存储策略

  • 持久化层:SQLite(aiosqlite),将会话槽位落盘到本地数据库文件(位于项目目录下,无需单独部署数据库服务)。

7.3 槽位填充表结构

CREATE TABLE IF NOT EXISTS slot_info_table (
    group_id TEXT PRIMARY KEY,
    slots_info_dict TEXT,           -- JSON 序列化的槽位字典
    created_time TEXT NOT NULL,
    type TEXT                       -- 业务类型,如"保险咨询"
);

配置见 config_parser.pydb_path(如 insurance_consult.dbdata/insurance_consult.db)、slot_table_name(默认 slot_info_table)。首次连接时若表不存在会自动创建。

7.4 工作流程

  1. 用户开始咨询,系统生成唯一 group_id,插入空 slot_dict
  2. 每轮对话后,调用 update_SlotInfo 更新 slots_info_dict
  3. 用户再次进入时,通过 group_id 调用 Read_SlotInfo 恢复会话。

8. 主流程串联

8.1 策略编排核心要点(必读)

理解主流程前,先把下面几件事说清楚。

系统会按顺序逐话题问吗?会。 系统通过 get_next_message_by_slots 遍历当前的槽位填充情况(filled_slots),从投保人基本信息、家庭结构、需求与偏好、已有保障、健康告知、补充说明,一路往下查。查到哪个主题有没填的槽位,就先问那个主题,然后停掉,不会再往后找。所以问话是有顺序的。

顺序是否固定?固定。 六个主题的先后顺序由 slot_dictsort_nested_dict 决定,不会变。永远是 1 → 2 → 3 → 4 → 5 → 6 这样走。

用户必须按顺序回答吗?不用。 用户随便说,比如一次说「我 35 岁,年收入 50 万,想买重疾和寿险」,LLM 会通过 Function Calling 把多个主题的槽位都抽出来。下一轮系统再从头遍历,找到第一个还缺槽位的主题,继续追问。所以用户可以跳着答,系统会自己跟上。

槽位是否填满由谁判断?get_next_message_by_slots 判断,纯逻辑判断,看 value_elem == "" 就知道哪个槽位是空的,不靠 LLM 再确认一遍。

回复是否固定? 在走 Function Calling 分支的情况下,是的。总结用 FunctionCallSummaryConfig,追问用 main_question_schema(该主题全空时)或 params_schema(该主题部分已填时)拼话术,都是模板生成,不会二次调 LLM 来写回复。只有用户纯闲聊、没触发工具调用时,才会走 LLM 自由生成。

顺便提一下 main_question_schema:它不是被遍历的对象,而是一个「主题 → 标准问法」的查找表。当某主题一个槽位都没填时,就查这个表,拿出对应的整句来问;如果已经填了一部分,就只把还缺的那几个槽位名用 params_schema 拼起来问。

8.2 文字版流程

用户输入 → 读取历史槽位 → 首轮固定引导语/后续进入LLM
    → LLM流式输出
        ├── 普通回复(finish_reason=stop):直接流式返回
        └── 工具调用(finish_reason=tool_calls)
            → 解析并执行工具 → 更新all_filled_slots → 写库
            → 调用get_next_message_by_slots
                ├── 有缺失 → 模板化反问
                ├── 无缺失且有下一主题 → 总结+开启下一主题
                ├── 全部完成 → 发送final_message_template
                └── 无缺失且槽位未变 → 调用LLM自由对话

8.3 简化为核心路径

用户输入

首轮?

固定引导语

LLM调用

工具调用?

直接返回

更新槽位+写库

当前主题有缺失?

模板化反问

get_next_message_by_slots

还有下一主题?

总结+开启下一主题

槽位有更新?

结束语

输出


9. 方案优势总结

  1. 极致高效:一轮对话仅一次 LLM 调用,回复速度快,成本低。
  2. 流程可控:话术和流程由模板和策略函数驱动,避免 LLM 自由发挥。
  3. 信息完整:缺失槽位递归查找,确保必要信息都被收集。
  4. 开发简单:核心是定义槽位、模板和查找函数,代码量小,维护成本低。
  5. 可靠稳定:槽位持久化,保证多轮对话连续性和一致性。
  6. 可扩展:可增加提示词抽取、正则词槽抽取等补漏机制,提高信息抽取稳定性。

10. 典型对话示例

10.1 示例一:首轮对话(固定引导语)

用户你好

机器人:欢迎咨询保险服务,我是您的智能顾问。请问您的年龄、职业和大概年收入区间?


10.2 示例二:用户一次回答多主题(跨主题填槽)

用户我35岁,年收入50万,想买重疾和寿险

机器人:收到反馈:年龄、职业、年收入区间、险种意向等已记录;并继续追问当前主题下缺失的槽位(如职业)或下一主题(家庭结构、保额预算等)。


10.3 示例三:信息全部搜集完成后的结束语

用户没有了,就这些

机器人:我们已清楚您的需求:年龄:xx 职业:xx … 正在为您匹配方案,请稍候……


10.4 示例四:未触发 Function Calling(自由对话)

用户重疾险和寿险有什么区别?

机器人:由 LLM 直接生成回答,不走槽位与模板。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐