@7n/llm-lib 2.7.5 → 2.7.6

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.7.6] - 2026-07-18
4
+
5
+ ### Fixed
6
+
7
+ - `runOneShot`/`runAgentSkill` з нерозв'язаним `modelSpec` (`''`/`null` — consumer лишає вибір pi) більше не потрапляють у `chain.note()` як `model: ''`, через що `chain.mjs` мовчки класифікував їх cloud (`isLocalModel('') === false`) навіть коли pi фактично резолвив локальну модель. Тепер обидва раннери підставляють фактично резолвлену pi-модель (`session.model`, нове `formatModelSpec`), а `chain.note()` для випадків, коли резолвлена модель усе ж недоступна, веде окремий бакет `unknownCalls` замість неявного cloud.
8
+
3
9
  ## [2.7.5] - 2026-07-17
4
10
 
5
11
  ### Fixed
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import { env, stdout } from 'node:process'
21
- import { isLocalModel, resolveModel } from './model-tiers.mjs'
21
+ import { formatModelSpec, isLocalModel, resolveModel } from './model-tiers.mjs'
22
22
  import { getRegistry, resolveModelSpec } from './internal/registry.mjs'
23
23
  import { failOnMemoryGuard } from './internal/memory-guard.mjs'
24
24
  import { writeTrace } from './trace.mjs'
@@ -194,23 +194,28 @@ export async function runAgentSkill(prompt, opts = {}) {
194
194
  failOnMemoryGuard(error, prompt)
195
195
  }
196
196
 
197
+ // spec порожній/нерозв'язаний ('' → pi сам вибирає дефолт) — беремо фактично
198
+ // резолвлену pi-модель із сесії, щоб chain.note()/trace не бачили порожній
199
+ // model і не потрапляли в неявний cloud-бакет (isLocalModel('') === false).
200
+ const resolvedModel = spec || formatModelSpec(session.model)
201
+
197
202
  const telemetry = {
198
203
  skill: skillId,
199
204
  tier,
200
- model: spec || null,
205
+ model: resolvedModel,
201
206
  turnCount,
202
207
  toolCallCount,
203
208
  backstopHit,
204
209
  wallMs: clock() - startedAt
205
210
  }
