htaf commited on
Commit
90760e2
·
1 Parent(s): 5464613

basic fetch and asking question generator part of pipeline is working

Browse files
USAGE.md ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Below is a clean **USAGE.md** plus a simple **run_qg_verbose.sh** script so your successor can run the *question-first, fully verbose* pipeline with zero guesswork.
2
+
3
+ Everything is written for clarity, grounded practicality, and safe handoff.
4
+
5
+ ---
6
+
7
+ # **USAGE.md**
8
+
9
+ *How to run the distillation pipeline (static seeds & question-first mode)*
10
+
11
+ This guide shows how to:
12
+
13
+ * run the pipeline,
14
+ * enable verbose logging,
15
+ * use question-generation mode,
16
+ * understand outputs,
17
+ * and perform sanity checks.
18
+
19
+ It assumes you already have:
20
+
21
+ * `distill-pipeline` cloned,
22
+ * `.env` configured,
23
+ * `distill-rag` running (Elasticsearch + embedding server),
24
+ * and Node.js ≥ 20.
25
+
26
+ ---
27
+
28
+ # 1. **Basic Commands**
29
+
30
+ Install dependencies:
31
+
32
+ ```bash
33
+ npm install
34
+ ```
35
+
36
+ Run the default pipeline (static seeds):
37
+
38
+ ```bash
39
+ npm run pipeline -- --limit 10
40
+ ```
41
+
42
+ Verbose run:
43
+
44
+ ```bash
45
+ npm run pipeline -- --limit 10 --verbose
46
+ ```
47
+
48
+ ---
49
+
50
+ # 2. **Question-First Mode (QG Mode)**
51
+
52
+ This mode ignores static seeds and instead:
53
+
54
+ 1. fetches chunks from the `distill-rag` index,
55
+ 2. runs question generation on each chunk,
56
+ 3. feeds generated questions into the normal pipeline (retrieval → generator → verifier → reward).
57
+
58
+ Enable it using the environment flag:
59
+
60
+ ```bash
61
+ PIPELINE_SEED_MODE=question-first npm run pipeline -- --limit 10 --verbose
62
+ ```
63
+
64
+ If you need to cap questions per chunk:
65
+
66
+ ```env
67
+ QUESTION_MAX_PER_CHUNK=3
68
+ ```
69
+
70
+ ---
71
+
72
+ # 3. **Understanding Output**
73
+
74
+ All accepted gold samples are written to:
75
+
76
+ ```
77
+ gold/pipeline_gold.jsonl
78
+ ```
79
+
80
+ Each entry includes:
81
+
82
+ ```json
83
+ {
84
+ "question": "…",
85
+ "context": [ ... ],
86
+ "sample": { ... },
87
+ "verifier": { ... },
88
+ "reward": { ... }
89
+ }
90
+ ```
91
+
92
+ Rejected samples are not written.
93
+ Verbose mode will show why they were rejected.
94
+
95
+ ---
96
+
97
+ # 4. **Environment Variables**
98
+
99
+ All providers are configurable:
100
+
101
+ ```
102
+ GENERATOR_PROVIDER=ollama
103
+ VERIFIER_PROVIDER=ollama
104
+ REWARD_PROVIDER=ollama
105
+ QUESTION_PROVIDER=ollama
106
+
107
+ GENERATOR_MODEL=qwen3-vl:8b-thinking
108
+ VERIFIER_MODEL=tensortemplar/patronus-lynx:8b-instruct-q4_K_M
109
+ REWARD_MODEL=tensortemplar/patronus-lynx:8b-instruct-q4_K_M
110
+ QUESTION_MODEL=qwen2.5-7b-instruct
111
+ ```
112
+
113
+ Retrieval configuration:
114
+
115
+ ```
116
+ ES_NODE=http://localhost:9200
117
+ ES_INDEX=quo_distill_index
118
+ ```
119
+
120
+ Embeddings (used by distill-rag):
121
+
122
+ ```
123
+ EMBED_URL=http://localhost:11434/api/embeddings
124
+ EMBED_MODEL=mxbai-embed-large
125
+ ```
126
+
127
+ ---
128
+
129
+ # 5. **Debugging Tips**
130
+
131
+ ### See provider chosen for each stage:
132
+
133
+ ```bash
134
+ node -e "import('./src/providers/provider.mjs').then(m=>m.debugLoad())"
135
+ ```
136
+
137
+ ### Check prompt templates:
138
+
139
+ ```
140
+ prompts/generator_prompt.txt
141
+ prompts/verifier_prompt.txt
142
+ prompts/reward_prompt.txt
143
+ prompts/question_prompt.txt
144
+ ```
145
+
146
+ ### Run question generator directly:
147
+
148
+ ```bash
149
+ node ./src/question/question_cli.mjs "Give me questions about freedom"
150
+ ```
151
+
152
+ ### Run generator stage only:
153
+
154
+ ```bash
155
+ node ./src/generator/generator_cli.mjs "What is love?"
156
+ ```
157
+
158
+ ---
159
+
160
+ # 6. **Full End-to-End (Question-First) Verbose Run**
161
+
162
+ This is the most common “sanity test”:
163
+
164
+ ```bash
165
+ PIPELINE_SEED_MODE=question-first npm run pipeline -- --limit 5 --verbose
166
+ ```
167
+
168
+ You should see:
169
+
170
+ * chunk preview
171
+ * generated questions
172
+ * retrieval preview
173
+ * generator raw + parsed
174
+ * verifier judgement
175
+ * reward score
176
+
177
+ ---
178
+
179
+ # 7. **Output Cleanup**
180
+
181
+ To reset gold dataset:
182
+
183
+ ```bash
184
+ rm -f gold/pipeline_gold.jsonl
185
+ ```
186
+
187
+ To tail progress while running:
188
+
189
+ ```bash
190
+ tail -f gold/pipeline_gold.jsonl
191
+ ```
192
+
193
+ ---
194
+
195
+ # 8. **Run Tests**
196
+
197
+ ```bash
198
+ npm test
199
+ ```
200
+
201
+ You should see:
202
+
203
+ ```
204
+ 9 test files
205
+ 27 tests
206
+ 0 failures
207
+ ```
208
+
209
+ ---
210
+
211
+ # 9. **run_qg_verbose.sh**
212
+
213
+ Place this in project root:
214
+
215
+ ```
216
+ distill-pipeline/run_qg_verbose.sh
217
+ ```
218
+
219
+ Make executable:
220
+
221
+ ```bash
222
+ chmod +x run_qg_verbose.sh
223
+ ```
224
+
225
+ ### **run_qg_verbose.sh**
226
+
227
+ ```bash
228
+ #!/usr/bin/env bash
229
+
230
+ # Always fail on error
231
+ set -e
232
+
233
+ echo "==============================================="
234
+ echo " Running Question-First Distillation Pipeline"
235
+ echo "==============================================="
236
+ echo
237
+
238
+ # You can adjust limit here if needed
239
+ LIMIT="${1:-5}"
240
+
241
+ export PIPELINE_SEED_MODE=question-first
242
+
243
+ echo "→ Mode: question-first"
244
+ echo "→ Verbose: yes"
245
+ echo "→ Limit: $LIMIT"
246
+ echo
247
+
248
+ npm run pipeline -- --limit "$LIMIT" --verbose
249
+ ```
250
+
251
+ Run it:
252
+
253
+ ```bash
254
+ ./run_qg_verbose.sh
255
+ ```
256
+
257
+ Or specify a custom limit:
258
+
259
+ ```bash
260
+ ./run_qg_verbose.sh 20
261
+ ```
262
+
263
+ ---
264
+
265
+ # 10. **Successor Notes**
266
+
267
+ For successor engineers:
268
+
269
+ * If a stage fails instantly → the provider config is wrong.
270
+ * If questions are empty → question model needs `--verbose` output and better prompts.
271
+ * If verifier always rejects → fix your verifier prompt or lower strictness.
272
+ * If reward always rejects → ensure reward output JSON includes `"score": number`.
273
+ * If pipeline accepts zero samples → inspect generator raw output (check formatting / hallucination).
274
+
275
+ ---
276
+
277
+ If you’d like:
278
+
279
+ ✓ a troubleshooting guide
280
+ ✓ example `.env` with explanations
281
+ ✓ Q’uo-aligned prompt examples
282
+ ✓ diagrams for README / GitHub page
283
+
284
+ Just ask.
run_qg_verbose.sh ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ # Always fail on error
4
+ set -e
5
+
6
+ echo "==============================================="
7
+ echo " Running Question-First Distillation Pipeline"
8
+ echo "==============================================="
9
+ echo
10
+
11
+ # You can adjust limit here if needed
12
+ LIMIT="${1:-5}"
13
+
14
+ export PIPELINE_SEED_MODE=question-first
15
+
16
+ echo "→ Mode: question-first"
17
+ echo "→ Verbose: yes"
18
+ echo "→ Limit: $LIMIT"
19
+ echo
20
+
21
+ npm run pipeline -- --limit "$LIMIT" --verbose
src/pipeline/batch.mjs CHANGED
@@ -8,11 +8,12 @@ import {
8
  DEFAULT_OUT_PATH,
9
  loadSeedQuestions,
10
  seedToQuestion,
11
- seedToContextText,
12
  } from './seeds.mjs';
