ibombonato commited on
Commit
3cdec01
·
verified ·
1 Parent(s): 5b4a293

feat: add discount pct and change resumo for opiniao dos usuarios (#2)

Browse files

- feat: add discount pct and change resumo for opiniao dos usuarios (c1cde73334a70b53108de319a6e6c49b7dee5021)

Files changed (3) hide show
  1. app.py +65 -7
  2. fragrantica_crew.py +4 -4
  3. stealth_scrape_tool.py +1 -1
app.py CHANGED
@@ -5,6 +5,7 @@ from crewai import Agent, Task, Crew, Process, LLM
5
  from crewai_tools import ScrapeWebsiteTool
6
  from crewai.tools import BaseTool
7
  from dotenv import load_dotenv
 
8
 
9
  load_dotenv()
10
 
@@ -31,10 +32,28 @@ class ShortenerTool(BaseTool):
31
  return original_url
32
 
33
  class CalculateDiscountedPriceTool(BaseTool):
 
 
 
34
  name: str = "Calculate Discounted Price Tool"
35
  description: str = "Calculates the price after applying a given discount percentage."
36
-
37
  def _run(self, original_price: float, discount_percentage: float) -> float:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  if not isinstance(original_price, (int, float)) or not isinstance(discount_percentage, (int, float)):
39
  raise ValueError("Both original_price and discount_percentage must be numbers.")
40
  if discount_percentage < 0 or discount_percentage > 100:
@@ -43,6 +62,35 @@ class CalculateDiscountedPriceTool(BaseTool):
43
  discount_amount = original_price * (discount_percentage / 100)
44
  discounted_price = original_price - discount_amount
45
  return round(discounted_price, 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
  class SocialMediaCrew:
48
  def __init__(self, openai_api_key: str, natura_api_token: str, openai_base_url: str, openai_model_name: str):
@@ -50,9 +98,10 @@ class SocialMediaCrew:
50
  self.natura_api_token = natura_api_token
51
  self.openai_base_url = openai_base_url
52
  self.openai_model_name = openai_model_name
53
- self.scrape_tool = ScrapeWebsiteTool()
54
  self.shortener_tool = ShortenerTool(natura_api_token=self.natura_api_token)
55
  self.calculate_discounted_price_tool = CalculateDiscountedPriceTool()
 
56
 
57
  print("Initializing SocialMediaCrew with BASE URL:", self.openai_base_url)
58
  print("Using OpenAI Model:", self.openai_model_name)
@@ -69,9 +118,13 @@ class SocialMediaCrew:
69
  goal='Analyze the provided URL and extract key product information',
70
  backstory=("You are an expert in analyzing product pages and extracting the most important information. You can identify the product name, its main features, and the target audience."),
71
  verbose=True,
72
- tools=[self.scrape_tool, self.shortener_tool, self.calculate_discounted_price_tool],
 
 
 
73
  allow_delegation=False,
74
- llm=llm
 
75
  )
76
 
77
  self.social_media_copywriter = Agent(
@@ -80,7 +133,8 @@ class SocialMediaCrew:
80
  backstory=("You are a creative copywriter specialized in the beauty and fragrance market. You know how to craft posts that are engaging, persuasive, and tailored for a Portuguese-speaking audience. You are an expert in using emojis and hashtags to increase engagement."),
81
  verbose=True,
82
  allow_delegation=False,
83
- llm=llm
 
84
  )
85
 
86
  def run_crew(self, product_url: str, main_cupom: str, main_cupom_discount_percentage: float, cupom_1: str, cupom_2: str) -> str:
@@ -105,9 +159,9 @@ class SocialMediaCrew:
105
  return "INVALID_URL"
106
 
107
  analyze_product_task = Task(
108
- description=(f"1. Scrape the content of the URL: {product_url} using the 'scrape_tool'.\n2. Identify and extract the original product price and the final discounted price if existing. IGNORE any price breakdowns like 'produto' or 'consultoria'.\n3. Extract the product name, key characteristics, and any other relevant DISCOUNT available.\n4. Use the 'Calculate Discounted Price Tool' with the extracted final best price and the provided discount percentage ({main_cupom_discount_percentage}) to get the CUPOM DISCOUNTED PRICE.\n5. Use the 'URL Shortener Tool' to generate a short URL for {product_url}. If the shortener tool returns an error, use the original URL.\n6. Provide all this information, including the product name, ORIGINAL PRICE, DISCOUNTED PRICE (the one used as the input in the tool 'Calculate Discounted Price Tool'), 2) CUPOM DISCOUNTED PRICE, and the generated short URL (or the original if the shortener failed). If any of this information cannot be extracted, you MUST return 'MISSING_PRODUCT_INFO'."),
109
  agent=self.product_analyst,
110
- expected_output="A concise summary of the product including its name, key features, unique selling points, ORIGINAL PRICE, DISCOUNTED PRICE (the one used as the input in the tool 'Calculate Discounted Price Tool'), CUPOM DISCOUNTED PRICE, and the SHORT SHAREABLE URL (or the original if the shortener failed), OR 'MISSING_PRODUCT_INFO' if essential product details are not found."
111
  )
112
 
113
  create_post_task = Task(
@@ -122,11 +176,15 @@ class SocialMediaCrew:
122
  De ~~{{ORIGINAL PRICE}}~~
123
  🔥Por {{CUPOM DISCOUNTED PRICE}} 🔥
124
 
 
 
125
  🎟️ USE O CUPOM >>> {main_cupom}
126
 
127
  🛒 Link >>> {{short_url}}
128
 
129
  `🎟️ *Cupom válido para a primeira compra no link Minha Loja Natura, mesmo se já comprou no app ou link antigo. Demais compras ou app, use o cupom {cupom_1} ou {cupom_2} (o desconto é um pouco menor)`
 
 
130
  ###End Template
131
 
132
  Ensure a URL is always present in the output. Include a clear call to action and a MAXIMUM of 2 relevant emojis. DO NOT include hashtags. Keep it short and impactful and does not forget to include the backticks around the last paragraph.
 
5
  from crewai_tools import ScrapeWebsiteTool
6
  from crewai.tools import BaseTool
7
  from dotenv import load_dotenv
8
+ from stealth_scrape_tool import StealthScrapeTool
9
 
10
  load_dotenv()
11
 
 
32
  return original_url
33
 
34
  class CalculateDiscountedPriceTool(BaseTool):
35
+ """
36
+ A tool to calculate the final price of an item after a discount is applied.
37
+ """
38
  name: str = "Calculate Discounted Price Tool"
39
  description: str = "Calculates the price after applying a given discount percentage."
40
+
41
  def _run(self, original_price: float, discount_percentage: float) -> float:
42
+
43
+ """Calculates the discounted price and the total discount amount.
44
+
45
+ This method takes an original price and a discount percentage, validates
46
+ the inputs, and then computes the final price after the discount is
47
+ applied, as well as the amount saved.
48
+
49
+ Args:
50
+ original_price: The initial price of the item as a float or integer.
51
+
52
+ Returns:
53
+ float:
54
+ - The final discounted price, rounded to 2 decimal places.
55
+ """
56
+
57
  if not isinstance(original_price, (int, float)) or not isinstance(discount_percentage, (int, float)):
58
  raise ValueError("Both original_price and discount_percentage must be numbers.")
59
  if discount_percentage < 0 or discount_percentage > 100:
 
62
  discount_amount = original_price * (discount_percentage / 100)
63
  discounted_price = original_price - discount_amount
64
  return round(discounted_price, 2)
65
+
66
+ class CalculateDiscountValueTool(BaseTool):
67
+ """
68
+ A tool to calculate the final discount value of an item after comparing the original value and the final value.
69
+ """
70
+ name: str = "Calculate Discount Value Tool"
71
+ description: str = "Calculates the discount value after comparing two values."
72
+
73
+ def _run(self, original_price: float, final_price: float) -> float:
74
+
75
+ """Calculates the total discounted amount give the original and final price.
76
+
77
+ This method takes an original price and a final price, validates
78
+ the inputs, and then computes the final discounted value.
79
+
80
+ Args:
81
+ original_price: The initial price of the item as a float or integer.
82
+ final_price: The final price after discount as a float or integer.
83
+
84
+ Returns:
85
+ float:
86
+ - The final discount value, rounded to 0 decimal places.
87
+ """
88
+ if not isinstance(original_price, (int, float)) or not isinstance(final_price, (int, float)):
89
+ raise ValueError("Both original_price and final_price must be numbers.")
90
+
91
+ discount_value = original_price - final_price
92
+ discount_percentage = (discount_value / original_price) * 100
93
+ return round(discount_percentage, 0)
94
 
95
  class SocialMediaCrew:
96
  def __init__(self, openai_api_key: str, natura_api_token: str, openai_base_url: str, openai_model_name: str):
 
98
  self.natura_api_token = natura_api_token
99
  self.openai_base_url = openai_base_url
100
  self.openai_model_name = openai_model_name
101
+ self.scrape_tool = StealthScrapeTool() #ScrapeWebsiteTool()
102
  self.shortener_tool = ShortenerTool(natura_api_token=self.natura_api_token)
103
  self.calculate_discounted_price_tool = CalculateDiscountedPriceTool()
104
+ self.calculate_discount_value_tool = CalculateDiscountValueTool()
105
 
106
  print("Initializing SocialMediaCrew with BASE URL:", self.openai_base_url)
107
  print("Using OpenAI Model:", self.openai_model_name)
 
118
  goal='Analyze the provided URL and extract key product information',
119
  backstory=("You are an expert in analyzing product pages and extracting the most important information. You can identify the product name, its main features, and the target audience."),
120
  verbose=True,
121
+ tools=[self.scrape_tool,
122
+ self.shortener_tool,
123
+ self.calculate_discounted_price_tool,
124
+ self.calculate_discount_value_tool],
125
  allow_delegation=False,
126
+ llm=llm,
127
+ max_retries=3
128
  )
129
 
130
  self.social_media_copywriter = Agent(
 
133
  backstory=("You are a creative copywriter specialized in the beauty and fragrance market. You know how to craft posts that are engaging, persuasive, and tailored for a Portuguese-speaking audience. You are an expert in using emojis and hashtags to increase engagement."),
134
  verbose=True,
135
  allow_delegation=False,
136
+ llm=llm,
137
+ max_retries=3
138
  )
139
 
140
  def run_crew(self, product_url: str, main_cupom: str, main_cupom_discount_percentage: float, cupom_1: str, cupom_2: str) -> str:
 
159
  return "INVALID_URL"
160
 
161
  analyze_product_task = Task(
162
+ description=(f"1. Scrape the content of the URL: {product_url} using the 'scrape_tool' with css_element = '.product-detail-banner'.\n2. Identify and extract the original product price and the final discounted price if existing. IGNORE any price breakdowns like 'produto' or 'consultoria'.\n3. Extract the product name, key characteristics, and any other relevant DISCOUNT available.\n4. Use the 'Calculate Discounted Price Tool' with the extracted final best price and the provided DISCOUNT PERCENTAGE ({main_cupom_discount_percentage}) to get the CUPOM DISCOUNTED PRICE.\n4.1 Use the 'Calculate Discount Value Tool' with ORIGINAL PRICE and CUPOM DISCOUNTED PRICE to get the TOTAL DISCOUNT PERCENTAGE.\n5. Use the 'URL Shortener Tool' to generate a short URL for {product_url}. If the shortener tool returns an error, use the original URL.\n6. Provide all this information, including the product name, ORIGINAL PRICE, DISCOUNTED PRICE (the one used as the input in the tool 'Calculate Discounted Price Tool'), 2) CUPOM DISCOUNTED PRICE, and the generated short URL (or the original if the shortener failed). If any of this information cannot be extracted, you MUST return 'MISSING_PRODUCT_INFO'."),
163
  agent=self.product_analyst,
164
+ expected_output="A concise summary of the product including its name, key features, unique selling points, ORIGINAL PRICE, DISCOUNTED PRICE (the one used as the input in the tool 'Calculate Discounted Price Tool'), CUPOM DISCOUNTED PRICE, TOTAL DISCOUNT PERCENTAGE, and the SHORT SHAREABLE URL (or the original if the shortener failed), OR 'MISSING_PRODUCT_INFO' if essential product details are not found."
165
  )
166
 
167
  create_post_task = Task(
 
176
  De ~~{{ORIGINAL PRICE}}~~
177
  🔥Por {{CUPOM DISCOUNTED PRICE}} 🔥
178
 
179
+ 🔥 {{TOTAL DISCOUNT PERCENTAGE}}% OFF!
180
+
181
  🎟️ USE O CUPOM >>> {main_cupom}
182
 
183
  🛒 Link >>> {{short_url}}
184
 
185
  `🎟️ *Cupom válido para a primeira compra no link Minha Loja Natura, mesmo se já comprou no app ou link antigo. Demais compras ou app, use o cupom {cupom_1} ou {cupom_2} (o desconto é um pouco menor)`
186
+
187
+ `‼️ Faça login nesse link com o mesmo email e senha que já usa pra comprar Natura!`
188
  ###End Template
189
 
190
  Ensure a URL is always present in the output. Include a clear call to action and a MAXIMUM of 2 relevant emojis. DO NOT include hashtags. Keep it short and impactful and does not forget to include the backticks around the last paragraph.
fragrantica_crew.py CHANGED
@@ -54,7 +54,7 @@ class FragranticaCrew:
54
 
55
  - Este Perfume me Lembra do: Find the section titled "Este perfume me lembra do", and list the perfume names mentioned there.
56
 
57
- - Resumo detalhado: Look for a section containing detailed user reviews, such as "Todas as Resenhas por Data" or similar, and synthesize a detailed summary from these reviews.
58
 
59
  3. Present the extracted information in a clear, structured format, ready for reporting. If any specific piece of information cannot be found, check again to make sure they are not found, after check again, if you truly do not find the info, state 'N/A' for that field. If the entire scraping process fails, return "SCRAPING_FAILED".
60
  """
@@ -68,7 +68,7 @@ class FragranticaCrew:
68
  Longevidade,
69
  Projeção,
70
  Este Perfume me Lembra do,
71
- and Resumo detalhado.
72
  Ensure Longevidade and Projeção use the exact specified string values.
73
  If any information is not found, state 'N/A' for that specific field. If the scraping process fails entirely, return the exact string "SCRAPING_FAILED"."""
74
  )
@@ -82,7 +82,7 @@ class FragranticaCrew:
82
  - Intensidade: Ranging from 1 to 5\n
83
  - Fixação na minha pele: Ranging from 1 to 5\n
84
  - Projeção: Ranging from 1 to 5\n
85
- - Segue o estilo do perfurme: Select the perfume that most match this one, based on "Este Perfume me Lembra do" and "Resumo detalhado" extracted earlier\n
86
  - Como ele é, na minha percepção: Based on your analyses, write a concise summary about "How do I see it". Where you give your opinion using info about the perfume grades, and etc.\n
87
  - Eu indico para quem: Give your opinion two opinions about who would like it. Something like "gostam de frangâncias cítricas e amedeirado", "Querem um perfurme forte para usar no inverno"\n
88
  Your output must be a text containing the "Extraction" values and the "Process" values, in user friaendly format."""
@@ -90,7 +90,7 @@ class FragranticaCrew:
90
  agent=self.reporter_agent,
91
  expected_output=(
92
  """A comprehensive perfume analysis report in markdown format.
93
- The report must include all extracted information (Resumo, Acordes principais, Pirâmide Olfativa, Longevidade, Projeção, Este Perfume me Lembra, Resumo detalhado)
94
  and the "Human Friendly" analysis (Nível de "doçura", Intensidade, Fixação na minha pele, Projeção, Segue o estilo do perfurme, Como ele é, na minha percepção, Eu indico para quem)."""
95
  ),
96
  context=[research_task]
 
54
 
55
  - Este Perfume me Lembra do: Find the section titled "Este perfume me lembra do", and list the perfume names mentioned there.
56
 
57
+ - Opinião dos usuários: Look for a section containing detailed user reviews, such as "Todas as Resenhas por Data" or similar, and synthesize a detailed summary from these reviews.
58
 
59
  3. Present the extracted information in a clear, structured format, ready for reporting. If any specific piece of information cannot be found, check again to make sure they are not found, after check again, if you truly do not find the info, state 'N/A' for that field. If the entire scraping process fails, return "SCRAPING_FAILED".
60
  """
 
68
  Longevidade,
69
  Projeção,
70
  Este Perfume me Lembra do,
71
+ Opinião dos usuários.
72
  Ensure Longevidade and Projeção use the exact specified string values.
73
  If any information is not found, state 'N/A' for that specific field. If the scraping process fails entirely, return the exact string "SCRAPING_FAILED"."""
74
  )
 
82
  - Intensidade: Ranging from 1 to 5\n
83
  - Fixação na minha pele: Ranging from 1 to 5\n
84
  - Projeção: Ranging from 1 to 5\n
85
+ - Segue o estilo do perfurme: Select the perfume that most match this one, based on "Este Perfume me Lembra do" and "Opinião dos usuários" extracted earlier\n
86
  - Como ele é, na minha percepção: Based on your analyses, write a concise summary about "How do I see it". Where you give your opinion using info about the perfume grades, and etc.\n
87
  - Eu indico para quem: Give your opinion two opinions about who would like it. Something like "gostam de frangâncias cítricas e amedeirado", "Querem um perfurme forte para usar no inverno"\n
88
  Your output must be a text containing the "Extraction" values and the "Process" values, in user friaendly format."""
 
90
  agent=self.reporter_agent,
91
  expected_output=(
92
  """A comprehensive perfume analysis report in markdown format.
93
+ The report must include all extracted information (Resumo, Acordes principais, Pirâmide Olfativa, Longevidade, Projeção, Este Perfume me Lembra, Opinião dos usuários)
94
  and the "Human Friendly" analysis (Nível de "doçura", Intensidade, Fixação na minha pele, Projeção, Segue o estilo do perfurme, Como ele é, na minha percepção, Eu indico para quem)."""
95
  ),
96
  context=[research_task]
stealth_scrape_tool.py CHANGED
@@ -8,7 +8,7 @@ class StealthScrapeTool(BaseTool):
8
  name: str = "Stealth Web Scraper"
9
  description: str = "A tool for stealthily scraping content from a given URL using Playwright and a CSS selector."
10
 
11
- async def _arun(self, website_url: str, css_element: str) -> str:
12
  try:
13
  async with Stealth().use_async(async_playwright()) as p:
14
  browser = await p.chromium.launch(headless=True)
 
8
  name: str = "Stealth Web Scraper"
9
  description: str = "A tool for stealthily scraping content from a given URL using Playwright and a CSS selector."
10
 
11
+ async def _arun(self, website_url: str, css_element = "body") -> str:
12
  try:
13
  async with Stealth().use_async(async_playwright()) as p:
14
  browser = await p.chromium.launch(headless=True)