阅读书籍: 《大模型推荐系统 | 算法原理、代码实战与案例分析》刘强
数据集来源:

注:大型文件包工具git-lfs、强化学习训练工具trl

直接推荐范式

利用大模型的上下文学习进行推荐

1、上下文学习推荐基本原理

  • 当模型参数规模足够大时,大模型本身就具备非常强的泛化能力。基本能够直接使用这类大模型进行个性化推荐。
  • 只需要使用特定提示词激发大模型的推荐能力就可以将其用于下游推荐任务,而无需进行任何微调,这种能力就叫做上下文学习,分为 zero-shot 和 few-shot 的。
  • 另外,可以分解任务,逐步解决,叫做思维链能力。
  • 注意,在经典的召回->排序推荐范式下,直接推荐范式其实是利用大模型进行排序,即对召回的候选物品进行排序。召回阶段使用其他推荐算法。

2、案例

2.1 LLMRank 实现

  • 构造用户历史交互序列(方法:顺序提示词、关注最近行为的提示词、上下文学习提示词)
  • 构造抽取候选集(方法:多路召回后,生成不同顺序。同时使用自举法减少位置偏差)
  • 使用大模型进行排序
  • 解析输出

注:实际效果:

  1. 可以个性化排序,但很难感知给定历史交互的顺序。
  2. 专门提示词的确可以改善排序效果。
  3. 大模型优于 zero-shot 推荐方法。
  4. 大模型排序存在位置偏见和热门偏见,需提示词或自举法缓解。

2.2 多任务实现

  • 直接设计一组提示词在 5 项推荐任务:
    1. 评分预测
    2. 序列推荐
    3. 直接推荐
    4. 解释生成
    5. 评论摘要
  • 做法:
    1. 构造特定任务的提示词
    2. 利用 chatGPT 生成推荐结果
    3. 优化推荐结果

2.3 NIR实现

  • Next-Item Recommendation 框架
  • 做法:
    1. 构建用户兴趣
    2. 代表性电影生成
    3. 电影推荐

3、上下文学习推荐代码实现

3.1 数据准备

构建数据集代码generate_json_data.py

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
import csv
import json

