Hola-Dermat:护肤AI助手
Hola-Dermat是一个个性化的护肤方案助手,它不只是向你推荐产品,而是真正理解你独特的皮肤需求、环境因素和偏好,为你量身定制早晚护肤流程。
AI编程/Vibe Coding 遇到问题需要帮助的,联系微信 ezpoda,免费咨询。
"找到合适的护肤产品就像大海捞针……只不过大海是整个互联网,而你甚至不确定针长什么样。"
如果你曾经在护肤品货架前站过,盯着一排排承诺能改变你皮肤的产品……你就知道这种挣扎。我需要透明质酸吗?视黄醇呢?这是SPF 30还是50?最重要的是,这对我的肤质、在我的气候下、配合我的生活方式真的有效吗?
这就是Hola-Dermat——一个个性化的护肤方案助手,它不只是向你推荐产品,而是真正理解你独特的皮肤需求、环境因素和偏好,为你量身定制早晚护肤流程。
但关键是:这不只是另一个聊天机器人。Hola-Dermat利用Qdrant的ACORN算法、Perplexity网络搜索和CrewAI的智能体工作流等前沿技术,解决了困扰推荐系统多年的问题:可怕的"零结果"问题。
出发!!
让我带你了解我们是如何构建这个系统的,为什么它很重要,以及这些技术如何协同工作创造出真正强大的东西……
1、问题:为什么护肤推荐系统是失败的
想象一下:你是一名住在印度海得拉巴的软件工程师。你的皮肤是混合干性,有点容易长痘。你每天花18小时盯着屏幕(是的,我看到你在点头……),很少晒太阳,你现在用的洗面奶让你的皮肤感觉像撒哈拉沙漠。

