| import type { DagLayoutMode } from '../prediction_attribution/causal_flow/genAttributeDagView'; |
| import type { TokenGenStep } from '../prediction_attribution/causal_flow/tokenGenAttributionRunner'; |
| import type { PromptTokenSpan } from '../prediction_attribution/causal_flow/genAttributeDagPreprocess'; |
| import { |
| canonicalizeCompletionFinishReason, |
| isCompletionFinishReason, |
| isKnownPersistedCompletionReason, |
| type CompletionFinishReason, |
| } from '../cross/generationEndReasonLabel'; |
| import { |
| buildContentKeyFromBusinessKey, |
| getByContentKey, |
| listMru, |
| type CachedHistoryListRow, |
| removeByContentKey, |
| touchByContentKey, |
| upsertEntry, |
| } from './cachedHistoryStore'; |
|
|
| const NAMESPACE = 'gen_attr'; |
| const MAX_ENTRIES = 50; |
|
|
| |
| export type GenAttrRunDraft = { |
| mode: 'raw' | 'chat'; |
| |
| model?: string; |
| |
| maxTokens?: number; |
| |
| system?: string; |
| |
| user?: string; |
| |
| useSystem?: boolean; |
| |
| enableThinking?: boolean; |
| |
| teacherForcing?: string; |
| |
| stopAfterTeacherForcing?: boolean; |
| }; |
|
|
| |
| |
| |
| |
| |
| export type GenAttrCachedRunContentFields = { |
| initialContext: string; |
| steps: TokenGenStep[]; |
| |
| promptSpans?: PromptTokenSpan[]; |
| |
| completionReason?: CompletionFinishReason; |
| |
| draft?: GenAttrRunDraft; |
| }; |
|
|
| |
| |
| |
| |
| export type GenAttrDemoUiOptions = { |
| layoutMode: DagLayoutMode; |
| measureWidthPx: number; |
| dagCompactness: number; |
| linearArcAdjacentGapPx: number; |
| hideExcludedTokens: boolean; |
| edgeTopPCoverage: number; |
| nodeCiVisualScaleEnabled: boolean; |
| decayAttributionToHighSurprisalTargetEnabled: boolean; |
| hideInactiveEdges: boolean; |
| showDownstreamInfluence: boolean; |
| |
| recursiveAttributionEnabled: boolean; |
| |
| recursiveEdgeBatchAnimationDirection: 'backward' | 'forward'; |
| |
| showTokenInfoOnSelected: boolean; |
| replayPacingMode: 'total' | 'step'; |
| playbackTotalS: number; |
| playbackStepMs: number; |
| |
| excludePromptPatternsEnabled: boolean; |
| excludePromptPatternsText: string; |
| |
| excludeGeneratedPatternsEnabled: boolean; |
| excludeGeneratedPatternsText: string; |
| |
| selectedNodeId?: string | null; |
| }; |
|
|
| |
| export type GenAttrCachedRun = GenAttrCachedRunContentFields & { |
| demoUiOptions?: Partial<GenAttrDemoUiOptions>; |
| }; |
|
|
| |
| |
| |
| |
| export type GenAttrCacheKey = { |
| initialContext: string; |
| model: string; |
| maxTokens: number; |
| |
| teacherForcing?: string; |
| |
| stopAfterTeacherForcing?: boolean; |
| }; |
|
|
| |
| function normalizeKey(key: GenAttrCacheKey): object { |
| const tf = key.teacherForcing && key.teacherForcing.length > 0 ? key.teacherForcing : undefined; |
| return { |
| initialContext: key.initialContext, |
| model: key.model, |
| maxTokens: key.maxTokens, |
| ...(tf !== undefined ? { teacherForcing: tf, stopAfterTeacherForcing: key.stopAfterTeacherForcing ?? false } : {}), |
| }; |
| } |
|
|
| function keyHash(key: GenAttrCacheKey): string { |
| return buildContentKeyFromBusinessKey(normalizeKey(key)); |
| } |
|
|
| |
| export function buildGenAttrCachedRunContentPayload(params: { |
| initialContext: string; |
| steps: TokenGenStep[]; |
| promptSpans: PromptTokenSpan[]; |
| completionReason?: CompletionFinishReason; |
| draft?: GenAttrRunDraft; |
| }): GenAttrCachedRunContentFields { |
| const { initialContext, steps, promptSpans, completionReason, draft } = params; |
| let reasonToStore: CompletionFinishReason | undefined; |
| if (completionReason !== undefined) { |
| const c = canonicalizeCompletionFinishReason(completionReason); |
| if (!isCompletionFinishReason(c)) { |
| throw new Error(`gen_attr cache: invalid completionReason: ${completionReason}`); |
| } |
| reasonToStore = c; |
| } |
| return { |
| initialContext, |
| steps, |
| ...(promptSpans.length > 0 ? { promptSpans } : {}), |
| ...(reasonToStore !== undefined ? { completionReason: reasonToStore } : {}), |
| ...(draft !== undefined ? { draft } : {}), |
| }; |
| } |
|
|
| |
| |
| |
| export function buildGenAttrExportedDemoPayload( |
| params: { |
| initialContext: string; |
| steps: TokenGenStep[]; |
| promptSpans: PromptTokenSpan[]; |
| completionReason?: CompletionFinishReason; |
| draft?: GenAttrRunDraft; |
| demoUiOptions: GenAttrDemoUiOptions; |
| } |
| ): GenAttrCachedRun { |
| const { demoUiOptions, ...contentParams } = params; |
| return { ...buildGenAttrCachedRunContentPayload(contentParams), demoUiOptions }; |
| } |
|
|
| function isValidPromptSpansPayload(v: unknown): boolean { |
| if (!Array.isArray(v)) return false; |
| for (const item of v) { |
| if (item == null || typeof item !== 'object') return false; |
| const o = item as Record<string, unknown>; |
| const off = o.offset; |
| if (!Array.isArray(off) || off.length !== 2) return false; |
| if (typeof off[0] !== 'number' || !Number.isFinite(off[0])) return false; |
| if (typeof off[1] !== 'number' || !Number.isFinite(off[1])) return false; |
| if (typeof o.raw !== 'string') return false; |
| if (o.token_id !== undefined && (typeof o.token_id !== 'number' || !Number.isFinite(o.token_id))) { |
| return false; |
| } |
| } |
| return true; |
| } |
|
|
| function isValidGenAttrRunDraftPayload(v: unknown): boolean { |
| if (v == null || typeof v !== 'object') return false; |
| const d = v as Record<string, unknown>; |
| if (d.mode !== 'raw' && d.mode !== 'chat') return false; |
| if (d.model !== undefined && typeof d.model !== 'string') return false; |
| if (d.maxTokens !== undefined && (typeof d.maxTokens !== 'number' || !Number.isFinite(d.maxTokens))) { |
| return false; |
| } |
| if (d.system !== undefined && typeof d.system !== 'string') return false; |
| if (d.user !== undefined && typeof d.user !== 'string') return false; |
| if (d.useSystem !== undefined && typeof d.useSystem !== 'boolean') return false; |
| if (d.teacherForcing !== undefined && typeof d.teacherForcing !== 'string') return false; |
| if (d.stopAfterTeacherForcing !== undefined && typeof d.stopAfterTeacherForcing !== 'boolean') { |
| return false; |
| } |
| return true; |
| } |
|
|
| function isDagLayoutModePayload(v: unknown): v is DagLayoutMode { |
| return ( |
| v === 'text-flow' || |
| v === 'linear-arc' || |
| v === 'linear-arc-step-down' || |
| v === 'spiral' |
| ); |
| } |
|
|
| function isValidDemoUiOptionsPayload(v: unknown): v is Partial<GenAttrDemoUiOptions> { |
| if (v == null || typeof v !== 'object') return false; |
| const d = v as Record<string, unknown>; |
| if (d.layoutMode !== undefined && !isDagLayoutModePayload(d.layoutMode)) return false; |
| if (d.measureWidthPx !== undefined && (typeof d.measureWidthPx !== 'number' || !Number.isFinite(d.measureWidthPx))) { |
| return false; |
| } |
| if (d.dagCompactness !== undefined && (typeof d.dagCompactness !== 'number' || !Number.isFinite(d.dagCompactness))) { |
| return false; |
| } |
| if ( |
| d.linearArcAdjacentGapPx !== undefined && |
| (typeof d.linearArcAdjacentGapPx !== 'number' || !Number.isFinite(d.linearArcAdjacentGapPx)) |
| ) { |
| return false; |
| } |
| if (d.hideExcludedTokens !== undefined && typeof d.hideExcludedTokens !== 'boolean') return false; |
| if ( |
| d.edgeTopPCoverage !== undefined && |
| (typeof d.edgeTopPCoverage !== 'number' || !Number.isFinite(d.edgeTopPCoverage)) |
| ) { |
| return false; |
| } |
| if (d.nodeCiVisualScaleEnabled !== undefined && typeof d.nodeCiVisualScaleEnabled !== 'boolean') { |
| return false; |
| } |
| if ( |
| d.decayAttributionToHighSurprisalTargetEnabled !== undefined && |
| typeof d.decayAttributionToHighSurprisalTargetEnabled !== 'boolean' |
| ) { |
| return false; |
| } |
| const legacyDecay = (d as { edgeWeakenHighSurprisalEnabled?: unknown }).edgeWeakenHighSurprisalEnabled; |
| if (legacyDecay !== undefined && typeof legacyDecay !== 'boolean') { |
| return false; |
| } |
| if (d.hideInactiveEdges !== undefined && typeof d.hideInactiveEdges !== 'boolean') return false; |
| if ( |
| d.showDownstreamInfluence !== undefined && |
| typeof d.showDownstreamInfluence !== 'boolean' |
| ) { |
| return false; |
| } |
| if ( |
| d.recursiveAttributionEnabled !== undefined && |
| typeof d.recursiveAttributionEnabled !== 'boolean' |
| ) { |
| return false; |
| } |
| if ( |
| d.recursiveEdgeBatchAnimationDirection !== undefined && |
| d.recursiveEdgeBatchAnimationDirection !== 'backward' && |
| d.recursiveEdgeBatchAnimationDirection !== 'forward' |
| ) { |
| return false; |
| } |
| if ( |
| d.showTokenInfoOnSelected !== undefined && |
| typeof d.showTokenInfoOnSelected !== 'boolean' |
| ) { |
| return false; |
| } |
| if (d.replayPacingMode !== undefined && d.replayPacingMode !== 'total' && d.replayPacingMode !== 'step') { |
| return false; |
| } |
| if (d.playbackTotalS !== undefined && (typeof d.playbackTotalS !== 'number' || !Number.isFinite(d.playbackTotalS))) { |
| return false; |
| } |
| if (d.playbackStepMs !== undefined && (typeof d.playbackStepMs !== 'number' || !Number.isFinite(d.playbackStepMs))) { |
| return false; |
| } |
| if ( |
| d.excludePromptPatternsEnabled !== undefined && |
| typeof d.excludePromptPatternsEnabled !== 'boolean' |
| ) { |
| return false; |
| } |
| if (d.excludePromptPatternsText !== undefined && typeof d.excludePromptPatternsText !== 'string') { |
| return false; |
| } |
| if ( |
| d.excludeGeneratedPatternsEnabled !== undefined && |
| typeof d.excludeGeneratedPatternsEnabled !== 'boolean' |
| ) { |
| return false; |
| } |
| if ( |
| d.excludeGeneratedPatternsText !== undefined && |
| typeof d.excludeGeneratedPatternsText !== 'string' |
| ) { |
| return false; |
| } |
| if ( |
| d.selectedNodeId !== undefined && |
| d.selectedNodeId !== null && |
| typeof d.selectedNodeId !== 'string' |
| ) { |
| return false; |
| } |
| return true; |
| } |
|
|
| |
| |
| |
| export function isValidGenAttrCachedRunPayload(v: unknown): v is GenAttrCachedRun { |
| if (v == null || typeof v !== 'object') return false; |
| const o = v as Record<string, unknown>; |
| if (typeof o.initialContext !== 'string' || !Array.isArray(o.steps) || o.steps.length === 0) { |
| return false; |
| } |
| if (o.completionReason !== undefined) { |
| if (typeof o.completionReason !== 'string' || !isKnownPersistedCompletionReason(o.completionReason)) { |
| return false; |
| } |
| } |
| if (o.promptSpans !== undefined && !isValidPromptSpansPayload(o.promptSpans)) { |
| return false; |
| } |
| if (o.draft !== undefined && !isValidGenAttrRunDraftPayload(o.draft)) { |
| return false; |
| } |
| if (o.demoUiOptions !== undefined && !isValidDemoUiOptionsPayload(o.demoUiOptions)) { |
| return false; |
| } |
| return true; |
| } |
|
|
| |
| |
| |
| export function parseGenAttrCachedRunPayload( |
| raw: unknown, |
| contextForLog?: string |
| ): GenAttrCachedRun | undefined { |
| if (!isValidGenAttrCachedRunPayload(raw)) { |
| const suffix = |
| contextForLog !== undefined && contextForLog.length > 0 ? ` (${contextForLog})` : ''; |
| console.warn(`[genAttributeRunCache] invalid GenAttrCachedRun payload${suffix}`); |
| return undefined; |
| } |
| return raw; |
| } |
|
|
| export async function save( |
| key: GenAttrCacheKey, |
| steps: TokenGenStep[], |
| promptSpans: PromptTokenSpan[], |
| status: 'partial' | 'complete' = steps.length > 0 ? 'partial' : 'complete', |
| completionReason?: CompletionFinishReason, |
| draft?: GenAttrRunDraft |
| ): Promise<{ contentKey: string }> { |
| const { initialContext } = key; |
| const payload = buildGenAttrCachedRunContentPayload({ |
| initialContext, |
| steps, |
| promptSpans, |
| completionReason, |
| draft, |
| }); |
| return upsertEntry({ |
| namespace: NAMESPACE, |
| businessKeyJson: JSON.stringify(normalizeKey(key)), |
| listLabel: initialContext, |
| payload, |
| status, |
| maxEntries: MAX_ENTRIES, |
| }); |
| } |
|
|
| export async function get(key: GenAttrCacheKey): Promise<GenAttrCachedRun | undefined> { |
| const row = await getByContentKey<GenAttrCachedRun>(NAMESPACE, keyHash(key)); |
| if (!row) return undefined; |
| return parseGenAttrCachedRunPayload(row.payload, 'get(GenAttrCacheKey)'); |
| } |
|
|
| export async function getCachedEntryByContentKey(raw: string): Promise<GenAttrCachedRun | undefined> { |
| if (!raw) return undefined; |
| const row = await getByContentKey<GenAttrCachedRun>(NAMESPACE, raw); |
| if (!row) return undefined; |
| return parseGenAttrCachedRunPayload(row.payload, `contentKey=${raw}`); |
| } |
|
|
| |
| export function buildCachedContentUrlParam(key: GenAttrCacheKey): string { |
| return keyHash(key); |
| } |
|
|
| export async function removeCachedEntryByContentKey(contentKey: string): Promise<void> { |
| await removeByContentKey(NAMESPACE, contentKey); |
| } |
|
|
| export async function touchCachedEntryByContentKey(contentKey: string): Promise<void> { |
| await touchByContentKey(NAMESPACE, contentKey); |
| } |
|
|
| export async function listCachedHistoryRows(): Promise<CachedHistoryListRow[]> { |
| const rows = await listMru<GenAttrCachedRun>(NAMESPACE); |
| return rows.map((r) => ({ contentKey: r.contentKey, listLabel: r.listLabel })); |
| } |
|
|