feat: 自适应支线插入引擎(WIP:scoring/branching 抽取、挂点重定向、10 条支线 JSON、决策落库)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import type { StoryEvent } from './eventStore';
|
||||
import type { JevClient } from '../services/jev';
|
||||
|
||||
export const PROFILE_DIMENSIONS = [
|
||||
{ id: 'attachment', label: '依恋类型', description: '你在亲密与分离面前的姿态' },
|
||||
{ id: 'conflict_style', label: '冲突处理风格', description: '分歧当前,你先做什么' },
|
||||
{ id: 'values', label: '价值观与人生优先级', description: '两体面或两难堪之间,你让什么先走' },
|
||||
{ id: 'emotional_stability', label: '情绪稳定性', description: '突发与悔恨面前的波形' },
|
||||
{ id: 'risk', label: '风险偏好', description: '稳妥落点与精确落点之间的你' },
|
||||
{ id: 'culture', label: '文化底蕴', description: '碑文、星象与礼制,实测而非自报' },
|
||||
{ id: 'mate_preference', label: '择偶偏好', description: '你信任谁、把后背交给谁' },
|
||||
] as const;
|
||||
|
||||
export type ProfileDimension = (typeof PROFILE_DIMENSIONS)[number]['id'];
|
||||
export interface DimensionScore {
|
||||
id: ProfileDimension;
|
||||
label: string;
|
||||
score: number;
|
||||
max: number;
|
||||
confidence: number;
|
||||
}
|
||||
export type Confidences = Partial<Record<ProfileDimension, number>>;
|
||||
|
||||
/** 身份与同意不是人格证据;路由标记只说明采样路径,不作为性格结论。 */
|
||||
export function buildState(events: StoryEvent[]): string {
|
||||
const lines = events.flatMap((e) => {
|
||||
if (e.pointId === 'C3-02') return [];
|
||||
const prefix = `[轨迹 ${e.sessionId} / ${e.pointId}]`;
|
||||
switch (e.kind) {
|
||||
case 'choice':
|
||||
return [`${prefix} 选择「${e.optionLabel}」(维度: ${e.dimensions.join('/')})` +
|
||||
(e.behavior ? ` 行为: ${JSON.stringify(e.behavior)}` : '')];
|
||||
case 'narration':
|
||||
return [`${prefix} ${e.transcript === null ? '沉默' : `讲述: ${e.transcript}`} 情绪签名: ${e.emotionSignature ?? '未知'}`];
|
||||
case 'followup': return [`${prefix} 追问(${e.followUpDimension})回答: ${e.transcript}`];
|
||||
case 'followup_skipped': return [`${prefix} 追问(${e.followUpDimension})回答: 记不清了(跳过)`];
|
||||
case 'branch_inserted': return [`${prefix} 采样路径: ${e.branchId},补测 ${e.dimensions.join('/')},触发置信度 ${e.confidence}(不是行为证据)`];
|
||||
case 'evidence_insufficient': return [`${prefix} 路由标记: ${e.dimensions.join('/')} 证据不足(不是性格结论)`];
|
||||
default: return [];
|
||||
}
|
||||
});
|
||||
return `用户在互动剧情《苍梧疑冢》第一幕中的行为轨迹(按时间排序,轨迹分开比较)。支线与主线同等采样;结论不一致也是信号,不回滚。沉默、跳过和使用提示不等于低人格水平。路径标记不可当作行为证据:\n${lines.join('\n')}`;
|
||||
}
|
||||
|
||||
export async function scoreProfile(events: StoryEvent[], jev: JevClient): Promise<DimensionScore[]> {
|
||||
const criteria = Array.from({ length: 10 }, (_, i) => `${i + 1} 级`);
|
||||
const answers = await jev.ask(buildState(events), Object.fromEntries(PROFILE_DIMENSIONS.map((d) => [d.id, {
|
||||
type: 'score' as const,
|
||||
instructions: `根据用户行为轨迹,评估其「${d.label}」维度水平:${d.description}。仅按已观察到的行为判断,证据少或矛盾时降低置信度。`,
|
||||
criteria,
|
||||
}])));
|
||||
return PROFILE_DIMENSIONS.map((d) => {
|
||||
const a = answers?.[d.id];
|
||||
if (a?.type !== 'score' || !Number.isFinite(a.score) || a.score < 0 || a.score > 9 ||
|
||||
!Number.isFinite(a.confidence) || a.confidence < 0 || a.confidence > 1) {
|
||||
throw new Error(`Jev 返回无效的 ${d.id} 评分`);
|
||||
}
|
||||
return { id: d.id, label: d.label, score: Math.round((a.score + 1) * 10) / 10, max: 10, confidence: a.confidence };
|
||||
});
|
||||
}
|
||||
|
||||
export function extractConfidences(scores: DimensionScore[]): Confidences {
|
||||
return Object.fromEntries(scores.map((s) => [s.id, s.confidence]));
|
||||
}
|
||||
Reference in New Issue
Block a user