哦,我的皮肤……
现在,试着找到满足以下条件的产品:
- 适合混合干性、易长痘的皮肤
- 在海得拉巴(或至少印度)有售
- 解决过度使用屏幕造成的蓝光伤害
- 提供保湿但不厚重
- 价格不要太高
传统的推荐系统要么:
- 因为你的筛选条件"太具体"而返回零结果
- 给你不考虑区域可用性的通用产品
- 忽略紫外线指数、空气质量和湿度等环境因素
- 忘记你以前尝试过什么,不断推荐同样的东西
英雄登场!
这就是Hola-Dermat介入的地方。它不只是一个产品搜索引擎,而是一个智能助手,理解上下文,从你的历史中学习,并适应你的环境。
2、解决方案:对话式AI meets 向量搜索
魔法始于简单的对话。用户不需要填写50个字段的表单(说实话,没人想填),而是可以……聊天。
用户:"嗨,很高兴见到你。我的皮肤是混合型的,但稍微偏干,
而且容易长痘。我来自南印度,住在特伦甘纳邦的海得拉巴。
我是一名软件工程师,每天屏幕时间18小时,很少晒太阳。
我现在只用普通洗面奶,一天一次,但它让我的皮肤干得要命。"
从这条消息中,Hola-Dermat提取出:
- 肤质:混合干性,易长痘
- 原籍地区:南印度
- 当前位置:特伦甘纳邦海得拉巴
- 职业:软件工程师
- 屏幕时间:每天18小时(高)
- 日晒:少
- 当前产品问题:洗面奶导致过度干燥
这种智能解析通过模式匹配和自然语言理解实现,允许系统构建全面的用户画像,而无需传统表单的摩擦。
LLM(我们使用的是Claude Sonnet 4.5)然后使用这个画像来:
- 理解用户的具体需求
- 只在必要时提出澄清问题
- 生成自然的、对话式的回复
- 提供个性化的推荐,而不是模板化的
3、为什么使用向量数据库?语义搜索革命
事情变得有趣了。传统数据库擅长精确匹配:"找到所有SPF 50的产品"。但护肤是微妙的。用户可能会说"我需要适合干性敏感肌、帮助淡化黑斑的东西",系统需要理解这意味着含有烟酰胺、α-熊果苷或维生素C等成分的产品。
好吧,这很令人困惑……
向量数据库通过将文本转换为捕获语义含义的高维向量(嵌入)来解决这个问题。具有相似成分、功效或用例的产品在这个向量空间中彼此接近。
在Hola-Dermat中,我们使用Qdrant——一个在AI社区中获得严重关注的开源向量数据库。以下是我们如何设置产品集合:
def create_products_collection(client: QdrantClient) -> None:
"""使用ACORN配置创建产品集合。"""
collection_name = settings.PRODUCTS_COLLECTION
client.create_collection(
collection_name=collection_name,
vectors_config=VectorParams(
size=EMBEDDING_DIM, # sentence-transformers的384维
distance=Distance.COSINE,
),
optimizers_config=OptimizersConfigDiff(
indexing_threshold=10000,
),
hnsw_config=HnswConfigDiff(
m=16,
ef_construct=100,
),
)
每个产品被转换为捕获其本质的嵌入:
def create_product_text(product: dict) -> str:
"""从产品数据创建可搜索文本。"""
text_parts = [
product.get('name', ''),
product.get('brand', ''),
product.get('description', ''),
product.get('product_type', ''),
', '.join(product.get('skin_type_compatibility', [])),
', '.join(product.get('key_benefits', [])),
', '.join(product.get('ingredients', [])), # 成分作为列表
]
return ' '.join(text_parts)
# 生成嵌入
embedding = get_embedding(product_text) # 返回384维向量
当用户搜索"干性皮肤保湿的东西"时,系统:
- 将查询转换为向量
- 找到相似向量的产品(语义相似性)
- 按相关性排名- 返回最匹配的
但问题是……仅靠语义搜索还不够。你还需要筛选——这就是事情变得复杂的地方。
那可是个超级过滤器
4、零结果问题……当筛选成为你的敌人时 😈
想象一下,你正在搜索满足以下条件的产品:
- 适合混合干性皮肤
- 在印度有售
- 含有透明质酸
- 可在早上使用
- 价格低于50美元
在传统系统中,你会按顺序应用这些筛选器:
按肤质筛选 → 50个产品- 按地区筛选 → 20个产品- 按成分筛选 → 5个产品- 按使用方式筛选 → 2个产品- 按价格筛选 → 0个产品
游戏结束。零结果。用户沮丧,你失去了他们。
我不想失去客户
这是因为传统的筛选使用AND逻辑——每个条件都必须满足。但在现实世界中,产品可能是:
- 非常适合你的肤质,但在不同地区有售(但仍可发货)
- 含有相似成分(肽而不是透明质酸,但功效相同)
- 稍微超出预算但值得额外花费
Qdrant的ACORN算法正好解决了这个问题。
5、ACORN:改变一切的算法
ACORN(复杂OR查询导航算法)是Qdrant对零结果问题的回答。在Qdrant v1.16中引入,ACORN通过理解何时放宽约束来智能处理复杂筛选。
以下是它在Hola-Dermat中的工作原理:
def search_products(
query_text: str,
skin_type: Optional[str] = None,
regions: Optional[List[str]] = None,
usage: Optional[str] = None,
required_ingredients: Optional[List[str]] = None,
limit: int = 10
) -> List[Dict[str, Any]]:
# 构建ACORN筛选条件
filter_conditions = []
if skin_type:
filter_conditions.append(
FieldCondition(
key="skin_type_compatibility",
match=MatchValue(value=skin_type)
)
)
if regions:
# 产品必须至少在其中一个地区有售(OR逻辑)
filter_conditions.append(
Filter(
should=[ # "should"意味着至少一个必须匹配
FieldCondition(
key="regions_available",
match=MatchValue(value=region)
)
for region in regions
]
)
)
if required_ingredients:
# 至少一种成分必须匹配
ingredient_conditions = [
FieldCondition(
key="ingredients",
match=MatchValue(value=ingredient.lower().strip())
)
for ingredient in required_ingredients
]
filter_conditions.append(
Filter(should=ingredient_conditions)
)
# ACORN自动处理复杂筛选
query_filter = Filter(must=filter_conditions) if filter_conditions else None
# 执行混合搜索(语义 + 关键词)
results = client.search(
collection_name=collection_name,
query_vector=query_embedding,
query_filter=query_filter, # ACORN智能处理
limit=limit,
score_threshold=0.3,
)
ACORN的魔力在于它能够:
- 理解筛选器关系:它知道"在印度或亚洲有售"比要求两者更灵活
- 智能放宽约束:如果没有产品匹配所有筛选器,它可以放宽非关键条件
- 保持相关性:即使放宽筛选器,也确保结果在语义上仍然相关
- 处理嵌套条件:带有AND/OR逻辑的复杂筛选器不会破坏系统
在生产中,这意味着:
- 更高的转化率:用户即使要求具体也能找到产品
- 更好的用户体验:不再有令人沮丧的"无结果"页面
- 可扩展性:即使数百万产品和复杂查询也能高效工作
6、混合搜索:语义 + 关键词 = 两全其美
等等,等等,等等!!
等等,还有更多!Hola-Dermat不仅仅依赖语义搜索。我们使用混合搜索——结合语义相似性和关键词匹配。
# 语义搜索(理解含义)
results = client.search(
collection_name=collection_name,
query_vector=query_embedding, # 基于向量
query_filter=query_filter,
limit=limit,
)
# 关键词搜索(精确匹配)
keyword_results = client.scroll(
collection_name=collection_name,
scroll_filter=Filter(
must=[
FieldCondition(
key="text",
match=MatchText(text=query_text) # 基于文本
)
] + filter_conditions
),
limit=limit,
)
# 合并并去重
products = combine_results(results, keyword_results)
为什么两者都要?因为有时你需要精确匹配(如品牌名或特定成分名),有时你需要语义理解(如"干性皮肤的东西"匹配含有"补水"或"保湿"的产品)。
7、Perplexity:带来实时网络智能
Hola-Dermat变得非常智能的地方在这里。虽然Qdrant存储我们精心策划的产品数据库,但Perplexity带来实时网络智能。
7.1 为什么选择Perplexity?
- 最新信息:天气数据、紫外线指数、空气质量——全部实时更新
- 区域产品可用性:今天海得拉巴实际可售的产品可能不在我们的数据库中
- 最新评论和趋势:新产品、重新配方、停产商品
以下是我们如何集成Perplexity:
def research_weather_atmosphere(
region: str,
include_forecast: bool = True,
days_back: int = 3,
days_forward: int = 5
) -> Dict[str, Any]:
"""研究当前和预报的天气状况。"""
query = f"""提供{region}的详细天气和大气状况:
- 当前温度及过去{days_back}天的温度趋势
- 当前紫外线指数及接下来{days_forward}天的紫外线预报
- 当前空气质量指数(AQI)及空气质量趋势
- 湿度水平
- 任何影响皮肤健康的相关环境因素"""
payload = {
"model": "llama-3.1-sonar-large-128k-online",
"messages": [
{
"role": "system",
"content": "你是天气和环境数据专家……"
},
{
"role": "user",
"content": query
}
],
"temperature": 0.2,
"max_tokens": 2000
}
response = httpx.post(PERPLEXITY_API_URL, headers=headers, json=payload)
return response.json()
这些数据帮助Hola-Dermat理解:
- 防晒需求:紫外线指数高?推荐更强的SPF
- 保湿需求:湿度低?强调保湿产品
- 空气质量影响:AQI高?建议屏障修复产品
7.2 动态产品发现
但是,等等!如果Qdrant没有匹配用户需求的产品,Perplexity会在网络上搜索区域产品,然后我们自动将它们添加到Qdrant:
class QdrantAddProductTool(BaseTool):
"""用于向Qdrant数据库添加新产品的工具。"""
def _run(self, product: Dict[str, Any]) -> str:
"""执行产品添加。"""
success = add_product_to_collection(product)
return f"成功将产品'{product.get('name')}'添加到数据库"
这创建了一个自我改进的系统,随着时间的推移变得更智能。
8、多集合架构:产品和历史
Qdrant的多集合支持非常适合Hola-Dermat。我们维护两个独立的集合:
8.1 产品集合
存储所有护肤产品及其嵌入、元数据和可搜索文本。
payload = {
'id': product['id'],
'name': product['name'],
'brand': product['brand'],
'description': product['description'],
'product_type': product['product_type'],
'skin_type_compatibility': product['skin_type_compatibility'],
'regions_available': product['regions_available'],
'price_range': product['price_range'],
'usage': product['usage'],
'key_benefits': product['key_benefits'],
'ingredients': product['ingredients'], # 成分列表
'text': product_text, # 用于混合搜索
}
8.2 历史集合
跟踪用户交互、产品推荐、反馈和结果。
def update_user_history(
user_id: str,
interaction_type: str,
product_id: Optional[str] = None,
product_name: Optional[str] = None,
feedback: Optional[str] = None,
results: Optional[str] = None,
rating: Optional[int] = None
) -> bool:
"""在历史集合中存储用户交互。"""
history_text = f"{interaction_type} {product_name} {feedback} {results}"
embedding = get_embedding(history_text)
payload = {
"user_id": user_id,
"timestamp": datetime.now().isoformat(),
"interaction_type": interaction_type,
"product_id": product_id,
"product_name": product_name,
"feedback": feedback,
"results": results,
"rating": rating,
"text": history_text
}
client.upsert(
collection_name=settings.HISTORY_COLLECTION,
points=[PointStruct(id=point_id, vector=embedding, payload=payload)]
)
8.3 为什么分开集合?
- 不同用例:产品需要产品导向搜索;历史需要用户导向搜索
- 性能:更小、专注的集合查询更快
- 可扩展性:可以独立扩展每个集合
- 隐私:用户历史可以有不同的保留策略
8.4 历史的力量
当用户返回时,Hola-Dermat搜索他们的历史:
def search_user_history(
user_id: str,
query_text: Optional[str] = None,
limit: int = 20
) -> List[Dict[str, Any]]:
"""检索用户的历史交互。"""
filter_conditions = [
FieldCondition(
key="user_id",
match=MatchValue(value=user_id)
)
]
if query_text:
# 在用户历史中进行语义搜索
query_embedding = get_embedding(query_text)
results = client.search(
collection_name=settings.HISTORY_COLLECTION,
query_vector=query_embedding,
query_filter=Filter(must=filter_conditions),
limit=limit,
)
这允许系统:
- 记住用户尝试过什么产品
- 理解什么有效什么无效* 避免推荐他们已经拒绝的产品* 随着时间的推移构建个性化的偏好画像
9、CrewAI:编排智能体工作流
现在,这就是一切汇集的地方。CrewAI编排整个工作流,智能决定何时使用哪个工具。
9.1 什么是CrewAI?
CrewAI是一个构建智能体AI系统的框架——能够:
- 使用多个工具
- 自主决策
- 协同解决复杂问题
- 从行动中学习
在Hola-Dermat中,我们有一个单一智能体(护肤顾问),可以访问多个工具:
def create_skincare_agent() -> Agent:
"""创建主要的护肤专家智能体。"""
return Agent(
role='个性化护肤顾问',
goal='理解用户需求并提供个性化推荐',
backstory="""你是具有深入了解区域护肤实践、
影响皮肤健康的环境因素、
和产品配方的专家皮肤科医生……""",
llm=llm, # Claude Sonnet 4.5
tools=[
PerplexityWeatherTool(), # 天气研究
PerplexityProductResearchTool(), # 网络产品搜索
PerplexitySkinAnalysisTool(), # 环境分析
QdrantProductSearchTool(), # 产品数据库搜索
QdrantHistorySearchTool(), # 用户历史检索
QdrantHistoryUpdateTool(), # 历史更新
QdrantAddProductTool() # 添加新产品
]
)
9.2 智能体工作流
当用户请求推荐时,智能体:
- 检查用户历史:"这个用户以前尝试过什么?"
- 研究天气:"海得拉巴的紫外线指数和空气质量如何?"
- 分析环境:"这些条件如何影响护肤需求?"
- 搜索产品:"找到匹配用户画像的产品"
- 如果没有结果:"搜索Perplexity寻找区域产品"
- 添加新产品:"将发现的产品存储到Qdrant"
- 生成推荐:"创建早晚护肤方案"
- 更新历史:"记录推荐了什么"
所有这些都是自主发生的。智能体决定:
- 何时使用Perplexity vs Qdrant
- 应用哪些筛选器
- 如何组合来自多个来源的信息
- 向用户提出什么问题
9.3 任务定义
以下是我们如何定义智能体的任务:
task_description = f"""分析用户画像并创建个性化护肤推荐。
用户画像:
- 肤质:{profile_dict.get('skin_type')}
- 当前区域:{profile_dict.get('region_stay')}
- 职业:{profile_dict.get('occupation')}
- 屏幕时间:{profile_dict.get('screen_time')}
- 日晒:{profile_dict.get('sun_exposure')}
……
你的任务:
1. 检查用户历史以了解过去的产品使用情况
2. 研究{region}的天气状况
3. 分析区域因素如何影响皮肤需求
4. 搜索产品数据库寻找匹配产品
5. 如果没有找到产品,使用Perplexity研究区域产品
6. 将任何新产品添加到数据库
7. 创建全面的早晚护肤方案
8. 解释为什么选择每个产品
9. 静默更新用户历史(后台任务)
"""
智能体执行这个任务,沿途做出决策,并返回全面的推荐。
9.4 数据摄入:奠定基础
在所有这些魔法发生之前,我们需要用产品填充Qdrant。以下是我们的摄入流程:
def ingest_products(products: list[dict], client) -> None:
"""将产品摄入Qdrant。"""
collection_name = settings.PRODUCTS_COLLECTION
points = []
for product in products:
# 创建可搜索文本
product_text = create_product_text(product)
# 使用sentence-transformers生成嵌入
embedding = get_embedding(product_text)
# 创建带元数据的点
point = PointStruct(
id=hash(product['id']) % (2**63),
vector=embedding,
payload={
'id': product['id'],
'name': product['name'],
'brand': product['brand'],
'description': product['description'],
'product_type': product['product_type'],
'skin_type_compatibility': product['skin_type_compatibility'],
'regions_available': product['regions_available'],
'price_range': product['price_range'],
'usage': product['usage'],
'key_benefits': product['key_benefits'],
'ingredients': product['ingredients'],
'text': product_text, # 用于混合搜索
}
)
points.append(point)
# 批量upsert以提高效率
client.upsert(
collection_name=collection_name,
points=points
)
我们的产品数据结构如下:
注意:如果你没有任何产品也没关系,我们将在探索性网络搜索后搜索并添加新发现的产品。
{
"id": "prod_001",
"name": "透明质酸保湿精华",
"brand": "GlowSkin",
"description": "含2%透明质酸的密集保湿精华……",
"ingredients": ["hyaluronic acid", "peptides", "ceramides"],
"product_type": "serum",
"skin_type_compatibility": ["dry", "normal", "combination", "sensitive"],
"regions_available": ["US", "EU", "Asia", "Global"],
"price_range": "$$",
"usage": "both",
"key_benefits": ["Hydration", "Plumping", "Moisture retention"]
}
注意成分如何存储为列表而不是布尔标志。这使数据更灵活且可搜索。
10、Streamlit界面:让一切触手可及
所有这些强大的技术都包裹在一个干净、对话式的Streamlit界面中:
def main():
st.title("✨ Hola-Dermat")
st.markdown("### 你的个性化护肤助手")
# 初始化会话状态
ChatManager.initialize_session_state()
# 显示聊天历史
for message in ChatManager.get_messages():
with st.chat_message(message["role"]):
st.write(message["content"])
# 用户输入
user_input = st.chat_input("在这里输入消息……")
if user_input:
# 智能画像提取
ChatManager.extract_profile_info(user_input, profile)
# 如果画像完整,触发CrewAI工作流
if ChatManager.is_profile_collected():
with st.spinner("正在分析你的画像……"):
recommendations = generate_recommendations(profile, user_id)
st.markdown("### 你的个性化护肤方案")
st.markdown(recommendations['recommendations'])
界面处理:
- 智能解析用户消息
- 渐进式画像构建(只询问缺失的信息)
- 后台处理(向用户隐藏智能体操作)
- 干净的输出(过滤掉冗长的日志)
11、生产影响
让我们谈谈这在生产中意味着什么……
可扩展性
- Qdrant的ACORN算法高效处理数百万产品
- 多集合架构允许独立扩展
- 混合搜索确保即使复杂查询也能快速获得结果
用户体验
- 无零结果:ACORN确保用户总能找到相关的东西
- 个性化:历史集合构建长期用户画像
- 实时智能:Perplexity保持推荐最新
业务价值
- 更高的转化率:用户找到真正匹配他们需求的产品
- 减少流失:个性化体验让用户参与
- 数据洞察:历史集合提供有价值的用户行为数据
技术优势
- 自我改进:新产品自动添加到数据库
- 灵活的筛选:ACORN优雅地处理复杂查询
- 可维护:产品和用户数据之间的清晰分离
12、结论:智能体AI系统的未来
Hola-Dermat代表的不仅仅是护肤助手——它是智能体AI系统如何解决各种领域复杂、个性化问题的蓝图。
关键要点
- 智能体AI使系统能够自主决策、使用多个工具并适应用户需求
- Qdrant的ACORN算法解决了长期存在的零结果问题,使生产系统更可靠
- 向量数据库实现传统数据库无法匹配的语义理解
- 混合搜索结合了语义和关键词匹配的优点
- 多集合架构允许可扩展、可维护的系统
- 实时网络智能(通过Perplexity)保持推荐最新和相关
- 用户历史创建长期个性化,随着时间的推移改进
大局观
驱动Hola-Dermat的技术——Qdrant、CrewAI、Perplexity和现代LLM——不仅仅是工具。它们是新一代AI系统的构建模块,这些系统:
- 理解上下文而不是仅仅匹配关键词
- 从交互中学习而不是静态的
- 做出智能决策而不是遵循 rigid 规则
- 优雅地扩展而不是在复杂性下崩溃
无论你是构建推荐系统、搜索引擎,还是任何需要理解用户意图并提供个性化结果的应用程序,这些模式都适用。
Qdrant的ACORN算法特别解决了困扰生产系统多年的问题。通过智能处理复杂筛选器并避免零结果场景,它使向量数据库真正为生产做好准备。
而CrewAI?它是将所有这些整合在一起的编排器——使智能体AI变得可访问、强大和实用。
所以下次你构建需要理解用户、智能搜索并随时间适应的东西时……记住Hola-Dermat。AI的未来不仅仅是关于更大的模型——而是关于无缝协同工作的更智能的系统。
现在,如果你不介意的话……啊……我需要去更新我的护肤程序了。我的屏幕时间正在显现…… 😅
想自己构建吗?
查看Hola-Dermat仓库并开始使用Qdrant、CrewAI和智能体AI系统进行实验。
原文链接:Hola-Dermat: Personalized Skincare Agentic AI Assistant, Powered by Qdrant + Perplexity + CrewAI
汇智网翻译整理,转载请标明出处