@akemi-mio/creativity 0.0.0-stage → 0.1.1
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/dist/ConceptMixer.d.ts +41 -0
- package/dist/ConceptMixer.js +207 -0
- package/dist/CreativityPrompt.d.ts +24 -0
- package/dist/CreativityPrompt.js +206 -0
- package/dist/ExperimentPlanner.d.ts +14 -0
- package/dist/ExperimentPlanner.js +60 -0
- package/dist/ExperimentRunStore.d.ts +97 -0
- package/dist/ExperimentRunStore.js +160 -0
- package/dist/HypothesisGenerator.d.ts +60 -0
- package/dist/HypothesisGenerator.js +246 -0
- package/dist/IdeaGenerator.d.ts +51 -0
- package/dist/IdeaGenerator.js +145 -0
- package/dist/InsightSamplingExperiment.d.ts +82 -0
- package/dist/InsightSamplingExperiment.js +308 -0
- package/dist/LocalModelService.d.ts +38 -0
- package/dist/LocalModelService.js +162 -0
- package/dist/MechanismGraph.d.ts +46 -0
- package/dist/MechanismGraph.js +76 -0
- package/dist/MechanismRegistry.d.ts +118 -0
- package/dist/MechanismRegistry.js +233 -0
- package/dist/NoveltyScorer.d.ts +40 -0
- package/dist/NoveltyScorer.js +93 -0
- package/dist/SourceAggregator.d.ts +21 -0
- package/dist/SourceAggregator.js +59 -0
- package/dist/TemplateLibrary.d.ts +29 -0
- package/dist/TemplateLibrary.js +484 -0
- package/dist/WhenThenRunner.d.ts +135 -0
- package/dist/WhenThenRunner.js +358 -0
- package/dist/types.d.ts +213 -0
- package/dist/types.js +9 -0
- package/package.json +67 -4
- package/README.md +0 -3
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ExperimentRunStore — 实验运行存储(机制涌现 P2b,定案 D4/D5/A1/A3/A4)
|
|
4
|
+
*
|
|
5
|
+
* ExperimentRun 实体:candidateId/baselineId/assignment/outcome/metrics/evidenceIds/stale。
|
|
6
|
+
* 三模式:baseline/candidate/ablation;消融缺任一侧=inconclusive。
|
|
7
|
+
* 停止规则:minSamples 硬门槛(默认 30),n<minSamples 只能产出 inconclusive、禁进 PromotionRequest。
|
|
8
|
+
* stale 标记:baseSnapshotId 与当前 runtime 不匹配时标 stale,stale 禁晋级。
|
|
9
|
+
* Memory 机制性隔离(A1):MIO EXPERIMENT 标志路由 experiment-memory.jsonl。
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.DEFAULT_STOPPING_RULE = void 0;
|
|
13
|
+
exports.parseStoppingRule = parseStoppingRule;
|
|
14
|
+
exports.assignExperimentMode = assignExperimentMode;
|
|
15
|
+
exports.evaluateStoppingRule = evaluateStoppingRule;
|
|
16
|
+
exports.checkPromotionGate = checkPromotionGate;
|
|
17
|
+
exports.routeExperimentMemoryPath = routeExperimentMemoryPath;
|
|
18
|
+
exports.isExperimentMode = isExperimentMode;
|
|
19
|
+
/** 默认停止规则:minSamples=30(硬门槛) */
|
|
20
|
+
exports.DEFAULT_STOPPING_RULE = { minSamples: 30 };
|
|
21
|
+
/**
|
|
22
|
+
* 解析并校验停止规则 JSON 配置。
|
|
23
|
+
* 失败返回 null(fail-closed);未知字段忽略。
|
|
24
|
+
*/
|
|
25
|
+
function parseStoppingRule(raw) {
|
|
26
|
+
if (raw === null || typeof raw !== 'object')
|
|
27
|
+
return null;
|
|
28
|
+
const obj = raw;
|
|
29
|
+
const minSamples = obj.minSamples;
|
|
30
|
+
if (typeof minSamples !== 'number' || !Number.isFinite(minSamples) || minSamples < 1)
|
|
31
|
+
return null;
|
|
32
|
+
const config = { minSamples: Math.floor(minSamples) };
|
|
33
|
+
if (typeof obj.maxSamples === 'number' && Number.isFinite(obj.maxSamples) && obj.maxSamples >= config.minSamples) {
|
|
34
|
+
config.maxSamples = Math.floor(obj.maxSamples);
|
|
35
|
+
}
|
|
36
|
+
if (typeof obj.minSuccessRate === 'number' && Number.isFinite(obj.minSuccessRate) && obj.minSuccessRate >= 0 && obj.minSuccessRate <= 1) {
|
|
37
|
+
config.minSuccessRate = obj.minSuccessRate;
|
|
38
|
+
}
|
|
39
|
+
if (typeof obj.maxFailureRate === 'number' && Number.isFinite(obj.maxFailureRate) && obj.maxFailureRate >= 0 && obj.maxFailureRate <= 1) {
|
|
40
|
+
config.maxFailureRate = obj.maxFailureRate;
|
|
41
|
+
}
|
|
42
|
+
if (typeof obj.maxDurationMs === 'number' && Number.isFinite(obj.maxDurationMs) && obj.maxDurationMs > 0) {
|
|
43
|
+
config.maxDurationMs = Math.floor(obj.maxDurationMs);
|
|
44
|
+
}
|
|
45
|
+
return config;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 根据可用侧分配三模式。
|
|
49
|
+
* - 有 candidate + baseline → candidate(对照)
|
|
50
|
+
* - 只有 baseline → baseline
|
|
51
|
+
* - 消融:需要 candidate + baseline + ablation 标识;缺任一侧 → inconclusive
|
|
52
|
+
*/
|
|
53
|
+
function assignExperimentMode(input) {
|
|
54
|
+
const { candidateId, baselineId, ablationId } = input;
|
|
55
|
+
if (ablationId) {
|
|
56
|
+
// 消融需要双侧对照
|
|
57
|
+
if (!candidateId || !baselineId) {
|
|
58
|
+
return { assignment: 'ablation', reason: 'inconclusive_missing_side' };
|
|
59
|
+
}
|
|
60
|
+
return { assignment: 'ablation' };
|
|
61
|
+
}
|
|
62
|
+
if (candidateId && baselineId)
|
|
63
|
+
return { assignment: 'candidate' };
|
|
64
|
+
if (baselineId)
|
|
65
|
+
return { assignment: 'baseline' };
|
|
66
|
+
return { assignment: 'baseline', reason: 'no_baseline' };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 基于样本评估停止规则。
|
|
70
|
+
*
|
|
71
|
+
* - n < minSamples → 只能产出 inconclusive(A3 硬门槛)
|
|
72
|
+
* - n >= minSamples 且有 maxFailureRate/minSuccessRate → 可提前停止
|
|
73
|
+
* - maxSamples / maxDurationMs 达到 → 停止
|
|
74
|
+
*/
|
|
75
|
+
function evaluateStoppingRule(config, runs, now = Date.now()) {
|
|
76
|
+
const n = runs.length;
|
|
77
|
+
const successCount = runs.filter((r) => r.outcome === 'success').length;
|
|
78
|
+
const failureCount = runs.filter((r) => r.outcome === 'failure').length;
|
|
79
|
+
const inconclusiveCount = runs.filter((r) => r.outcome === 'inconclusive').length;
|
|
80
|
+
const successRate = n > 0 ? successCount / n : 0;
|
|
81
|
+
const failureRate = n > 0 ? failureCount / n : 0;
|
|
82
|
+
// 硬门槛:n < minSamples → inconclusive(A3)
|
|
83
|
+
if (n < config.minSamples) {
|
|
84
|
+
return {
|
|
85
|
+
shouldStop: false,
|
|
86
|
+
recommended: null,
|
|
87
|
+
reason: 'below_min_samples',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// maxSamples
|
|
91
|
+
if (config.maxSamples !== undefined && n >= config.maxSamples) {
|
|
92
|
+
return { shouldStop: true, recommended: majorityOutcome(successCount, failureCount, inconclusiveCount), reason: 'max_samples' };
|
|
93
|
+
}
|
|
94
|
+
// maxFailureRate 超过 → 停止 + reject
|
|
95
|
+
if (config.maxFailureRate !== undefined && failureRate > config.maxFailureRate) {
|
|
96
|
+
return { shouldStop: true, recommended: 'failure', reason: 'failure_rate_exceeded' };
|
|
97
|
+
}
|
|
98
|
+
// minSuccessRate 达到 → 停止 + success
|
|
99
|
+
if (config.minSuccessRate !== undefined && successRate >= config.minSuccessRate) {
|
|
100
|
+
return { shouldStop: true, recommended: 'success', reason: 'success_rate_met' };
|
|
101
|
+
}
|
|
102
|
+
// maxDurationMs
|
|
103
|
+
if (config.maxDurationMs !== undefined) {
|
|
104
|
+
const oldest = Math.min(...runs.map((r) => r.createdAt));
|
|
105
|
+
if (now - oldest >= config.maxDurationMs) {
|
|
106
|
+
return { shouldStop: true, recommended: majorityOutcome(successCount, failureCount, inconclusiveCount), reason: 'max_duration' };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { shouldStop: false, recommended: null, reason: 'continue' };
|
|
110
|
+
}
|
|
111
|
+
function majorityOutcome(s, f, i) {
|
|
112
|
+
if (s > f && s > i)
|
|
113
|
+
return 'success';
|
|
114
|
+
if (f > s && f > i)
|
|
115
|
+
return 'failure';
|
|
116
|
+
return 'inconclusive';
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 检查是否允许进入 PromotionRequest。
|
|
120
|
+
* A3:n < minSamples 禁晋级。
|
|
121
|
+
* A4:stale 禁晋级。
|
|
122
|
+
*/
|
|
123
|
+
function checkPromotionGate(config, runs, currentSnapshotId) {
|
|
124
|
+
// A4: stale 检查
|
|
125
|
+
const anyStale = runs.some((r) => r.stale === true);
|
|
126
|
+
if (anyStale) {
|
|
127
|
+
return { allowed: false, reason: 'stale_snapshot' };
|
|
128
|
+
}
|
|
129
|
+
// A4: baseSnapshotId 与当前 runtime 不匹配
|
|
130
|
+
if (currentSnapshotId) {
|
|
131
|
+
const mismatched = runs.some((r) => r.baseSnapshotId !== undefined && r.baseSnapshotId !== currentSnapshotId);
|
|
132
|
+
if (mismatched) {
|
|
133
|
+
return { allowed: false, reason: 'snapshot_mismatch' };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// A3: minSamples 硬门槛
|
|
137
|
+
const n = runs.length;
|
|
138
|
+
if (n < config.minSamples) {
|
|
139
|
+
return { allowed: false, reason: 'below_min_samples' };
|
|
140
|
+
}
|
|
141
|
+
return { allowed: true, reason: 'ok' };
|
|
142
|
+
}
|
|
143
|
+
// ══════════════════════════════════════════
|
|
144
|
+
// Memory 机制性隔离(A1)
|
|
145
|
+
// ══════════════════════════════════════════
|
|
146
|
+
/**
|
|
147
|
+
* 实验 Memory 路由:MIO EXPERIMENT 标志 → experiment-memory.jsonl。
|
|
148
|
+
* 服务端 memory-record 检测此标志并强制路由。
|
|
149
|
+
*/
|
|
150
|
+
function routeExperimentMemoryPath(baseMemoryPath, isExperiment) {
|
|
151
|
+
if (!isExperiment)
|
|
152
|
+
return baseMemoryPath;
|
|
153
|
+
// 替换扩展名为 experiment-memory.jsonl
|
|
154
|
+
const dir = baseMemoryPath.includes('/') ? baseMemoryPath.slice(0, baseMemoryPath.lastIndexOf('/') + 1) : '';
|
|
155
|
+
return `${dir}experiment-memory.jsonl`;
|
|
156
|
+
}
|
|
157
|
+
/** MIO EXPERIMENT 环境标志检测 */
|
|
158
|
+
function isExperimentMode(env = process.env) {
|
|
159
|
+
return env.MIO_EXPERIMENT === '1' || env.MIO_EXPERIMENT === 'true';
|
|
160
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Hypothesis, ConceptCombo, CreativitySource, ExternalSignal } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* HypothesisGenerator — 将概念组合转化为可验证的假设
|
|
4
|
+
*
|
|
5
|
+
* 四级生成链(逐级降级):
|
|
6
|
+
* 远程 LLM → 本地模型 (transformers.js) → TemplateLibrary (30+ 模式) → 原始模板兜底
|
|
7
|
+
*/
|
|
8
|
+
export declare class HypothesisGenerator {
|
|
9
|
+
private idCounter;
|
|
10
|
+
private rng;
|
|
11
|
+
private chatJson;
|
|
12
|
+
private templateLib;
|
|
13
|
+
private localModel;
|
|
14
|
+
private rotationSeed;
|
|
15
|
+
private callCount;
|
|
16
|
+
constructor(chatJson: (userText: string, options?: {
|
|
17
|
+
system?: string;
|
|
18
|
+
temperature?: number;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
requestId?: string;
|
|
21
|
+
maxTokens?: number;
|
|
22
|
+
}) => Promise<{
|
|
23
|
+
data?: any;
|
|
24
|
+
error?: string;
|
|
25
|
+
}>, seed?: number, rotationSeed?: number);
|
|
26
|
+
/**
|
|
27
|
+
* 从概念组合生成假设 — 远程 LLM → 本地模型 → TemplateLibrary → 模板兜底
|
|
28
|
+
*
|
|
29
|
+
* 四级 fallback 链:
|
|
30
|
+
* 1. 远程 LLM (高质,但 30s 超时、网络依赖)
|
|
31
|
+
* 2. 本地 transformers.js (进程内,~1s,无网络依赖)
|
|
32
|
+
* 3. TemplateLibrary (30+ 模式,类型感知,零延迟)
|
|
33
|
+
* 4. 原始模板 (5 个硬编码,终极兜底)
|
|
34
|
+
*/
|
|
35
|
+
generate(combos: ConceptCombo[], sources: CreativitySource[],
|
|
36
|
+
/** 梦境模式使用更高温度 */
|
|
37
|
+
dreamMode?: boolean,
|
|
38
|
+
/** 外部信号 — 不参与配对,作为审视视角注入 LLM */
|
|
39
|
+
externalSignals?: ExternalSignal[],
|
|
40
|
+
/** 显式技法;缺省走五技法轮换(rotationSeed + 调用次数) */
|
|
41
|
+
method?: string): Promise<Hypothesis[]>;
|
|
42
|
+
/**
|
|
43
|
+
* 调 LLM 生成创意
|
|
44
|
+
*/
|
|
45
|
+
private tryLLM;
|
|
46
|
+
/**
|
|
47
|
+
* 调本地 transformers.js 模型生成创意
|
|
48
|
+
*/
|
|
49
|
+
private tryLocalModel;
|
|
50
|
+
private hasRelevanceJustification;
|
|
51
|
+
/** 记录本轮使用的轮换技法(idea.generate 的 provenance.technique 字段写它)。 */
|
|
52
|
+
private tag;
|
|
53
|
+
/** 根据 sourceLabels 构建来源深度详情(默认 shallow) */
|
|
54
|
+
private buildSourceDetails;
|
|
55
|
+
/**
|
|
56
|
+
* 模板 fallback — 从旧实现保留
|
|
57
|
+
*/
|
|
58
|
+
private templateFallback;
|
|
59
|
+
private stableHash;
|
|
60
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HypothesisGenerator = void 0;
|
|
4
|
+
const CreativityPrompt_1 = require("./CreativityPrompt");
|
|
5
|
+
const TemplateLibrary_1 = require("./TemplateLibrary");
|
|
6
|
+
const LocalModelService_1 = require("./LocalModelService");
|
|
7
|
+
const random_1 = require("@akemi-mio/core/utils/random");
|
|
8
|
+
const Logger_1 = require("@akemi-mio/core/logger/Logger");
|
|
9
|
+
/**
|
|
10
|
+
* HypothesisGenerator — 将概念组合转化为可验证的假设
|
|
11
|
+
*
|
|
12
|
+
* 四级生成链(逐级降级):
|
|
13
|
+
* 远程 LLM → 本地模型 (transformers.js) → TemplateLibrary (30+ 模式) → 原始模板兜底
|
|
14
|
+
*/
|
|
15
|
+
class HypothesisGenerator {
|
|
16
|
+
idCounter = 0;
|
|
17
|
+
rng;
|
|
18
|
+
chatJson;
|
|
19
|
+
templateLib;
|
|
20
|
+
localModel;
|
|
21
|
+
rotationSeed = 0;
|
|
22
|
+
callCount = 0;
|
|
23
|
+
constructor(chatJson, seed, rotationSeed = 0) {
|
|
24
|
+
this.chatJson = chatJson;
|
|
25
|
+
this.rng = (0, random_1.resolveRandom)(seed);
|
|
26
|
+
this.templateLib = new TemplateLibrary_1.TemplateLibrary(seed);
|
|
27
|
+
this.localModel = new LocalModelService_1.LocalModelService();
|
|
28
|
+
this.rotationSeed = rotationSeed;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 从概念组合生成假设 — 远程 LLM → 本地模型 → TemplateLibrary → 模板兜底
|
|
32
|
+
*
|
|
33
|
+
* 四级 fallback 链:
|
|
34
|
+
* 1. 远程 LLM (高质,但 30s 超时、网络依赖)
|
|
35
|
+
* 2. 本地 transformers.js (进程内,~1s,无网络依赖)
|
|
36
|
+
* 3. TemplateLibrary (30+ 模式,类型感知,零延迟)
|
|
37
|
+
* 4. 原始模板 (5 个硬编码,终极兜底)
|
|
38
|
+
*/
|
|
39
|
+
async generate(combos, sources,
|
|
40
|
+
/** 梦境模式使用更高温度 */
|
|
41
|
+
dreamMode = false,
|
|
42
|
+
/** 外部信号 — 不参与配对,作为审视视角注入 LLM */
|
|
43
|
+
externalSignals = [],
|
|
44
|
+
/** 显式技法;缺省走五技法轮换(rotationSeed + 调用次数) */
|
|
45
|
+
method) {
|
|
46
|
+
if (combos.length === 0)
|
|
47
|
+
return [];
|
|
48
|
+
const technique = method ?? (0, CreativityPrompt_1.pickTechnique)(this.rotationSeed + this.callCount);
|
|
49
|
+
this.callCount += 1;
|
|
50
|
+
// 第一级:远程 LLM 驱动生成
|
|
51
|
+
const llmResults = await this.tryLLM(sources, combos, dreamMode, externalSignals, technique);
|
|
52
|
+
if (llmResults.length > 0) {
|
|
53
|
+
return this.tag(llmResults.map((r) => ({
|
|
54
|
+
id: `hyp_${Date.now()}_${++this.idCounter}_${this.rng().toString(36).slice(2, 4)}`,
|
|
55
|
+
title: r.title,
|
|
56
|
+
idea: r.idea,
|
|
57
|
+
expectedBenefit: r.expectedBenefit,
|
|
58
|
+
risk: r.risk,
|
|
59
|
+
sourceLabels: r.sourceLabels,
|
|
60
|
+
sourceDetails: this.buildSourceDetails(r.sourceLabels, sources),
|
|
61
|
+
novelty: Math.max(10, Math.min(100, r.novelty)),
|
|
62
|
+
feasibility: Math.max(10, Math.min(100, r.feasibility)),
|
|
63
|
+
impact: Math.max(10, Math.min(100, r.impact)),
|
|
64
|
+
status: 'draft',
|
|
65
|
+
createdAt: Date.now(),
|
|
66
|
+
})), technique);
|
|
67
|
+
}
|
|
68
|
+
// 第二级:本地 transformers.js 模型
|
|
69
|
+
if (this.localModel.isEnabled) {
|
|
70
|
+
(0, Logger_1.log)('INFO', 'hypothesis_fallback_local', { count: combos.length });
|
|
71
|
+
const localResults = await this.tryLocalModel(sources, combos, dreamMode, technique);
|
|
72
|
+
if (localResults.length > 0) {
|
|
73
|
+
return this.tag(localResults.map((r) => ({
|
|
74
|
+
id: `hyp_local_${Date.now()}_${++this.idCounter}_${this.rng().toString(36).slice(2, 4)}`,
|
|
75
|
+
title: r.title,
|
|
76
|
+
idea: r.idea,
|
|
77
|
+
expectedBenefit: r.expectedBenefit,
|
|
78
|
+
risk: r.risk,
|
|
79
|
+
sourceLabels: r.sourceLabels,
|
|
80
|
+
sourceDetails: this.buildSourceDetails(r.sourceLabels, sources),
|
|
81
|
+
novelty: Math.max(10, Math.min(100, r.novelty)),
|
|
82
|
+
feasibility: Math.max(10, Math.min(100, r.feasibility)),
|
|
83
|
+
impact: Math.max(10, Math.min(100, r.impact)),
|
|
84
|
+
status: 'draft',
|
|
85
|
+
createdAt: Date.now(),
|
|
86
|
+
})), technique);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// 第二/三级失败 → 记录日志
|
|
90
|
+
(0, Logger_1.log)('INFO', 'hypothesis_fallback_templates', { count: combos.length });
|
|
91
|
+
// 第三级:TemplateLibrary (30+ 类型感知模板)
|
|
92
|
+
const tplResults = [];
|
|
93
|
+
for (const combo of combos) {
|
|
94
|
+
const h = this.templateLib.generate(combo, sources);
|
|
95
|
+
if (h)
|
|
96
|
+
tplResults.push(h);
|
|
97
|
+
}
|
|
98
|
+
if (tplResults.length > 0) {
|
|
99
|
+
return this.tag(tplResults, technique);
|
|
100
|
+
}
|
|
101
|
+
// 第四级:原始模板兜底
|
|
102
|
+
(0, Logger_1.log)('WARN', 'hypothesis_fallback_legacy', { count: combos.length });
|
|
103
|
+
return this.tag(combos.map((combo) => this.templateFallback(combo)), technique);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 调 LLM 生成创意
|
|
107
|
+
*/
|
|
108
|
+
async tryLLM(sources, combos, dreamMode, externalSignals = [], technique = '') {
|
|
109
|
+
// 限制输入量,避免超 token
|
|
110
|
+
const topCombos = combos.slice(0, dreamMode ? 5 : 3);
|
|
111
|
+
const comboInfo = topCombos.map((c) => ({
|
|
112
|
+
sources: c.sources,
|
|
113
|
+
description: c.description,
|
|
114
|
+
}));
|
|
115
|
+
const prompt = (0, CreativityPrompt_1.buildCreativityPrompt)(sources, comboInfo, externalSignals);
|
|
116
|
+
try {
|
|
117
|
+
const result = await this.chatJson(prompt, {
|
|
118
|
+
system: (0, CreativityPrompt_1.buildSystemPrompt)(technique),
|
|
119
|
+
temperature: dreamMode ? 1.0 : 0.8,
|
|
120
|
+
timeoutMs: 60000,
|
|
121
|
+
// 输出无界会顶满超时(M2):生成上限 1200 tokens
|
|
122
|
+
maxTokens: 1200,
|
|
123
|
+
});
|
|
124
|
+
if (result.error) {
|
|
125
|
+
(0, Logger_1.log)('WARN', 'hypothesis_llm_error', { error: result.error });
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const ideas = Array.isArray(result.data) ? result.data : [];
|
|
129
|
+
// 验证基本格式
|
|
130
|
+
return ideas.filter((i) => typeof i.title === 'string' &&
|
|
131
|
+
typeof i.idea === 'string' &&
|
|
132
|
+
Array.isArray(i.sourceLabels) &&
|
|
133
|
+
typeof i.novelty === 'number' &&
|
|
134
|
+
this.hasRelevanceJustification(i));
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
(0, Logger_1.log)('ERROR', 'hypothesis_llm_exception', { error: String(err) });
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 调本地 transformers.js 模型生成创意
|
|
143
|
+
*/
|
|
144
|
+
async tryLocalModel(sources, combos, dreamMode, technique = '') {
|
|
145
|
+
const topCombos = combos.slice(0, dreamMode ? 3 : 2);
|
|
146
|
+
const comboInfo = topCombos.map((c) => ({
|
|
147
|
+
sources: c.sources,
|
|
148
|
+
description: c.description,
|
|
149
|
+
}));
|
|
150
|
+
const prompt = (0, CreativityPrompt_1.buildCreativityPrompt)(sources, comboInfo);
|
|
151
|
+
const result = await this.localModel.generate(prompt, {
|
|
152
|
+
system: (0, CreativityPrompt_1.buildSystemPrompt)(technique),
|
|
153
|
+
temperature: dreamMode ? 0.8 : 0.6,
|
|
154
|
+
maxTokens: dreamMode ? 1024 : 768,
|
|
155
|
+
});
|
|
156
|
+
if (result.error) {
|
|
157
|
+
(0, Logger_1.log)('WARN', 'hypothesis_local_error', { error: result.error });
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
const ideas = Array.isArray(result.data) ? result.data : [];
|
|
161
|
+
return ideas.filter((i) => typeof i.title === 'string' &&
|
|
162
|
+
typeof i.idea === 'string' &&
|
|
163
|
+
Array.isArray(i.sourceLabels) &&
|
|
164
|
+
typeof i.novelty === 'number' &&
|
|
165
|
+
this.hasRelevanceJustification(i));
|
|
166
|
+
}
|
|
167
|
+
hasRelevanceJustification(i) {
|
|
168
|
+
const relevance = typeof i.relevance === 'string' ? i.relevance.trim() : '';
|
|
169
|
+
return relevance.length >= 10;
|
|
170
|
+
}
|
|
171
|
+
/** 记录本轮使用的轮换技法(idea.generate 的 provenance.technique 字段写它)。 */
|
|
172
|
+
tag(hypotheses, technique) {
|
|
173
|
+
return hypotheses.map((h) => ({ ...h, technique }));
|
|
174
|
+
}
|
|
175
|
+
/** 根据 sourceLabels 构建来源深度详情(默认 shallow) */
|
|
176
|
+
buildSourceDetails(labels, sources) {
|
|
177
|
+
const byName = new Map(sources.map((s) => [s.name, s.sourceDepth ?? 'shallow']));
|
|
178
|
+
return labels.map((name) => ({ name, depth: byName.get(name) ?? 'shallow' }));
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 模板 fallback — 从旧实现保留
|
|
182
|
+
*/
|
|
183
|
+
templateFallback(combo) {
|
|
184
|
+
const [a, b] = combo.sources;
|
|
185
|
+
const hash = this.stableHash(a, b);
|
|
186
|
+
const idx = hash % TEMPLATES.length;
|
|
187
|
+
const t = TEMPLATES[idx];
|
|
188
|
+
return {
|
|
189
|
+
id: `hyp_fb_${Date.now()}_${++this.idCounter}_${this.rng().toString(36).slice(2, 4)}`,
|
|
190
|
+
title: t.title(a, b),
|
|
191
|
+
idea: t.idea(a, b),
|
|
192
|
+
expectedBenefit: t.expectedBenefit,
|
|
193
|
+
risk: t.risk,
|
|
194
|
+
sourceLabels: [a, b],
|
|
195
|
+
sourceDetails: [a, b].map((name) => ({ name, depth: 'shallow' })),
|
|
196
|
+
novelty: 50 + Math.round(this.rng() * 30),
|
|
197
|
+
feasibility: 40 + Math.round(this.rng() * 30),
|
|
198
|
+
impact: 50 + Math.round(this.rng() * 25),
|
|
199
|
+
status: 'draft',
|
|
200
|
+
createdAt: Date.now(),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
stableHash(a, b) {
|
|
204
|
+
let h = 0;
|
|
205
|
+
const s = a + '|' + b;
|
|
206
|
+
for (let i = 0; i < s.length; i++) {
|
|
207
|
+
h = (h << 5) - h + s.charCodeAt(i);
|
|
208
|
+
h = h & h;
|
|
209
|
+
}
|
|
210
|
+
return Math.abs(h);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
exports.HypothesisGenerator = HypothesisGenerator;
|
|
214
|
+
const TEMPLATES = [
|
|
215
|
+
{
|
|
216
|
+
title: (a, b) => `${a} 驱动的 ${b}`,
|
|
217
|
+
idea: (a, b) => `将「${a}」的核心机制作为「${b}」的新输入维度`,
|
|
218
|
+
expectedBenefit: '解锁之前被忽视的新能力组合',
|
|
219
|
+
risk: '组合可能引入不必要的复杂度',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
title: (a, b) => `基于 ${a} 的 ${b} 增强`,
|
|
223
|
+
idea: (a, b) => `让「${a}」和「${b}」通过一个共享接口互相增强`,
|
|
224
|
+
expectedBenefit: '系统灵活性提升,涌现新的行为模式',
|
|
225
|
+
risk: '两个概念耦合后难以单独演进',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
title: (a, b) => `${a} × ${b} 混合系统`,
|
|
229
|
+
idea: (a, b) => `借鉴「${a}」的设计哲学重新思考「${b}」的实现`,
|
|
230
|
+
expectedBenefit: '减少重复逻辑,提升模块间信息复用',
|
|
231
|
+
risk: '可能存在隐含的语义冲突',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
title: (a, b) => `从 ${a} 到 ${b} 的抽象跳跃`,
|
|
235
|
+
idea: (a, b) => `在「${a}」的基础上构建「${b}」的新抽象层`,
|
|
236
|
+
expectedBenefit: '降低认知负载,统一概念模型',
|
|
237
|
+
risk: '抽象层过多影响 runtime 性能',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
title: (a, b) => `${a} 视角下的 ${b} 重构`,
|
|
241
|
+
idea: (a, b) => `将「${a}」的某一种能力移植到「${b}」的上下文中`,
|
|
242
|
+
expectedBenefit: '架构更优雅,扩展点增加',
|
|
243
|
+
risk: '预期收益不确定,需要实验验证',
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
// 保留旧文件中的 PATTERN_LIBRARY 作为参考,但不再使用
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CreativitySource, ConceptCombo, Hypothesis, CreativeIdea, Strategy, ExternalSignal } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* IdeaGenerator — 创造力引擎的核心编排器
|
|
4
|
+
*
|
|
5
|
+
* 流程:
|
|
6
|
+
* 1. Source Gathering — 从多个来源收集"概念"
|
|
7
|
+
* 2. Concept Mixing — 随机配对重组(规则引擎,保留)
|
|
8
|
+
* 3. Hypothesis Generation — 将组合转化为假设(LLM 驱动,new)
|
|
9
|
+
* 4. Experiment Planning — 为假设生成验证方案(规则引擎,保留)
|
|
10
|
+
*/
|
|
11
|
+
export declare class IdeaGenerator {
|
|
12
|
+
private mixer;
|
|
13
|
+
private hypothesisGen;
|
|
14
|
+
private experimentPlanner;
|
|
15
|
+
private rng;
|
|
16
|
+
private temperature;
|
|
17
|
+
/** 已探索过的配对 key 列表,传给 ConceptMixer 以降权 */
|
|
18
|
+
private exploredPairs;
|
|
19
|
+
constructor(chatJson: (userText: string, options?: {
|
|
20
|
+
system?: string;
|
|
21
|
+
temperature?: number;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
requestId?: string;
|
|
24
|
+
}) => Promise<{
|
|
25
|
+
data?: any;
|
|
26
|
+
error?: string;
|
|
27
|
+
}>, temperature?: number, seed?: number, rotationSeed?: number);
|
|
28
|
+
/**
|
|
29
|
+
* 完整一轮"灵感涌现"流程
|
|
30
|
+
* @param strategy 生成策略,约束 ConceptMixer 配对空间
|
|
31
|
+
*/
|
|
32
|
+
generateIdeas(sources: CreativitySource[], maxIdeas?: number, strategy?: Strategy, externalSignals?: ExternalSignal[], method?: string): Promise<CreativeIdea[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Dream Mode — 高随机性、跨时间跨度的"梦境"模式
|
|
35
|
+
*/
|
|
36
|
+
dreamIdeas(recentSources: CreativitySource[], historicalCombos: ConceptCombo[], failedHypotheses: Hypothesis[], maxIdeas?: number, strategy?: Strategy): Promise<CreativeIdea[]>;
|
|
37
|
+
setTemperature(t: number): void;
|
|
38
|
+
/** 设置已探索过的配对,用于 ConceptMixer 降权 */
|
|
39
|
+
setExploredPairs(pairs: string[]): void;
|
|
40
|
+
/**
|
|
41
|
+
* 可行性/质量门禁 — 过滤明显不靠谱的想法
|
|
42
|
+
* - 可行性评分 >= 30 才保留
|
|
43
|
+
* - 想法描述至少 20 个字符(排除模板填空)
|
|
44
|
+
* - novelty > 80 但 feasibility < 40 的"可疑高新颖性"需要额外检查描述长度
|
|
45
|
+
*/
|
|
46
|
+
private feasibilityGate;
|
|
47
|
+
/**
|
|
48
|
+
* 温度影响来源选择权重
|
|
49
|
+
*/
|
|
50
|
+
private applyTemperature;
|
|
51
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdeaGenerator = void 0;
|
|
4
|
+
const ConceptMixer_1 = require("./ConceptMixer");
|
|
5
|
+
const HypothesisGenerator_1 = require("./HypothesisGenerator");
|
|
6
|
+
const ExperimentPlanner_1 = require("./ExperimentPlanner");
|
|
7
|
+
const random_1 = require("@akemi-mio/core/utils/random");
|
|
8
|
+
/**
|
|
9
|
+
* IdeaGenerator — 创造力引擎的核心编排器
|
|
10
|
+
*
|
|
11
|
+
* 流程:
|
|
12
|
+
* 1. Source Gathering — 从多个来源收集"概念"
|
|
13
|
+
* 2. Concept Mixing — 随机配对重组(规则引擎,保留)
|
|
14
|
+
* 3. Hypothesis Generation — 将组合转化为假设(LLM 驱动,new)
|
|
15
|
+
* 4. Experiment Planning — 为假设生成验证方案(规则引擎,保留)
|
|
16
|
+
*/
|
|
17
|
+
class IdeaGenerator {
|
|
18
|
+
mixer;
|
|
19
|
+
hypothesisGen;
|
|
20
|
+
experimentPlanner;
|
|
21
|
+
rng;
|
|
22
|
+
// 创造力温度:越高越随机,越低越保守
|
|
23
|
+
temperature = 0.3;
|
|
24
|
+
/** 已探索过的配对 key 列表,传给 ConceptMixer 以降权 */
|
|
25
|
+
exploredPairs = [];
|
|
26
|
+
constructor(chatJson, temperature = 0.3, seed, rotationSeed = 0) {
|
|
27
|
+
this.rng = (0, random_1.resolveRandom)(seed);
|
|
28
|
+
this.mixer = new ConceptMixer_1.ConceptMixer(seed);
|
|
29
|
+
this.hypothesisGen = new HypothesisGenerator_1.HypothesisGenerator(chatJson, seed, rotationSeed);
|
|
30
|
+
this.experimentPlanner = new ExperimentPlanner_1.ExperimentPlanner(seed);
|
|
31
|
+
this.temperature = temperature;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 完整一轮"灵感涌现"流程
|
|
35
|
+
* @param strategy 生成策略,约束 ConceptMixer 配对空间
|
|
36
|
+
*/
|
|
37
|
+
async generateIdeas(sources, maxIdeas = 5, strategy = 'explore', externalSignals = [], method) {
|
|
38
|
+
if (sources.length < 2)
|
|
39
|
+
return [];
|
|
40
|
+
// 1. 随机扰动:温度越高,来源选择越随机
|
|
41
|
+
const activeSources = this.applyTemperature(sources);
|
|
42
|
+
// 2. 概念重组:按策略约束配对 + 打分
|
|
43
|
+
const scoredCombos = this.mixer.mix(activeSources, maxIdeas * 3, this.exploredPairs, strategy);
|
|
44
|
+
const combos = scoredCombos.map((c) => c.combo);
|
|
45
|
+
if (combos.length === 0)
|
|
46
|
+
return [];
|
|
47
|
+
// 3. 用 LLM 生成假设(外部信号作为审视视角注入,不参与配对)
|
|
48
|
+
const hypotheses = await this.hypothesisGen.generate(combos, activeSources, false, externalSignals, method);
|
|
49
|
+
// 4. 可行性/质量门禁(与 dream 模式同一套门槛)——此前只有梦境路径
|
|
50
|
+
// 过 feasibilityGate,主路径仅按 novelty 筛,可行性 0 分也能入库。
|
|
51
|
+
const feasible = this.feasibilityGate(hypotheses);
|
|
52
|
+
// 5. 策略感知的新颖性门禁 — stable 模式同类型配对新颖度天然偏低,
|
|
53
|
+
// 降低阈值避免误杀;signal 模式需要更高的新颖度才有价值
|
|
54
|
+
const noveltyThreshold = strategy === 'stable' ? 30 : strategy === 'signal' ? 40 : 35;
|
|
55
|
+
const novel = feasible.filter((h) => h.novelty >= noveltyThreshold);
|
|
56
|
+
// 6. 为每个假设生成实验方案
|
|
57
|
+
const ideas = novel.slice(0, maxIdeas).map((h) => ({
|
|
58
|
+
hypothesis: h,
|
|
59
|
+
experiment: this.experimentPlanner.plan(h),
|
|
60
|
+
}));
|
|
61
|
+
return ideas;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Dream Mode — 高随机性、跨时间跨度的"梦境"模式
|
|
65
|
+
*/
|
|
66
|
+
async dreamIdeas(recentSources, historicalCombos, failedHypotheses, maxIdeas = 3, strategy = 'explore') {
|
|
67
|
+
// 梦境模式:提高温度,纳入失败历史,强制跨类型组合
|
|
68
|
+
const dreamTemperature = 0.8;
|
|
69
|
+
// 将失败方案转化为来源(高权重)
|
|
70
|
+
const failureSources = failedHypotheses.map((h) => ({
|
|
71
|
+
name: `失败:${h.title}`,
|
|
72
|
+
content: `${h.idea}\n风险:${h.risk}`,
|
|
73
|
+
type: 'failure',
|
|
74
|
+
weight: 0.8,
|
|
75
|
+
}));
|
|
76
|
+
const allSources = [
|
|
77
|
+
...recentSources,
|
|
78
|
+
...failureSources,
|
|
79
|
+
// 添加随机扰动源
|
|
80
|
+
{
|
|
81
|
+
name: `随机种子_${Date.now()}`,
|
|
82
|
+
content: this.rng().toString(36),
|
|
83
|
+
type: 'random',
|
|
84
|
+
weight: 0.3,
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
// 用高温度混合
|
|
88
|
+
const activeSources = this.mixer.pickRandomSources(allSources, dreamTemperature, 4);
|
|
89
|
+
const scoredCombos = this.mixer.mix(activeSources, maxIdeas * 5, [], strategy);
|
|
90
|
+
const combos = scoredCombos.map((c) => c.combo);
|
|
91
|
+
if (combos.length === 0)
|
|
92
|
+
return [];
|
|
93
|
+
// 梦境模式:高温度 + dreamMode=true
|
|
94
|
+
const hypotheses = await this.hypothesisGen.generate(combos, activeSources, true);
|
|
95
|
+
const feasible = this.feasibilityGate(hypotheses);
|
|
96
|
+
const novel = feasible.filter((h) => h.novelty >= 50);
|
|
97
|
+
return novel.slice(0, maxIdeas).map((h) => ({
|
|
98
|
+
hypothesis: h,
|
|
99
|
+
experiment: this.experimentPlanner.plan(h),
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
setTemperature(t) {
|
|
103
|
+
this.temperature = Math.max(0, Math.min(1, t));
|
|
104
|
+
}
|
|
105
|
+
/** 设置已探索过的配对,用于 ConceptMixer 降权 */
|
|
106
|
+
setExploredPairs(pairs) {
|
|
107
|
+
this.exploredPairs = pairs;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 可行性/质量门禁 — 过滤明显不靠谱的想法
|
|
111
|
+
* - 可行性评分 >= 30 才保留
|
|
112
|
+
* - 想法描述至少 20 个字符(排除模板填空)
|
|
113
|
+
* - novelty > 80 但 feasibility < 40 的"可疑高新颖性"需要额外检查描述长度
|
|
114
|
+
*/
|
|
115
|
+
feasibilityGate(hypotheses) {
|
|
116
|
+
return hypotheses.filter((h) => {
|
|
117
|
+
if (h.feasibility < 30)
|
|
118
|
+
return false;
|
|
119
|
+
if (!h.idea || h.idea.length < 20)
|
|
120
|
+
return false;
|
|
121
|
+
if (h.novelty > 80 && h.feasibility < 40) {
|
|
122
|
+
if (!h.idea || h.idea.length < 60)
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 温度影响来源选择权重
|
|
130
|
+
*/
|
|
131
|
+
applyTemperature(sources) {
|
|
132
|
+
if (this.temperature < 0.2) {
|
|
133
|
+
return [...sources].sort((a, b) => b.weight - a.weight).slice(0, 4);
|
|
134
|
+
}
|
|
135
|
+
if (this.temperature > 0.7) {
|
|
136
|
+
return sources.filter(() => this.rng() > 0.2);
|
|
137
|
+
}
|
|
138
|
+
return sources.filter((s) => {
|
|
139
|
+
if (s.weight > 0.7)
|
|
140
|
+
return true;
|
|
141
|
+
return this.rng() < 0.85;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.IdeaGenerator = IdeaGenerator;
|