13
  import { runPipelineStep } from './step.mjs';
14
  import { loadProviderFor } from '../providers/provider.mjs';
15
  import { runQuestionGenerator } from '../question/question_core.mjs';
 
16
 
17
  /**
18
  * Append a single accepted record to a JSONL file.
@@ -28,8 +29,8 @@ export async function appendGoldRecord(outPath, record) {
28
  * samples to a JSONL file.
29
  *
30
  * Modes:
31
- * - static (default): seeds are questions (current behaviour)
32
- * - question-first: seeds are chunks; we first generate questions
33
  *
34
  * Options:
35
  * - seedsPath: JSONL of seeds (defaults to test_samples/seed_questions.jsonl)
@@ -37,18 +38,18 @@ export async function appendGoldRecord(outPath, record) {
37
  * - limit: max number of seeds to process
38
  * - verbose: extra per-stage logging
39
  * - logger: optional logger (defaults to console)
40
- * - seedMode: 'static' | 'question-first' (or PIPELINE_SEED_MODE env)
41
  *
42
  * Returns:
43
  * {
44
  * mode,
45
- * total, // number of seed lines
46
- * processed, // number of questions run through pipeline
47
  * accepted,
48
  * outPath,
49
  * statusCounts,
50
- * processedSeeds?, // only meaningful in question-first
51
- * processedQuestions?, // alias for processed in question-first
52
  * }
53
  */
