选择策略目录、保留额度及可行性筛选
策略的具体实现如下。
ts
contextSelectionCatalog = {
sequence: { validate: validateSequence, choose: ordered },
fixed: { validate: validateFixed, choose: fixed },
auto: { validate: validateAuto, choose: automatic },
model: { validate: validateModel, choose: model },
} satisfies Record<ContextSelection['mode'], SelectionHandler>
export async function selectContextSteps(input: SelectionInput): Promise<SelectionResult> {
const selection = input.selection;
const modelId = selection.mode === 'model' ? selection.handlerId : undefined;
const selector = input.selectors.find((handler) => handler.id === modelId);
const selectorCalls = selector ? contextHandlerCalls(selector) : 0;
const reserve = reservedContextCalls(selection);
// The selector may call the model itself, so its allowance is unavailable to the steps it picks.
const available = input.remainingCalls - reserve - selectorCalls;
if (selection.mode === 'model' && available < 0)
throw new BudgetError('No calls available for context selection');
const candidates: ContextSelectionCandidate[] = input.candidates.filter(
(candidate) => candidate.requiredCalls <= available,
);
if (selection.mode === 'model' && !candidates.length)
throw new BudgetError('No feasible context operations after reserving calls');
const result = await contextSelectionCatalog[selection.mode].choose({ ...input, candidates });
if (selection.mode === 'model') {
const required = result.stepIds.reduce(
(sum, id) => sum + candidates.find((candidate) => candidate.id === id)!.requiredCalls,
0,
);
breakWhile(required > available, invalid('Selected operations exceed context call allowance'));
}
return result;
}
async function automatic({ selection, candidates }: SelectionInput): Promise<SelectionResult> {
if (selection.mode !== 'auto') throw invalid('Invalid automatic context selection');
const ratio = selection.maxTrimLossRatio ?? 0.25;
const trims = candidates
.filter(
(candidate) =>
candidate.kind === 'trim' &&
(candidate.trimLossRatio ?? 1) <= ratio &&
candidate.projectedChars! < candidate.chars,
)
.sort((a, b) => a.trimLossRatio! - b.trimLossRatio!);
const chosen = trims[0] ?? candidates.find((candidate) => candidate.kind === 'compress');
return {
stepIds: chosen ? [chosen.id] : [],
reason:
chosen?.kind === 'trim'
? 'bounded-trim-loss'
: chosen
? 'preserve-through-compression'
: 'no-safe-candidate',
};
}
reservedContextCalls = (selection: ContextSelection) =>
selection.mode === 'auto' || selection.mode === 'model' ? (selection.reserveCalls ?? 1) : 0
contextHandlerCalls = (handler: { maxCalls?: number }) => handler.maxCalls ?? 1