180 lines
5.9 KiB
JavaScript
180 lines
5.9 KiB
JavaScript
|
|
#!/usr/bin/env node
|
|||
|
|
/**
|
|||
|
|
* Corpus COS Fetcher · 从腾讯云对象存储拉取冰朔上传的语料
|
|||
|
|
*
|
|||
|
|
* 系统底层标识: SYS-GLW-0001 / TCS-0002∞
|
|||
|
|
* 版权号: 国作登字-2026-A-00037559
|
|||
|
|
* 作者: 冰朔 (ICE-GL∞) · 实现: 铸渊 (ICE-GL-ZY001)
|
|||
|
|
*
|
|||
|
|
* 架构引用: HLDP-ARCH-002 §九 · factory/training/README.md
|
|||
|
|
*
|
|||
|
|
* 设计:
|
|||
|
|
* 冰朔把所有语料(GPT 聊天记录 / Notion 导出 / 其他)放在 COS 存储桶。
|
|||
|
|
* 训练机/本地从 COS 按 prefix 拉取到 ./corpus/raw/{source}/ 目录。
|
|||
|
|
* 后续由 harvest.js + manual-import 接力处理 → 切分 → 训练就绪。
|
|||
|
|
*
|
|||
|
|
* 用法:
|
|||
|
|
* node scripts/corpus-harvester/cos-fetch.js \
|
|||
|
|
* --bucket guanghu-corpus-1300000000 \
|
|||
|
|
* --region ap-shanghai \
|
|||
|
|
* --prefix gpt-export/ \
|
|||
|
|
* --dest ./corpus/raw/gpt
|
|||
|
|
*
|
|||
|
|
* # 或者从环境变量读
|
|||
|
|
* COS_BUCKET=... COS_REGION=... COS_PREFIX=gpt-export/ \
|
|||
|
|
* node scripts/corpus-harvester/cos-fetch.js --dest ./corpus/raw/gpt
|
|||
|
|
*
|
|||
|
|
* 凭据:
|
|||
|
|
* 通过环境变量读取 (TENCENT_COS_SECRET_ID / TENCENT_COS_SECRET_KEY),
|
|||
|
|
* 不写在仓库里。也可走临时密钥 (STS) 注入。
|
|||
|
|
*
|
|||
|
|
* 状态: 骨架(本地无 SDK / 无凭据时进入 dry-run,仅打印将要拉取的内容)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
const ARGS = parseArgs(process.argv.slice(2));
|
|||
|
|
const CONFIG = {
|
|||
|
|
bucket: ARGS.bucket || process.env.COS_BUCKET,
|
|||
|
|
region: ARGS.region || process.env.COS_REGION,
|
|||
|
|
prefix: ARGS.prefix || process.env.COS_PREFIX || '',
|
|||
|
|
dest: ARGS.dest || process.env.COS_DEST || './corpus/raw',
|
|||
|
|
secretId: process.env.TENCENT_COS_SECRET_ID,
|
|||
|
|
secretKey: process.env.TENCENT_COS_SECRET_KEY,
|
|||
|
|
dryRun: ARGS['dry-run'] === true || !process.env.TENCENT_COS_SECRET_ID,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const COPYRIGHT = {
|
|||
|
|
registration: '国作登字-2026-A-00037559',
|
|||
|
|
sovereign: '冰朔 · TCS-0002∞',
|
|||
|
|
system_root: 'SYS-GLW-0001',
|
|||
|
|
arch_ref: 'HLDP-ARCH-002',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
function parseArgs(argv) {
|
|||
|
|
const out = {};
|
|||
|
|
for (let i = 0; i < argv.length; i++) {
|
|||
|
|
const a = argv[i];
|
|||
|
|
if (!a.startsWith('--')) continue;
|
|||
|
|
const key = a.slice(2);
|
|||
|
|
const next = argv[i + 1];
|
|||
|
|
if (next && !next.startsWith('--')) {
|
|||
|
|
out[key] = next;
|
|||
|
|
i++;
|
|||
|
|
} else {
|
|||
|
|
out[key] = true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function ensureDir(dir) {
|
|||
|
|
fs.mkdirSync(dir, { recursive: true });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function logBanner() {
|
|||
|
|
console.log('═'.repeat(64));
|
|||
|
|
console.log('Corpus COS Fetcher · 光湖语料拉取器');
|
|||
|
|
console.log('灵魂印记:', JSON.stringify(COPYRIGHT, null, 0));
|
|||
|
|
console.log('═'.repeat(64));
|
|||
|
|
console.log(`bucket: ${CONFIG.bucket || '<未设置>'}`);
|
|||
|
|
console.log(`region: ${CONFIG.region || '<未设置>'}`);
|
|||
|
|
console.log(`prefix: ${CONFIG.prefix || '<空>'}`);
|
|||
|
|
console.log(`dest: ${CONFIG.dest}`);
|
|||
|
|
console.log(`dryRun: ${CONFIG.dryRun}`);
|
|||
|
|
console.log('─'.repeat(64));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function validateConfig() {
|
|||
|
|
if (!CONFIG.bucket || !CONFIG.region) {
|
|||
|
|
console.error('❌ 缺少 --bucket / --region(或环境变量 COS_BUCKET / COS_REGION)');
|
|||
|
|
process.exit(2);
|
|||
|
|
}
|
|||
|
|
if (CONFIG.dryRun) {
|
|||
|
|
console.warn(
|
|||
|
|
'⚠️ dry-run 模式(未提供 TENCENT_COS_SECRET_ID/KEY 或显式 --dry-run):' +
|
|||
|
|
'\n 仅打印将要执行的操作,不真正下载。'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 真实下载实现(骨架)。
|
|||
|
|
* 当前不引入 cos-nodejs-sdk-v5 依赖,避免本仓多一份重依赖。
|
|||
|
|
* 训练机上线时铸渊会:
|
|||
|
|
* 1) `npm i --save-optional cos-nodejs-sdk-v5`
|
|||
|
|
* 2) 把下面的 TODO 替换为实际 listObjects + getObject 循环
|
|||
|
|
*/
|
|||
|
|
async function fetchFromCOS() {
|
|||
|
|
if (CONFIG.dryRun) {
|
|||
|
|
console.log('[dry-run] 将列出 prefix 下所有对象 → 拉取到本地 dest');
|
|||
|
|
console.log('[dry-run] 实际启用需安装 cos-nodejs-sdk-v5 + 注入凭据。');
|
|||
|
|
return { downloaded: 0, dryRun: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TODO: 训练机上线后启用
|
|||
|
|
// const COS = require('cos-nodejs-sdk-v5');
|
|||
|
|
// const cos = new COS({ SecretId: CONFIG.secretId, SecretKey: CONFIG.secretKey });
|
|||
|
|
// const list = await new Promise((res, rej) =>
|
|||
|
|
// cos.getBucket({ Bucket: CONFIG.bucket, Region: CONFIG.region, Prefix: CONFIG.prefix, MaxKeys: 1000 },
|
|||
|
|
// (err, data) => (err ? rej(err) : res(data))));
|
|||
|
|
// for (const obj of list.Contents) {
|
|||
|
|
// if (obj.Key.endsWith('/')) continue; // 跳过目录占位
|
|||
|
|
// const localPath = path.join(CONFIG.dest, obj.Key.replace(CONFIG.prefix, ''));
|
|||
|
|
// ensureDir(path.dirname(localPath));
|
|||
|
|
// await new Promise((res, rej) =>
|
|||
|
|
// cos.getObject({ Bucket: CONFIG.bucket, Region: CONFIG.region, Key: obj.Key, Output: fs.createWriteStream(localPath) },
|
|||
|
|
// (err) => (err ? rej(err) : res())));
|
|||
|
|
// console.log(` ✓ ${obj.Key} → ${localPath} (${obj.Size} bytes)`);
|
|||
|
|
// }
|
|||
|
|
// return { downloaded: list.Contents.length, dryRun: false };
|
|||
|
|
|
|||
|
|
throw new Error(
|
|||
|
|
'真实下载尚未启用 · 见 cos-fetch.js 中的 TODO(需安装 cos-nodejs-sdk-v5)'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function writeFetchManifest(result) {
|
|||
|
|
const manifestPath = path.join(CONFIG.dest, '_fetch-manifest.json');
|
|||
|
|
ensureDir(CONFIG.dest);
|
|||
|
|
const manifest = {
|
|||
|
|
schema: 'cos-fetch-manifest/v1',
|
|||
|
|
fetched_at: new Date().toISOString(),
|
|||
|
|
config: {
|
|||
|
|
bucket: CONFIG.bucket,
|
|||
|
|
region: CONFIG.region,
|
|||
|
|
prefix: CONFIG.prefix,
|
|||
|
|
dest: CONFIG.dest,
|
|||
|
|
dryRun: CONFIG.dryRun,
|
|||
|
|
},
|
|||
|
|
result,
|
|||
|
|
soul_marker: COPYRIGHT,
|
|||
|
|
};
|
|||
|
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), 'utf8');
|
|||
|
|
console.log(`📄 manifest 写入: ${manifestPath}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async function main() {
|
|||
|
|
logBanner();
|
|||
|
|
validateConfig();
|
|||
|
|
ensureDir(CONFIG.dest);
|
|||
|
|
|
|||
|
|
let result;
|
|||
|
|
try {
|
|||
|
|
result = await fetchFromCOS();
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error('❌ 拉取失败:', err.message);
|
|||
|
|
result = { error: err.message };
|
|||
|
|
process.exitCode = 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
writeFetchManifest(result);
|
|||
|
|
console.log('─'.repeat(64));
|
|||
|
|
console.log('完成。下一步: 运行 manual-import 模式把 raw 数据切分成训练就绪格式。');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
main();
|