54
  export async function runPipelineBatch({
@@ -62,17 +63,26 @@ export async function runPipelineBatch({
62
  const log = logger?.log?.bind(logger) || console.log;
63
  const errLog = logger?.error?.bind(logger) || console.error;
64
 
65
- const seeds = await loadSeedQuestions(seedsPath);
66
- const maxSeeds = typeof limit === 'number' ? limit : seeds.length;
 
 
67
 
68
  let processed = 0; // number of questions sent through runPipelineStep
69
  let accepted = 0;
70
  const statusCounts = {};
71
 
72
  // ----------------------------------------
73
- // MODE 1: existing behaviour (static questions)
74
  // ----------------------------------------
75
  if (seedMode === 'static') {
 
 
 
 
 
 
 
76
  for (let idx = 0; idx < maxSeeds; idx++) {
77
  const seed = seeds[idx];
78
  const question = seedToQuestion(seed);
@@ -117,7 +127,7 @@ export async function runPipelineBatch({
117
  }
118
 
119
  return {
120
- mode: 'static',
121
  total: seeds.length,
122
  processed,
123
  accepted,
@@ -127,22 +137,39 @@ export async function runPipelineBatch({
127
  }
128
 
129
  // ----------------------------------------
130
- // MODE 2: question-first (generate Qs from chunks)
131
  // ----------------------------------------
132
  if (seedMode === 'question-first') {
133
  const questionProvider = loadProviderFor('question');
134
- const maxQuestionsPerChunk = Number(process.env.QUESTION_MAX || '5');
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  let processedSeeds = 0;
137
 
138
- for (let idx = 0; idx < maxSeeds; idx++) {
139
- const seed = seeds[idx];
140
- const label = `[seed ${idx + 1}/${maxSeeds}]`;
 
141
 
142
- const contextText = seedToContextText(seed);
143
  if (!contextText || !contextText.trim()) {
144
  if (verbose) {
145
- log(`${label} context is empty, skipping`);
146
  }
147
  continue;
148
  }
@@ -150,7 +177,8 @@ export async function runPipelineBatch({
150
  processedSeeds += 1;
151
 
152
  if (verbose) {
153
- log(`\n🧩 ${label} generating questions from chunk…`);
 
154
  log(
155
  ' [question] chunk preview:\n ' +
156
  preview(contextText, 300).replace(/\n/g, '\n '),
@@ -160,7 +188,7 @@ export async function runPipelineBatch({
160
  );
161
  }
162
 
163
- // 1) generate questions from the chunk
164
  let qResult;
165
  try {
166
  qResult = await runQuestionGenerator(
@@ -198,7 +226,9 @@ export async function runPipelineBatch({
198
  if (!q || !q.trim()) continue;
199
 
200
  const qLabel = `[q ${processed + 1}]`;
201
- log(` → ${qLabel} Running pipeline for generated question: "${q}"`);
 
 
202
 
203
  try {
204
  const result = await runPipelineStep({
@@ -218,8 +248,9 @@ export async function runPipelineBatch({
218
  if (result.status === 'accepted') {
219
  const record = {
220
  question: q,
221
- sourceSeed: seed, // keep origin of the question
222
- sourceChunk: contextText, // raw chunk we asked about
 
223
  context: result.context,
224
  sample: result.gen,
225
  verifier: result.ver,
@@ -244,8 +275,8 @@ export async function runPipelineBatch({
244
  }
245
 
246
  return {
247
- mode: 'question-first',
248
- total: seeds.length,
249
  processed, // number of questions processed
250
  processedSeeds, // how many chunks we actually used
251
  processedQuestions: processed,
@@ -257,3 +288,4 @@ export async function runPipelineBatch({
257
 
258
  throw new Error(`Unknown PIPELINE_SEED_MODE: ${seedMode}`);
259
  }
 
 
8
  DEFAULT_OUT_PATH,
9
  loadSeedQuestions,
10
  seedToQuestion,
11
+ // seedToContextText, // <-- no longer needed for QG mode
12
  } from './seeds.mjs';
13
  import { runPipelineStep } from './step.mjs';
14
  import { loadProviderFor } from '../providers/provider.mjs';
15
  import { runQuestionGenerator } from '../question/question_core.mjs';
16
+ import { fetchChunksFromIndex } from '../retrieval/retrieval.mjs';
17
 
18
  /**
19
  * Append a single accepted record to a JSONL file.
 
29
  * samples to a JSONL file.
30
  *
31
  * Modes:
32
+ * - question-first (default): seeds are CHUNKS; we generate questions from each chunk
33
+ * - static: seeds are questions (legacy / low-priority mode)
34
  *
35
  * Options:
36
  * - seedsPath: JSONL of seeds (defaults to test_samples/seed_questions.jsonl)
 
38
  * - limit: max number of seeds to process
39
  * - verbose: extra per-stage logging
40
  * - logger: optional logger (defaults to console)
41
+ * - seedMode: 'question-first' | 'static' (overrides env + default)
42
  *
43
  * Returns:
44
  * {
45
  * mode,
46
+ * total,
47
+ * processed, // number of questions run through pipeline
48
  * accepted,
49
  * outPath,
50
  * statusCounts,
51
+ * processedSeeds?, // (question-first) how many chunks actually used
52
+ * processedQuestions? // (question-first) alias for processed
53
  * }
54
  */
55
  export async function runPipelineBatch({
 
63
  const log = logger?.log?.bind(logger) || console.log;
64
  const errLog = logger?.error?.bind(logger) || console.error;
65
 
66
+ if (verbose) {
67
+ log('[pipeline] mode:', seedMode);
68
+ log('[pipeline] outPath:', outPath);
69
+ }
70
 
71
  let processed = 0; // number of questions sent through runPipelineStep
72
  let accepted = 0;
73
  const statusCounts = {};
74
 
75
  // ----------------------------------------
76
+ // MODE 1: static questions from JSONL (legacy / tests)
77
  // ----------------------------------------
78
  if (seedMode === 'static') {
79
+ const seeds = await loadSeedQuestions(seedsPath);
80
+ const maxSeeds = typeof limit === 'number' ? limit : seeds.length;
81
+
82
+ if (verbose) {
83
+ log('[pipeline] seedsPath (static questions):', seedsPath);
84
+ }
85
+
86
  for (let idx = 0; idx < maxSeeds; idx++) {
87
  const seed = seeds[idx];
88
  const question = seedToQuestion(seed);
 
127
  }
128
 
129
  return {
130
+ mode: seedMode,
131
  total: seeds.length,
132
  processed,
133
  accepted,
 
137
  }
138
 
139
  // ----------------------------------------
140
+ // MODE 2: question-first (ES chunks → QG → pipeline)
141
  // ----------------------------------------
142
  if (seedMode === 'question-first') {
143
  const questionProvider = loadProviderFor('question');
 
144
 
145
+ // support both env names
146
+ const maxQuestionsPerChunk = Number(
147
+ process.env.QUESTION_MAX_PER_CHUNK ||
148
+ process.env.QUESTION_MAX ||
149
+ '5',
150
+ );
151
+
152
+ const chunkLimit =
153
+ typeof limit === 'number' ? limit : undefined;
154
+
155
+ if (verbose) {
156
+ log(
157
+ `[pipeline] fetching chunks from ES (limit=${chunkLimit ?? 'default'})`,
158
+ );
159
+ }
160
+
161
+ const chunks = await fetchChunksFromIndex(chunkLimit);
162
+ const totalChunks = chunks.length;
163
  let processedSeeds = 0;
164
 
165
+ for (let idx = 0; idx < chunks.length; idx++) {
166
+ const chunk = chunks[idx];
167
+ const label = `[chunk ${idx + 1}/${chunks.length}]`;
168
+ const contextText = chunk.content;
169
 
 
170
  if (!contextText || !contextText.trim()) {
171
  if (verbose) {
172
+ log(`${label} chunk content empty, skipping`);
173
  }
174
  continue;
175
  }
 
177
  processedSeeds += 1;
178
 
179
  if (verbose) {
180
+ log(`\n🧩 ${label} generating questions from ES chunk…`);
181
+ log(` [question] es _id: ${chunk.id}`);
182
  log(
183
  ' [question] chunk preview:\n ' +
184
  preview(contextText, 300).replace(/\n/g, '\n '),
 
188
  );
189
  }
190
 
191
+ // 1) generate questions from this chunk
192
  let qResult;
193
  try {
194
  qResult = await runQuestionGenerator(
 
226
  if (!q || !q.trim()) continue;
227
 
228
  const qLabel = `[q ${processed + 1}]`;
229
+ log(
230
+ ` → ${qLabel} Running pipeline for generated question: "${q}"`,
231
+ );
232
 
233
  try {
234
  const result = await runPipelineStep({
 
248
  if (result.status === 'accepted') {
249
  const record = {
250
  question: q,
251
+ sourceChunkId: chunk.id,
252
+ sourceChunk: contextText, // raw ES chunk text
253
+ sourceDoc: chunk.source, // full ES _source
254
  context: result.context,
255
  sample: result.gen,
256
  verifier: result.ver,
 
275
  }
276
 
277
  return {
278
+ mode: seedMode,
279
+ total: totalChunks,
280
  processed, // number of questions processed
281
  processedSeeds, // how many chunks we actually used
282
  processedQuestions: processed,
 
288
 
289
  throw new Error(`Unknown PIPELINE_SEED_MODE: ${seedMode}`);
290
  }
291
+
src/pipeline/pipeline_cli.js CHANGED
@@ -25,6 +25,7 @@ function parseArgs(argv) {
25
  let seedsPath;
26
  let outPath;
27
  let verbose = false;
 
28
 
29
  for (let i = 0; i < args.length; i++) {
30
  const a = args[i];
@@ -38,6 +39,9 @@ function parseArgs(argv) {
38
  } else if (a === '--out') {
39
  outPath = args[i + 1];
40
  i++;
 
 
 
41
  } else if (a === '--verbose' || a === '-v') {
42
  verbose = true;
43
  }
@@ -56,25 +60,39 @@ function parseArgs(argv) {
56
  seedsPath: seedsPath || DEFAULT_SEEDS,
57
  outPath: outPath || DEFAULT_OUT,
58
  verbose,
 
59
  };
60
  }
61
 
62
  async function main() {
63
- const { limit, seedsPath, outPath, verbose } = parseArgs(process.argv);
 
 
 
 
 
 
64
 
65
  const generatorProvider = process.env.GENERATOR_PROVIDER || 'ollama';
66
  const verifierProvider = process.env.VERIFIER_PROVIDER || generatorProvider;
67
  const rewardProvider = process.env.REWARD_PROVIDER || generatorProvider;
68
 
69
  const generatorModel =
70
- process.env.GENERATOR_MODEL || process.env.OLLAMA_MODEL || 'qwen3-vl:8b-thinking';
 
 
71
  const verifierModel =
72
  process.env.VERIFIER_MODEL || generatorModel;
73
  const rewardModel =
74
  process.env.REWARD_MODEL || verifierModel;
75
 
 
 
 
 
76
  console.log('');
77
  console.log('🚀 Starting Distillation Pipeline');
 
78
  console.log(` Seeds: ${seedsPath}`);
79
  console.log(` Output: ${outPath}`);
80
  console.log(` Providers:`);
@@ -98,15 +116,21 @@ async function main() {
98
  limit,
99
  verbose,
100
  logger: console,
 
101
  });
102
 
103
  console.log('');
104
  console.log('🎉 Pipeline completed');
 
105
  console.log(` Total Seeds: ${result.total}`);
106
- console.log(` Processed: ${result.processed}`);
107
  console.log(` Accepted: ${result.accepted}`);
108
  console.log(` Written to: ${result.outPath}`);
109
 
 
 
 
 
110
  if (result.statusCounts) {
111
  console.log('');
112
  console.log(' Status breakdown:');
 
25
  let seedsPath;
26
  let outPath;
27
  let verbose = false;
28
+ let seedMode; // optional CLI override
29
 
30
  for (let i = 0; i < args.length; i++) {
31
  const a = args[i];
 
39
  } else if (a === '--out') {
40
  outPath = args[i + 1];
41
  i++;
42
+ } else if (a === '--mode') {
43
+ seedMode = args[i + 1]; // "question-first" | "static"
44
+ i++;
45
  } else if (a === '--verbose' || a === '-v') {
46
  verbose = true;
47
  }
 
60
  seedsPath: seedsPath || DEFAULT_SEEDS,
61
  outPath: outPath || DEFAULT_OUT,
62
  verbose,
63
+ seedMode,
64
  };
65
  }
66
 
67
  async function main() {
68
+ const {
69
+ limit,
70
+ seedsPath,
71
+ outPath,
72
+ verbose,
73
+ seedMode: cliSeedMode,
74
+ } = parseArgs(process.argv);
75
 
76
  const generatorProvider = process.env.GENERATOR_PROVIDER || 'ollama';
77
  const verifierProvider = process.env.VERIFIER_PROVIDER || generatorProvider;
78
  const rewardProvider = process.env.REWARD_PROVIDER || generatorProvider;
79
 
80
  const generatorModel =
81
+ process.env.GENERATOR_MODEL ||
82
+ process.env.OLLAMA_MODEL ||
83
+ 'qwen3-vl:8b-thinking';
84
  const verifierModel =
85
  process.env.VERIFIER_MODEL || generatorModel;
86
  const rewardModel =
87
  process.env.REWARD_MODEL || verifierModel;
88
 
89
+ // Resolve mode: CLI > env > default
90
+ const mode =
91
+ cliSeedMode || process.env.PIPELINE_SEED_MODE || 'question-first';
92
+
93
  console.log('');
94
  console.log('🚀 Starting Distillation Pipeline');
95
+ console.log(` Mode: ${mode}`);
96
  console.log(` Seeds: ${seedsPath}`);
97
  console.log(` Output: ${outPath}`);
98
  console.log(` Providers:`);
 
116
  limit,
117
  verbose,
118
  logger: console,
119
+ seedMode: mode,
120
  });
121
 
122
  console.log('');
123
  console.log('🎉 Pipeline completed');
124
+ console.log(` Mode: ${result.mode}`);
125
  console.log(` Total Seeds: ${result.total}`);
126
+ console.log(` Processed Qs: ${result.processed}`);
127
  console.log(` Accepted: ${result.accepted}`);
128
  console.log(` Written to: ${result.outPath}`);
129
 
130
+ if (result.processedSeeds != null) {
131
+ console.log(` Chunks used: ${result.processedSeeds}`);
132
+ }
133
+
134
  if (result.statusCounts) {
135
  console.log('');
136
  console.log(' Status breakdown:');
src/pipeline/seeds.mjs CHANGED
@@ -17,6 +17,8 @@ export const DEFAULT_OUT_PATH = path.join(
17
 
18
  /**
19
  * Load JSONL seed questions or chunks.
 
 
20
  * Each line may be:
21
  * - { "question": "..." }
22
  * - { "prompt": "..." }
@@ -34,6 +36,7 @@ export async function loadSeedQuestions(seedsPath = DEFAULT_SEEDS_PATH) {
34
 
35
  /**
36
  * Extract a question string from a seed record.
 
37
  */
38
  export function seedToQuestion(seed) {
39
  if (typeof seed === 'string') return seed;
@@ -41,7 +44,9 @@ export function seedToQuestion(seed) {
41
  }
42
 
43
  /**
44
- * Extract a chunk of text from a seed record (for question-first mode).
 
 
45
  */
46
  export function seedToContextText(seed) {
47
  if (typeof seed === 'string') return seed;
 
17
 
18
  /**
19
  * Load JSONL seed questions or chunks.
20
+ *
21
+ * Used only by the legacy "static" mode of the pipeline.
22
  * Each line may be:
23
  * - { "question": "..." }
24
  * - { "prompt": "..." }
 
36
 
37
  /**
38
  * Extract a question string from a seed record.
39
+ * Only used in legacy static mode.
40
  */
41
  export function seedToQuestion(seed) {
42
  if (typeof seed === 'string') return seed;
 
44
  }
45
 
46
  /**
47
+ * Extract a chunk of text from a seed record.
48
+ * Not used in the ES-driven question-first mode, but kept
49
+ * for backwards compatibility and potential tooling.
50
  */
51
  export function seedToContextText(seed) {
52
  if (typeof seed === 'string') return seed;
src/retrieval/retrieval.mjs CHANGED
@@ -140,10 +140,40 @@ Do NOT include JSON or formatting.
140
  return fuseRRF(dense, sparse).slice(0, k);
141
  }
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  export default {
144
  embed,
145
  bm25Search,
146
  vectorSearch,
147
  hybridSearch,
148
- hydeHybrid
 
149
  };
 
 
140
  return fuseRRF(dense, sparse).slice(0, k);
141
  }
142
 
143
+ // ----------------------------------------
144
+ // Chunk sampling from ES (for QG pipeline)
145
+ // ----------------------------------------
146
+ export async function fetchChunksFromIndex(limit = 10) {
147
+ const size = Number.isFinite(limit) ? limit : 10;
148
+
149
+ const res = await client.search({
150
+ index: ES_INDEX,
151
+ size,
152
+ query: { match_all: {} },
153
+ });
154
+
155
+ const hits = res.hits?.hits || [];
156
+
157
+ return hits.map((h) => ({
158
+ id: h._id,
159
+ score: h._score,
160
+ // distill-rag usually stores text in `content`, but we try a few keys
161
+ content:
162
+ h._source?.content ??
163
+ h._source?.text ??
164
+ h._source?.chunk ??
165
+ '',
166
+ source: h._source,
167
+ }));
168
+ }
169
+
170
+
171
  export default {
172
  embed,
173
  bm25Search,
174
  vectorSearch,
175
  hybridSearch,
176
+ hydeHybrid,
177
+ fetchChunksFromIndex,
178
  };
179
+