TravelContentCreator/docs/ARCHITECTURE_ANALYSIS.md

900 lines
37 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.

# AIGC 架构深度分析与改造方案
> 文档版本: 1.0.0
> 更新日期: 2024-12-09
> 状态: 待实施
---
## 1. 当前架构概览
### 1.1 系统组成
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 前端 (Vue/React) │
│ - 用户交互界面 │
│ - WebSocket 接收进度推送 │
│ - HTTP API 调用 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Java 后端 (Spring Boot) │
│ - 用户认证与权限 │
│ - 数据库 CRUD (MySQL) │
│ - 消息队列调度 (RabbitMQ) │
│ - WebSocket 推送 │
│ - API 次数管理 │
│ - 任务状态管理 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Python 后端 (FastAPI) │
│ - AI/LLM 调用 │
│ - Prompt 管理与渲染 │
│ - 海报图片生成 │
│ - 内容生成逻辑 │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 1.2 核心业务流程
#### NoteCreator (笔记生成) 完整流程
```
用户点击"生成笔记"
┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. NoteCreatorController (Java) │
│ - 接收请求: userId, packageIds, styleIds, audienceIds, dates, numTopics │
│ - 创建 NoteCreatorTask 记录 (状态: PENDING) │
│ - 发送消息到 RabbitMQ (NOTE_CREATOR_QUEUE_HIGH/NORMAL) │
│ - 立即返回 taskId 给前端 │
└─────────────────────────────────────────────────────────────────────────────┘
│ RabbitMQ 异步消费
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. NoteCreatorQueueConsumer (Java) │
│ - 更新任务状态: PROCESSING │
│ - 推送 WebSocket: 任务开始 (1%) │
│ - 调用 NoteCreatorGenerateService.executeFullNoteCreator() │
│ - 接收进度回调,推送 WebSocket (5% → 20% → 80% → 90% → 100%) │
│ - 更新任务状态: SUCCESS/FAILED │
│ - ACK 消息 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 3. NoteCreatorGenerateServiceImpl (Java) │
│ │
│ 步骤 1: 生成选题 (5% → 20%) │
│ ├── 将 packageIds 转换为 productIds + scenicSpotIds │
│ ├── 调用 TopicGenerateService.generateAndSaveTopic() │
│ └── 推送进度: 20% │
│ │
│ 步骤 2: 预扣除 API 次数 │
│ │
│ 步骤 3: 生成内容 (20% → 90%) │
│ ├── 遍历每个选题 │
│ ├── 调用 ContentGenerateService.generateAndSaveContents() │
│ ├── 每完成一篇,推送进度 + 新结果 (流式推送) │
│ └── 保存到数据库 │
│ │
│ 步骤 4: 处理失败退款 │
│ │
│ 步骤 5: 返回结果 (90% → 100%) │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4. TopicGenerateServiceImpl (Java) │
│ │
│ 当前实现 (有问题): │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Map<String, Object> requestBody = new HashMap<>(); │ │
│ │ requestBody.put("dates", datesStr); │ │
│ │ requestBody.put("numTopics", request.getNumTopics()); │ │
│ │ requestBody.put("styleIds", request.getStyleIds()); // ⚠️ ID │ │
│ │ requestBody.put("audienceIds", request.getAudienceIds()); // ⚠️ ID │ │
│ │ requestBody.put("scenicSpotIds", ...); // ⚠️ ID │ │
│ │ requestBody.put("productIds", ...); // ⚠️ ID │ │
│ │ │ │
│ │ // 调用旧接口 │ │
│ │ externalServiceClient.post("content-generate", "topics", ...); │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
│ HTTP 同步调用
┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. Python FastAPI │
│ │
│ 旧接口 (已废弃): │
│ POST /api/v1/tweet/topics │
│ - 接收 ID 列表 │
│ - 查询数据库获取完整信息 ← ⚠️ Python 访问数据库 │
│ - 构建 Prompt │
│ - 调用 LLM │
│ - 返回选题列表 │
│ │
│ 新接口 (V2): │
│ POST /api/v2/aigc/execute │
│ - 接收完整对象 │
│ - 直接使用 PromptRegistry 渲染 │
│ - 调用 LLM │
│ - 返回结果 │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## 2. 问题分析
### 2.1 数据库双端访问问题
**现状**
```
Java 端:
- 存储 景区(ScenicSpot)、产品(Product)、风格(Style)、人群(Audience)
- 存储 选题(Topic)、内容(Content)、任务(Task)
Python 端:
- 也需要查询 景区、产品、风格、人群 信息来构建 Prompt
- 导致两端都需要数据库连接
```
**问题**
1. 数据一致性风险
2. 连接池资源浪费
3. 部署复杂度增加
4. Python 端需要维护 ORM 模型
### 2.2 ID vs 完整对象
**现状**
```java
// Java 端传递 ID
requestBody.put("scenicSpotIds", Arrays.asList("1", "2", "3"));
```
```python
# Python 端需要查数据库
scenic_spots = db.query(ScenicSpot).filter(ScenicSpot.id.in_(ids)).all()
```
**问题**
1. 额外的数据库查询
2. ID 可能无效或已删除
3. 数据版本不一致
### 2.3 Prompt 管理分散
**现状**
```
Python 端:
prompts/
├── topic_generate/v1.0.0.yaml
├── content_generate/v1.0.0.yaml
├── style/gonglue/v1.0.0.yaml
└── audience/qinzi/v1.0.0.yaml
Java 端:
- 需要知道有哪些风格/人群可选
- 需要展示风格/人群的名称和描述
- 但这些信息在 Python 端的 YAML 文件中
```
**问题**
1. Java 端无法动态获取可用的风格/人群列表
2. 前端下拉框的选项需要硬编码或单独维护
3. 新增风格/人群需要两端同步修改
---
## 3. 目标架构 (方案 A: Python 作为纯计算服务)
### 3.1 职责划分
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Java 后端 │
│ │
│ 职责: │
│ ✅ 用户认证与权限管理 │
│ ✅ 所有数据库 CRUD 操作 │
│ ✅ 消息队列调度 (RabbitMQ) │
│ ✅ WebSocket 进度推送 │
│ ✅ 任务状态管理 │
│ ✅ API 次数管理 │
│ ✅ 查询并组装完整数据对象 │
│ ✅ 提供风格/人群配置 API (从 Python 获取或本地缓存) │
│ │
│ 不做: │
│ ❌ Prompt 模板管理 │
│ ❌ LLM 调用 │
│ ❌ 图片处理 │
└─────────────────────────────────────────────────────────────────────────────┘
│ HTTP (同步调用,传完整对象)
┌─────────────────────────────────────────────────────────────────────────────┐
│ Python 后端 │
│ │
│ 职责: │
│ ✅ Prompt 模板管理 (PromptRegistry) │
│ ✅ Prompt 渲染 (Jinja2) │
│ ✅ LLM 调用 │
│ ✅ 海报图片生成 │
│ ✅ 提供风格/人群配置查询 API │
│ │
│ 不做: │
│ ❌ 数据库访问 │
│ ❌ 用户认证 │
│ ❌ 任务状态管理 │
│ ❌ 消息队列 │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 3.2 新的调用流程
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Java: TopicGenerateServiceImpl │
│ │
│ // 1. 从数据库查询完整对象 │
│ ScenicSpot scenicSpot = scenicSpotService.getById(scenicSpotId); │
│ Product product = productService.getById(productId); │
│ Style style = styleService.getById(styleId); │
│ Audience audience = audienceService.getById(audienceId); │
│ │
│ // 2. 构建请求体 (完整对象) │
│ Map<String, Object> request = Map.of( │
│ "engine", "topic_generate", │
│ "params", Map.of( │
│ "month", "2025-01", │
│ "num_topics", 5, │
│ "scenic_spot", Map.of( │
│ "id", scenicSpot.getId(), │
│ "name", scenicSpot.getName(), │
│ "description", scenicSpot.getDescription(), │
│ "address", scenicSpot.getAddress(), │
│ "highlights", scenicSpot.getHighlights() │
│ ), │
│ "product", Map.of( │
│ "id", product.getId(), │
│ "name", product.getName(), │
│ "price", product.getPrice(), │
│ "description", product.getDescription() │
│ ), │
│ "style", Map.of( │
│ "id", style.getId(), │
│ "name", style.getName() │
│ ), │
│ "audience", Map.of( │
│ "id", audience.getId(), │
│ "name", audience.getName() │
│ ) │
│ ) │
│ ); │
│ │
│ // 3. 调用 Python V2 接口 │
│ Response response = externalServiceClient.post( │
│ "content-generate", │
│ "/api/v2/aigc/execute", │
│ request, │
│ TopicGenerateResponse.class │
│ ); │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ Python: /api/v2/aigc/execute │
│ │
│ // 1. 解析请求 │
│ engine_id = request.engine # "topic_generate" │
│ params = request.params # 包含完整对象 │
│ │
│ // 2. 获取引擎并执行 │
│ engine = registry.get(engine_id) │
│ result = await engine.execute(params) │
│ │
│ // 3. 引擎内部 │
│ // - 使用 PromptRegistry 渲染 Prompt │
│ // - 调用 LLM │
│ // - 返回结果 │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## 4. Prompt 与配置管理方案
### 4.1 风格/人群配置的双端访问
**问题**Java 端需要展示风格/人群列表供用户选择,但配置在 Python 端。
**方案 1: Python 提供查询 API (推荐)**
```
Python 端提供 API:
GET /api/v2/aigc/prompts/list
返回:
{
"prompts": [
"topic_generate",
"content_generate",
"style/gonglue",
"style/tuijian",
"audience/qinzi",
"audience/zhoubianyou",
"audience/gaoshe"
]
}
GET /api/v2/aigc/prompts/styles
返回:
{
"styles": [
{
"id": "gonglue",
"name": "攻略风",
"description": "以实用信息为主,包含详细的游玩路线..."
},
{
"id": "tuijian",
"name": "极力推荐风",
"description": "热情推荐,强调产品亮点..."
}
]
}
GET /api/v2/aigc/prompts/audiences
返回:
{
"audiences": [
{
"id": "qinzi",
"name": "亲子向",
"description": "家庭出游,有小孩同行..."
},
...
]
}
```
**Java 端使用**
```java
// 启动时或定时刷新,缓存到本地
@Scheduled(fixedRate = 3600000) // 每小时刷新
public void refreshStylesAndAudiences() {
StylesResponse styles = pythonClient.get("/api/v2/aigc/prompts/styles");
AudiencesResponse audiences = pythonClient.get("/api/v2/aigc/prompts/audiences");
// 缓存到 Redis 或内存
cache.put("styles", styles.getStyles());
cache.put("audiences", audiences.getAudiences());
}
// 前端请求时返回缓存
@GetMapping("/styles")
public List<StyleVO> getStyles() {
return cache.get("styles");
}
```
**方案 2: 配置同步到数据库**
```
Python 端:
- 仍然是 Prompt 的权威来源
- 提供导出 API
Java 端:
- 定时从 Python 同步配置到数据库
- 前端从 Java 数据库读取
问题:
- 数据可能不一致
- 需要同步机制
```
**推荐方案 1**,因为:
- Python 是 Prompt 的唯一权威来源
- 无数据一致性问题
- Java 只做缓存,不存储
### 4.2 Prompt YAML 结构规范
```yaml
# prompts/style/gonglue/v1.0.0.yaml
meta:
name: style_gonglue
version: "1.0.0"
description: "攻略风文案风格"
author: "team"
created_at: "2024-12-08"
# 用于 Java 端展示的元信息
style_id: "gonglue" # 唯一标识
style_name: "攻略风" # 显示名称
style_description: "以实用信息为主,包含详细的游玩路线、时间安排、费用预算等"
style_icon: "📝" # 可选,前端图标
style_order: 1 # 排序权重
# 风格提示词内容 (用于 Prompt 渲染)
content: |
你是景区小红书爆款文案策划...
```
```yaml
# prompts/audience/qinzi/v1.0.0.yaml
meta:
name: audience_qinzi
version: "1.0.0"
description: "亲子向用户画像"
# 用于 Java 端展示的元信息
audience_id: "qinzi"
audience_name: "亲子向"
audience_description: "家庭出游,有小孩同行,关注安全性和趣味性"
audience_icon: "👨‍👩‍👧"
audience_order: 1
# 人群提示词内容
content: |
用户画像:亲子家庭
- 年龄构成父母25-45岁孩子3-12岁
- 出游特点:注重安全、趣味、教育意义
...
```
---
## 5. 接口规范
### 5.1 Python V2 API
#### 5.1.1 执行引擎
```
POST /api/v2/aigc/execute
请求体:
{
"engine": "topic_generate",
"params": {
"month": "2025-01",
"num_topics": 5,
"scenic_spot": {
"id": 1,
"name": "天津冒险湾",
"description": "天津最大的水上乐园...",
"address": "天津市滨海新区...",
"highlights": ["水上过山车", "儿童戏水区"]
},
"product": {
"id": 10,
"name": "家庭套票",
"price": 299,
"original_price": 399,
"description": "含2大1小门票..."
},
"style": {
"id": "gonglue",
"name": "攻略风"
},
"audience": {
"id": "qinzi",
"name": "亲子向"
}
}
}
响应体:
{
"success": true,
"data": {
"topics": [
{
"index": 1,
"date": "2025-01-15",
"title": "寒假遛娃好去处",
"logic": "寒假期间家庭出游需求旺盛..."
},
...
]
},
"execution_time": 12.5,
"metadata": {
"model": "gpt-4",
"prompt_version": "v1.0.0",
"tokens_used": 1500
}
}
```
#### 5.1.2 查询风格列表
```
GET /api/v2/aigc/config/styles
响应体:
{
"styles": [
{
"id": "gonglue",
"name": "攻略风",
"description": "以实用信息为主...",
"icon": "📝",
"order": 1
},
{
"id": "tuijian",
"name": "极力推荐风",
"description": "热情推荐,强调亮点...",
"icon": "🔥",
"order": 2
}
],
"count": 2
}
```
#### 5.1.3 查询人群列表
```
GET /api/v2/aigc/config/audiences
响应体:
{
"audiences": [
{
"id": "qinzi",
"name": "亲子向",
"description": "家庭出游...",
"icon": "👨‍👩‍👧",
"order": 1
},
...
],
"count": 3
}
```
### 5.2 Java 端改造清单
#### 5.2.1 TopicGenerateServiceImpl 改造
```java
// 改造前
private TopicGenerateResponse callAIService(TopicGenerateRequest request) {
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("dates", datesStr);
requestBody.put("styleIds", request.getStyleIds()); // ❌ 传 ID
requestBody.put("audienceIds", request.getAudienceIds()); // ❌ 传 ID
requestBody.put("scenicSpotIds", request.getScenicSpotIds());
requestBody.put("productIds", request.getProductIds());
return externalServiceClient.post(SERVICE_NAME, "topics", requestBody, ...);
}
// 改造后
private TopicGenerateResponse callAIService(TopicGenerateRequest request) {
// 1. 查询完整对象
List<ScenicSpot> scenicSpots = scenicSpotService.listByIds(request.getScenicSpotIds());
List<Product> products = productService.listByIds(request.getProductIds());
Style style = styleConfigService.getById(request.getStyleIds().get(0));
Audience audience = audienceConfigService.getById(request.getAudienceIds().get(0));
// 2. 构建 V2 请求
Map<String, Object> params = new HashMap<>();
params.put("month", datesStr);
params.put("num_topics", request.getNumTopics());
params.put("scenic_spot", convertToMap(scenicSpots.get(0)));
params.put("product", convertToMap(products.get(0)));
params.put("style", Map.of("id", style.getId(), "name", style.getName()));
params.put("audience", Map.of("id", audience.getId(), "name", audience.getName()));
Map<String, Object> requestBody = Map.of(
"engine", "topic_generate",
"params", params
);
// 3. 调用 V2 接口
return externalServiceClient.post(SERVICE_NAME, "/api/v2/aigc/execute", requestBody, ...);
}
private Map<String, Object> convertToMap(ScenicSpot spot) {
Map<String, Object> map = new HashMap<>();
map.put("id", spot.getId());
map.put("name", spot.getName());
map.put("description", spot.getDescription());
map.put("address", spot.getAddress());
map.put("traffic_info", spot.getTrafficInfo());
map.put("highlights", spot.getHighlights());
map.put("opening_hours", spot.getOpeningHours());
return map;
}
```
#### 5.2.2 ContentGenerateServiceImpl 改造
```java
// 改造后
private ContentGenerateResponse callAIService(ContentGenerateRequest.ContentsInfo request) {
// 1. 查询完整对象
ScenicSpot scenicSpot = scenicSpotService.getById(request.getScenicSpotIds().get(0));
Product product = productService.getById(request.getProductIds().get(0));
Style style = styleConfigService.getById(request.getStyleIds().get(0));
Audience audience = audienceConfigService.getById(request.getAudienceIds().get(0));
// 2. 构建 V2 请求
Map<String, Object> params = new HashMap<>();
params.put("topic", request.getTopic());
params.put("scenic_spot", convertToMap(scenicSpot));
params.put("product", convertToMap(product));
params.put("style", Map.of("id", style.getId(), "name", style.getName()));
params.put("audience", Map.of("id", audience.getId(), "name", audience.getName()));
params.put("enable_judge", true);
Map<String, Object> requestBody = Map.of(
"engine", "content_generate",
"params", params
);
return externalServiceClient.post(SERVICE_NAME, "/api/v2/aigc/execute", requestBody, ...);
}
```
#### 5.2.3 ExternalServicesConfig 改造
```yaml
# application.yml
external-services:
content-generate:
enabled: true
base-url: http://localhost:8000
connect-timeout: 30
read-timeout: 120
endpoints:
# 旧接口 (待废弃)
topics: /api/v1/tweet/topics
content: /api/v1/tweet/content
# 新接口 (V2)
execute: /api/v2/aigc/execute
styles: /api/v2/aigc/config/styles
audiences: /api/v2/aigc/config/audiences
engines: /api/v2/aigc/engines
```
#### 5.2.4 新增 StyleConfigService
```java
@Service
public class StyleConfigService {
@Autowired
private ExternalServiceClient pythonClient;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String CACHE_KEY_STYLES = "aigc:config:styles";
private static final String CACHE_KEY_AUDIENCES = "aigc:config:audiences";
/**
* 获取所有风格配置
*/
public List<StyleConfig> getAllStyles() {
// 1. 先查缓存
List<StyleConfig> cached = (List<StyleConfig>) redisTemplate.opsForValue().get(CACHE_KEY_STYLES);
if (cached != null) {
return cached;
}
// 2. 从 Python 获取
StylesResponse response = pythonClient.get("content-generate", "styles", StylesResponse.class);
List<StyleConfig> styles = response.getStyles();
// 3. 缓存 1 小时
redisTemplate.opsForValue().set(CACHE_KEY_STYLES, styles, 1, TimeUnit.HOURS);
return styles;
}
/**
* 根据 ID 获取风格
*/
public StyleConfig getById(String styleId) {
return getAllStyles().stream()
.filter(s -> s.getId().equals(styleId))
.findFirst()
.orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "风格不存在: " + styleId));
}
/**
* 刷新缓存
*/
@Scheduled(fixedRate = 3600000)
public void refreshCache() {
try {
redisTemplate.delete(CACHE_KEY_STYLES);
redisTemplate.delete(CACHE_KEY_AUDIENCES);
getAllStyles();
getAllAudiences();
log.info("风格/人群配置缓存已刷新");
} catch (Exception e) {
log.error("刷新配置缓存失败", e);
}
}
}
```
---
## 6. 迁移计划
### 6.1 阶段划分
```
阶段 1: Python 端准备 (已完成 ✅)
├── [x] 实现 PromptRegistry
├── [x] 迁移所有 Prompt 到 YAML
├── [x] 实现 V2 引擎 (topic_generate, content_generate, poster_generate)
├── [x] 实现 /api/v2/aigc/execute 接口
└── [x] 删除旧的数据库访问代码
阶段 2: Python 端补充 (待实施)
├── [ ] 实现 /api/v2/aigc/config/styles 接口
├── [ ] 实现 /api/v2/aigc/config/audiences 接口
├── [ ] 完善 Prompt YAML 的 meta 信息
└── [ ] 添加接口文档
阶段 3: Java 端改造 (待实施)
├── [ ] 新增 StyleConfigService
├── [ ] 新增 AudienceConfigService
├── [ ] 改造 TopicGenerateServiceImpl
├── [ ] 改造 ContentGenerateServiceImpl
├── [ ] 改造 PosterGenerateServiceImpl
├── [ ] 更新 ExternalServicesConfig
└── [ ] 添加配置缓存机制
阶段 4: 联调测试 (待实施)
├── [ ] 单元测试
├── [ ] 集成测试
├── [ ] 性能测试
└── [ ] 回归测试
阶段 5: 上线切换 (待实施)
├── [ ] 灰度发布
├── [ ] 监控告警
├── [ ] 旧接口废弃
└── [ ] 文档更新
```
### 6.2 兼容性策略
```
过渡期间:
1. Python 端同时保留 V1 和 V2 接口
2. Java 端逐步切换到 V2 接口
3. 通过配置开关控制使用哪个版本
4. 完全切换后删除 V1 接口
配置示例:
external-services:
content-generate:
use-v2-api: true # 开关
```
---
## 7. 风险评估
| 风险 | 等级 | 影响 | 缓解措施 |
|-----|------|------|---------|
| Python 服务不可用 | 🔴 高 | 所有 AIGC 功能不可用 | 健康检查、自动重启、降级提示 |
| 配置缓存过期 | 🟡 中 | 风格/人群列表可能不一致 | 定时刷新、手动刷新接口 |
| 接口响应超时 | 🟡 中 | 用户体验差 | 合理超时设置、进度推送 |
| Prompt 渲染错误 | 🟡 中 | 生成内容质量差 | 变量验证、默认值、错误提示 |
| 数据对象字段缺失 | 🟢 低 | Prompt 渲染不完整 | 字段校验、默认值填充 |
---
## 8. 附录
### 8.1 数据对象字段规范
#### ScenicSpot (景区)
```json
{
"id": 1,
"name": "天津冒险湾",
"description": "天津最大的水上乐园,拥有多种刺激水上项目和亲子设施",
"address": "天津市滨海新区海滨大道",
"location": "天津市",
"traffic_info": "地铁9号线直达自驾可走津滨高速",
"highlights": ["水上过山车", "儿童戏水区", "漂流河"],
"opening_hours": "09:00-18:00",
"ticket_info": "成人票 199 元,儿童票 99 元",
"tips": "建议自带泳衣,园区内也有售卖"
}
```
#### Product (产品)
```json
{
"id": 10,
"name": "家庭套票",
"price": 299,
"original_price": 399,
"description": "含2大1小门票赠送储物柜",
"includes": ["2张成人票", "1张儿童票", "储物柜1个"],
"valid_period": "2025-01-01 至 2025-03-31",
"usage_rules": "需提前1天预约入园当日有效"
}
```
#### Style (风格)
```json
{
"id": "gonglue",
"name": "攻略风"
}
```
#### Audience (人群)
```json
{
"id": "qinzi",
"name": "亲子向"
}
```
#### Topic (选题)
```json
{
"index": 1,
"date": "2025-01-15",
"title": "寒假遛娃好去处",
"object": "天津冒险湾",
"product": "家庭套票",
"style": "攻略风",
"targetAudience": "亲子向",
"logic": "寒假期间家庭出游需求旺盛,水上乐园是热门选择"
}
```
### 8.2 错误码规范
| 错误码 | 说明 | HTTP 状态码 |
|-------|------|------------|
| ENGINE_NOT_FOUND | 引擎不存在 | 404 |
| INVALID_PARAMS | 参数校验失败 | 400 |
| PROMPT_NOT_FOUND | Prompt 不存在 | 404 |
| PROMPT_RENDER_ERROR | Prompt 渲染失败 | 500 |
| LLM_ERROR | LLM 调用失败 | 500 |
| TIMEOUT | 执行超时 | 504 |
---
## 9. 变更记录
| 版本 | 日期 | 作者 | 变更内容 |
|-----|------|------|---------|
| 1.0.0 | 2024-12-09 | Team | 初始版本 |