Files
ozon-seller-kit/docs/ai-copy-backend-plan.md
T
2026-08-07 17:34:24 +08:00

458 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.
# Ozon Seller Kit:俄文文案生成方案
> 状态:方案稿
> 日期:2026-08-07
> 后端语言:PythonFastAPI
> 范围:先落地「中文采买信息 → 俄文标题 / 描述 / 标签 + 中文对照」;图片白底、Ozon 上传作为后续阶段预留扩展位。
---
## 1. 背景与目标
当前项目原为根目录散落的纯静态页面工具(`ozonSeller.html` + `js/app.js`),可直接 `file://` 打开,已具备:
- 计价 / 录入 / 上品登记表 / 组合码导出
- 浏览器 Canvas 水印
后续需要接入大模型 API。浏览器直连 DeepSeek 会遇到 CORS,且 API Key 不能放前端,因此引入 **本地 Python 服务** 做代理与业务编排;仓库采用一体扁平结构,静态页放在 `web/`
本阶段目标:
1. 在现有页面增加「俄文文案」独立区块。
2. 用户在单个输入框粘贴采买站商品信息(如有本次特殊要求可直接写在后面),点击生成。
3. 后端调用 DeepSeek,返回俄文标题 / 描述 / 标签,并同步中文对照。
4. 前端可编辑、一键复制;可选回填左侧「商品名」。
---
## 2. 总体架构
```
┌─────────────────────────────────────────────────────────────┐
│ 浏览器 │
│ web/ozonSeller.html + js/ + css/ │
│ http://127.0.0.1:8000/ozonSeller.html(由 FastAPI 托管) │
└───────────────────────────┬─────────────────────────────────┘
│ fetch JSON
┌─────────────────────────────────────────────────────────────┐
│ 同仓 FastAPImain.py
│ - 挂载 web/ 静态资源 │
│ - /api/ai/copy 文案生成 │
│ - (预留)/api/image/* /api/ozon/* │
└───────────────────────────┬─────────────────────────────────┘
│ HTTPS + Bearer Key
┌─────────────────────────────────────────────────────────────┐
│ DeepSeek APIOpenAI 兼容) │
│ https://api.deepseek.com │
└─────────────────────────────────────────────────────────────┘
```
原则:
- **密钥只放 `.env`**(不进 git);**模型清单放 `config/models.yaml`**(可入库)。
- **前端只请求本机 API**,不直连大模型、不接触密钥。
- **一体扁平结构**:不拆 `frontend/` / `backend/`;Python 入口在仓库根,静态资源独占 `web/`
- **现有静态能力尽量保留**;服务端先做薄代理 + Prompt 编排。
- 本地开发统一用 `http://127.0.0.1`,不再依赖 `file://``file://` 无法稳定调后端)。
---
## 3. 页面落位(前端交互)
### 3.1 插入位置
**计价双栏区域之后**、**上品登记表之前**,新增全宽区块「俄文文案」。
页面顺序变为:
1. Header(外链 + 汇率)
2. 计价输入 + 计价结果
3. **俄文文案(新)**
4. 上品登记表
5. 上品组合码
6. 图片水印
### 3.2 区块结构
| 区域 | 内容 |
|------|------|
| 左侧输入 | 商品资料(单个大文本,事实与本次要求写在一起)、带入当前商品名/型号(只读提示) |
| 右侧结果 | 标题 / 描述 / 标签:俄文主展示 + 中文对照;可编辑;各字段复制按钮 |
| 操作 | 「生成文案」「重新生成」;可选「中文标题填入商品名」 |
### 3.3 与现有字段关系
- 左侧 `productName` / SKU / 型号:仍服务计价与登记,不改成俄文主编辑区。
- 文案结果独立保存于本区块 DOM / 内存;后续接 Ozon 上传时再读取俄文字段。
- 「填入商品名」仅把生成的 **中文标题** 写入 `#productName`
---
## 4. 仓库目录与代码放置
采用 **一体扁平结构**(方案 B):不拆 `frontend/` / `backend/`。Python 入口与配置在仓库根;静态页独占 `web/`,避免挂载时误暴露源码。
```
ozon-seller-kit/
├── docs/
│ └── ai-copy-backend-plan.md # 本方案
├── main.py # FastAPI 入口、CORS、挂载 web/
├── config/
│ ├── settings.py # .env 密钥与运行参数
│ └── models.yaml # 模型目录(可入库,不含密钥)
├── api/
│ ├── ai.py # /api/ai/*
│ ├── image.py # (预留)/api/image/*
│ └── ozon.py # (预留)/api/ozon/*
├── services/
│ ├── models_catalog.py # 读取 models.yaml、解析密钥
│ ├── deepseek.py # OpenAI 兼容 chat 调用
│ └── prompts/
│ └── copy_ru.py
├── schemas/
│ └── copy.py
├── web/
│ ├── ozonSeller.html
│ ├── css/
│ ├── imgs/
│ └── js/
│ ├── app.js
│ ├── ai-copy.js
│ ├── watermark-data.js
│ └── tailwind.config.js
├── requirements.txt
├── .env.example
├── start.command
├── .gitignore
└── README.md
```
### 4.1 放置约定
| 内容 | 放哪里 | 说明 |
|------|--------|------|
| 页面结构 | `web/ozonSeller.html` | 文案区 DOM、模型下拉 |
| 文案交互 | `web/js/ai-copy.js` | 拉模型列表、带 model 调生成 |
| 模型目录 | `config/models.yaml` | id/label/base_url/api_key_env;可入库 |
| 密钥 | 根目录 `.env` | 仅密钥与 HOST/PORT;不入库 |
| API 路由 | `api/` | 按业务拆文件 |
| LLM 调用 | `services/deepseek.py` | 按 ModelSpec 调 OpenAI 兼容接口 |
| Prompt | `services/prompts/` | 方便单独迭代文案质量 |
### 4.2 静态资源如何被访问
由 FastAPI 挂载 `web/`,一个进程同时提供页面和 API
```python
# main.py 示意
WEB_DIR = Path(__file__).resolve().parent / "web"
app.mount("/", StaticFiles(directory=WEB_DIR, html=True), name="web")
```
注意:静态挂载应放在 `/api` 路由注册之后,避免吞掉 API 路径。
访问地址:
- 页面:`http://127.0.0.1:8000/ozonSeller.html`
- API`http://127.0.0.1:8000/api/ai/copy`
前端请求基址:
```js
const API_BASE = window.location.origin; // 同域,无 CORS 烦恼
```
若暂时用 VS Code Live Server 单独打开 `web/`、Python 另开端口,则需开 CORS,前端写死 `API_BASE = 'http://127.0.0.1:8000'`。**首选同域挂载方案。**
---
## 5. 后端设计
### 5.1 技术选型
| 项 | 选择 | 原因 |
|----|------|------|
| Web 框架 | FastAPI | 轻量、类型清晰、异步友好 |
| HTTP 客户端 | `httpx` | 调 OpenAI 兼容接口 |
| 密钥/运行参数 | `pydantic-settings` + `.env` | 只放秘密与端口 |
| 模型目录 | `config/models.yaml` | 可扩展多模型/多厂商 |
| 运行 | `uvicorn` | 标准 ASGI |
### 5.2 核心依赖
```text
fastapi
uvicorn[standard]
httpx
pydantic-settings
python-dotenv
PyYAML
```
### 5.3 配置分层(模型目录 + 密钥)
**原则:**
- `config/models.yaml`:模型清单(id、显示名、api_model、base_url、api_key_env),**可入库,不含密钥**。
- `.env`:只放密钥与 HOST/PORT 等运行参数,**不入库**。
- 前端通过 `GET /api/ai/models` 获取可选项,**永不接触密钥**。
`config/models.yaml` 示例:
```yaml
default: deepseek-v4-flash
models:
- id: deepseek-v4-flash
label: Flash(快/省)
provider: deepseek
api_model: deepseek-v4-flash
base_url: https://api.deepseek.com
api_key_env: DEEPSEEK_API_KEY
- id: deepseek-v4-pro
label: Pro(质量更好)
provider: deepseek
api_model: deepseek-v4-pro
base_url: https://api.deepseek.com
api_key_env: DEEPSEEK_API_KEY
```
`.env.example`
```env
DEEPSEEK_API_KEY=sk-xxxx
# OPENAI_API_KEY=sk-xxxx # 若 models.yaml 引用了该变量再配置
HOST=127.0.0.1
PORT=8000
CORS_ORIGINS=
```
同一厂商可共用一把 Key;按「账号/厂商」分 Key,不必每个模型名硬拆一把。
### 5.4 API 约定
#### `GET /api/ai/models`
```json
{
"default": "deepseek-v4-flash",
"models": [
{ "id": "deepseek-v4-flash", "label": "Flash(快/省)" },
{ "id": "deepseek-v4-pro", "label": "Pro(质量更好)" }
]
}
```
#### `POST /api/ai/copy`
**请求:**
```json
{
"source_text": "从采买站粘贴的中文商品信息,可在末尾附带本次生成要求……",
"product_name": "可选,当前表单商品名",
"model_code": "可选,型号",
"model": "deepseek-v4-pro"
}
```
- `model` 可选;空则用 `models.yaml``default`
- 不在白名单 → `400`
**成功响应:**
```json
{
"titles_ru": ["……", "……"],
"titles_zh": ["……", "……"],
"description_ru": "……",
"description_zh": "……",
"tags_ru": ["……", "……"],
"tags_zh": ["……", "……"],
"model": "deepseek-v4-pro",
"usage": {
"prompt_tokens": 0,
"completion_tokens": 0
}
}
```
校验规则(后端):
- `source_text` 必填,去空白后长度 ≥ 10。
- `model` 必须属于 `models.yaml`(或空=默认)。
- 要求模型输出 **严格 JSON**,解析失败则重试 1 次或返回 502。
### 5.5 Prompt 策略(概要)
System 角色职责:
- 面向俄罗斯消费者和 Ozon 搜索生成完整商品卡,而非把采买资料压缩成摘要。
- 优先级固定为:事实准确 > 俄语自然 > 信息完整与转化力 > 关键词覆盖。
- 标题一次输出 2 个互补方案(`titles_ru` / `titles_zh`),以核心品类词开头,前 30 字符覆盖关键属性,长度 60~90 字符,去除年份、新款、爆款等噪声。
- 描述采用 `Описание товара / Характеристики / Преимущества` 三段结构,原文明确提到配件时追加 `Комплектация`;资料足够时约 900~1500 个俄文字符。
- 允许将已有事实改写成温和的使用利益点,但禁止补充原文没有的结构、认证、安全结论、适用年龄、开口位置、礼物节日等硬信息。
- 对常见行业词做翻译约束,例如“搪胶”优先译为 `винил (ПВХ)`,不擅自译为天然橡胶 `каучук`
- 标签输出 10~15 个;每个俄文标签只允许一个单词,不带 `#`,中俄数组按索引一一对应。
- 中文标题和描述保持与最终俄文相同的事实与段落结构,便于逐项核对。
- 调用参数使用较稳定的 `temperature=0.45`,并给描述预留充足输出长度。
User 内容拼接:
```
<当前商品名>…</当前商品名>
<型号>…</型号>
<商品资料>
</商品资料>
```
输入用边界标签隔离,避免把采买文本中的内容误当作系统指令。实现放在
`services/prompts/copy_ru.py`,便于单独调优,不必改路由。
### 5.6 服务分层
```
api/ai.py
GET /models → models_catalog.list_model_options()
POST /copy → deepseek.generate_copy(req)
services/models_catalog.py
→ 读 models.yaml
→ 白名单校验
→ 按 api_key_env 从环境变量取密钥
services/deepseek.py
→ 组装 messages
→ 用 ModelSpec.base_url / api_model 调 /chat/completions
→ 解析 JSON → CopyResponse
```
---
## 6. 前端设计
### 6.1 DOM 落点
`web/ozonSeller.html` 中,计价 `grid` 结束后、上品登记表 `mt-8` 卡片前插入文案区。
模型下拉位于「生成文案」按钮上方;区块标题右侧提供折叠按钮。
| 元素 | id |
|------|----|
| 折叠按钮 / 内容区 | `aiToggleBtn` / `aiCopyBody` |
| 模型下拉 | `aiModelSelect` |
| 商品资料 | `aiSourceText` |
| 生成按钮 | `aiGenerateBtn` |
| 推荐标题容器 | `aiTitlesContainer` |
| 描述俄/中 | `aiDescRu` / `aiDescZh` |
| 标签容器 | `aiTagsContainer` |
| 状态提示 | `aiCopyStatus` |
结果区为只读展示:标题渲染成卡片(含复制俄文、填入商品名),描述用等高只读文本块保留换行,标签渲染成「俄文|中文」芯片,点击即复制俄文。需要调整文案时通过「生成要求」重新生成。
### 6.2 JS 职责拆分
`web/js/ai-copy.js`
- 启动时 `GET /api/ai/models` 填充下拉;`localStorage` 记住上次选择
- 收集输入(含 `#productName``#modelCode`、选中的 `model`
- `POST /api/ai/copy`
- 渲染标题卡片、描述文本块、标签芯片,并处理 loading / 错误态
- 复制(标题、描述、单个标签、全部标签)与「填入商品名」
- 折叠 / 展开整个文案区
`web/js/app.js`:保持计价、历史、水印。
### 6.3 交互细节
- 生成中:按钮 disabled +「生成中…」。
- 失败:在 `aiCopyStatus` 显示可读错误。
- 不在前端存 API Key;模型列表不硬编码(以后加模型只改 `models.yaml`)。
---
## 7. 本地运行方式
### 7.1 一次性准备
在仓库根目录:
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# 编辑 .env,填入 DEEPSEEK_API_KEY
```
### 7.2 启动
```bash
source .venv/bin/activate
uvicorn main:app --reload --host 127.0.0.1 --port 8000
```
浏览器打开:
`http://127.0.0.1:8000/ozonSeller.html`
### 7.3 `.gitignore` 建议追加
```gitignore
.venv/
.env
**/__pycache__/
*.pyc
.DS_Store
web/ozonSeller.html.bak
```
---
## 8. 分阶段落地
### Phase 1(本方案当前实施范围)
- [x] 静态资源收拢到 `web/`
- [x] 一体扁平:`main.py` / `api/` / `services/` / `schemas/`
- [x] 实现 `POST /api/ai/copy`
- [x] 页面插入「俄文文案」区块
- [x] `web/js/ai-copy.js` 对接 API
- [x] FastAPI 挂载 `web/`,同域静态托管可本地跑通
- [x] 根目录 `README.md` / `start.command`
- [x] `config/models.yaml` 模型目录 + `GET /api/ai/models` + 前端下拉切换
### Phase 2(图片)
- `POST /api/image/process`:白底、服务端水印(可选保留前端水印)
- 预留图生图代理接口
### Phase 3Ozon
- `POST /api/ozon/products`:读取文案区俄文字段 + 计价结果上传
- Client-Id / Api-Key 仅存根目录 `.env`
---
## 9. 风险与约束
| 点 | 说明 |
|----|------|
| 不能用 `file://` 调后端 | 需通过 `http://127.0.0.1:8000` 打开页面 |
| Prompt 质量 | 俄文 listing 需多轮样本调优;Prompt 独立文件便于改 |
| 模型偶发非 JSON | 后端要做解析容错与明确报错 |
| Key 泄露 | `.env` 不入库;勿把 Key 打进前端或日志 |
| 费用 | 前端防连点;可后续加简易速率限制 |
---
## 10. 结论
- **目录**:一体扁平;Python 在仓库根,静态页在 `web/`
- **模型配置**:清单在 `config/models.yaml`,密钥在 `.env`;前端只消费 `/api/ai/models`
- **服务**FastAPI`main.py`)挂载 `web/` 并提供 `/api/*`
- **前端**:俄文文案区支持模型下拉;逻辑在 `web/js/ai-copy.js`
- **扩展**`api/image.py``api/ozon.py` 预留;加模型只需改 yaml + 对应密钥环境变量。