(opts?: {
embedDelayMs?: number
failFirstPipelineCreation?: boolean
failPipelineCreationForModelIds?: ReadonlySet<string>
onEmbedBatchStarted?: () => void
})
| 25 | const BASE_TS_SECONDS = 1_700_000_000 |
| 26 | |
| 27 | function setup(opts?: { |
| 28 | embedDelayMs?: number |
| 29 | failFirstPipelineCreation?: boolean |
| 30 | failPipelineCreationForModelIds?: ReadonlySet<string> |
| 31 | onEmbedBatchStarted?: () => void |
| 32 | }) { |
| 33 | const baseDir = fs.existsSync('/private/tmp') ? '/private/tmp' : os.tmpdir() |
| 34 | const dir = fs.mkdtempSync(path.join(baseDir, 'chatlab-si-svc-')) |
| 35 | const chatDbPath = path.join(dir, `${SESSION_ID}.db`) |
| 36 | const db = openBetterSqliteDatabase(chatDbPath) |
| 37 | db.exec(CHAT_DB_SCHEMA) |
| 38 | db.exec(FTS_TABLE_SCHEMA) |
| 39 | db.exec(` |
| 40 | INSERT INTO meta (name, platform, type, imported_at) VALUES ('测试群', 'wechat', 'group', 0); |
| 41 | INSERT INTO member (id, platform_id, account_name) VALUES (1, 'p1', '张三'), (2, 'p2', '李四'); |
| 42 | `) |
| 43 | const insert = db.prepare('INSERT INTO message (id, sender_id, ts, type, content) VALUES (?, ?, ?, 0, ?)') |
| 44 | for (let i = 1; i <= 40; i++) { |
| 45 | insert.run(i, (i % 2) + 1, BASE_TS_SECONDS + i * 60, `第${i}条关于项目排期和需求讨论的消息内容`) |
| 46 | } |
| 47 | buildFtsIndex(db) |
| 48 | |
| 49 | const adapter: SessionRuntimeAdapter = { |
| 50 | listSessionIds: () => [SESSION_ID], |
| 51 | openReadonly: (id) => (id === SESSION_ID ? db : null), |
| 52 | openWritable: (id) => (id === SESSION_ID ? db : null), |
| 53 | closeSession: () => {}, |
| 54 | getDbPath: () => chatDbPath, |
| 55 | deleteSessionFile: () => false, |
| 56 | ensureReadonly: (id) => { |
| 57 | if (id !== SESSION_ID) throw new Error('not found') |
| 58 | return db |
| 59 | }, |
| 60 | ensureWritable: (id) => { |
| 61 | if (id !== SESSION_ID) throw new Error('not found') |
| 62 | return db |
| 63 | }, |
| 64 | } |
| 65 | |
| 66 | // 本地 pipeline 工厂:返回 Qwen3 维度向量,按文本长度给一点变化,避免零向量 |
| 67 | // embedTextCount 统计累计嵌入文本数,用于区分 rebuild(重新嵌入) 与 build 续跑(跳过不嵌入) |
| 68 | let embedTextCount = 0 |
| 69 | let pipelineCreateAttempts = 0 |
| 70 | const localPipelineFactory: LocalPipelineFactory = async ({ modelId }) => { |
| 71 | pipelineCreateAttempts++ |
| 72 | if (opts?.failFirstPipelineCreation && pipelineCreateAttempts === 1) { |
| 73 | throw new Error('temporary preload failure') |
| 74 | } |
| 75 | if (opts?.failPipelineCreationForModelIds?.has(modelId)) { |
| 76 | throw new Error(`temporary preload failure for ${modelId}`) |
| 77 | } |
| 78 | return async (texts) => { |
| 79 | opts?.onEmbedBatchStarted?.() |
| 80 | embedTextCount += texts.length |
| 81 | if (opts?.embedDelayMs) await new Promise((r) => setTimeout(r, opts.embedDelayMs)) |
| 82 | return texts.map((t) => { |
| 83 | const v = new Array(QWEN3_PROFILE.dim).fill(0) |
| 84 | v[t.length % QWEN3_PROFILE.dim] = 1 |
no test coverage detected