Files
jev-match-game/scripts/verify-live.ts
T

111 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 真 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];
}
}
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') 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') 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<string, JevQuestion> = 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 冒烟完成');