| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- import asyncio
- from datetime import datetime
- import re
- import aiofiles
- import os
- import sys
- from typing import List, Dict, Any
- from typing import Any, Dict, List, Optional, Union
- from pydantic import BaseModel
- from typing import List
- from llama_index.core.program import LLMTextCompletionProgram
- from llama_index.core.output_parsers import PydanticOutputParser
- from llama_index.program.lmformatenforcer import (
- LMFormatEnforcerPydanticProgram,
- )
- from llama_index.core.prompts import PromptTemplate
- from llama_index.llms.llama_cpp import LlamaCPP
- from llama_index.llms.openai import OpenAI
- from llama_index.llms.litellm import LiteLLM
- from llama_index.core.llms.llm import LLM
- from src.models.product_model import Product
- from src.manager.template_manager import TemplateManager, TemplateService, TemplateType
- from src.models.ai_execution_record import MarketingInfo, LLMConfig, SuperPromptMixin, AgentConfig, AgentContent, AICompetitorAnalyzeMainKeywords, AICompetitorAnalyzeMainKeywordsResult, MarketingContentGeneration
- from ai.base_agent import BaseAgent
- from llama_index.core.prompts import PromptTemplate
- from llama_index.llms.litellm import LiteLLM
- from config.settings import MONGO_URL, MONGO_DB_NAME,LITELLM_API_BASE, LITELLM_API_KEY,OPENAI_API_KEY,OPENAI_API_BASE
- from utils.file import save_to_file, read_file
- from utils.logu import get_logger
- logger = get_logger("ai")
- class ProductTitleAgent(BaseAgent):
- async def get_promp(self, product_name, prompt: str='', prompt_keywords:Dict={}) -> PromptTemplate:
- context_template = '''\
- 这是我的产品信息:
- ```\n{product_info}\n```\n
- 这是我根据自己产品信息,参考同行竞品的主关键词和长尾关键词:
- ```\n{llm_keyword_markdown}\n```\n
- ---
- '''
- prompt_product = prompt or '''
- ### 必须满足以下要求
- #### 平台要求:
- 亚马逊商品要点(Bullet Point)也就是卖家常说的五点描述,五点描述重点了解商品的五个主要特征和优势,帮助买家快速确认商品是否适合自己。
- 要点
- 这些内容应等同于商品的“功能与优点”。建议您为 5 个要点采用以下格式:
- 特性要点 1: 内容(例如材质)
- 特性要点 2: 功能与优点
- 特性要点 3: 功能与优点
- 特性要点 4: 功能与优点
- 特性要点 5: 护理或特殊护理或保修
- 示例:
- 由 (100%) 柔软磨毛棉制成
- 适合周末休闲穿着的舒适衬衫
- 抗皱性强,无需熨烫
- 做旧水洗工艺让这款衬衫呈现出一种“穿过的”复古风格
- 温水机洗。滚筒干燥
- 常见的五点描述的例子包括:产品总体概要、尺寸 、 年龄适宜性、原产国、主要功能、材料和结构、保修信息
- 写作要求:
- - 以 大 写 字 母 开 头 。 例 如 : Cotton Fabric: Made from (100 %) cotton for softness andbreathability
- - 采用句段形式,请勿使用末尾标点符号。例如:Long Sleeve: Long sleeves add coverageand style
- - 在一个要点中使用分号分隔各个短语。例如: Machine Washable: Durable construction;easier care
- - 使用10 个以上字符,但不超过255 个字符。
- 例如:Loose Fit: Relaxed fit allows for easy movement and comfort
- 解释:这里有59 个字符,包括50 个英文字母和每个词之间的空格,所以多过 10 个字符,且没有超过255 个字符
- - 写出数字1-9 的文字形式,但名称、型号和测量值除外。
- 例如:Long Battery Life: Total of twenty-four hours battery life
- - 为要点添加标题,并在标题后面使用“:”,然后再在后面提供完整信息。
- 例如:Machine Washable: Durable construction allows for easy care
- - 在数字和度量单位之间添加一个空格。
- 例如:...60 Milliliter…
- - 使用清晰、自然的语言编写要点,避免使用不必要的关键词或短语。
- 例如:可机洗:经久耐用,易于护理
- - 突出商品特征和优势,而不是品牌的营销活动。
- 反面示例:限时折扣:现在购买,享受 \(50 \%\) 折扣
- - 突出商品如何满足买家需求,而不只是列出事实。
- 例如:多用途款式:适合舞蹈练习、娱乐玩耍或户外活动
- - 在商品变体之间保持数据一致。
- 例如:
- 变体1 要点:棉织物:由 \(100 \%\) 纯棉制成,柔软、透气
- 变体2 要点:棉织物:由 \(100 \%\) 纯棉制成,柔软、透气
- - 请勿转述或引用此ASIN 不包含的其他商品。
- 反面示例:充电速度:本充电器属于快充,和苹果官方售卖的充电器的充电速度一样快\(\textcircled{13}\) 去除或尽可能减少商品名称、商品描述或商品概览等属性的重复。突出附加信息或支持信息,以帮助买家做出更明智的决策。
- 例如:
- 要点1:材质:这款水壶由优质不锈钢制成,持久耐用要点2:容量:水壶容量为1.5 升,适合家庭或户外使用要点3:保温:独特的双层保温设计,能够保持饮品温度长达12 小时解释:在正确的例子中,每个要点都提供了不同的产品信息(材料、容量和保温设计),这样客户可以更全面地了解产品的各个特点\(\textcircled{14}\) 避免使用任何主观声明、绩效声明或对比声明,除非它们可在商品包装上得到核实。
- 反面示例:…比X 品牌更耐用、更便宜,功能更多
- - 避免使用与荣誉和奖项有关的声明,除非商品详情页面提供有详细证明信息,例如日期和授予机构等。
- 反面示例:…ASIN 销量常年保持同类目第三
- - 避免使用与消费者调查结果有关的声明,即使相关调查收集了一些主观意见,除非提供有来源和日期。
- 反面示例:…根据调查, \(9 5 \%\) 的用户都推荐这款产品
- 写作禁止:
- 1.禁止使用特殊字符
- 例如™、®、€、…、†、‡、o、¢、£、¥、©、±、\~、â。
- 【错误示例】¥¥ Cotton Fabric ¥¥: Made from \(100 \%\) cotton for softness and breathability
- 2.禁止使用任何表情符号例如☺
- 【错误示例】 Cotton Fabric: Made from \(100 \%\) cotton for softness and breathability
- 3.禁止使用ASIN 编号或以下字样
- 例如“不适用” \(/ ^ { \prime \prime } \mathsf { N A ^ { \prime \prime } } / ^ { \prime \prime } \mathsf { n } / \mathsf { a ^ { \prime \prime } } / ^ { \prime \prime } \mathsf { N } / \mathsf { A ^ { \prime \prime } }\) 、“不符合要求”、“尚待决定”、“待决定”、“待定”、“待复制”。
- 【错误示例】Material:NA
- 4.禁止使用禁用短语例如生态友好、环境友好、抗微生物、抗菌、竹制、含竹类、大豆制成、含大豆。
- 5.禁止使用禁用短语例如生态友好、环境友好、抗微生物、抗菌、竹制、含竹类、大豆制成、含大豆。有关更多信息,您可复制以下链接至浏览器,前往参阅一般商品限制内容https://sellercentral.amazon.com/help/hub/reference/external/G201707070?locale \(\ c =\) en-US【错误示例】材质:本产品原材料是 \(100 \%\) 环保的,具有抗菌特性
- 6.禁用保证信息
- 例如“全额退款”、“如果不满意,请将其退回”或“无条件保证,无限制”。
- 【错误示例】…如果不满意,我们提供全额退款7.禁用公司信息、网站链接、外部超链接或任何联系信息【错误示例】限时优惠:欢迎访问我们的网站(www.example.com)获取更多额外优惠
- 8.禁用引导评论相关的信息
- 包括但不限于鼓励买家留下5 星好评,或者引导买家不留负面评论等。
- 【错误示例】…5 星好评免费赠送小礼物
- 9.禁用时效性信息例如宣传活动、研讨会或讲座的日期。
- 【错误示例】…欢迎参加9 月16 号xx 产品线下推广活动,地址位于…
- 10.禁止要点重复每个要点必须说明一条唯一的商品信息。
- 【错误示例】棉织物:由 \(100 \%\) 纯棉制成,柔软、透气;宽松合体:宽松合体便于运动且穿
- 着舒适;可机洗:经久耐用,易于护理
- 解释:示例里同时包含了3 条商品信息
- #### 用户要求
- 作为专业的日本站亚马逊运营,你需要根据产品信息和参考关键词,为我生成日语的产品标题、ST搜索词、卖点1~5。
- 亚马逊标题要求:
- - 保持简洁:200 个字符是允许的最大字符数,因为手机屏幕会缩短较长的商品名称避免冗余:请勿在商品名称中包含冗余信息、不必要的同义词或过多的关键词优化词序:仅包含有助于买家快速识别和了解商品的信息,将词语排序以优先展示最重要的商品信息。
- - 创建商品名称时,请遵循以下标准:
- - 商品名称不能超过 200 个字符(包括空格)。
- - 商品名称不可包含促销用语,如“ 無料配送”或“ 100% 品質保証、人気商品、ベストセラー 。”
- - 商品名称不能使用以下特殊字符:!、$、?、_、{、}、^、¬、¦。其他特殊字符,如 ~、#、<、> 和 *,只能在特定上下文中使用。
- - 例如,您可以使用这些符号作为商品编码(“款式 #4301”)或测量值(“<10 磅”)。
- - 不允许使用特殊字符作为装饰。例如,商品名称“Paradise Towel Wear Co. Beach Coverup << Size Kids XXS >>”不符合要求,因为尺码周围存在过多使用符号的情况。
- - 商品名称必须包含可用于清晰描述商品的最少信息,如“Amazon Essentials Dress”“Columbia Hiking Boots”或“Sony Headphones”。
- - 商品名称中不能有重复字词。例如,“Baby Boy Outfits Baby Boy fall Winter Clothes Baby Boy Long Sleeve Suspender Outfit Sets”是不符合要求的商品名称。
- - 标题中需要选择1-3 个关键词/产品定位词/消费者用来搜索产品的词语+ 材质/功能/场景等和其他同类产品形成差异化的词语。做到简洁清晰、格式分明、无词语堆砌。
- - 标题参考顺序:品牌名称+大流量的核心关键词商品名称+关键属性(即商品的唯一销售主张)+1-3 个特性词/功能词/属性词+口味/款式+颜色+尺寸+包装数量+型号。例子:
- - (Brand name) Brushed Grey Towel Bar, 22 Inch 304 Stainless Steel Towel Rack Bathroom, Towel Holder Brushed Grey Wall Mount, Total Length 24 Inch
- - 根据标题公式,建议颜色/规格信息置后,将核心关键词和特性词提前,尽量避免重复信息。
- - (Brand Name) Towel Racks for Bathroom,304 Stainless Steel Towel Bar,Wall Mount Towel Holder with Total Length 24 Inch(22 Inch, Brushed Grey)
- - 核心关键词往前放,3-5 个最佳消费者关注的核心卖点。例如“纯白高纤维棉花脚趾保护套”这个产品,必须把“脚趾保护套”关键词放在标题最前面(这是核心商品关键词),颜色、规格、材质等信息放在后面。
- - 一定要写遵守亚马逊标题格式要求,控制字符,尽量精简善用标点和括号无语法、拼写错误,无中式表达
- - 生成的内容“产品标题、ST搜索词”各个关键词之间只能空格隔开,卖点需要用 '【】' 将重要的卖点括起来。
- ②关键词权重分配
- - 前60字符:放置核心关键词,确保移动端优先展示
- - 中间部分:补充高转化词和场景词末尾:加入技术参数或独特卖点
- 观点补充:
- - 根据你生成的 日语的产品标题、ST搜索词、卖点1~5 进行观点补充,解释你为什么要这样选择
- ---
- 生成内容的格式 {output_type}
- '''
- prompt_tmpl = PromptTemplate(template=context_template + prompt_product, )
- return prompt_tmpl.partial_format(
- **prompt_keywords,
- )
- async def get_promp_template_result(self, product_name, prompt: str='',llm_model='', output_type='markdown', verbose=False):
- llm_name = llm_model or self.llm._get_model_name()
- models = await AgentContent.find_one(
- AgentContent.product_name == product_name,
- AgentContent.model_name==llm_name,
- )
- if not models:
- msg = f"no content for {product_name}"
- # Either raise a proper exception
- raise ValueError(msg)
- main_keys_info = models.competitor.get('markdown')
- if not main_keys_info:
- # logger.error(f"AgentContent.competitor.get('markdown') not exist: {product_name}")
- msg = f"AgentContent.competitor.get('markdown') not exist: {product_name}"
- raise msg
- variables = {'product_name': product_name}
- product_info = await self.template_manager.execute_template("product_info", variables)
- prompt_tmpl = await self.get_promp(
- product_name, prompt,
- prompt_keywords={
- 'llm_keyword_markdown':main_keys_info, 'output_type':output_type, 'product_info':product_info
- })
- result = prompt_tmpl.format()
- logger.info(f"{result}")
- return result
- async def acomplete(self, product_name, prompt: str='',llm_model='', output_type='markdown', verbose=False):
- promp_template_result = await self.get_promp_template_result(product_name, prompt,llm_model, output_type, verbose)
- response = await self.llm.acomplete(promp_template_result, verbose=verbose)
- res = self.filter_markdown_content(response.text)
- logger.info(f"{res}")
- llm_name = llm_model or self.llm._get_model_name()
- models = await AgentContent.find_one(
- AgentContent.product_name == product_name,
- AgentContent.model_name==llm_name,
- )
- if not models.marketing:
- models.marketing = {}
- models.marketing[output_type] = res
- return await models.save()
- async def gen_marketing_file(self, product_name, output_path: str, llm_model: str=''):
- models = await AgentContent.find(
- AgentContent.product_name == product_name,
- ).to_list()
- logger.info(f"models {models}")
- content = f"# {product_name}\n\n"
- for model in models:
- logger.info(f"{model.product_name} {model.model_name}")
- marketing = model.marketing.get('markdown')
- content += f"## {model.model_name}\n\n{marketing}\n\n"
- promp_template_result = await self.get_promp_template_result(product_name, llm_model=models[0].model_name)
- content += f"## 提示词\n\n{promp_template_result}\n\n"
- return save_to_file(content, output_path)
-
- async def llm_task(product_name='狗刷牙指套'):
- m = TemplateManager(MONGO_URL, MONGO_DB_NAME)
- await m.initialize()
- model = 'openai/groq/llama-3.1-8b-instant'
- # model = 'groq/groq/qwen-2.5-coder-32b'
- # model = 'openai/glm-4-flash'
- # model = 'openai/deepseek-v3'
- # model = 'openai/groq/qwen-2.5-32b'
- # model = 'openai/deepseek-chat'
- model = 'openai/deepseek-reasoner'
- # model = 'openai/doubao-pro-32k-241215'
- llm_list = [
- LiteLLM(model='openai/doubao-pro-32k-241215', api_key=OPENAI_API_KEY, api_base=OPENAI_API_BASE),
- LiteLLM(model='openai/deepseek-reasoner', api_key=OPENAI_API_KEY, api_base=OPENAI_API_BASE),
- LiteLLM(model='openai/Qwen/Qwen3-235B-A22B', api_key=OPENAI_API_KEY, api_base=OPENAI_API_BASE),
- # LiteLLM(model='openai/deepseek-v3', api_key=OPENAI_API_KEY, api_base=OPENAI_API_BASE),
- # 'openai/QwQ-32B',
- ]
- agent_task_list = []
- for llm in llm_list:
- model = await AgentContent.find_one(
- AgentContent.product_name == product_name,
- AgentContent.model_name==llm.model,
- )
- if model:
- agent = ProductTitleAgent(llm=llm, template_manager=m)
- agent_task_list.append(agent.acomplete(product_name=product_name))
- res = await asyncio.gather(*agent_task_list)
- return
- task_list = []
- for llm in llm_list:
- agent = ProductTitleAgent(llm=llm, template_manager=m)
- agent_model = agent.get_promp(product_name=product_name)
- task_list.append(agent_model)
- # logger.info(f"{agent_model.competitor.items()}")
- res = await asyncio.gather(*task_list)
- async def gen_marketing_file(product_name):
- m = TemplateManager(MONGO_URL, MONGO_DB_NAME)
- await m.initialize()
- llm = LiteLLM(model='openai/doubao-pro-32k-241215', api_key=OPENAI_API_KEY, api_base=OPENAI_API_BASE),
- agent = ProductTitleAgent(llm=llm, template_manager=m)
- output_path = r'G:\code\amazone\copywriting_production\output\temp' + f"\\{product_name}-商品标题.md"
- res = await agent.gen_marketing_file(product_name, output_path)
- logger.info(f"{res}")
- return
- async def multi_llm_task():
- list_product_name = [
- '死皮处理五件套',
- # '镊子铲两件套',
- # '折叠剪刀',
- # '脸用刮毛刀4件套',
- # '路亚钓鱼小剪刀',
- ]
- llm_task_list = []
- gen_marketing_file_task_list = []
- for product_name in list_product_name:
- llm_task_list.append(llm_task(product_name))
- gen_marketing_file_task_list.append(gen_marketing_file(product_name))
- res = await asyncio.gather(*llm_task_list)
- res = await asyncio.gather(*gen_marketing_file_task_list)
- return res
-
- def main():
- # asyncio.run(llm_task())
- asyncio.run(multi_llm_task())
- if __name__ == "__main__":
- main()
|