File size: 7,291 Bytes
54fa6eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
from typing import Any, Dict
from openai import OpenAI
from string import Template 
import logging
import json


class LLMHandler:
    def __init__(self, api_key, model_name, default_temperature, default_max_tokens, default_system_message=None):

        self.client = OpenAI(api_key=api_key)
        self.model_name = model_name
        self.default_temperature = default_temperature
        self.default_max_tokens = default_max_tokens
        self.default_system_message = default_system_message
  
    def generate(
            self, 
            prompt, 
            model_name, 
            temperature=None, 
            max_tokens=None, 
            system_message=None):
        
        # optional parameters
        model_name = model_name or self.model_name
        temperature = temperature or self.default_temperature
        max_tokens = max_tokens or self.default_max_tokens
        system_message = system_message or self.default_system_message

        # prepare messages
        messages = [{"role": "user", "content": prompt}]
        if system_message:
            messages.insert(0, {"role": "system", "content": system_message}) 

        # get response
        logging.info(f"Generating text with model {model_name}, temperature {temperature}, and max tokens {max_tokens}...")
        response = self.client.chat.completions.create(
            model=model_name,
            messages=messages,
            temperature=temperature,
            max_tokens=max_tokens,
            seed=1234,
            response_format={ "type": "json_object" } 
        )
        return response.choices[0].message.content



def format_personalization(personalization):
    return '\n'.join([f"- {key}: {value}" for key, value in personalization.items()])


def build_prompt(
        context: Dict[str, Any]) -> str:
        """
        Create a detailed prompt incorporating all personalization factors
        
        :param section_type: Type of section to generate
        :param context: Context data including items and customer info
        :param additional_textual_context: additional context for personalization
        :return: Formatted prompt string
        """

        # Create a detailed user prompt with all context and personalization
        user_prompt = f"""
        Generate personalized newsletter content based on the following information:

        Customer Purchase Context:
        - Previously bought items: {context.pop('bought_items', [])}
        - Recommended items: {context.pop('recommended_items', [])}
        
        Personalization Parameters:
        {format_personalization(context)}

        Required Sections:
        1. greeting: A warm personalized welcome that considers all personalization parameters. Start with "Hello [customer name]," 
        2. intro: Acknowledge previous purchases and build connection warmly. Mention the previous purchases. Only acknoweldge previous purchases that are actually present in the context. 
        3. recommendations: Present recommended items naturally, explaining why they match the customer's preferences and previous purchases.
        4. closing: Wrap up maintaining the established tone. 

        General reequirements:
        - Write in first person. 
        - Don't be too formal, maintain a friendly, warm and engaging tone, avoid stuff like "I hope you are doing well".
        - The reader should feel like they have a special dedicated fashion assistant, who is also a friend.
        - When mentioning items, use the item name and avoid mentioning colors that are not present in the image, or using adjectives like "vibrant".
        - The goal should be to make the reader feel special and excited about the items they are being recommended.

        
        Please generate all sections in a single, coherent response that maintains consistency in tone and style throughout. Begin each section with the section name in all caps.
        """
        return user_prompt


def initialize_newsletter(newsletter_meta_info, transactions, recommendations):
    
    # get the paths to the template files
    newsletter_example_path = newsletter_meta_info['newsletter_example_path']
    
    # load the template from file
    with open(newsletter_example_path, "r") as f:
        newsletter_text = Template(f.read())
        newsletter_text = newsletter_text.safe_substitute(
            brand_logo=newsletter_meta_info.get("brand_logo", ""),
            brand_name=newsletter_meta_info.get("brand_name", ""),
            )
    
    # iterate over the transactions and replace the placeholders with the actual content
    for i, transaction in enumerate(transactions):
        newsletter_text = newsletter_text.replace("${transaction_url}", transaction['product_image_url'], 1)
        newsletter_text = newsletter_text.replace("${transaction_name}", transaction['product_name'], 1)

    # iterate over the recommendations and replace the placeholders with the actual content
    for i, recommendation in enumerate(recommendations):
        newsletter_text = newsletter_text.replace("${recommendation_url}", recommendation['product_image_url'], 1)
        newsletter_text = newsletter_text.replace("${recommendation_name}", recommendation['product_name'], 1)
    return newsletter_text


def integrate_personalized_text(newsletter_text, customer_info, textual_sections):
    # get dctionary from the textual sections json
    textual_sections = json.loads(textual_sections)

    logging.info(textual_sections)

    newsletter_text = Template(newsletter_text)
    newsletter_text = newsletter_text.safe_substitute(
        **textual_sections
    )

    # make sure the greeting line is h1, so replace Hello ${customer_name} with <h1>Hello ${customer_name}</h1>
    customer_name = customer_info.get("customer name")
    newsletter_text = newsletter_text.replace(f"Hello {customer_name},", f"<h1>Hello {customer_name},</h1>")

    return newsletter_text


def build_context(recommendations, transactions, additional_context, customer_info):
    
    # clean recommendations and transactions 
    for rec in recommendations:
        rec.pop('product_image_url', None)
        rec.pop('article_id', None)
        rec.pop('sales_channel_id', None)
        rec.pop('transaction_date', None)
    for tr in transactions:
        tr.pop('product_image_url', None)
        tr.pop('article_id', None)
        tr.pop('sales_channel_id', None)
        tr.pop('transaction_date', None)
        tr.pop('price', None)    

    context = {
        'recommended_items': recommendations,
        'bought_items': transactions,
        'customer_name': customer_info.get('customer name', 'Unknown'),
        'customer_age': customer_info.get('customer age', 'Unknown'),
        'last_visit': customer_info.get('last_visit', 'Unknown'),
        # 'preferred_categories': customer_info.get('preferred_categories', 'Unknown'),
        # 'shopping_frequency': customer_info.get('shopping_frequency', 'Unknown'),
        # 'style_preferences': customer_info.get('style_preferences', 'Unknown'),
        # 'size_preferences': customer_info.get('size_preferences', 'Unknown'),
        'preferences': additional_context if len(additional_context) > 1 else 'No additional preferences',
    }

    return context