@7n/llm-lib 2.0.4 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/lib/agent-fix.mjs +91 -0
- package/lib/docs/agent-fix.md +5 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.1.1] - 2026-07-11
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- test(llm-lib): дедиковані тести prompt-budget і with-timeout
|
|
8
|
+
|
|
9
|
+
## [2.1.0] - 2026-07-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- agent-fix: evidence-гейт verify-loop (Фаза A1) — opts.verify/verifyMax, фідбек провалу у ту саму сесію, телеметрія verifyAttempts
|
|
14
|
+
|
|
3
15
|
## [2.0.4] - 2026-07-10
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/lib/agent-fix.mjs
CHANGED
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
* error, rollback }` — застосуванням володіє worker, orchestrator робить лише зовнішній
|
|
16
16
|
* verdict-recheck і за провалу кличе `rollback()` (clean-slate per rung).
|
|
17
17
|
*
|
|
18
|
+
* Evidence-гейт (дизайн 2026-07-11, Фаза A1): опційний `opts.verify` — canonical
|
|
19
|
+
* перевірка від consumer-а. Після prompt-у harness сам жене verify; провал →
|
|
20
|
+
* вивід перевірки інʼєктиться фідбеком у ТУ САМУ сесію (до `verifyMax` додаткових
|
|
21
|
+
* ітерацій, у межах загального `timeoutMs` рунга). Гейт структурний, а не
|
|
22
|
+
* model-контракт: заяви агента про успіх не важать — джерело правди лишається
|
|
23
|
+
* зовнішнім. Без `verify` поведінка попередня (один прохід).
|
|
24
|
+
*
|
|
18
25
|
* Pi вантажиться lazy (top-level import модуля pi-free). Логіка інжектована через
|
|
19
26
|
* `deps` для unit-тестів; `deps.astContext` — споживацький AST-екстрактор (напр.
|
|
20
27
|
* oxc-based у `@nitra/cursor`), без нього tool чесно відповідає «недоступний».
|
|
@@ -46,6 +53,18 @@ const TURN_CEILING = Number(env.N_LLM_FIX_TURN_CEILING ?? env.N_CURSOR_FIX_TURN_
|
|
|
46
53
|
*/
|
|
47
54
|
const DEFAULT_TIMEOUT_MS = 300_000
|
|
48
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Дефолт додаткових verify-ітерацій (фідбек у ту саму сесію) при заданому `opts.verify`.
|
|
58
|
+
* Consumer-и тюнять per tier (local — менше, cloud — більше).
|
|
59
|
+
*/
|
|
60
|
+
const VERIFY_MAX_DEFAULT = 2
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Мінімальний залишок бюджету часу рунга, з яким ще є сенс запускати verify-ітерацію
|
|
64
|
+
* (менше — фідбек-prompt майже гарантовано не встигне, чесніше зупинитись одразу).
|
|
65
|
+
*/
|
|
66
|
+
const VERIFY_MIN_BUDGET_MS = 5000
|
|
67
|
+
|
|
49
68
|
/**
|
|
50
69
|
* Порожній rollback для fail-шляхів.
|
|
51
70
|
* @returns {void}
|
|
@@ -54,6 +73,21 @@ function noop() {
|
|
|
54
73
|
/* навмисно порожньо: нема чого відкочувати */
|
|
55
74
|
}
|
|
56
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Будує фідбек-prompt verify-ітерації: точний вивід canonical-перевірки + нагадування
|
|
78
|
+
* обмежень (той самий semantic-collateral guard, що й у buildFixPrompt).
|
|
79
|
+
* @param {string} output вивід перевірки (порушення, що лишились)
|
|
80
|
+
* @returns {string} prompt для тієї самої сесії
|
|
81
|
+
*/
|
|
82
|
+
export function buildVerifyFeedbackPrompt(output) {
|
|
83
|
+
return (
|
|
84
|
+
'Перевірка правила показує, що порушення ДОСІ активне після твоїх правок:\n\n' +
|
|
85
|
+
`${output}\n\n` +
|
|
86
|
+
'Виправ залишок. Обмеження ті самі: лише механічні зміни, лише target-файли, ' +
|
|
87
|
+
'без хардкоду значень і симуляції поведінки.'
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
57
91
|
/**
|
|
58
92
|
* Маркер недоступності `astContext`.
|
|
59
93
|
* @returns {{ error: string }} повідомлення про недоступність AST facts
|
|
@@ -62,6 +96,48 @@ function astUnavailable() {
|
|
|
62
96
|
return { error: 'ast_facts недоступний: consumer не надав astContext' }
|
|
63
97
|
}
|
|
64
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Evidence-петля: canonical verify → (не ok) → фідбек у ТУ САМУ сесію, поки не ok /
|
|
101
|
+
* вичерпано `verifyMax` / вичерпано бюджет часу рунга (`timeoutMs` спільний з першим
|
|
102
|
+
* prompt-ом — нових таймерів не вводимо). Помилка самої перевірки — інфраструктурна:
|
|
103
|
+
* ітерації не палимо, чесний error. Зовнішній canonical re-detect consumer-а лишається
|
|
104
|
+
* джерелом правди — тут лише рання й точніша петля всередині рунга.
|
|
105
|
+
* @param {{ session: object, verify: (args: { touchedFiles: string[] }) => Promise<{ ok: boolean, output?: string }> | { ok: boolean, output?: string },
|
|
106
|
+
* verifyMax: number, timeoutMs: number, startedAt: number, clock: () => number,
|
|
107
|
+
* guard: { touchedFiles: () => string[] }, fixPrompt: string }} args контекст петлі.
|
|
108
|
+
* @returns {Promise<{ verifyAttempts: Array<{ ok: boolean, infra?: boolean }>, error: string|null }>} результат петлі.
|
|
109
|
+
*/
|
|
110
|
+
async function runVerifyLoop({ session, verify, verifyMax, timeoutMs, startedAt, clock, guard, fixPrompt }) {
|
|
111
|
+
const verifyAttempts = []
|
|
112
|
+
for (let attempt = 0; ; attempt++) {
|
|
113
|
+
let evidence
|
|
114
|
+
try {
|
|
115
|
+
evidence = await verify({ touchedFiles: guard.touchedFiles() })
|
|
116
|
+
} catch (verifyError) {
|
|
117
|
+
verifyAttempts.push({ ok: false, infra: true })
|
|
118
|
+
return { verifyAttempts, error: `verify: ${verifyError.message}` }
|
|
119
|
+
}
|
|
120
|
+
verifyAttempts.push({ ok: evidence?.ok === true })
|
|
121
|
+
if (evidence?.ok === true) return { verifyAttempts, error: null }
|
|
122
|
+
if (attempt >= verifyMax) {
|
|
123
|
+
return { verifyAttempts, error: `verify: порушення лишилось після ${attempt + 1} перевірок` }
|
|
124
|
+
}
|
|
125
|
+
const remainingMs = timeoutMs - (clock() - startedAt)
|
|
126
|
+
if (remainingMs < VERIFY_MIN_BUDGET_MS) {
|
|
127
|
+
return { verifyAttempts, error: 'verify: бюджет часу рунга вичерпано' }
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
await withTimeout(session.prompt(buildVerifyFeedbackPrompt(evidence?.output ?? '')), remainingMs, {
|
|
131
|
+
onTimeout: () => session.abort?.(),
|
|
132
|
+
label: 'fix-verify'
|
|
133
|
+
})
|
|
134
|
+
} catch (promptError) {
|
|
135
|
+
failOnMemoryGuard(promptError.message, fixPrompt)
|
|
136
|
+
return { verifyAttempts, error: promptError.message }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
65
141
|
/**
|
|
66
142
|
* Будує fix-промпт для рунга: правило + порушення + (опц.) target-файли + (опц.) feedback
|
|
67
143
|
* попереднього провалу + жорсткий блок обмежень (лише механічні зміни) + інструкція
|
|
@@ -172,6 +248,8 @@ async function defaultCreateSession({ registry, model, thinkingLevel, cwd, facto
|
|
|
172
248
|
* model: string, tier?: string, feedback?: object, caller?: string, timeoutMs?: number, ruleText?: string,
|
|
173
249
|
* chain?: object,
|
|
174
250
|
* targetFiles?: string[],
|
|
251
|
+
* verify?: (args: { touchedFiles: string[] }) => Promise<{ ok: boolean, output?: string }> | { ok: boolean, output?: string },
|
|
252
|
+
* verifyMax?: number,
|
|
175
253
|
* deps?: { createSession?: (args: object) => Promise<object>, getRegistry?: () => Promise<object>,
|
|
176
254
|
* registry?: object, root?: string|null,
|
|
177
255
|
* astContext?: (path: string) => object,
|
|
@@ -190,6 +268,8 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
|
|
|
190
268
|
ruleText,
|
|
191
269
|
chain = null,
|
|
192
270
|
targetFiles,
|
|
271
|
+
verify = null,
|
|
272
|
+
verifyMax = VERIFY_MAX_DEFAULT,
|
|
193
273
|
deps = {}
|
|
194
274
|
} = opts
|
|
195
275
|
const createSession = deps.createSession ?? defaultCreateSession
|
|
@@ -307,6 +387,14 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
|
|
|
307
387
|
failOnMemoryGuard(error, fixPrompt)
|
|
308
388
|
}
|
|
309
389
|
|
|
390
|
+
// Evidence-гейт: verify → (не ok) → фідбек у ТУ САМУ сесію (див. runVerifyLoop).
|
|
391
|
+
let verifyAttempts = []
|
|
392
|
+
if (verify && !error) {
|
|
393
|
+
const loop = await runVerifyLoop({ session, verify, verifyMax, timeoutMs, startedAt, clock, guard, fixPrompt })
|
|
394
|
+
verifyAttempts = loop.verifyAttempts
|
|
395
|
+
error = loop.error
|
|
396
|
+
}
|
|
397
|
+
|
|
310
398
|
const touchedFiles = guard.touchedFiles()
|
|
311
399
|
const telemetry = {
|
|
312
400
|
rule: ruleId,
|
|
@@ -318,6 +406,7 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
|
|
|
318
406
|
edits: guard.state.editLog,
|
|
319
407
|
blocks: guard.state.blocks,
|
|
320
408
|
backstopHit,
|
|
409
|
+
verifyAttempts,
|
|
321
410
|
wallMs: clock() - startedAt
|
|
322
411
|
}
|
|
323
412
|
// Usage кроку для chain-агрегатів: сума по turns агентної сесії.
|
|
@@ -348,6 +437,8 @@ export async function runAgentFix(ruleId, violation, cwd, opts = {}) {
|
|
|
348
437
|
toolCallCount,
|
|
349
438
|
touchedFiles,
|
|
350
439
|
backstopHit,
|
|
440
|
+
verifyAttempts: verifyAttempts.length,
|
|
441
|
+
verifyOk: verifyAttempts.length > 0 ? verifyAttempts.at(-1).ok : null,
|
|
351
442
|
wallMs: clock() - startedAt,
|
|
352
443
|
error,
|
|
353
444
|
...chain?.traceFields()
|
package/lib/docs/agent-fix.md
CHANGED
|
@@ -3,7 +3,7 @@ type: JS Module
|
|
|
3
3
|
title: agent-fix.mjs
|
|
4
4
|
resource: llm-lib/lib/agent-fix.mjs
|
|
5
5
|
docgen:
|
|
6
|
-
crc:
|
|
6
|
+
crc: 0f2be7f8
|
|
7
7
|
model: omlx/gemma-4-e4b-it-OptiQ-4bit
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -18,10 +18,13 @@ buildFixPrompt готує текстовий промпт, що містить
|
|
|
18
18
|
runAgentFix виконує повний агентний цикл для спроби виправлення порушення правила, включаючи взаємодію з інструментами, застосування патча та фіксацію телеметрії.
|
|
19
19
|
Виклик сесії огорнутий timeout-гонкою: `opts.timeoutMs` (дефолт 300s, коли consumer не передав значення) на спрацюванні abort-ить сесію і повертає помилку `fix timeout …` — зависла LLM-сесія (напр. мертва SSE) не блокує виклик назавжди.
|
|
20
20
|
|
|
21
|
+
Evidence-гейт (опційний `opts.verify`, Фаза A1 run-harness): після prompt-у модуль сам запускає canonical-перевірку consumer-а; провал інʼєктиться фідбеком у ту саму сесію — до `opts.verifyMax` додаткових ітерацій (дефолт 2), у межах того самого `timeoutMs` (залишок бюджету < 5s — чесна зупинка без ітерації). Гейт структурний: заяви агента про успіх не важать, джерелом правди лишається зовнішня перевірка. Помилка самої перевірки — інфраструктурна: ітерації не витрачаються, повертається `error` з префіксом `verify:`. Без `verify` — поведінка попередня (один прохід). Спроби фіксуються у `telemetry.verifyAttempts` і у trace (`verifyAttempts`, `verifyOk`).
|
|
22
|
+
|
|
21
23
|
## Публічний API
|
|
22
24
|
|
|
23
25
|
buildFixPrompt — формує інструкцію для виправлення проблеми, включаючи відповідне правило, описане порушення, опційний перелік target-файлів (єдині наявні файли, які дозволено редагувати) та можливий відгук з попередньої невдалої спроби; містить обовʼязковий блок обмежень semantic-collateral guard (лише механічні зміни: без зміни бізнес-логіки, без хардкоду значень, без симуляції поведінки — spec pi-migration §12, addendum 2026-07-05) і директиву щодо кроків перед редагуванням та самоперевірки.
|
|
24
|
-
|
|
26
|
+
buildVerifyFeedbackPrompt — формує фідбек-повідомлення verify-ітерації: точний вивід canonical-перевірки + нагадування обмежень semantic-collateral guard.
|
|
27
|
+
runAgentFix — виконує єдину спробу виправлення коду з боку агента для заданого правила, використовуючи вказані параметри (модель, рівень, зворотний зв'язок, verify-гейт, тощо) та ін'єкції для тестування.
|
|
25
28
|
|
|
26
29
|
## Гарантії поведінки
|
|
27
30
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7n/llm-lib",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Тонкий шар роботи з LLM (локальні omlx + хмарні провайдери) поверх pi: model tiers, one-shot, agentic-раннери, write-guard, trace, telemetry, prompt-budget",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nitra",
|