Hola-Dermat:护肤AI助手

Hola-Dermat是一个个性化的护肤方案助手,它不只是向你推荐产品,而是真正理解你独特的皮肤需求、环境因素和偏好,为你量身定制早晚护肤流程。

Hola-Dermat:护肤AI助手
AI编程/Vibe Coding 遇到问题需要帮助的,联系微信 ezpoda,免费咨询。

"找到合适的护肤产品就像大海捞针……只不过大海是整个互联网,而你甚至不确定针长什么样。"

如果你曾经在护肤品货架前站过,盯着一排排承诺能改变你皮肤的产品……你就知道这种挣扎。我需要透明质酸吗?视黄醇呢?这是SPF 30还是50?最重要的是,这对我的肤质、在我的气候下、配合我的生活方式真的有效吗?

这就是Hola-Dermat——一个个性化的护肤方案助手,它不只是向你推荐产品,而是真正理解你独特的皮肤需求、环境因素和偏好,为你量身定制早晚护肤流程。

但关键是:这不只是另一个聊天机器人。Hola-Dermat利用Qdrant的ACORN算法Perplexity网络搜索CrewAI的智能体工作流等前沿技术,解决了困扰推荐系统多年的问题:可怕的"零结果"问题。

出发!!

让我带你了解我们是如何构建这个系统的,为什么它很重要,以及这些技术如何协同工作创造出真正强大的东西……

1、问题:为什么护肤推荐系统是失败的

想象一下:你是一名住在印度海得拉巴的软件工程师。你的皮肤是混合干性,有点容易长痘。你每天花18小时盯着屏幕(是的,我看到你在点头……),很少晒太阳,你现在用的洗面奶让你的皮肤感觉像撒哈拉沙漠。

哦,我的皮肤……

现在,试着找到满足以下条件的产品:

  • 适合混合干性、易长痘的皮肤
  • 在海得拉巴(或至少印度)有售
  • 解决过度使用屏幕造成的蓝光伤害
  • 提供保湿但不厚重
  • 价格不要太高

传统的推荐系统要么:

  1. 因为你的筛选条件"太具体"而返回零结果
  2. 给你不考虑区域可用性的通用产品
  3. 忽略紫外线指数、空气质量和湿度等环境因素
  4. 忘记你以前尝试过什么,不断推荐同样的东西

英雄登场!

这就是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

汇智网翻译整理,转载请标明出处