使用LLM进行文本分类
像许多现代人一样,我发现我自己将人工智能融入日常生活的各个方面。人工智能的灵活性和能力使其成为众多应用中不可或缺的工具,从自动化简单的任务到提供深刻的分析。最近,我一直在探索大型语言模型(LLMs)的潜力,并发现了一个有趣的想法:使用LLMs进行分类任务。
分类,对于不熟悉的人来说,涉及将数据标记或分类到不同的组中。这个标记的数据集用于训练AI模型。这是自然语言处理(NLP)中的基本任务,并有众多应用——其中之一就是训练这些大型语言模型(LLMs)。
文本分类在处理非结构化数据时可能会很有挑战性。鉴于对世界的总体理解,我认为可以使用大型语言模型(LLMs)来实现这一目标。为了验证我的理论,我进行了一项小的概念验证(POC),以确定其可行性并识别任何潜在问题。
作为一名电商领域的相关性工程师,我决定将某个搜索词下的产品分类为三类:相关、不太相关和不相关。
例如,如果你搜索“Apple iPhone 15”,你会看到诸如“iPhone 15”、“iPhone 15 Pro”和“iPhone 13”这样的结果。虽然“iPhone 15”与搜索词相关,但“iPhone 15 Pro”由于型号不匹配而相关性较低。“iPhone 13”则完全不相关。通常,这种分类需要手动努力。
前置条件:在深入Python脚本之前,让我们先来看看所需的准备工作。以下是确保一切顺利运行的步骤:
- 环境搭建:为了更高效地管理依赖和版本,我使用了Conda。它简化了流程并有助于避免冲突。
- 成本效益模型:在有限预算内,我选择了成本效益的解决方案,如Ollama和本地模型,例如Llama 3和Mistral LLMs。这些模型提供了强大的性能,而不产生高昂的成本。
- 代码编辑器:我使用了Visual Studio Code来编写Python代码。它强大的功能和扩展使其成为编码和调试的绝佳选择。
提示工程流程
提示工程是指设计和优化输入提示,以有效与大型语言模型(LLMs)交互的做法。目标是从模型中提取所需的输出,无论是生成文本、回答问题、分类数据,还是执行其他任务。
我写了一个详细的单个提示,并要求模型根据提供的搜索词对产品进行分类。
"""
该函数根据搜索查询从亚马逊、eBay或Flipkart等电子商务网站检索产品。
它向指定网站发送请求,处理响应,并提取相关的产品信息以供进一步分析。
"""
def getProductDetails(searchTerm: str) -> list:
url = 'https://example.com/search' # 使用您喜欢的任何网站
params = {
'searchTerm': searchTerm,
'page': '1',
'start': '0',
'itemPerPage': '40'
# 其他参数
}
headers = {} # 根据您要调用的网站设置头部信息
try:
response = requests.get(url, headers=headers, params=params, verify=False) # 设置 verify=False 以忽略SSL验证
if response.status_code == 200:
response_data = response.json()
# 检查API响应是否成功且产品不为空
if response_data.get('products'):
# 提取产品
products = response_data['products']
# 初始化一个空列表以存储格式化结果
formatted_results = []
# 遍历每个产品并提取所需信息
for product in products:
name = product.get('name', '')
sku = product.get('sku', '')
# 创建格式化结果的字典
formatted_result = {
"sku": product.get('sku', ''),
"name": product.get('name', ''),
"category": product.get('categories', ''),
"brand": product.get('brand', ''),
"description": product.get('description', '')
}
# 将格式化结果添加到列表中
formatted_results.append(formatted_result)
return formatted_results
else:
print("API响应不成功或产品为空。")
else:
print("错误:", response)
except Exception as e:
print(f"发生错误: {e}")
return []
if __name__ == '__main__':
load_dotenv()
client = OpenAI(
base_url='http://localhost:11434/v1/',
# 忽略但需要的参数
api_key='ollama',
)
searchTerm = input("请输入搜索词: ")
prompt = """
对产品列表中的产品是否与搜索词 {searchTerm} 相关进行分类。
从购买这些产品的电子商务在线商店的用户角度思考。
您可以将每个产品分为以下三类:
1. 相关
2. 不相关
3. 某程度相关
对于每个产品,提供详细的选择理由。
审核理由可以从以下列表中选择:
1. 对于“相关”可能的审核理由:"相关"
2. 对于“不相关”可能的审核理由:"错误产品" 或 "性别不符"
3. 对于“某程度相关”可能的审核理由:"品牌不符" 或 "尺寸不符" 或 "颜色不符" 或 "数量/计数不符" 或 "口味不符" 或 "变体不符" 或 "其他属性不符" 或 "配件或互补产品" 或 "型号不符 - 完全不同" 或 "型号不符 - 稍有差异" 或 "其他原因"
确保仅从相应类别中选择原因,不要混合使用不同类别的原因。
DOs:
1. 具体明确。
2. 理解搜索词以及产品数据,然后将其分类为上述三类。
3. 从用户角度考虑。
DONTs:
1. 不要猜测或虚构。
2. 不要创建新的审核理由。使用上面提供的理由。
输出格式应如下所示:
{
relevant: "",
auditReasons: [],
"reason": ""
}
relevant 应该是上述类别中的字符串,即“相关”、“不相关”或“某程度相关”。
auditReasons 应该是上述原因中的字符串列表。
reason 应该是选择该类别的原因。
示例:
1. 搜索词: 'samsung s23 red'。
如果产品是三星s23红色 ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词和产品相同"}
如果产品是三星s23蓝色 ->
输出: { relevant: "某程度相关", auditReasons: ['颜色不符'], "reason": "它属于某程度相关类别,因为产品的颜色是蓝色,但搜索词是红色"}
如果产品是三星s24红色 ->
输出: { relevant: "某程度相关", auditReasons: ['型号不符 - 完全不同'], "reason": "它属于某程度相关类别,因为型号是s24,但搜索词是s23"}
如果产品是三星s23 ->
输出: { relevant: "某程度相关", auditReasons: ['其他原因'], "reason": "它属于某程度相关类别,因为颜色未指定"}
如果产品是三星Galaxy Note ->
输出: { relevant: "不相关", auditReasons: ['错误产品'], "reason": "它属于不相关类别,因为产品是Galaxy Note,但搜索词是s23"}
2. 搜索词: 'samsung'。
如果产品是三星s23红色 ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词是通用的,品牌相同"}
如果产品是三星s23蓝色 ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词是通用的,品牌相同"}
如果产品是三星T恤 ->
输出: { relevant: "相关", auditReasons: [], "reason": "因为搜索词是通用的,用户可能在寻找T恤"}
如果产品是三星洗衣机 ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词是通用的,品牌相同,且类别是电子产品"}
如果产品是三星手表 ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词是通用的,品牌相同,且类别是电子产品"}
3. 搜索词: 'apple'。
如果产品是苹果 ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词是通用的,用户可能在寻找水果"}
如果产品是iPhone ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词是通用的,苹果以生产手机闻名"}
如果产品是iPhone 15 ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词是通用的,苹果以生产手机闻名"}
如果产品是香蕉 ->
输出: { relevant: "某程度相关", auditReasons: ["型号不符 - 完全不同"], "reason": "它属于相关类别,因为搜索词是通用的,类别可能是水果"}
如果产品是三星s23 ->
输出: { relevant: "不相关", auditReasons: ["错误产品"], "reason": "因为搜索词是通用的,可能是水果或电子产品。但品牌不同"}
4. 搜索词: 'oil 2ltr'。
如果产品是'油' ->
输出: { relevant: "某程度相关", auditReasons: ["数量/计数不符"], "reason": "它属于某程度相关类别,因为数量未指定"}
如果产品是'油 10ltr' ->
输出: { relevant: "某程度相关", auditReasons: ["数量/计数不符"], "reason": "它属于某程度相关类别,因为数量不同"}
如果产品是'水 5ltr' ->
输出: { relevant: "不相关", auditReasons: ["错误产品"], "reason": "它属于不相关类别,因为产品是水,但搜索词是油"}
5. 搜索词: 'womens black shirt'。
产品选项:
如果产品是'黑色衬衫' ->
输出: { relevant: "某程度相关", auditReasons: ["其他属性不符"], "reason": "它属于某程度相关类别,因为性别未指定"}
如果产品是'男士黑色衬衫' ->
输出: { relevant: "不相关", auditReasons: ["性别不符"], "reason": "它属于不相关类别,因为性别不同"}
如果产品是'女士红色衬衫' ->
输出: { relevant: "某程度相关", auditReasons: ["颜色不符"], "reason": "它属于某程度相关类别,因为颜色不同"}
如果产品是'女士红色T恤' ->
输出: { relevant: "某程度相关", auditReasons: ["其他属性不符"], "reason": "它属于某程度相关类别,因为搜索词是衬衫,但产品是T恤"}
6. 搜索词: 'nike shoes'。
如果产品是'nike jordan shoes' ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为品牌和类别相同"}
如果产品是'addidas shoes' ->
输出: { relevant: "某程度相关", auditReasons: ['品牌不符'], "reason": "它属于某程度相关类别,因为品牌不同"}
如果产品是'nike joggers' ->
输出: { relevant: "不相关", auditReasons: ['错误产品'], "reason": "它属于不相关类别,因为产品是运动裤,但搜索词是鞋子"}
7. 搜索词: 'mobile phone'。
产品选项:
如果产品是'mobile covers' ->
输出: { relevant: "某程度相关", auditReasons: ["配件或互补产品"], "reason": "它属于某程度相关类别,因为产品是手机壳,但搜索词是手机"}
如果产品是'samsung s23' ->
输出: { relevant: "相关", auditReasons: [], "reason": "它属于相关类别,因为搜索词是通用的,产品属于电子/手机类别"}
如果产品是'apple macbook' ->
输出: { relevant: "不相关", auditReasons: ["错误产品"], "reason": "它属于不相关类别,因为产品是Macbook,但搜索词是手机"}
不要忘记注意事项,并按照上述输出格式回复。
"""
# 使用上述函数获取产品
products = getDataForVS(searchTerm)
finalData = []
for product in products:
print(product)
# 调用本地LLM获取相关性
chat_completion = client.chat.completions.create(
response_format={ "type": "json_object" },
messages=[
{"role": "system", "content": prompt},
{'role': 'user','content': f"获取产品 {product} 与搜索词 {searchTerm} 的相关性",}
],
model='mistral:latest',
)
print(chat_completion.choices[0].message)
response = json.loads(chat_completion.choices[0].message.content)
finalData.append(
{
'sku': product['sku'],
'relevancyFromLlm': response['relevant'],
'reasonFromLlm_1': response['auditReasons']
}
)
print(finalData)
结果完全出乎我的意料。LLM 对许多事物的分类出现了错误。对于同一产品,多次运行时会被分类到不同的类别,每次的分类理由都不同。
输出:LLM 对“samsung s23”搜索词的三个不同产品的分类完成
LLM 对“彩色铅笔”搜索词的分类完成
一个假设可能是有‘n’种不同的可能性,而我的提示无法涵盖所有场景。一次性提示没有很好地发挥作用。
所以我决定缩小范围。我决定只为一个类别:“手机和平板”进行分类,这样提示会更简单,可能性也会减少。结果比第一次方法好一些,但仍然没有达到预期。我尝试调整提示,但并没有太大帮助。
方法2:通过添加一些规则并逐步进行分类来帮助大语言模型(LLMs)经过数小时的努力后,我决定尝试另一种方法。如果我们通过引入某些规则来帮助大语言模型(LLM),一步一步地进行产品分类,就像人类一样去做呢?
如果我是审计员,第一步会检查搜索词和产品是否属于相同的类别。之后,我会查找搜索词和产品中的品牌信息。如果两者不匹配,通常意味着产品不相关。然而,如果两者匹配,我会进一步检查型号、颜色和特定属性信息。如果所有信息都匹配,那么产品是相关的。如果有任何不匹配,产品则不太相关。
def processLLM(product, searchTerm, resp):
# 标志用于检查不同的事项
isCategorySimilar = "No"
isBrandAvailable = "No"
isBrandSimilar = "No"
isAccessory = "No"
isModelAvailable = "No"
isModelSimilar = "No"
isColorAvailable = "No"
isColorSimilar = "No"
reasonList = []
MODEL = os.getenv('MODEL')
RESPONSE_FORMAT = { "type": "json_object" }
openAiClient = OpenAI(
base_url='http://localhost:11434/v1/',
# 忽略但需要
api_key='ollama',
)
# 1. 检查类别是否匹配
PROMPT = [
{"role": "system", "content": "您是一位电商审计员,您的工作是将搜索词和产品进行比较,并将产品分类为相关、部分相关或不相关。为此,您将被问及一些问题,您需要以是或否来回答,并根据回答进行分类。"},
{'role': 'user','content': f"搜索词 '{searchTerm}' 和产品类别 '{product['category']}' 是否相似或属于子类别?如果类别相似或属于子类别,请回答 '是',如果类别不同,请回答 '否'。请按照以下格式回答:{'answer': '是/否', 'reason': '回答的原因'}"},
]
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
print(f"比较 {searchTerm} 和 {product['category']}")
print(f"答案 1 -> 类别检查: {chat_completion.choices[0].message.content}")
isCategorySimilar = json.loads(chat_completion.choices[0].message.content)["answer"]
if(isCategorySimilar == "是"):
# 检查搜索词中是否存在品牌数据
PROMPT.append({"role": "assistant", "content": chat_completion.choices[0].message.content})
PROMPT.append({"role": "user", "content": f"搜索词 '{searchTerm}' 是否包含品牌信息?请按照以下格式回答:{'answer': '是/否', 'reason': '回答的原因'}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
print(f"答案 2 -> 品牌存在检查: {searchTerm}:{chat_completion.choices[0].message.content}")
isBrandAvailable = json.loads(chat_completion.choices[0].message.content)["answer"]
PROMPT.append({"role": "assistant", "content": chat_completion.choices[0].message.content})
if(isBrandAvailable == "是"):
# 检查品牌是否与产品匹配
PROMPT.append({"role": "user", "content": f"搜索词 '{searchTerm}' 和产品品牌 '{product['brand']}' 是否相似?如果品牌相似,请回答 '是',如果品牌不同,请回答 '否'。请按照以下格式回答:{'answer': '是/否', 'reason': '回答的原因'}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
print(f"答案 2.1 -> 品牌检查: {product['name']} | {product['brand']} : {chat_completion.choices[0].message.content}")
isBrandSimilar = json.loads(chat_completion.choices[0].message.content)["answer"]
PROMPT.append({"role": "assistant", "content": chat_completion.choices[0].message.content})
# 继续检查配件
PROMPT.append({"role": "user", "content": f"""搜索词 '{searchTerm}' 是否是 {product['name']} 的配件,反之亦然?如果不是相似产品,请回答 '是',否则回答 '否'。例如:
1. 如果搜索词是 '手机',那么 '手机壳'、'屏幕保护膜'、'屏幕保护器'、'手机壳'、'耳机' 都是手机的配件,回答应为 '是'。
2. 如果搜索词是 '手机',那么 '手机'、'iPhone 13'、'三星 S23' 都是手机,回答应为 '否'。
3. 如果搜索词是 '耳机',那么 '耳机'、'耳机' 都是耳机,回答应为 '否'。
4. 如果搜索词是 '手机壳',那么 '手机壳'、'手机壳' 都是相似产品,回答应为 '否'。
请务必仔细检查原因,然后形成回答。""" +
"请按照以下格式回答:{'answer': '', 'reason': ''}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
print(f"答案 3 -> 配件存在检查: {searchTerm} : {chat_completion.choices[0].message.content}")
accessoryReason = json.loads(chat_completion.choices[0].message.content)["reason"]
PROMPT.append({"role": "assistant", "content": "原因: " + accessoryReason})
PROMPT.append({"role": "user", "content": f"根据上面助手提供的原因,产品是否是配件?如果不是,请回答 '否',否则回答 '是'。请按照以下格式回答:{'answer': ''}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
isAccessory = json.loads(chat_completion.choices[0].message.content)["answer"]
PROMPT.append({"role": "assistant", "content": chat_completion.choices[0].message.content})
print(f"答案 3.1 -> 配件检查: {searchTerm} : {chat_completion.choices[0].message.content}")
# 检查搜索词中是否存在型号信息
PROMPT.append({"role": "user", "content": f"搜索词 '{searchTerm}' 是否包含型号信息?请按照以下格式回答:{'answer': '是/否', 'reason': '回答的原因'}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
print(f"答案 4 -> 型号存在检查: {searchTerm} : {chat_completion.choices[0].message.content}")
isModelAvailable = json.loads(chat_completion.choices[0].message.content)["answer"]
PROMPT.append({"role": "assistant", "content": chat_completion.choices[0].message.content})
if(isModelAvailable == "是"):
# 检查型号信息是否与产品匹配
PROMPT.append({"role": "user", "content": f"搜索词 '{searchTerm}' 和产品型号 '{product['name']}' 是否相似?如果型号相同且属于同一系列/版本,请回答 '是',如果型号不同且属于不同系列/版本,请回答 '否'。例如:三星 S23 和三星 S24 是不同型号的不同版本,回答应为 '否'。三星 S23 和三星 S23+ 是不同型号的不同版本,回答应为 '否'。iPhone 15 和 iPhone 15 Pro 是不同版本,回答应为 '否'。在比较时忽略大小、RAM 等属性。请按照以下格式回答:{'answer': '是/否', 'reason': '回答的原因'}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
print(f"答案 4.1 -> 型号检查: {product['name']} || {chat_completion.choices[0].message.content}")
modelReason = json.loads(chat_completion.choices[0].message.content)["reason"]
PROMPT.append({"role": "assistant", "content": modelReason})
PROMPT.append({"role": "user", "content": f"根据助手提供的原因,型号是否相同?请按照以下格式回答:{'answer': '是/否', 'reason': '回答的原因'}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
isModelSimilar = json.loads(chat_completion.choices[0].message.content)["answer"]
print(f"答案 4.2 -> 使用原因的型号检查: {product['name']} || {chat_completion.choices[0].message.content}")
PROMPT.append({"role": "assistant", "content": chat_completion.choices[0].message.content})
# 检查搜索词中是否存在颜色信息
PROMPT.append({"role": "user", "content": f"搜索词 '{searchTerm}' 是否包含颜色信息?请按照以下格式回答:{'answer': '是/否', 'reason': '回答的原因'}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
print("答案 5 -> 颜色存在检查: " + chat_completion.choices[0].message.content)
isColorAvailable = json.loads(chat_completion.choices[0].message.content)["answer"]
PROMPT.append({"role": "assistant", "content": chat_completion.choices[0].message.content})
if(isColorAvailable == "是"):
# 检查颜色信息是否与产品相同
PROMPT.append({"role": "user", "content": f"搜索词 '{searchTerm}' 和产品 '{product}' 的颜色是否相同?如果颜色相同,请回答 '是',如果颜色不同,请回答 '否'。请按照以下格式回答:{'answer': '是/否', 'reason': '回答的原因'}"})
chat_completion = openAiClient.chat.completions.create(
response_format=RESPONSE_FORMAT,
messages=PROMPT,
model=MODEL,
)
print("答案 5.1 -> 颜色检查: " + chat_completion.choices[0].message.content)
isColorSimilar = json.loads(chat_completion.choices[0].message.content)["answer"]
PROMPT.append({"role": "assistant", "content": chat_completion.choices[0].message.content})
# 现在我们有了所有信息,根据一些 if-else 检查对产品进行分类
if(isBrandAvailable == "是" and isBrandSimilar == "否"):
if (isAccessory):
reasonList.append("配件")
elif (isAccessory is False):
reasonList.append("品牌不匹配")
elif(isBrandAvailable == "否" or (isBrandAvailable == "是" and isBrandSimilar == "是")):
if(isModelAvailable == "是"):
if(isModelSimilar == "是"):
if(isColorAvailable == "是"):
if(isColorSimilar == "是"):
if(isAccessory == "是"):
reasonList.append("配件")
elif(isColorSimilar == "否"):
reasonList.append("颜色不匹配")
elif(isColorAvailable == "否"):
if(isAccessory == "是"):
reasonList.append("配件")
elif(isModelSimilar == "否"):
reasonList.append("型号不匹配")
elif(isModelAvailable == "否"):
if(isColorAvailable == "是"):
if(isColorSimilar == "是"):
if(isAccessory == "是"):
reasonList.append("配件")
elif(isColorSimilar == "否"):
reasonList.append("颜色不匹配")
elif(isColorAvailable == "否"):
if(isAccessory == "是"):
reasonList.append("配件")
elif(isCategorySimilar == "否"):
# 由于类别不匹配,产品不相关
reasonList.append("类别不匹配")
if('类别不匹配' in reasonList or '品牌不匹配' in reasonList):
resp['relevant'] = '不相关'
resp['auditReasons'] = " ".join(entry for entry in reasonList)
resp['reason'] = '查询具有特定性质,但产品不属于该类别/品牌'
elif(not reasonList):
resp['relevant'] = '相关'
resp['auditReasons'] = []
resp['reason'] = '相关'
else:
resp['relevant'] = '不太相关'
resp['auditReasons'] = " ".join(entry for entry in reasonList)
resp['reason'] = '不太相关'
输出:
由LLM完成的逐步分类
结果在最初的几次尝试中好得多且很有希望。我想要在一个更大的数据集上验证它,所以我对超过500个搜索词和产品组合进行了测试。
示例结果集
推理:搜索词和产品配对总数:500
LLMs 连续一致的次数:406
百分比:81.2
- 大型语言模型(LLMs)确实表现出不一致性。即使将任务分解为更小的子任务,LLMs 仍然在虚构。
- 进一步分析后发现,LLMs 能够准确分类某些属性,如类别、品牌、颜色等,但在其他属性如配件上则表现不佳。
- 即使在如此小的测试集上,准确率仍然很低。增加测试集也会显示出类似的趋势。
- 我尝试了不同的模型,如 Mistral 7B、Llama3 7B 等,结果非常相似。
- 获取更多数据点并进行进一步分析。
- 在LLM出现幻觉或提供不一致响应的地方增强提示。
- 考虑对通用LLM进行微调或用更适合特定分类任务的LLM替换。
尽管上述问题陈述的分类并不完美,可能是因为其复杂性,但在自动化此类分类任务方面仍有很大的潜力。随着LLM的不断发展和改进,它们在这些任务中的准确性和有效性也将提高。然而,LLM完全取代人类还需要相当长的时间。
你有没有处理过类似的分类任务,或者对此话题有自己的见解?我很想听听你的想法和观点!欢迎留言或直接联系我。
感谢阅读!如果你喜欢这篇文章并觉得它有所帮助,你的支持,比如 点赞、评论或分享 ,对我来说意义重大!如果你想要提供更多的支持,可以考虑买我一杯咖啡 买我一杯咖啡 ☕💻🚀