4S 美学分析流程优化方案
一、当前执行流程与耗时分析
1.1 执行时序(完整串行)
总耗时 = T1(Style Top3) + T2(完整4S报告) + T3(安全检查) + T4(项目映射)
其中:
T1 ≈ 5-15秒(LLM调用1:Style分类)
T2 ≈ 10-25秒(LLM调用2:完整报告,包含4S全部维度)
T3 ≈ 0.1秒(本地文本处理)
T4 ≈ 0.1秒(本地映射)
1.2 耗时主要来源
|
步骤 |
耗时占比 |
代码位置 |
原因分析 |
|---|---|---|---|
|
Stage 1: Style Top3 |
30-40% |
|
VLM视觉理解+复杂的九型判定逻辑 |
|
Stage 2: 完整4S报告 |
60-70% |
|
单次调用生成8个维度,token量大 |
|
安全检查+项目映射 |
<1% |
|
纯本地计算,可忽略 |
1.3 关键瓶颈:Stage 2 的单次调用过大
在 domain/prompts2step.py 中,Stage 2 的系统提示词长达 200+行,要求模型一次性生成:
-
Stype(分相)
-
Style(分型)
-
Subzone(6个分区)
-
Struct(4个分层)
-
radar_scores(4个分数)
-
aesthetic_optimization(方向建议)
-
project_directions(2-4个项目)
这导致:
-
模型思考时间长(需要同时处理8个维度)
-
max_tokens=1800,生成内容多
-
temperature=0.25,模型更谨慎,推理慢
二、异步优化可行性分析
2.1 四个维度的依赖关系
┌─────────────────────────────────────────────────────────────────┐
│ 原始依赖关系 │
├─────────────────────────────────────────────────────────────────┤
│ Stage 1: Style Top3 │
│ │ │
│ ▼ │
│ Stage 2: 完整报告 (全部4S维度在一次调用中生成) │
│ │ │
│ ├── Stype ← 独立(只依赖图片) │
│ ├── Style ← 依赖 Stage 1 的 Top1 │
│ ├── Subzone ← 独立(只依赖图片) │
│ └── Struct ← 独立(只依赖图片) │
└─────────────────────────────────────────────────────────────────┘
核心发现:
-
✅ Stype、Subzone、Struct 三个维度之间没有依赖关系,且不依赖 Stage 1 的结果
-
⚠️ Style 依赖 Stage 1 的 Top1 结果(当前设计)
-
❌ radar_scores、aesthetic_optimization、project_directions 依赖四个维度的分数和内容
2.2 异步优化方案对比
|
方案 |
并行维度 |
预期提速 |
复杂度 |
一致性风险 |
|---|---|---|---|---|
|
方案A |
保持现状 |
0% |
低 |
无 |
|
方案B |
Stage1 + (Stype+Subzone+Struct) |
40-50% |
中 |
低 |
|
方案C |
四个维度各自独立 |
60-70% |
高 |
中 |
2.3 推荐方案:方案B(Stage 1 与三个独立维度并行)
优化思路:
-
Stage 1(Style Top3)与 Stype、Subzone、Struct 的分析同时启动
-
Style 等待 Stage 1 完成后单独生成
-
最后合并结果,生成 radar_scores 和优化建议
优化后时序:
┌─────────────────────────────────────────────────────────────┐
│ 时间轴 → │
│ │
│ ┌──────────────┐ │
│ │ Stage 1 │ ← Style Top3 (5-15s) │
│ │ Style分类 │ │
│ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ Style生成 │ ← 等待Stage1完成后启动 (3-8s) │
│ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Stype分相 │ │ Subzone分区 │ │ Struct分层 │ ← 并行 │
│ │ (3-8s) │ │ (3-8s) │ │ (3-8s) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ 并行完成后合并 → radar_scores + 优化建议 │
└─────────────────────────────────────────────────────────────┘
预期耗时对比:
-
优化前:T1 + T2 ≈ 15-40秒
-
优化后:max(T1, T_stype+T_subzone+T_struct) + T_style ≈ 8-20秒
三、代码层面的优化建议
3.1 当前代码的关键串行点
在 services/llm.py#L108-L223 的 _call_two_stage_provider() 方法中:
# 串行点1:Stage 1 必须先完成
style_response = self._create_completion(...) # 等待完成
final_style = style_top3[0]["label"]
# 串行点2:Stage 2 在 Stage 1 之后
report_response = self._create_completion(...) # 等待完成
3.2 异步优化的技术实现
方案B的代码结构:
# 使用 asyncio 并行调用
import asyncio
async def _call_two_stage_parallel_provider(self, ...):
# 同时启动多个任务
tasks = [
self._async_create_completion(..., prompt=STYLE_TOP3_PROMPT), # Stage 1
self._async_create_completion(..., prompt=STYPE_PROMPT), # Stype
self._async_create_completion(..., prompt=SUBZONE_PROMPT), # Subzone
self._async_create_completion(..., prompt=STRUCT_PROMPT), # Struct
]
# 等待所有任务完成
style_result, stype_result, subzone_result, struct_result = await asyncio.gather(*tasks)
# 提取 Style Top1
final_style = style_top3[0]["label"]
# 生成 Style 详细内容(依赖 Stage 1 结果)
style_detail = await self._async_create_completion(..., prompt=STYLE_DETAIL_PROMPT.format(final_style))
# 合并结果
return self._merge_results(...)
3.3 需要新增的提示词
|
新提示词文件 |
内容 |
说明 |
|---|---|---|
|
|
只生成 Stype 分相 |
独立,不依赖其他维度 |
|
|
只生成 Subzone 分区 |
独立,不依赖其他维度 |
|
|
只生成 Struct 分层 |
独立,不依赖其他维度 |
|
|
基于固定 Style 生成详细描述 |
依赖 Stage 1 的 Top1 |
四、结论
4.1 核心回答
是的,异步调用整体会更快,但需要合理设计并行策略:
-
可以并行的维度:
-
✅ Stype、Subzone、Struct 三个维度完全独立,可以并行
-
✅ Stage 1(Style Top3)与上述三个维度可以并行启动
-
-
不能完全并行的维度:
-
⚠️ Style 的详细描述依赖 Stage 1 的 Top1 结果,需要等待
-
⚠️ radar_scores、aesthetic_optimization 需要所有维度的分数
-
-
预期提速:
-
方案B(推荐):40-50% 的耗时减少
-
如果四个维度完全独立:60-70% 的耗时减少
-
4.2 当前耗时的主要原因
-
两阶段串行:Stage 2 必须等待 Stage 1 完成
-
单次调用过大:Stage 2 在一次调用中生成全部8个维度
-
模型参数保守:temperature=0.25 导致推理较慢
4.3 建议的优化路径
第一步:拆分提示词,为每个维度创建独立的提示词文件
第二步:修改 LLMService,使用 asyncio 实现并行调用
第三步:调整 AnalysisService,支持异步分析
第四步:测试并验证四个维度的一致性和质量
五、风险评估
5.1 一致性风险
风险描述:当前单次调用保证了4S维度的跨维度一致性,例如"甜美少女型"的眉眼分区分数自然会偏高。拆分为独立调用后,各维度可能产生矛盾结论。
影响程度:高
应对策略:
-
在合并结果阶段增加一致性校验,检测维度间的矛盾(如 Style 为"飒爽前卫型"但 Stype 为"皮相主导")
-
设置分数阈值,当某维度分数与 Style 类型严重不符时,触发重新生成或降级处理
-
保留 Style 对其他维度的参考作用,在 Stype/Subzone/Struct 的提示词中注入 Style 类型信息
5.2 成本影响
风险描述:每次 LLM 调用都要发送完整图片,从 2 次调用变为 5 次调用,图片 token 消耗翻 2.5 倍,API 成本显著增加。
影响程度:高
应对策略:
-
方案B(推荐):仅并行 Stage 1 + Stype + Subzone + Struct,Style 在 Stage 1 完成后单独生成(共 5 次调用)
-
方案B+缓存优化:如果使用支持图片缓存的模型服务,可复用图片 token,降低成本
-
成本监控:实施前先进行小流量测试,评估实际成本变化
5.3 提示词质量回归风险
风险描述:拆分后的独立提示词需要大量测试才能达到当前单次调用的质量水平,可能出现维度分析不完整或不准确的情况。
影响程度:中
应对策略:
-
A/B 对比实验:先实现并行版本,但默认使用串行版本,通过配置开关进行 A/B 测试
-
人工审核:对并行版本的输出进行人工审核,对比串行版本的质量差异
-
迭代优化:根据测试结果逐步调整独立提示词,确保质量达标
5.4 技术实现风险
风险描述:当前使用同步 OpenAI 客户端,异步化需要切换到 AsyncOpenAI,且 FastAPI 的 async endpoint 需注意不要在 async def 中调用同步阻塞操作。
影响程度:中
应对策略:
-
使用
AsyncOpenAI客户端替代同步客户端 -
确保所有阻塞操作(如文件 I/O、同步 API 调用)都放在
asyncio.to_thread()中执行 -
增加超时控制和异常处理,防止某个维度调用超时影响整体流程
六、技术实现细节
6.1 OpenAI 客户端切换
当前代码使用同步客户端:
from openai import OpenAI
client = OpenAI(api_key=api_key, base_url=base_url)
response = client.chat.completions.create(...)
需要切换为异步客户端:
from openai import AsyncOpenAI
client = AsyncOpenAI(api_key=api_key, base_url=base_url)
response = await client.chat.completions.create(...)
6.2 FastAPI 异步端点
# 当前同步端点
@app.post("/analyze")
def analyze(request: AnalysisRequest):
service = AnalysisService(settings)
result = service.analyze_url(request.image_url)
return result
# 异步端点改造
@app.post("/analyze")
async def analyze(request: AnalysisRequest):
service = AnalysisService(settings)
result = await service.analyze_url(request.image_url)
return result
6.3 Asyncio 并行调用模式
import asyncio
async def _call_parallel_provider(self, *, base_url, api_key, model, image_url):
"""并行调用多个维度分析"""
client = AsyncOpenAI(api_key=api_key, base_url=base_url)
# 并行任务组1:Stage 1 + 三个独立维度
tasks_group1 = [
self._async_create_completion(client, model, image_url, STYLE_TOP3_SYSTEM_PROMPT, STYLE_TOP3_USER_PROMPT),
self._async_create_completion(client, model, image_url, STYPE_SYSTEM_PROMPT, STYPE_USER_PROMPT),
self._async_create_completion(client, model, image_url, SUBZONE_SYSTEM_PROMPT, SUBZONE_USER_PROMPT),
self._async_create_completion(client, model, image_url, STRUCT_SYSTEM_PROMPT, STRUCT_USER_PROMPT),
]
style_response, stype_response, subzone_response, struct_response = await asyncio.gather(*tasks_group1)
# 提取 Style Top1
style_content = style_response.choices[0].message.content or ""
style_result = extract_json_from_text(style_content)
style_top3 = self._extract_style_top3(style_result)
final_style = style_top3[0]["label"]
# 串行任务:Style 详细内容(依赖 Stage 1 结果)
style_detail_prompt = build_style_detail_prompt(final_style, style_top3)
style_detail_response = await self._async_create_completion(
client, model, image_url, STYLE_DETAIL_SYSTEM_PROMPT, style_detail_prompt
)
# 合并结果
return self._merge_4s_results(
style_result=style_result,
style_detail_response=style_detail_response,
stype_response=stype_response,
subzone_response=subzone_response,
struct_response=struct_response,
)
async def _async_create_completion(self, client, model, image_url, system_prompt, user_prompt, max_tokens=700, temperature=0.25):
messages = [
{"role": "system", "content": system_prompt},
{
"role": "user",
"content": [
{"type": "image_url", "image_url": {"url": image_url}},
{"type": "text", "text": user_prompt},
],
},
]
try:
return await client.chat.completions.create(
model=model,
temperature=temperature,
max_tokens=max_tokens,
extra_body={"enable_thinking": False},
messages=messages,
)
except Exception as exc:
if "enable_thinking" in str(exc) or "extra_body" in str(exc):
return await client.chat.completions.create(
model=model,
temperature=temperature,
max_tokens=max_tokens,
messages=messages,
)
raise
6.4 结果合并与一致性校验
def _merge_4s_results(self, style_result, style_detail_response, stype_response, subzone_response, struct_response):
"""合并四个维度的分析结果"""
# 解析各维度结果
stype_result = extract_json_from_text(stype_response.choices[0].message.content or "")
subzone_result = extract_json_from_text(subzone_response.choices[0].message.content or "")
struct_result = extract_json_from_text(struct_response.choices[0].message.content or "")
style_detail_result = extract_json_from_text(style_detail_response.choices[0].message.content or "")
# 合并为完整报告
report = {
"report_type": "4S 美学报告",
"stype": stype_result.get("stype", {}),
"style": style_detail_result.get("style", {}),
"subzone": subzone_result.get("subzone", {}),
"struct": struct_result.get("struct", {}),
}
# 一致性校验
warnings = self._validate_consistency(report)
if warnings:
logger.warning("4S report consistency warnings: %s", warnings)
# 标准化和分数兜底
report = normalize_4s_report(report)
report, score_warnings = validate_dynamic_scores(report)
return report
def _validate_consistency(self, report):
"""检查维度间的一致性"""
warnings = []
style_label = report.get("style", {}).get("label", "")
stype_label = report.get("stype", {}).get("label", "")
# 示例:飒爽前卫型通常是骨相主导
if style_label == "飒爽前卫型" and stype_label in ["肉相主导", "皮相主导"]:
warnings.append(f"Style={style_label} 与 Stype={stype_label} 可能存在矛盾")
return warnings
七、分阶段实施策略
阶段一:最小化验证(1-2周)
|
任务 |
内容 |
|---|---|
|
创建独立提示词 |
为 Stype、Subzone、Struct 创建独立的提示词文件 |
|
实现并行调用 |
修改 LLMService,添加异步并行调用方法 |
|
A/B 测试框架 |
添加配置开关,支持切换串行/并行模式 |
|
小规模测试 |
使用 10-20 张测试图片进行对比测试 |
阶段二:质量优化(2-3周)
|
任务 |
内容 |
|---|---|
|
人工审核 |
对比并行版本与串行版本的输出质量 |
|
提示词调优 |
根据审核结果调整独立提示词 |
|
一致性校验 |
实现维度间一致性校验逻辑 |
|
性能监控 |
添加耗时统计和成本监控 |
阶段三:全面推广(1周)
|
任务 |
内容 |
|---|---|
|
正式上线 |
将并行版本设为默认模式 |
|
灰度发布 |
通过灰度发布逐步扩大流量 |
|
持续监控 |
监控性能、成本和质量指标 |
|
优化迭代 |
根据线上反馈持续优化 |
八、4S 维度定义
8.1 Stype 分相
|
分相类型 |
定义 |
|---|---|
|
骨相主导 |
骨骼轮廓清晰,线条硬朗,骨感明显 |
|
肉相主导 |
软组织饱满,线条柔和,圆润感强 |
|
皮相主导 |
皮肤质感优秀,光泽均匀,肤质细腻 |
|
复合型 |
骨相、肉相、皮相特征混合,无明显主导 |
8.2 Style 分型
|
分型类型 |
核心特征 |
|---|---|
|
甜美少女型 |
卧蚕、苹果肌、圆润唇,幼态元气亲和 |
|
魅力时尚型 |
微菱形脸、丝滑颈部,都市摩登高级 |
|
盐系简洁型 |
直鼻、清爽干净,少年感克制精致 |
|
温婉柔美型 |
柔和苹果肌、温柔端庄,淑女成熟 |
|
自然优雅型 |
鹅蛋脸、骨肉均衡,比例协调大方 |
|
知性沉着型 |
微驼峰鼻、深人中沟,理性沉静清晰 |
|
浪漫性感型 |
微翘微翻唇、曲线主导,成熟妩媚女人味 |
|
华丽高雅型 |
大而上扬眼、高挺立体鼻,明艳大气立体 |
|
飒爽前卫型 |
颧弓、锋利下颌线,帅气犀利骨感 |
8.3 Subzone 分区
|
分区 |
关注点 |
|---|---|
|
额部 |
平整度、发际线衔接、饱满度 |
|
眉眼 |
眼型、眉毛形态、眼周状态 |
|
中面部 |
苹果肌、颊部线条、饱满度 |
|
鼻部 |
鼻梁高度、鼻头形态、比例协调 |
|
唇部 |
唇形、唇色、饱满度 |
|
下颌轮廓 |
下颌线条、轮廓清晰度、比例 |
8.4 Struct 分层
|
分层 |
关注点 |
|---|---|
|
皮肤层 |
肤质、肤色均匀度、清透感 |
|
皮下组织层 |
饱满度、柔和度、自然过渡 |
|
筋膜支撑层 |
紧致感、线条走向、支撑状态 |
|
骨骼轮廓层 |
轮廓框架、比例协调、结构感 |
九、代码文件索引
|
文件 |
功能 |
关键行 |
|---|---|---|
|
|
分析编排服务 |
主流程编排 |
|
|
LLM调用服务 |
两阶段串行调用 |
|
|
Stage 2 提示词 |
完整报告生成提示词 |
|
|
Stage 1 提示词 |
Style Top3 分类提示词 |
|
|
安全检查服务 |
敏感词检测与替换 |
|
|
雷达图数据处理 |
4S报告标准化 |
本文来自博客园,作者:limingqi,转载请注明原文链接:https://www.cnblogs.com/limingqi/p/21184718
浙公网安备 33010602011771号