"""
按照如下格式构建验证数据集:

{

"instruction": "You are a recommendation system expert who provides personalized recommendations to users based on the background information provided.",

"input": "I've browsed the following news in the past in order:


[''Wheel Of Fortune' Guest Delivers Hilarious, Off The Rails Introduction','Hard Rock Hotel New Orleans collapse: Former site engineer weighs in','Felicity Huffman begins prison sentence for college admissions scam','Outer Banks storms unearth old shipwreck from 'Graveyard of the Atlantic'','Tiffany's is selling a holiday advent calendar for $112,000','This restored 1968 Winnebago is beyond adorable','Lori Loughlin Is 'Absolutely Terrified' After Being Hit With New Charge','Bruce Willis brought Demi Moore to tears after reading her book','Celebrity kids then and now: See how they've grown','Felicity Huffman Smiles as She Begins Community Service Following Prison Release','Queen Elizabeth Finally Had Her Dream Photoshoot, Thanks to Royal Dresser Angela Kelly','Hundreds of thousands of people in California are downriver of a dam that 'could fail'','Alexandria Ocasio-Cortez 'sincerely' apologizes for blocking ex-Brooklyn politician on Twitter, settles federal lawsuit','The Rock's Gnarly Palm Is a Testament to Life Without Lifting Gloves']


Then if I ask you to recommend a new news to me according to my browsing history, you should recommend 'Donald Trump Jr. reflects on explosive 'View' chat: 'I don't think they like me much anymore'' and now that I've just browsed 'Donald Trump Jr. reflects on explosive 'View' chat: 'I don't think they like me much anymore'', there are 22 candidate news that I can browse next:
1. 'Browns apologize to Mason Rudolph, call Myles Garrett's actions 'unacceptable'',
2. 'I've been writing about tiny homes for a year and finally spent 2 nights in a 300-foot home to see what it's all about here's how it went',
3. 'Opinion: Colin Kaepernick is about to get what he deserves: a chance',
4. 'The Kardashians Face Backlash Over 'Insensitive' Family Food Fight in KUWTK Clip',
5. 'THEN AND NOW: What all your favorite '90s stars are doing today',6. 'Report: Police investigating woman's death after Redskins' player Montae Nicholson took her to hospital',
7. 'U.S. Troops Will Die If They Remain in Syria, Bashar Al-Assad Warns',
8. '3 Indiana judges suspended after a night of drinking turned into a White Castle brawl',
9. 'Cows swept away by Hurricane Dorian found alive but how?',
10. 'Surviving Santa Clarita school shooting victims on road to recovery: Latest',
11. 'The Unlikely Star of My Family's Thanksgiving Table',
12. 'Meghan Markle and Hillary Clinton Secretly Spent the Afternoon Together at Frogmore Cottage',
13. 'Former North Carolina State, NBA player Anthony Grundy dies in stabbing, police say',
14. '85 Thanksgiving Recipes You Can Make Ahead',
15. 'Survivor Contestants Missy Byrd and Elizabeth Beisel Apologize For Their Actions',
16. 'Pete Davidson, Kaia Gerber Are Dating, Trying to Stay 'Low Profile'',
17. 'There's a place in the US where its been over 80 degrees since March',
18. 'Taylor Swift Rep Hits Back at Big Machine, Claims She's Actually Owed $7.9 Million in Unpaid Royalties',
19. 'The most talked about movie moments of the 2010s',
20. 'Belichick mocks social media in comments on Garrett incident',
21. '13 Reasons Why's Christian Navarro Slams Disney for Casting 'the White Guy' in The Little Mermaid',
22. '66 Cool Tech Gifts Anyone Would Be Thrilled to Receive'


Please select some news that I would like to browse next according to my browsing history. Please think step by step.


Please show me your results. Split your output with line break. You MUST select from the given candidate news. You can not generate news that are not in the given candidate list."

"output": "['Opinion: Colin Kaepernick is about to get what he deserves: a chance'\n]"

}

"""

instruction = ("You are a recommendation system expert who provides personalized recommendations to users based on "
"the background information provided.")

news_dict = {} # 从news.tsv获取每个新闻id到标题的映射字典。
with open('../data/mind/news.tsv', 'r') as file:
reader = csv.reader(file, delimiter='\t')
for row in reader:
news_id = row[0]
news_title = row[3]
news_dict[news_id] = news_title

data_list = [] # 利用behaviors.tsv数据获取用户喜欢和不喜欢的新闻
with open('../data/mind/behaviors.tsv', 'r') as file:
reader = csv.reader(file, delimiter='\t')
for row in reader:
history = row[3]
impression = row[4]
history_list = history.split(" ")
impre_list = impression.split(" ")
if len(history_list) >= 6 and len(impre_list) >= 10: # 用户至少要有6个点击历史和10个曝光历史
his = []
for news_id in history_list[:-1]:
title = news_dict[news_id]
his.append(title)
last_view_id = history_list[-1]
last_view = news_dict[last_view_id]
preference = []
candidate = []
index = 1
for impre in impre_list:
[impre_id, action] = impre.split("-")
title = news_dict[impre_id]
candidate.append(str(index) + ". " + title + "\n")
index = index + 1
if int(action) == 1:
preference.append(title + "\n")
input = ("I’ve browsed the following news in the past in order:\n\n" +
"[" + ','.join(his) + "]" + "\n\n" +
"Then if I ask you to recommend a new news to me according to my browsing history, "
"you should recommend " + last_view + " and now that I've just browsed " + last_view + ", " +
"there are " + str(len(impre_list)) + " candidate news that I can browse next:" +
','.join(candidate) + "\n\n" +
"Please select some news that I would like to browse next according to my browsing history. " +
"Please think step by step.\n\n" +
"Please show me your results. Split your output with line break. You MUST select from the given " +
"candidate news. You can not generate news that are not in the given candidate list."
)
output = "[\n" + ','.join(preference) + "\n]"
res_dic = {
"instruction": instruction,
"input": input,
"output": output
}
data_list.append(res_dic)

