Files
baiyue-logistics-chat/README.md
T

253 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 柏悦智能物流客服系统
基于 Flask + DeepSeek 的中欧/英国跨境物流智能客服,支持多轮对话、运费试算、运单查询、历史会话管理。
## 功能
- **多轮上下文**:能理解"那空运呢""这个多少钱"这类追问
- **知识库优先**:优先使用本地知识库回答,减少模型编造
- **追问兜底**:当前问题没命中关键词时,用上一句提问去检索
- **运费试算**:识别重量/尺寸/渠道,按报价表阶梯单价计算并展示完整明细
- **运单号查询**:发送 8~20 位运单号,返回轨迹卡片;先展示最近节点,可展开全部轨迹
- **历史会话**:会话保存到 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
```
### 配置说明
- 本地密钥放在项目根目录的 `.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 服务,再配置独立域名的 HTTPS 和全站访问密码。密钥由 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 模拟轨迹与卡片协议
│ ├── 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}
```
## 数据库与运行数据
首次启动会自动创建 `chat_history`、`messages`、`feedback`、`human_sessions`、`human_messages`、`ratings`、`agents`、`orders` 和 `tickets` 九张表,并兼容已有数据,不预置账号或演示数据。本地入口使用根目录 `history.db`,Linux 部署工厂另存至专用数据目录。客服账号需由管理员在实际环境中配置。会话备注保存在 `human_sessions.note`;消息撤回事件通过 `human_messages.recalled_id` 指向原消息,轮询客户端按编号更新。运单卡片以 `__CARD__{json}` 形式保存,前端据此恢复展示。
普通导入 `am.py` 或 `logistics_chat` 不创建运行数据。显式调用 `create_app()` 或访问 WSGI 兼容入口 `am.app` 时,默认初始化 SQLite、创建正常运行日志、备份数据库并清理修改时间超过 7 天的上传文件。测试须事先配置临时数据库、附件、日志和备份目录;`INITIALIZE_RUNTIME=False` 可禁止自动初始化,此时调用方负责准备 schema。历史会话的自动清理仍已关闭。
## 当前限制
- 运单轨迹为模拟数据,尚未接入真实物流查询接口。
- 不预置客服账号;应用内客服登录仍沿用旧账号校验逻辑。公网自用验收可按部署指南使用全站访问密码进入工作台。
- 后台登录尚未建立完整会话鉴权,管理页面和接口缺少登录校验。
- 运费数字由 Python 计算后交给模型组织说明;附加费是否适用仍需结合具体货物和地址核对。
- 回答提示词目标为 120–350 字;代码压缩阈值为中文 800、英文 1400 字符,超过阈值最多尝试压缩两次。
- 服务优先使用 Waitress 的 8 个线程;缺少 Waitress 时会回退到 Flask 开发服务器。公网部署前还需完善鉴权及部署配置。