KirkHan commited on
Commit
7706e61
·
verified ·
1 Parent(s): a2eac08

Upload rag_engine.py

Browse files
Files changed (1) hide show
  1. rag_engine.py +480 -0
rag_engine.py ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ RAG引擎:实现传统RAG和GraphRAG的检索逻辑
3
+ """
4
+ from typing import List, Dict, Tuple
5
+ # 优先使用轻量级版本(避免超过 Vercel 250MB 限制)
6
+ try:
7
+ from database_setup_lite import SimpleGraphDB, VectorDB
8
+ except ImportError:
9
+ from database_setup import SimpleGraphDB, VectorDB
10
+ import json
11
+ import requests
12
+
13
+ # LLM配置(从环境变量读取,确保安全)
14
+ import os
15
+ LLM_API_BASE = os.getenv("LLM_API_BASE", "https://api.ai-gaochao.cn/v1")
16
+ LLM_API_KEY = os.getenv("LLM_API_KEY", "")
17
+ LLM_MODEL = os.getenv("LLM_MODEL", "gemini-2.5-flash")
18
+
19
+ if not LLM_API_KEY:
20
+ raise ValueError("LLM_API_KEY 环境变量未设置!请在 .env 文件中设置 LLM_API_KEY")
21
+
22
+ class TraditionalRAG:
23
+ """传统语义RAG"""
24
+ def __init__(self, vector_db: VectorDB, graph_db: SimpleGraphDB = None):
25
+ self.vector_db = vector_db
26
+ self.graph_db = graph_db # 用于限制搜索范围
27
+
28
+ def retrieve(self, query: str, product_name: str = None, style_name: str = None, n_results: int = 5) -> Dict:
29
+ """语义检索(传统RAG:直接向量搜索,不利用图结构,返回片段句子)"""
30
+ # 传统RAG的特点:直接进行语义相似度搜索,不利用图结构
31
+ # 使用相同的文案数据库,但只返回相似的片段句子(而不是完整文案)
32
+
33
+ # 直接进行向量搜索(传统RAG的特点)
34
+ # 传统RAG限制结果数量,只返回最相关的2-3个结果
35
+ limited_results = min(3, n_results) # 最多返回3个结果
36
+ all_results = self.vector_db.search(query, n_results=limited_results * 2) # 多搜索一些,用于提取片段
37
+
38
+ # 从完整文案中提取与查询最相关的片段句子
39
+ processed_results = []
40
+ query_keywords = set(query.lower().split())
41
+
42
+ for result in all_results[:limited_results * 2]:
43
+ full_content = result.get("content", "")
44
+ if not full_content:
45
+ continue
46
+
47
+ # 将文案按句子分割(中文句号、英文句号、感叹号、问号)
48
+ import re
49
+ sentences = re.split(r'[。!?.!?]', full_content)
50
+ sentences = [s.strip() for s in sentences if s.strip()]
51
+
52
+ # 找到与查询最相关的句子片段
53
+ best_sentences = []
54
+ for sentence in sentences:
55
+ # 计算句子与查询的相关度(简单关键词匹配)
56
+ sentence_lower = sentence.lower()
57
+ keyword_matches = sum(1 for keyword in query_keywords if keyword in sentence_lower)
58
+ if keyword_matches > 0:
59
+ best_sentences.append((sentence, keyword_matches))
60
+
61
+ # 按相关度排序,取前2-3个最相关的句子
62
+ best_sentences.sort(key=lambda x: x[1], reverse=True)
63
+ selected_sentences = [s[0] for s in best_sentences[:3]]
64
+
65
+ # 如果没有找到相关句子,取前3个句子作为片段
66
+ if not selected_sentences and sentences:
67
+ selected_sentences = sentences[:3]
68
+
69
+ # 组合成片段(最多150字,确保有足够内容)
70
+ snippet = "。".join(selected_sentences)
71
+ if not snippet and sentences:
72
+ # 如果还是空的,至少取前3个句子
73
+ snippet = "。".join(sentences[:3])
74
+ if len(snippet) > 150:
75
+ snippet = snippet[:150] + "..."
76
+ elif len(snippet) < 30 and len(sentences) > 0:
77
+ # 如果片段太短,至少取前2-3个句子
78
+ snippet = "。".join(sentences[:min(3, len(sentences))])
79
+ if len(snippet) > 150:
80
+ snippet = snippet[:150] + "..."
81
+
82
+ if snippet:
83
+ processed_results.append({
84
+ "content": snippet, # 返回片段而不是完整文案
85
+ "full_content": full_content, # 保留完整内容用于显示
86
+ "metadata": result.get("metadata", {}),
87
+ "distance": result.get("distance", 0),
88
+ "is_snippet": True # 标记这是片段
89
+ })
90
+
91
+ if len(processed_results) >= limited_results:
92
+ break
93
+
94
+ # 如果结果太少,至少返回1-2个语义相似的结果
95
+ if len(processed_results) < 1:
96
+ # 如果提取片段失败,至少返回一些结果
97
+ for result in all_results[:max(1, limited_results)]:
98
+ content = result.get("content", "")
99
+ if content:
100
+ # 简单截取前150字作为片段
101
+ snippet = content[:150] + "..." if len(content) > 150 else content
102
+ processed_results.append({
103
+ "content": snippet,
104
+ "full_content": content,
105
+ "metadata": result.get("metadata", {}),
106
+ "distance": result.get("distance", 0),
107
+ "is_snippet": True
108
+ })
109
+ if len(processed_results) >= limited_results:
110
+ break
111
+
112
+ return {
113
+ "method": "语义检索",
114
+ "query": query,
115
+ "product": product_name,
116
+ "style": style_name,
117
+ "results": processed_results[:limited_results],
118
+ "retrieval_path": [
119
+ "向量相似度搜索(传统RAG:不利用图结构)",
120
+ f"找到 {len(processed_results)} 个语义相似的片段",
121
+ "⚠️ 局限性:只返回片段句子,没有图结构,无法找到跨品类的风格相关文案"
122
+ ],
123
+ "explanation": "传统RAG直接通过语义相似度搜索相关文案,使用相同的文案数据库,但只返回与查询最相关的片段句子(而不是完整文案)。没有图结构,无法找到跨品类的风格相关文案。"
124
+ }
125
+
126
+ class GraphRAG:
127
+ """图增强RAG"""
128
+ def __init__(self, graph_db: SimpleGraphDB, vector_db: VectorDB):
129
+ self.graph_db = graph_db
130
+ self.vector_db = vector_db
131
+
132
+ def retrieve(self, query: str, product_name: str = None, style_name: str = None, n_results: int = 5) -> Dict:
133
+ """图增强检索"""
134
+ retrieval_path = []
135
+ retrieved_docs = []
136
+
137
+ # 步骤1: 尝试找到风格节点
138
+ style_node = None
139
+ if style_name:
140
+ style_node = self.graph_db.find_node_by_property("Style", "name", style_name)
141
+ if style_node:
142
+ retrieval_path.append(f"定位风格节点: {style_node['properties']['name']}")
143
+
144
+ # 步骤2: 通过风格节点找到相关文案(跨品类)
145
+ if style_node:
146
+ # 反向查找:找到连接到风格的文案节点
147
+ for edge in self.graph_db.edges:
148
+ if edge["target"] == style_node["id"] and edge["relationship"] == "HAS_STYLE":
149
+ copy_node = self.graph_db.nodes.get(edge["source"])
150
+ if copy_node and copy_node["type"] == "Copywriting":
151
+ content = copy_node["properties"]["content"]
152
+ # 获取该文案关联的产品(HAS_COPY关系:Product -> Copywriting)
153
+ product_id = None
154
+ for e in self.graph_db.edges:
155
+ if e["target"] == edge["source"] and e["relationship"] == "HAS_COPY":
156
+ product_id = e["source"]
157
+ break
158
+
159
+ product_info = self.graph_db.nodes.get(product_id, {}).get("properties", {})
160
+ retrieved_docs.append({
161
+ "content": content,
162
+ "source": "图遍历",
163
+ "product": product_info.get("name", "未知"),
164
+ "style": style_name,
165
+ "tag": copy_node["properties"].get("tag", ""),
166
+ "retrieval_reason": f"通过风格节点'{style_name}'找到的跨品类文案(来自产品:{product_info.get('name', '未知')})"
167
+ })
168
+
169
+ if retrieved_docs:
170
+ retrieval_path.append(f"通过风格节点遍历找到 {len(retrieved_docs)} 个相关文案")
171
+ else:
172
+ retrieval_path.append("未找到该风格的相关文案")
173
+
174
+ # 步骤3: 如果指定了产品,查找产品特征
175
+ product_features = []
176
+ if product_name:
177
+ product_node = self.graph_db.find_node_by_property("Product", "name", product_name)
178
+ if product_node:
179
+ retrieval_path.append(f"定位产品节点: {product_name}")
180
+ features = product_node["properties"].get("features", [])
181
+ keywords = product_node["properties"].get("keywords", [])
182
+ product_features = features + keywords
183
+ retrieval_path.append(f"提取产品特征: {', '.join(product_features[:5])}")
184
+
185
+ # 步骤4: 如果图检索结果不足,用向量检索补充
186
+ if len(retrieved_docs) < n_results:
187
+ vector_results = self.vector_db.search(query, n_results=n_results - len(retrieved_docs))
188
+ for result in vector_results:
189
+ # 避免重复
190
+ if not any(doc["content"] == result["content"] for doc in retrieved_docs):
191
+ retrieved_docs.append({
192
+ "content": result["content"],
193
+ "source": "向量检索补充",
194
+ "product": result["metadata"].get("product_id", "未知"),
195
+ "style": result["metadata"].get("style_id", "未知"),
196
+ "tag": result["metadata"].get("tag", ""),
197
+ "retrieval_reason": "语义���似度补充检索"
198
+ })
199
+ if vector_results:
200
+ retrieval_path.append(f"向量检索补充 {len(vector_results)} 个结果")
201
+
202
+ return {
203
+ "method": "图增强检索",
204
+ "query": query,
205
+ "product": product_name,
206
+ "style": style_name,
207
+ "product_features": product_features,
208
+ "results": retrieved_docs[:n_results],
209
+ "retrieval_path": retrieval_path,
210
+ "explanation": "通过图结构找到跨品类的风格相关文案,即使产品不同,但风格相通,可以借鉴文案模板。"
211
+ }
212
+
213
+ class RAGEngine:
214
+ """RAG引擎主类"""
215
+ def __init__(self, graph_db: SimpleGraphDB, vector_db: VectorDB):
216
+ self.graph_db = graph_db
217
+ self.traditional_rag = TraditionalRAG(vector_db, graph_db)
218
+ self.graph_rag = GraphRAG(graph_db, vector_db)
219
+
220
+ def compare_retrieval(self, query: str, product_name: str = None, style_name: str = None) -> Dict:
221
+ """对比传统RAG和GraphRAG的检索结果"""
222
+ traditional_result = self.traditional_rag.retrieve(query, product_name, style_name)
223
+ graph_result = self.graph_rag.retrieve(query, product_name, style_name)
224
+
225
+ return {
226
+ "traditional_rag": traditional_result,
227
+ "graph_rag": graph_result,
228
+ "comparison": {
229
+ "traditional_count": len(traditional_result["results"]),
230
+ "graph_count": len(graph_result["results"]),
231
+ "graph_cross_category": len([r for r in graph_result["results"] if r.get("source") == "图遍历"])
232
+ }
233
+ }
234
+
235
+ def generate_copywriting(self, query: str, product_name: str, style_name: str, use_graph: bool = True) -> Dict:
236
+ """生成文案(使用LLM)"""
237
+ if use_graph:
238
+ retrieval_result = self.graph_rag.retrieve(query, product_name, style_name)
239
+ else:
240
+ retrieval_result = self.traditional_rag.retrieve(query, product_name, style_name)
241
+
242
+ # 获取检索到的参考文案
243
+ retrieved_texts = [r["content"] for r in retrieval_result["results"][:5]] # 取前5个作为参考
244
+
245
+ # 统计信息
246
+ cross_category_count = len([r for r in retrieval_result["results"] if r.get("source") == "图遍历"]) if use_graph else 0
247
+
248
+ # 获取产品特征(用于GraphRAG)
249
+ product_features = []
250
+ if use_graph and retrieval_result.get("product_features"):
251
+ product_features = retrieval_result["product_features"]
252
+
253
+ # 调用LLM生成文案
254
+ try:
255
+ llm_generated = self._call_llm_generate(
256
+ product_name=product_name,
257
+ style_name=style_name,
258
+ reference_texts=retrieved_texts,
259
+ product_features=product_features,
260
+ use_graph=use_graph,
261
+ cross_category_count=cross_category_count
262
+ )
263
+ except Exception as e:
264
+ print(f"LLM生成失败: {e}")
265
+ # 如果LLM失败,使用模板生成
266
+ llm_generated = self._generate_template(retrieved_texts, product_name, style_name)
267
+
268
+ # 组装最终输出
269
+ if use_graph and product_features:
270
+ features = ", ".join(product_features[:3])
271
+ reference_sources = ', '.join([r.get('product', '未知') for r in retrieval_result["results"][:3]])
272
+ generated_text = f"""基于图增强检索生成的文案:
273
+
274
+ ✨ 检索策略:通过图结构找到跨品类的风格相关文案
275
+ 📊 检索结果:找到 {len(retrieved_texts)} 个相关文案,其中 {cross_category_count} 个来自跨品类(通过风格节点关联)
276
+ 🎯 产品特征:{features}
277
+ 📝 参考文案来源:{reference_sources}
278
+
279
+ 【{style_name}风格】{product_name}文案:
280
+
281
+ {llm_generated}
282
+
283
+ 💡 说明:GraphRAG 通过风格节点找到了跨品类的参考文案(如香薰蜡烛的清冷避世风文案),即使产品不同,但风格相通,可以借鉴文案模板。"""
284
+ else:
285
+ generated_text = f"""基于传统语义检索生成的文案:
286
+
287
+ 🔍 检索策略:直接通过语义相似度搜索
288
+ 📊 检索结果:找到 {len(retrieved_texts)} 个语义相似的文案
289
+ ⚠️ 局限性:如果数据库中没有相似内容,可能返回不相关的结果
290
+
291
+ 【{style_name}风格】{product_name}文案:
292
+
293
+ {llm_generated}
294
+
295
+ 💡 说明:传统 RAG 只能找到语义相似的文案,如果数据库中没有该产品的该风格文案,可能无法生成合适的文案。"""
296
+
297
+ return {
298
+ "generated_text": generated_text,
299
+ "retrieval_result": retrieval_result,
300
+ "method": "GraphRAG" if use_graph else "Traditional RAG"
301
+ }
302
+
303
+ def _call_llm_generate(self, product_name: str, style_name: str, reference_texts: List[str],
304
+ product_features: List[str] = None, use_graph: bool = True,
305
+ cross_category_count: int = 0) -> str:
306
+ """调用LLM生成文案"""
307
+ headers = {
308
+ "Content-Type": "application/json",
309
+ "Authorization": f"Bearer {LLM_API_KEY}"
310
+ }
311
+ url = f"{LLM_API_BASE}/chat/completions"
312
+
313
+ # 构建参考文案说明
314
+ reference_context = ""
315
+ if reference_texts:
316
+ reference_context = "\n\n参考文案(用于学习风格和句式):\n"
317
+ for i, text in enumerate(reference_texts[:3], 1):
318
+ reference_context += f"{i}. {text}\n"
319
+ else:
320
+ reference_context = "\n\n⚠️ 注意:没有找到相关参考文案,请根据产品特征和风格要求创作。"
321
+
322
+ # 构建产品特征说明
323
+ features_context = ""
324
+ if product_features:
325
+ features_context = f"\n产品特征:{', '.join(product_features[:5])}"
326
+
327
+ # 构建prompt
328
+ if use_graph and cross_category_count > 0:
329
+ prompt = f"""你是一名擅长小红书文案写作的创意编辑。请根据以下信息,生成一篇适合在小红书发布的文案(200-300字,要求内容丰富、有细节感)。
330
+
331
+ 产品名称:{product_name}
332
+ 目标风格:{style_name}
333
+ {features_context}
334
+
335
+ {reference_context}
336
+
337
+ 重要提示:
338
+ 1. 这些参考文案来自其他产品(跨品类),但风格相同,请学习它们的句式、语气和情感表达方式
339
+ 2. 将参考文案的风格和句式应用到目标产品上
340
+ 3. 文案要有细节感、人情味,符合小红书用户的阅读习惯
341
+ 4. 保持{style_name}的风格特征
342
+ 5. 文案长度要求200-300字,要有丰富的内容和细节描述,可以包含使用场景、情感体验、产品特色等多个方面
343
+ 6. 请确保文案完整,不要被截断,以完整的句子结尾
344
+
345
+ **必须遵守的输出格式要求:**
346
+ - 你必须使用中英对照格式输出文案,按段落进行中英对照
347
+ - 格式:中文段落(换行)English paragraph(再换行)
348
+ - 每个中文段落后面必须换行,然后添加对应的英文段落翻译,英文段落后再换行
349
+ - 示例格式:
350
+ 这款真丝眼罩真的太舒服了,遮光效果特别好,戴上之后整个世界都安静了。
351
+ This silk eye mask is really comfortable, with excellent light-blocking effect. After putting it on, the whole world becomes quiet.
352
+
353
+ 每天晚上睡前戴上它,就像给自己创造了一个专属的避风港。
354
+ Every night before sleep, putting it on is like creating a personal sanctuary for yourself.
355
+
356
+ 材质柔软亲肤,完全不会压迫眼睛,真的爱了。
357
+ The material is soft and skin-friendly, completely non-pressuring on the eyes, I really love it.
358
+ - 不要只输出中文,必须每个段落都包含对应的英文翻译
359
+ - 可以一个段落包含多句话,然后整体翻译成英文
360
+ - 每个中文段落和英文段落之间必须换行,段落之间用空行分隔
361
+
362
+ 请直接输出文案内容,不要包含"好的"、"没问题"等前缀,也不要使用markdown格式。只输出文案正文,确保内容完整,并且严格按照以下格式输出:中文段落(换行)English paragraph(换行)。"""
363
+ else:
364
+ prompt = f"""你是一名擅长小红书文案写作的创意编辑。请根据以下信息,生成一篇适合在小红书发布的文案(200-300字,要求内容丰富、有细节感)。
365
+
366
+ 产品名称:{product_name}
367
+ 目标风格:{style_name}
368
+ {features_context}
369
+
370
+ {reference_context}
371
+
372
+ 重要提示:
373
+ 1. 参考文案可能有限或不够相关,请根据产品特征和风格要求创作
374
+ 2. 文案要有细节感、人情味,符合小红书用户的阅读习惯
375
+ 3. 保持{style_name}的风格特征
376
+ 4. 文案长度要求200-300字,要有丰富的内容和细节描述,可以包含使用场景、情感体验、产品特色等多个方面
377
+ 5. 请确保文案完整,不要被截断,以完整的句子结尾
378
+
379
+ **必须遵守的输出格式要求:**
380
+ - 你必须使用中英对照格式输出文案,按段落进行中英对照
381
+ - 格式:中文段落(换行)English paragraph(再换行)
382
+ - 每个中文段落后面必须换行,然后添加对应的英文段落翻译,英文段落后再换行
383
+ - 示例格式:
384
+ 这款真丝眼罩真的太舒服了,遮光效果特别好,戴上之后整个世界都安静了。
385
+ This silk eye mask is really comfortable, with excellent light-blocking effect. After putting it on, the whole world becomes quiet.
386
+
387
+ 每天晚上睡前戴上它,就像给自己创造了一个专属的避风港。
388
+ Every night before sleep, putting it on is like creating a personal sanctuary for yourself.
389
+
390
+ 材质柔软亲肤,完全不会压迫眼睛,真的爱了。
391
+ The material is soft and skin-friendly, completely non-pressuring on the eyes, I really love it.
392
+ - 不要只输出中文,必须每个段落都包含对应的英文翻译
393
+ - 可以一个段落包含多句话,然后整体翻译成英文
394
+ - 每个中��段落和英文段落之间必须换行,段落之间用空行分隔
395
+
396
+ 请直接输出文案内容,不要包含"好的"、"没问题"等前缀,也不要使用markdown格式。只输出文案正文,确保内容完整,并且严格按照以下格式输出:中文段落(换行)English paragraph(换行)。"""
397
+
398
+ body = {
399
+ "model": LLM_MODEL,
400
+ "messages": [
401
+ {
402
+ "role": "system",
403
+ "content": "你是一名擅长文案写作的创意编辑,擅长创作小红书风格的文案。你必须使用中英对照格式输出所有文案内容,按段落进行中英对照,每个中文段落后面换行添加对应的英文翻译。格式:中文段落(换行)English paragraph"
404
+ },
405
+ {
406
+ "role": "user",
407
+ "content": prompt
408
+ }
409
+ ],
410
+ "max_tokens": 4000, # 增加token限制以支持更长的文案(200-300字约需要800-1200 tokens,设置4000确保完整输出)
411
+ "temperature": 0.9
412
+ }
413
+
414
+ resp = requests.post(url, headers=headers, json=body, timeout=60)
415
+ resp.raise_for_status()
416
+ data = resp.json()
417
+ generated = data["choices"][0]["message"]["content"].strip()
418
+
419
+ # 清理生成的内容
420
+ # 移除常见的前缀(只移除开头的前缀,不要截断内容)
421
+ prefixes_to_remove = [
422
+ "好的,没问题!",
423
+ "好的,",
424
+ "没问题!",
425
+ "好的!",
426
+ ]
427
+ for prefix in prefixes_to_remove:
428
+ if generated.startswith(prefix):
429
+ generated = generated[len(prefix):].strip()
430
+
431
+ # 移除markdown格式符号(但保留内容)
432
+ generated = generated.replace("**", "").replace("*", "").strip()
433
+
434
+ return generated
435
+
436
+ def _generate_template(self, reference_texts: List[str], product_name: str, style_name: str) -> str:
437
+ """生成文案模板(简化版,实际应调用LLM)"""
438
+ # 如果有参考文案,提取关键句式
439
+ key_phrases = []
440
+ if reference_texts:
441
+ for text in reference_texts[:2]: # 只取前2个参考
442
+ # 提取关键句式(简单提取)
443
+ if "避难所" in text:
444
+ key_phrases.append("避难所")
445
+ if "安静" in text:
446
+ key_phrases.append("安静")
447
+ if "唯一" in text:
448
+ key_phrases.append("唯一")
449
+ if "绝绝子" in text:
450
+ key_phrases.append("绝绝子")
451
+
452
+ # 根据风格和产品生成
453
+ if "清冷避世风" in style_name or "深夜emo" in style_name.lower():
454
+ if "眼罩" in product_name:
455
+ if key_phrases:
456
+ # GraphRAG:使用参考文案的句式
457
+ return f"戴上眼罩的这片刻漆黑,是我在繁杂城市里唯一的{'避难所' if '避难所' in key_phrases else '避风港'}。物理意义上的关灯,也是心理上的断联。世界终于{'安静了' if '安静' in key_phrases else '静下来了'},今晚只属于我自己。"
458
+ else:
459
+ # 传统RAG:没有参考,使用通用模板
460
+ return f"这个{product_name}真的很不错,遮光效果好,推荐给大家使用。"
461
+ elif "CCD" in product_name or "相机" in product_name:
462
+ return "深夜拿起它,在颗粒感的画面里,所有的情绪都有了出口。低像素不是缺陷,是另一种真实。"
463
+ else:
464
+ if key_phrases:
465
+ return f"每一个与{product_name}的瞬间,都是我与世界的{'唯一连接' if '唯一' in key_phrases else '连接'}。"
466
+ else:
467
+ return f"这个{product_name}真的很不错,推荐给大家。"
468
+ elif "疯狂种草" in style_name:
469
+ if key_phrases and "绝绝子" in key_phrases:
470
+ # GraphRAG:使用参考文案的语气
471
+ return f"家人们谁懂啊!这个{product_name}真的绝绝子,一秒沦陷!必须人手一个!"
472
+ else:
473
+ # 传统RAG:没有参考,使用通用语气
474
+ return f"这个{product_name}真的很不错,推荐给大家购买!"
475
+ else:
476
+ if key_phrases:
477
+ return f"这个{product_name}真的很不错,{'强烈推荐' if '绝绝子' in key_phrases else '推荐'}给大家!"
478
+ else:
479
+ return f"这个{product_name}真的很不错,推荐给大家!"
480
+