tt-help-cli-ycl 1.3.93 → 1.3.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -79,9 +79,66 @@ export function parseTagsFromResponse(content) {
79
79
  return tags;
80
80
  }
81
81
 
82
+ /**
83
+ * 解析 LLM 的两阶段响应(strategy + tags)
84
+ * 期望格式: {"strategy": "...", "tags": [...]}
85
+ * 兼容旧格式: 纯数组或纯文本
86
+ */
87
+ export function parseDiscoverResponse(content) {
88
+ let strategy = null;
89
+ let tags = [];
90
+
91
+ try {
92
+ const parsed = JSON.parse(content);
93
+
94
+ // 新格式: { strategy, tags }
95
+ if (parsed.strategy && Array.isArray(parsed.tags)) {
96
+ strategy = parsed.strategy;
97
+ tags = parsed.tags.map(normalizeTag).filter((t) => t && t.length >= 2);
98
+ return { strategy, tags };
99
+ }
100
+
101
+ // 旧格式: 纯数组
102
+ if (Array.isArray(parsed)) {
103
+ tags = parsed.map(normalizeTag).filter((t) => t && t.length >= 2);
104
+ return { strategy, tags };
105
+ }
106
+
107
+ // 旧格式: { tags: [...] }
108
+ if (Array.isArray(parsed.tags)) {
109
+ tags = parsed.tags.map(normalizeTag).filter((t) => t && t.length >= 2);
110
+ return { strategy, tags };
111
+ }
112
+ } catch {
113
+ // JSON 解析失败,尝试从文本中提取
114
+ // 先尝试提取 strategy(可能在 JSON 块之前)
115
+ const strategyMatch = content.match(/"strategy"\s*:\s*"([^"]+)"/);
116
+ if (strategyMatch) {
117
+ strategy = strategyMatch[1];
118
+ }
119
+ }
120
+
121
+ // 降级:从文本行中提取 tags
122
+ const lines = content.split(/[\n,]+/);
123
+ for (const line of lines) {
124
+ const cleaned = normalizeTag(line.replace(/^[-\d.\s]+/, ""));
125
+ if (cleaned && /^[a-z0-9_]+$/.test(cleaned) && cleaned.length >= 2) {
126
+ tags.push(cleaned);
127
+ }
128
+ }
129
+
130
+ return { strategy, tags };
131
+ }
132
+
82
133
  // ====== Prompt 组装 ======
83
134
 
