Skip to content
English

场景目录、生成的步骤和精确覆盖 ​

策略的具体实现如下。

ts
contextStrategyCatalog = {
  recent: { operation: 'trim', build: recent },
  continuity: { operation: 'compress', build: continuity },
} satisfies Record<GeneratedContextStrategyOptions['kind'], StrategyDefinition>

export function createContextStrategy(
  options: ContextStrategyOptions | readonly ContextStrategyOptions[],
  settings: ContextStrategySettings = {},
): ContextStrategy {
  const definitions: readonly ContextStrategyOptions[] = Array.isArray(options)
    ? options
    : [options as ContextStrategyOptions];
  breakWhile(!definitions.length, 'Context strategy requires at least one history');
  const single = definitions.length === 1 ? definitions[0] : undefined;
  // Array items generate operations; global processing settings belong to the second argument.
  if (
    !single &&
    definitions.some((item) =>
      [
        'selection',
        'steps',
        'selectors',
        'compressors',
        'triggerChars',
        'targetInputChars',
        'triggerTokens',
        'targetInputTokens',
      ].some((key) => key in item),
    )
  ) {
    throw new TypeError('Configure shared context strategy settings in the second argument');
  }
  const config: Record<string, unknown> = {};
  const keys = [
    'selection',
    'steps',
    'selectors',
    'compressors',
    'triggerChars',
    'targetInputChars',
    'triggerTokens',
    'targetInputTokens',
  ] as const;
  for (const source of [single, settings])
    if (source) for (const key of keys) if (key in source) config[key] = source[key];
  const configured = config as ContextStrategySettings;
  const generated =
    configured.steps !== undefined
      ? []
      : (definitions as readonly GeneratedContextStrategyOptions[]).map((options) => {
          const { kind, fragment, field } = options;
          const preset = options.preset;
          breakWhile(
            preset !== undefined && !Object.hasOwn(contextHistoryStrategies, preset),
            'Unknown history strategy preset',
          );
          breakWhile(
            !Number.isSafeInteger(fragment.maxChars) || fragment.maxChars < 1,
            'History strategy requires a positive fragment allowance',
          );
          const defaults = preset === undefined ? undefined : contextHistoryStrategies[preset];
          const id = fragment.id,
            targetChars =
              options.targetChars ??
              (defaults
                ? Math.max(1, Math.floor(fragment.maxChars * defaults.targetRatio))
                : fragment.maxChars);
          const grouping = options.grouping ?? { kind: 'user-turn' };
          const preserve = {
            field,
            keepGroups: options.keepGroups ?? defaults?.keepGroups ?? 1,
            grouping:
              grouping.kind === 'fixed'
                ? { kind: grouping.kind, size: grouping.size }
                : { kind: grouping.kind },
          };
          breakWhile(!Object.hasOwn(contextStrategyCatalog, kind), 'Unknown context strategy');
          return contextStrategyCatalog[kind].build({ options, id, targetChars, preserve });
        });
  const steps = configured.steps ?? generated.map((item) => item.step);
  // A single step needs no selection decision; several steps keep declared order.
  const selection =
    configured.selection ??
    (steps.length === 1
      ? { mode: 'fixed' as const, kind: steps[0].kind }
      : { mode: 'sequence' as const });
  const compressors = [
    ...generated.flatMap((item) => item.compressors),
    ...(configured.compressors ?? []),
  ];
  const unique = new Map<string, ContextCompressor>();
  for (const compressor of compressors) {
    // The same handler may be referenced by several steps; a different object under one ID is
    // ambiguous for the policy, so it is rejected instead of silently replaced.
    breakWhile(
      unique.has(compressor.id) && unique.get(compressor.id) !== compressor,
      `Conflicting context compressor: ${compressor.id}`,
    );
    unique.set(compressor.id, compressor);
  }
  const selectors = [...(configured.selectors ?? [])];
  const policy: ContextPolicy = {
    mode: 'reduce',
    steps: [...steps],
    selection,
    triggerChars: configured.triggerChars,
    targetInputChars: configured.targetInputChars,
    triggerTokens: configured.triggerTokens,
    targetInputTokens: configured.targetInputTokens,
  };
  breakWhile(
    selection.mode === 'fixed' && !steps.some((step) => step.kind === selection.kind),
    'Context strategy selection has no matching operation',
  );
  validateContextPolicy(policy, [...unique.values()], selectors);
  return Object.freeze({
    policy,
    compressors: Object.freeze([...unique.values()]),
    selectors: Object.freeze(selectors),
  });
}

function recent({ id, targetChars, preserve }: StrategyInput) {
  return {
    step: { id: `${id}.window`, kind: 'trim' as const, fragmentId: id, targetChars, ...preserve },
    compressors: [],
  };
}

function continuity({ options, id, targetChars, preserve }: StrategyInput) {
  const compressor = options.compressor;
  breakWhile(!compressor, 'Continuity strategy requires a compressor');
  return {
    step: {
      id: `${id}.summary`,
      kind: 'compress' as const,
      fragmentId: id,
      targetChars,
      handlerId: compressor!.id,
      preserve,
    },
    compressors: [compressor!],
  };
}

接口参考、使用示例与开发指南。