# 柏悦智能物流客服系统 基于 Flask + DeepSeek 的中欧/英国跨境物流智能客服,支持多轮对话、运费试算、运单查询、历史会话管理。 ## 功能 - **多轮上下文**:能理解"那空运呢""这个多少钱"这类追问 - **知识库优先**:优先使用本地知识库回答,减少模型编造 - **追问兜底**:当前问题没命中关键词时,用上一句提问去检索 - **运费试算**:识别重量/尺寸/渠道,按报价表阶梯单价计算并展示完整明细 - **订单查询**:直接输入入仓码查询订单与已有轨迹,卡片主显示入仓码;兼容订单号与 Shipment ID,展示概要、申报重量/体积和业务状态历史,不生成模拟运输轨迹 - **历史会话**:会话保存到 SQLite,支持打开、搜索、删除 - **交互**:停止生成、复制(纯文本)、重答、多行输入、回到底部 - **中英文切换**:右上角「中/EN」按钮或快捷键 `Ctrl + L` - **异常处理**:接口超时、Key 失效、中断时给出明确提示与重试 - **会话恢复**:连接中断保留已收到的正文;切换话题取消旧附件,等待建会话时保留新草稿;历史加载失败保留已有列表 - **深色模式**:右上角切换,偏好本地保存 - **响应式**:窄屏下侧栏自动变为抽屉 - **附件预览**:发送前查看文件名或缩略图,可移除待发送附件 - **客服工作台**:默认展示会话列表与全高聊天区,统计收在「数据概览」,服务资料按需展开;支持会话搜索、多行回复和切换会话保留草稿,常用回复先填入再发送 - **手机客服接待**:会话列表与聊天整屏切换,返回列表保留草稿;回复框固定在聊天底部,可从列表或聊天打开服务资料 ## 报价数据 代码标注的报价来源为 **炜易达 VIP 报价表(2026-10-15 9:00 执行版)**,覆盖 8 条渠道。以下逐渠道列出代码中的重量档位;按计费重量选择不大于该重量的最高档位: 报价试算需先确认目的国家或地区;未识别到目的地时先补充信息。英国报价及其连续追问使用英国费率,“海运呢”“空运呢”等短句和重试保留原有条件。明确提到美国等当前未覆盖的目的地时交由人工确认;英国按方报价也不会套用欧洲仓价。没有匹配的国家或仓点时不自动回退到其他仓点。 | 渠道 | 重量档位 → 基础单价(元/kg) | 参考时效 | |---|---|---| | 欧洲海运快线 | 21KG+ → 12.3;101KG+ → 10.3;500KG+ → 10.2;2000KG+ → 10.0 | 开船后 38-45 天 | | 义乌中欧铁路 | 21KG+ → 15.5;100KG+ → 13.5;1000KG+ → 13.5 | 发车后 36-40 天 | | 中欧卡航 | 21KG+ → 19.4;101KG+ → 17.4;500KG+ → 17.4;2000KG+ → 17.3;4000KG+ → 17.3 | 发车后 16-20 天 | | 欧洲空运 | 21KG+ → 39;45KG+ → 38;71KG+ → 37;101KG+ → 36 | 10-13 天 | | 英国海运 | 21KG+ → 12.8;101KG+ → 10.9;301KG+ → 10.9 | 约 40 天 | | 英国铁路 | 21KG+ → 18.0;101KG+ → 16.2;301KG+ → 16.2 | 25-30 天 | | 中英卡航 | 21KG+ → 21.4;101KG+ → 19.4;301KG+ → 19.4 | 20-22 天 | | 英国空运 | 22KG+ → 48;51KG+ → 44;101KG+ → 42 | 8-10 天 | **计费规则**(均来自报价表): - 运费 = 阶梯单价 × 计费重量,按重量落入的档位取价 - 计费重量 = 实重与材积重取大,材积 = 长×宽×高(cm) ÷ 6000(1CBM = 167KG) - 最低起收档 21KG(英国空运 22KG) - 铁路、卡航单件计费重不足 13KG 加收超轻费 5 元/kg;空运单件不足 12KG 按 12KG 计费 - 偏远地址 +7 元/kg,单票最低 250 元 基础报价维护在 `logistics_chat/pricing.py` 的 `RATES` 字典中;修改价格时同步知识资料,避免调整计算逻辑。 `calc_freight()` 的 `total` 当前仅为基础运费。超轻费、偏远费及产品附加费以说明或单独结果展示,尚未统一计入 `total`;上述业务规则不能理解为已全部自动结算。 ### 扩展数据(logistics_chat/rates_extra.py) 从同一份报价表提取、目前已支持: - **海外仓卡派按方计费**:0.5CBM+ / 1CBM+ 两档,按提问里的国家自动匹配仓点分组 - **产品附加费**:47 类产品(手机 5 元/kg、电池 1-2 元/kg、玩具 2 元/kg 等) - **DPD 岛屿偏远判断**:12 个国家的岛屿/偏远邮编,命中即提示 35 欧/件 用法示例: | 提问 | 效果 | |---|---| | 2方 海运 到德国海外仓多少钱 | 按 1CBM+ 档计算并列出德国仓点分组 | | 寄手机到德国要加什么费用 | 给出 5 元/kg 附加费 | | 邮编07000 算偏远吗 | 命中意大利撒丁岛,提示 35 欧/件 | > ⚠️ 报价表含商业价格与联系人信息,公开发布前请脱敏或替换为示例数据。 ## 运行环境与启动 项目使用 Python、Flask、SQLite 和 DeepSeek API,无需前端构建。建议使用独立虚拟环境;所有应用启动命令均在项目根目录执行。 ### Windows ```powershell py -3 -m venv .venv .\.venv\Scripts\python.exe -m pip install -r requirements.txt Copy-Item .env.example .env # 仅首次运行;已有 .env 时跳过 # 编辑 .env,填写 DEEPSEEK_API_KEY 后再启动 .\.venv\Scripts\python.exe am.py ``` 上述命令固定使用项目 `.venv`,不受其他已激活环境影响。可选的一键入口 `start.bat` 按已激活的 `VIRTUAL_ENV`、项目 `.venv`、`py -3`、PATH 中的 `python` 顺序逐个验证 Python 3。使用它之前先退出不需要的旧环境,或检查诊断结果是否指向预期解释器。`启动.bat` 调用同一入口: ```powershell .\start.bat ``` 仅检查并打印选中的解释器,不安装依赖或启动服务: ```powershell .\start.bat --check-python ``` 浏览器访问 http://127.0.0.1:5000;客服登录页为 http://127.0.0.1:5000/admin。 ### Linux / macOS ```bash python3 -m venv .venv .venv/bin/python -m pip install -r requirements.txt cp .env.example .env # 仅首次运行;已有 .env 时跳过 # 编辑 .env,填写 DEEPSEEK_API_KEY 后再启动 .venv/bin/python am.py ``` ### 配置说明 手机语音输入:点击输入区上方的麦克风按钮,再按住说话,松开后转文字并发送;上滑或取消触摸不发送,最长 60 秒。微信和手机浏览器使用同一服务端转写链路;页面必须通过 HTTPS 或本机 localhost 打开,并允许麦克风。旧版内置浏览器不支持录音时仍可文字输入。手机订单信息默认折叠,物流轨迹优先展示。 在根目录 `.env` 配置 `ASR_PROVIDER=baidu`、`BAIDU_ASR_API_KEY` 和 `BAIDU_ASR_SECRET_KEY`,使用已开通短语音识别的百度语音应用凭据,不能混用 OCR 或 DeepSeek 凭据。服务端须安装 FFmpeg 并加入 PATH,也可用 `FFMPEG_BIN` 指定可执行文件绝对路径;Docker 应用镜像已包含 FFmpeg。浏览器 WebM/MP4 录音通过内存管道转为 16 kHz 单声道 PCM,录音不写入上传目录或历史,只发送识别文字。修改后重启服务。可选的 `ASR_PROVIDER=dashscope` 使用 `.env.example` 中独立的 ASR 配置。 - 本地密钥放在项目根目录的 `.env` 中:首次运行复制 [`.env.example`](.env.example),填写 `DEEPSEEK_API_KEY=你的密钥`。`.env` 已被 Git 忽略,示例文件中的值应保持为空。 - 应用启动时自动读取根目录 `.env`,不依赖终端的当前目录;`am.py`、Windows 启动脚本和 WSGI 应用工厂均使用同一配置入口。修改密钥后须重启服务。 - 配置优先级为:显式传给 `create_app` 的配置 > 进程环境变量 > `.env` > 空值。环境变量即使为空也会覆盖 `.env`;本地填写后仍提示未配置时,请检查并移除旧的空环境变量。 - 部署环境仍可注入 `DEEPSEEK_API_KEY`。PowerShell 的 `$env:DEEPSEEK_API_KEY` 只影响当前终端及其子进程,双击启动脚本可直接使用 `.env`,无需配置系统环境变量。 - 密钥申请或更换可在 [DeepSeek 平台](https://platform.deepseek.com/api_keys) 完成;不要把密钥写入脚本、提交到 Git,或打印到日志。 - 当前服务监听 `0.0.0.0:5000`,局域网设备可能访问。仅供本机开发时,将 `logistics_chat/config.py` 中默认的 `HOST` 设置为 `"127.0.0.1"`。 - 历史启动脚本曾提交明文密钥。当前文件已移除该配置,但 Git 历史仍保留旧内容,应在平台作废旧密钥,并在 `.env` 或部署环境中配置新密钥。 ### Jenkins 灰度部署 现有灰度环境按 [Jenkins 部署指南](docs/deployment.md) 发布:构建和测试镜像,SSH 发送至专用 Compose 服务,Nginx 提供 HTTPS。客户聊天入口免登录,只有明确列出的客户 API 开放;客服后台、搜索、导出和其他未知路径继续使用访问密码。密钥由 Jenkins 注入,数据和证书保存在独立宿主机目录;启动失败时恢复之前的应用和站点。上游端口必须仅绑定回环地址。另附 [Linux 单机参考](docs/deployment-linux.md)。 ## 目录结构 ```text baiyue-logistics-chat/ ├── am.py CLI/WSGI 兼容入口 ├── Jenkinsfile 灰度构建、测试及受管发布 ├── Dockerfile 应用、测试及证书运维镜像 ├── .dockerignore 显式限制构建上下文 ├── rates_extra.py 扩展数据旧导入路径的兼容转发 ├── logistics_chat/ │ ├── __init__.py create_app 应用工厂 │ ├── config.py .env、环境变量与绝对路径配置 │ ├── db.py SQLite、历史持久化、备份 │ ├── knowledge.py 知识与关键词检索 │ ├── pricing.py 运费解析与计算 │ ├── rates_extra.py 扩展报价唯一数据源 │ ├── tracking.py 卡片协议与旧演示函数兼容 │ ├── order_data.py 本地订单测试表、精确查询与快照导入 │ ├── ai_client.py 模型 HTTP、压缩和追问 │ ├── chat_service.py 问答与 SSE 编排 │ ├── history_service.py 会话 CRUD │ ├── human_service.py 人工会话 │ ├── business_service.py 登录、统计、订单、留言、导出 │ ├── file_service.py 附件与清理 │ ├── hooks.py 限流和缓存头 │ ├── runtime.py 服务启动 │ └── routes/ HTTP Blueprints ├── templates/ 客户、登录、客服工作台 HTML ├── static/ │ ├── css/ theme.css 共享主题 + chat/admin/admin_login 页面样式 │ ├── js/ 对应经典页面脚本 │ └── fa.jpg ├── scripts/ 人工会话检查/关闭 CLI ├── deploy/ 灰度 Compose、发布/回退、证书及单机参考 ├── tests/ 规则、存储、HTTP/SSE、启动与维护回归 ├── docs/ architecture.md、knowledge.md ├── examples/requests/ 人工接口示例 ├── uploads/.gitkeep 本地附件不纳入版本控制 ├── start.bat 可选 Windows 启动检查 ├── 启动.bat 兼容入口,调用 start.bat ├── requirements.txt ├── .env.example ├── .gitignore ├── AGENTS.md └── README.md ``` 运行后还会生成 `history.db`、`logs/` 和 `backups/`,这些运行数据以及虚拟环境均由 Git 忽略。已经上传的本地文件在本次目录整理中保留。 ## 测试 先安装项目依赖,再使用同一个项目虚拟环境运行: ```powershell .\.venv\Scripts\python.exe -m pytest -v ``` 也可执行 `.\.venv\Scripts\python.exe -m unittest discover -s tests -v`。Linux/macOS 将解释器替换为 `.venv/bin/python`。纯规则和维护 CLI 本身仅依赖标准库,整套业务测试还需要 Flask 等项目依赖。 安装 Node.js 后,Python 测试还会运行 `tests/frontend/admin.test.cjs`,验证发送失败保留草稿、重复点击、切换会话、输入法及手机抽屉;也可单独执行 `node --test tests/frontend/admin.test.cjs`,无需安装 npm 依赖。未安装 Node.js 时该项明确跳过。 覆盖范围包括配置文件加载与优先级、运费边界与单位换算、知识检索、运单识别、旧 SQLite schema 迁移、事务回滚、附件、人工状态、历史、CSV、SSE 压缩/追问/异常/取消,以及 Windows 解释器选择。Windows 启动测试在其他系统上跳过,三个页面的外置资源也由接口测试检查。 模型请求使用模拟响应,数据库和附件使用临时目录;这不代表完成真实 DeepSeek 或真实物流接口联调。旧文档提及的原始 52 个业务测试和 29 个场景记录未随仓库提供,当前结果以实际测试输出为准。 ## 维护与调试 在根目录执行: ```powershell py -3 scripts/inspect_human_sessions.py py -3 scripts/close_active_sessions.py ``` 两个脚本默认定位到仓库根目录的 `history.db`,不依赖调用时的工作目录,也可用 `--db "D:\data\history.db"` 指定已有数据库。检查脚本只读;关闭脚本将**所有 active 人工会话**标记为 closed,保留 waiting 会话和聊天消息。数据库不存在时会报错退出,不会误建空库。 人工客服接口请求示例见 [examples/requests/README.md](examples/requests/README.md)。知识资料见 [docs/knowledge.md](docs/knowledge.md),实际知识内容维护在 `logistics_chat/knowledge.py` 的 `knowledge_list` 中。 模块定位、文件归属和后续拆分规范见 [docs/architecture.md](docs/architecture.md)。该文档与当前已拆分的目录保持同步。 ## 主要接口 | 方法 | 路径 | 说明 | |---|---|---| | GET | `/` | 客户聊天页面 | | GET / POST | `/stream_chat` | SSE 流式对话;POST JSON 接收 `msg`、`conv_id` | | POST | `/api/new_chat` | 新建会话并保存欢迎语,返回 `id`、`welcome` | | GET | `/api/get_history` | 会话列表 | | GET | `/api/get_messages?conv_id=` | 单个会话的消息 | | DELETE | `/api/del_history?id=` | 删除单个会话和消息 | | DELETE | `/api/clear_all` | 清空聊天历史 | | POST | `/api/upload` | 上传图片、PDF、TXT、CSV,最大 10MB | | POST | `/api/human/request` | 申请人工客服 | | POST | `/api/human/accept` | 客服接待 | | POST | `/api/human/send` | 发送人工消息 | | GET | `/api/human/poll` | 轮询人工消息 | | POST | `/api/human/close` | 关闭人工会话 | | POST | `/api/human/transfer` | 将接待中的会话转回排队列表 | | POST | `/api/human/note` | 保存会话备注 | | POST | `/api/human/recall` | 按 `mid` 撤回客服消息 | | POST | `/api/rate` | 提交人工服务评价(`sid`、1–5 的 `stars`、`comment`) | | GET | `/api/agents/list` | 客服列表,不包含密码 | | GET | `/api/search_chat?q=` | 搜索历史消息,最多返回 50 条 | | GET | `/admin` | 客服登录页面 | | POST | `/admin/login` | 校验客服账号密码 | | GET | `/admin/dashboard` | 客服工作台 | | GET | `/api/export_chat` | 导出 Excel;`?format=csv` 保留 CSV 导出 | 人工客服每天 8:00–22:00 接受新申请,由服务端校验时间;其余时间可提交离线留言。客服工作台保留白色界面,转接后由其他客服在排队列表主动接入。页面的铃铛按钮可开启浏览器通知,页面切到后台时提示排队或新消息;实际通知能力取决于浏览器权限。 SSE 每帧为一条 JSON,主要事件包括: ```text data: {"t": "正文片段"} data: {"e": "错误提示"} data: {"card": {...}} data: {"replace": "精简后的全文"} data: {"suggest": ["相关追问"]} data: {"d": 1} ``` ## 数据库与运行数据 订单数据保存在聊天服务运行时 SQLite 的 `order_test_data` 表中,与灰度业务库独立,可直接通过 SQLite 工具新增或修改;不会自动同步源库,没有管理页面和对外增删改接口。游客咨询无需登录,查询订单时引导客户登录;查询按服务端账号关联的客户范围过滤。`ORDER_DATA_TENANT_ID` 是额外限制,不能代替客户身份。 默认前台客户账号为 `admin / 123456`,与受 Nginx 保护的客服后台独立。默认按当前本地数据建立客户范围关联:单客户写入 `customer_users.tenant_id`,多客户写入 `customer_tenant_grants`,查询始终按服务端白名单过滤。首次关联完成后不自动加入新客户范围,同一客户下新增订单可直接查询。`CUSTOMER_DEMO_TENANT_ID` 可指定单个范围;`CUSTOMER_DEMO_LOCAL_ORDERS=false` 关闭多客户自动关联。账号保存在 `customer_users`,密码为 PBKDF2 哈希;已有账号启动时不覆盖,可直接维护哈希、`tenant_id`、`enabled` 及客户范围关联表。`CUSTOMER_DEMO_ENABLED=false` 禁用默认账号及其已有登录;真实客户账号不受该开关影响。`CUSTOMER_DEMO_USERNAME` 与 `CUSTOMER_DEMO_PASSWORD_HASH` 用于首次创建默认账号。 登录凭据通过独立 HttpOnly Cookie 及服务端 `customer_sessions` 校验,绑定当前访客,8 小时有效;退出后撤销当前登录。登录后自动继续刚才的订单问题;含订单事实的会话在退出后隐藏,只有相同访客重新登录同一客户和范围后才能恢复。真实用户认证接入时替换账号校验/关联层,继续使用内部客户 ID、可信客户范围和已有登录会话契约;MCP 数据查询仍通过 `lookup_order` 的客户范围过滤。 游客首次访问由浏览器生成 256 位随机 key,保存在当前站点的 localStorage;服务端只保存其哈希作为会话归属,以 HttpOnly、SameSite Cookie 校验请求。刷新或同浏览器再次打开可恢复自己的历史,其他浏览器拥有独立记录;清除站点数据后无法自动找回。存储被禁用时使用 Cookie 维持访客会话。旧版未记录归属的历史不自动分配给首位访客,继续保留给受保护的客服后台。访客只能访问、删除自己的聊天、人工会话和附件,不能凭会话编号或访客 key 查询真实订单。 未发送的文字草稿按游客和会话分别保存在当前浏览器的 localStorage,当前标签页通过 sessionStorage 记录上次打开的会话;切换会话和刷新可恢复对应草稿,发送或删除会话后清理。草稿不跨设备同步,不恢复未发送的附件。浏览器禁止存储或空间不足时只在当前页面暂存,并提示刷新前保留文字;清除站点数据会同时删除草稿。历史加载失败期间禁止发送,重新选择会话或新建对话后再继续。 快照 JSON 放仓库外的私有 artifacts 目录,使用明确路径导入: ```powershell .\.venv\Scripts\python.exe scripts/import_order_test_data.py --snapshot "D:\private\orders.snapshot.json" --db "D:\data\history.db" ``` 脚本默认保留已有记录,避免覆盖手工调整;确需覆盖时显式加 `--replace`。`order_no` 与 `tenant_id` 组成唯一键;`gross_weight_kg`、`volume_cbm`、`package_count` 是申报汇总,`history_json` 是形如 `[{"time":"2026-10-29 10:00","status":"SUBMITTED"}]` 的业务状态列表。子订单未提供独立货物数据时保持空值。快照、运行数据库和凭据不提交 Git。后续 MCP 接口开放后替换 `order_data.lookup_order` 的取数实现即可保留聊天契约。 首次启动创建聊天、访客附件、客户登录、人工接待、反馈及订单所需表,并兼容已有数据。默认前台客户账号按上述配置初始化,客服账号和订单数据由管理员配置。本地入口使用根目录 `history.db`,Linux 部署工厂另存至专用数据目录。会话备注保存在 `human_sessions.note`;消息撤回事件通过 `human_messages.recalled_id` 指向原消息,轮询客户端按编号更新。订单卡片以 `__CARD__{json}` 形式保存,前端据此恢复展示。 普通导入 `am.py` 或 `logistics_chat` 不创建运行数据。显式调用 `create_app()` 或访问 WSGI 兼容入口 `am.app` 时,默认初始化 SQLite、创建正常运行日志、备份数据库,并清理未被聊天记录引用且修改时间超过 7 天的临时上传文件。已发送附件随 AI/人工聊天记录保留;已被删除的旧文件无法恢复,页面会显示附件无法访问的提示。测试须事先配置临时数据库、附件、日志和备份目录;`INITIALIZE_RUNTIME=False` 可禁止自动初始化,此时调用方负责准备 schema。历史会话的自动清理仍已关闭。 ## 当前限制 - 订单查询使用可编辑测试副本,尚未接入实时承运商物流轨迹或 MCP;旧历史卡片继续兼容展示。 - 不预置客服账号;应用内客服登录仍沿用旧账号校验逻辑。客服工作台及接口仍须通过 Nginx 的专用访问密码保护,客户聊天不要求此密码。 - 后台登录尚未建立完整会话鉴权,管理页面和接口缺少登录校验。 - 运费数字由 Python 计算后交给模型组织说明;附加费是否适用仍需结合具体货物和地址核对。 - 回答提示词目标为 120–350 字;代码压缩阈值为中文 800、英文 1400 字符,超过阈值最多尝试压缩两次。 - 服务优先使用 Waitress 的 8 个线程;缺少 Waitress 时会回退到 Flask 开发服务器。公网部署前还需完善鉴权及部署配置。