Route tool ranker fallback through executor

This commit is contained in:
2026-05-18 17:16:28 +03:00
parent 58f5a645fd
commit e520c412af
3 changed files with 66 additions and 10 deletions
+1 -1
View File
@@ -74,7 +74,7 @@
- [x] Убрать дублирующий ручной `tool-rank-audit.ts`, если stage полностью заменит его. - [x] Убрать дублирующий ручной `tool-rank-audit.ts`, если stage полностью заменит его.
- [x] Сохранить status UX: `🧩 Выбираю подходящие инструменты...`. - [x] Сохранить status UX: `🧩 Выбираю подходящие инструменты...`.
- [x] Гарантировать `clearStatus()` после ranker success/failure. - [x] Гарантировать `clearStatus()` после ranker success/failure.
- [ ] Добавить fallback через `PipelineFallbackExecutor`: main model, all tools, no tools. - [x] Добавить fallback через `PipelineFallbackExecutor`: main model, all tools, no tools.
- [x] Добавить tests на fallback ranker policy. - [x] Добавить tests на fallback ranker policy.
## 6. Сделать model_call и tool_loop физически отдельными stages ## 6. Сделать model_call и tool_loop физически отдельными stages
+39 -6
View File
@@ -1,23 +1,56 @@
import {ToolRankerFallbackPolicy} from "../common/policies.js"; import {ToolRankerFallbackPolicy} from "../common/policies.js";
import {decidePipelineFallback, type PipelineFallbackDecision} from "./user-request-pipeline/fallback-executor.js";
export type ToolRankerFallbackSelection = { export type ToolRankerFallbackSelection = {
toolNames: string[]; toolNames: string[];
usedRanker: boolean; usedRanker: boolean;
}; };
export function resolveToolRankerFallbackSelection(params: { export type ToolRankerFallbackDecision = PipelineFallbackDecision & ToolRankerFallbackSelection;
function fallbackActionForPolicy(policy: ToolRankerFallbackPolicy) {
return policy === ToolRankerFallbackPolicy.MAIN_MODEL
? "use_alternate_target"
: "continue_without_stage";
}
export function decideToolRankerFallback(params: {
fallbackPolicy: ToolRankerFallbackPolicy; fallbackPolicy: ToolRankerFallbackPolicy;
availableToolNames: readonly string[]; availableToolNames: readonly string[];
}): ToolRankerFallbackSelection { reason: "unavailable" | "failed";
if (params.fallbackPolicy === ToolRankerFallbackPolicy.NO_TOOLS) { }): ToolRankerFallbackDecision {
const action = fallbackActionForPolicy(params.fallbackPolicy);
const decision = decidePipelineFallback({
stage: "tool_rank",
reason: params.reason,
policies: [{
stage: "tool_rank",
onUnavailable: action,
onFailed: action,
}],
});
return { return {
toolNames: [], ...decision,
toolNames: params.fallbackPolicy === ToolRankerFallbackPolicy.NO_TOOLS
? []
: [...params.availableToolNames],
usedRanker: false, usedRanker: false,
}; };
} }
export function resolveToolRankerFallbackSelection(params: {
fallbackPolicy: ToolRankerFallbackPolicy;
availableToolNames: readonly string[];
}): ToolRankerFallbackSelection {
const decision = decideToolRankerFallback({
fallbackPolicy: params.fallbackPolicy,
availableToolNames: params.availableToolNames,
reason: "failed",
});
return { return {
toolNames: [...params.availableToolNames], toolNames: decision.toolNames,
usedRanker: false, usedRanker: decision.usedRanker,
}; };
} }
+24 -1
View File
@@ -2,7 +2,10 @@ import test from "node:test";
import assert from "node:assert/strict"; import assert from "node:assert/strict";
const {ToolRankerFallbackPolicy} = await import("../dist/common/policies.js"); const {ToolRankerFallbackPolicy} = await import("../dist/common/policies.js");
const {resolveToolRankerFallbackSelection} = await import("../dist/ai/tool-ranker-fallback.js"); const {
decideToolRankerFallback,
resolveToolRankerFallbackSelection,
} = await import("../dist/ai/tool-ranker-fallback.js");
const availableToolNames = ["read_file", "search_files"]; const availableToolNames = ["read_file", "search_files"];
@@ -32,6 +35,26 @@ test("tool ranker fallback returns all tools when policy is ALL_TOOLS", () => {
); );
}); });
test("tool ranker fallback decision uses executor semantics", () => {
assert.deepEqual(
decideToolRankerFallback({
fallbackPolicy: ToolRankerFallbackPolicy.MAIN_MODEL,
availableToolNames,
reason: "failed",
}),
{
stage: "tool_rank",
reason: "failed",
action: "use_alternate_target",
shouldContinue: true,
shouldNotifyUser: false,
shouldFailRequest: false,
toolNames: ["read_file", "search_files"],
usedRanker: false,
},
);
});
test("tool ranker fallback keeps all tools when policy is MAIN_MODEL", () => { test("tool ranker fallback keeps all tools when policy is MAIN_MODEL", () => {
assert.deepEqual( assert.deepEqual(
resolveToolRankerFallbackSelection({ resolveToolRankerFallbackSelection({