#模型概览
FSRS 模型根据评分和经过时间计算新的记忆状态,通过遗忘曲线估算回忆概率,并根据记忆状态与目标保留率计算基础间隔。
本节的 FSRS-3 至 FSRS-6 术语与公式依据 awesome-fsrs Wiki 校对,主要参考其中的 The Algorithm。TypeScript API 与框架职责边界以 ts-fsrs 本身为准。
模型只负责记忆状态更新、遗忘曲线计算和基础间隔计算。scheduler 负责组织复习流程,chrono 负责日期与经过时间,middleware 负责学习步骤、模糊处理、最大间隔限制和队列状态策略。
复制这段提示词,让 AI Agent 遵守 Model 契约,避免把调度策略混入模型。
#公共符号
| 符号 | 含义 |
|---|---|
| 难度,范围为 1(最简单)至 10(最困难)。 | |
| FSRS-3 至 FSRS-6 的稳定性,单位为天;当 时回忆概率为 90%。 | |
| 经过 天后能够回忆的估计概率。 | |
| 评分:1 为 Again、2 为 Hard、3 为 Good、4 为 Easy。 | |
| 距离上次复习经过的天数。 | |
nextInterval 使用的目标保留率。 | |
| 根据稳定性 和目标保留率 计算的基础间隔,单位为天,尚未应用 middleware 策略。 | |
| 、 | 模型允许的稳定性下限和上限。 |
| 、 | 模型允许的难度下限和上限。 |
| 训练得到的模型权重。 |
FSRS-7 根据其实现快照单独说明:它增加快稳定性,使用混合曲线并返回小数天间隔。上表中稳定性的 90% 定义和 I(r, S) 记法适用于 FSRS-3 至 FSRS-6。
#使用版本化模型
DefaultScheduler 默认使用 FSRS-7,可通过 version 选择任一支持的 FSRS 版本。也可以直接创建并调用各版本模型;下面以 FSRS-6 为例:
import { type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating } from 'ts-fsrs'
import { const FSRS6_DEFAULT_WEIGHTS: number[] FSRS6_DEFAULT_WEIGHTS , const FSRS6Model: Model<{
readonly name: "fsrs-6";
readonly config: SRSSchema<{
input: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
output: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
}>;
readonly memoryState: SRSSchema<{
input: {
stability: number;
difficulty: number;
};
output: {
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS6Algorithm;
}>
FSRS6Model } from 'ts-fsrs/models/fsrs-6'
const const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model = const FSRS6Model: Model<{
readonly name: "fsrs-6";
readonly config: SRSSchema<{
input: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
output: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
}>;
readonly memoryState: SRSSchema<{
input: {
stability: number;
difficulty: number;
};
output: {
stability: number;
difficulty: number;
};
}>;
readonly algorithm: FSRS6Algorithm;
}>
FSRS6Model .Model<{ readonly name: "fsrs-6"; readonly config: SRSSchema<{ input: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; output: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; }>; readonly memoryState: SRSSchema<{ input: { stability: number; difficulty: number; }; output: { stability: number; difficulty: number; }; }>; readonly algorithm: FSRS6Algorithm; }>.create: (ctx: {
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
} | {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly migrate?: boolean;
readonly clip?: boolean;
readonly check?: boolean;
readonly bypass?: boolean;
}) => ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
create ({
config: {
weights: const FSRS6_DEFAULT_WEIGHTS: number[] FSRS6_DEFAULT_WEIGHTS ,
enableShortTerm: true,
numRelearningSteps: 1, // Constrains weights; middleware runs the steps.
},
})
const memoryState = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.step: (input: ModelStepInput<{
stability: number;
difficulty: number;
}>) => {
stability: number;
difficulty: number;
}
step ({
ModelStepInput<{ stability: number; difficulty: number; }>.memoryState: Readonly<{
stability: number;
difficulty: number;
}> | null
memoryState : null,
ModelStepInput<{ stability: number; difficulty: number; }>.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good,
ModelStepInput<{ stability: number; difficulty: number; }>.elapsedDays: number elapsedDays : 0,
})
const desiredRetention = 0.9
const interval = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.nextInterval: (memoryState: Readonly<{
stability: number;
difficulty: number;
}>, desiredRetention: number) => number
nextInterval (memoryState, desiredRetention)
const retrievabilityAtInterval = const model: ModelCore<{
readonly config: {
readonly weights: number[];
readonly enableShortTerm: boolean;
readonly numRelearningSteps: number;
};
readonly memoryState: {
stability: number;
difficulty: number;
};
readonly algorithm: FSRS6Algorithm;
}>
model .ModelCore<{ readonly config: { readonly weights: number[]; readonly enableShortTerm: boolean; readonly numRelearningSteps: number; }; readonly memoryState: { stability: number; difficulty: number; }; readonly algorithm: FSRS6Algorithm; }>.forgettingCurve: (memoryState: Readonly<{
stability: number;
difficulty: number;
}>, elapsedDays: number) => number
forgettingCurve (memoryState, interval)
console.log(
JSON.stringify(
{
memoryState,
desiredRetention,
interval,
retrievabilityAtInterval,
},
null,
2
)
)
示例先根据 90% 的目标保留率计算复习间隔,再通过 forgettingCurve 估算到达该间隔时的回忆概率。由于 FSRS-6 的 nextInterval 返回整数天,结果可能与 90% 略有差异。
在 FSRS-6 配置中,enableShortTerm 控制模型的同日稳定性更新。numRelearningSteps 只参与权重裁剪与检查;重新学习步骤仍由 middleware 调度。
#定义模型
defineModel 会保留具体的配置、记忆状态和算法类型,并把 schema、默认值与模型核心工厂组织为一个定义。它本身不会解析配置:
下面通过实现一个 SM-2 变体,演示如何定义模型。
import {
function clamp(value: number, min: number, max: number): number clamp ,
function defineModel<const Name extends string | symbol, const ConfigSchema extends AnySchema, const MemoryStateSchema extends AnyObjectSchema, const Algorithm>(definition: {
readonly name: Name;
readonly schema: ModelSchema<{
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
}>;
readonly defaultValue: {
readonly memoryState: (ctx: {
readonly config: SchemaOutput<ConfigSchema>;
}) => SchemaInput<MemoryStateSchema>;
};
readonly create: ModelCreate<{
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
}, Algorithm>;
}): Model<{
readonly name: Name;
readonly config: ConfigSchema;
readonly memoryState: MemoryStateSchema;
readonly algorithm: Algorithm;
}>
defineModel ,
type interface ModelForwardInput<MemoryState extends object> ModelForwardInput ,
type interface ModelStepInput<MemoryState extends object> ModelStepInput ,
type Rating = 0 | 1 | 2 | 3 | 4
const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating ,
} from 'ts-fsrs'
import { z } from 'zod'
const configSchema = z.object({
weights: z.tuple([
z.number().min(0.01).max(100),
z.number().min(0.01).max(100),
z.number().min(1.3).max(10),
z.number().nonnegative(),
z.number().min(5),
z.number(),
]),
})
const memoryStateSchema = z.object({
interval: z.number().min(0).max(36500),
easeFactor: z.number().min(1.3).max(10),
repetitions: z.number().int().nonnegative(),
})
type type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState = z.output<typeof memoryStateSchema>
const const SM2Model: Model<{
readonly name: "sm2";
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
readonly algorithm: null;
}>
SM2Model = defineModel<"sm2", z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>, z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>, null>(definition: {
readonly name: "sm2";
readonly schema: ModelSchema<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
...;
}, z.core.$strip>;
}>;
readonly defaultValue: {
...;
};
readonly create: ModelCreate<...>;
}): Model<...>
defineModel ({
name: 'sm2',
schema: ModelSchema<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
}>
schema : { ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>
config : configSchema, ModelSchema<{ readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; }>.memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>
memoryState : memoryStateSchema },
defaultValue: {
memoryState: ({ config }) => ({
interval: 0,
easeFactor: config.weights[2],
repetitions: 0,
}),
},
create: ModelCreate<{
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
}, null>
create ({ config }) {
const parsed = configSchema.ZodType<any, any, $ZodObjectInternals<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>>.parse(data: unknown, params?: z.core.ParseContext<z.core.$ZodIssue>): {
weights: [number, number, number, number, number, number];
}
parse (config)
const w = parsed.weights
const const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step = ({ memoryState, rating }: interface ModelStepInput<MemoryState extends object> ModelStepInput <type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >) => {
const previous = memoryState ?? {
interval: 0,
easeFactor: parsed.weights[2],
repetitions: 0,
}
// Again / Hard / Good / Easy -> SM-2 quality 2 / 3 / 4 / 5.
const quality = rating + 1
const repetitions = rating > const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Again ? previous.repetitions + 1 : 0
const interval = function clamp(value: number, min: number, max: number): number clamp (
repetitions <= 1
? w[0]
: repetitions === 2
? w[1]
: previous.interval * previous.easeFactor,
0.01,
36500
)
const easeFactor = function clamp(value: number, min: number, max: number): number clamp (
previous.easeFactor - w[3] * (quality - w[4]) ** 2 + w[5],
1.3,
10
)
return { interval, easeFactor, repetitions }
}
const const forward: ({ history, initialState, }: ModelForwardInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward = ({
history: readonly ModelReview[] history ,
initialState,
}: interface ModelForwardInput<MemoryState extends object> ModelForwardInput <type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >) => {
const const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states : type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState [] = []
let memoryState = initialState ?? null
for (const const review: ModelReview review of history: readonly ModelReview[] history ) {
memoryState = const step: ({ memoryState, rating }: ModelStepInput<MemoryState>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step ({
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}> | null
memoryState ,
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.rating: 1 | 2 | 3 | 4 rating : const review: ModelReview review .ModelReview.rating: 1 | 2 | 3 | 4 rating ,
ModelStepInput<{ interval: number; easeFactor: number; repetitions: number; }>.elapsedDays: number elapsedDays : const review: ModelReview review .ModelReview.deltaT: number deltaT ,
})
const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states .push(memoryState)
}
return const states: {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states
}
return {
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.config: {
weights: [number, number, number, number, number, number];
}
config : parsed,
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.bounds: ModelBoundsShape<{
interval: number;
easeFactor: number;
repetitions: number;
}>
bounds : {
iMin: 0.01,
iMax: 36500,
eMin: 1.3,
eMax: 10,
rMin: 0,
rMax: Infinity,
},
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.algorithm: null algorithm : null,
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.step: (input: ModelStepInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => {
interval: number;
easeFactor: number;
repetitions: number;
}
step ,
// Invert the benchmark curve to satisfy the Model interval contract.
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.nextInterval: (memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}>, desiredRetention: number) => number
nextInterval : (state: Readonly<type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >, desiredRetention: number) =>
Math.max(
1,
Math.round(
(state.interval * Math.log(desiredRetention)) / Math.log(0.9)
)
),
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forgettingCurve: (memoryState: Readonly<{
interval: number;
easeFactor: number;
repetitions: number;
}>, elapsedDays: number) => number
forgettingCurve : (state: Readonly<type MemoryState = {
interval: number;
easeFactor: number;
repetitions: number;
}
MemoryState >, elapsedDays: number) =>
0.9 ** (elapsedDays / state.interval),
ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward ,
}
},
})
const const model: ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
model = const SM2Model: Model<{
readonly name: "sm2";
readonly config: z.ZodObject<{
weights: z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>;
}, z.core.$strip>;
readonly memoryState: z.ZodObject<{
interval: z.ZodNumber;
easeFactor: z.ZodNumber;
repetitions: z.ZodNumber;
}, z.core.$strip>;
readonly algorithm: null;
}>
SM2Model .Model<{ readonly name: "sm2"; readonly config: ZodObject<{ weights: ZodTuple<[ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber, ZodNumber], null>; }, $strip>; readonly memoryState: ZodObject<...>; readonly algorithm: null; }>.create: (ctx: {
readonly config: {
weights: [number, number, number, number, number, number];
} | {
weights: [number, number, number, number, number, number];
};
readonly migrate?: boolean;
readonly clip?: boolean;
readonly check?: boolean;
readonly bypass?: boolean;
}) => ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
create ({
config: { weights: [1, 6, 2.5, 0.02, 7, 0.18] },
})
const const states: readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states = const model: ModelCore<{
readonly config: {
weights: [number, number, number, number, number, number];
};
readonly memoryState: {
interval: number;
easeFactor: number;
repetitions: number;
};
readonly algorithm: null;
}>
model .ModelCore<{ readonly config: { weights: [number, number, number, number, number, number]; }; readonly memoryState: { interval: number; easeFactor: number; repetitions: number; }; readonly algorithm: null; }>.forward: (input: ModelForwardInput<{
interval: number;
easeFactor: number;
repetitions: number;
}>) => readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
forward ({
ModelForwardInput<{ interval: number; easeFactor: number; repetitions: number; }>.history: readonly ModelReview[] history : [
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 0 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 6 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Again, ModelReview.deltaT: number deltaT : 15 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
{ ModelReview.rating: 1 | 2 | 3 | 4 rating : const Rating: Readonly<{
readonly Manual: 0;
readonly Again: 1;
readonly Hard: 2;
readonly Good: 3;
readonly Easy: 4;
}>
Rating .Good, ModelReview.deltaT: number deltaT : 1 },
],
})
console.log(JSON.stringify(const states: readonly {
interval: number;
easeFactor: number;
repetitions: number;
}[]
states , null, 2)) // Intervals: 1, 6, 15, 1, 1, 6 days.
Schema 描述公共边界,并在 scheduler 组合 schema 时使用。直接调用 create 时,具体实现必须先完成所需的解析和规范化,再返回 config、bounds、algorithm、step、nextInterval、forgettingCurve 和 forward。该核心应保持确定性,且不包含调度策略。
只有调用方已经明确完成该模型的 migrate、clip、schema 校验和范围检查后,才能使用 FSRS6Model.create({ config, bypass: true })。它会跳过这些 create 阶段的处理以及冻结。不要对来自用户、存储或网络的原始配置使用 bypass。