res = json.dumps(data_list, indent=4, ensure_ascii=False)
user_sequence_path = "../data/mind/test.json" # 将生成的训练数据保存起来
with open(user_sequence_path, 'a') as file:
file.write(res)


3.2 上下文学习推荐

有两种方法:

  1. 单步骤上下文学习推荐
  2. 多步骤上下文学习推荐

单步骤icl_rec.py

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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import json

import requests
import torch
from datasets import load_dataset
from langchain.callbacks import StreamingStdOutCallbackHandler
from langchain.chains import LLMChain
from langchain.prompts.chat import ChatPromptTemplate
from langchain_community.llms import LlamaCpp
from langchain_community.llms.huggingface_pipeline import HuggingFacePipeline
from mlx_lm import load, generate
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, TextStreamer


def icl_rec(
model_path: str = "",
icl_type: str = "",
instruction: str = "",
prompt: str = "",
temperature: float = 0.1,
top_p: float = 0.95,
ctx: int = 13000,
):
"""
针对单个prompt为用户进行icl推荐
:param model_path: 模型地址
:param icl_type: icl推理类型,我们实现了ollama、llamacpp、transformers、mlx 4类icl推理方法
:param instruction: 指令
:param prompt: 提示词
:param temperature: 大模型温度系数
:param top_p: 大模型的top_p
:param ctx: 输出token长度
:return: 无
"""
if not instruction:
instruction = ("You are a recommendation system expert who provides personalized recommendations to users "
"based on the background information provided.")
if not prompt:
prompt = """
"I've browsed the following news in the past in order:


[''Wheel Of Fortune' Guest Delivers Hilarious, Off The Rails Introduction','Hard Rock Hotel New Orleans collapse: Former site engineer weighs in','Felicity Huffman begins prison sentence for college admissions scam','Outer Banks storms unearth old shipwreck from 'Graveyard of the Atlantic'','Tiffany's is selling a holiday advent calendar for $112,000','This restored 1968 Winnebago is beyond adorable','Lori Loughlin Is 'Absolutely Terrified' After Being Hit With New Charge','Bruce Willis brought Demi Moore to tears after reading her book','Celebrity kids then and now: See how they've grown','Felicity Huffman Smiles as She Begins Community Service Following Prison Release','Queen Elizabeth Finally Had Her Dream Photoshoot, Thanks to Royal Dresser Angela Kelly','Hundreds of thousands of people in California are downriver of a dam that 'could fail'','Alexandria Ocasio-Cortez 'sincerely' apologizes for blocking ex-Brooklyn politician on Twitter, settles federal lawsuit','The Rock's Gnarly Palm Is a Testament to Life Without Lifting Gloves']


Then if I ask you to recommend a new news to me according to my browsing history, you should recommend 'Donald Trump Jr. reflects on explosive 'View' chat: 'I don't think they like me much anymore'' and now that I've just browsed 'Donald Trump Jr. reflects on explosive 'View' chat: 'I don't think they like me much anymore'', there are 22 candidate news that I can browse next:
1. 'Browns apologize to Mason Rudolph, call Myles Garrett's actions 'unacceptable'',
2. 'I've been writing about tiny homes for a year and finally spent 2 nights in a 300-foot home to see what it's all about here's how it went',
3. 'Opinion: Colin Kaepernick is about to get what he deserves: a chance',
4. 'The Kardashians Face Backlash Over 'Insensitive' Family Food Fight in KUWTK Clip',
5. 'THEN AND NOW: What all your favorite '90s stars are doing today',6. 'Report: Police investigating woman's death after Redskins' player Montae Nicholson took her to hospital',
7. 'U.S. Troops Will Die If They Remain in Syria, Bashar Al-Assad Warns',
8. '3 Indiana judges suspended after a night of drinking turned into a White Castle brawl',
9. 'Cows swept away by Hurricane Dorian found alive but how?',
10. 'Surviving Santa Clarita school shooting victims on road to recovery: Latest',
11. 'The Unlikely Star of My Family's Thanksgiving Table',
12. 'Meghan Markle and Hillary Clinton Secretly Spent the Afternoon Together at Frogmore Cottage',
13. 'Former North Carolina State, NBA player Anthony Grundy dies in stabbing, police say',
14. '85 Thanksgiving Recipes You Can Make Ahead',
15. 'Survivor Contestants Missy Byrd and Elizabeth Beisel Apologize For Their Actions',
16. 'Pete Davidson, Kaia Gerber Are Dating, Trying to Stay 'Low Profile'',
17. 'There's a place in the US where its been over 80 degrees since March',
18. 'Taylor Swift Rep Hits Back at Big Machine, Claims She's Actually Owed $7.9 Million in Unpaid Royalties',
19. 'The most talked about movie moments of the 2010s',
20. 'Belichick mocks social media in comments on Garrett incident',
21. '13 Reasons Why's Christian Navarro Slams Disney for Casting 'the White Guy' in The Little Mermaid',
22. '66 Cool Tech Gifts Anyone Would Be Thrilled to Receive'


Please select some news that I would like to browse next according to my browsing history. Please think step by step.


Please show me your results. Split your output with line break. You MUST select from the given candidate news. You can not generate news that are not in the given candidate list."

"""

