267 lines
11 KiB
Markdown
267 lines
11 KiB
Markdown
|
|
# ADR-0002: AI 能力通过外部 API 调用实现
|
|||
|
|
|
|||
|
|
> **状态**: 已决议
|
|||
|
|
> **日期**: 2026-05-20
|
|||
|
|
> **父决策**: ADR-0001(整体技术栈)
|
|||
|
|
> **决策者**: 架构评审
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 上下文
|
|||
|
|
|
|||
|
|
平台需要 AI 能力支撑四大核心模块:配方指标预测、NL 搜索、配方生成/推演、颜色推荐。考虑两种实现路径:
|
|||
|
|
|
|||
|
|
- **方案 A**:自建 Python AI 微服务(FastAPI + 自训练模型)
|
|||
|
|
- **方案 B**:通过外部 AI API 调用实现(LLM API + Prompt Engineering)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 决策
|
|||
|
|
|
|||
|
|
**选择方案 B**:所有 AI 能力通过调用外部 LLM API 实现。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 理由
|
|||
|
|
|
|||
|
|
### 方案 B 优势
|
|||
|
|
|
|||
|
|
| 维度 | 方案 A(自建) | 方案 B(外部 API) |
|
|||
|
|
| :--- | :--- | :--- |
|
|||
|
|
| **开发成本** | 需要 ML 工程师训练模型;数据清洗和标注投入大 | Prompt Engineering 即可;无需 ML 专业背景 |
|
|||
|
|
| **运维成本** | GPU 服务器(至少 1 台 A100)+ 模型部署和监控 | 零运维,按调用量付费 |
|
|||
|
|
| **迭代速度** | 重新训练需数天到数周 | 调整 Prompt 即时生效 |
|
|||
|
|
| **模型能力** | 受限于自有数据量和训练资源 | 持续获得最新大模型能力升级 |
|
|||
|
|
| **部署复杂度** | 增加 1 个微服务 + GPU 依赖 + gRPC | 仅 BFF 层 HTTP 调用 |
|
|||
|
|
| **冷启动** | 模型加载需数分钟 | 即用即走 |
|
|||
|
|
|
|||
|
|
### 化妆品配方场景的特殊适配
|
|||
|
|
|
|||
|
|
配方研发的 AI 需求特点:
|
|||
|
|
|
|||
|
|
1. **推理为主,非训练密集型**:预测肤感/稳定性本质是"基于成分知识的推理",LLM 的常识推理 + few-shot learning 可以胜任
|
|||
|
|
2. **数据量小**:企业内部配方数据通常是千到万级,不足以训练专用深度学习模型
|
|||
|
|
3. **领域知识密集**:LLM 已具备化学/化妆品基础知识,通过 Prompt 注入成分数据库即可精准推理
|
|||
|
|
4. **需求多变**:配方推演的约束条件千变万化,API 调用的灵活性远胜固定模型
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## BFF 层 AI 调用架构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────┐
|
|||
|
|
│ Fastify BFF │
|
|||
|
|
│ │
|
|||
|
|
│ ┌────────────────────────────────────────────────────┐ │
|
|||
|
|
│ │ AI Service Module │ │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │ │
|
|||
|
|
│ │ │ Cache │ │ Rate │ │ Fallback │ │ │
|
|||
|
|
│ │ │ Layer │ │ Limiter │ │ Handler │ │ │
|
|||
|
|
│ │ └──────────┘ └──────────┘ └───────────────┘ │ │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ ┌────────────────────────────────────────────┐ │ │
|
|||
|
|
│ │ │ Prompt Templates (per capability) │ │ │
|
|||
|
|
│ │ │ - predictFormulaMetrics │ │ │
|
|||
|
|
│ │ │ - parseNLQuery │ │ │
|
|||
|
|
│ │ │ - generateFormulaOptions │ │ │
|
|||
|
|
│ │ │ - recommendColorants │ │ │
|
|||
|
|
│ │ │ - extractFormulaStructure │ │ │
|
|||
|
|
│ │ └────────────────────────────────────────────┘ │ │
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ ┌────────────────────────────────────────────┐ │ │
|
|||
|
|
│ │ │ AI API Client │ │ │
|
|||
|
|
│ │ │ - streaming (SSE proxy) │ │ │
|
|||
|
|
│ │ │ - retry with backoff │ │ │
|
|||
|
|
│ │ │ - timeout (30s default, 120s streaming) │ │ │
|
|||
|
|
│ │ └────────────────────────────────────────────┘ │ │
|
|||
|
|
│ └────────────────────────────────────────────────────┘ │
|
|||
|
|
└─────────────────────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 核心模块
|
|||
|
|
|
|||
|
|
| 模块 | 职责 |
|
|||
|
|
| :--- | :--- |
|
|||
|
|
| **Prompt Templates** | 每种 AI 能力对应独立的 Prompt 模板,包含 system prompt + 结构化输出指令 |
|
|||
|
|
| **Cache Layer** | LRU 缓存相同/相似查询的 AI 响应(基于 query hash),TTL 5min~1h |
|
|||
|
|
| **Rate Limiter** | 令牌桶 + 并发控制,保护 API 配额 |
|
|||
|
|
| **Fallback Handler** | API 不可用时返回缓存结果或友好降级提示 |
|
|||
|
|
| **AI API Client** | 统一的 HTTP 客户端,处理 streaming、重试、超时 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 各 AI 能力的 API 调用策略
|
|||
|
|
|
|||
|
|
### 1. 配方指标预测
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /api/ai/predict
|
|||
|
|
|
|||
|
|
BFF 流程:
|
|||
|
|
1. 从 PostgreSQL 检索该成分组合的历史相似配方(pgvector 向量搜索)
|
|||
|
|
2. 构建 Prompt:
|
|||
|
|
- System: 化妆品配方专家角色 + 指标定义
|
|||
|
|
- Context: 历史相似配方的指标数据(few-shot examples)
|
|||
|
|
- User: 当前配方的成分列表和比例
|
|||
|
|
3. 调用 AI API(非 streaming,预期 < 5s)
|
|||
|
|
4. 缓存结果(key: 成分+比例 hash)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. NL 搜索解析
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /api/formulas/search?q=不含酒精的高保湿精华
|
|||
|
|
|
|||
|
|
BFF 流程:
|
|||
|
|
1. 调用 AI API 将 NL 转为结构化查询:
|
|||
|
|
{
|
|||
|
|
"filters": { "exclude_ingredients": ["alcohol", "ethanol"], "category": "精华液" },
|
|||
|
|
"vector_query": "高保湿、补水、滋润配方",
|
|||
|
|
"sort": "保湿指数 DESC"
|
|||
|
|
}
|
|||
|
|
2. filters → PostgreSQL WHERE 子句
|
|||
|
|
3. vector_query → pgvector embedding → HNSW 相似搜索
|
|||
|
|
4. 合并结果返回
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 配方推演
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /api/ai/explore
|
|||
|
|
|
|||
|
|
BFF 流程:
|
|||
|
|
1. 用户设置约束(目标成本、保留成分、禁止成分、目标指标)
|
|||
|
|
2. 检索当前配方 + 相关历史配方作为 context
|
|||
|
|
3. 构建 Prompt → 调用 AI API(streaming 模式)
|
|||
|
|
4. BFF 转发 SSE 流到前端,逐步展示生成的候选方案
|
|||
|
|
5. 每个候选方案附带置信度和变更说明
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 颜色推荐
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /api/color/recommend
|
|||
|
|
|
|||
|
|
BFF 流程:
|
|||
|
|
1. 前端传入目标颜色 Lab 值
|
|||
|
|
2. 在 PostgreSQL 中检索 ΔE < 3.0 的历史颜色配方(pgvector)
|
|||
|
|
3. 将目标色 + 最近匹配配方作为 context,调用 AI API 推荐色浆组合
|
|||
|
|
4. 返回推荐色浆 + 预测比例 + 预测 ΔE
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 配方结构化提取
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /api/formulas/extract
|
|||
|
|
|
|||
|
|
BFF 流程:
|
|||
|
|
1. 用户粘贴配方文本(或上传 Excel)
|
|||
|
|
2. 调用 AI API with function calling / structured output
|
|||
|
|
3. 提取:成分 INCI 名、中文名、比例、所属相、工艺备注
|
|||
|
|
4. 与成分目录(ingredients 表)模糊匹配校验
|
|||
|
|
5. 返回结构化 JSON,前端展示确认
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## AI API 选型
|
|||
|
|
|
|||
|
|
### 主选
|
|||
|
|
|
|||
|
|
| API | 优势 | 适用场景 |
|
|||
|
|
| :--- | :--- | :--- |
|
|||
|
|
| **OpenAI GPT-4o / GPT-4.1** | 推理能力最强;structured output 原生;streaming 稳定 | 配方推演、NL 解析、结构化提取 |
|
|||
|
|
| **Anthropic Claude 4** | 长上下文(200K);化工领域知识强 | 配方生成(需大量 context)、复杂推理 |
|
|||
|
|
| **DeepSeek V3** | 性价比高;中文能力强 | 指标预测(高频调用)、批量处理 |
|
|||
|
|
|
|||
|
|
### 推荐策略
|
|||
|
|
|
|||
|
|
| 场景 | 模型 | 理由 |
|
|||
|
|
| :--- | :--- | :--- |
|
|||
|
|
| 配方推演(流式,低频,高质量) | GPT-4o | streaming 体验最好,推理质量最高 |
|
|||
|
|
| 指标预测(非流式,高频,需快) | DeepSeek V3 | 便宜、快、中文好 |
|
|||
|
|
| NL 搜索解析(高频,需结构化输出) | GPT-4o-mini / DeepSeek V3 | 便宜 + function calling |
|
|||
|
|
| 配方结构化提取(批量,需准确) | GPT-4o | structured output 精度最高 |
|
|||
|
|
| 颜色推荐(低频,需领域知识) | GPT-4o | 需要强推理 |
|
|||
|
|
|
|||
|
|
### API 配置抽象
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// BFF 层多 provider 抽象
|
|||
|
|
interface AIProvider {
|
|||
|
|
chat(messages: Message[], options: ChatOptions): Promise<ChatResponse>;
|
|||
|
|
chatStream(messages: Message[], options: ChatOptions): AsyncIterable<ChatChunk>;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const providers: Record<string, AIProvider> = {
|
|||
|
|
openai: new OpenAIProvider({ apiKey: env.OPENAI_API_KEY }),
|
|||
|
|
deepseek: new DeepSeekProvider({ apiKey: env.DEEPSEEK_API_KEY }),
|
|||
|
|
// 预留其他 provider
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
所有 AI 调用通过统一的 `AIService` 模块,根据场景路由到对应 provider,上层业务不感知具体模型。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 降级策略
|
|||
|
|
|
|||
|
|
| 场景 | 降级行为 |
|
|||
|
|
| :--- | :--- |
|
|||
|
|
| AI API 超时(5s) | 指标预测:返回"无法预测,请手动评估";NL 搜索:降级为基础关键词搜索 |
|
|||
|
|
| AI API 不可用(连续失败) | 全部能力降级为提示模式,告知用户"AI 服务暂不可用" |
|
|||
|
|
| 配额耗尽 | 限流 + 排队;高频能力(指标预测)优先缓存命中 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 缓存策略
|
|||
|
|
|
|||
|
|
| 缓存内容 | TTL | Key |
|
|||
|
|
| :--- | :--- | :--- |
|
|||
|
|
| 指标预测结果 | 1 小时 | 成分列表 + 比例的 hash |
|
|||
|
|
| NL 搜索解析 | 5 分钟 | 原始查询文本 hash |
|
|||
|
|
| 颜色推荐 | 30 分钟 | 目标 Lab 值 + 允许 ΔE |
|
|||
|
|
| 成分结构化提取 | 永久(除非成分库更新) | 原料名称 hash |
|
|||
|
|
|
|||
|
|
使用 Redis 存储。BFF 启动时无需依赖 Redis,降级为内存 LRU 缓存。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 安全考虑
|
|||
|
|
|
|||
|
|
- **API Key 管理**:环境变量注入(非代码硬编码),支持 vault/secret manager
|
|||
|
|
- **数据脱敏**:发送给 AI API 的 prompt 不包含公司敏感配方全量数据,仅发送 necesary context
|
|||
|
|
- **Prompt 注入防护**:用户输入(NL 搜索词)经过清洗后嵌入 prompt 模板
|
|||
|
|
- **审计日志**:所有 AI 调用记录(请求摘要、token 消耗、耗时)存储到 PostgreSQL audit 表
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 后果
|
|||
|
|
|
|||
|
|
### 正向
|
|||
|
|
|
|||
|
|
- 零 ML 基础设施投入,开发周期缩短 50%+
|
|||
|
|
- 部署仅需 4 个服务,运维复杂度极低
|
|||
|
|
- 模型能力随 API 升级自动提升,无迁移成本
|
|||
|
|
- 成本可控:按调用量付费,低用量时几乎为零
|
|||
|
|
- BFF 层可独立开发和测试(mock AI 响应)
|
|||
|
|
|
|||
|
|
### 风险和缓解
|
|||
|
|
|
|||
|
|
| 风险 | 缓解 |
|
|||
|
|
| :--- | :--- |
|
|||
|
|
| API 延迟影响用户体验 | 缓存 + streaming + 降级;预测 API 设置 5s 超时 |
|
|||
|
|
| 外部 API 数据隐私 | Prompt 中不发送完整配方;仅发送必要上下文 |
|
|||
|
|
| 供应商锁定 | 多 provider 抽象层;标准化 prompt 模板可跨模型复用 |
|
|||
|
|
| LLM 幻觉(生成不合理配方) | 结果后处理校验(比例总和 100%、成分存在性检查) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 参考
|
|||
|
|
|
|||
|
|
- ADR-0001: 整体技术栈选型
|
|||
|
|
- PRD: `.scratch/formula-rd-platform/PRD.md`
|
|||
|
|
- OpenAI Structured Outputs: https://platform.openai.com/docs/guides/structured-outputs
|
|||
|
|
- pgvector: https://github.com/pgvector/pgvector
|