# Ozon Seller Kit:俄文文案生成方案 > 状态:方案稿 > 日期:2026-08-07 > 后端语言:Python(FastAPI) > 范围:先落地「中文采买信息 → 俄文标题 / 描述 / 标签 + 中文对照」;图片白底、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 ▼ ┌─────────────────────────────────────────────────────────────┐ │ 同仓 FastAPI(main.py) │ │ - 挂载 web/ 静态资源 │ │ - /api/ai/copy 文案生成 │ │ - (预留)/api/image/* /api/ozon/* │ └───────────────────────────┬─────────────────────────────────┘ │ HTTPS + Bearer Key ▼ ┌─────────────────────────────────────────────────────────────┐ │ DeepSeek API(OpenAI 兼容) │ │ 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 3(Ozon) - `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 + 对应密钥环境变量。