assert (
icl_type
), "Please specify a --icl_type, e.g. --icl_type='ollama'"

if icl_type == "ollama":
"""
利用Ollama框架将大模型封装成类似ChatGPT接口规范的API服务,直接通过调用接口来实现大模型icl推荐
"""
url = "http://localhost:11434/api/chat" # Ollama的api地址
data = {
"model": "yi:34b-chat", # Ollama安装的模型名
"options": {
"temperature": temperature,
"top_p": top_p,
"num_ctx": ctx,
"num_gpu": 128,
},
"messages": [
{
"role": "user",
"content": prompt
}
]
}
response = requests.post(url=url, json=data, stream=True)
for chunk in response.iter_content(chunk_size=None, decode_unicode=True):
j = json.loads(chunk.decode('utf-8'))
print(j['message']['content'], end="")

elif icl_type == "llamacpp":
if not model_path:
model_path = "/Users/liuqiang/Desktop/code/llm/models/gguf/qwen1.5-72b-chat-q5_k_m.gguf"
callback = StreamingStdOutCallbackHandler()
n_gpu_layers = 128 # Change this value based on your model and your GPU VRAM pool.
n_batch = 4096 # Should be between 1 and n_ctx, consider the amount of RAM of your Apple Silicon Chip.
llm = LlamaCpp(
streaming=True,
model_path=model_path,
n_gpu_layers=n_gpu_layers,
n_batch=n_batch,
f16_kv=True, # MUST set to True, otherwise you will run into problem after a couple of calls
temperature=temperature,
top_p=top_p,
n_ctx=ctx,
callbacks=[callback],
verbose=True
)
system = [("system", instruction),
("user", "{input}")]
template = ChatPromptTemplate.from_messages(system)
chain = LLMChain(prompt=template, llm=llm)
chain.invoke({"input": prompt})

elif icl_type == "transformers":
if not model_path:
model_path = "/Users/liuqiang/Desktop/code/llm/models/Yi-34B-Chat"
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
model_path,
trust_remote_code=True,
torch_dtype=torch.float16,
device_map="mps",
use_cache=True,
)
model = model.to("mps")
streamer = TextStreamer(tokenizer)
pipe = pipeline(
"text-generation",
model=model,
tokenizer=tokenizer,
streamer=streamer,
max_length=13000,
temperature=temperature,
pad_token_id=tokenizer.eos_token_id,
top_p=top_p,
repetition_penalty=1.15,
do_sample=False,
)
llm = HuggingFacePipeline(pipeline=pipe)
system = [("system", instruction),
("user", "{input}")]
template = ChatPromptTemplate.from_messages(system)
chain = LLMChain(prompt=template, llm=llm)
chain.invoke({"input": prompt})

