/** * 真 key 冒烟(pnpm verify:live):直连 https://api.typesafe.ai/v1/systemone。 * 手动解析根目录 .env(不引 dotenv),任何输出都不打印 key 本身。 * 只跑三次调用控制 token 消耗: * 1. C1-01 身份抽取问题组(gender/age/occupation/education/city) * 2. C1-02 情绪签名 → 场景变体选择(choice) * 3. 画像报告 7 维 score */ import { readFileSync } from 'node:fs'; import { StoryEngine } from '../src/engine/engine'; import type { EventStore, StoryEvent } from '../src/engine/eventStore'; import type { Chapter } from '../src/engine/types'; import { HttpJevClient, type JevQuestion } from '../src/services/jev'; function loadApiKey(): string | null { let raw: string; try { raw = readFileSync(new URL('../.env', import.meta.url), 'utf8'); } catch { return null; } for (const line of raw.split('\n')) { const m = line.match(/^\s*VITE_TYPESAFE_API_KEY\s*=\s*(.+?)\s*$/); if (m && m[1] && !m[1].startsWith('#')) return m[1]; } return null; } class MemoryStore implements EventStore { events: StoryEvent[] = []; async append(e: StoryEvent) { this.events.push(e); } async list(sessionId?: string) { return sessionId ? this.events.filter((e) => e.sessionId === sessionId) : [...this.events]; } async purge() { this.events = []; } } const key = loadApiKey(); if (!key) { console.error('未在 .env 中找到 VITE_TYPESAFE_API_KEY,跳过真 key 冒烟'); process.exit(1); } console.log('已加载 API key(长度 ' + key.length + ',不打印内容)'); const jev = new HttpJevClient(key); const chapters = [1, 2, 3].map( (i) => JSON.parse(readFileSync(new URL(`../content/chapters/chapter${i}.json`, import.meta.url), 'utf8')) as Chapter, ); // --- 1. C1-01 身份抽取(走引擎真实代码路径) --- const store = new MemoryStore(); const engine = new StoryEngine(chapters, store, jev); while (engine.currentNode().type !== 'identity') await engine.advance(); const intro = '我叫林晚,今年29岁,在杭州一家互联网公司做产品经理,硕士毕业五年了。'; const profile = await engine.extractIdentity(intro); console.log('\n[1] C1-01 身份抽取'); console.log(' 输入:', intro); console.log(' 抽取:', JSON.stringify(profile)); // --- 2. C1-02 情绪签名 → 场景变体(choice 格式实测) --- await engine.confirmIdentity(profile); while (engine.currentNode().type !== 'narration') await engine.advance(); const farewell = '外婆走的那天我在外地考试,没赶上最后一面。后来我每年清明都回去,院子里的桂花树还在。'; let result = await engine.submitNarration(farewell); console.log('\n[2] C1-02 情绪签名 → 场景变体'); console.log(' 输入:', farewell); if (!result && engine.state.pendingFollowUp) { // 追问补全(六维缺失检测):真 key 下可能挂起追问,回答一句完成流程 const p = engine.state.pendingFollowUp; console.log(` 追问(${p.dimension}): ${p.prompt}`); result = await engine.answerFollowUp('院子里是桂花的甜香,风一吹就往屋里钻。'); } if (!result) throw new Error('讲述流程未完成'); console.log(` 签名: ${result.variant.signature} → 变体: ${result.variant.variantId}(${result.variant.label})`); // --- 3. 画像报告 7 维 score --- const dims: [string, string][] = [ ['attachment', '依恋类型'], ['conflict_style', '冲突处理风格'], ['values', '价值观与人生优先级'], ['emotional_stability', '情绪稳定性'], ['risk', '风险偏好'], ['culture', '文化底蕴'], ['mate_preference', '择偶偏好'], ]; const criteria = Array.from({ length: 10 }, (_, i) => `${i + 1} 级`); const state = `用户在互动剧情中的行为轨迹: [C1-01] 身份登记:${JSON.stringify(profile)} [C1-02] 离别之门讲述:${farewell}(情绪签名: ${result.variant.signature}) [C1-04] 老鬼提议带纪念品,选择「我们是去记录的,不是去拿的」当场回绝 [C1-06] 穿越落点选择「精确落点:直接进城,赌一把」`; const questions: Record = Object.fromEntries( dims.map(([id, label]) => [ id, { type: 'score' as const, instructions: `根据用户行为轨迹评估其「${label}」维度水平`, criteria }, ]), ); const answers = await jev.ask(state, questions); console.log('\n[3] 画像报告 7 维 score(1-10 级,浮点为概率加权)'); for (const [id, label] of dims) { const a = answers[id]; if (a?.type === 'score') { console.log(` ${label}: ${(a.score + 1).toFixed(1)}/10 置信度 ${(a.confidence * 100).toFixed(0)}%`); } else { console.log(` ${label}: 未返回 score 答案`, JSON.stringify(a)); } } console.log('\n真 key 冒烟完成');