Files

221 lines
8.1 KiB
Markdown
Raw Permalink 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 条渠道:
| 渠道 | 21KG+ | 101KG+ | 500KG+ | 2000KG+ | 参考时效 |
|---|---|---|---|---|---|
| 欧洲海运快线 | 12.3 | 10.3 | 10.2 | 10.0 | 开船后 38-45 天 |
| 义乌中欧铁路 | 15.5 | 13.5 | — | 13.5(1000+) | 发车后 36-40 天 |
| 中欧卡航 | 19.4 | 17.4 | 17.4 | 17.3 | 发车后 16-20 天 |
| 欧洲空运 | 39 | 36 | — | 37(71+) | 10-13 天 |
| 英国海运 | 12.8 | 10.9 | — | — | 约 40 天 |
| 英国铁路 | 18.0 | 16.2 | — | — | 25-30 天 |
| 中英卡航 | 21.4 | 19.4 | — | — | 20-22 天 |
| 英国空运 | 48(22+) | 42 | — | 44(51+) | 8-10 天 |
**计费规则**(均来自报价表):
- 运费 = 阶梯单价 × 计费重量,按重量落入的档位取价
- 计费重量 = 实重与材积重取大,材积 = 长×宽×高(cm) ÷ 6000(1CBM = 167KG)
- 最低起收档 21KG(英国空运 22KG)
- 铁路、卡航单件计费重不足 13KG 加收超轻费 5 元/kg;空运单件不足 12KG 按 12KG 计费
- 偏远地址 +7 元/kg,单票最低 250 元
数据定义在 `am.py` 的 `RATES` 字典中,修改价格只需改这里,不需要动计算逻辑。
### 扩展数据(rates_extra.py)
从同一份报价表提取、目前已支持:
- **海外仓卡派按方计费**:0.5CBM+ / 1CBM+ 两档,按提问里的国家自动匹配仓点分组
- **产品附加费**:47 类产品(手机 5 元/kg、电池 1-2 元/kg、玩具 2 元/kg 等)
- **DPD 岛屿偏远判断**:12 个国家的岛屿/偏远邮编,命中即提示 35 欧/件
用法示例:
| 提问 | 效果 |
|---|---|
| 2方 海运 到德国海外仓多少钱 | 按 1CBM+ 档计算并列出德国仓点分组 |
| 寄手机到德国要加什么费用 | 给出 5 元/kg 附加费 |
| 邮编07000 算偏远吗 | 命中意大利撒丁岛,提示 35 欧/件 |
> ⚠️ 报价表含商业价格与联系人信息,公开发布前请脱敏或替换为示例数据。
## 运行环境
- Python 3.9 及以上
- Windows / macOS / Linux
## 快速启动(Windows)
双击 `start.bat` 即可,脚本会自动检查端口占用、API Key 和依赖。
## 安装与启动
### 1. 安装依赖
```bash
pip install -r requirements.txt
```
### 2. 配置 API Key
Key 不写进代码,通过系统环境变量提供。
Windows PowerShell:
```powershell
setx DEEPSEEK_API_KEY "sk-你的key"
```
设置后需要**重新打开终端**才生效,可用下面的命令确认:
```powershell
echo $env:DEEPSEEK_API_KEY
```
Linux / macOS:
```bash
export DEEPSEEK_API_KEY="sk-你的key"
```
Key 申请地址:https://platform.deepseek.com/api_keys
也可以参考项目根目录的 **.env.example**(不含真实 Key 的配置示例文件),其中列出了需要设置的环境变量及说明。
### 3. 启动服务
```bash
cd E:\wuliu_chat
python am.py
```
### 4. 打开页面
浏览器访问 http://127.0.0.1:5000
## 测试
### 单元测试
```bash
pytest -v
```
共 **52 个用例**,覆盖运费意图解析、运单号识别、阶梯价计算、卡派按方、附加费识别、岛屿邮编、卡片转换等。
测试在临时目录中运行,不会改动 `history.db`。
### 回答长度控制
- 中文回答:120~350 字;英文回答:120~700 字
- 超过上限时由代码自动压缩一次(保留全部数字),仍超标再压一次
- 提示词之外的第二道保险,确保字数可控
### 场景测试
`测试记录.md` 记录了 **29 个业务场景**的输入、预期结果、首次结果、修复动作与复测结果,
其中 26 项通过或已修复,3 项列为未解决/待验证(超长输入、并发提问、无答案兜底)。
## 目录结构
```
wuliu_chat/
├── am.py 主程序(路由、知识库、对话逻辑、运单查询、运费试算)
├── rates_extra.py 报价表扩展数据(卡派按方、附加费、岛屿邮编)
├── test_am.py 单元测试(52 个用例)
├── start.bat Windows 一键启动脚本
├── requirements.txt 依赖清单
├── README.md 说明文档
├── knowledge.md 知识资料(由 knowledge_list 导出)
├── 测试记录.md 场景测试记录(29 个场景)
├── .gitignore Git 忽略规则
├── .env.example 配置示例(不含 Key)
├── history.db SQLite 数据库(会话与消息)
├── static/ 静态资源
│ └── fa.jpg 网站图标
├── templates/
│ └── index.html 前端页面
└── .vscode/
└── launch.json VS Code 调试配置
```
> `kefu.py`、`SQLite.py` 为早期遗留文件,不影响运行,可安全删除。
## 主要接口
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/` | 聊天页面 |
| GET | `/stream_chat?msg=&conv_id=` | 流式对话(SSE) |
| POST | `/api/new_chat` | 新建会话,返回 `{"id": 会话id}` |
| GET | `/api/get_history` | 会话列表 |
| GET | `/api/get_messages?conv_id=` | 单个会话的全部消息 |
| DELETE | `/api/del_history?id=` | 删除会话(级联删消息) |
| DELETE | `/api/clear_all` | 清空全部历史 |
### 流式事件格式
`/stream_chat` 返回 Server-Sent Events,每帧是一条 JSON:
```
data: {"t": "正文片段"} 正常回答片段
data: {"e": "错误提示"} 出错时
data: {"card": {...}} 运单轨迹卡片
data: {"d": 1} 正常结束
```
## 数据库
SQLite,文件为 `history.db`,首次启动自动创建。
- `chat_history`:会话(id、title、ctime、utime)
- `messages`:消息(id、conv_id、role、content、ctime)
运单卡片以 `__CARD__{json}` 形式保存在 `messages.content`,前端识别后重新渲染。
## 常见问题
**页面报"服务未配置 API Key"**
环境变量没读到。确认变量名是 `DEEPSEEK_API_KEY`,并**重新打开终端**后再启动。
**页面报"API Key 无效或已过期"**
Key 已作废,去平台重新生成一把,再用 `setx` 覆盖。
**端口 5000 被占用**
```powershell
netstat -ano | findstr ":5000 "
taskkill /PID <进程号> /F
```
确认只有一行 `LISTENING`,避免两个进程同时抢端口。
**改了 am.py 没生效**
Flask 启动时加载代码,改后端必须重启服务;只改 `index.html` 则刷新页面即可。
## 说明
- 运单轨迹当前为**模拟数据**(按单号固定生成),接真实接口只需修改 `am.py` 中的 `build_track()`。
- 服务使用 Waitress 多线程启动(8 个线程),适合内网小范围使用;面向公网仍需反向代理与 HTTPS。
- 默认只监听 `127.0.0.1`,如需局域网访问,把 `am.py` 中的 `HOST` 改为 `"0.0.0.0"`。
- 运费计费方式为**阶梯单价 × 计费重量**,价格与时效来自炜易达报价表。报价更新时只需修改 `RATES`;知识库文案在 `knowledge_list` 中,两者建议同步更新。
- 材积按 ÷6000 计算(1CBM = 167KG),与部分渠道 ÷5000 的口径不同,修改时注意核对报价表。