elif icl_type == "mlx": # 苹果的MLX框架
if not model_path:
model_path = "/Users/liuqiang/Desktop/code/llm/models/Yi-34B-Chat"
model, tokenizer = load(model_path)
response = generate(
model,
tokenizer,
prompt=prompt,
temp=temperature,
max_tokens=10000,
verbose=True
)
print(response)

else:
raise NotImplementedError("the case not implemented!")


def icl_rec_batch(validation_path: str, icl_type='llamacpp'):
"""
针对从训练数据构建的测试集,逐条为用户进行icl推理,你可以跟真实值(即output)对比,查看大模型icl推荐的效果
"""
data = load_dataset("json", data_files=validation_path)
index = 1
for row in data['train']:
instruction = row['instruction']
input = row['input']
output = row['output']
print(str(index) + " : --------------------")
icl_rec(icl_type=icl_type, instruction=instruction, prompt=input)
print(output)


if __name__ == "__main__":
# fire.Fire(icl_rec)
icl_rec(icl_type="llamacpp")
# icl_rec_batch("data/mind/test.json")

多步骤icl_multi_step_rec.py

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
171
172
173
174
175
176
import json

import requests


def personalized_generation(
model: str = "qwen:72b-chat-v1.5-q5_K_M",
temperature: float = 0.1,
top_p: float = 0.95,
ctx: int = 13000,
message: list = None,
is_stream: bool = False
):
"""
利用Ollama框架将大模型封装成类似ChatGPT接口规范的API服务,基于提供的信息为你生产个性化的回答
:param model: ollama安装的大模型名
:param temperature: 温度
:param top_p: top_p
:param ctx: 生成token长度
:param message: 提供给大模型的对话信息
:param is_stream: 是否流式输出大模型的应答
:return: 基于prompt,利用大模型生成的结果
"""
if message is None:
message = [{}]
url = "http://localhost:11434/api/chat" # Ollama的api地址
data = {
"model": model, # Ollama安装的模型名
"options": {
"temperature": temperature,
"top_p": top_p,
"num_ctx": ctx,
"num_gpu": 128,
},
"messages": message,
"stream": is_stream
}
if is_stream:
response = requests.post(url=url, json=data, stream=True)
res = ""
for chunk in response.iter_content(chunk_size=None, decode_unicode=True):
j = json.loads(chunk.decode('utf-8'))
res = res + j['message']['content']
print(j['message']['content'], end="")
return res
else:
response = requests.post(url=url, json=data, stream=False)
res = json.loads(response.content)["message"]["content"]
return res


if __name__ == "__main__":

instruction = ("You are a recommendation system expert in the news field, providing personalized "
"recommendations to users based on the background information provided.")

portrait_prompt = """
The news I have browsed:
[''Wheel Of Fortune' Guest Delivers Hilarious, Off The Rails Introduction','Hard Rock Hotel New Orleans collapse: Former site engineer weighs in','Felicity Huffman begins prison sentence for college admissions scam','Outer Banks storms unearth old shipwreck from 'Graveyard of the Atlantic'','Tiffany's is selling a holiday advent calendar for $112,000','This restored 1968 Winnebago is beyond adorable','Lori Loughlin Is 'Absolutely Terrified' After Being Hit With New Charge','Bruce Willis brought Demi Moore to tears after reading her book','Celebrity kids then and now: See how they've grown','Felicity Huffman Smiles as She Begins Community Service Following Prison Release','Queen Elizabeth Finally Had Her Dream Photoshoot, Thanks to Royal Dresser Angela Kelly','Hundreds of thousands of people in California are downriver of a dam that 'could fail'','Alexandria Ocasio-Cortez 'sincerely' apologizes for blocking ex-Brooklyn politician on Twitter, settles federal lawsuit','The Rock's Gnarly Palm Is a Testament to Life Without Lifting Gloves']

What features are most important to me when selecting news (Summarize my preferences briefly)?
"""

