185 KiB
保险智能客服系统 — 需求文档
版本:V1.0
日期:2026-05-31
技术基础:BaoDan(AI 引擎)+ 自研企微后端
开发人数:1 人
功能项总计:137 项(用户前端 35 + 管理前端 56 + 企微机器人 5 + 后端接口 41)
实现方式说明(必读)
每个功能点后标注了实现方式,含义如下:
| 标注 | 含义 | 说明 |
|---|---|---|
| BaoDan 原生 | BaoDan 自带,无需写代码 | 在 BaoDan 后台配置即可使用 |
| BaoDan + 增强 | BaoDan 有基础功能,需少量代码增强 | BaoDan 提供 80%,你补 20% |
| BaoDan Workflow | 在 BaoDan 可视化编辑器中拖拽编排 | 不写代码,但需要设计 Workflow 逻辑 |
| 自研 | 需要完全自己写代码 | 前端页面或后端接口 |
| 自研(调 BaoDan) | 自研后端接口,内部调 BaoDan API | Flask Blueprint -> 直接调用 BaoDan Python 模块 |
| 自研 + 企微 API | 自研后端,对接企微开放平台 API | 需要企微应用权限 |
一、项目概述
1.1 项目目标
为保险代理团队构建一个基于知识库的智能客服系统,核心能力:
-
智能问答:代理人/客户通过网页或企微提问,系统从知识库中检索准确答案
-
产品推荐:根据客户需求自动生成保险产品推荐方案,支持导出 PDF/Word/PPT
-
知识库管理:支持批量导入 9GB 的 MD 格式保险文档,按险种/保司分类管理
-
企微集成:通过企微机器人实现单聊/群聊问答,通过企微 OAuth 实现免密登录
-
管理后台:知识库管理、日志审计、用户权限、系统配置、数据统计
1.2 用户角色
| 角色 | 说明 | 访问方式 | 核心操作 |
|---|---|---|---|
| 超级管理员 | 系统管理者 | 网页后台 | 全部功能,含系统配置和用户管理 |
| 管理员 | 运营/合规人员 | 网页后台 | 知识库管理、日志查看、数据统计 |
| 销售主管 | 团队负责人 | 网页/企微 H5 | 查看团队问答数据、产品推荐 |
| 销售人员 | 保险代理人 | 网页/企微 H5 | 智能问答、产品推荐 |
| 客户 | 投保人/被保人 | 网页/企微 H5 | 智能问答(受限)、产品推荐 |
| 客户 | 投保人/被保人 | 网页/企微 H5 | 智能问答(受限)、产品推荐 |
1.3 技术架构与实现方式
| 功能模块 | 实现方式 | 说明 |
|---|---|---|
| 智能问答(M1) | BaoDan 原生 | 直接用 BaoDan 的对话功能 |
| 产品推荐(M2) | 自研 | BaoDan Workflow + 自研前端表单,代码放 api/insurance/recommend/ |
| 知识库管理(M3) | BaoDan 原生 + 自研增强 | BaoDan 后台为主,标签/编号等功能需自研 |
| 留痕与日志(M4) | BaoDan 原生 + 自研增强 | BaoDan 有基础日志,导出/操作日志需自研 |
| 用户与权限(M5) | 自研 | 企微 OAuth + 角色权限体系,代码放 api/insurance/permissions/ |
| 系统配置(M6) | BaoDan 原生 + 自研增强 | BaoDan 管理模型/Prompt,模板和告警需自研 |
| 数据统计(M7) | 自研 | 成本监控、健康度统计,代码放 api/insurance/stats/ |
| 企微机器人 | 自研 | Flask Blueprint 对接企微 API + BaoDan,代码放 api/insurance/wecom/ |
| 后端接口(A1-A8) | 自研 | Flask Blueprint,复用 BaoDan 基础设施 |
| 企微 OAuth 登录 | 自研 + 企微 API | OAuth 免密登录,代码放 api/insurance/wecom/ |
二、用户前端功能(M1 + M2)
M1:智能问答
1.1 对话交互
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 1.1.1 | 文字输入框 | 支持回车发送;Shift+Enter 换行;字数上限提示 | 高 | BaoDan 原生 |
| 1.1.2 | 流式输出(Streaming) | 回答逐字打印,降低等待焦虑感;显示「生成中...」状态 | 高 | BaoDan 原生 |
| 1.1.3 | 多轮对话上下文保持 | 追问时携带历史上下文,支持「刚才你说的 XX 是什么意思」等追问 | 高 | BaoDan 原生 |
| 1.1.4 | 会话管理 | 新建会话、切换历史会话列表、删除会话(软删除) | 中 | BaoDan 原生 |
| 1.1.5 | 会话标题自动命名 | 首条问题提取关键词作为会话标题,可手动重命名 | 低 | BaoDan 原生 |
1.2 回复展示
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 1.2.1 | Markdown 渲染 | 加粗、表格、有序/无序列表、代码块等格式正确渲染 | 高 | BaoDan 原生 |
| 1.2.2 | 图片内联展示 | 回复中引用产品条款截图、对比图时可直接预览;支持点击放大 | 中 | BaoDan + 增强 |
| 1.2.3 | 来源引用标注 | 回复末尾附来源文档名或 API 来源标签,可点击查看原始片段 | 高 | BaoDan 原生 |
| 1.2.4 | 相关推荐问题 | 回答下方展示 2-3 条系统推荐的延伸问题,点击直接提问 | 低 | BaoDan + 增强 |
| 1.2.5 | 一键复制回答 | 复制按钮,将完整回答文本复制到剪贴板 | 中 | BaoDan + 增强 |
1.3 检索范围控制
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 1.3.1 | 险种筛选器 | 提问前可选定特定险种(重疾/寿险/医疗/储蓄等)缩小检索范围 | 中 | BaoDan + 增强 |
| 1.3.2 | 保司筛选器 | 可指定一家或多家保司范围,避免跨保司混淆 | 中 | BaoDan + 增强 |
| 1.3.3 | 全库检索(默认) | 未选筛选器时跨险种、跨保司综合检索 | 高 | BaoDan 原生 |
1.4 反馈与纠错
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 1.4.1 | 点赞/点踩按钮 | 回答下方快捷反馈,「有用」/「无用」一键投票 | 高 | BaoDan 原生 |
| 1.4.2 | 纠错反馈入口 | 点击「回答有误」后弹窗,用户可输入正确信息提交 | 中 | 自研 |
M2:产品推荐方案
2.1 基础信息表单
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 2.1.1 | 基础信息表单 | 姓名、年龄、性别、健康状况(体况选项)、职业类别 | 高 | 自研 |
| 2.1.2 | 险种选择 | 多选险种;选择后动态渲染对应必填字段(不同险种字段不同) | 高 | 自研 |
| 2.1.3 | 保障需求设置 | 保额目标(滑块/输入框)、月供预算上限 | 高 | 自研 |
| 2.1.4 | 保障期限选择 | 定期 N 年 / 保至某岁 / 终身,与险种联动可选项 | 高 | 自研 |
| 2.1.5 | 已有保单录入 | 录入客户现有保障,AI 可避免重复建议;非必填 | 中 | 自研 |
| 2.1.6 | 表单草稿自动保存 | 每次输入变更后自动保存草稿,刷新或意外关闭后可恢复 | 中 | 自研 |
表单字段详细定义:
|字段|类型|必填|选项/规则|说明|
|------|------|:---:|-----------|------|--------
|姓名|文本|否|最大 20 字符|客户姓名| |年龄|数字|是|0-150 整数|影响保费计算| |性别|单选|是|男 / 女|部分险种性别差异定价| |健康状况|单选|是|健康 / 有既往病史 / 慢性病 / 重大疾病史|影响核保结果| |职业|文本|是|最大 50 字符|高危职业部分险种不可投| |关注险种|多选|是|寿险 / 重疾险 / 医疗险 / 意外险 / 年金险 / 储蓄险|决定推荐范围| |保额目标|数字|是|1-1000 万元|每个险种独立设置| |月预算上限|数字|是|单位:元|所有险种总预算| |保障期限|下拉|是|定期 10/20/30 年 / 保至 60/70/80 岁 / 终身|随险种联动|
2.2 AI 自动匹配产品组合
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 2.2.1 | AI 自动匹配产品组合 | 基于客户信息 + 知识库,LLM 输出推荐产品列表及配置方案 | 高 | BaoDan Workflow |
| 2.2.2 | 多方案输出 | 生成 2-3 套保障方案(基础/均衡/全面),供销售选择 | 中 | BaoDan Workflow |
| 2.2.3 | 推荐理由说明 | 每款产品附加 AI 生成的推荐原因(1-2 句话) | 中 | BaoDan Workflow |
BaoDan Workflow 设计:
输入参数(JSON:年龄、性别、职业、收入、预算、关注险种等)
[节点1] 参数校验 - 检查必填项
[节点2] 检索策略 - 根据险种确定搜索范围
[节点3] 知识库检索 - 从对应知识库搜索匹配的产品条款
[节点4] LLM 方案生成 - 基于检索结果,生成基础/均衡/全面三套方案
[节点5] 格式化输出 - Markdown 格式的方案报告
2.3 方案预览编辑
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 2.3.1 | 方案预览页 | 实时渲染推荐方案内容,支持编辑、导出、分享操作 | 高 | 自研 |
| 2.3.2 | 关键字段手动修改 | 保额、保费、受益人、保障期限等可直接在预览页编辑 | 高 | 自研 |
| 2.3.3 | 产品替换 | 从候选产品列表中替换某款产品,实时刷新预览 | 中 | 自研 |
2.4 方案导出分享
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 2.4.1 | 导出 PDF/PPT 格式 | 按客户方提供模板渲染,输出标准 PDF/PPT 文件 | 高 | 自研 |
| 2.4.2 | 导出 Word (.docx) | 可编辑的 Word 格式,方便进一步调整 | 中 | 自研 |
| 2.4.3 | 分享链接 | 生成有时效的在线预览链接,无需登录即可查看 | 低 | 自研 |
| 2.4.4 | 重新生成 | 保留原始输入参数,一键重新调用 AI 生成新版本 | 中 | 自研 |
2.5 历史方案管理
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 2.5.1 | 历史方案列表页 | 展示所有已生成的推荐方案,支持分页浏览 | 高 | 自研 |
| 2.5.2 | 多维筛选与搜索 | 按客户姓名、险种、时间段、创建人筛选 | 高 | 自研 |
| 2.5.3 | 方案详情查看 | 查看单个方案的完整内容,包括各方案对比 | 高 | 自研 |
| 2.5.4 | 历史版本重新下载 | 任意历史版本均可再次导出 PDF/Word | 高 | 自研 |
三、管理前端功能(M3 - M7)
M3:知识库管理
3.1 文档上传管理
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 3.1.1 | 批量上传文档 | 支持 MD、Word、TXT 格式,可多文件同时上传,显示上传进度条 | 高 | BaoDan 原生 |
| 3.1.2 | 文档列表 | 展示文件名、险种、保司、上传时间、处理状态;支持排序和搜索 | 高 | BaoDan 原生 |
| 3.1.3 | 文档编号自动生成 | 上传时按「险种代码-保司代码-序号」规则自动分配编号,可手动修改 | 高 | BaoDan + 增强 |
| 3.1.4 | 分类标签管理 | 险种标签、保司标签、自定义标签;支持新建/编辑/删除标签 | 高 | BaoDan + 增强 |
| 3.1.5 | 文档删除与归档 | 软删除:归档后不再参与检索,但保留文件与记录;可恢复 | 中 | BaoDan 原生 |
| 3.1.6 | 文档版本管理 | 上传同名新版本时保留旧版本;可切换使用哪个版本参与检索 | 中 | BaoDan + 增强 |
知识库分类结构:
| 知识库名称 | 分类维度 | 预估规模 | ||
|---|---|---|---|---|
| 寿险-产品条款 | 险种 | 数百到数千 MD 文件 | ||
| 寿险-核保规则 | 险种 | 数十到数百 | ||
| 重疾险-产品条款 | 险种 | 数百 | ||
| 重疾险-核保规则 | 险种 | 数十 | ||
| 医疗险-产品条款 | 险种 | 数百 | ||
| 意外险-产品条款 | 险种 | 数百 | ||
| 车险-产品条款 | 险种 | 数百 | ||
| 年金险-产品条款 | 险种 | 数百 | ||
| 通用-理赔流程 | 通用 | 数十 | ||
| 通用-监管法规 | 通用 | 数十 |
文档分段规则(Chunk 策略):
| 设置项 | 推荐值 | 说明 |
|---|---|---|
| 分段标识符 | 按 Markdown 标题层级 | 用 \n## 或 \n### 作为分隔符 |
| 最大分段长度 | 800 tokens | 保险条款句子长,太小会切断语义 |
| 分段重叠 | 150 tokens | 避免边界处丢信息 |
| 索引模式 | 高质量 | 使用 DeepSeek Embedding |
3.2 处理状态监控
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 3.2.1 | 处理流水线状态 | 每份文档展示状态:待处理/格式转换中/向量化中/完成/失败 | 高 | BaoDan 原生 |
| 3.2.2 | 失败原因与重试 | 失败文档显示错误原因(OCR 失败/格式错误等),支持手动重试 | 高 | BaoDan 原生 |
| 3.2.3 | 向量化进度 | 大文档分批处理时展示百分比进度条 | 低 | BaoDan 原生 |
3.3 保司 API 数据源
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 3.3.1 | 数据源配置 | 新增/编辑保司 API:接口地址、密钥(加密存储)、同步频率(定时 CRON) | 高 | 自研 |
| 3.3.2 | 手动立即同步 | 一键触发指定数据源立即同步,显示实时进度 | 高 | 自研 |
| 3.3.3 | 同步状态监控 | 上次同步时间、同步条目数、耗时、成功/失败状态 | 中 | 自研 |
| 3.3.4 | 数据变更日志 | 每次同步记录新增/更新/删除的产品条目明细 | 低 | 自研 |
| 3.3.5 | 同步异常告警 | 失败时通过配置渠道(邮件/企微)通知管理员 | 中 | 自研 |
3.4 检索效果测试
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 3.4.1 | 知识库测试入口 | 输入测试问题,预览检索命中的文档片段与相关度分数 | 中 | BaoDan 原生 |
| 3.4.2 | 未命中问题列表 | 近 7/30 天用户提问中知识库无法回答的问题汇总,辅助补充文档 | 高 | 自研 |
| 3.4.3 | FAQ 手动条目 | 高频问题可手动添加固定问答对,优先级高于向量检索结果 | 中 | 自研 |
M4:留痕与日志
4.1 问答记录
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 4.1.1 | 全量对话记录查看 | 完整存储:问题/回答/引用来源/用户/时间戳;支持单条详情查看 | 高 | BaoDan 原生 |
| 4.1.2 | 多维筛选 | 按用户、时间范围、险种、关键词、评分筛选 | 高 | BaoDan + 增强 |
| 4.1.3 | 对话记录导出 | BaoDan 对话 API 支持分页查询,后端封装导出为 CSV | 中 | 自研(调 BaoDan) |
| 4.1.4 | 负反馈管理 | BaoDan 内置标注系统可查看反馈;处理流程需自研 | 中 | BaoDan + 增强 |
4.2 导出日志
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 4.2.2 | 导出下载操作日志 | 记录每次导出操作(操作人 + 时间 + 格式) | 高 | 自研 |
4.3 系统操作日志
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 4.3.1 | 用户登录日志 | 登录/登出记录:用户/IP/设备/时间 | 中 | 自研 |
| 4.3.2 | 知识库操作日志 | 文档上传/删除/归档/标签变更等操作记录 | 中 | 自研 |
| 4.3.3 | 配置变更日志 | 系统配置(模型/Prompt/模板)的变更记录,含变更前后值 | 低 | 自研 |
M5:用户与权限
5.1 账号管理
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 5.1.1 | 新增/编辑/停用账号 | 基础信息维护;停用后立即踢出登录态 | 高 | BaoDan 原生 |
| 5.1.2 | 绑定企微账号 | 通过企微 UserId 关联,实现企微 OAuth 免密登录 | 高 | 自研 |
| 5.1.3 | 分组管理 | 按城市/团队/部门建组,用于数据权限隔离 | 中 | 自研 |
| 5.1.4 | 批量导入用户 | 提供 Excel 模板,批量创建账号 | 低 | 自研 |
5.2 角色与权限
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 5.2.1 | 内置角色 | 超级管理员/管理员/销售主管/销售人员/客户 五档内置角色 | 高 | BaoDan + 增强 |
| 5.2.2 | 功能权限配置 | 各角色可访问的功能模块开关,可视化勾选配置 | 高 | 自研 |
| 5.2.3 | 数据权限分层 | 销售见自己;主管见本组;管理员全量可见 | 高 | 自研 |
| 5.2.4 | 自定义角色 | 可新建角色并自由组合权限项 | 低 | 自研 |
M6:系统配置
6.1 LLM 模型配置
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 6.1.1 | 多模型配置 | 支持 GPT-4o / DeepSeek 等,每个模型单独配置 API Key | 高 | BaoDan 原生 |
| 6.1.2 | API Key 加密存储 | Key 入库前 AES 加密,页面显示脱敏;不可明文查看 | 高 | BaoDan + 增强 |
| 6.1.3 | 模型删除/切换默认 | 删除未使用的模型配置;切换默认模型 | 低 | BaoDan 原生 |
| 6.1.4 | 模型参数调整 | Temperature / Max Tokens / Top-P 等参数调节 | 低 | BaoDan 原生 |
| 6.1.5 | 连通性测试 | 一键 Ping 检测模型 API 是否可达,显示延迟 | 中 | BaoDan 原生 |
6.2 Prompt 提示词管理
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 6.2.1 | 问答 Prompt 编辑 | 富文本编辑器,支持变量占位符(如 {{险种}}) |
高 | BaoDan 原生 |
| 6.2.2 | Prompt 变量配置 | 配置 Prompt 中使用的变量(如险种、保司),绑定数据源 | 中 | BaoDan 原生 |
| 6.2.3 | 版本管理 | 每次保存自动生成版本快照,支持查看历史版本与一键回滚 | 中 | BaoDan 原生 |
| 6.2.4 | 即时测试 | 编辑页内置测试入口,输入测试问题直接预览 Prompt 效果 | 中 | BaoDan 原生 |
系统 Prompt 设计:
你是一名专业的保险顾问助手,服务于保险代理团队。
## 回答规则
1. 只基于知识库内容回答。如果没有相关信息,明确告知"当前知识库中未找到相关内容",不要编造答案
2. 标注信息来源:回答时引用具体的文档名称或条款编号,方便用户查证
3. 回答格式:
- 先用 1-2 句话给出核心结论
- 再用条目列出详细说明
- 最后附上注意事项或免责声明
4. 专业术语处理:首次出现的专业术语用括号做简要解释
5. 涉及金额/比例:必须精确引用知识库中的数字,不可四舍五入或估算
6. 涉及免责/拒赔条款:必须完整列出,不可省略
## 特殊场景
- 如果用户问"推荐什么产品",引导用户提供年龄、职业、预算等信息
- 如果用户的问题模糊,先追问澄清再回答
- 如果知识库中有多个产品/条款适用,列出所有适用项并说明区别
6.3 导出模板管理
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 6.3.1 | 模板文件上传 | 上传 Word/PDF 底版模板文件 | 高 | 自研 |
| 6.3.2 | 险种与模板映射 | 不同险种指定不同模板,支持一险种对应多模板(A/B 选择) | 高 | 自研 |
| 6.3.3 | 占位符字段定义 | 配置模板中哪些字段由 AI 填充,字段名与数据字段映射 | 高 | 自研 |
6.4 通知告警配置
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 6.4.1 | 通知渠道设置 | 邮件 SMTP 配置 / 企微机器人 Webhook URL | 中 | 自研 |
| 6.4.2 | 告警规则配置 | API 同步失败 N 次告警 / Token 月消耗超 X 元告警 | 中 | 自研 |
M7:数据统计
7.1 使用概览
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 7.1.1 | 核心指标卡片 | 今日问答数、活跃用户数、知识库命中率等关键指标 | 高 | 自研 |
| 7.1.2 | 趋势折线图 | 近 7/30 天问答量趋势、用户活跃度趋势 | 高 | 自研 |
| 7.1.3 | 险种/保司分布饼图 | 当期问答按险种、按保司维度分布 | 低 | 自研 |
7.2 知识库健康度
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 7.2.1 | 热门问题 TOP20 | 按提问频次排序,辅助补充/优化知识库 | 中 | 自研 |
| 7.2.2 | 未命中问题汇总 | 近 7/30 天知识库无法回答的问题列表,标记已处理状态 | 高 | 自研 |
| 7.2.3 | 文档覆盖率概览 | 各险种/保司文档数量、向量化状态、最后更新时间 | 低 | 自研 |
7.3 成本监控
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| 7.3.1 | Token 用量统计 | 按模型、按天统计 Token 消耗量和费用 | 中 | 自研 |
| 7.3.2 | 费用估算 | 基于 Token 单价估算当月累计费用,与预算阈值对比 | 中 | 自研 |
| 7.3.3 | 费用超限预警 | 当月费用超过设定阈值时页面内显示警告横幅 | 中 | 自研 |
企微机器人(新增模块)
| 编号 | 功能点 | 功能说明 | 优先级 | 实现方式 |
|---|---|---|---|---|
| WB-01 | 单聊对话 | 用户在企微私聊机器人,发送文字消息后收到 AI 回复 | 高 | 自研 + 企微 API |
| WB-02 | 群聊 @触发 | 在企微群内 @机器人 提问,AI 回复到群内 | 高 | 自研 + 企微 API |
| WB-03 | 非文本消息处理 | 用户发图片/表情/文件时,回复「暂不支持该消息类型」 | 中 | 自研 + 企微 API |
| WB-04 | 消息异步处理 | 多人同时提问不阻塞,企微 5 秒内返回响应 | 高 | 自研 |
| WB-05 | 回复超长消息处理 | AI 回复超过企微限制时自动分段发送 | 低 | 自研 |
单聊交互流程:
用户私聊机器人发送"重疾险等待期多少天?"
-> 企微服务器推送消息到后端
-> 后端调 BaoDan API
-> BaoDan 检索知识库并生成回答
-> 后端通过企微 API 回复用户
群聊交互流程:
群内用户 "@机器人 重疾险等待期多少天?"
-> 企微服务器推送消息到后端
-> 后端去掉"@机器人"前缀,调 BaoDan API
-> 企微 API 回复到群内(所有人可见)
企微 OAuth 登录
交互流程:
1. 用户访问系统 -> 点击"企微登录"
2. 跳转到企微授权页面 -> 用户点击"同意授权"
3. 企微回调后端,携带 code 参数
4. 后端用 code 换取企微用户信息(userid、name、department)
5. 后端在系统中查找/创建对应用户
6. 生成 JWT Token -> 重定向到系统首页
四、后端接口详细设计(A1-A8)
A1:认证鉴权
A1.1 身份认证接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A1.1.1 | POST | /auth/wework-login | 接收企微用户信息,签发 JWT Token | 高 | 自研(调 BaoDan) | |
| A1.1.2 | POST | /auth/password-login | 账号 + 密码登录,返回 JWT;密码 bcrypt 哈希校验 | 中 | 自研(调 BaoDan) | |
| A1.1.3 | POST | /auth/refresh-token | 刷新过期 Token,返回新 JWT | 中 | 自研(调 BaoDan) | 自研(调 BaoDan) |
| A1.1.4 | POST | /auth/logout | 吊销 Token(加入黑名单或清除 Redis Session) | 中 | 自研 | 自研(调 BaoDan) |
A1.1.1 企微登录接口详细设计:
请求:
POST /auth/wework-login
{
"code": "企微授权回调code",
"state": "随机状态值"
}
响应:
{
"code": 0,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 7200,
"user": {
"id": "user-001",
"username": "张三",
"wecom_userid": "zhangsan",
"role": "sales",
"department": "上海团队"
}
}
}
A1.1.2 账密登录接口详细设计:
请求:
POST /auth/password-login
{
"username": "admin",
"password": "hashed_password"
}
响应:
{
"code": 0,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 7200
}
}
A1.1.3 刷新 Token 接口详细设计:
请求:
POST /auth/refresh-token
Headers: { "Authorization": "Bearer <old_token>" }
响应:
{
"code": 0,
"message": "success",
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_in": 7200
}
}
A1.1.4 退出登录接口详细设计:
请求:
POST /auth/logout
Headers: { "Authorization": "Bearer <token>" }
响应:
{
"code": 0,
"message": "success"
}
A2:智能问答接口
A2.1 对话接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A2.1.1 | POST | /chat/message(SSE 流式) | 接收问题 + 会话 ID,调用 RAG 检索 + LLM,SSE 流式返回回答与引用来源 | 高 | 自研(调 BaoDan) | |
| A2.1.2 | GET | /chat/sessions | 获取当前用户的会话列表(分页) | 高 | 自研(调 BaoDan) | |
| A2.1.3 | POST | /chat/sessions | 创建新会话,返回 session_id | 高 | 自研(调 BaoDan) | 自研(调 BaoDan) |
| A2.1.4 | DELETE | /chat/sessions/{id} | 软删除指定会话 | 中 | 自研(调 BaoDan) | 自研(调 BaoDan) |
| A2.1.5 | GET | /chat/sessions/{id}/messages | 获取指定会话完整消息记录(含来源引用) | 高 | 自研(调 BaoDan) | 自研(调 BaoDan) |
| A2.1.6 | POST | /chat/messages/{id}/feedback | 提交回答评分(有用/无用)或纠错内容 | 中 | 自研(调 BaoDan) | 自研(调 BaoDan) |
A2.1.1 发送消息接口详细设计:
请求:
POST /chat/message
Headers: { "Authorization": "Bearer <token>" }
{
"session_id": "sess-001",
"message": "重疾险的等待期是多少天?",
"filters": {
"险种": "重疾险",
"保司": null
}
}
响应(SSE 流式):
data: {"type": "source", "data": {"doc_name": "XX重疾险条款.pdf", "chunk": "等待期为合同生效之日起90天..."}}
data: {"type": "delta", "data": "根据"}
data: {"type": "delta", "data": "XX重疾险"}
data: {"type": "delta", "data": "条款"}
data: {"type": "delta", "data": "规定"}
data: {"type": "delta", "data": ","}
data: {"type": "delta", "data": "等待期为合同生效之日起90天。"}
data: {"type": "done", "data": {"message_id": "msg-001", "conversation_id": "conv-001"}}
A2.1.2 获取会话列表接口详细设计:
请求:
GET /chat/sessions?page=1&page_size=20
Headers: { "Authorization": "Bearer <token>" }
响应:
{
"code": 0,
"data": {
"total": 35,
"items": [
{
"id": "sess-001",
"title": "重疾险等待期问题",
"created_at": "2026-05-31T10:00:00Z",
"updated_at": "2026-05-31T10:05:00Z",
"message_count": 6
}
]
}
}
A2.1.5 获取消息记录接口详细设计:
请求:
GET /chat/sessions/{id}/messages
Headers: { "Authorization": "Bearer <token>" }
响应:
{
"code": 0,
"data": {
"messages": [
{
"id": "msg-001",
"role": "user",
"content": "重疾险的等待期是多少天?",
"created_at": "2026-05-31T10:00:00Z"
},
{
"id": "msg-002",
"role": "assistant",
"content": "根据 XX 重疾险条款规定,等待期为合同生效之日起90天。",
"sources": [
{"doc_name": "XX重疾险条款.pdf", "chunk": "等待期为合同生效之日起90天..."}
],
"created_at": "2026-05-31T10:00:05Z"
}
]
}
}
A2.1.6 反馈接口详细设计:
请求:
POST /chat/messages/{id}/feedback
Headers: { "Authorization": "Bearer <token>" }
{
"rating": "helpful",
"comment": null
}
或纠错:
POST /chat/messages/{id}/feedback
Headers: { "Authorization": "Bearer <token>" }
{
"rating": "not_helpful",
"comment": "等待期应该是180天而不是90天"
}
A2.2 知识库检索接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A2.2.1 | POST | /retrieval/search | 向量检索:输入 query + 筛选条件(险种/保司),返回 Top-K 文档片段与相关度 | 高 | 自研(调 BaoDan) | |
| A2.2.2 | GET | /retrieval/suggest | 基于当前问题生成推荐追问列表(2-3 条) | 低 | 自研 |
A2.2.1 检索接口详细设计:
请求:
POST /retrieval/search
Headers: { "Authorization": "Bearer <token>" }
{
"query": "重疾险等待期",
"filters": {
"险种": "重疾险",
"保司": null
},
"top_k": 5
}
响应:
{
"code": 0,
"data": {
"results": [
{
"doc_name": "XX重疾险条款.md",
"chunk": "等待期为合同生效之日起90天...",
"score": 0.92,
"metadata": {
"险种": "重疾险",
"保司": "XX人寿"
}
}
]
}
}
A3:方案生成接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A3.1.1 | POST | /proposals/generate | 接收客户信息 + 险种,调用 LLM 生成产品推荐方案,返回方案 JSON | 高 | 自研(调 BaoDan) | |
| A3.1.2 | GET | /proposals/generate/{task_id} | 轮询生成任务状态:pending / processing / done / failed | 高 | 自研 | |
| A3.1.3 | POST | /proposals/{id}/export | 导出推荐方案为 PDF/PPT/Word 格式 | 高 | 自研 | |
| A3.1.4 | POST | /proposals/{id}/share | 生成有时效的分享链接(Token 鉴权,N 小时有效) | 低 | 自研 | 自研(调 BaoDan) |
A3.1.1 生成方案接口详细设计:
请求:
POST /proposals/generate
Headers: { "Authorization": "Bearer <token>" }
{
"customer": {
"name": "李四",
"age": 35,
"gender": "male",
"health_status": "健康",
"occupation": "软件工程师",
"annual_income": 300000,
"monthly_budget": 2000
},
"insurance_types": ["重疾险", "医疗险", "意外险"],
"coverage_amount": 500000,
"coverage_period": "终身",
"existing_policies": []
}
响应:
{
"code": 0,
"data": {
"task_id": "task-001",
"status": "processing"
}
}
A3.1.2 轮询任务状态接口详细设计:
请求:
GET /proposals/generate/{task_id}
Headers: { "Authorization": "Bearer <token>" }
响应(生成中):
{
"code": 0,
"data": {
"task_id": "task-001",
"status": "processing"
}
}
响应(完成):
{
"code": 0,
"data": {
"task_id": "task-001",
"status": "done",
"proposal": {
"id": "prop-001",
"plans": [
{
"name": "基础方案",
"total_premium": 4800,
"items": [
{
"product_name": "XX重疾险",
"coverage": 300000,
"premium": 2400,
"reason": "35岁男性投保,性价比高,覆盖120种重疾"
}
],
"summary": "基础保障方案,覆盖重疾、医疗、意外三大类..."
}
]
}
}
}
A3.1.3 方案导出接口详细设计:
请求:
POST /proposals/{id}/export
Headers: { "Authorization": "Bearer <token>" }
{
"format": "pdf"
}
响应:
{
"code": 0,
"data": {
"download_url": "https://your-domain.com/api/proposals/{id}/download?token=xxx",
"expire_at": "2026-06-03T10:00:00Z"
}
}
A3.1.4 分享链接接口详细设计:
请求:
POST /proposals/{id}/share
Headers: { "Authorization": "Bearer <token>" }
{
"expire_hours": 72
}
响应:
{
"code": 0,
"data": {
"share_url": "https://your-domain.com/shared/prop-001?token=xxx",
"expire_at": "2026-06-03T10:00:00Z"
}
}
A4:知识库接口
A4.1 文档管理接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A4.1.1 | POST | /kb/documents/upload | 多文件上传,返回 upload_id;触发异步处理流水线(格式转换 -> 向量化) | 高 | 自研(调 BaoDan) | |
| A4.1.2 | GET | /kb/documents | 文档列表,支持按险种/保司/状态/关键词筛选 | 高 | 自研(调 BaoDan) | |
| A4.1.3 | GET | /kb/documents/{id}/status | 查询单个文档处理流水线状态 | 高 | 自研(调 BaoDan) | 自研(调 BaoDan) |
| A4.1.4 | POST | /kb/documents/{id}/retry | 手动重试处理失败的文档 | 高 | 自研(调 BaoDan) | 自研(调 BaoDan) |
| A4.1.5 | PATCH | /kb/documents/{id} | 更新文档元数据(编号/分类/标签) | 中 | 自研(调 BaoDan) | 自研(调 BaoDan) |
| A4.1.6 | DELETE | /kb/documents/{id} | 归档/软删除文档,下线向量索引 | 中 | 自研(调 BaoDan) | 自研(调 BaoDan) |
A4.1.1 上传文档接口详细设计:
请求:
POST /kb/documents/upload
Content-Type: multipart/form-data
Headers: { "Authorization": "Bearer <token>" }
Form Fields:
files: [file1.md, file2.md, file3.md]
险种: "重疾险"
保司: "XX人寿"
响应:
{
"code": 0,
"data": {
"upload_id": "upload-001",
"documents": [
{"doc_id": "doc-001", "filename": "file1.md", "status": "processing"},
{"doc_id": "doc-002", "filename": "file2.md", "status": "processing"},
{"doc_id": "doc-003", "filename": "file3.md", "status": "processing"}
]
}
}
A4.1.2 文档列表接口详细设计:
请求:
GET /kb/documents?page=1&page_size=20&险种=重疾险&status=completed&keyword=等待期
Headers: { "Authorization": "Bearer <token>" }
响应:
{
"code": 0,
"data": {
"total": 150,
"items": [
{
"doc_id": "doc-001",
"filename": "XX重疾险条款.md",
"编号": "CJ-XX-001",
"险种": "重疾险",
"保司": "XX人寿",
"tags": ["产品条款", "等待期"],
"status": "completed",
"uploaded_at": "2026-05-15T10:00:00Z",
"processed_at": "2026-05-15T10:05:00Z"
}
]
}
}
A4.1.3 文档状态接口详细设计:
请求:
GET /kb/documents/{id}/status
Headers: { "Authorization": "Bearer <token>" }
响应:
{
"code": 0,
"data": {
"doc_id": "doc-001",
"filename": "XX重疾险条款.md",
"pipeline": [
{"stage": "upload", "status": "completed", "time": "2026-05-15T10:00:01Z"},
{"stage": "format_convert", "status": "completed", "time": "2026-05-15T10:00:02Z"},
{"stage": "chunking", "status": "completed", "time": "2026-05-15T10:00:03Z", "chunk_count": 45},
{"stage": "embedding", "status": "completed", "time": "2026-05-15T10:05:00Z"}
]
}
}
A4.2 数据源同步接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A4.2.1 | GET | /kb/datasources | 获取保司 API 数据源列表及同步状态 | 高 | 自研 | |
| A4.2.2 | POST | /kb/datasources | 新建数据源配置 | 高 | 自研 | |
| A4.2.3 | POST | /kb/datasources/{id}/sync | 手动触发指定数据源立即同步 | 高 | 自研 | 自研(调 BaoDan) |
| A4.2.4 | GET | /kb/datasources/{id}/logs | 获取同步历史日志(变更明细) | 低 | 自研 | 自研(调 BaoDan) |
A4.2.1 数据源列表接口详细设计:
请求:
GET /kb/datasources
Headers: { "Authorization": "Bearer <token>" }
响应:
{
"code": 0,
"data": [
{
"id": "ds-001",
"name": "XX人寿产品接口",
"api_url": "https://api.xxlife.com/products",
"sync_frequency": "0 2 * * *",
"last_sync": "2026-05-31T02:00:00Z",
"last_sync_status": "success",
"last_sync_count": 156
}
]
}
A5:用户权限接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A5.1.1 | GET/POST/PUT/DELETE | /admin/users | 用户 CRUD;停用时吊销所有 Token | 高 | 自研(调 BaoDan) | |
| A5.1.2 | POST | /admin/users/batch-import | 解析 Excel 模板,批量创建用户 | 低 | 自研 | |
| A5.1.3 | GET/POST/PUT/DELETE | /admin/roles | 角色 CRUD 及权限项绑定 | 高 | 自研 | 自研(调 BaoDan) |
A5.1.1 用户管理接口详细设计:
列表:
GET /admin/users?page=1&page_size=20&role=sales&department=上海团队
Headers: { "Authorization": "Bearer <admin_token>" }
响应:
{
"code": 0,
"data": {
"total": 50,
"items": [
{
"id": "user-001",
"username": "张三",
"wecom_userid": "zhangsan",
"role": "sales",
"department": "上海团队",
"status": "active",
"created_at": "2026-05-01T10:00:00Z",
"last_active_at": "2026-05-31T09:30:00Z"
}
]
}
}
新增:
POST /admin/users
{
"username": "李四",
"wecom_userid": "lisi",
"role": "sales",
"department": "上海团队"
}
A5.1.3 角色管理接口详细设计:
列表:
GET /admin/roles
响应:
{
"code": 0,
"data": [
{
"id": "role-001",
"name": "超级管理员",
"permissions": ["*"],
"builtin": true
},
{
"id": "role-002",
"name": "管理员",
"permissions": ["kb_manage", "log_view", "user_manage", "config_manage"],
"builtin": true
},
{
"id": "role-003",
"name": "销售主管",
"permissions": ["chat", "proposal", "kb_view", "team_stats"],
"builtin": true
},
{
"id": "role-004",
"name": "销售人员",
"permissions": ["chat", "proposal"],
"builtin": true
},
{
"id": "role-005",
"name": "客户",
"permissions": ["chat"],
"builtin": true
}
]
}
A6:系统配置接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A6.1.1 | GET/POST/PUT | /admin/llm-configs | LLM 配置 CRUD;Key 写入时服务端加密 | 高 | 自研(调 BaoDan) | |
| A6.1.2 | POST | /admin/llm-configs/{id}/ping | 测试模型 API 连通性,返回延迟 ms | 中 | 自研(调 BaoDan) | |
| A6.1.3 | GET/POST | /admin/prompts | Prompt 模板 CRUD,保存时自动生成版本快照 | 高 | 自研(调 BaoDan) | 自研(调 BaoDan) |
| A6.1.4 | POST | /admin/prompts/test | 用测试问题即时验证 Prompt 效果 | 中 | 自研(调 BaoDan) | 自研(调 BaoDan) |
A6.1.1 LLM 配置接口详细设计:
请求:
POST /admin/llm-configs
Headers: { "Authorization": "Bearer <admin_token>" }
{
"provider": "deepseek",
"model": "deepseek-chat",
"api_key": "sk-xxxxxxxxxxxx",
"base_url": "https://api.deepseek.com/v1",
"is_default": true,
"params": {
"temperature": 0.7,
"max_tokens": 4096,
"top_p": 0.9
}
}
响应:
{
"code": 0,
"data": {
"id": "llm-001",
"provider": "deepseek",
"model": "deepseek-chat",
"api_key_masked": "sk-xxxx****xxxx",
"is_default": true,
"status": "active"
}
}
A6.1.2 连通性测试接口详细设计:
请求:
POST /admin/llm-configs/{id}/ping
Headers: { "Authorization": "Bearer <admin_token>" }
响应:
{
"code": 0,
"data": {
"reachable": true,
"latency_ms": 320,
"model": "deepseek-chat"
}
}
A6.1.3 Prompt 管理接口详细设计:
列表:
GET /admin/prompts
新增/更新:
POST /admin/prompts
{
"name": "保险顾问系统提示词",
"type": "system",
"content": "你是一名专业的保险顾问助手...",
"variables": ["险种", "保司"]
}
响应(保存时自动生成版本快照):
{
"code": 0,
"data": {
"id": "prompt-001",
"name": "保险顾问系统提示词",
"version": 3,
"versions": [
{"version": 1, "created_at": "2026-05-01T10:00:00Z"},
{"version": 2, "created_at": "2026-05-15T10:00:00Z"},
{"version": 3, "created_at": "2026-05-31T10:00:00Z"}
]
}
}
A6.1.4 Prompt 测试接口详细设计:
请求:
POST /admin/prompts/test
{
"prompt_id": "prompt-001",
"test_query": "重疾险等待期多少天?"
}
响应:
{
"code": 0,
"data": {
"response": "根据条款规定,重疾险等待期为90天...",
"latency_ms": 1200,
"token_usage": {"prompt_tokens": 520, "completion_tokens": 180}
}
}
A7:留痕日志接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A7.1.1 | GET | /admin/logs/chat | 问答记录查询(分页 + 多维筛选 + 关键词),支持导出 CSV | 高 | 自研(调 BaoDan) | |
| A7.1.2 | GET | /admin/logs/chat/export | 导出对话记录为 CSV 文件流 | 高 | 自研(调 BaoDan) | |
| A7.1.3 | GET | /admin/logs/system | 系统操作日志查询 | 中 | 自研 |
A7.1.1 问答日志接口详细设计:
请求:
GET /admin/logs/chat?page=1&page_size=50&user_id=user-001&start_date=2026-05-01&end_date=2026-05-31&keyword=重疾险&rating=not_helpful&export=csv
Headers: { "Authorization": "Bearer <admin_token>" }
响应(JSON 模式):
{
"code": 0,
"data": {
"total": 1200,
"items": [
{
"id": "msg-001",
"user_id": "user-001",
"username": "张三",
"session_id": "sess-001",
"question": "重疾险等待期多少天?",
"answer": "根据 XX 重疾险条款规定,等待期为合同生效之日起90天。",
"sources": ["XX重疾险条款.md"],
"rating": "helpful",
"created_at": "2026-05-31T10:00:00Z"
}
]
}
}
导出(CSV 模式):
当 export=csv 时,返回 CSV 文件流
Content-Type: text/csv
Content-Disposition: attachment; filename="chat_logs_202605.csv"
A7.1.2 导出对话记录接口详细设计:
请求:同 A7.1.1,export=csv 时返回 CSV 文件流
A7.1.3 系统日志接口详细设计:
请求:
GET /admin/logs/system?page=1&page_size=50&action=login&user_id=user-001
Headers: { "Authorization": "Bearer <admin_token>" }
响应:
{
"code": 0,
"data": {
"total": 200,
"items": [
{
"id": "log-001",
"action": "login",
"user_id": "user-001",
"username": "张三",
"ip": "192.168.1.100",
"device": "Chrome/120 Windows",
"detail": "企微 OAuth 登录",
"created_at": "2026-05-31T09:00:00Z"
}
]
}
}
A8:统计报表接口
| 接口编号 | 方法 | URL | 说明 | 优先级 | 实现方式 | |
|---|---|---|---|---|---|---|
| A8.1.1 | GET | /stats/overview | 使用概览:今日问答数、活跃用户数、知识库命中率 | 高 | 自研 | |
| A8.1.2 | GET | /stats/trend | 指定指标的日趋势数据(折线图数据源) | 中 | 自研 | |
| A8.1.3 | GET | /stats/kb-health | 知识库健康度:热门问题/未命中列表/文档覆盖率 | 中 | 自研 | |
| A8.1.4 | GET | /stats/token-cost | Token 消耗统计(按模型/用途/时间段),含费用估算 | 中 | 自研 | 自研(调 BaoDan) |
A8.1.1 使用概览接口详细设计:
请求:
GET /stats/overview
Headers: { "Authorization": "Bearer <admin_token>" }
响应:
{
"code": 0,
"data": {
"today_chats": 45,
"active_users": 12,
"kb_hit_rate": 0.87,
"total_documents": 1500
}
}
A8.1.2 趋势数据接口详细设计:
请求:
GET /stats/trend?metric=chat_count&start_date=2026-05-01&end_date=2026-05-31&granularity=day
Headers: { "Authorization": "Bearer <admin_token>" }
响应:
{
"code": 0,
"data": {
"metric": "chat_count",
"granularity": "day",
"data": [
{"date": "2026-05-01", "value": 45},
{"date": "2026-05-02", "value": 62},
{"date": "2026-05-03", "value": 38}
]
}
}
A8.1.3 知识库健康度接口详细设计:
请求:
GET /stats/kb-health?days=30
Headers: { "Authorization": "Bearer <admin_token>" }
响应:
{
"code": 0,
"data": {
"hot_questions": [
{"question": "重疾险等待期多少天", "count": 230},
{"question": "车险理赔流程", "count": 180}
],
"missed_questions": [
{"question": "XX新产品费率", "count": 45, "handled": false},
{"question": "跨境保险政策", "count": 30, "handled": true}
],
"coverage": {
"寿险": {"doc_count": 320, "status": "completed", "last_update": "2026-05-20"},
"重疾险": {"doc_count": 280, "status": "completed", "last_update": "2026-05-18"},
"医疗险": {"doc_count": 150, "status": "completed", "last_update": "2026-05-15"}
}
}
}
A8.1.4 Token 消耗接口详细设计:
请求:
GET /stats/token-cost?start_date=2026-05-01&end_date=2026-05-31&group_by=model
Headers: { "Authorization": "Bearer <admin_token>" }
响应:
{
"code": 0,
"data": {
"total_tokens": 2500000,
"total_cost_usd": 12.5,
"by_model": [
{
"model": "deepseek-chat",
"input_tokens": 1500000,
"output_tokens": 500000,
"cost_usd": 10.0
},
{
"model": "deepseek-embedding",
"input_tokens": 500000,
"output_tokens": 0,
"cost_usd": 2.5
}
],
"daily_trend": [
{"date": "2026-05-01", "tokens": 80000, "cost_usd": 0.4},
{"date": "2026-05-02", "tokens": 95000, "cost_usd": 0.475}
]
}
}
五、非功能性需求
5.1 性能要求
| 指标 | 要求 | 说明 |
|---|---|---|
| AI 问答响应时间 | < 15 秒 | 从发送到开始看到回复(Streaming 首字) |
| 产品推荐生成时间 | < 60 秒 | 从提交表单到方案展示 |
| 页面首屏加载 | < 3 秒 | 首次访问 |
| 同时在线用户 | >= 20 人 | 单服务器承载 |
| 知识库容量 | 9GB / 50 万 chunk | 当前规模 |
5.2 可用性要求
| 指标 | 要求 | 说明 |
|---|---|---|
| 系统可用率 | >= 99% | 工作时间 9:00-21:00 |
| 数据备份 | 每日自动 | PostgreSQL + 知识库数据 |
| 故障恢复 | < 30 分钟 | 重启服务后自动恢复 |
| 企微消息不丢失 | 是 | 5 秒内返回"处理中",异步生成回答 |
5.3 安全要求
| 项目 | 要求 | 说明 |
|---|---|---|
| 传输加密 | HTTPS | 全站 HTTPS,企微要求 |
| API Key 存储 | AES 加密 | DeepSeek Key 等不得明文存储 |
| 企微消息验证 | 签名校验 | 回调 URL 必须验证企微签名 |
| XSS 防护 | 输入过滤 + 输出编码 | 所有用户输入经过转义 |
| SQL 注入防护 | 参数化查询 | 使用 ORM 或参数化查询 |
| 密码策略 | bcrypt 哈希 | 密码不可明文存储 |
| Token 有效期 | JWT + 刷新机制 | Access Token 2 小时过期 |
5.4 兼容性要求
| 平台 | 浏览器 | 最低版本 |
|---|---|---|
| PC | Chrome / Edge / Firefox | 90+ |
| 手机 | 微信/企微内置浏览器 | iOS 14+ / Android 10+ |
| 手机 | 手机 Chrome | 90+ |
5.5 部署环境
| 项目 | 要求 | |
|---|---|---|
| 服务器 | 自有服务器,Linux(推荐 Ubuntu 22.04) | |
| CPU | 4 核+ | |
| 内存 | 8 GB+ | |
| 磁盘 | 100 GB+ SSD | |
| 网络 | 公网 IP + 域名 + HTTPS 证书 | |
| 运行时 | Python 3.12 + Node.js 22 + PostgreSQL 15 + Redis 6 |
六、数据库设计
6.1 数据存储说明
| 数据类型 | 存储位置 | 说明 |
|---|---|---|
| BaoDan 核心数据(用户、对话、知识库) | BaoDan PostgreSQL | BaoDan 自己管理,不直接操作 |
| 企微用户映射 | 你的 PostgreSQL | 企微 userid 与 BaoDan 用户的对应关系 |
| 推荐方案记录 | 你的 PostgreSQL | 产品推荐的历史记录 |
| BaoDan 服务配置 | BaoDan PostgreSQL | 模型配置、Prompt、工作流等 |
6.2 自研表结构
表 1:wecom_user_mapping(企微用户映射表)
用途:存储企微用户 ID 与 BaoDan 用户 ID 的对应关系,企微 OAuth 登录时查找或创建用户。
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 自增主键 |
| wecom_userid | VARCHAR(64) | NOT NULL, UNIQUE | 企微用户 ID |
| baodan_user_id | VARCHAR(64) | NOT NULL | BaoDan 内部用户 ID |
| username | VARCHAR(128) | 用户姓名 | |
| department | VARCHAR(128) | 所属部门 | |
| role | VARCHAR(32) | DEFAULT 'sales' | 角色:super_admin/admin/manager/sales |
| status | VARCHAR(16) | DEFAULT 'active' | 状态:active/disabled |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| last_active_at | TIMESTAMP | 最后活跃时间 |
索引:
-
UNIQUE INDEX ON wecom_userid
-
INDEX ON baodan_user_id
表 2:recommendation_records(推荐方案记录表)
用途:记录每次产品推荐的输入参数和生成结果,用于历史查询和方案对比。
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 自增主键 |
| user_id | VARCHAR(64) | NOT NULL, FOREIGN KEY | 创建人(关联 wecom_user_mapping) |
| customer_name | VARCHAR(64) | 客户姓名 | |
| customer_age | SMALLINT | 客户年龄 | |
| customer_gender | VARCHAR(8) | 性别:male/female | |
| health_status | VARCHAR(32) | 健康状况 | |
| occupation | VARCHAR(64) | 职业 | |
| annual_income | INTEGER | 年收入(元) | |
| monthly_budget | INTEGER | 月预算(元) | |
| insurance_types | TEXT | 关注险种(JSON 数组,如 ["重疾险","医疗险"]) | |
| coverage_amount | INTEGER | 保额目标(元) | |
| coverage_period | VARCHAR(32) | 保障期限 | |
| existing_policies | TEXT | 已有保单(JSON,可为空) | |
| generated_plan | TEXT | 生成的方案内容(Markdown 文本) | |
| plan_variants | TEXT | 多套方案(JSON:基础/均衡/全面) | |
| baodan_task_id | VARCHAR(64) | BaoDan Workflow 任务 ID | |
| status | VARCHAR(16) | DEFAULT 'pending' | 状态:pending/processing/done/failed |
| error_message | TEXT | 失败原因(可为空) | |
| created_at | TIMESTAMP | DEFAULT NOW() | 创建时间 |
| completed_at | TIMESTAMP | 生成完成时间 |
索引:
-
INDEX ON user_id
-
INDEX ON status
-
INDEX ON created_at
表 3:system_operation_logs(系统操作日志表)
用途:记录关键系统操作(登录、知识库变更、配置修改等),用于审计。
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
| id | SERIAL | PRIMARY KEY | 自增主键 |
| user_id | VARCHAR(64) | NOT NULL | 操作人 |
| action | VARCHAR(32) | NOT NULL | 操作类型:login/logout/upload/delete/config_change |
| target_type | VARCHAR(32) | 操作对象类型:document/user/prompt/llm_config | |
| target_id | VARCHAR(64) | 操作对象 ID | |
| detail | JSONB | 操作详情(如 {"old_value": "...", "new_value": "..."}) | |
| ip | VARCHAR(45) | 操作 IP | |
| user_agent | VARCHAR(256) | 设备信息 | |
| created_at | TIMESTAMP | DEFAULT NOW() | 操作时间 |
索引:
-
INDEX ON user_id
-
INDEX ON action
-
INDEX ON created_at
七、页面结构与导航
7.1 页面清单
| 页面 | 路径 | 说明 | 权限 |
|---|---|---|---|
| 登录页 | /login | 企微 OAuth + 账密登录 | 所有人 |
| 对话页 | /chat | 智能问答主界面(BaoDan WebApp) | 销售/主管/管理员 |
| 产品推荐页 | /recommend | 填写客户需求 + 查看生成结果 | 销售/主管/管理员 |
| 方案历史页 | /recommend/history | 历史推荐方案列表 | 销售/主管/管理员 |
| 管理后台首页 | /admin | 管理后台入口 | 管理员/超级管理员 |
| 知识库管理 | /admin/knowledge-base | 文档上传/列表/状态监控 | 管理员 |
| 对话日志 | /admin/logs/chat | 问答记录查询/导出 | 管理员 |
| 系统日志 | /admin/logs/system | 操作日志查询 | 超级管理员 |
| 用户管理 | /admin/users | 用户 CRUD | 超级管理员 |
| 角色管理 | /admin/roles | 角色权限配置 | 超级管理员 |
| LLM 配置 | /admin/llm-configs | 模型 API Key 管理 | 超级管理员 |
| Prompt 管理 | /admin/prompts | 提示词编辑/版本管理 | 管理员 |
| 数据统计 | /admin/stats | 趋势图/健康度/成本 | 管理员 |
7.2 导航结构
[登录页]
+-- [用户端]
| |-- 左侧导航栏
| | |-- 智能问答 -> /chat
| | |-- 产品推荐 -> /recommend
| | |-- 历史方案 -> /recommend/history
| | +-- (销售人员只能看到这三项)
||
| +-- 右上角:用户信息 + 退出登录
+-- [管理端](仅管理员可见)
|-- 左侧导航栏
| |-- 知识库管理 -> /admin/knowledge-base
| |-- 对话日志 -> /admin/logs/chat
| |-- 系统日志 -> /admin/logs/system(仅超级管理员)
| |-- 用户管理 -> /admin/users(仅超级管理员)
| |-- 角色管理 -> /admin/roles(仅超级管理员)
| |-- LLM 配置 -> /admin/llm-configs(仅超级管理员)
| |-- Prompt 管理 -> /admin/prompts
| +-- 数据统计 -> /admin/stats
+-- 顶部:管理后台标题 + 切换到用户端
7.3 页面交互细节
对话页(/chat)
+---------------------------------------------------+
|[新建对话] [会话列表侧边栏]|
||
|+-----------------------------------------------+|
||用户: 重疾险等待期多少天?||
|+-----------------------------------------------+|||
||
|+-----------------------------------------------+|
||AI: 根据 XX 重疾险条款规定...||
||[来源: XX重疾险条款.md]||
||[点赞] [点踩] [复制]||
|+-----------------------------------------------+|||
||
|+-----------------------------------------------+|
||[险种筛选: 全部 v] [保司筛选: 全部 v]||
||||
||输入框 [发送]||
|+-----------------------------------------------+|||
+---------------------------------------------------+
产品推荐页(/recommend)
+---------------------------------------------------+
|产品推荐 - 新建方案|
||
|[客户信息]|
|姓名: [____] 年龄: [____] 性别: (o)男 ( )女|
|健康状况: [健康 v] 职业: [__________]|
|年收入: [____]万 月预算: [____]元|
||
|[关注险种] [x]重疾险 [x]医疗险 [ ]意外险 ...|
||
|[保障需求]|
|保额目标: [====o========] 50万|
|保障期限: [终身 v]|
||
|[生成方案]|
||
|+-----------------------------------------------+|
||方案预览区||
||=== 基础方案 (年保费: 4,800元) ===||
||1. XX重疾险 - 保额30万 - 年保费2,400元||
||推荐理由: ...||
||2. XX医疗险 - 年保费1,200元||
||...||
||||
||[浏览器打印] [重新生成]||
|+-----------------------------------------------+|||
+---------------------------------------------------+
八、企微机器人详细规格
8.1 接入配置
| 配置项 | 说明 | 在哪里获取 |
|---|---|---|
| CorpID | 企业 ID | 企微管理后台 -> 我的企业 |
| AgentID | 应用 ID | 企微管理后台 -> 应用管理 -> 创建应用 |
| AgentSecret | 应用密钥 | 应用详情页 |
| Token | 回调消息验证 Token | 应用 -> 接收消息 -> 设置 API 接收(自行生成) |
| EncodingAESKey | 消息加密密钥 | 同上(随机生成 43 位字符串) |
8.2 回调 URL 验证(GET)
企微在配置回调 URL 时会发送 GET 请求验证:
GET /api/wecom/callback?msg_signature=xxx×tamp=xxx&nonce=xxx&echostr=xxx
验证流程:
-
将 Token、timestamp、nonce、echostr 按字典序排列拼接
-
对拼接字符串做 SHA1 哈希
-
比较哈希结果与 msg_signature
-
验证通过则返回解密后的 echostr
8.3 消息接收(POST)
企微用户发消息时推送的加密 XML:
<xml>
<ToUserName><![CDATA[你的企业ID]]></ToUserName>
<Encrypt><![CDATA[加密后的消息]]></Encrypt>
</xml>
解密后的消息体(text 类型):
<xml>
<ToUserName><![CDATA[你的企业ID]]></ToUserName>
<FromUserName><![CDATA[用户UserID]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[重疾险等待期多少天]]></Content>
<MsgId>1234567890123456</MsgId>
<AgentID>1000002</AgentID>
</xml>
群聊消息额外字段:
<xml>
...
<ChatId><![CDATA[群聊ID]]></ChatId>
<Content><![CDATA[@机器人 重疾险等待期多少天]]></Content>
...
</xml>
8.4 消息回复
通过企微 API 发送文本消息:
POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN
请求体:
{
"touser": "用户UserID",
"msgtype": "text",
"agentid": 1000002,
"text": {
"content": "根据 XX 重疾险条款规定,等待期为90天..."
}
}
群聊回复到群:
POST https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=ACCESS_TOKEN
请求体:
{
"chatid": "群聊ID",
"msgtype": "text",
"text": {
"content": "根据 XX 重疾险条款规定,等待期为90天..."
}
}
8.5 消息长度限制
| 场景 | 限制 | 处理方式 |
|---|---|---|
| 单条文本消息 | 最大 2048 字节 | 超过时分段发送,每段间隔 500ms |
| Access Token | 有效期 2 小时 | 代码内缓存,提前 5 分钟刷新 |
| 回调响应时间 | 5 秒内 | 先返回"success",异步处理消息和回复 |
8.6 非文本消息处理
| 消息类型 | 处理方式 | |
|---|---|---|
| 图片 (image) | 回复"暂不支持图片识别,请用文字描述您的问题" | |
| 语音 (voice) | 回复"暂不支持语音识别,请用文字描述您的问题" | |
| 视频 (video) | 回复"暂不支持视频消息" | |
| 文件 (file) | 回复"暂不支持文件消息" | |
| 位置 (location) | 忽略不回复 | |
| 事件 (event) | 忽略不回复(如成员加入/退出事件) |
九、BaoDan 集成规范
架构变更:自研代码直接运行在 BaoDan 进程内(Flask Blueprint),调用 BaoDan 的 RAG 引擎和 LLM 服务时直接 import Python 函数,不走 HTTP API。企微回调 URL 直接指向 BaoDan 服务端口。
9.1 后端调用方式
你的 insurance 模块(Flask Blueprint)
|
|-- 直接 import BaoDan 的 RAG 引擎
| from core.rag.retrieval.dataset_retrieval import DatasetRetrieval
| from core.rag.embedding.model import ModelEmbedding
|
|-- 直接 import BaoDan 的 LLM 服务
| from core.model_runtime.model_manager import ModelManager
|
|-- 直接调用 BaoDan 的 Workflow
| from core.workflow.workflow_engine import WorkflowEngine
|
|-- 复用 BaoDan 的数据库连接
| from extensions.ext_database import db
| from models.dataset import Dataset, Document
|
|-- 复用 BaoDan 的认证
| from extensions.ext_login import login
| from models.account import Account
| Headers: Authorization: Bearer <BAODAN_APP_API_KEY>
|-- 调用 BaoDan Workflow API(产品推荐)
| POST http://baodan-api:5001/v1/workflows/run
| Headers: Authorization: Bearer <BAODAN_WORKFLOW_API_KEY>
|-- 调用 BaoDan Knowledge API(文档管理,可选)
| POST http://baodan-api:5001/v1/datasets/{id}/documents
| Headers: Authorization: Bearer <BAODAN_DATASET_API_KEY>
### 9.2 BaoDan Chat API 调用规范
**请求**:
```json
POST /v1/chat-messages
Headers:
Authorization: Bearer app-xxxxxxxxxxxx
Content-Type: application/json
{
"inputs": {},
"query": "重疾险等待期多少天?",
"response_mode": "blocking",
"user": "wecom_zhangsan",
"conversation_id": "",
"files": []
}
| 参数 | 类型 | 说明 |
|---|---|---|
| inputs | object | 自定义变量,与 Prompt 中的占位符对应 |
| query | string | 用户的问题文本 |
| response_mode | string | blocking(等完整回答)或 streaming(流式返回) |
| user | string | 用户标识,企微场景用 "wecom_{userid}" |
| conversation_id | string | 空字符串 = 新对话;非空 = 继续已有对话 |
| files | array | 附件列表(暂不使用) |
blocking 模式响应:
{
"event": "message",
"message_id": "msg-xxx",
"conversation_id": "conv-xxx",
"mode": "blocking",
"answer": "根据 XX 重疾险条款规定,等待期为合同生效之日起90天。",
"metadata": {
"usage": {
"prompt_tokens": 520,
"completion_tokens": 180,
"total_tokens": 700
},
"retriever_resources": [
{
"dataset_name": "重疾险-产品条款",
"document_name": "XX重疾险条款.md",
"segment_content": "等待期为合同生效之日起90天...",
"score": 0.92
}
]
},
"created_at": 1700000000
}
9.3 BaoDan Workflow API 调用规范
请求:
POST /v1/workflows/run
Headers:
Authorization: Bearer app-yyyyyyyyyyyy
Content-Type: application/json
{
"inputs": {
"age": "35",
"gender": "male",
"occupation": "软件工程师",
"annual_income": "300000",
"monthly_budget": "2000",
"insurance_types": "重疾险,医疗险,意外险",
"coverage_amount": "500000",
"coverage_period": "终身"
},
"response_mode": "blocking",
"user": "wecom_zhangsan"
}
响应:
{
"workflow_run_id": "wf-xxx",
"task_id": "task-xxx",
"data": {
"outputs": {
"recommendation": "## 基础方案\n\n..."
},
"status": "succeeded",
"elapsed_time": 15.2,
"total_tokens": 2000
}
}
9.4 用户标识映射
| 场景 | BaoDan user 格式 | 说明 |
|---|---|---|
| 企微单聊 | wecom_{userid} |
如 wecom_zhangsan |
| 企微群聊 | wecom_{userid} |
同上,按发送人区分 |
| 网页登录 | web_{baodan_user_id} |
BaoDan 内部用户 ID |
9.5 对话上下文管理
| 场景 | conversation_id | 说明 |
|---|---|---|
| 新对话 | 空字符串 "" |
BaoDan 返回新的 conversation_id |
| 继续对话 | 传入上次返回的 conversation_id | BaoDan 自动携带历史上下文 |
| 切换会话 | 传入目标会话的 conversation_id | BaoDan 加载该会话的上下文 |
| 新建会话 | 传入空字符串 | 开始全新的对话 |
9.6 BaoDan WebApp 嵌入方式
对话页面直接嵌入 BaoDan 的 WebApp,通过 iframe 方式:
<!-- 方式一:直接嵌入 BaoDan WebApp -->
<iframe
src="http://你的BAODAN地址/chat/{app_id}?user=用户ID"
style="width:100%; height:100%; border:none;"
allow="microphone">
</iframe>
BaoDan WebApp URL 格式:
-
对话应用:
{baodan_base_url}/chat/{app_id} -
工作流应用:
{baodan_base_url}/chat/{app_id}
iframe 通信:BaoDan WebApp 通过 postMessage 与父页面通信(如需要定制交互,需要监听 message 事件)。
十、异常与边界处理
10.1 智能问答异常处理
| 异常场景 | 系统行为 | 用户看到什么 |
|---|---|---|
| BaoDan API 不可达 | 后端返回 500 | "服务暂时不可用,请稍后重试" |
| BaoDan API 超时(>60秒) | 中断请求 | "回答生成超时,请重试" |
| DeepSeek API Key 过期 | BaoDan 返回错误 | "AI 服务配置异常,请联系管理员" |
| 知识库无匹配结果 | BaoDan 正常返回 | AI 回复"当前知识库中未找到相关内容" |
| 用户输入为空 | 前端拦截 | 发送按钮禁用 |
| 用户输入超长(>10000字) | 前端拦截 | 提示"输入内容过长,请缩短后重试" |
| BaoDan 返回空回答 | 后端兜底 | "抱歉,暂时无法回答您的问题,请换个方式提问" |
| 流式输出中断 | 前端显示已收到部分 | "回答中断,请重新提问" |
10.2 产品推荐异常处理
| 异常场景 | 系统行为 | 用户看到什么 |
|---|---|---|
| 必填项未填 | 前端拦截 | 对应字段下方红色提示"请填写XX" |
| 无效输入(年龄=-1) | 前端校验 | "请输入有效的年龄(1-150)" |
| Workflow 执行失败 | 记录错误,更新状态 | "方案生成失败,请重试" |
| Workflow 执行超时(>120秒) | 任务标记为 failed | "方案生成超时,请重试" |
| 生成结果为空 | BaoDan Workflow 内处理 | "未找到匹配的保险产品,请调整筛选条件" |
| 网络中断 | 任务可能仍在后端执行 | "网络异常,方案正在生成中,请稍后查看" |
10.3 企微机器人异常处理
| 异常场景 | 系统行为 | 用户看到什么 |
|---|---|---|
| 消息解密失败 | 记录日志,返回 success | 不回复(避免企微重试) |
| 用户不在白名单 | 根据配置决定 | "您暂无使用权限"或忽略 |
| BaoDan 处理中超过 5 秒 | 已返回 success | 先不回复,异步处理完后回复 |
| 企微 API 回复失败 | 记录日志 | 无(用户看不到) |
| Access Token 获取失败 | 重试 3 次 | 机器人不回复 |
| 消息内容包含敏感词 | 正常处理 | 不做额外过滤(合规由管理员负责) |
10.4 企微 OAuth 异常处理
| 异常场景 | 系统行为 | 用户看到什么 |
|---|---|---|
| 用户拒绝授权 | 跳转回登录页 | "授权已取消,请重新登录" |
| code 已过期(5分钟有效期) | 企微返回错误 | "授权已过期,请重新登录" |
| 企微用户未在系统中注册 | 根据配置决定 | "您的账号暂未开通,请联系管理员" |
| JWT Token 过期 | 前端检测 401 | 跳转到登录页 |
| 刷新 Token 失败 | 清除本地 Token | 跳转到登录页 |
10.5 网络与基础设施异常
| 异常场景 | 系统行为 | 恢复方式 |
|---|---|---|
| PostgreSQL 连接断开 | 后端报错日志 | 数据库恢复后自动重连 |
| Redis 连接断开 | Celery 暂停 | Redis 恢复后自动恢复 |
| 磁盘空间满 | 知识库上传失败,其他功能正常 | 清理磁盘后手动重试 |
| 服务器重启 | 所有服务停止 | Docker 自动重启 / 手动启动 |
| BaoDan 服务重启 | 所有对话断开 | BaoDan 自动恢复,用户刷新页面即可 |
十一、术语表
11.1 保险领域术语
| 术语 | 英文 | 说明 |
|---|---|---|
| 重疾险 | Critical Illness Insurance | 确诊约定重大疾病后一次性赔付的保险 |
| 寿险 | Life Insurance | 身故后向受益人赔付的保险 |
| 医疗险 | Medical Insurance | 报销医疗费用的保险 |
| 意外险 | Accident Insurance | 因意外事故导致伤害/身故时赔付的保险 |
| 年金险 | Annuity Insurance | 按约定周期领取保险金的保险 |
| 储蓄险 | Savings Insurance | 兼具保障和储蓄功能的保险 |
| 等待期 | Waiting Period | 保单生效后到保障开始的等待天数(通常 90-180 天) |
| 免赔额 | Deductible | 保险公司不赔付的部分(如医疗险的 1 万免赔额) |
| 保额 | Sum Insured | 保险赔付的最高金额 |
| 保费 | Premium | 被保险人需缴纳的保险费用 |
| 核保 | Underwriting | 保险公司评估是否承保及承保条件的过程 |
| 体况 | Health Status | 投保人的身体健康状况 |
| 条款 | Policy Terms | 保险合同的详细约定内容 |
| 理赔 | Claim | 向保险公司申请赔付的过程 |
11.2 技术术语
| 术语 | 说明 | |
|---|---|---|
| BaoDan | 开源 AI 应用开发平台,提供 RAG、对话管理、工作流等功能 | |
| RAG | 检索增强生成(Retrieval-Augmented Generation),先从知识库检索相关文档,再让 LLM 基于检索结果生成回答 | |
| LLM | 大语言模型(Large Language Model),如 DeepSeek、GPT-4o | |
| Embedding | 向量嵌入,将文本转换为数学向量以支持语义搜索 | |
| Chunk | 文档被切分成的小段落,是知识库检索的最小单位 | |
| Workflow | BaoDan 的可视化工作流编辑器,可拖拽编排多步骤 AI 流程 | |
| SSE | Server-Sent Events,流式传输协议,用于逐字显示 AI 回答 | |
| JWT | JSON Web Token,用于用户认证的令牌机制 | |
| PTY | 伪终端(本项目不涉及,仅 Dinotty 相关) | |
| WebApp | BaoDan 提供的内嵌对话界面,可通过 iframe 嵌入你的系统 |
十二、数据流转图
以下用文本/伪代码形式展示系统核心业务的完整数据流转链路,每条链路标注了每一步的 API 调用、数据库读写、外部服务调用、返回数据和错误处理。
12.1 用户提问 → AI 回答(完整链路)
用户(浏览器/企微)
│
▼
[步骤1] 前端发起请求
│ POST /api/chat/message
│ Headers: Authorization: Bearer <JWT>
│ Body: {"session_id":"sess-001", "message":"重疾险等待期多少天?", "filters":{"险种":"重疾险"}}
│
▼
[步骤2] 后端 - JWT 鉴权
│ ├─ 读取 Redis: GET token:blacklist:<token> → 检查是否被吊销
│ ├─ 解析 JWT → 提取 user_id、role
│ ├─ 读取 PostgreSQL: SELECT * FROM wecom_user_mapping WHERE baodan_user_id = ?
│ │
│ ├─ [成功] → 继续步骤3
│ └─ [失败] → 返回 {"code":1002, "message":"未授权,请重新登录"}
│
▼
[步骤3] 后端 - 调用 BaoDan Chat API(blocking 模式)
│ POST http://baodan-api:5001/v1/chat-messages
│ Headers: Authorization: Bearer app-xxxxxxxxxxxx
│ Body: {
│ "inputs": {"险种": "重疾险", "保司": ""},
│ "query": "重疾险等待期多少天?",
│ "response_mode": "blocking",
│ "user": "wecom_zhangsan",
│ "conversation_id": "sess-001",
│ "files": []
│ }
│
│ ├─ [成功] → 继续步骤4
│ ├─ [超时 >60s] → 返回 {"code":2002, "message":"AI 回答生成超时,请重试"}
│ └─ [BaoDan 返回错误] → 返回 {"code":2001, "message":"AI 服务暂时不可用,请稍后重试"}
│
▼
[步骤4] BaoDan 内部处理(无需后端干预)
│ ├─ BaoDan 接收 query
│ ├─ Embedding: 将 query 向量化
│ ├─ 知识库检索: Weaviate 向量相似度搜索 → 返回 Top-K 文档片段
│ ├─ Prompt 组装: System Prompt + 检索结果 + 用户 query
│ ├─ LLM 调用: DeepSeek API → 流式生成回答
│ └─ 返回完整回答 + metadata(来源引用)
│
▼
[步骤5] 后端 - 处理 BaoDan 响应并存储
│ ├─ 提取 answer、conversation_id、retriever_resources
│ ├─ 写入 PostgreSQL(可选): 记录问答日志
│ └─ 组装返回数据
│
▼
[步骤6] 后端 → 前端 响应
│ 返回 SSE 流式数据:
│ data: {"type":"source", "data":{"doc_name":"XX重疾险条款.md", "chunk":"等待期为90天..."}}
│ data: {"type":"delta", "data":"根据"}
│ data: {"type":"delta", "data":"XX重疾险条款规定..."}
│ ...
│ data: {"type":"done", "data":{"message_id":"msg-001", "conversation_id":"conv-001"}}
│
▼
[步骤7] 前端渲染
├─ 逐字显示 AI 回答
├─ 展示来源引用(可展开查看原文)
├─ 显示 [点赞] [点踩] [复制] 按钮
└─ 完成
12.2 产品推荐(完整链路)
用户(浏览器)
│
▼
[步骤1] 前端表单提交
│ POST /api/proposals/generate
│ Headers: Authorization: Bearer <JWT>
│ Body: {
│ "customer": {"name":"李四", "age":35, "gender":"male", "health_status":"健康",
│ "occupation":"软件工程师", "annual_income":300000, "monthly_budget":2000},
│ "insurance_types": ["重疾险","医疗险","意外险"],
│ "coverage_amount": 500000,
│ "coverage_period": "终身",
│ "existing_policies": []
│ }
│
▼
[步骤2] 后端 - JWT 鉴权
│ 同 12.1 步骤2
│
▼
[步骤3] 后端 - 参数校验
│ ├─ 必填字段检查:age/gender/occupation/insurance_types/monthly_budget/coverage_amount
│ ├─ 类型检查:age 为整数 1-150,gender 为 "male"/"female"
│ ├─ 业务规则:insurance_types 至少选1项,monthly_budget > 0
│ │
│ ├─ [校验通过] → 继续步骤4
│ └─ [校验失败] → 返回 {"code":1001, "message":"参数错误:年龄必须为1-150之间的整数"}
│
▼
[步骤4] 后端 - 创建推荐记录
│ ├─ 写入 PostgreSQL: INSERT INTO recommendation_records (user_id, customer_name, ..., status='pending')
│ └─ 返回 task_id
│
▼
[步骤5] 后端 - 异步调用 BaoDan Workflow API
│ POST http://baodan-api:5001/v1/workflows/run
│ Headers: Authorization: Bearer app-yyyyyyyyyyyy
│ Body: {
│ "inputs": {
│ "age": "35", "gender": "male", "occupation": "软件工程师",
│ "annual_income": "300000", "monthly_budget": "2000",
│ "insurance_types": "重疾险,医疗险,意外险",
│ "coverage_amount": "500000", "coverage_period": "终身"
│ },
│ "response_mode": "blocking",
│ "user": "wecom_zhangsan"
│ }
│
│ ├─ [成功] → 继续步骤6
│ ├─ [超时 >120s] → 更新 PostgreSQL: UPDATE recommendation_records SET status='failed' → 返回 {"code":2004}
│ └─ [Workflow 失败] → 更新 PostgreSQL: SET status='failed', error_message=... → 返回 {"code":2003}
│
▼
[步骤6] BaoDan Workflow 内部处理
│ ├─ 节点1: 参数校验(Code Node)
│ ├─ 节点2: 检索策略生成(Code Node)→ 为每个险种生成检索 query
│ ├─ 节点3: 知识库检索(Knowledge Retrieval Node)→ Weaviate 检索 Top-5
│ ├─ 节点4: LLM 方案生成(LLM Node)→ DeepSeek 生成三套方案
│ ├─ 节点5: 格式化输出(Code Node)→ Markdown 清理
│ └─ 返回 outputs.recommendation(Markdown 文本)
│
▼
[步骤7] 后端 - 保存结果
│ ├─ 写入 PostgreSQL: UPDATE recommendation_records SET
│ │ generated_plan=?, status='done', completed_at=NOW()
│ └─ 返回完整方案数据
│
▼
[步骤8] 后端 → 前端 响应
│ 返回 {"code":0, "data":{"task_id":"task-001", "status":"done",
│ "proposal":{"id":"prop-001", "plans":[...]}}}
│
▼
[步骤9] 前端轮询(如步骤5返回 processing)
│ GET /api/proposals/generate/{task_id}
│ ├─ [status=processing] → 继续轮询(间隔 2 秒)
│ ├─ [status=done] → 展示方案
│ └─ [status=failed] → 显示错误提示
│
▼
[步骤10] 前端展示方案
├─ 渲染三套方案(基础/均衡/全面)
├─ 每套方案包含:产品表格 + 总保费 + 推荐理由
├─ 显示 [浏览器打印] [重新生成] 按钮
└─ 完成
12.3 企微机器人问答(完整链路)
企微用户(手机/电脑)
│
▼
[步骤1] 用户发送消息
│ 用户在企微中 @机器人 或私聊发送:"重疾险等待期多少天?"
│
▼
[步骤2] 企微服务器 → 你的后端 推送加密消息
│ POST https://your-domain.com/api/wecom/callback
│ Headers: msg_signature=xxx×tamp=xxx&nonce=xxx
│ Body: <xml><ToUserName>...</ToUserName><Encrypt>加密消息</Encrypt></xml>
│
▼
[步骤3] 后端 - 立即返回 "success"(5秒内必须响应)
│ 返回空字符串 "success"
│ (企微要求5秒内响应,否则会重试推送)
│
▼
[步骤4] 后端 - 异步处理消息
│ ├─ 验证签名: SHA1(sort([Token, timestamp, nonce])) == msg_signature
│ │ ├─ [验证失败] → 记录日志,流程结束(不回复)
│ │ └─ [验证通过] → 继续
│ ├─ 解密消息: AES-CBC 解密 Encrypt 字段 → 得到明文 XML
│ ├─ 解析 XML: 提取 FromUserName(用户ID)、Content(消息内容)、MsgType
│ │
│ ├─ [MsgType != text] → 回复"暂不支持该消息类型,请用文字描述" → 流程结束
│ ├─ [用户不在白名单] → 回复"您暂无使用权限" → 流程结束
│ └─ [正常文本消息] → 继续步骤5
│
▼
[步骤5] 后端 - 查询/创建用户映射
│ ├─ 读取 PostgreSQL: SELECT * FROM wecom_user_mapping WHERE wecom_userid = ?
│ │
│ ├─ [用户不存在 + 系统允许注册] → INSERT 新记录 → 继续步骤6
│ ├─ [用户不存在 + 系统不允许] → 回复"您的账号暂未开通" → 流程结束
│ └─ [用户存在] → 继续步骤6
│
▼
[步骤6] 后端 - 调用 BaoDan Chat API
│ POST http://baodan-api:5001/v1/chat-messages
│ Headers: Authorization: Bearer app-xxxxxxxxxxxx
│ Body: {
│ "inputs": {},
│ "query": "重疾险等待期多少天?",
│ "response_mode": "blocking",
│ "user": "wecom_zhangsan",
│ "conversation_id": "",
│ "files": []
│ }
│
│ ├─ [成功] → 继续步骤7
│ ├─ [超时 >55s] → 回复"处理时间较长,请稍后再试" → 流程结束
│ └─ [BaoDan 错误] → 回复"AI 服务暂时不可用,请稍后重试" → 流程结束
│
▼
[步骤7] 后端 - 回复企微消息
│ ├─ 获取企微 Access Token:
│ │ POST https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=CORPID&corpsecret=SECRET
│ │ 返回 {"access_token":"xxx", "expires_in":7200}
│ │ (Token 缓存到 Redis,提前5分钟刷新)
│ │
│ ├─ 判断消息长度:
│ │ ├─ [<=2048字节] → 单条回复
│ │ └─ [>2048字节] → 分段回复(每段间隔500ms)
│ │
│ ├─ 单聊回复:
│ │ POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=TOKEN
│ │ Body: {"touser":"zhangsan", "msgtype":"text", "agentid":1000002,
│ │ "text":{"content":"根据XX重疾险条款规定,等待期为90天..."}}
│ │
│ ├─ 群聊回复:
│ │ POST https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=TOKEN
│ │ Body: {"chatid":"群聊ID", "msgtype":"text",
│ │ "text":{"content":"根据XX重疾险条款规定,等待期为90天..."}}
│ │
│ ├─ [发送成功] → 记录日志 → 流程结束
│ └─ [发送失败] → 记录错误日志 → 流程结束(用户无感知)
│
▼
[步骤8] 企微用户收到回复
└─ 完成
12.4 企微 OAuth 登录(完整链路)
用户(企微内置浏览器)
│
▼
[步骤1] 用户点击"企微登录"按钮
│ 前端构造 OAuth 授权 URL 并跳转:
│ https://open.weixin.qq.com/connect/oauth2/authorize?
│ corp_id=CORPID&
│ redirect_uri=https://your-domain.com/api/auth/wework-callback&
│ response_type=code&
│ scope=snsapi_base&
│ state=RANDOM_STATE_STRING
│ #wechat_redirect
│
▼
[步骤2] 企微授权页面
│ ├─ [用户点击"同意"] → 企微携带 code + state 回调 redirect_uri
│ └─ [用户点击"取消"] → 回调 URL 带 error=access_denied
│ 前端捕获 → 显示"授权已取消,请重新登录"
│
▼
[步骤3] 企微服务器 → 你的后端 回调
│ GET https://your-domain.com/api/auth/wework-callback?
│ code=XXXXX&
│ state=RANDOM_STATE_STRING
│
▼
[步骤4] 后端 - 验证 state 防 CSRF
│ ├─ 读取 Redis: GET oauth:state:<session_id>
│ ├─ 比对 state 值
│ │
│ ├─ [state 不匹配] → 返回 403 "登录验证失败,请重试"
│ └─ [state 匹配] → 继续步骤5
│
▼
[步骤5] 后端 - 用 code 换取企微用户信息
│ ├─ 获取 Access Token:
│ │ GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?
│ │ corpid=CORPID&corpsecret=SECRET
│ │ 返回 {"access_token":"xxx", "expires_in":7200}
│ │
│ ├─ 获取用户信息:
│ │ GET https://qyapi.weixin.qq.com/cgi-bin/user/get?
│ │ access_token=TOKEN&userid=USERID
│ │ 返回 {"userid":"zhangsan", "name":"张三", "department":[1]}
│ │
│ ├─ [企微 API 失败] → 返回 500 "企微服务异常,请稍后重试"
│ └─ [企微 API 成功] → 继续步骤6
│
▼
[步骤6] 后端 - 查找或创建系统用户
│ ├─ 读取 PostgreSQL: SELECT * FROM wecom_user_mapping WHERE wecom_userid = 'zhangsan'
│ │
│ ├─ [用户存在 + status=active] → 继续步骤7
│ ├─ [用户存在 + status=disabled] → 返回 403 "您的账号已被禁用,请联系管理员"
│ ├─ [用户不存在 + 允许自动注册] → INSERT 新记录 → 继续步骤7
│ └─ [用户不存在 + 不允许自动注册] → 返回 403 "您的账号暂未开通,请联系管理员"
│
▼
[步骤7] 后端 - 签发 JWT Token
│ ├─ 生成 JWT: payload = {user_id, username, role, department, exp=now+2h}
│ ├─ 用 SECRET_KEY 签名
│ ├─ 写入 Redis: SET token:user:<user_id> = <token> EX 7200
│ └─ 更新 PostgreSQL: UPDATE wecom_user_mapping SET last_active_at = NOW()
│
▼
[步骤8] 后端 → 前端 重定向
│ 302 重定向到前端页面,URL 携带 Token:
│ https://your-domain.com/app/login/callback?
│ token=eyJhbGciOiJIUzI1NiIs...&
│ expires_in=7200&
│ user={"id":"user-001","username":"张三","role":"sales","department":"上海团队"}
│
▼
[步骤9] 前端 - 处理回调
│ ├─ 解析 URL 参数,提取 token 和 user 信息
│ ├─ 存储 Token: localStorage.setItem('token', token)
│ ├─ 存储用户信息: localStorage.setItem('user', userJSON)
│ ├─ 设置 Axios 拦截器: 每次请求自动携带 Authorization: Bearer <token>
│ ├─ 设置 Token 自动刷新: 在 token 过期前 5 分钟调用 /auth/refresh-token
│ └─ 跳转到主页: router.push('/chat')
│
▼
[步骤10] 登录完成
└─ 用户进入系统主界面
十三、接口字段约束表
每个接口的所有请求参数和响应字段的完整约束定义。字段验证分为前端验证(即时反馈)和后端验证(安全兜底),两层都必须实现。
13.1 A1 认证鉴权接口
A1.1.1 POST /auth/wework-login
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| code | string | 是 | 1-128字符 | - | - | 非空,去除首尾空格后长度>=1 | 企微授权码不能为空 |
| string | state | 是 | 1-64字符 | - | - | 必须与 Redis 中存储的 state 匹配 | 登录验证失败,请重试 |
A1.1.2 POST /auth/password-login
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | username | 是 | 3-64字符 | - | - | 仅允许字母、数字、下划线 | 用户名不能为空 |
| string | password | 是 | 8-128字符 | - | - | 非空字符串 | 密码不能为空 |
A1.1.3 POST /auth/refresh-token
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | Authorization (Header) | 是 | - | - | - | Bearer 格式,token 为有效 JWT | 登录已过期,请重新登录 |
A1.1.4 POST /auth/logout
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | Authorization (Header) | 是 | - | - | - | Bearer 格式 | 登录已过期,请重新登录 |
13.2 A2 智能问答接口
A2.1.1 POST /chat/message
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | session_id | 是 | 1-64字符 | - | - | 非空,必须是当前用户拥有的会话 ID | 会话不存在或已删除 |
| string | message | 是 | 1-10000字符 | - | - | 去除首尾空格后长度>=1 | 请输入您的问题 |
| object | filters | 否 | - | {} | - | 对象类型,包含险种和保司字段 | - |
| string | filters.险种 | 否 | 0-32字符 | null | 重疾险/寿险/医疗险/意外险/年金险/储蓄险 | 如果提供,必须是有效险种名 | 无效的险种筛选条件 |
| string | filters.保司 | 否 | 0-64字符 | null | - | 字符串类型 | 无效的保司筛选条件 |
A2.1.2 GET /chat/sessions
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| int | page | 否 | 1-10000 | 1 | - | 正整数 | 页码必须为正整数 |
| int | page_size | 否 | 1-100 | 20 | - | 正整数,最大100 | 每页条数不能超过100 |
A2.1.3 POST /chat/sessions
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | title | 否 | 0-128字符 | 自动根据首条消息生成 | - | 如果提供则为非空字符串 | - |
A2.1.4 DELETE /chat/sessions/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空,必须是当前用户拥有的会话 ID | 会话不存在或已删除 |
A2.1.5 GET /chat/sessions/{id}/messages
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空,必须是当前用户拥有的会话 ID | 会话不存在或已删除 |
A2.1.6 POST /chat/messages/{id}/feedback
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空,必须是当前用户的消息 ID | 消息不存在 |
| string | rating | 是 | - | - | helpful/not_helpful | 必须是 helpful 或 not_helpful | 请选择评分(有用或无用) |
| string | comment | 否 | 0-2000字符 | null | - | 如果 rating 为 not_helpful 且提供 comment,长度>=1 | - |
A2.2.1 POST /retrieval/search
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | query | 是 | 1-1000字符 | - | - | 非空搜索词 | 请输入搜索关键词 |
| object | filters | 否 | - | {} | - | 对象类型 | - |
| string | filters.险种 | 否 | 0-32字符 | null | 重疾险/寿险/医疗险/意外险/年金险/储蓄险 | 有效险种名 | - |
| string | filters.保司 | 否 | 0-64字符 | null | - | 字符串 | - |
| int | top_k | 否 | 1-20 | 5 | - | 正整数 | 返回数量不能超过20 |
A2.2.2 GET /retrieval/suggest
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | query | 是 | 1-500字符 | - | - | 非空,当前对话上下文 | 请输入上下文 |
13.3 A3 方案生成接口
A3.1.1 POST /proposals/generate
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| object | customer | 是 | - | - | - | 非空对象 | 客户信息不能为空 |
| string | customer.name | 是 | 1-64字符 | - | - | 非空字符串 | 请输入客户姓名 |
| int | customer.age | 是 | 1-150 | - | - | 正整数 | 请输入有效的年龄(1-150) |
| string | customer.gender | 是 | - | - | male/female | 必须是 male 或 female | 请选择客户性别 |
| string | customer.health_status | 否 | 0-32字符 | 健康 | 健康/有既往病史/拒保史 | - | |
| string | customer.occupation | 是 | 1-64字符 | - | - | 非空字符串 | 请输入客户职业 |
| int | customer.annual_income | 否 | 0-100000000 | 0 | - | 非负整数 | 年收入不能为负数 |
| int | customer.monthly_budget | 是 | 1-1000000 | - | - | 正整数 | 月预算必须大于0 |
| array | insurance_types | 是 | 1-10项 | - | 重疾险/寿险/医疗险/意外险/年金险/储蓄险 | 非空数组,每项为有效险种名 | 请至少选择一个关注险种 |
| int | coverage_amount | 是 | 10000-10000000 | - | - | 正整数,>=10000 | 保额不能低于1万元 |
| string | coverage_period | 是 | - | - | 10年/20年/30年/至60岁/至70岁/至80岁/终身 | 必须是有效期限 | 请选择保障期限 |
| array | existing_policies | 否 | 0-20项 | [] | - | 数组,每项为对象 | - |
A3.1.2 GET /proposals/generate/{task_id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | task_id (Path) | 是 | 1-64字符 | - | - | 非空,必须是当前用户的任务 ID | 任务不存在 |
A3.1.4 POST /proposals/{id}/share
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空,必须是当前用户拥有的方案 ID | 方案不存在 |
| int | expire_hours | 否 | 1-720 | 72 | - | 正整数,最大30天 | 分享有效期不能超过720小时 |
13.4 A4 知识库接口
A4.1.1 POST /kb/documents/upload
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| file[] | files | 是 | 1-50个文件 | - | md/doc/docx/txt/pdf | 仅支持 MD/Word/TXT/PDF 格式 | 仅支持 MD、Word、TXT、PDF 格式的文件 |
| file | 单个文件大小 | - | 最大 50MB | - | - | 单文件不超过 50MB | 文件大小不能超过50MB |
| string | 险种 | 是 | 1-32字符 | - | 重疾险/寿险/医疗险/意外险/年金险/储蓄险 | 必须选择险种分类 | 请选择险种分类 |
| string | 保司 | 是 | 1-64字符 | - | - | 非空字符串 | 请选择保险公司 |
A4.1.2 GET /kb/documents
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| int | page | 否 | 1-10000 | 1 | - | 正整数 | - |
| int | page_size | 否 | 1-100 | 20 | - | 正整数 | - |
| string | 险种 | 否 | 0-32字符 | null | 重疾险/寿险/医疗险/意外险/年金险/储蓄险 | 有效险种名 | - |
| string | 保司 | 否 | 0-64字符 | null | - | 字符串 | - |
| string | status | 否 | - | null | processing/completed/failed | 有效状态值 | - |
| string | keyword | 否 | 0-128字符 | null | - | 搜索关键词(文件名/编号匹配) | - |
A4.1.3 GET /kb/documents/{id}/status
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空文档 ID | 文档不存在 |
A4.1.4 POST /kb/documents/{id}/retry
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空,文档必须是 failed 状态 | 文档不存在或状态不允许重试 |
A4.1.5 PATCH /kb/documents/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空文档 ID | 文档不存在 |
| string | 编号 | 否 | 0-32字符 | 当前值 | - | 字母数字和连字符 | - |
| string | 险种 | 否 | 1-32字符 | 当前值 | 重疾险/寿险/医疗险/意外险/年金险/储蓄险 | 有效险种名 | - |
| string | 保司 | 否 | 1-64字符 | 当前值 | - | 非空字符串 | - |
| array | tags | 否 | 0-20项 | 当前值 | - | 字符串数组,每项 1-32 字符 | - |
A4.1.6 DELETE /kb/documents/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空文档 ID | 文档不存在 |
A4.2.1 GET /kb/datasources
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| (无请求参数) |
A4.2.2 POST /kb/datasources
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | name | 是 | 1-128字符 | - | - | 非空字符串 | 数据源名称不能为空 |
| string | api_url | 是 | 1-512字符 | - | - | 合法 URL 格式 | 请输入有效的 API 地址 |
| string | sync_frequency | 是 | - | - | - | Cron 表达式格式(5字段) | 请输入有效的定时表达式 |
| string | 险种 | 是 | 1-32字符 | - | 重疾险/寿险/医疗险/意外险/年金险/储蓄险 | 有效险种名 | 请选择险种分类 |
A4.2.3 POST /kb/datasources/{id}/sync
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空数据源 ID | 数据源不存在 |
A4.2.4 GET /kb/datasources/{id}/logs
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空数据源 ID | 数据源不存在 |
| int | page | 否 | 1-10000 | 1 | - | 正整数 | - |
| int | page_size | 否 | 1-100 | 20 | - | 正整数 | - |
13.5 A5 用户权限接口
A5.1.1 GET /admin/users
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| int | page | 否 | 1-10000 | 1 | - | 正整数 | - |
| int | page_size | 否 | 1-100 | 20 | - | 正整数 | - |
| string | role | 否 | - | null | super_admin/admin/manager/sales | 有效角色值 | - |
| string | department | 否 | 0-128字符 | null | - | 字符串 | - |
| string | keyword | 否 | 0-128字符 | null | - | 用户名搜索关键词 | - |
A5.1.1 POST /admin/users
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | username | 是 | 2-64字符 | - | - | 非空,不能与已有用户名重复 | 用户名不能为空 |
| string | wecom_userid | 是 | 1-64字符 | - | - | 非空,不能与已有映射重复 | 企微用户ID不能为空 |
| string | role | 是 | - | - | super_admin/admin/manager/sales | 必须是有效角色 | 请选择有效角色 |
| string | department | 否 | 0-128字符 | - | - | 字符串 | - |
A5.1.1 PUT /admin/users/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空用户 ID | 用户不存在 |
| string | role | 否 | - | 当前值 | super_admin/admin/manager/sales | 有效角色 | - |
| string | department | 否 | 0-128字符 | 当前值 | - | 字符串 | - |
| string | status | 否 | - | 当前值 | active/disabled | 有效状态 | - |
A5.1.1 DELETE /admin/users/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空用户 ID | 用户不存在 |
| (注意:禁用用户时吊销其所有 Token) |
A5.1.2 POST /admin/users/batch-import
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| file | file | 是 | 1个文件 | - | xls/xlsx | 必须是 Excel 文件且符合模板格式 | 请上传符合模板格式的 Excel 文件 |
| (模板列:username, wecom_userid, role, department) |
A5.1.3 GET /admin/roles
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| (无请求参数) |
A5.1.3 POST /admin/roles
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | name | 是 | 2-32字符 | - | - | 非空,不能与已有角色名重复 | 角色名不能为空 |
| array | permissions | 是 | 1-50项 | - | - | 非空数组,每项为有效权限标识 | 请至少分配一个权限 |
A5.1.3 PUT /admin/roles/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空角色 ID | 角色不存在 |
| string | name | 否 | 2-32字符 | 当前值 | - | 非空字符串 | - |
| array | permissions | 否 | 0-50项 | 当前值 | - | 权限标识数组 | - |
A5.1.3 DELETE /admin/roles/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空,内置角色不可删除 | 角色不存在或为内置角色不可删除 |
13.6 A6 系统配置接口
A6.1.1 GET /admin/llm-configs
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| (无请求参数) |
A6.1.1 POST /admin/llm-configs
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | provider | 是 | 1-32字符 | - | deepseek/openai/zhipu/- | 非空,有效供应商名 | 请选择模型供应商 |
| string | model | 是 | 1-64字符 | - | - | 非空,有效模型名 | 请选择模型 |
| string | api_key | 是 | 1-512字符 | - | - | 非空字符串,服务端 AES 加密存储 | API Key 不能为空 |
| string | base_url | 否 | 0-512字符 | 供应商默认值 | - | 合法 URL 格式 | 请输入有效的 API 地址 |
| bool | is_default | 否 | - | false | - | 布尔值 | - |
| object | params | 否 | - | {} | - | 模型参数对象 | - |
| float | params.temperature | 否 | 0.0-2.0 | 0.7 | - | 浮点数范围校验 | Temperature 必须在 0-2 之间 |
| int | params.max_tokens | 否 | 1-32768 | 4096 | - | 正整数 | Max Tokens 必须为正整数 |
| float | params.top_p | 否 | 0.0-1.0 | 0.9 | - | 浮点数范围校验 | Top P 必须在 0-1 之间 |
A6.1.1 PUT /admin/llm-configs/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空配置 ID | 配置不存在 |
| (字段同 POST,均为可选,只更新提供的字段) |
A6.1.2 POST /admin/llm-configs/{id}/ping
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空配置 ID | 配置不存在 |
A6.1.3 GET /admin/prompts
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| (无请求参数) |
A6.1.3 POST /admin/prompts
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | name | 是 | 2-128字符 | - | - | 非空字符串 | Prompt 名称不能为空 |
| string | type | 是 | - | - | system/user | 有效类型 | 请选择 Prompt 类型 |
| string | content | 是 | 1-50000字符 | - | - | 非空字符串,保存时自动生成版本快照 | Prompt 内容不能为空 |
| array | variables | 否 | 0-20项 | [] | - | 字符串数组,变量名格式 | - |
A6.1.3 PUT /admin/prompts/{id}
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | id (Path) | 是 | 1-64字符 | - | - | 非空 Prompt ID | Prompt 不存在 |
| (字段同 POST,均为可选) |
A6.1.4 POST /admin/prompts/test
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | prompt_id | 是 | 1-64字符 | - | - | 非空 Prompt ID | Prompt 不存在 |
| string | test_query | 是 | 1-2000字符 | - | - | 非空测试问题 | 请输入测试问题 |
13.7 A7 留痕日志接口
A7.1.1 GET /admin/logs/chat
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| int | page | 否 | 1-10000 | 1 | - | 正整数 | - |
| int | page_size | 否 | 1-100 | 50 | - | 正整数 | - |
| string | user_id | 否 | 0-64字符 | null | - | 用户 ID | - |
| string | start_date | 否 | 10字符 | null | - | YYYY-MM-DD 格式日期 | 日期格式不正确 |
| string | end_date | 否 | 10字符 | null | - | YYYY-MM-DD 格式日期,>= start_date | 结束日期不能早于开始日期 |
| string | keyword | 否 | 0-128字符 | null | - | 搜索关键词 | - |
| string | rating | 否 | - | null | helpful/not_helpful | 有效评分值 | - |
| string | export | 否 | - | null | csv | 导出格式 | - |
A7.1.3 GET /admin/logs/system
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| int | page | 否 | 1-10000 | 1 | - | 正整数 | - |
| int | page_size | 否 | 1-100 | 50 | - | 正整数 | - |
| string | action | 否 | - | null | login/logout/upload/delete/config_change | 有效操作类型 | - |
| string | user_id | 否 | 0-64字符 | null | - | 用户 ID | - |
| string | start_date | 否 | 10字符 | null | - | YYYY-MM-DD 格式 | - |
| string | end_date | 否 | 10字符 | null | - | YYYY-MM-DD 格式 | - |
13.8 A8 统计报表接口
A8.1.2 GET /stats/trend
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | metric | 是 | - | - | chat_count/user_count/proposal_count | 有效指标名 | 请选择统计指标 |
| string | start_date | 是 | 10字符 | - | - | YYYY-MM-DD 格式 | 请选择开始日期 |
| string | end_date | 是 | 10字符 | - | - | YYYY-MM-DD 格式 | 请选择结束日期 |
| string | granularity | 否 | - | day | day/week/month | 有效粒度值 | - |
A8.1.3 GET /stats/kb-health
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| int | days | 否 | 1-365 | 30 | - | 正整数 | 统计天数不能超过365 |
A8.1.4 GET /stats/token-cost
| 字段名 | 类型 | 必填 | 长度/范围约束 | 默认值 | 枚举值 | 验证规则 | 错误提示文案 |
|---|---|---|---|---|---|---|---|
| string | start_date | 是 | 10字符 | - | - | YYYY-MM-DD 格式 | 请选择开始日期 |
| string | end_date | 是 | 10字符 | - | - | YYYY-MM-DD 格式 | 请选择结束日期 |
| string | group_by | 否 | - | model | model/day | 有效分组维度 | - |
十四、产品推荐表单验证规则
产品推荐页面(/recommend)所有表单字段的完整验证规则,包含前端即时验证和后端兜底验证。
14.1 客户信息表单
| 字段名 | 类型 | 必填 | 前端验证规则 | 后端验证规则 | 错误提示文案 | 边界值测试用例 |
|---|---|---|---|---|---|---|
| 客户姓名 | text | 是 | 非空,去除首尾空格后长度 1-64,仅允许中文/英文/空格/· | 同前端 | 请输入客户姓名 | 空值→"请输入客户姓名";空格→"请输入客户姓名";65字符→"姓名不能超过64个字符" |
| 年龄 | number | 是 | 整数,范围 1-150,不支持小数 | 同前端 | 请输入有效的年龄(1-150) | 空值→"请输入年龄";0→"请输入有效的年龄(1-150)";-1→同上;151→同上;3.5→"年龄必须为整数";1→合法;150→合法 |
| 性别 | radio | 是 | 必须选择 male 或 female,默认无选中 | 同前端 | 请选择客户性别 | 未选择→"请选择客户性别";male→合法;female→合法 |
| 健康状况 | select | 否 | 枚举值:健康/有既往病史/拒保史,默认"健康" | 同前端 | - | 空值→默认"健康";"健康"→合法 |
| 职业 | text | 是 | 非空,1-64字符,去除首尾空格 | 同前端 | 请输入客户职业 | 空值→"请输入客户职业";65字符→"职业不能超过64个字符" |
| 年收入 | number | 否 | 非负整数,范围 0-100000000,默认 0 | 同前端 | 年收入不能为负数 | 空值→默认0;-1→"年收入不能为负数";100000001→"年收入不能超过1亿元" |
| 月预算 | number | 是 | 正整数,范围 1-1000000 | 同前端 | 月预算必须大于0 | 空值→"请输入月预算";0→"月预算必须大于0";-1→同上;1000001→"月预算不能超过100万元" |
14.2 保障需求表单
| 字段名 | 类型 | 必填 | 前端验证规则 | 后端验证规则 | 错误提示文案 | 边界值测试用例 |
|---|---|---|---|---|---|---|
| 关注险种 | checkbox-group | 是 | 至少勾选1项,最多6项;可选项:重疾险/寿险/医疗险/意外险/年金险/储蓄险 | 同前端 | 请至少选择一个关注险种 | 未勾选→"请至少选择一个关注险种";勾选1项→合法;勾选6项→合法 |
| 保额目标 | slider/number | 是 | 正整数,范围 10000-10000000,步进 10000 | 同前端 | 保额不能低于1万元 | 10000→合法;9999→"保额不能低于1万元";10000001→"保额不能超过1000万元" |
| 保障期限 | select | 是 | 枚举值:10年/20年/30年/至60岁/至70岁/至80岁/终身 | 同前端 | 请选择保障期限 | 未选择→"请选择保障期限";"终身"→合法 |
14.3 已有保单信息(可选)
| 字段名 | 类型 | 必填 | 前端验证规则 | 后端验证规则 | 错误提示文案 | 边界值测试用例 |
|---|---|---|---|---|---|---|
| 已有保单 | dynamic-form | 否 | 每个保单条目包含:产品名称(必填)、保司(必填)、保额(可选)、生效日期(可选) | 同前端 | - | 不填→合法(跳过);填1条→合法;填20条→合法;填21条→"已有保单不能超过20条" |
14.4 表单提交综合验证
| 验证场景 | 触发条件 | 前端行为 | 后端行为 | 错误提示 |
|---|---|---|---|---|
| 全部必填项未填 | 点击"生成方案" | 各必填字段下方显示红色行内错误 | 返回 1001 错误码 | 各字段对应错误提示 |
| 部分必填项未填 | 点击"生成方案" | 未填字段标红,已填字段正常 | 返回 1001 错误码 | 缺失字段的错误提示 |
| 所有字段填写正确 | 点击"生成方案" | 按钮变为 loading 状态,显示"方案生成中..." | 调用 BaoDan Workflow | 无 |
| 网络断开 | 提交时 | 弹窗提示"网络异常,请检查网络连接" | 不到达后端 | 网络异常,请检查网络连接 |
| JWT 过期 | 提交时 | 自动跳转登录页 | 返回 1003 错误码 | 登录已过期,请重新登录 |
| 重复提交 | 快速双击"生成方案" | 第一次点击后按钮立即禁用 | 正常处理第一个请求 | 无 |
| 生成超时(>120s) | 后端响应超时 | 弹窗提示"方案生成超时,请重试" | 标记任务为 failed | 方案生成超时,请重试 |
| 生成失败 | BaoDan Workflow 执行失败 | 弹窗提示"方案生成失败,请重试" | 标记任务为 failed | 方案生成失败,请重试 |
14.5 前端验证实现规范
- 所有字段在用户离开输入框时(blur 事件)触发首次验证
- 在用户修改输入时(input 事件)触发实时验证(仅在已触发过首次验证后)
- 点击"生成方案"按钮时执行全量验证
- 错误提示显示在字段下方,使用红色文字(#F56C6C),字号 12px
- 验证不通过时输入框边框变为红色(border-color: #F56C6C)
- 验证通过时恢复默认边框颜色
- 提交按钮在所有必填项验证通过前保持 disabled 状态(灰色,不可点击)
十五、BaoDan 配置操作手册
从零开始配置 BaoDan 平台的完整操作步骤,面向从未接触过 BaoDan 的开发者。每一步都标注了具体的按钮名称、菜单位置和配置值。
15.1 BaoDan 初始化(首次访问)
前提:BaoDan Docker 部署已完成(参见第二十章部署清单)。
- 浏览器打开
http://你的服务器IP:3000 - 首次访问会看到"BaoDan"欢迎页面,点击 "设置管理员账号" 按钮
- 填写管理员信息:
- 邮箱:
admin@your-domain.com - 密码:设置一个强密码(至少8位,含大小写+数字)
- 名称:
系统管理员
- 邮箱:
- 点击 "创建管理员" 按钮
- 自动跳转到 BaoDan 后台首页,初始化完成
15.2 添加 DeepSeek 模型供应商
- 登录 BaoDan 后台(
http://你的服务器IP:3000) - 点击左下角 齿轮图标(设置)→ 进入设置页面
- 点击顶部 "模型供应商" 标签页
- 点击 "添加供应商" 按钮
- 在供应商列表中找到 "DeepSeek",点击 "设置" 按钮
- 填写配置:
- API Key:
sk-xxxxxxxxxxxxxxxxxxxxxxxx(从 platform.deepseek.com 获取) - API Base URL:
https://api.deepseek.com/v1(默认已填,无需修改)
- API Key:
- 点击 "保存" 按钮
- 添加 Embedding 模型:
- 在同一页面找到 "文本嵌入模型" 区域
- 点击 "设置默认模型"
- 选择供应商
DeepSeek,模型deepseek-embedding - 点击 "保存"
- 验证:点击 "测试" 按钮,显示"连接成功"即可
15.3 创建知识库(按险种分类)
每个险种创建一个独立知识库,便于管理和检索筛选。
- 点击左侧导航 "知识库" 菜单
- 点击 "创建知识库" 按钮
- 填写信息:
- 名称:
重疾险-产品条款(格式:险种-分类) - 描述:
重疾险相关产品条款、费率表、理赔规则等文档
- 名称:
- 点击 "创建" 按钮
- 进入知识库设置页面,配置分段规则(见15.4)
- 重复以上步骤,依次创建以下知识库:
寿险-产品条款医疗险-产品条款意外险-产品条款年金险-产品条款储蓄险-产品条款保险法规-政策文件(可选,存放监管政策类文档)
15.4 上传文档到知识库
- 进入目标知识库页面(如"重疾险-产品条款")
- 点击 "添加文件" 按钮
- 选择 "上传文件" 选项
- 拖拽或选择
.md文件(支持批量上传) - 配置分段设置:
- 分段标识符:选择 "Markdown 标题"(按
#/##/###分段) - 分段最大长度:
1000tokens - 分段重叠:
100tokens(确保上下文连续性) - 文本预处理规则:
- 勾选 "替换连续空格/换行/制表符"
- 勾选 "删除所有 URL 和邮箱地址"(可选)
- 分段标识符:选择 "Markdown 标题"(按
- 索引方式选择:
- 高质量模式(推荐):使用 Embedding 向量 + 全文检索混合模式
- Embedding 模型:
deepseek-embedding
- 点击 "保存并处理" 按钮
- 等待处理完成(状态从 "处理中" 变为 "可用")
- 重复以上步骤上传所有文档
15.5 创建聊天应用(智能问答)
- 点击左侧导航 "工作室" 菜单
- 点击 "创建应用" 按钮
- 选择 "聊天助手" 类型
- 填写应用信息:
- 名称:
保险智能客服 - 描述:
基于保险知识库的智能问答助手
- 名称:
- 点击 "创建" 按钮
- 进入应用编辑页面,配置以下内容:
配置 Prompt:
- 点击 "编排" 标签页
- 在 "提示词" 区域编辑系统提示词:
你是一名专业的保险顾问助手,专门为保险代理人提供产品咨询和方案建议服务。
## 核心规则
1. 所有回答必须基于检索到的知识库内容,不可编造产品信息
2. 如果知识库中没有相关内容,明确告知用户"当前知识库中未找到相关信息,建议咨询相关保险公司"
3. 回答要专业、准确、简洁,适合保险代理人向客户转述
4. 涉及具体条款时,必须注明产品名称和出处
## 回答格式
- 使用清晰的分段和编号
- 重要数据(保额、保费、等待期等)用粗体标注
- 如果涉及多个产品,使用表格对比展示
关联知识库:
- 点击 "上下文" 区域
- 点击 "添加" 按钮
- 选择所有已创建的知识库(全选或按需选择)
- 配置检索参数:
- Top-K:
5(返回最相关的5个文档片段) - Score 阈值:
0.5(低于此分数的片段不返回)
- Top-K:
配置模型参数:
- 点击 "模型" 区域
- 选择模型:
deepseek-chat - 设置参数:
- Temperature:
0.7 - Top P:
0.9 - Max Tokens:
4096
- Temperature:
15.6 设置应用公开访问
- 在应用编辑页面,点击右上角 "发布" 按钮
- 弹窗中确认发布
- 发布后,点击 "访问 API" 获取 API 地址
- 记录 API 密钥(格式:
app-xxxxxxxxxxxx),后续后端配置使用 - 获取 WebApp URL:
http://baodan:3000/chat/{app_id}
15.7 创建 Workflow 应用(产品推荐)
- 点击左侧导航 "工作室"
- 点击 "创建应用" → 选择 "工作流" 类型
- 名称:
产品推荐方案生成 - 点击 "创建" 后进入可视化编辑器
- 按以下顺序添加节点(拖拽组件到画布):
节点1 - 开始节点(默认已存在):
- 添加输入变量:
age:类型 string,必填gender:类型 string,必填occupation:类型 string,必填annual_income:类型 string,必填monthly_budget:类型 string,必填insurance_types:类型 string,必填coverage_amount:类型 string,必填coverage_period:类型 string,必填
节点2 - 代码节点(参数校验):
- 拖入 "代码执行" 组件
- 输入:所有开始节点变量
- 代码逻辑:校验 age 为 1-150 整数,gender 为 male/female 等
节点3 - 代码节点(检索策略):
- 输入:校验后的参数
- 代码:根据 insurance_types 生成检索 query 列表
节点4 - 知识库检索节点:
- 拖入 "知识库" 组件
- 关联所有险种知识库
- 输入:检索 query
- Top-K:5
- Score 阈值:0.6
节点5 - LLM 节点:
- 拖入 "LLM" 组件
- 模型:
deepseek-chat - Temperature:0.7
- Prompt:参见第十九章完整 Prompt
节点6 - 代码节点(格式化输出):
- 输入:LLM 输出的 Markdown
- 逻辑:清理格式,确保可读性
节点7 - 结束节点:
- 输出变量:
recommendation(格式化后的方案文本)
- 点击 "发布" 按钮保存 Workflow
- 记录 API 密钥(Workflow 专用 Key)
15.8 配置 iframe 嵌入参数
- 进入聊天应用设置
- 点击 "API 访问" 标签页
- 勾选 "启用 WebApp 访问"
- 配置 "跨域设置":
- 允许的来源(Allowed Origins):添加
https://your-domain.com
- 允许的来源(Allowed Origins):添加
- 获取 iframe 嵌入代码:
<iframe src="http://baodan:3000/chat/{app_id}?user={user_id}" style="width:100%; height:100%; border:none;" allow="microphone"> </iframe>
15.9 测试对话功能
- 在聊天应用页面,点击右上角 "预览" 按钮
- 输入测试问题:
重疾险的等待期是多少天? - 验证以下内容:
- 收到基于知识库的回答(非编造内容)
- 回答中包含来源引用
- 回答在 15 秒内开始输出
- 测试边界情况:
- 输入空消息 → 应提示"请输入问题"
- 输入无关问题 → 应回答"当前知识库中未找到相关内容"
- 输入超长文本(>10000字)→ 应正常处理或提示过长
15.10 测试 Workflow 功能
- 进入 Workflow 应用页面,点击 "预览" 按钮
- 填写测试输入:
- age:
35 - gender:
male - occupation:
软件工程师 - annual_income:
300000 - monthly_budget:
2000 - insurance_types:
重疾险,医疗险 - coverage_amount:
500000 - coverage_period:
终身
- age:
- 点击 "运行" 按钮
- 验证以下内容:
- 每个节点正常执行(无红色错误标记)
- 知识库检索返回了相关产品信息
- LLM 生成了三套方案(基础/均衡/全面)
- 每套方案包含产品表格、保费、推荐理由
- 总保费不超过月预算 x 12(24000元/年)
- 整体执行时间 < 120 秒
十六、企微应用配置操作手册
企微管理后台的完整配置步骤,从创建应用到机器人可用的全流程。
16.1 登录企微管理后台
- 打开浏览器访问
https://work.weixin.qq.com/ - 使用管理员账号扫码或账密登录
- 进入管理后台首页
16.2 创建自建应用
- 点击左侧导航 "应用管理"
- 点击 "自建" 区域的 "创建应用" 按钮
- 填写应用信息:
- 应用名称:
保险智能客服 - 应用 Logo:上传一个图标(建议 200x200px PNG)
- 应用介绍:
基于 AI 的保险知识问答和产品推荐助手 - 可见范围:选择需要使用此应用的部门(如:全部部门,或指定销售部门)
- 应用名称:
- 点击 "创建应用" 按钮
- 创建成功后,记录以下信息:
- AgentId:在应用详情页顶部显示(如
1000002) - Secret:点击 "Secret" 旁边的 "查看" 按钮,输入管理员密码后获取
- AgentId:在应用详情页顶部显示(如
16.3 配置应用可见范围
- 在应用详情页,点击 "可见范围" 区域的 "编辑" 按钮
- 勾选需要使用此应用的部门
- 点击 "保存"
- 重要:可见范围决定了哪些用户能在企微中看到此应用
16.4 配置接收消息(回调 URL)
- 在应用详情页,找到 "接收消息" 区域
- 点击 "设置API接收" 按钮
- 填写以下信息:
- URL:
https://your-domain.com/api/wecom/callback(注意:必须是 HTTPS,企微要求) - Token:点击 "随机获取" 按钮自动生成
- EncodingAESKey:点击 "随机获取" 按钮自动生成(43位字符串)
- URL:
- 点击 "保存" 按钮
- 企微会向你填写的 URL 发送验证请求
- 此时后端服务必须已部署并运行,否则保存会失败
- 验证通过后,点击 "接收消息" 下的 "设置接收消息",选择 "使用 API 接收消息"
- 将 Token 和 EncodingAESKey 记录下来,配置到后端环境变量:
WECOM_TOKEN=你复制的Token WECOM_ENCODING_AES_KEY=你复制的EncodingAESKey
16.5 配置企业可信 IP
- 在应用详情页,找到 "企业可信IP" 区域
- 点击 "配置" 按钮
- 添加你服务器的公网 IP 地址(如
123.45.67.89) - 如果有多个出口 IP,全部添加
- 点击 "保存"
- 注意:如果不配置,企微消息回调会被拒绝
16.6 获取 CorpID / AgentID / Secret
| 信息 | 获取位置 | 格式示例 |
|---|---|---|
| CorpID | 管理后台 → 我的企业 → 企业信息 → 企业ID | ww1234567890abcdef |
| AgentID | 应用管理 → 应用详情页顶部 | 1000002 |
| AgentSecret | 应用详情 → Secret → 查看 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
配置到后端环境变量:
WECOM_CORP_ID=ww1234567890abcdef
WECOM_AGENT_ID=1000002
WECOM_AGENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
16.7 配置 OAuth 回调域名
- 管理后台 → "我的企业" → "企业微信授权登录"
- 点击 "设置授权回调域"
- 填写你的域名:
your-domain.com - 点击 "保存"
- 注意:这里填的是根域名,不是具体路径。回调 URL 为
https://your-domain.com/api/auth/wework-callback
16.8 测试机器人消息收发
- 在企微手机端或电脑端,搜索应用名称 "保险智能客服"
- 打开应用,发送一条消息:
你好 - 验证以下内容:
- 收到 AI 回复(非超时错误)
- 回复内容与知识库相关
- 回复延迟 < 10 秒
- 如果没有收到回复:
- 检查后端日志是否有企微回调记录
- 检查可信 IP 配置是否正确
- 检查 Token/Secret 配置是否正确
16.9 测试 OAuth 登录
- 在企微内置浏览器中访问:
https://your-domain.com/app/login - 点击 "企微登录" 按钮
- 应自动跳转到企微授权页面
- 点击 "同意" 授权
- 验证以下内容:
- 成功跳转回系统主页
- 右上角显示用户姓名
- 可以正常使用各项功能
- 如果登录失败:
- 检查 OAuth 回调域名是否配置正确
- 检查后端 WECOM_CORP_ID 和 WECOM_AGENT_SECRET 是否正确
- 查看浏览器控制台网络请求的错误信息
16.10 配置应用主页 URL(企微 H5)
- 在应用详情页,找到 "应用主页" 区域
- 点击 "设置" 按钮
- 选择 "自定义主页"
- 填写主页 URL:
https://your-domain.com/app - 设置 "工作台" 展示:
- 勾选 "在企业微信工作台展示"
- 设置展示名称:
保险智能客服
- 点击 "保存"
- 验证:在企微工作台中应能看到"保险智能客服"入口
十七、前端页面交互设计
每个页面的完整交互设计,包括所有状态、用户操作流程、按钮行为和状态变化。
17.1 登录页(/login)
页面状态:
| 状态 | 显示内容 | 触发条件 |
|---|---|---|
| 初始状态 | 登录方式选择界面 | 页面首次加载 |
| 企微授权中 | 全屏 loading + "正在跳转企微授权..." | 点击"企微登录"按钮后 |
| 授权回调中 | 全屏 loading + "正在登录..." | 企微回调到达后端时 |
| 登录失败 | 登录界面 + 顶部红色提示条 | 登录接口返回错误 |
| 已登录 | 自动跳转到 /chat | 已存在有效 Token |
用户操作流程:
- 用户访问 /login → 显示登录界面
- 选择登录方式:
- 方式A - 企微登录:点击"企微登录"按钮 → 跳转企微授权 → 同意授权 → 回调到后端 → 签发 JWT → 跳转 /chat
- 方式B - 账密登录:输入用户名 + 密码 → 点击"登录" → 后端校验 → 签发 JWT → 跳转 /chat
- 登录成功 → 存储 Token 到 localStorage → 跳转 /chat
按钮行为:
- "企微登录"按钮:点击后变为 loading 状态,禁止重复点击,跳转企微授权页
- "登录"按钮:表单验证通过后可点击,点击后显示 loading + "登录中...",禁止重复点击
- Enter 键:在密码输入框中按 Enter 等同于点击"登录"按钮
17.2 对话页(/chat)
页面状态:
| 状态 | 显示内容 | 触发条件 |
|---|---|---|
| 加载中 | 骨架屏(Skeleton) | 页面首次加载 |
| 空状态 | 居中图标 + "开始新的对话吧" + 快捷提问按钮 | 无任何会话 |
| 正常 | 左侧会话列表 + 右侧对话区域 | 有会话数据 |
| 发送中 | 用户消息气泡 + AI 回复区域显示"思考中..."动画 | 发送消息后等待响应 |
| 流式输出 | AI 回复区域逐字显示文字 | SSE 流式响应中 |
| 错误 | 对话区域顶部红色提示条 | 接口返回错误 |
| 会话删除确认 | 弹窗"确定要删除这个会话吗?" | 点击删除按钮 |
用户操作流程:
- 进入页面 → 加载会话列表 → 显示最近会话或空状态
- 点击"新建对话" → 清空对话区域 → 等待用户输入
- 在输入框输入问题 → 点击发送(或按 Enter)→ 显示"思考中..."
- 收到流式响应 → 逐字显示 AI 回答
- 回答完成 → 显示来源引用 + 操作按钮(复制/点赞/点踩)
- 可继续在同一会话中提问
按钮行为:
- "新建对话"按钮:清空对话区域,重置 session_id 为空,输入框获得焦点
- "发送"按钮:输入框为空时禁用(灰色),有内容时启用(蓝色);点击后立即禁用直到回复完成
- "复制"按钮:复制 AI 回答的纯文本到剪贴板,点击后变为"已复制"状态(2秒后恢复)
- "点赞/点踩"按钮:点击后调用反馈接口,已评的按钮高亮,可切换
- "删除会话"按钮:显示确认弹窗,确认后调用 DELETE 接口,从列表移除
- 筛选下拉框(险种/保司):选择后影响后续对话的知识库检索范围
17.3 产品推荐页(/recommend)
页面状态:
| 状态 | 显示内容 | 触发条件 |
|---|---|---|
| 初始状态 | 空白表单,所有字段为默认值 | 页面首次加载或重置 |
| 表单填写中 | 表单各字段可编辑 | 用户交互中 |
| 表单验证失败 | 未通过字段标红 + 红色行内错误提示 | 点击"生成方案"时 |
| 提交中 | 按钮 loading + "方案生成中..." + 禁止重复提交 | 提交成功后 |
| 生成中(轮询) | 进度提示 + "正在生成方案,请稍候..." | 后端返回 processing |
| 生成完成 | 方案预览区显示三套方案 | 轮询返回 done |
| 生成失败 | 红色错误提示 + "重新生成"按钮 | 轮询返回 failed |
| 网络错误 | 弹窗"网络异常,请检查网络连接" | 请求超时或网络断开 |
用户操作流程:
- 进入页面 → 显示空白表单
- 填写客户信息(姓名/年龄/性别/职业/收入/预算)
- 选择关注险种(勾选复选框)
- 设置保额目标和保障期限
- 可选:添加已有保单信息
- 点击"生成方案" → 前端全量校验 → 校验通过则提交
- 显示 loading → 轮询任务状态 → 显示生成的方案
- 可点击"重新生成"回到步骤2
按钮行为:
- "生成方案"按钮:全量验证通过后可点击;点击后显示 loading 状态,disabled 直到生成完成或失败
- "重新生成"按钮:重置表单为上次提交值,允许修改后重新提交
- "浏览器打印"按钮:调用 window.print() 打印方案预览区
- "添加保单"按钮:在已有保单区域动态添加一行表单
- "删除保单"行按钮:移除对应保单行,至少保留0行
17.4 推荐结果页
页面状态:
| 状态 | 显示内容 | 触发条件 |
|---|---|---|
| 加载中 | 骨架屏 | 页面加载时 |
| 正常 | 三套方案(基础/均衡/全面),每套含产品表格 | 数据加载完成 |
| 空状态 | 暂无方案 + "去生成"按钮 | 无历史方案 |
| 错误 | 错误提示 + "重试"按钮 | 接口返回错误 |
用户操作流程:
- 生成完成后直接在推荐页下方显示方案
- 可切换查看三套方案(Tab 切换:基础方案/均衡方案/全面方案)
- 每套方案显示:方案名称、总年保费、产品明细表格、推荐理由
- 可点击"浏览器打印"导出
- 可点击"重新生成"回到表单页
17.5 历史方案页(/recommend/history)
页面状态:
| 状态 | 显示内容 | 触发条件 |
|---|---|---|
| 加载中 | 表格骨架屏 | 页面加载时 |
| 正常 | 方案列表表格 + 分页器 | 有历史数据 |
| 空状态 | 居中图标 + "暂无推荐方案" | 无历史数据 |
| 筛选结果为空 | 表格显示"暂无匹配数据" | 筛选条件无匹配 |
| 删除确认 | 弹窗"确定要删除此方案吗?" | 点击删除按钮 |
用户操作流程:
- 进入页面 → 加载方案列表(默认按时间倒序)
- 可使用筛选条件:客户姓名、险种、日期范围、状态
- 点击某条记录 → 查看方案详情
- 可对方案进行操作:查看、删除、分享
按钮行为:
- "查看"按钮:弹窗展示方案详情(Markdown 渲染)
- "删除"按钮:显示确认弹窗,确认后删除
- "分享"按钮:调用分享接口,生成有时效的链接,显示在弹窗中可复制
- 分页器:翻页加载对应页数据
- "导出CSV"按钮:调用日志接口 export=csv,触发浏览器下载
17.6 管理后台页面
17.6.1 管理后台框架(/admin)
布局:左侧导航栏 + 右侧内容区 + 顶部栏
左侧导航菜单:
- 知识库管理 → /admin/knowledge-base
- 对话日志 → /admin/logs/chat
- 系统日志 → /admin/logs/system(仅超级管理员可见)
- 用户管理 → /admin/users(仅超级管理员可见)
- 角色管理 → /admin/roles(仅超级管理员可见)
- LLM 配置 → /admin/llm-configs(仅超级管理员可见)
- Prompt 管理 → /admin/prompts
- 数据统计 → /admin/stats
顶部栏:管理后台标题 + "切换到用户端"链接 + 用户信息 + 退出登录
17.6.2 知识库管理页(/admin/knowledge-base)
页面状态:
| 状态 | 显示内容 | 触发条件 |
|---|---|---|
| 加载中 | 表格骨架屏 | 页面加载 |
| 正常 | 文档列表表格 + 上传按钮 + 筛选栏 | 有文档数据 |
| 上传中 | 上传进度条 + "正在处理..." | 上传文件后 |
| 处理中 | 文档状态列显示 spinner + "处理中" | 文档正在向量化 |
按钮行为:
- "上传文档"按钮:打开文件选择器,选择文件后弹出分类配置(险种/保司),确认后上传
- "重试"按钮(仅 failed 状态文档可见):重新触发处理流水线
- "查看状态"按钮:弹窗显示处理流水线各阶段状态和耗时
- "删除"按钮:确认后软删除文档并下线索引
- "编辑"按钮:弹窗编辑文档元数据(编号/分类/标签)
17.6.3 对话日志页(/admin/logs/chat)
按钮行为:
- "查询"按钮:根据筛选条件重新加载日志
- "重置"按钮:清空所有筛选条件,恢复默认
- "导出CSV"按钮:下载日志数据为 CSV 文件
17.6.4 用户管理页(/admin/users)
按钮行为:
- "新增用户"按钮:弹窗表单(用户名/企微ID/角色/部门),填写后保存
- "批量导入"按钮:下载 Excel 模板 → 填写后上传 → 预览导入结果 → 确认导入
- "编辑"按钮:弹窗修改角色/部门/状态
- "禁用"按钮:确认后禁用用户,同时吊销其所有 Token
- "启用"按钮:恢复已禁用的用户
17.6.5 角色管理页(/admin/roles)
按钮行为:
- "新增角色"按钮:弹窗表单(角色名 + 权限树勾选),保存后生效
- "编辑"按钮:修改角色名和权限(内置角色仅可编辑权限)
- "删除"按钮:内置角色不可删除(按钮灰色禁用),自定义角色可删除
17.6.6 LLM 配置页(/admin/llm-configs)
按钮行为:
- "添加模型"按钮:弹窗表单(供应商/模型/Key/Base URL/参数),保存后自动加密存储 Key
- "测试连通性"按钮:调用 ping 接口,显示延迟结果(绿色=成功/红色=失败)
- "设为默认"按钮:将该模型设为默认,其他模型取消默认
- "删除"按钮:确认后删除配置(已被应用引用的不可删除)
十八、错误提示文案清单
所有面向用户的错误消息完整清单,按错误码组织。每条错误消息都是完整的中文句子,前端直接展示给用户。
18.1 统一响应格式
{
"code": 0,
"message": "success",
"data": { ... }
}
code = 0表示成功code != 0表示失败,message字段为用户可见的错误文案- 前端根据
code值决定展示方式(弹窗/行内/Toast)
18.2 认证相关错误(1xxx)
| 错误码 | 触发场景 | 显示位置 | 具体文案 | 是否可重试 |
|---|---|---|---|---|
| 1001 | 请求参数缺少必填项或类型错误 | 页面顶部 Toast 提示 | 参数错误:{具体字段}不能为空 | 修改后重试 |
| 1001 | 请求参数类型不匹配 | 页面顶部 Toast 提示 | 参数错误:{字段名}格式不正确 | 修改后重试 |
| 1002 | 未携带 Token 或 Token 格式无效 | 自动跳转登录页 | 登录已过期,请重新登录 | 否(需重新登录) |
| 1002 | Token 不在有效用户中 | 自动跳转登录页 | 登录已过期,请重新登录 | 否(需重新登录) |
| 1003 | JWT Token 已超过有效期 | 自动跳转登录页 | 登录已过期,请重新登录 | 否(需重新登录) |
| 1003 | refresh-token 接口 Token 过期 | 自动跳转登录页 | 登录已过期,请重新登录 | 否(需重新登录) |
| 1004 | 用户角色无权访问该接口 | 页面顶部 Toast 提示 | 权限不足,您没有访问该功能的权限 | 否(需管理员授权) |
| 1004 | 非管理员访问管理接口 | 页面顶部 Toast 提示 | 权限不足,您没有管理权限 | 否 |
| 1005 | 请求的资源 ID 不存在 | 行内提示或弹窗 | 请求的资源不存在或已删除 | 否 |
| 1005 | 访问不存在的会话 | 行内提示 | 会话不存在或已删除 | 否 |
| 1005 | 访问不存在的方案 | 行内提示 | 方案不存在或已删除 | 否 |
| 1005 | 访问不存在的文档 | 行内提示 | 文档不存在或已删除 | 否 |
| 1005 | 访问不存在的用户 | 行内提示 | 用户不存在 | 否 |
| 1005 | 访问不存在的角色 | 行内提示 | 角色不存在 | 否 |
| 1005 | 访问不存在的 Prompt | 行内提示 | Prompt 不存在 | 否 |
| 1005 | 访问不存在的数据源 | 行内提示 | 数据源不存在 | 否 |
| 1005 | 访问不存在的任务 | 行内提示 | 任务不存在 | 否 |
18.3 BaoDan 相关错误(2xxx)
| 错误码 | 触发场景 | 显示位置 | 具体文案 | 是否可重试 |
|---|---|---|---|---|
| 2001 | BaoDan API 不可达或返回错误 | 页面顶部 Toast 提示 | AI 服务暂时不可用,请稍后重试 | 是 |
| 2001 | BaoDan API 返回 500 错误 | 页面顶部 Toast 提示 | AI 服务暂时不可用,请稍后重试 | 是 |
| 2001 | BaoDan API Key 过期或无效 | 页面顶部 Toast 提示 | AI 服务配置异常,请联系管理员 | 否(需管理员修复) |
| 2002 | BaoDan Chat API 响应超过 60 秒 | 页面顶部 Toast 提示 | AI 回答生成超时,请重试 | 是 |
| 2003 | BaoDan Workflow 执行失败 | 弹窗提示 | 方案生成失败,请重试 | 是 |
| 2003 | Workflow 节点执行出错 | 弹窗提示 | 方案生成失败,请稍后重试 | 是 |
| 2004 | BaoDan Workflow 执行超过 120 秒 | 弹窗提示 | 方案生成超时,请重试 | 是 |
18.4 企微相关错误(3xxx)
| 错误码 | 触发场景 | 显示位置 | 具体文案 | 是否可重试 |
|---|---|---|---|---|
| 3001 | 企微 API 调用失败 | 服务端日志(用户不可见) | (不展示给用户,记录日志) | 自动重试 |
| 3002 | 企微 Access Token 获取失败 | 服务端日志(用户不可见) | (不展示给用户,3次重试后放弃) | 自动重试3次 |
| 3003 | 企微消息回复失败 | 服务端日志(用户不可见) | (不展示给用户,记录日志) | 否 |
| 3004 | 企微 OAuth 授权码已过期(5分钟有效) | 登录页顶部 Toast 提示 | 授权已过期,请重新登录 | 是(重新发起授权) |
| 3005 | 企微用户不在系统白名单中 | 登录页顶部 Toast 提示 | 您的账号暂未开通,请联系管理员 | 否 |
| 3005 | 企微用户已被禁用 | 登录页顶部 Toast 提示 | 您的账号已被禁用,请联系管理员 | 否 |
18.5 文件相关错误(4xxx)
| 错误码 | 触发场景 | 显示位置 | 具体文案 | 是否可重试 |
|---|---|---|---|---|
| 4001 | 文件上传失败(网络中断等原因) | 页面顶部 Toast 提示 | 文件上传失败,请检查网络后重试 | 是 |
| 4001 | 文件写入磁盘失败 | 页面顶部 Toast 提示 | 文件保存失败,请稍后重试 | 是 |
| 4002 | 文件格式不支持 | 上传弹窗行内提示 | 仅支持 MD、Word、TXT、PDF 格式的文件 | 否(需转换格式) |
| 4003 | 单个文件超过 50MB 限制 | 上传弹窗行内提示 | 文件大小不能超过 50MB | 否(需压缩文件) |
| 4003 | 批量上传总大小超过限制 | 上传弹窗行内提示 | 上传文件总大小不能超过 200MB | 否(需分批上传) |
| 4004 | Excel 导入模板格式错误 | 弹窗提示 | 请下载模板后按模板格式填写 | 否(需使用模板) |
18.6 系统相关错误(5xxx-9xxx)
| 错误码 | 触发场景 | 显示位置 | 具体文案 | 是否可重试 |
|---|---|---|---|---|
| 5001 | PostgreSQL 数据库操作失败 | 页面顶部 Toast 提示 | 系统繁忙,请稍后重试 | 是 |
| 5002 | Redis 缓存操作失败 | 页面顶部 Toast 提示 | 系统繁忙,请稍后重试 | 是 |
| 5003 | 文件操作失败(读写异常) | 页面顶部 Toast 提示 | 文件处理异常,请稍后重试 | 是 |
| 9999 | 服务器内部未知错误 | 页面顶部 Toast 提示 | 系统异常,请稍后重试 | 是 |
18.7 前端本地验证错误(非接口返回)
| 触发场景 | 显示位置 | 具体文案 | 是否可重试 |
|---|---|---|---|
| 登录用户名为空 | 行内错误 | 用户名不能为空 | 修改后重试 |
| 登录密码为空 | 行内错误 | 密码不能为空 | 修改后重试 |
| 登录用户名过短 | 行内错误 | 用户名至少3个字符 | 修改后重试 |
| 登录密码过短 | 行内错误 | 密码至少8个字符 | 修改后重试 |
| 客户姓名为空 | 行内错误 | 请输入客户姓名 | 修改后重试 |
| 年龄为空 | 行内错误 | 请输入年龄 | 修改后重试 |
| 年龄不在有效范围 | 行内错误 | 请输入有效的年龄(1-150) | 修改后重试 |
| 年龄非整数 | 行内错误 | 年龄必须为整数 | 修改后重试 |
| 性别未选择 | 行内错误 | 请选择客户性别 | 修改后重试 |
| 职业为空 | 行内错误 | 请输入客户职业 | 修改后重试 |
| 年收入为负数 | 行内错误 | 年收入不能为负数 | 修改后重试 |
| 月预算为空 | 行内错误 | 请输入月预算 | 修改后重试 |
| 月预算为零或负数 | 行内错误 | 月预算必须大于0 | 修改后重试 |
| 未选择关注险种 | 行内错误 | 请至少选择一个关注险种 | 修改后重试 |
| 保额低于下限 | 行内错误 | 保额不能低于1万元 | 修改后重试 |
| 保额超过上限 | 行内错误 | 保额不能超过1000万元 | 修改后重试 |
| 保障期限未选择 | 行内错误 | 请选择保障期限 | 修改后重试 |
| 已有保单超过20条 | 行内错误 | 已有保单不能超过20条 | 修改后重试 |
| 网络断开时提交 | 页面弹窗 | 网络异常,请检查网络连接 | 检查网络后重试 |
| 输入内容过长(>10000字) | 行内提示 | 输入内容过长,请缩短后重试 | 修改后重试 |
| 搜索关键词为空 | 行内提示 | 请输入搜索关键词 | 修改后重试 |
18.8 错误展示规范
| 展示方式 | 适用场景 | 展示位置 | 持续时间 | 是否可关闭 |
|---|---|---|---|---|
| Toast 提示(轻提示) | 一般性错误、操作成功提示 | 页面顶部居中 | 3秒后自动消失 | 否 |
| 行内错误 | 表单字段验证失败 | 对应字段下方 | 持续显示直到修正 | 否 |
| 弹窗(Modal) | 严重错误、需用户确认的操作 | 页面居中遮罩层 | 用户点击关闭或确认 | 是 |
| 错误页面(404/500) | 页面级错误 | 全屏替换内容 | 持续显示 | 点击按钮跳转 |
十九、BaoDan Workflow 节点完整设计
产品推荐方案 BaoDan Workflow 的每个节点完整配置,包含输入变量、输出变量、完整 Prompt、条件分支逻辑、错误处理和测试数据。
19.1 Workflow 总览
[开始] → [节点1:参数校验] → [节点2:检索策略] → [节点3:知识库检索(循环)]
↓
[节点6:格式化输出] ← [节点4:LLM方案生成] ← [节点5:异常处理]
↓
[结束]
19.2 节点 1:参数校验(Code Node)
节点配置:
| 属性 | 值 |
|---|---|
| 节点名称 | 参数校验 |
| 节点类型 | Code(代码执行) |
| 编程语言 | Python3 |
输入变量:
| 变量名 | 类型 | 来源 | 必填 |
|---|---|---|---|
| age | string | 开始节点 | 是 |
| gender | string | 开始节点 | 是 |
| occupation | string | 开始节点 | 是 |
| annual_income | string | 开始节点 | 是 |
| monthly_budget | string | 开始节点 | 是 |
| insurance_types | string | 开始节点 | 是 |
| coverage_amount | string | 开始节点 | 是 |
| coverage_period | string | 开始节点 | 是 |
输出变量:
| 变量名 | 类型 | 说明 |
|---|---|---|
| validated_params | object | 校验通过的参数对象 |
| error_msg | string | 校验失败时的错误信息(为空表示通过) |
| is_valid | bool | 是否校验通过 |
代码逻辑:
import json
def main(age: str, gender: str, occupation: str, annual_income: str,
monthly_budget: str, insurance_types: str, coverage_amount: str,
coverage_period: str) -> dict:
errors = []
# 年龄校验
try:
age_int = int(age)
if not (1 <= age_int <= 150):
errors.append("年龄必须在1-150之间")
except ValueError:
errors.append("年龄必须为整数")
# 性别校验
if gender not in ("male", "female"):
errors.append("性别必须为 male 或 female")
# 职业校验
if not occupation or not occupation.strip():
errors.append("职业不能为空")
# 月预算校验
try:
budget = int(monthly_budget)
if budget <= 0:
errors.append("月预算必须大于0")
except ValueError:
errors.append("月预算必须为正整数")
# 保额校验
try:
amount = int(coverage_amount)
if amount < 10000:
errors.append("保额不能低于1万元")
except ValueError:
errors.append("保额必须为正整数")
# 险种校验
valid_types = ["重疾险", "寿险", "医疗险", "意外险", "年金险", "储蓄险"]
selected = [t.strip() for t in insurance_types.split(",") if t.strip()]
if not selected:
errors.append("请至少选择一个险种")
for t in selected:
if t not in valid_types:
errors.append(f"无效的险种: {t}")
if errors:
return {
"validated_params": {},
"error_msg": "; ".join(errors),
"is_valid": False
}
return {
"validated_params": {
"age": age_int,
"gender": gender,
"occupation": occupation.strip(),
"annual_income": int(annual_income) if annual_income else 0,
"monthly_budget": budget,
"insurance_types": selected,
"coverage_amount": amount,
"coverage_period": coverage_period
},
"error_msg": "",
"is_valid": True
}
错误处理:当 is_valid = False 时,直接跳转到结束节点,输出 error_msg 作为错误提示。
19.3 节点 2:检索策略生成(Code Node)
输入变量:
| 变量名 | 类型 | 来源 |
|---|---|---|
| validated_params | object | 节点1输出 |
输出变量:
| 变量名 | 类型 | 说明 |
|---|---|---|
| search_queries | array | 检索关键词列表,每项含险种和query |
代码逻辑:
def main(validated_params: dict) -> dict:
queries = []
age = validated_params["age"]
for insurance_type in validated_params["insurance_types"]:
query = f"{insurance_type} 产品条款 保额 费率 {age}岁"
queries.append({
"险种": insurance_type,
"query": query
})
return {"search_queries": queries}
19.4 节点 3:知识库检索(Knowledge Retrieval Node)
节点配置:
| 属性 | 值 |
|---|---|
| 节点名称 | 知识库检索 |
| 节点类型 | Knowledge Retrieval(知识库检索) |
| 关联知识库 | 所有险种知识库(重疾险/寿险/医疗险/意外险/年金险/储蓄险) |
| 检索模式 | 混合检索(向量 + 全文) |
| Top-K | 每个查询返回 5 个最相关片段 |
| Score 阈值 | 0.6(低于此分数的片段被过滤) |
输入变量:
| 变量名 | 类型 | 来源 |
|---|---|---|
| query | string | 从 search_queries 数组中提取的检索词 |
输出变量:
| 变量名 | 类型 | 说明 |
|---|---|---|
| retrieved_docs | array | 检索到的文档片段列表,含文档名、内容、分数、来源 |
节点行为:
- 对每个险种分别执行一次检索
- 每次检索返回 Top-5 相关片段
- 所有检索结果合并后传递给下游节点
19.5 节点 4:LLM 方案生成(LLM Node)
节点配置:
| 属性 | 值 |
|---|---|
| 节点名称 | 方案生成 |
| 节点类型 | LLM |
| 模型 | deepseek-chat |
| Temperature | 0.7 |
| Top P | 0.9 |
| Max Tokens | 8192 |
输入变量:
| 变量名 | 类型 | 来源 |
|---|---|---|
| validated_params | object | 节点1输出 |
| retrieved_docs | array | 节点3输出 |
输出变量:
| 变量名 | 类型 | 说明 |
|---|---|---|
| recommendation | string | 生成的三套方案(Markdown 格式) |
完整 Prompt:
你是一名资深保险方案规划师,拥有 10 年保险行业经验。你的任务是根据客户的个人信息和检索到的保险产品条款,为客户量身定制保险产品推荐方案。
## 客户信息
- 年龄:{{validated_params.age}} 岁
- 性别:{{validated_params.gender}}
- 职业:{{validated_params.occupation}}
- 年收入:{{validated_params.annual_income}} 元
- 月预算:{{validated_params.monthly_budget}} 元
- 关注险种:{{validated_params.insurance_types}}
- 保额目标:{{validated_params.coverage_amount}} 元
- 保障期限:{{validated_params.coverage_period}}
## 检索到的产品信息
{{retrieved_docs}}
## 输出要求
请严格按照以下格式生成三套方案(基础方案 / 均衡方案 / 全面方案),每套方案需独立完整:
### 基础方案(年保费约 XXXX 元)
|产品名称|所属保险公司|险种|保额|年保费|推荐理由|
|---------|-----------|-----|-----|------|--------|
|XX重疾险|XX人寿|重疾险|30万|2400元|35岁男性投保,性价比高,覆盖120种重疾|
方案总结:(2-3句话说明基础方案的特点和适用人群)
### 均衡方案(年保费约 XXXX 元)
(同上格式)
方案总结:(2-3句话)
### 全面方案(年保费约 XXXX 元)
(同上格式)
方案总结:(2-3句话)
## 重要规则
1. **保费数据必须来自检索到的产品信息,绝对不可编造**
2. 如果某险种在知识库中没有匹配到合适产品,在该险种下标注"暂无合适产品推荐,请咨询相关保险公司"
3. 三套方案的总年保费不得超过客户月预算 x 12
4. 基础方案侧重核心保障,保费最低;均衡方案保障适中;全面方案覆盖最广
5. 优先推荐性价比高的产品
6. 每款产品的推荐理由控制在 1-2 句话,突出核心卖点
7. 最后附上免责声明:"以上方案仅供参考,具体保障内容以保险合同条款为准。投保前请仔细阅读产品条款。"
19.6 节点 5:异常处理(Code Node)
输入变量:
| 变量名 | 类型 | 来源 |
|---|---|---|
| is_valid | bool | 节点1输出 |
| error_msg | string | 节点1输出 |
| recommendation | string | 节点4输出(可能为空) |
输出变量:
| 变量名 | 类型 | 说明 |
|---|---|---|
| final_output | string | 最终输出内容 |
| is_success | bool | 是否成功 |
代码逻辑:
def main(is_valid: bool, error_msg: str, recommendation: str) -> dict:
if not is_valid:
return {
"final_output": f"参数校验失败:{error_msg}",
"is_success": False
}
if not recommendation or not recommendation.strip():
return {
"final_output": "未能生成推荐方案,请尝试调整筛选条件后重新提交。",
"is_success": False
}
return {
"final_output": recommendation,
"is_success": True
}
19.7 节点 6:格式化输出(Code Node)
输入变量:
| 变量名 | 类型 | 来源 |
|---|---|---|
| final_output | string | 节点5输出 |
输出变量:
| 变量名 | 类型 | 说明 |
|---|---|---|
| recommendation | string | 格式化后的方案文本 |
代码逻辑:
import re
def main(final_output: str) -> dict:
# 去除多余空行(保留最多2个连续换行)
text = re.sub(r'\n{3,}', '\n\n', final_output)
# 去除首尾空白
text = text.strip()
return {"recommendation": text}
19.8 条件分支逻辑
节点1(参数校验)
├─ is_valid = True → 节点2(检索策略) → 节点3(知识库检索) → 节点4(LLM生成) → 节点5(异常处理) → 节点6(格式化) → 结束
└─ is_valid = False → 节点5(异常处理) → 节点6(格式化) → 结束
19.9 节点失败时的行为
| 失败节点 | 错误处理 | 输出内容 |
|---|---|---|
| 节点1(参数校验) | 直接跳转节点5 | 返回 error_msg |
| 节点2(检索策略) | BaoDan 自动重试1次,失败则跳转节点5 | 返回"检索策略生成失败" |
| 节点3(知识库检索) | BaoDan 自动重试1次,失败则跳转节点5 | 返回"知识库检索失败,请稍后重试" |
| 节点4(LLM生成) | BaoDan 自动重试1次,失败则跳转节点5 | 返回"方案生成失败,请稍后重试" |
| 节点5/6(处理节点) | BaoDan 自动重试1次 | 返回"系统处理异常" |
19.10 测试数据与预期输出
测试用例 1:标准场景
输入:
{
"age": "35",
"gender": "male",
"occupation": "软件工程师",
"annual_income": "300000",
"monthly_budget": "2000",
"insurance_types": "重疾险,医疗险",
"coverage_amount": "500000",
"coverage_period": "终身"
}
预期输出:
- 三套方案(基础/均衡/全面)
- 每套方案含产品表格(产品名/保司/险种/保额/保费/推荐理由)
- 基础方案总年保费 <= 24000 元(2000 x 12)
- 不得包含编造的产品信息
测试用例 2:边界 - 低预算
输入:
{
"age": "25",
"gender": "female",
"occupation": "教师",
"annual_income": "80000",
"monthly_budget": "500",
"insurance_types": "意外险",
"coverage_amount": "50000",
"coverage_period": "10年"
}
预期输出:基础方案总年保费 <= 6000 元
测试用例 3:异常 - 无效参数
输入:
{
"age": "-1",
"gender": "unknown",
"occupation": "",
"annual_income": "0",
"monthly_budget": "0",
"insurance_types": "",
"coverage_amount": "0",
"coverage_period": ""
}
预期输出:返回参数校验失败错误信息
二十、从零到运行部署清单
从一台全新的 Linux 服务器到系统完全可用的完整部署步骤,每一步包含具体命令和验证方法。适用于 Ubuntu 22.04 LTS。
20.1 服务器环境准备
最低配置:4核CPU / 8GB内存 / 100GB SSD / 公网IP
第一步:系统更新
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git vim ufw
第二步:设置时区
sudo timedatectl set-timezone Asia/Shanghai
第三步:配置防火墙
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
sudo ufw status
验证:sudo ufw status 应显示 22、80、443 端口已允许。
第四步:安装 Docker
# 安装 Docker
curl -fsSL https://get.docker.com | sudo bash
# 启动 Docker 并设置开机自启
sudo systemctl start docker
sudo systemctl enable docker
# 验证
docker --version
# 预期输出:Docker version 24.x.x 或更高
第五步:安装 Docker Compose
# 安装 Docker Compose 插件
sudo apt install -y docker-compose-plugin
# 验证
docker compose version
# 预期输出:Docker Compose version v2.x.x
20.2 BaoDan 部署
第一步:克隆 BaoDan 仓库
cd /opt
sudo git clone https://github.com/langgenius/baodan.git
cd /opt/baodan/docker
sudo cp .env.example .env
第二步:修改 BaoDan 环境变量
sudo vim .env
修改以下关键配置:
# 数据库配置
DB_USERNAME=postgres
DB_PASSWORD=your_strong_db_password_here
DB_HOST=db
DB_PORT=5432
DB_DATABASE=baodan
# Redis 配置
REDIS_PASSWORD=your_strong_redis_password_here
# 密钥配置(随机生成,勿用默认值)
SECRET_KEY=随机生成的64位字符串
INIT_PASSWORD=your_admin_password_here
# 外部访问地址
CONSOLE_WEB_URL=https://your-domain.com
SERVICE_API_URL=https://your-domain.com
APP_WEB_URL=https://your-domain.com
生成随机密钥:
openssl rand -base64 42
第三步:启动 BaoDan
sudo docker compose up -d
等待所有容器启动完成(约 2-5 分钟):
sudo docker compose ps
验证:所有容器状态应为 running。
第四步:验证 BaoDan 访问
浏览器打开 http://你的服务器IP:3000,应看到 BaoDan 欢迎页面。
20.3 PostgreSQL 配置
BaoDan 的 Docker Compose 已包含 PostgreSQL。如需在同一实例使用自研表:
第一步:进入 PostgreSQL
sudo docker compose exec db psql -U postgres
第二步:创建自研数据库
CREATE DATABASE insurance_bot OWNER postgres;
\c insurance_bot
第三步:创建自研表结构
-- 企微用户映射表
CREATE TABLE wecom_user_mapping (
id SERIAL PRIMARY KEY,
wecom_userid VARCHAR(64) NOT NULL UNIQUE,
baodan_user_id VARCHAR(64) NOT NULL,
username VARCHAR(128),
department VARCHAR(128),
role VARCHAR(32) DEFAULT 'sales',
status VARCHAR(16) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW(),
last_active_at TIMESTAMP
);
CREATE UNIQUE INDEX idx_wecom_userid ON wecom_user_mapping(wecom_userid);
CREATE INDEX idx_baodan_user_id ON wecom_user_mapping(baodan_user_id);
-- 推荐方案记录表
CREATE TABLE recommendation_records (
id SERIAL PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
customer_name VARCHAR(64),
customer_age SMALLINT,
customer_gender VARCHAR(8),
health_status VARCHAR(32),
occupation VARCHAR(64),
annual_income INTEGER,
monthly_budget INTEGER,
insurance_types TEXT,
coverage_amount INTEGER,
coverage_period VARCHAR(32),
existing_policies TEXT,
generated_plan TEXT,
plan_variants TEXT,
baodan_task_id VARCHAR(64),
status VARCHAR(16) DEFAULT 'pending',
error_message TEXT,
created_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP
);
CREATE INDEX idx_rec_user_id ON recommendation_records(user_id);
CREATE INDEX idx_rec_status ON recommendation_records(status);
CREATE INDEX idx_rec_created_at ON recommendation_records(created_at);
-- 系统操作日志表
CREATE TABLE system_operation_logs (
id SERIAL PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
action VARCHAR(32) NOT NULL,
target_type VARCHAR(32),
target_id VARCHAR(64),
detail JSONB,
ip VARCHAR(45),
user_agent VARCHAR(256),
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_sol_user_id ON system_operation_logs(user_id);
CREATE INDEX idx_sol_action ON system_operation_logs(action);
CREATE INDEX idx_sol_created_at ON system_operation_logs(created_at);
验证:\dt 应显示 3 张表已创建。
20.4 Redis 配置
BaoDan 的 Docker Compose 已包含 Redis。自研后端共用同一个 Redis 实例即可。
验证:
sudo docker compose exec redis redis-cli -a your_redis_password ping
# 预期输出:PONG
20.5 后端部署
第一步:安装 Python 环境
# 安装 Python 3.12
sudo apt install -y python3.12 python3.12-venv python3-pip
# 验证
python3.12 --version
# 预期输出:Python 3.12.x
第二步:创建项目目录并配置虚拟环境
mkdir -p /opt/insurance-bot/backend
cd /opt/insurance-bot/backend
# 创建虚拟环境
python3.12 -m venv venv
source venv/bin/activate
第三步:安装依赖
pip install fastapi uvicorn[standard] psycopg2-binary redis python-jose[cryptography] passlib[bcrypt] python-multipart httpx pydantic
第四步:配置环境变量
cat > /opt/insurance-bot/backend/.env << 'EOF'
# 数据库
DATABASE_URL=postgresql://postgres:your_db_password@db:5432/insurance_bot
# Redis
REDIS_URL=redis://:your_redis_password@redis:6379/0
# JWT
JWT_SECRET_KEY=your_jwt_secret_key_here
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=120
# BaoDan
BAODAN_API_BASE_URL=http://api:5001
BAODAN_CHAT_API_KEY=app-xxxxxxxxxxxx
BAODAN_WORKFLOW_API_KEY=app-yyyyyyyyyyyy
# 企微
WECOM_CORP_ID=ww1234567890abcdef
WECOM_AGENT_ID=1000002
WECOM_AGENT_SECRET=your_agent_secret_here
WECOM_TOKEN=your_callback_token
WECOM_ENCODING_AES_KEY=your_encoding_aes_key
# 服务器
SERVER_HOST=0.0.0.0
SERVER_PORT=8000
EOF
第五步:编写 Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
第六步:启动后端服务
# 如果使用 Docker
sudo docker build -t insurance-bot-backend .
sudo docker run -d \
--name insurance-backend \
--restart always \
--env-file .env \
--network docker_default \
-p 8000:8000 \
insurance-bot-backend
# 或者直接运行
cd /opt/insurance-bot/backend
source venv/bin/activate
uvicorn main:app --host 0.0.0.0 --port 8000
验证:
curl http://localhost:8000/docs
# 预期输出:Flask Swagger UI 页面
20.6 前端部署
第一步:安装 Node.js
# 安装 Node.js 22
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -
sudo apt install -y nodejs
# 验证
node --version # v22.x.x
npm --version # 10.x.x
第二步:构建前端
cd /opt/insurance-bot/frontend
# 安装依赖
npm install
# 构建生产版本
npm run build
# 产物在 dist/ 目录
第三步:配置 Nginx
sudo apt install -y nginx
创建 Nginx 配置文件:
sudo vim /etc/nginx/sites-available/insurance-bot
server {
listen 80;
server_name your-domain.com;
# 重定向到 HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name your-domain.com;
# SSL 证书(参见 20.8)
ssl_certificate /etc/ssl/your-domain.com.pem;
ssl_certificate_key /etc/ssl/your-domain.com.key;
# 安全头
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# 你的前端(Vue 3 产品推荐页面)
location /app {
alias /opt/insurance-bot/frontend/dist;
try_files $uri $uri/ /app/index.html;
}
# 你的后端 API
location /api/ {
proxy_pass http://127.0.0.1:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 120s;
}
# BaoDan 后台 + 对话页面
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# BaoDan API
location /v1/ {
proxy_pass http://127.0.0.1:5001/v1/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
启用配置:
sudo ln -s /etc/nginx/sites-available/insurance-bot /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
验证:sudo nginx -t 应输出 syntax is ok 和 test is successful。
20.7 企微后台配置
参见第十六章《企微应用配置操作手册》完整步骤。关键信息确认清单:
| 配置项 | 获取方式 | 配置位置 |
|---|---|---|
| CorpID | 企微管理后台 → 我的企业 | 后端 .env |
| AgentID | 应用详情页 | 后端 .env |
| AgentSecret | 应用详情 → Secret | 后端 .env |
| Token | 接收消息设置页 | 后端 .env |
| EncodingAESKey | 接收消息设置页 | 后端 .env |
| 回调URL | https://your-domain.com/api/wecom/callback |
企微后台设置 |
| 可信IP | 服务器公网IP | 企微后台设置 |
| OAuth域名 | your-domain.com |
企微管理后台 → 授权登录 |
20.8 域名 + HTTPS 配置
第一步:购买域名并解析
在域名服务商处添加 DNS 记录:
| 记录类型 | 主机记录 | 记录值 |
|---|---|---|
| A | @ | 服务器公网IP |
| A | chat | 服务器公网IP |
| A | api | 服务器公网IP |
第二步:申请 SSL 证书
使用 Let's Encrypt 免费证书:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com -d chat.your-domain.com -d api.your-domain.com
按照提示输入邮箱并同意条款。Certbot 会自动修改 Nginx 配置并安装证书。
第三步:设置自动续期
sudo crontab -e
# 添加以下行(每天凌晨2点检查续期)
0 2 * * * certbot renew --quiet --post-hook "systemctl reload nginx"
验证:浏览器访问 https://your-domain.com,应显示安全锁标志。
20.9 验证清单
| 步骤 | 验证命令/操作 | 预期结果 | 通过标准 |
|---|---|---|---|
| 1. Docker 运行 | docker compose ps |
所有容器 running | 无 exited 容器 |
| 2. PostgreSQL 连接 | docker compose exec db psql -U postgres -c '\l' |
显示 baodan 和 insurance_bot 数据库 | 两个库都存在 |
| 3. Redis 连接 | docker compose exec redis redis-cli ping |
PONG | 返回 PONG |
| 4. BaoDan 后台访问 | 浏览器打开 http://IP:3000 |
BaoDan 欢迎页面 | 页面正常加载 |
| 5. BaoDan 管理员登录 | 使用 15.1 设置的账号登录 | 进入 BaoDan 后台 | 登录成功 |
| 6. DeepSeek 模型配置 | BaoDan 设置 → 模型供应商 | 显示 DeepSeek 且状态正常 | 测试连通成功 |
| 7. 知识库文档 | BaoDan 知识库页面 | 文档状态为"可用" | 无 processing/failed |
| 8. 对话功能 | BaoDan 预览对话 | 回答基于知识库内容 | 回答准确 |
| 9. 后端 API | curl http://localhost:8000/docs |
Swagger UI 页面 | 页面正常加载 |
| 10. 前端页面 | 浏览器打开 https://your-domain.com/app |
Vue 应用页面 | 页面正常加载 |
| 11. HTTPS | 浏览器访问 https://your-domain.com |
安全锁标志 | 证书有效 |
| 12. 企微机器人 | 在企微中发送消息给机器人 | 收到 AI 回复 | 回复在 10 秒内 |
| 13. 企微 OAuth | 在企微内置浏览器访问登录页 | 跳转授权后登录成功 | 进入系统主页 |
| 14. 产品推荐 | 填写表单并提交 | 生成三套方案 | 方案内容合理 |
| 15. 权限验证 | 用销售人员账号访问管理页面 | 被拒绝访问 | 显示权限不足提示 |
二十一、角色-功能权限矩阵
21.1 页面访问权限
| 页面 | 超级管理员 | 管理员 | 销售主管 | 销售人员 | 客户 |
|---|---|---|---|---|---|
| /login | Y | Y | Y | Y | Y |
| /chat | Y | Y | Y | Y | Y |
| /recommend | Y | Y | Y | Y | Y |
| /recommend/history | Y | Y | Y | Y | Y |
| /admin/knowledge-base | Y | Y | - | - | - |
| /admin/logs/chat | Y | Y | 仅本组 | 仅自己 | - |
| /admin/logs/system | Y | - | - | - | - |
| /admin/users | Y | - | - | - | - |
| /admin/roles | Y | - | - | - | - |
| /admin/llm-configs | Y | - | - | - | - |
| /admin/prompts | Y | Y | - | - | - |
| /admin/stats | Y | Y | 仅本组 | - | - |
21.2 数据权限规则
| 角色 | 可见数据范围 |
|---|---|
| 超级管理员 | 全部数据 |
| 管理员 | 本部门数据 |
| 销售主管 | 本组数据 |
| 销售人员 | 仅自己的数据 |
| 客户 | 仅自己的对话 |
二十二、端到端业务流程
[开始]
v
[1. 用户登录]
| 企微 OAuth / 账密登录
| -> 生成 JWT Token
v
[2. 进入对话页]
| BaoDan WebApp 加载(iframe 嵌入)
| 显示会话列表(历史对话)
v
[3. 选择检索范围](可选)
| 选择险种筛选器(重疾/寿险/医疗...)
| 选择保司筛选器(XX人寿/太平洋...)
v
[4. 用户输入问题]
| 回车发送 / 点击发送按钮
| 前端校验:非空、长度限制
v
[5. 发送请求]
| -> BaoDan Chat API(SSE 流式)
| -> BaoDan 执行 RAG 检索(知识库 -> 向量搜索 -> Top-K 段落)
| -> LLM 基于检索结果生成回答
v
[6. 流式展示回答]
| 逐字打印 AI 回复
| 显示引用来源文档
v
[7. 用户评价]
| 点赞/点踩 -> BaoDan 记录
| 或纠错 -> 弹窗输入 -> 提交到后端
v
[结束]
14.2 产品推荐流程(用户端)
[开始]
v
[1. 进入产品推荐页]
| 显示空白表单
v
[2. 填写客户信息]
| 基础信息(姓名/年龄/性别/健康/职业)
| 选择关注险种(多选 -> 动态显示对应字段)
| 设置保障需求(保额滑块/预算输入)
| 选择保障期限
| (可选)录入已有保单
v
[3. 前端校验]
| 必填项检查、数值范围检查
| 不通过 -> 红色提示
| 通过 -> 继续
v
[4. 点击"生成方案"]
| 前端显示 loading + 进度提示
| -> POST /api/recommend/generate
| -> 后端调 BaoDan Workflow API
v
[5. BaoDan Workflow 执行]
| 参数校验 -> 知识库检索 -> LLM 生成方案
| 返回 2-3 套方案(基础/均衡/全面)
v
[6. 方案展示]
| Markdown 渲染方案报告
| 产品对比表格 + 推荐理由
v
[7. 用户操作]
| a. 满意 -> 打印导出 / 分享链接
| b. 不满意 -> 重新生成
| c. 需修改 -> 进入编辑模式
v
[8. 保存/导出]
| 方案自动保存到数据库
| 用户可选择浏览器打印为 PDF
v
[结束]
14.3 知识库文档入库流程(管理员端)
[开始]
v
[1. 管理员进入知识库管理页]
| 查看当前文档列表和状态
v
[2. 上传文档]
| 选择文件(MD/Word/TXT)
| 选择分类标签(险种/保司)
| 可多文件批量上传
| -> POST /kb/documents/upload
v
[3. 异步处理流水线]
| a. 格式转换(Word -> 纯文本)
| b. 文档分段(按 Markdown 标题切分,800 tokens/段)
| c. 向量化(DeepSeek Embedding 调用)
| d. 写入向量数据库
v
[4. 处理完成]
| 文档状态 -> completed
| 文档可被检索
v
[5. 异常处理](如失败)
| 文档状态 -> failed
| 显示错误原因
| 管理员可点击"重试"
v
[结束]
14.4 企微机器人问答流程
[用户在企微中操作]
v
[1. 发送消息]
| 单聊:直接发消息给机器人
| 群聊:@机器人 + 问题内容
v
[2. 企微服务器推送]
| -> POST /api/wecom/callback
| 加密 XML 消息体
v
[3. 后端接收]
| a. 验证签名
| b. 解密消息
| c. 立即返回 "success"(<5秒)
v
[4. 异步处理](Background Task)
| a. 解析消息类型
| - 文本 -> 继续
| - 非文本 -> 回复"暂不支持"
| b. 提取问题内容
| - 群聊:去掉"@机器人"前缀
| c. 调用 BaoDan Chat API
| d. 等待 AI 回复
v
[5. 回复消息]
| 单聊 -> wecom send message API
| 群聊 -> wecom appchat send API
| 超长消息 -> 自动分段
v
[结束]
二十三、状态机定义
15.1 推荐方案状态机
+----------+
| pending | <-- 用户提交请求
+----+-----+
| Workflow 开始执行
v
+----------+
|processing| <-- BaoDan Workflow 运行中
+----+-----+
+--------+--------+
||
v v
+--------+ +--------+
| done | | failed | <-- 超时或错误
+--------+ +--------+
|||
| | 用户点击"重新生成"
+--------+--------+
v
+----------+
| pending | <-- 重新提交
+----------+
状态转换规则:
| 当前状态 | 触发条件 | 目标状态 | 操作 |
|---|---|---|---|
| pending | Workflow 开始执行 | processing | 开始异步任务 |
| processing | Workflow 返回成功 | done | 保存 generated_plan |
| processing | Workflow 返回失败 | failed | 记录 error_message |
| processing | 超时(>120秒) | failed | 记录 "生成超时" |
| failed | 用户点击重新生成 | pending | 重置状态,重新提交 |
| done | 用户点击重新生成 | pending | 重置状态,重新提交 |
15.2 知识库文档处理状态机
+------------+
| uploaded | <-- 文件上传完成
+-----+------+
v
+------------+
| converting | <-- 格式转换中(Word->文本)
+-----+------+
v
+------------+
| chunking | <-- 文档分段中
+-----+------+
v
+------------+
| embedding | <-- 向量化中(调 DeepSeek Embedding)
+-----+------+
+--------+--------+
||
v v
+----------+ +--------+
|completed | | failed | <-- 任一步骤出错
+----------+ +--------+
|||
| | 管理员点击"重试"
+--------+--------+
v
+------------+
| uploaded | <-- 回到起点
+------------+
状态转换规则:
| 当前状态 | 触发条件 | 目标状态 | 操作 |
|---|---|---|---|
| uploaded | 开始处理 | converting | 调用格式转换 |
| converting | 转换成功 | chunking | 开始分段 |
| chunking | 分段完成 | embedding | 开始向量化 |
| embedding | 向量化完成 | completed | 文档可被检索 |
| 任一环节出错 | 异常 | failed | 记录错误原因 |
| failed | 点击"重试" | uploaded | 重新开始处理 |
15.3 企微消息处理状态机
+-----------+
| received | <-- 收到企微推送
+-----+-----+
v
+-----------+
| decrypting| <-- 解密消息
+-----+-----+
+-----+-----+
||
v v
+---------+ +----------+
| success | | error | <-- 解密失败
+---------+ +----------+
v
+-----------+
| processing| <-- 异步调用 BaoDan
+-----+-----+
+----+----+
||
v v
+------+ +--------+
|done||failed|
+------+ +--------+
|||
v v
+---------+ +----------+
| replied | | no_reply | <-- 回复失败时记录日志
+---------+ +----------+
二十四、数据安全与合规
16.1 敏感数据分类
| 数据类型 | 敏感级别 | 包含字段 | 保护措施 |
|---|---|---|---|
| 客户个人信息 | 高 | 姓名、年龄、健康状况、收入 | 不在日志中明文记录;数据库加密存储 |
| 企微用户信息 | 中 | userid、姓名、部门 | 仅在后端使用,不暴露给前端 |
| API Key | 高 | DeepSeek Key、企微 Secret | AES 加密存储,前端脱敏显示 |
| 对话内容 | 中 | 用户问题、AI 回答 | 存储在 BaoDan 数据库,有访问权限控制 |
| JWT Token | 高 | 用户认证令牌 | HTTPS 传输,2 小时过期 |
16.2 数据保护规则
| 规则 | 说明 | 实现方式 | |
|---|---|---|---|
| 传输加密 | 所有数据传输走 HTTPS | Nginx 配置 SSL 证书 | |
| 存储加密 | API Key 等敏感配置加密 | AES-256 加密后入库 | |
| 日志脱敏 | 日志中不记录完整敏感信息 | 日志截断:问题/回答记录前 50 字符 | |
| 前端脱敏 | API Key 在页面上显示为 sk-xxxx****xxxx |
后端返回时自动脱敏 | |
| 访问控制 | 不同角色只能访问授权数据 | 接口层权限校验 | |
| 数据隔离 | 销售人员只能看自己的数据 | 查询时自动注入 user_id 过滤条件 |
16.3 合规要求
| 要求 | 说明 |
|---|---|
| 个人信息保护法 | 收集客户个人信息需告知目的,最小必要原则 |
| 保险行业监管 | AI 推荐方案需附免责声明,不可替代专业建议 |
| 对话记录留存 | 保险行业建议保留至少 10 年对话记录 |
| 知识库版本溯源 | 修改知识库需保留版本记录,可追溯 |
二十五、备份与恢复策略
17.1 备份范围
| 备份对象 | 存储位置 | 重要性 | 备份频率 |
|---|---|---|---|
| PostgreSQL 数据库 | 远程存储 | 高 | 每日凌晨 2 点 |
| BaoDan 向量数据库 | 远程存储 | 高 | 每日凌晨 2 点 |
| 知识库原始文件(9GB MD) | 远程存储 | 高 | 每周一次(数据变化少) |
| 用户上传的文件 | 本地 + 远程 | 中 | 每日增量 |
| 系统配置文件 | Git 仓库 | 中 | 代码提交即备份 |
| 日志文件 | 本地 | 低 | 保留 90 天后自动清理 |
17.2 恢复指标
| 指标 | 目标值 | 说明 | |
|---|---|---|---|
| RPO(恢复点目标) | < 24 小时 | 最多丢失一天的数据 | |
| RTO(恢复时间目标) | < 2 小时 | 从故障到服务恢复的时间 | |
| 备份验证 | 每月一次 | 恢复到测试环境验证备份可用 |
17.3 恢复流程
[数据库恢复]
1. 从备份存储下载最近的数据库备份
2. 停止 BaoDan 服务
3. 恢复 PostgreSQL 数据库
4. 启动 BaoDan 服务
5. 验证知识库检索正常
6. 通知管理员恢复完成
[全量恢复]
1. 重新部署 BaoDan(Docker 或源码)
2. 恢复 PostgreSQL 数据库
3. 恢复知识库文件
4. 重新启动所有服务
5. 全面功能验证
二十六、部署架构
18.1 服务器拓扑
[互联网]
[域名 + HTTPS]
+------v------+
| Nginx | 端口: 80/443
|反向代理|
+------+------+
+--------------+--------------+
|||
+------v------+ +----v----+ +-------v-------+
|BaoDan Web||BaoDan||你的前端|
|(Next.js)||API||(Vue 3)|
|端口:3000||端口||端口:3000|
+-------------+ | :5001 | +---------------+
+---------+
||||
+------v--------------v--------------v------+
|PostgreSQL||
|端口: 5432|
|(BaoDan 数据 + 你的自研表)|
+---------------------------------------------+
|||
+------v------+ +----v----+ +-------v-------+
|Redis||Weaviate||Celery Worker|
|端口:6379||端口||(后台任务)|
+-------------+ | :8080 | +---------------+
+---------+
+-------------------+
|BaoDan 服务(含 insurance 模块)|||||
|端口: 8000|
|(企微回调/推荐接口)|
+-------------------+
18.2 端口规划
| 服务 | 端口 | 对外暴露 | 说明 |
|---|---|---|---|
| Nginx | 80, 443 | 是 | HTTPS 入口 |
| BaoDan Web | 3000 | 否(Nginx 转发) | 管理后台 + 对话 WebApp |
| BaoDan API | 5001 | 否(内部) | BaoDan 核心 API |
| BaoDan 服务(含 insurance 模块) | 5001 | 是(Nginx 转发) | 企微回调 + 推荐接口 |
| PostgreSQL | 5432 | 否(内部) | 数据库 |
| Redis | 6379 | 否(内部) | 缓存 |
| Weaviate | 8080 | 否(内部) | 向量数据库 |
| 你的 Vue 前端 | 5173 | 否(Nginx 转发) | 产品推荐页面 |
18.3 Nginx 配置要点
# HTTPS 证书
ssl_certificate /etc/ssl/your-domain.com.pem;
ssl_certificate_key /etc/ssl/your-domain.com.key;
# BaoDan 后台 + 对话页面
location / {
proxy_pass http://127.0.0.1:3000;
}
# BaoDan API
location /v1/ {
proxy_pass http://127.0.0.1:5001/v1/;
}
# 你的前端(产品推荐页面等)
location /app/ {
proxy_pass http://127.0.0.1:5173/;
}
# 你的后端 API(企微回调 + 推荐接口)
location /api/ {
proxy_pass http://127.0.0.1:8000/api/;
}
# 企微回调路径(需要较长超时)
location /api/wecom/callback {
proxy_pass http://127.0.0.1:8000/api/wecom/callback;
proxy_read_timeout 120s; # 企微回调需要较长超时
}
18.4 域名规划
| 域名/路径 | 指向 | 说明 |
|---|---|---|
| chat.your-domain.com | Nginx -> BaoDan Web | 对话页面入口 |
| chat.your-domain.com/admin | Nginx -> BaoDan Web | 管理后台入口 |
| chat.your-domain.com/app | Nginx -> Vue 前端 | 产品推荐页面 |
| chat.your-domain.com/api/wecom/callback | Nginx -> BaoDan | 企微机器人回调 URL |
| api.your-domain.com | Nginx -> BaoDan | 后端 API 入口 |
二十七、BaoDan 功能映射表
每项需求对应 BaoDan 的哪个功能,开发时可直接对照。
| 需求编号 | 功能点 | BaoDan 对应功能 | 配置位置 |
|---|---|---|---|
| 1.1.1 | 文字输入框 | WebApp 内置输入框 | 默认 |
| 1.1.2 | 流式输出 | WebApp Streaming 模式 | 应用设置 -> 对话速度 -> 流式 |
| 1.1.3 | 多轮对话上下文 | WebApp 会话管理 | 应用设置 -> 上下文窗口长度 |
| 1.1.4 | 会话管理 | WebApp 侧边栏 | 默认 |
| 1.1.5 | 会话标题自动命名 | WebApp 自动标题 | 应用设置 -> 自动话题转换 |
| 1.2.1 | Markdown 渲染 | WebApp 内置 Markdown 渲染 | 默认 |
| 1.2.3 | 来源引用标注 | WebApp 引用显示 | 应用设置 -> 引用与归属 |
| 1.3.1-3 | 检索范围控制 | 知识库选择器 | 多知识库配置 |
| 1.4.1 | 点赞/点踩 | WebApp 反馈功能 | 应用设置 -> 对话反馈 |
| 3.1.1 | 批量上传文档 | 知识库上传 | 知识库 -> 上传文档 |
| 3.1.2 | 文档列表 | 知识库文档管理 | 知识库 -> 文档列表 |
| 3.2.1 | 处理流水线状态 | 文档处理状态 | 知识库 -> 文档状态 |
| 3.2.2 | 失败原因与重试 | 文档错误详情 | 知识库 -> 文档详情 |
| 3.4.1 | 检索效果测试 | 召回测试 | 知识库 -> 召回测试 |
| 4.1.1 | 对话记录查看 | 对话日志 | 日志 -> 对话日志 |
| 6.1.1 | 多模型配置 | 模型供应商设置 | 设置 -> 模型供应商 |
| 6.2.1 | Prompt 编辑 | 提示词编排 | 应用 -> 提示词编排 |
| 2.2.1 | AI 匹配产品组合 | Workflow 编排 | 应用 -> 工作室 -> 创建工作流 |
二十八、全局错误码定义
19.1 统一响应格式
所有自研后端接口返回统一 JSON 格式:
{
"code": 0,
"message": "success",
"data": { ... }
}
19.2 错误码表
| 错误码 | 说明 | HTTP 状态码 | 处理建议 |
|---|---|---|---|
| 0 | 成功 | 200 | - |
| 1001 | 参数错误(缺少必填项/类型错误) | 200 | 检查请求参数 |
| 1002 | 未授权(无 Token 或 Token 无效) | 200 | 重新登录 |
| 1003 | Token 已过期 | 200 | 调用 refresh-token 接口 |
| 1004 | 权限不足(角色无权访问) | 200 | 联系管理员提升权限 |
| 1005 | 资源不存在(如用户/方案/文档 ID 不存在) | 200 | 检查资源 ID |
| 2001 | BaoDan API 调用失败 | 200 | 检查 BaoDan 服务状态和 API Key |
| 2002 | BaoDan API 调用超时(>60 秒) | 200 | 重试或检查 BaoDan 服务负载 |
| 2003 | BaoDan Workflow 执行失败 | 200 | 检查 Workflow 配置 |
| 2004 | BaoDan Workflow 执行超时(>120 秒) | 200 | 简化输入参数或检查 Workflow |
| 3001 | 企微 API 调用失败 | 200 | 检查企微 CorpID/Secret 配置 |
| 3002 | 企微 Access Token 获取失败 | 200 | 检查网络和企微配置 |
| 3003 | 企微消息回复失败 | 200 | 检查 AgentID 和用户权限 |
| 3004 | 企微 OAuth code 已过期 | 200 | 让用户重新授权 |
| 3005 | 企微用户未授权/不在白名单 | 200 | 联系管理员开通权限 |
| 4001 | 文件上传失败 | 200 | 检查文件格式和大小 |
| 4002 | 文件格式不支持 | 200 | 仅支持 MD/Word/TXT |
| 4003 | 文件大小超限 | 200 | 单文件最大 50MB |
| 5001 | 数据库操作失败 | 200 | 检查数据库连接 |
| 5002 | Redis 操作失败 | 200 | 检查 Redis 连接 |
| 9999 | 服务器内部错误 | 500 | 检查服务器日志 |
19.3 说明
-
业务异常(1xxx-5xxx)统一使用 HTTP 200,通过
code字段区分成功/失败 -
系统异常(9xxx)使用对应 HTTP 状态码
-
message字段为人类可读的错误描述,前端直接展示给用户 -
生产环境不返回详细的错误堆栈,只在服务端日志中记录
二十九、页面归属与构建方式
明确每个页面由谁提供,开发者知道哪些需要自己写代码,哪些直接用。
20.1 页面归属清单
|页面|路径|提供方|构建方式|说明|
|------|------|:---:|---------|------|--------
|登录页|/login|自研|Vue 3 组件|企微 OAuth 跳转 + 账密登录表单| |对话页|/chat|BaoDan WebApp|iframe 嵌入|直接嵌入 BaoDan 的 Chat WebApp| |产品推荐页|/recommend|自研|Vue 3 页面|表单 + 结果展示,调后端 API| |方案历史页|/recommend/history|自研|Vue 3 页面|方案列表 + 筛选,调后端 API| |知识库管理页|/admin/kb|BaoDan 后台|BaoDan 原生|直接用 BaoDan 知识库管理界面| |对话日志页|/admin/logs/chat|BaoDan 后台|BaoDan 原生|直接用 BaoDan 日志界面| |系统日志页|/admin/logs/system|自研|Vue 3 页面|BaoDan 没有系统操作日志| |用户管理页|/admin/users|自研|Vue 3 页面|企微用户绑定 + 角色分配| |角色管理页|/admin/roles|自研|Vue 3 页面|权限树勾选配置| |LLM 配置页|/admin/llm-configs|BaoDan 后台|BaoDan 原生|直接用 BaoDan 模型配置| |Prompt 管理页|/admin/prompts|BaoDan 后台|BaoDan 原生|直接用 BaoDan 提示词编辑| |数据统计页|/admin/stats|自研|Vue 3 页面|图表展示(ECharts/Chart.js)| |管理后台框架|/admin|自研|Vue 3 组件|左侧导航 + 顶部栏 + 路由|
20.2 统计
| 类型 | 页面数 | 说明 | ||
|---|---|---|---|---|
| BaoDan 直接提供 | 4 个 | 对话页、知识库管理、对话日志、LLM 配置、Prompt 管理 | ||
| 自研前端 | 9 个 | 登录页、推荐页、历史页、系统日志、用户管理、角色管理、统计页、管理框架 | ||
| 合计 | 13 个 | - |
20.3 BaoDan 页面嵌入方式
| BaoDan 页面 | 嵌入方式 | URL 格式 |
|---|---|---|
| 对话 WebApp | iframe | http://baodan:3000/chat/{app_id}?user={user_id} |
| 知识库管理 | 新窗口打开 BaoDan 后台 | http://baodan:3000/datasets |
| 对话日志 | 新窗口打开 BaoDan 后台 | http://baodan:3000/logs |
| LLM 配置 | 新窗口打开 BaoDan 后台 | http://baodan:3000/settings/model |
| Prompt 管理 | 新窗口打开 BaoDan 后台 | http://baodan:3000/apps/{app_id}/prompt-engine |
说明:知识库管理、日志、LLM 配置、Prompt 管理这 4 个页面目前通过新窗口跳转到 BaoDan 后台操作。如果后续需要深度定制(如加标签管理、编号规则),需要改为自研前端 + 调 BaoDan API。
三十、BaoDan Workflow 节点详细设计
产品推荐方案的 BaoDan Workflow 每个节点的输入、输出、Prompt 定义。
21.1 Workflow 总览
[开始] -> [节点1:参数校验] -> [节点2:检索策略] -> [节点3:知识库检索]
-> [节点4:LLM方案生成] -> [节点5:格式化输出] -> [结束]
21.2 各节点定义
节点 1:参数校验
| 属性 | 值 |
|---|---|
| 节点类型 | 代码节点(Code) |
| 输入 | 用户提交的 JSON 参数 |
| 输出 | validated_params(校验后的参数)或 error_msg(错误信息) |
校验规则:
-
age: 必填,整数,1-150
-
gender: 必填,"male" 或 "female"
-
occupation: 必填,非空字符串
-
insurance_types: 必填,非空数组
-
monthly_budget: 必填,正整数
-
coverage_amount: 必填,正整数
节点 2:检索策略
| 属性 | 值 |
|---|---|
| 节点类型 | 代码节点(Code) |
| 输入 | validated_params |
| 输出 | search_queries(检索关键词列表) |
逻辑:根据用户选择的险种,为每个险种生成一条检索 query:
def generate_search_queries(params):
queries = []
for insurance_type in params["insurance_types"]:
query = f"{insurance_type} 产品条款 保额 费率 {params['age']}岁"
queries.append({"险种": insurance_type, "query": query})
return queries
节点 3:知识库检索
| 属性 | 值 |
|---|---|
| 节点类型 | 知识库检索节点(Knowledge Retrieval) |
| 输入 | search_queries |
| 输出 | retrieved_docs(检索到的文档片段) |
| 知识库 | 关联所有险种相关知识库 |
| Top-K | 每个险种检索 Top 5 |
| 相似度阈值 | 0.6 |
节点 4:LLM 方案生成
| 属性 | 值 |
|---|---|
| 节点类型 | LLM 节点 |
| 输入 | retrieved_docs + validated_params |
| 输出 | recommendation(Markdown 格式方案) |
| 模型 | deepseek-chat |
| Temperature | 0.7 |
节点 Prompt:
你是一名专业的保险方案规划师。根据以下客户信息和检索到的产品条款,生成保险产品推荐方案。
## 客户信息
- 年龄:{{age}} 岁
- 性别:{{gender}}
- 职业:{{occupation}}
- 年收入:{{annual_income}} 元
- 月预算:{{monthly_budget}} 元
- 关注险种:{{insurance_types}}
- 保额目标:{{coverage_amount}} 元
- 保障期限:{{coverage_period}}
## 检索到的产品信息
{{retrieved_docs}}
## 输出要求
请生成三套方案(基础方案 / 均衡方案 / 全面方案),每套方案包含:
1. 方案名称和总年保费
2. 每款推荐产品的表格:
|产品名称|所属保司|险种|保额|年保费|推荐理由(1-2句话)|
3. 方案总结(2-3句话说明方案特点)
4. 免责声明:"以上方案仅供参考,具体保障内容以保险合同条款为准。"
## 注意事项
- 保费数据必须来自检索到的产品信息,不可编造
- 如果某险种在知识库中没有匹配产品,标注"暂无合适产品推荐"
- 总保费不得超过客户月预算 * 12
- 优先推荐性价比高的产品
节点 5:格式化输出
| 属性 | 值 | ||||
|---|---|---|---|---|---|
| 节点类型 | 代码节点(Code) | ||||
| 输入 | recommendation(LLM 输出的 Markdown) |
逻辑:对 LLM 输出做基本格式清理(去除多余空行、确保 Markdown 格式正确),直接输出给前端渲染。
|模块|总数|高优先级|中优先级|低优先级|
|------|:---:|:---:|:---:|:---:|
|M1 智能问答(用户前端)|16|9|5|2| |M2 产品推荐(用户前端)|19|9|7|3| |M3 知识库管理(管理前端)|17|9|5|3| |M4 留痕与日志(管理前端)|9|4|4|1| |M5 用户与权限(管理前端)|8|5|2|1| |M6 系统配置(管理前端)|14|6|5|3| |M7 数据统计(管理前端)|9|3|4|2| |企微机器人|5|3|1|1| |A1 认证鉴权(后端接口)|4|2|2|0| |A2 智能问答(后端接口)|8|5|2|1| |A3 方案生成(后端接口)|4|3|0|1| |A4 知识库(后端接口)|10|7|2|1| |A5 用户权限(后端接口)|3|2|0|1| |A6 系统配置(后端接口)|4|2|2|0| |A7 留痕日志(后端接口)|3|2|1|0| |A8 统计报表(后端接口)|4|1|3|0| |合计|137|77|45|15|
三十一、功能清单统计
|模块|功能项数|高优先级|中优先级|低优先级|
|------|:---:|:---:|:---:|:---:|
|M1 智能问答(用户前端)|16|9|5|2| |M2 产品推荐(用户前端)|19|9|7|3| |M3 知识库管理(管理前端)|17|9|5|3| |M4 留痕与日志(管理前端)|9|4|4|1| |M5 用户与权限(管理前端)|8|5|2|1| |M6 系统配置(管理前端)|14|6|5|3| |M7 数据统计(管理前端)|9|3|4|2| |企微机器人|5|3|1|1| |A1 认证鉴权(后端接口)|4|2|2|0| |A2 智能问答(后端接口)|8|5|2|1| |A3 方案生成(后端接口)|4|3|0|1| |A4 知识库(后端接口)|10|7|2|1| |A5 用户权限(后端接口)|3|2|0|1| |A6 系统配置(后端接口)|4|2|2|0| |A7 留痕日志(后端接口)|3|2|1|0| |A8 统计报表(后端接口)|4|1|3|0| |合计|137|77|45|15|
三十二、验收标准
| 验收项 | 标准 | 验证方式 | ||
|---|---|---|---|---|
| 智能问答准确率 | 30 个测试问题,准确率 >= 85% | 人工测试 | ||
| 产品推荐质量 | 10 组客户数据,方案合理且产品信息准确 | 人工评审 | ||
| 企微单聊 | 10 轮连续对话无错误 | 手动测试 | ||
| 企微群聊 | 3 人同时 @机器人 不混淆 | 手动测试 | ||
| 企微 OAuth | 登录流程完整走通 | 手动测试 | ||
| H5 适配 | 手机端可正常使用 | 真机测试 | ||
| 知识库覆盖 | 9GB MD 文档全部入库且可检索 | BaoDan 后台检查 | ||
| 响应时间 | AI 回答 < 15 秒 | 计时测试 | ||
| 并发能力 | 5 人同时使用无明显延迟 | 手动测试 | ||
| 方案导出 | PDF/PPT/Word 可正常下载且排版正确 | 下载验证 | ||
| 角色权限 | 不同角色看到不同功能和数据范围 | 切换账号验证 | ||
| 日志完整性 | 问答记录和操作日志可查询导出 | 后台验证 |