tt-help-cli-ycl 1.4.21 → 1.4.22

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.
@@ -23,8 +23,10 @@ export const COUNTRY_LANG = {
23
23
  AT: "de",
24
24
  };
25
25
 
26
- export const LLM_URL = "http://82.156.52.214:18000/v1/chat/completions";
27
- export const LLM_MODEL = "zc-fast";
26
+ // 旧常量保留兼容(外部可能引用),实际调用走 llm-config 动态配置
27
+ export const LLM_URL =
28
+ process.env.LLM_URL || "http://82.156.52.214:18000/v1/chat/completions";
29
+ export const LLM_MODEL = process.env.LLM_MODEL || "zc-fast";
28
30
 
29
31
  export function getLang(country) {
30
32
  return COUNTRY_LANG[country] || "en";
@@ -33,25 +35,74 @@ export function getLang(country) {
33
35
  // ====== LLM 调用 ======
34
36
 
35
37
  export async function callLLM(prompt, temperature = 0.7) {
36
- const apiKey = process.env.APIKEY || "";
37
- const { fetch } = await import("undici");
38
-
39
- const response = await fetch(LLM_URL, {
40
- method: "POST",
41
- headers: {
42
- "Content-Type": "application/json",
43
- Authorization: `Bearer ${apiKey}`,
44
- },
45
- body: JSON.stringify({
46
- model: LLM_MODEL,
47
- messages: [{ role: "user", content: prompt }],
48
- max_tokens: 1024,
49
- temperature,
50
- }),
51
- });
38
+ const { getLlmUrl, getLlmApiKey, isAutoMode, getAutoPool } =
39
+ await import("./llm-config.js");
40
+ const url = getLlmUrl();
41
+ const apiKey = getLlmApiKey();
42
+
43
+ // auto 模式:依次尝试候选池中的模型
44
+ const models = isAutoMode() ? getAutoPool() : [null];
45
+
46
+ let lastError = "";
47
+ for (let i = 0; i < models.length; i++) {
48
+ const model =
49
+ models[i] !== null
50
+ ? models[i]
51
+ : (await import("./llm-config.js")).getLlmModel();
52
+ const { fetch } = await import("undici");
53
+
54
+ console.error(
55
+ `[callLLM] → ${url} model=${model} (${i + 1}/${models.length})`,
56
+ );
52
57
 
53
- const result = await response.json();
54
- return result.choices?.[0]?.message?.content || "";
58
+ let response;
59
+ try {
60
+ response = await fetch(url, {
61
+ method: "POST",
62
+ headers: {
63
+ "Content-Type": "application/json",
64
+ Authorization: `Bearer ${apiKey}`,
65
+ },
66
+ body: JSON.stringify({
67
+ model,
68
+ messages: [{ role: "user", content: prompt }],
69
+ max_tokens: 1024,
70
+ temperature,
71
+ enable_thinking: false,
72
+ }),
73
+ });
74
+ } catch (e) {
75
+ lastError = e.message;
76
+ console.error(`[callLLM] ${model} 网络失败: ${e.message},尝试下一个`);
77
+ continue;
78
+ }
79
+
80
+ const status = response.status;
81
+ const result = await response.json().catch(() => null);
82
+
83
+ if (status !== 200 || !result || !result.choices?.[0]?.message?.content) {
84
+ const errMsg = result?.error?.message || `HTTP ${status}`;
85
+ lastError = errMsg;
86
+ console.error(`[callLLM] ${model} 失败: ${errMsg},尝试下一个`);
87
+ continue;
88
+ }
89
+
90
+ const content = result.choices[0].message.content;
91
+ if (i > 0) {
92
+ console.error(`[callLLM] ✅ auto 切换成功: ${model}`);
93
+ }
94
+ // 记录成功的模型,下次优先用它
95
+ if (isAutoMode()) {
96
+ const { recordAutoSuccess } = await import("./llm-config.js");
97
+ recordAutoSuccess(model);
98
+ }
99
+ return content;
100
+ }
101
+
102
+ console.error(
103
+ `[callLLM] ❌ 全部 ${models.length} 个模型均失败,最后错误: ${lastError}`,
104
+ );
105
+ return "";
55
106
  }
56
107
 
57
108
  export function normalizeTag(t) {
@@ -750,21 +801,31 @@ export async function discoverTagsForCountry(
750
801
  };
751
802
 
752
803
  let forceNicheSwitch = null;
753
- if (health && health.consecutiveLowProductiveRounds >= 2) {
754
- // 连续 ≥2 轮 productive=0,强制切换方向
755
- // 找出当前 productive tag 覆盖最多的 niche(即当前方向)
804
+ if (health && health.consecutiveLowProductiveRounds >= 3) {
805
+ // 连续 >=3 轮 productive=0,强制切换方向
806
+ // from = LLM 当前正在攻击的 niche(从 discover_log 追踪)
756
807
  const currentNiche =
757
- nicheRecommendation?.saturated?.[0]?.niche || "marketplace_local";
758
- // 找出覆盖最少的 niche 作为新方向
759
- const targetNiche = nicheRecommendation?.uncovered?.[0]?.niche || "food";
808
+ health.currentNiche ||
809
+ nicheRecommendation?.saturated?.[0]?.niche ||
810
+ "marketplace_local";
811
+
812
+ // to = uncovered 中排除 currentNiche 后的第一个
813
+ const uncoveredList = nicheRecommendation?.uncovered || [];
814
+ const nextUncovered = uncoveredList.find((n) => n.niche !== currentNiche);
815
+ // 如果所有 uncovered 都被排除(极端情况),回退到 uncovered 最后一个
816
+ const targetNiche =
817
+ nextUncovered?.niche ||
818
+ uncoveredList[uncoveredList.length - 1]?.niche ||
819
+ "food";
820
+
760
821
  forceNicheSwitch = {
761
822
  from: currentNiche,
762
823
  to: targetNiche,
763
- reason: `连续 ${health.consecutiveLowProductiveRounds} 轮无有效产出`,
824
+ reason: `连续 ${health.consecutiveLowProductiveRounds} 轮无有效产出,当前位置: ${currentNiche}`,
764
825
  description: NICHE_CATEGORIES[targetNiche] || targetNiche,
765
826
  };
766
827
  console.error(
767
- `[tag-service] 🔄 ${country} 强制方向切换: ${currentNiche} → ${targetNiche} (原因: 连续${health.consecutiveLowProductiveRounds}轮低效)`,
828
+ `[tag-service] 🔄 ${country} 强制方向切换: ${currentNiche} -> ${targetNiche} (原因: 连续${health.consecutiveLowProductiveRounds}轮低效, 当前niche=${health.currentNiche || "?"})`,
768
829
  );
769
830
  }
770
831
 
@@ -778,16 +839,17 @@ export async function discoverTagsForCountry(
778
839
  }
779
840
 
780
841
  // ====== 自闭环 Step 3: 动态温度调整 ======
842
+ // 策略:默认 0.7,卡死时升温打破 LLM 确定性重复
781
843
  let temperature = 0.7; // 默认
782
844
  if (health) {
783
- if (health.healthStatus === "critical") {
784
- temperature = 0.3; // 极度保守
845
+ if (health.consecutiveLowProductiveRounds >= 3) {
846
+ temperature = 1.0; // 最大随机性,强制探索新空间
847
+ } else if (health.consecutiveLowProductiveRounds >= 2) {
848
+ temperature = 0.9; // 较高随机性,打破模式
849
+ } else if (health.healthStatus === "critical") {
850
+ temperature = 0.5; // 有问题但还没卡死,略保守
785
851
  } else if (health.healthStatus === "warning") {
786
- temperature = 0.5; // 较保守
787
- }
788
- // 连续低效轮时进一步降温,让 LLM 更保守
789
- if (health.consecutiveLowProductiveRounds >= 2) {
790
- temperature = Math.min(temperature, 0.3);
852
+ temperature = 0.6;
791
853
  }
792
854
  }
793
855
 
@@ -903,6 +965,7 @@ export async function discoverTagsForCountry(
903
965
  country,
904
966
  round,
905
967
  strategy,
968
+ niche: niche || forceNicheSwitch?.to || null,
906
969
  tagsRaw: deduped,
907
970
  tagsGenerated: unique,
908
971
  tagsAdded: inserted.length,
@@ -1343,12 +1406,7 @@ export async function reflectAndLearn(store, country) {
1343
1406
  console.error(` [${rule.ruleType}] ${rule.ruleText}`);
1344
1407
  }
1345
1408
 
1346
- // Step 6: 废弃旧规则,写入新规则
1347
- const supersedeResult = supersedeRules(country, "new reflection round");
1348
- console.error(
1349
- `[reflection] ${country} 废弃 ${supersedeResult.superseded} 条旧规则`,
1350
- );
1351
-
1409
+ // Step 6: 废弃旧规则(带相似度去重),写入新规则
1352
1410
  const rulesToInsert = reflection.rules.map((r) => ({
1353
1411
  country,
1354
1412
  ruleType: r.ruleType,
@@ -1359,6 +1417,26 @@ export async function reflectAndLearn(store, country) {
1359
1417
  analysisSummary: reflection.analysisSummary,
1360
1418
  }));
1361
1419
 
1420
+ const supersedeResult = supersedeRules(
1421
+ country,
1422
+ rulesToInsert,
1423
+ "new reflection round",
1424
+ );
1425
+ if (supersedeResult.skipped) {
1426
+ console.error(
1427
+ `[reflection] ${country} 跳过反思(规则与上次高度相似),不写入新规则`,
1428
+ );
1429
+ return {
1430
+ reflected: true,
1431
+ rulesAdded: 0,
1432
+ analysis: reflection.analysisSummary,
1433
+ skipped: "rules too similar to previous reflection",
1434
+ };
1435
+ }
1436
+ console.error(
1437
+ `[reflection] ${country} 废弃 ${supersedeResult.superseded} 条旧规则`,
1438
+ );
1439
+
1362
1440
  const insertResult = insertReflectionRules(rulesToInsert);
1363
1441
  console.error(
1364
1442
  `[reflection] ${country} 写入 ${insertResult.inserted} 条新规则`,