representable_prompt = """
You will select the news (at most 5 news) that appeal to me the most from the list of news I have browsed, based on my personal preferences. The selected news will be presented in descending order of preference. (Format: no. a browsed news).
"""

rec_prompt = """
Candidate Set (candidate news):
1. 'Browns apologize to Mason Rudolph, call Myles Garrett's actions 'unacceptable'',
2. 'I've been writing about tiny homes for a year and finally spent 2 nights in a 300-foot home to see what it's all about here's how it went',
3. 'Opinion: Colin Kaepernick is about to get what he deserves: a chance',
4. 'The Kardashians Face Backlash Over 'Insensitive' Family Food Fight in KUWTK Clip',
5. 'THEN AND NOW: What all your favorite '90s stars are doing today',6. 'Report: Police investigating woman's death after Redskins' player Montae Nicholson took her to hospital',
7. 'U.S. Troops Will Die If They Remain in Syria, Bashar Al-Assad Warns',
8. '3 Indiana judges suspended after a night of drinking turned into a White Castle brawl',
9. 'Cows swept away by Hurricane Dorian found alive but how?',
10. 'Surviving Santa Clarita school shooting victims on road to recovery: Latest',
11. 'The Unlikely Star of My Family's Thanksgiving Table',
12. 'Meghan Markle and Hillary Clinton Secretly Spent the Afternoon Together at Frogmore Cottage',
13. 'Former North Carolina State, NBA player Anthony Grundy dies in stabbing, police say',
14. '85 Thanksgiving Recipes You Can Make Ahead',
15. 'Survivor Contestants Missy Byrd and Elizabeth Beisel Apologize For Their Actions',
16. 'Pete Davidson, Kaia Gerber Are Dating, Trying to Stay 'Low Profile'',
17. 'There's a place in the US where its been over 80 degrees since March',
18. 'Taylor Swift Rep Hits Back at Big Machine, Claims She's Actually Owed $7.9 Million in Unpaid Royalties',
19. 'The most talked about movie moments of the 2010s',
20. 'Belichick mocks social media in comments on Garrett incident',
21. '13 Reasons Why's Christian Navarro Slams Disney for Casting 'the White Guy' in The Little Mermaid',
22. '66 Cool Tech Gifts Anyone Would Be Thrilled to Receive'


Can you recommend 3 news from the Candidate Set similar to the selected news I've browsed (Format: [no. a browsed news : <- a candidate news >])?

"""

stream = True

step_1_message = [
{
"role": "system",
"content": instruction
},
{
"role": "user",
"content": portrait_prompt
}
]

print("========== step 1 start ================")

step_1_output = personalized_generation(message=step_1_message, is_stream=stream)

if not stream:
print(step_1_output)

step_2_message = [
{
"role": "system",
"content": instruction
},
{
"role": "user",
"content": portrait_prompt
},
{
"role": "assistant",
"content": step_1_output
},
{
"role": "user",
"content": representable_prompt
}
]

print("========== step 2 start ================")

step_2_output = personalized_generation(message=step_2_message, is_stream=stream)

if not stream:
print(step_2_output)

step_3_message = [
{
"role": "system",
"content": instruction
},
{
"role": "user",
"content": portrait_prompt
},
{
"role": "assistant",
"content": step_1_output
},
{
"role": "user",
"content": representable_prompt
},
{
"role": "assistant",
"content": step_2_output
},
{
"role": "user",
"content": rec_prompt
}
]

print("========== step 3 start ================")

step_3_output = personalized_generation(message=step_3_message, is_stream=stream)

if not stream:
print(step_3_output)