206
- chain?.note({ model: spec || null, usage, error })
211
+ chain?.note({ model: resolvedModel, usage, error })
207
212
  trace({
208
213
  caller,
209
214
  backend: 'pi-ai',
210
215
  kind: 'skill',
211
216
  skill: skillId,
212
217
  tier,
213
- model: spec || null,
218
+ model: resolvedModel,
214
219
  cwd,
215
220
  turnCount,
216
221
  toolCallCount,
@@ -224,7 +229,7 @@ export async function runAgentSkill(prompt, opts = {}) {
224
229
  chainId: chain?.traceFields()?.chainId,
225
230
  caller,
226
231
  step: chain?.traceFields()?.chainStep,
227
- model: spec || null,
232
+ model: resolvedModel,
228
233
  promptHash: pHash,
229
234
  prompt,
230
235
  output: outputText,
package/lib/chain.mjs CHANGED
@@ -104,6 +104,7 @@ export function startChain({ kind, unit, cwd, meta = {}, deps = {} }) {
104
104
  let step = 0
105
105
  let localCalls = 0
106
106
  let cloudCalls = 0
107
+ let unknownCalls = 0
107
108
  let errors = 0
108
109
  let finalModel = null
109
110
  let ended = null
@@ -126,15 +127,23 @@ export function startChain({ kind, unit, cwd, meta = {}, deps = {} }) {
126
127
 
127
128
  /**
128
129
  * Раннер репортує результат кроку — акумуляція local/cloud/usage/errors.
130
+ * `call.model` falsy (нерозв'язаний modelSpec, напр. `''`/`null` — консюмер
131
+ * лишив вибір pi) — бакет `unknownCalls`, НЕ cloud: без факту резолву модель
132
+ * могла піти на локальний pi-дефолт, і мовчазний запис у cloudCalls/usageCloud
133
+ * спотворює local/cloud cost-аналітику для будь-кого, хто не передає `model`.
129
134
  * @param {{ model?: string|null, usage?: object|null, error?: string|null, stopReason?: string|null }} call результат виклику
130
135
  * @returns {void}
131
136
  */
132
137
  note(call = {}) {
133
- const local = call.model ? isLocal(call.model) : false
134
- if (local) localCalls++
135
- else cloudCalls++
138
+ if (!call.model) {
139
+ unknownCalls++
140
+ } else if (isLocal(call.model)) {
141
+ localCalls++
142
+ } else {
143
+ cloudCalls++
144
+ addUsage(usageCloud, call.usage)
145
+ }
136
146
  addUsage(usage, call.usage)
137
- if (!local) addUsage(usageCloud, call.usage)
138
147
  if (call.error) errors++
139
148
  else if (call.model) finalModel = call.model
140
149
  },
@@ -179,6 +188,7 @@ export function startChain({ kind, unit, cwd, meta = {}, deps = {} }) {
179
188
  steps: step,
180
189
  localCalls,
181
190
  cloudCalls,
191
+ unknownCalls,
182
192
  escalated: localCalls > 0 && cloudCalls > 0,
183
193
  finalModel,
184
194
  errors,
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: agent-skill.mjs
4
4
  resource: llm-lib/lib/agent-skill.mjs
5
5
  docgen:
6
- crc: 196f6fcf
6
+ crc: 085ebccf
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
package/lib/docs/chain.md CHANGED
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: chain.mjs
4
4
  resource: llm-lib/lib/chain.mjs
5
5
  docgen:
6
- crc: c9259fcb
6
+ crc: 68b416b8
7
7
  ---
8
8
 
9
9
  ## Огляд
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: model-tiers.mjs
4
4
  resource: llm-lib/lib/model-tiers.mjs
5
5
  docgen:
6
- crc: adfe6189
6
+ crc: ba67ab5c
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
@@ -3,7 +3,7 @@ type: JS Module
3
3
  title: one-shot.mjs
4
4
  resource: llm-lib/lib/one-shot.mjs
5
5
  docgen:
6
- crc: 286493bf
6
+ crc: 68779501
7
7
  model: omlx/gemma-4-e4b-it-OptiQ-4bit
8
8
  ---
9
9
 
@@ -77,6 +77,19 @@ export function parseModelId(spec) {
77
77
  return { provider: spec.slice(0, i), id: spec.slice(i + 1) }
78
78
  }
79
79
 
80
+ /**
81
+ * Форматує pi `Model`-об'єкт (`{provider, id}`) назад у `"provider/model-id"`.
82
+ * Інверсія {@link parseModelId} — застосовується до фактично резолвленої
83
+ * pi-моделі (`session.model`), коли consumer лишив `modelSpec` порожнім і pi
84
+ * сам вибрав дефолт (локальний чи хмарний).
85
+ * @param {{provider: string, id: string}|null|undefined} model pi Model-об'єкт
86
+ * @returns {string|null} `"provider/model-id"` або `null`, якщо модель відсутня
87
+ */
88
+ export function formatModelSpec(model) {
89
+ if (!model || !model.provider || !model.id) return null
90
+ return `${model.provider}/${model.id}`
91
+ }
92
+
80
93
  // ── Класифікація local vs cloud ──────────────────────────────────────────────
81
94
 
82
95
  /** Провайдери, що вважаються локальними. Override: `N_LLM_LOCAL_PROVIDERS` (кома-список). */
package/lib/one-shot.mjs CHANGED
@@ -17,7 +17,7 @@
17
17
  * ретраїти нема куди, RAM-стеля фіксована (fail-fast політика пакета).
18
18
  */
19
19
 
20
- import { isLocalModel, resolveModel } from './model-tiers.mjs'
20
+ import { formatModelSpec, isLocalModel, resolveModel } from './model-tiers.mjs'
21
21
  import { getRegistry, resolveModelSpec } from './internal/registry.mjs'
22
22
  import { failOnMemoryGuard } from './internal/memory-guard.mjs'
23
23
  import { writeTrace } from './trace.mjs'
@@ -158,12 +158,17 @@ export async function runOneShot({
158
158
  failOnMemoryGuard(promptError, userText)
159
159
  }
160
160
 
161
- chain?.note({ model: spec, usage, error: promptError, stopReason })
161
+ // spec порожній/нерозв'язаний ('' pi сам вибирає дефолт) беремо фактично
162
+ // резолвлену pi-модель із сесії, щоб chain.note()/trace не бачили порожній
163
+ // model і не потрапляли в неявний cloud-бакет (isLocalModel('') === false).
164
+ const resolvedModel = spec || formatModelSpec(session.model)
165
+
166
+ chain?.note({ model: resolvedModel, usage, error: promptError, stopReason })
162
167
  trace({
163
168
  caller,
164
169
  backend: 'pi-ai',
165
170
  kind: 'one-shot',
166
- model: spec,
171
+ model: resolvedModel,
167
172
  cwd: cwd ?? null,
168
173
  usage,
169
174
  stopReason,
@@ -175,12 +180,12 @@ export async function runOneShot({
175
180
  chainId: chain?.traceFields()?.chainId,
176
181
  caller,
177
182
  step: chain?.traceFields()?.chainStep,
178
- model: spec,
183
+ model: resolvedModel,
179
184
  promptHash: pHash,
180
185
  prompt: userText,
181
186
  output: text.trim(),
182
187
  usage,
183
188
  error: promptError
184
189
  })
185
- return { content: text.trim(), usage, error: promptError, model: spec, stopReason, caller }
190
+ return { content: text.trim(), usage, error: promptError, model: resolvedModel, stopReason, caller }
186
191
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7n/llm-lib",
3
- "version": "2.7.5",
3
+ "version": "2.7.6",
4
4
  "description": "Тонкий шар роботи з LLM (локальні omlx + хмарні провайдери) поверх pi: model tiers, one-shot, agentic-раннери, write-guard, trace, telemetry, prompt-budget",
5
5
  "keywords": [
6
6
  "nitra",