feat(m3-d): контракты + look-ahead в промпт роутера + проброс runRouter (фикс-2)
This commit is contained in:
+11
-4
@@ -81,20 +81,27 @@ export function groundTrace(trace, graph) {
|
||||
* L-ядро (L1/L3/L4/L5/L7) + схема слотов трассы 5.1 + ПОЛНЫЙ граф+каталог (6.2-исключение:
|
||||
* в system для кэша). user: задача (+план/прогресс) — волатильное.
|
||||
*/
|
||||
export function buildRouterPrompt({ prompt = '', plan = null, graph = null, catalog = null } = {}) {
|
||||
export function buildRouterPrompt({ prompt = '', plan = null, graph = null, catalog = null, contracts = [] } = {}) {
|
||||
const nodesForCtx = (catalog && catalog.nodes) || (graph && graph.nodes) || [];
|
||||
const catalogText = nodesForCtx.map((n) => `${n.id || ''} ${n.slug || ''} — ${n.capabilities || n.name || ''}`).join('\n');
|
||||
const cs = Array.isArray(contracts) ? contracts : [];
|
||||
const lookAheadText = cs.map((ct) =>
|
||||
`${ct.skill || '(?)'}: нужды[${(ct.needs || []).join(', ')}] ключевые-решения[${(ct['key-decisions'] || []).join(', ')}] критерии[${(ct['acceptance-criteria'] || []).join(', ')}]`,
|
||||
).join('\n');
|
||||
const system = [
|
||||
'Ты — РОУТЕР-НАСТАВНИК. Выбираешь навык по СМЫСЛУ задачи рассуждением (ключевые слова сняты).',
|
||||
'МЕТОД (вшитый брейнсторм): придумай 2–3 варианта-кандидата с разных углов графа → поспорь с собой (почему не сосед-близнец? а если я неправ?) → выбери лучший с обоснованием.',
|
||||
'НЮХ (5.3): размытый/общий шаг («сделай красиво», «настрой») — ПОДОЗРИТЕЛЬНО, не глотай молча.',
|
||||
'ИНТЕРВЬЮЕР (4.4): на размытом/неоднозначном — поставь низкую уверенность и сформулируй уточняющий вопрос (переспрос), не угадывай.',
|
||||
'LOOK-AHEAD: ДО выбора посмотри нужды/ключевые-решения/критерии вызываемых навыков (раздел КОНТРАКТЫ ниже, если есть) и вытащи их вопросы ВПЕРЁД — заложи решения в план заранее.',
|
||||
'L-ядро: L1 предложи дешёвый образец до полной стройки; L3 давай разумный дефолт-на-вето вместо открытого вопроса; L4 порядок решений по влиянию (фундамент раньше мелочи); L5 ограничения как пре-мортем; L7 объяви критерий приёмки заранее.',
|
||||
'ВЫВОД — строго JSON-трасса со слотами: candidates (массив реальных узлов графа), chosen, why_chosen, twins_considered (почему не близнец), confidence (0..1). Пустой слот недопустим.',
|
||||
'Кандидаты ОБЯЗАНЫ быть реальными узлами из каталога ниже — не выдумывай.',
|
||||
'--- ГРАФ+КАТАЛОГ УЗЛОВ (100%) ---',
|
||||
catalogText,
|
||||
].join('\n');
|
||||
cs.length ? '--- КОНТРАКТЫ ВЫЗЫВАЕМЫХ НАВЫКОВ (look-ahead) ---' : '',
|
||||
cs.length ? lookAheadText : '',
|
||||
].filter(Boolean).join('\n');
|
||||
const user = [
|
||||
`ЗАДАЧА: ${prompt}`,
|
||||
plan ? `ПЛАН (прогресс): ${typeof plan === 'string' ? plan : JSON.stringify(plan)}` : '',
|
||||
@@ -116,9 +123,9 @@ export function parseRouterResponse(text) {
|
||||
* распарсить → МЕХАНИЧЕСКИ провалидировать трассу (слоты) + заземлить кандидатов (ОВ-Д2) →
|
||||
* воздержание 5.2 при низкой уверенности. Сам выбор — у модели; всё вокруг — механика.
|
||||
*/
|
||||
export async function runRouter({ prompt, plan = null, graph, catalog = null, llmCall, confidenceThreshold = 0.5 }) {
|
||||
export async function runRouter({ prompt, plan = null, graph, catalog = null, contracts = [], llmCall, confidenceThreshold = 0.5 }) {
|
||||
let trace;
|
||||
try { trace = await llmCall({ buildPrompt: () => buildRouterPrompt({ prompt, plan, graph, catalog }) }); }
|
||||
try { trace = await llmCall({ buildPrompt: () => buildRouterPrompt({ prompt, plan, graph, catalog, contracts }) }); }
|
||||
catch { return { ok: false, reason: 'сбой вызова модели', trace: null }; }
|
||||
if (typeof trace === 'string') trace = parseRouterResponse(trace);
|
||||
if (!trace) return { ok: false, reason: 'пустой/неразборный ответ роутера', trace: null };
|
||||
|
||||
@@ -118,6 +118,19 @@ describe('buildRouterPrompt (чистая, {system,user}; граф+катало
|
||||
expect(system).toMatch(/нюх|5\.3|размыт/i);
|
||||
expect(system).toMatch(/4\.4|уточня|переспрос/i);
|
||||
});
|
||||
it('фикс-2: контракты в промпте + look-ahead (нужды/решения вызываемых навыков вперёд)', () => {
|
||||
const contracts = [{ skill: 'writing-plans', needs: ['spec'], 'key-decisions': ['file structure'], 'acceptance-criteria': ['each step 2-5 min'] }];
|
||||
const { system } = buildRouterPrompt({ prompt: 'add feature', graph: GRAPH, catalog: CATALOG, contracts });
|
||||
expect(system).toMatch(/look-ahead|вперёд|вызываемых/i);
|
||||
expect(system).toMatch(/spec/);
|
||||
expect(system).toMatch(/file structure/);
|
||||
});
|
||||
it('фикс-2: без контрактов — раздел КОНТРАКТЫ не добавляется (детерминизм сохранён)', () => {
|
||||
const a = buildRouterPrompt({ prompt: 'x', graph: GRAPH, catalog: CATALOG });
|
||||
const b = buildRouterPrompt({ prompt: 'x', graph: GRAPH, catalog: CATALOG, contracts: [] });
|
||||
expect(a.system).toBe(b.system);
|
||||
expect(a.system).not.toMatch(/КОНТРАКТЫ ВЫЗЫВАЕМЫХ/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseRouterResponse', () => {
|
||||
|
||||
Reference in New Issue
Block a user