84
- export function buildDiscoverPrompt(country, count, history, userPrompt) {
135
+ export function buildDiscoverPrompt(
136
+ country,
137
+ count,
138
+ history,
139
+ userPrompt,
140
+ pastStrategies,
141
+ ) {
85
142
  const lang = getLang(country);
86
143
  const langNames = {
87
144
  cs: "Czech",
@@ -98,11 +155,15 @@ export function buildDiscoverPrompt(country, count, history, userPrompt) {
98
155
  };
99
156
  const langName = langNames[lang] || lang;
100
157
 
101
- // 正样本:该国高分 tag(只给 LLM 看效果,不给模板)
158
+ // 正样本:该国高分 tag(带分数,让 LLM 知道哪些方向最有效)
102
159
  const productive = history.productive || [];
103
160
  const productiveHint =
104
161
  productive.length > 0
105
- ? `\nTags that already worked well for ${country}: ${productive.map((t) => t.tag).join(", ")}. These are examples of what works — explore DIFFERENT directions, not variations of these.`
162
+ ? `\nTags that already worked well for ${country} (with scores): ${productive
163
+ .map((t) => `${t.tag}(score:${Math.round(t.score)})`)
164
+ .join(
165
+ ", ",
166
+ )}. These are examples of what works — explore DIFFERENT directions, not variations of these.`
106
167
  : "";
107
168
 
108
169
  // 负样本:该国 dead tag
@@ -132,6 +193,23 @@ export function buildDiscoverPrompt(country, count, history, userPrompt) {
132
193
  ? `\nAdditional focus: ${userPrompt}. Generate tags specifically for this niche.`
133
194
  : "";
134
195
 
196
+ // 历史策略反馈:让 LLM 知道之前的策略效果如何
197
+ let strategyReview = "";
198
+ if (pastStrategies && pastStrategies.length > 0) {
199
+ const strategyLines = pastStrategies
200
+ .map(
201
+ (s) =>
202
+ `Round ${s.round}: strategy="${s.strategy?.substring(0, 200)}", added ${s.tags_added} tags, avg_score=${(s.avg_score || 0).toFixed(1)}, productive=${s.productive_count || 0}, dead=${s.dead_count || 0}`,
203
+ )
204
+ .join("\n");
205
+ strategyReview = `
206
+ === Previous discovery rounds (learn from these results) ===
207
+ ${strategyLines}
208
+
209
+ Based on the above, which strategies produced high-scoring tags? Which failed?
210
+ Use this analysis to decide your strategy for this round.`;
211
+ }
212
+
135
213
  return `You are discovering TikTok hashtags used by people who sell things in ${country}.
136
214
 
137
215
  Your goal: Find hashtags that real sellers in ${country} actually use — any kind of tag they might use. Think broadly:
@@ -139,16 +217,32 @@ Your goal: Find hashtags that real sellers in ${country} actually use — any ki
139
217
  - What they sell (shoes, clothes, jewelry, food, pets, furniture...)
140
218
  - How they sell (online, handmade, second-hand, local pickup...)
141
219
  - Product-specific tags (sneakers, dresses, cakes, necklaces...)
220
+ - Niche categories: beauty, fitness, pets, plants, books, toys, music, art, photography, gardening, DIY, automotive, real estate...
142
221
 
143
222
  All tags must be in ${langName} language (or widely used in ${country}).
144
- Generate ${count} tags that are ALL DIFFERENT from each other and from any existing tags.
223
+ Generate ${count} tags that are ALL DIFFERENT from each other and from any existing tags.${productiveHint}${deadHint}${errorHint}${existingHint}${userHint}${strategyReview}
224
+
225
+ ## IMPORTANT: Think first, then generate
226
+
227
+ Before generating tags, analyze:
228
+ 1. Which tag directions scored highest? What makes them work?
229
+ 2. Which directions completely failed? Why?
230
+ 3. What seller niches are NOT yet covered? (e.g., if we have "shop" but no "bakery", "petstore", "bookshop"...)
231
+ 4. What specific direction will YOU explore this round? Be concrete.
232
+
233
+ ## Output format
234
+
235
+ Return ONLY a JSON object with two fields:
236
+ {
237
+ "strategy": "Your analysis and plan for this round (2-4 sentences). Explain what direction you're exploring and why.",
238
+ "tags": ["tag1", "tag2", "tag3", "tag4"]
239
+ }
145
240
 
146
241
  Rules:
147
242
  - Each tag should explore a DIFFERENT angle — don't just swap country suffixes
148
243
  - Prefer specific and niche tags over generic ones (e.g., "vendozapatos" beats "vender")
149
- - Do NOT generate tags that already exist${productiveHint}${deadHint}${errorHint}${existingHint}${userHint}
150
-
151
- Return ONLY a JSON array of tag strings, nothing else. Example: ["ventas","tiendaonline","vender"]`;
244
+ - Do NOT generate tags that already exist
245
+ - Your strategy should be different from previous rounds — if a direction worked, go deeper; if it failed, try something new`;
152
246
  }
153
247
 
154
248
  // ====== discover: 单国家标签发现 ======
@@ -171,15 +265,32 @@ export async function discoverTagsForCountry(
171
265
  const allExisting = allTags.map((t) => t.tag);
172
266
  const history = { productive, dead, allExisting };
173
267
 
174
- // 组装 prompt 并调用 LLM
175
- const prompt = buildDiscoverPrompt(country, count, history, userPrompt);
268
+ // 读取历史策略效果(自学习)
269
+ const { getDiscoverRound, getDiscoverEffectiveness } =
270
+ await import("./db-tags.js");
271
+ const round = getDiscoverRound(country);
272
+ const pastStrategies = getDiscoverEffectiveness(country);
273
+
274
+ // 组装 prompt 并调用 LLM(两阶段:先思考策略,再生成 tag)
275
+ const prompt = buildDiscoverPrompt(
276
+ country,
277
+ count,
278
+ history,
279
+ userPrompt,
280
+ pastStrategies,
281
+ );
176
282
  console.error(
177
- `[tag-service] LLM discover ${country} (lang=${getLang(country)}, count=${count})`,
283
+ `[tag-service] LLM discover ${country} round=${round} (lang=${getLang(country)}, count=${count})`,
178
284
  );
179
285
  const content = await callLLM(prompt);
180
- const tags = parseTagsFromResponse(content);
286
+
287
+ // 解析响应:提取 strategy 和 tags
288
+ const { strategy, tags } = parseDiscoverResponse(content);
181
289
  const unique = [...new Set(tags)].slice(0, count);
182
290
 
291
+ console.error(
292
+ `[tag-service] LLM strategy: ${strategy?.substring(0, 150) || "(none)"}`,
293
+ );
183
294
  console.error(
184
295
  `[tag-service] LLM generated ${unique.length} tags: ${unique.join(", ")}`,
185
296
  );
@@ -191,8 +302,28 @@ export async function discoverTagsForCountry(
191
302
  if (result.inserted) inserted.push(result.tag);
192
303
  }
193
304
 
305
+ // 记录 discover log(策略 + 结果)
306
+ const { insertDiscoverLog } = await import("./db-tags.js");
307
+ insertDiscoverLog({
308
+ country,
309
+ round,
310
+ strategy,
311
+ tagsGenerated: unique,
312
+ tagsAdded: inserted.length,
313
+ productiveSample: productive.slice(0, 20).map((t) => t.tag),
314
+ deadSample: dead.slice(0, 20).map((t) => t.tag),
315
+ avgProductiveScore:
316
+ productive.length > 0
317
+ ? productive.reduce((s, t) => s + t.score, 0) / productive.length
318
+ : 0,
319
+ avgDeadScore:
320
+ dead.length > 0 ? dead.reduce((s, t) => s + t.score, 0) / dead.length : 0,
321
+ });
322
+
194
323
  return {
195
324
  country,
325
+ round,
326
+ strategy,
196
327
  added: inserted.length,
197
328
  tags: inserted,
198
329
  total: unique.length,