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.
- package/package.json +1 -1
- package/src/cli/comments.js +63 -1
- package/src/lib/parse-ssr.mjs +6 -0
- package/src/lib/tag-discover.js +8 -4
- package/src/lib/tag-fetcher.js +5 -0
- package/src/watch/data-store.js +2 -0
- package/src/watch/db-tags.js +237 -17
- package/src/watch/llm-config.js +269 -0
- package/src/watch/llm-scoring.js +103 -63
- package/src/watch/public/app.js +196 -8
- package/src/watch/public/index.html +39 -0
- package/src/watch/public/style.css +154 -0
- package/src/watch/server.js +104 -6
- package/src/watch/tag-service.js +119 -41
package/src/watch/tag-service.js
CHANGED
|
@@ -23,8 +23,10 @@ export const COUNTRY_LANG = {
|
|
|
23
23
|
AT: "de",
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
export const
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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 >=
|
|
754
|
-
// 连续
|
|
755
|
-
//
|
|
804
|
+
if (health && health.consecutiveLowProductiveRounds >= 3) {
|
|
805
|
+
// 连续 >=3 轮 productive=0,强制切换方向
|
|
806
|
+
// from = LLM 当前正在攻击的 niche(从 discover_log 追踪)
|
|
756
807
|
const currentNiche =
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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}
|
|
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.
|
|
784
|
-
temperature = 0
|
|
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.
|
|
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} 条新规则`,
|