zorya-classifier 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/NOTICE.md +11 -0
- package/README.md +127 -2
- package/dist/app/cli-route.d.ts +3 -0
- package/dist/app/cli-route.js +40 -0
- package/dist/app/create-classifier.d.ts +14 -0
- package/dist/app/create-classifier.js +100 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +33 -0
- package/dist/entities/contracts.d.ts +395 -0
- package/dist/entities/contracts.js +90 -0
- package/dist/entities/errors.d.ts +7 -0
- package/dist/entities/errors.js +16 -0
- package/dist/features/choice-beam-layout.d.ts +18 -0
- package/dist/features/choice-beam-layout.js +136 -0
- package/dist/features/choice.d.ts +4 -0
- package/dist/features/choice.js +83 -0
- package/dist/features/prompts.d.ts +8 -0
- package/dist/features/prompts.js +56 -0
- package/dist/features/uniform-grid.d.ts +3 -0
- package/dist/features/uniform-grid.js +35 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/infrastructure/openrouter-common.d.ts +130 -0
- package/dist/infrastructure/openrouter-common.js +317 -0
- package/dist/infrastructure/openrouter-vision.d.ts +8 -0
- package/dist/infrastructure/openrouter-vision.js +12 -0
- package/dist/infrastructure/openrouter.d.ts +19 -0
- package/dist/infrastructure/openrouter.js +207 -0
- package/dist/infrastructure/raster.d.ts +2 -0
- package/dist/infrastructure/raster.js +121 -0
- package/dist/infrastructure/vision-chat.d.ts +64 -0
- package/dist/infrastructure/vision-chat.js +309 -0
- package/dist/openai-compatible.d.ts +5 -0
- package/dist/openai-compatible.js +7 -0
- package/dist/openrouter.d.ts +4 -0
- package/dist/openrouter.js +2 -0
- package/dist/processes/classify.d.ts +2 -0
- package/dist/processes/classify.js +21 -0
- package/dist/processes/locate.d.ts +2 -0
- package/dist/processes/locate.js +201 -0
- package/dist/shared/choice-mass.d.ts +2 -0
- package/dist/shared/choice-mass.js +19 -0
- package/dist/shared/gate.d.ts +11 -0
- package/dist/shared/gate.js +52 -0
- package/dist/shared/scope.d.ts +12 -0
- package/dist/shared/scope.js +29 -0
- package/docs/README.ru.md +245 -0
- package/docs/acceptance.md +31 -0
- package/docs/api.md +45 -0
- package/docs/architecture.md +32 -0
- package/docs/benchmarks.md +53 -0
- package/docs/evidence/current-linux-check.json +15 -0
- package/docs/evidence/decisions-completion-v1-manifest.json +96 -0
- package/docs/evidence/decisions-completion-v1-report.json +1205 -0
- package/docs/evidence/decisions-selection-v1-manifest.json +96 -0
- package/docs/evidence/decisions-selection-v1-report.json +1457 -0
- package/docs/evidence/jev-pricing-2026-10-18.json +69 -0
- package/docs/evidence/linux-check.json +20 -0
- package/docs/evidence/linux-check.txt +205 -0
- package/docs/evidence/live-release-smoke-manifest.json +82 -0
- package/docs/evidence/live-release-smoke-provenance.json +9 -0
- package/docs/evidence/live-release-smoke-report.json +2136 -0
- package/docs/evidence/live-v1-manifest.json +84 -0
- package/docs/evidence/live-v1-provenance.json +9 -0
- package/docs/evidence/live-v1-report.json +15555 -0
- package/docs/evidence/luna-decisions-fine-probe.json +19 -0
- package/docs/evidence/luna-decisions-locator-smoke-manifest.json +39 -0
- package/docs/evidence/luna-decisions-locator-smoke-report.json +149 -0
- package/docs/evidence/pack-check.json +26 -0
- package/docs/evidence/protocol-probes.json +61 -0
- package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-01.json +42 -0
- package/docs/evidence/rejected-vision-selection-v1-qwen-photo-dev-17.json +42 -0
- package/docs/evidence/rejected-vision-selection-v1-qwen-photo-heldout-09.json +42 -0
- package/docs/evidence/rejected-vision-selection-v2-haiku-photo-dev-16.json +42 -0
- package/docs/evidence/rejected-vision-selection-v2-haiku-support-heldout-016.json +42 -0
- package/docs/evidence/vision-cost-reconciliation.json +37 -0
- package/docs/evidence/vision-model-summary.json +562 -0
- package/docs/evidence/vision-selection-v1-manifest.json +95 -0
- package/docs/evidence/vision-selection-v1-report.json +4598 -0
- package/docs/evidence/vision-selection-v2-manifest.json +95 -0
- package/docs/evidence/vision-selection-v2-report.json +1139 -0
- package/docs/evidence/windows-native.json +73 -0
- package/docs/evidence/windows-secrets.json +46 -0
- package/docs/operations.md +15 -0
- package/docs/plan.md +25 -0
- package/docs/provenance.json +32 -0
- package/docs/publishing.md +21 -0
- package/docs/results.md +69 -0
- package/docs/vision-results.md +51 -0
- package/package.json +89 -4
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ClassifierError, assertActive } from '../entities/errors.js';
|
|
2
|
+
export class OperationScope {
|
|
3
|
+
parent;
|
|
4
|
+
controller = new AbortController();
|
|
5
|
+
signal = this.controller.signal;
|
|
6
|
+
startedAt = performance.now();
|
|
7
|
+
timer;
|
|
8
|
+
onAbort;
|
|
9
|
+
constructor(timeoutMs, parent) {
|
|
10
|
+
this.parent = parent;
|
|
11
|
+
this.onAbort = () => this.controller.abort(new ClassifierError('ABORTED', 'Operation cancelled'));
|
|
12
|
+
if (parent?.aborted)
|
|
13
|
+
this.onAbort();
|
|
14
|
+
else
|
|
15
|
+
parent?.addEventListener('abort', this.onAbort, { once: true });
|
|
16
|
+
this.timer = setTimeout(() => this.controller.abort(new ClassifierError('TIMEOUT', 'Operation deadline exceeded')), timeoutMs);
|
|
17
|
+
this.timer.unref();
|
|
18
|
+
}
|
|
19
|
+
guard() {
|
|
20
|
+
assertActive(this.signal);
|
|
21
|
+
}
|
|
22
|
+
abort() {
|
|
23
|
+
this.onAbort();
|
|
24
|
+
}
|
|
25
|
+
dispose() {
|
|
26
|
+
clearTimeout(this.timer);
|
|
27
|
+
this.parent?.removeEventListener('abort', this.onAbort);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Zorya Classifier: руководство
|
|
2
|
+
|
|
3
|
+
Zorya Classifier — библиотека для Node.js и TypeScript, которая помогает приложению принимать решения по тексту и изображениям. Вы задаёте допустимые классы или описываете искомый объект, библиотека передаёт их выбранной модели решений или языковой модели и проверяет структурированный ответ. Основной вариант — decision-модель, в примерах Luna Decisions. Обычные vision-модели без нативной классификации тоже поддерживаются через чат-адаптер. Код распространяется по MIT; веса модели в поставку не входят, запросы удалённых моделей оплачиваются отдельно; можно подключить и свой локальный сервер. Любая vision-модель, которая понимает изображения и выполняет контракт ответа, может использоваться через готовый OpenAI-совместимый адаптер или собственный `DecisionPort`. Точная проверенная совместимость и результаты перечислены в [таблице моделей](vision-results.md).
|
|
4
|
+
|
|
5
|
+
Это самостоятельная библиотека: ей не нужны Zorya Games, Electron, база данных или игровой процесс. Её можно встроить в сервер, бота, инструмент обработки фотографий или собственный HTTP-сервис. Готовый постоянно работающий HTTP-сервер в пакет не входит.
|
|
6
|
+
|
|
7
|
+
## Что умеет
|
|
8
|
+
|
|
9
|
+
| Задача | API | Результат |
|
|
10
|
+
| ---------------------------------------------------------- | ----------------------------------- | --------------------------------------------------------------------------------- |
|
|
11
|
+
| Распределить обращения по отделам, определить намерение | `classify` с текстом | Один из заданных классов и ранжирование вариантов |
|
|
12
|
+
| Проверить наличие объекта или выбрать категорию фотографии | `classify` с изображением | Один из заданных классов |
|
|
13
|
+
| Оценить изображение с текстовым условием | `classify` с текстом и изображением | Класс с учётом обоих входов |
|
|
14
|
+
| Найти примерную область предмета, значка или указателя | `locate` | Ячейка в координатах исходного изображения либо явная неопределённость/отсутствие |
|
|
15
|
+
|
|
16
|
+
`classify` делает один запрос к выбранному API модели. `locate` делает до трёх последовательных запросов: крупная сетка → уточнение в четырёх выбранных областях → сравнение четырёх увеличенных кандидатов. Проверки присутствия входят в те же запросы. Модель выбирает варианты, а координаты вычисляет код.
|
|
17
|
+
|
|
18
|
+
Библиотека не генерирует описание сцены и не ищет все экземпляры предмета: если подходящих объектов несколько, `locate` возвращает один кандидат. Выбранная ячейка может содержать только часть объекта; это не маска сегментации, не точная рамка и не доказанный центр. Дополнительный алгоритм уточнения можно подключить через `RefinementPort`.
|
|
19
|
+
|
|
20
|
+
## Установка и ключ
|
|
21
|
+
|
|
22
|
+
Нужен Node.js 22.12 или новее. Windows и Linux проверены; macOS отдельно не проверялась. Зависимости исполнения — Zod и Sharp, включая подходящий платформе нативный модуль Sharp.
|
|
23
|
+
|
|
24
|
+
Исходники: [zoomplus/zorya-classifier](https://github.com/zoomplus/zorya-classifier). Пакет: [zorya-classifier в npm](https://www.npmjs.com/package/zorya-classifier). Установка из реестра:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npm install zorya-classifier
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Для локального/релизного архива также работает `npm install ./zorya-classifier-0.1.0.tgz`.
|
|
31
|
+
|
|
32
|
+
Скопируйте пример конфигурации в локальный `.env` и заполните значение `OPENROUTER_API_KEY` своим ключом. В репозитории `.env` исключён из Git; в своём проекте тоже добавьте это исключение. Не помещайте ключ в браузерный React-код, приложение клиента или публичный репозиторий. Вызовы модели выполняйте на доверенном сервере.
|
|
33
|
+
|
|
34
|
+
Создайте `example.mjs` и запустите:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
node --env-file=.env example.mjs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Эта команда работает и в PowerShell, и в Linux shell. Вместо файла можно передать переменную окружения средствами вашего сервера/менеджера секретов. Библиотека сама `.env` не читает; флаг обрабатывает Node.js. Ключ требуется только реальному адаптеру; обычные тесты работают без него.
|
|
41
|
+
|
|
42
|
+
## Первый пример: классификация текста
|
|
43
|
+
|
|
44
|
+
Полный файл `example.mjs`:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import { createClassifier } from 'zorya-classifier'
|
|
48
|
+
import { createOpenRouterDecisions } from 'zorya-classifier/openrouter'
|
|
49
|
+
|
|
50
|
+
const apiKey = process.env.OPENROUTER_API_KEY
|
|
51
|
+
if (!apiKey) throw new Error('Заполните OPENROUTER_API_KEY')
|
|
52
|
+
|
|
53
|
+
const client = createClassifier({
|
|
54
|
+
decision: createOpenRouterDecisions({
|
|
55
|
+
apiKey,
|
|
56
|
+
model: 'openai/gpt-6-luna-decisions',
|
|
57
|
+
provider: 'openai',
|
|
58
|
+
}),
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const result = await client.classify({
|
|
63
|
+
input: 'Заказ ещё не приехал. Где посмотреть доставку?',
|
|
64
|
+
classes: [
|
|
65
|
+
{ id: 'delivery', description: 'Вопрос о доставке или отслеживании заказа' },
|
|
66
|
+
{ id: 'refund', description: 'Просьба вернуть деньги или товар' },
|
|
67
|
+
{ id: 'other', description: 'Любое другое обращение, не относящееся к доставке и возврату' },
|
|
68
|
+
],
|
|
69
|
+
})
|
|
70
|
+
console.log(result.choice)
|
|
71
|
+
console.log(result.probabilities)
|
|
72
|
+
console.log(result.metadata)
|
|
73
|
+
} finally {
|
|
74
|
+
await client.dispose()
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Передавайте от 2 до 255 классов с уникальными `id`. Описывайте различия между ними явно. Если ни один предметный класс может не подойти, добавьте `other` или `none` с точным условием выбора: скрытого универсального класса «не знаю» здесь нет.
|
|
79
|
+
|
|
80
|
+
`choice` — выбранный класс. В основном native-режиме возвращается настоящее распределение `probabilities` провайдера: выбирается максимум, при равенстве — первый заданный класс, без общего порога0.8. Для обычной языковой модели `probabilities` и `nativeChoice` равны `null`: баллы не выдумываются из порядка вариантов. Ответ обязан содержать только допустимые уникальные ID; некорректный JSON, лишние вопросы, отказ и обрезанный ответ считаются ошибкой. Правильная форма JSON ещё не доказывает фактическую правильность классификации.
|
|
81
|
+
|
|
82
|
+
Для обычных языковых моделей используйте `createOpenRouterVision` с явными `model`/`provider`, например `openai/gpt-6-luna` и `openai`. Он работает через chat completions. Для классификации нужен один ID, для двух пространственных этапов — четыре упорядоченных кандидата. Нативный Jev-адаптер сохранён отдельно для совместимости и исторических сравнений; автоматического перехода на него нет.
|
|
83
|
+
|
|
84
|
+
## Классификация изображения
|
|
85
|
+
|
|
86
|
+
Внутри того же `try` можно передать изображение и дополнительный текст. Добавьте `readFile` к импортам:
|
|
87
|
+
|
|
88
|
+
```js
|
|
89
|
+
import { readFile } from 'node:fs/promises'
|
|
90
|
+
|
|
91
|
+
const result = await client.classify({
|
|
92
|
+
input: {
|
|
93
|
+
image: await readFile('photo.jpg'),
|
|
94
|
+
text: 'Есть ли на фотографии велосипед, включая частично видимый?',
|
|
95
|
+
},
|
|
96
|
+
classes: [
|
|
97
|
+
{ id: 'present', description: 'На изображении виден хотя бы один велосипед' },
|
|
98
|
+
{ id: 'absent', description: 'На изображении нет видимого велосипеда' },
|
|
99
|
+
],
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Для одной картинки поле `text` можно опустить. Принимаются `Uint8Array` и `Buffer` с одним PNG/JPEG/WebP: до 16 MiB и 16 777 216 декодированных пикселей. Ссылки на изображения публичный API не загружает — скачивание, права доступа и ограничения внешних URL остаются у вызывающего приложения.
|
|
104
|
+
|
|
105
|
+
## Поиск объекта и координаты
|
|
106
|
+
|
|
107
|
+
Также внутри `try`:
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
const result = await client.locate({
|
|
111
|
+
image: await readFile('photo.jpg'),
|
|
112
|
+
target: 'красный рюкзак на стуле',
|
|
113
|
+
exclusions: ['сумка на полу'],
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
if (result.status === 'located') {
|
|
117
|
+
console.log(result.region) // left, top, width, height: пиксели
|
|
118
|
+
console.log(result.normalizedRegion) // те же поля в долях размеров изображения
|
|
119
|
+
console.log(result.metadata.image) // размеры и система координат
|
|
120
|
+
} else {
|
|
121
|
+
console.log(result.status, result.reason)
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Начало координат — левый верхний угол изображения после применения его EXIF-ориентации. X растёт вправо, Y вниз. `region` включает левую/верхнюю границы и не включает правую/нижнюю. Для экранного оверлея сначала учитывайте ориентацию, масштаб отображения и возможные поля вокруг картинки. Не переносите координаты на следующий кадр без проверки: библиотека не отслеживает объект во времени.
|
|
126
|
+
|
|
127
|
+
`roi: { left, top, width, height }` ограничивает поиск прямоугольником в целых пикселях того же ориентированного исходника. Координаты ответа остаются относительно всего исходника, а не относительно обрезанного фрагмента. Указывайте ROI только если он находится внутри реальных размеров изображения.
|
|
128
|
+
|
|
129
|
+
| Статус | Что означает | Как обрабатывать |
|
|
130
|
+
| ---------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
131
|
+
| `located` | Выбран кандидат в сетке | Показать область с учётом вероятностного характера результата |
|
|
132
|
+
| `not-found` | Проверка присутствия отвергла объект в анализируемой области | Не переносить этот вывод на область за пределами ROI |
|
|
133
|
+
| `unknown` | Неуверенность, противоречие, отсутствие цели среди кандидатов или невозможная сетка | Уточнить цель, взять более подходящее изображение или вернуть пользователю неопределённость |
|
|
134
|
+
| Исключение `ClassifierError` | Ошибка входа, сети, протокола, бюджета или жизненного цикла | Обработать код ошибки; не показывать её как отсутствие объекта |
|
|
135
|
+
|
|
136
|
+
Небольшие значки, перекрытия, похожие предметы и сложные карты требуют собственной проверки на ваших данных. Предметные фотографии не доказывают точность на игровых мини-картах. Если нужен центр объекта или маска, это отдельный этап уточнения.
|
|
137
|
+
|
|
138
|
+
## Отмена, нагрузка и ошибки
|
|
139
|
+
|
|
140
|
+
Переиспользуйте один `client` на жизненный цикл сервиса. По умолчанию одновременно выполняются две операции, в очереди допускается 16, общий тайм-аут — 45 секунд, включая ожидание очереди и обработку картинки. Параметры можно задать в `createClassifier`; максимумы — 4 операции, 32 ожидающих и 120 секунд. При переполнении возвращается `CAPACITY`, вместо неограниченного накопления задач.
|
|
141
|
+
|
|
142
|
+
Для отмены передайте `signal`. Например, этот вызов завершается не позднее тайм-аута сигнала с учётом физического освобождения нативных ресурсов:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const result = await client.classify({
|
|
146
|
+
input: 'Нужна помощь со входом',
|
|
147
|
+
classes: [
|
|
148
|
+
{ id: 'support', description: 'Техническая проблема' },
|
|
149
|
+
{ id: 'other', description: 'Другая тема' },
|
|
150
|
+
],
|
|
151
|
+
signal: AbortSignal.timeout(10_000),
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Для ручной отмены создайте `AbortController`, передайте `controller.signal`, вызовите `controller.abort()` при закрытии запроса пользователя. `await client.dispose()` отменяет активные/ожидающие операции и ждёт фактического завершения принадлежащих клиенту работ. Отмена уже отправленного HTTP-запроса не гарантирует отмену тарификации у провайдера.
|
|
156
|
+
|
|
157
|
+
Обрабатывайте `ClassifierError` через экспорт библиотеки:
|
|
158
|
+
|
|
159
|
+
```js
|
|
160
|
+
import { ClassifierError } from 'zorya-classifier'
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
// await client.classify(...) или await client.locate(...)
|
|
164
|
+
} catch (error) {
|
|
165
|
+
if (error instanceof ClassifierError) {
|
|
166
|
+
console.error(error.code, error.message)
|
|
167
|
+
} else {
|
|
168
|
+
throw error
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Коды включают `INVALID_INPUT`, `INVALID_RESPONSE`, `TIMEOUT`, `ABORTED`, `CAPACITY`, `DISPOSED`, `PROVIDER`, `AUTHENTICATION`, `UNSUPPORTED`, `RESOURCE_LIMIT`. Автоматических повторов, замены модели и провайдера нет. Собственный повтор учитывайте как новый платный запрос; `locate` к моменту ошибки уже мог выполнить предыдущие стадии.
|
|
174
|
+
|
|
175
|
+
## Командная строка
|
|
176
|
+
|
|
177
|
+
CLI требует явный `ZORYA_MODEL`; по умолчанию используется `ZORYA_MODE=decisions`. Для обычной языковой модели или прямого чат-сервера установите `ZORYA_MODE=chat`. Для OpenRouter задайте также `OPENROUTER_API_KEY` и `ZORYA_PROVIDER`. Для прямого API или локального сервера задайте полный `ZORYA_ENDPOINT` и при необходимости отдельный `ZORYA_API_KEY`; ключ OpenRouter туда не передаётся. Необязательные `ZORYA_REASONING_EFFORT` и `ZORYA_RESPONSE_FORMAT` используйте только при поддержке сервером. Например, для основного маршрута Luna Decisions:
|
|
178
|
+
|
|
179
|
+
```sh
|
|
180
|
+
export ZORYA_MODE=decisions
|
|
181
|
+
export ZORYA_MODEL=openai/gpt-6-luna-decisions
|
|
182
|
+
export ZORYA_PROVIDER=openai
|
|
183
|
+
npx --no-install zorya-classifier --help
|
|
184
|
+
npx --no-install zorya-classifier classify-text "Где мой заказ?" classes.json
|
|
185
|
+
npx --no-install zorya-classifier classify-image photo.jpg classes.json
|
|
186
|
+
npx --no-install zorya-classifier locate photo.jpg "красный рюкзак"
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`classes.json` — обычный массив:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
[
|
|
193
|
+
{ "id": "delivery", "description": "Вопрос о доставке" },
|
|
194
|
+
{ "id": "other", "description": "Другая тема" }
|
|
195
|
+
]
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Для загрузки `.env` без дополнительных зависимостей:
|
|
199
|
+
|
|
200
|
+
```sh
|
|
201
|
+
node --env-file=.env node_modules/zorya-classifier/dist/cli.js locate photo.jpg "красный рюкзак"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
CLI выводит JSON и обращается к реальному платному провайдеру. `--help` запросов не делает.
|
|
205
|
+
|
|
206
|
+
## Провайдеры и проверенные модели
|
|
207
|
+
|
|
208
|
+
OpenRouter — один из способов подключения. Для прямого провайдера или локального сервера:
|
|
209
|
+
|
|
210
|
+
```js
|
|
211
|
+
import { createOpenAICompatibleVision } from 'zorya-classifier/openai-compatible'
|
|
212
|
+
|
|
213
|
+
const decision = createOpenAICompatibleVision({
|
|
214
|
+
endpoint: 'http://localhost:8000/v1/chat/completions',
|
|
215
|
+
model: 'your-deployed-vision-model',
|
|
216
|
+
})
|
|
217
|
+
const client = createClassifier({ decision })
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Укажите настоящий ID развёрнутой модели и полный URL доверенного сервера. Для облачного API можно передать `apiKey`, а для другого способа авторизации — `headers`. Для API с другим протоколом реализуйте экспортируемый `DecisionPort`. Ключ OpenRouter не подставляется автоматически в запрос к вашему серверу. Локальный пример показывает настройку подключения; он не означает, что конкретная модель или сервер были проверены в реальном прогоне.
|
|
221
|
+
|
|
222
|
+
Для `classify` с одной картинкой достаточно поддержки одной картинки. Для трёхэтапного `locate` модель/сервер должны принимать несколько изображений в одном запросе, максимум девять. Поддержка JSON Schema и JSON-object опциональна: по умолчанию схема описывается в промпте, а ответ проверяется кодом. Модель без нативной классификации подходит; модель, которая не выполняет контракт ответа, вернёт явную ошибку.
|
|
223
|
+
|
|
224
|
+
[Таблица проверенных моделей](vision-results.md) содержит названия, провайдеров, число примеров, ошибки, точность, задержки и цену. Там отдельно указаны полноценная проверка на корпусе, короткий screening и тесты адаптера с искусственным сервером. Необозначенная модель не считается автоматически проверенной.
|
|
225
|
+
|
|
226
|
+
Исторический Jev-прогон дал 55/60 на фото и 30/30 на авторских текстовых примерах. Это baseline прежнего адаптера, а не доказательство качества другой модели. Подробности и исходные ошибки сохранены в [исторических результатах](results.md). Локализация остаётся экспериментальной; ячейка не является точной маской или центром объекта.
|
|
227
|
+
|
|
228
|
+
## Разработка и следующие документы
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
npm ci --ignore-scripts
|
|
232
|
+
npm run prepare
|
|
233
|
+
npm run check
|
|
234
|
+
npm run pack:check
|
|
235
|
+
npm run format:check
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`check` включает секреты, TypeScript, ESLint, границы слоёв, неиспользуемый код, тесты и сборку. Husky проверяет добавленные в Git версии файлов. `pack:check` устанавливает архив в независимый проект, проверяет его файлы и API. Эти проверки не вызывают реальную модель.
|
|
239
|
+
|
|
240
|
+
- [Полный контракт API](api.md): типы, лимиты, формат метаданных, округление баллов.
|
|
241
|
+
- [Архитектура](architecture.md): слои, порты, отмена и владение ресурсами.
|
|
242
|
+
- [Эксплуатация](operations.md): интеграция в сервис, расходы и завершение работы.
|
|
243
|
+
- [Воспроизведение benchmark](benchmarks.md): лицензированные фотографии и отдельный запуск платных проверок.
|
|
244
|
+
- [Безопасность](../SECURITY.md): ключи, Git, npm-архивы и ограничения сканирования.
|
|
245
|
+
- [Подготовка публикации](publishing.md): GitHub/npm и необходимая авторизация владельца.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Acceptance: standalone0.1.0 preview
|
|
2
|
+
|
|
3
|
+
This acceptance concerns the extracted MIT Node.js library, not the Zorya desktop application, installer, microphone or game session. Native Luna Decisions is the primary documented route; ordinary vision chat, direct OpenAI-compatible servers and custom DecisionPort implementations remain alternatives. Jev is optional historical compatibility.
|
|
4
|
+
|
|
5
|
+
## Executed checks and release gates
|
|
6
|
+
|
|
7
|
+
| Check | Evidence |
|
|
8
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| Linux source quality | Secret scan, TypeScript, strict ESLint, boundaries, dead-code,187 offline tests and build pass. Current local receipts are preserved with the release. |
|
|
10
|
+
| Installed archive | Independent consumer compiles exported declarations and exercises native Sharp, fixture classification/three-stage localization, native Decisions adapter and ordinary direct adapter. Actual installed bytes are compared with the reviewed archive and scanned. |
|
|
11
|
+
| Hosted Windows/Linux | The [CI matrix](https://github.com/zoomplus/zorya-classifier/actions) runs Node22/24 on both operating systems. Publication requires all four jobs green for the release source; the external release manifest records the exact run/commit and results. |
|
|
12
|
+
| Native classifier | 60 licensed photos:55 correct;40 heldout:39 correct;30 authored texts:30 correct; zero classification transport/protocol errors. [Exact model evidence](vision-results.md). |
|
|
13
|
+
| Other ordinary models | Full Luna and Qwen comparisons, Nex development screening and stopped Haiku photo batch are preserved, including errors and unstarted cases. |
|
|
14
|
+
| Lifecycle | Offline regressions cover input ownership, deadlines, queue saturation, late completion, cancellation/reader cleanup,1000 operation/disposal cycles and strict rank/probability interpretation. |
|
|
15
|
+
| Credentials/archive | Staged-blob/worktree scans, independent redacted Gitleaks and actual archive checks run before publication. Results and artifact hashes belong to the external release manifest. |
|
|
16
|
+
|
|
17
|
+
The [first hosted CI run](https://github.com/zoomplus/zorya-classifier/actions/runs/39074647509) is retained as a failure: Linux passed; Windows exposed an8.3 temporary-directory alias mismatch in the declaration-isolation check. The check now canonicalizes with native filesystem realpaths; allowed declaration roots are unchanged. The final hosted run, rather than that failed run or historical local results, determines release eligibility.
|
|
18
|
+
|
|
19
|
+
Historical native Windows/source and secret-scan receipts remain under docs/evidence; they describe the older runtime. Current hosted Windows exercises the new adapters and full source gates without Wine. macOS and live desktop/game integration remain unverified.
|
|
20
|
+
|
|
21
|
+
## Quality limits
|
|
22
|
+
|
|
23
|
+
Luna Decisions matches the historical Jev photo/text counts on this corpus; this is not proof of statistical non-inferiority, calibration or universal superiority. Two disjoint native sessions cover the full90 classification cases without replay or prompt changes. Public photographs may overlap training; text cases are authored fixtures, not customer traffic. No independent annotation/duplicate audit was performed.
|
|
24
|
+
|
|
25
|
+
Localization is experimental. The first native smoke failed at its195-option/nine-image fine request; an explicit replay returned HTTP502 with provider refusal. The original error and diagnostic remain preserved. A separate fixed flower-image smoke completed all three native stages in4.75s; its reviewed receipt is in docs/evidence, and it does not replace a full localization evaluation. A selected cell is not segmentation, an exact object center, tracking or a universal map/minimap guarantee. Offline fixtures are not real-model quality evidence.
|
|
26
|
+
|
|
27
|
+
Paid comparison accounting is separate from the historical337-call Jev work. The owner expanded the comparison limit to1000 calls while retaining the$0.50 ceiling. Reported costs are reconciled only from retained numeric receipts; failed classifications remain failures, unknown charges retain conservative reservations. Local admission accounting is not a provider-side account cap or a verified invoice. The comparison finished at332 calls, known cost$0.03456876 and four unknown costs; conservative accounted exposure is$0.23456876. Exact receipts are in the model evidence/release manifest.
|
|
28
|
+
|
|
29
|
+
## Traceability
|
|
30
|
+
|
|
31
|
+
Live manifests pin corpus/source snapshots. The clean source archive derives from the standalone Git commit; the external release manifest pins that commit, source-file manifest, all archive SHA256 values, CI results and final secret scans. It remains outside the archives to avoid self-reference. Credentials, personal media, private history, dependencies and corpus photo pixels are excluded. See [publishing](publishing.md) and the actual GitHub/npm destinations for publication status.
|
package/docs/api.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Public API
|
|
2
|
+
|
|
3
|
+
`createClassifier({ decision, timeoutMs?, concurrency?, maxQueue?, refinement? })` creates an owned client. Defaults: 45,000ms absolute budget including queue/image work/provider work; two active operations; sixteen waiters. Configured maxima: 120s, four active,32 queued. Input images are at most16MiB and16,777,216 decoded pixels; single PNG/JPEG/WebP only. The adapter accepts prepared PNG/JPEG with sides280..1568 and a32MiB total request cap. Byte inputs are copied before the first await, including Buffer views.
|
|
4
|
+
|
|
5
|
+
`classify({ input, classes, instructions?, signal? })`: input is a string or `{ text?, image? }` with at least one field. Classes are2..255 distinct IDs plus descriptions. IDs match `[a-zA-Z][a-zA-Z0-9_-]*`; descriptions at most300 chars. Text is at most32,000 chars, custom instructions4,000. Define explicit none/other behavior in your classes. With ordinary vision models the result contains `choice` (first ranked ID), `ranking`, `probabilities: null`, `nativeChoice: null`, empty `tied`, and metadata. No rank is converted to a fabricated probability. Optional native integrations instead retain raw probabilities, stable maximum/ties, native choice/confidence and optional quantization annotation. Confidence is never a calibrated spatial guarantee.
|
|
6
|
+
|
|
7
|
+
`locate({ image, target, exclusions?, roi?, signal? })`: target at most2,000 chars; up to16 exclusions of500 chars. ROI uses integer `left/top/width/height` in EXIF-oriented source pixels. Return coordinates reference that oriented image; metadata names this space and dimensions. Rectangles include left/top and exclude right/bottom. Normalized rectangle fields are divided by image dimensions. Tiny/impossible bounded grids return `unknown/layout-limit`.
|
|
8
|
+
|
|
9
|
+
`located` returns a selected grid region, normalized region, third-stage selection evidence and optional refinement. `not-found/absent-in-evaluated-region` is based on the initial independent presence check. A target missing from later shortlisted cells yields `unknown/candidate-union-empty`, not global absence. Ordinary models return present/absent/unknown as an independent categorical predicate. Legacy Noul0.5 abstains; contradictory presence/selection returns unknown. Multiple physical matching objects are permitted; this is an any-instance locator.
|
|
10
|
+
|
|
11
|
+
The algorithm searches4×4 coarse cells, keeps four source-distinct maxima, pads each by half a coarse cell within ROI, partitions each into at most49 fine cells (global≤196, side≤256), then compares the four best fine cells as enlarged A–D/none. Edges count as matches. Scores/rank hints are not sent to the final comparison. No cell-center correction is presented as object segmentation.
|
|
12
|
+
|
|
13
|
+
`RefinementPort.refine({ image, target, region, signal })` is optional trusted code. It receives a private oriented-source byte copy. A returned refined rectangle must fit the source and intersect the selected cell. It does not replace the original area evidence. No built-in heavy refiner is downloaded.
|
|
14
|
+
|
|
15
|
+
`dispose()` cancels current/queued work, rejects further calls and waits for physical settlement. Injected ports must honor cancellation. Native image work retains its concurrency slot until completion, even if cancellation has already been requested.
|
|
16
|
+
|
|
17
|
+
`ClassifierError.code`: INVALID_INPUT, INVALID_RESPONSE, TIMEOUT, ABORTED, CAPACITY, DISPOSED, PROVIDER, AUTHENTICATION, UNSUPPORTED, RESOURCE_LIMIT. Transport failures throw; they are not successful negative classifications. Provider metadata uses null for unknown model/provider/request/cost. Localization adds knownCostUsd and unknownCostCount; knownCostUsd alone is not a complete bill when unknownCostCount>0.
|
|
18
|
+
|
|
19
|
+
## Native decision models (primary integration)
|
|
20
|
+
|
|
21
|
+
`createOpenRouterDecisions({ apiKey, model, provider, providerName?, responseModelIds?, probabilityRoundingStep?, fetch?, maxRequestBytes?, maxResponseBytes? })` uses the typed `/api/alpha/decisions` endpoint. The primary documented route is `openai/gpt-6-luna-decisions` / `openai`. Model and provider are required; `provider.only` disables fallback. Compatible native models can be supplied explicitly; vision support and factual quality must be checked per model. Jev is not required.
|
|
22
|
+
|
|
23
|
+
The adapter sends the exact Choice criteria and independent Noul questions, preserving genuine raw distributions and metadata. Internal chat-only `rankingSize` hints are stripped from the native wire request. Model/provider identity, question correspondence, complete finite probabilities and the categorical maximum are verified. Valid date-stamped model snapshots are accepted. `probabilityRoundingStep` is unset by default: a selected model does not inherit Jev's observed0.01 wire quantization. Set it explicitly only with supporting provider/wire evidence; values are still never normalized.
|
|
24
|
+
|
|
25
|
+
Other native protocols can implement `DecisionPort`. No native-to-chat or model/provider fallback occurs automatically.
|
|
26
|
+
|
|
27
|
+
## Ordinary vision chat adapters
|
|
28
|
+
|
|
29
|
+
`createOpenAICompatibleVision({ endpoint, model, apiKey?, headers?, responseFormat?, maxOutputTokens?, reasoningEffort?, responseModelIds?, fetch?, maxRequestBytes?, maxResponseBytes? })` is exported from `zorya-classifier/openai-compatible`. The full HTTP(S) chat-completions URL is explicit and trusted; URL user/password credentials are rejected. Anonymous local servers can omit authentication. Custom headers support other server authentication. Supply either `apiKey` or a custom Authorization header, not both. No OpenRouter routing fields are sent by this adapter.
|
|
30
|
+
|
|
31
|
+
`createOpenRouterVision({ apiKey, model, provider, providerName?, ... })` is exported from `zorya-classifier/openrouter`. It wraps the same ordinary chat transport with a fixed OpenRouter chat-completions endpoint and explicit `provider.only`, no fallback, and required parameter support. `providerName` identifies a returned display name differing from the requested slug. Additional explicitly allowed response model snapshot IDs can be listed in `responseModelIds`; these are identity aliases, not alternative routes. Date-stamped snapshots of the requested model are accepted and preserved in metadata.
|
|
32
|
+
|
|
33
|
+
`responseFormat` is `prompt` by default, requiring only ordinary instruction-following JSON output, not provider-native structured outputs. `json-object` and `json-schema` are explicit opt-ins when the model/server supports them. `maxOutputTokens` defaults to1024, maximum4096. `reasoningEffort` is optional; do not request unsupported parameters from a custom model. No temperature or reasoning option is silently imposed by the generic adapter.
|
|
34
|
+
|
|
35
|
+
The model is given the exact class descriptions and required JSON shape. It returns one ranked ID for classification/final candidate selection and exactly `min(4, option count)` unique ranked IDs for each spatial beam question (`rankingSize: 1 | 4`; omitted defaults to4 on direct port calls) and an explicit present/absent/unknown for each presence question. The adapter checks all questions, IDs, types, finish state and byte limits. It rejects unknown IDs, duplicate/incomplete ranks, numeric probabilities invented in a ranking response, extra answers, malformed/fenced JSON, refusals, tool output and truncated generations. There are no hidden corrective retries. Factual correctness still depends on the model.
|
|
36
|
+
|
|
37
|
+
The public `DecisionPort` supports another SDK/protocol. For a ranked answer, return `{ type: 'ranked-choice', ranking: [...] }`; for presence return `{ type: 'presence', presence: 'present' | 'absent' | 'unknown' }`, with truthful route/cost metadata. The caller's deadline-bound AbortSignal must be honored. Optional legacy probability answers remain supported. `classify` needs at most one image; `locate` requires up to nine images per request. A server supporting only a single image is not claimed to support the locator.
|
|
38
|
+
|
|
39
|
+
`createOpenRouterJev({ apiKey, fetch?, maxRequestBytes?, maxResponseBytes? })` is an optional legacy Jev integration, never the required backend or an automatic fallback: fixed endpoint/model/provider, no automatic fallback/retry. Lower request/response caps can be supplied; response defaults1MiB. Direct port calls require the caller to supply a deadline-bound signal. The top-level client supplies it. The port accepts only top-level inline image parts, not remote URLs.
|
|
40
|
+
|
|
41
|
+
## Optional legacy native adapter and quantization
|
|
42
|
+
|
|
43
|
+
The legacy Jev adapter observed Jev 1.14 rounding scores to a 0.01 lattice: a real 153-option response sums to 0.99. Such values remain raw; they are never renormalized. For non-unit mass the adapter accepts only finite [0,1] values on that lattice whose clipped half-step intervals jointly admit total probability 1, and adds `probabilityRoundingStep: 0.01`. Complete option keys and the native maximum remain mandatory.
|
|
44
|
+
|
|
45
|
+
A custom probability-valued `DecisionPort` answer without this explicit annotation must provide unit mass within floating-point tolerance. The annotation describes transport quantization, not calibration or confidence; many tied zeros can be compatible with rounding at high class counts and convey no ranking information. Stable input-order tie selection still applies. The result preserves both the native choice and our deterministic choice.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Architecture and lifecycle
|
|
2
|
+
|
|
3
|
+
The public factory composes a DecisionPort and a Sharp-backed RasterPort. Processes orchestrate classify/locate; features contain pure Choice interpretation, grid geometry and prompt construction; entities define validated public contracts; infrastructure owns provider HTTP and image processing. No application runtime, game-state schema or workspace import is required.
|
|
4
|
+
|
|
5
|
+
```mermaid
|
|
6
|
+
flowchart LR
|
|
7
|
+
I[Owned input snapshot] --> G[Bounded operation queue]
|
|
8
|
+
G --> C[Classify: closed class ranking]
|
|
9
|
+
G --> L[Locate: coarse ranking]
|
|
10
|
+
L --> F[Fine-grid ranking]
|
|
11
|
+
F --> V[Candidate comparison]
|
|
12
|
+
V --> R[Source region]
|
|
13
|
+
R --> O[Optional injected refinement]
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
One deadline covers the whole operation, including queue time. AbortSignal passes through every boundary; guards after awaits reject late results. Physical Sharp/provider work retains its slot until its promise settles. Timers/listeners are cleared in finally. There is no import-time worker, automatic retry, persistent image cache, database or background polling loop.
|
|
17
|
+
|
|
18
|
+
Transport validates complete question/answer correspondence, probabilities, identity and byte limits. Errors omit credentials, provider bodies and raw user media. Raw images exist only for the active operation unless an explicit caller-owned diagnostic wrapper records requests. Scores are not mixed across Choice distributions or interpreted as pixel/pose confidence.
|
|
19
|
+
|
|
20
|
+
Image handling normalizes EXIF orientation, validates bounded decode, crops in source pixels and prepares PNG presentations with recorded aspect-ratio/padding transforms. Labels map to exact canonical rectangles. Original and clean crops remain available alongside grid overlays. Final candidate pixels are source crops enlarged with integer nearest-neighbor scale.
|
|
21
|
+
|
|
22
|
+
Extraction differences from the gaming service: the public result is an area; multiple objects are allowed with one selected; no session/epoch/database/process host remains; the caller owns freshness of supplied bytes; generic image operations have an explicit45s library timeout instead of borrowing the gaming background8s budget. Stable ties use supplied class/grid order. Prompt wording and compact grid labels are documented source changes and require new quality evidence; old game-map results do not qualify this library.
|
|
23
|
+
|
|
24
|
+
ESLint catches common promise/race/interval errors and architectural checks reject private imports/cycles. These checks cannot prove native memory safety; tests additionally exercise cancellation, queue overload, late completion, input mutation and repeated lifecycle disposal.
|
|
25
|
+
|
|
26
|
+
## Model and provider independence
|
|
27
|
+
|
|
28
|
+
`createOpenAICompatibleVision` accepts a trusted direct chat-completions endpoint, model ID, optional key/custom headers, and the same bounded transport policies. `createOpenRouterVision` adds explicit OpenRouter-only routing around that transport. Neither requires a native classifier endpoint. The core knows only `DecisionPort`; a non-OpenAI-compatible SDK can implement that port without changing geometry or lifecycle.
|
|
29
|
+
|
|
30
|
+
Ordinary language models receive images/text, closed option descriptions and a JSON answer schema. They return ranked IDs and independent categorical presence evidence. These are validated against the exact supplied questions/options; no ranks are converted to fake probabilities. The optional legacy native integration retains probability-valued answers for compatibility and historical benchmarking. A malformed/refused/truncated answer remains an error; it is never silently retried through another backend.
|
|
31
|
+
|
|
32
|
+
The primary native integration is `createOpenRouterDecisions` with explicit model/provider, initially Luna Decisions. It shares bounded snapshots/HTTP cleanup with the optional Jev wrapper and strips internal rank-count hints from the typed wire request. Native probability checks remain separate from ordinary chat ranking. Jev-specific quantization is never silently applied to another model.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Reproducible benchmarks
|
|
2
|
+
|
|
3
|
+
Current native Luna Decisions and ordinary vision comparisons are summarized in [tested models](vision-results.md). The fixed native-Jev runner below remains a historical/reproducibility tool, not the primary model configuration. New model-selection snapshots and budget continuations are retained separately; never label the Jev runner's output as evidence for Luna or another backend.
|
|
4
|
+
|
|
5
|
+
The optional corpus contains 60 licensed Open Images photographs and 30 authored text fixtures. Photographs cover 25 requested classes with ten each of baseline, absent target, small target, edge truncation, occlusion and repeated instances.20 photos/10 texts are development data;40/20 are reserved evaluation data. Public heldout does not guarantee exclusion from provider training.
|
|
6
|
+
|
|
7
|
+
Photo licenses and hashes are in benchmarks/corpus/ATTRIBUTION.md, licenses and manifests. Photo pixels and bulk CSVs are not shipped in npm or the source archive. Run the pinned fetch_photos.py script from benchmarks/corpus to obtain them; fetch_metadata.py acquires source CSVs for the independent audit. Image manifests use explicit human-verified negatives. Authored text cases are fixtures, not real customer traffic.
|
|
8
|
+
|
|
9
|
+
The benchmark is opt-in and must bound requests/costs before dispatch. Production library calls have no benchmark logger; a benchmark DecisionPort wrapper explicitly records input images, answers, errors and timing into an isolated run directory. The harness validates label files at startup; labels are used only by scoring and overlays after the corresponding producer results are recorded. They are never included in provider state, prompts, options or image filenames. Native classify uses a second call with the identical request; this tests wrapper parity and variance, not an expected accuracy gain. One/two-stage locate baselines reuse the exact successful prefix responses and are labeled correlated prefix ablations.
|
|
10
|
+
|
|
11
|
+
Metrics include classification accuracy/macro-F 1/confusion, errors in the denominator, any-instance point-in-reference-box, selected-region area, distance to reference boxes, negative false positives, p 50/p 95 and physical call/known/unknown costs. The point is the center of the selected cell for evaluation only, not a claimed object centroid. Bbox membership is a proxy and can include background; overlays and region sizes remain necessary. No all-instance recall is claimed.
|
|
12
|
+
|
|
13
|
+
Offline fixture tests validate mechanics only. Actual provider outcomes are recorded in [results](results.md), acceptance and the run reports. Keep failed and incomplete runs. Never silently retry errors, alter labels or discard difficult images to improve headline metrics.
|
|
14
|
+
|
|
15
|
+
## Current native Luna Decisions reproduction
|
|
16
|
+
|
|
17
|
+
For an independent fresh run of the selected non-Jev route, fetch the corpus, set your own `OPENROUTER_API_KEY`, then explicitly opt in:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
node --import tsx benchmarks/model-selection.ts --allow-paid --fresh-native --out .run/luna-native-example
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This caps admission at93 calls and$0.50:60 photos+30 texts, then at most three calls for one fixed positive locator smoke if classification meets the recorded target. It starts a new budget rather than importing the owner's historical runs. No retries/fallback or hidden key loading occur. The source-pinned snapshots in docs/evidence describe their original versions; future results can vary. Extension/completion flags in that script are historical budget-continuation tools and require the exact local earlier-run receipts; they are not needed for a fresh run.
|
|
24
|
+
|
|
25
|
+
## Historical Jev fixed-corpus runner
|
|
26
|
+
|
|
27
|
+
From the repository root, install development dependencies with `npm ci --ignore-scripts`. Fetch only the pinned public photo bytes, then run fixture mechanics:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
python3 benchmarks/corpus/fetch_photos.py
|
|
31
|
+
node -e "require('node:fs').mkdirSync('.run',{recursive:true})"
|
|
32
|
+
node --import tsx benchmarks/run.ts --mode offline --split dev --limit 2 --out .run/offline-example
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For a real run, set `OPENROUTER_API_KEY` in your shell/secret manager and explicitly opt in:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
node --import tsx benchmarks/run.ts --mode live --allow-paid --max-calls 360 --max-cost-usd 1 --out .run/live-example
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Each output directory must be new. The maximum is 60 × (two independent classifications + three locator calls) + 30 × two text calls = 360 provider calls; early absence can reduce it. No retries or resumes silently repeat paid requests. The runner stops further admission on budget exhaustion or evidence-write failure, keeps incomplete results, and exits unsuccessfully for an incomplete run. A completed run may still contain provider/model failures; those remain in accuracy denominators.
|
|
42
|
+
|
|
43
|
+
The budget reserves $0.02 before each request and retains that reservation for missing/invalid cost metadata. Current combined-context pricing gives a maximum input envelope of 262,144 × $0.000000042 = $0.011010048 per request; output tokens are free. See the [dated pricing snapshot](evidence/jev-pricing-2026-10-18.json), [provider context/pricing documentation](https://docs.typesafe.ai/models) and [OpenRouter image billing](https://openrouter.ai/docs/guides/community/multimodal-decisions). Recheck pricing before a future run. This is a local admission budget derived from published limits; it does not change the shared account's billing limits.
|
|
44
|
+
|
|
45
|
+
`manifest.json` pins source/corpus hashes and configuration. Per-case requests include public image data; receipts record validated answers or sanitized errors. A malformed answer rejected by the adapter is represented by its error, not silently repaired. `report.json` contains confusion matrices, failures, latency, known/unknown costs and all joined sample scores. Overlays show reference boxes and one/two/three-stage selected cells. Preserve raw evidence locally; share only reviewed public-corpus evidence.
|
|
46
|
+
|
|
47
|
+
The corpus README and validation report describe its frozen **pre-inference preparation**; measured model results live in this release's acceptance/report, not in that historical manifest.
|
|
48
|
+
|
|
49
|
+
## Protocol correction found during v1
|
|
50
|
+
|
|
51
|
+
The first live process is pinned to a pre-correction source snapshot: it rejected any probability sum farther than 1e-6 from 1. Two separate diagnostic replays captured an actual Jev 153-option response with sum 0.99 and a five-option response with unit mass. The release supports explicitly annotated quantization as described in the API; the exact 153-option response is covered by offline replay. The original live failures are retained and its metrics are not recomputed as successes. This correction changes response acceptance, not prompts, crops, argmax, geometry or model route. The release's complete future quality estimate still needs another frozen run; current figures describe the recorded v1 snapshot. The two diagnostic calls count toward the total call/cost accounting but not accuracy metrics.
|
|
52
|
+
|
|
53
|
+
The final corrected runtime smoke used `--mode live --allow-paid --max-calls 14 --max-cost-usd 0.1 --split dev --limit 2 --out .run/live-release-smoke`. Its 14 calls and the two diagnostic probes are accounted separately from the full run; neither is pooled into the original accuracy metrics.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "PASS",
|
|
3
|
+
"platform": "linux",
|
|
4
|
+
"node": "v24.12.0",
|
|
5
|
+
"checks": ["secrets:check", "typecheck", "lint", "boundaries", "dead-code:ci", "test", "build"],
|
|
6
|
+
"tests": {
|
|
7
|
+
"pass": 187,
|
|
8
|
+
"fail": 0,
|
|
9
|
+
"skipped": 0
|
|
10
|
+
},
|
|
11
|
+
"runtimeDistFiles": 44,
|
|
12
|
+
"runtimeDistSha256": "3c9317d9c6945eb00a3c043cfe5e6be4e12541db7fefa30398d177b955f01fbd",
|
|
13
|
+
"sourceLogSha256": "8f78477d719c1cb813b74230cc246e3645d6dfd0c96b92806b3e89bc89d5fcce",
|
|
14
|
+
"note": "Source gates passed; documentation evidence subsequently added. Final release/CI checks inspect the committed source snapshot."
|
|
15
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"startedAt": "2026-10-17T22:59:39.495Z",
|
|
3
|
+
"authorization": "User authorized up to1000 aggregate test requests, keeping the existing $0.50 ceiling; prior288 calls and reconciled unknown costs included",
|
|
4
|
+
"routes": [
|
|
5
|
+
{
|
|
6
|
+
"id": "luna-decisions",
|
|
7
|
+
"model": "openai/gpt-6-luna-decisions",
|
|
8
|
+
"provider": "openai",
|
|
9
|
+
"providerName": "OpenAI",
|
|
10
|
+
"inputPerMillion": 0.1,
|
|
11
|
+
"outputPerMillion": 0
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"prior": {
|
|
15
|
+
"sha256": "25c463dc9b005c2702dd94e78b12cee861e5f27827c5093cda857771837b19c7",
|
|
16
|
+
"physicalCalls": 288,
|
|
17
|
+
"budget": {
|
|
18
|
+
"admittedProviderCalls": 288,
|
|
19
|
+
"maxCalls": 1000,
|
|
20
|
+
"maxCostUsd": 0.5,
|
|
21
|
+
"knownCostUsd": 0.029322660000000056,
|
|
22
|
+
"unknownCostCalls": 3,
|
|
23
|
+
"inFlightCalls": 0,
|
|
24
|
+
"reservationPerUnknownUsd": 0.05,
|
|
25
|
+
"accountedExposureUsd": 0.17932266000000008,
|
|
26
|
+
"exceededByReportedCost": false
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"protocol": "native-choice-v1: exact criteria and raw distributions; no chat rank protocol",
|
|
30
|
+
"policy": "Complete the missing20 development photos and20 heldout texts for the same frozen Luna Decisions classifier; earlier40 heldout photos and10 development texts are retained, never replayed or repaired. No inference/prompt changes. At most40 new classification calls under expanded1000 aggregate request limit and unchanged $0.50 cap. No retries. Preserve previous locator provider error.",
|
|
31
|
+
"screen": [
|
|
32
|
+
"photo-dev-01",
|
|
33
|
+
"photo-dev-02",
|
|
34
|
+
"photo-dev-03",
|
|
35
|
+
"photo-dev-04",
|
|
36
|
+
"photo-dev-05",
|
|
37
|
+
"photo-dev-06",
|
|
38
|
+
"photo-dev-07",
|
|
39
|
+
"photo-dev-08",
|
|
40
|
+
"photo-dev-09",
|
|
41
|
+
"photo-dev-10"
|
|
42
|
+
],
|
|
43
|
+
"sourceHashes": {
|
|
44
|
+
"src/app/cli-route.ts": "769b0f2e8fa6d1fabbf2e7bd000fec724c1b94a3c6233eea7b93d55503052aad",
|
|
45
|
+
"src/app/create-classifier.ts": "55bfcf78b8c12c616d1144371541ec4eb1e56a6a4790700b366e41dbdf896491",
|
|
46
|
+
"src/cli.ts": "b47f83c9470de5fbba10bb7c38241ef1ba67dfd59e976fe1d73d19db2748b99e",
|
|
47
|
+
"src/entities/contracts.ts": "6dc59ce64331d748f57d2f192aeb693ec4c5ff9e021e62dd56f534b94aa67efe",
|
|
48
|
+
"src/entities/errors.ts": "1c92c94c9ed6917953ac5192bfd2d8896cdb6fe13b7d05593573b7f2103330b0",
|
|
49
|
+
"src/features/choice-beam-layout.ts": "8f61d6f784e0e6273467cea01315f17bbfa4fffa695dd7a7bb9cd5cac5354e1a",
|
|
50
|
+
"src/features/choice.ts": "ff972121520a339b82895914242bf40f7b8dcd198b4305fd57e51b204b13952d",
|
|
51
|
+
"src/features/prompts.ts": "b808ed8f42e8c92daf08060d2805da618f2b140f99b6a9b07df49622d57b62aa",
|
|
52
|
+
"src/features/uniform-grid.ts": "8cee38aee52ba2c4ac2eaed909d5f53cc0e5aa2c04fe87585c66b29573e21c4d",
|
|
53
|
+
"src/index.ts": "1cd73c74a874adf1a4a3b6edb48e230f54c671098b9a3d51bb3a01a0ad2866db",
|
|
54
|
+
"src/infrastructure/openrouter-common.ts": "415462c42da7a3748c6bfe942ee99085fba71bc1f4158d369edda8269efeb124",
|
|
55
|
+
"src/infrastructure/openrouter-vision.ts": "d95f1be71f8011d32ec6dfbe15c3ad93d12fe1aaf88799f8dfb06156353e1915",
|
|
56
|
+
"src/infrastructure/openrouter.ts": "486a919853949e6789dd4035b7699fd89b9682cb3e928632c77b216656f44c55",
|
|
57
|
+
"src/infrastructure/raster.ts": "8217346f671b957540f3c01a7eb3d8ef7d0bfb6a5e18a47c96f0ccb08f4bcb08",
|
|
58
|
+
"src/infrastructure/vision-chat.ts": "9df3aeda2a2cb15d068055bca70e1bacc7ec682d70a3fc1b5f91be529753c790",
|
|
59
|
+
"src/openai-compatible.ts": "ff9096393ecb437d9c917c5da716553a5a887a810e65c9815dd791b772895fc3",
|
|
60
|
+
"src/openrouter.ts": "7272441f86f6d143aab867231f617dfd806c802b2147f95e640ace65cdee3ced",
|
|
61
|
+
"src/processes/classify.ts": "e9dd25f86e28264f2d6f1b409879514cb443194b8efdd638e42867869f7136ff",
|
|
62
|
+
"src/processes/locate.ts": "a64d2d3c4120a4ff5389a8a9b4620ecff1132dd5c3b46e69efe04208c97f39b4",
|
|
63
|
+
"src/shared/choice-mass.ts": "7f645998d9cd2688ca425b4765ec0bf7d125e932f085f9e84599b4c3b97743a3",
|
|
64
|
+
"src/shared/gate.ts": "97e4e32749dc9d3a9def6faaea2566b5d0ed831ba4451999740c719fafa8e576",
|
|
65
|
+
"src/shared/scope.ts": "cc557e9fd83a838f5366c64d026859db119491d927dfb3a84ef4320b2f0b0beb"
|
|
66
|
+
},
|
|
67
|
+
"harnessHashes": {
|
|
68
|
+
"benchmarks/ablations.ts": "d4450a1c962bd96f89af3499fd0d5da52bb20eb2c6fdf8c79d2e6057d0d6325b",
|
|
69
|
+
"benchmarks/budget.ts": "c8ac95573a79900e82583a11fe43b631c2959d15bf1bd5c49f1e4e6deb875564",
|
|
70
|
+
"benchmarks/corpus.ts": "0af52e2f896e74e8e9ab0937e34862cea5396ac0649f896299631160314848b4",
|
|
71
|
+
"benchmarks/metrics.ts": "024e2461104df77add39ef8fa1c501863b11ca126079d1ab28d512dbbee98d37",
|
|
72
|
+
"benchmarks/model-selection.ts": "123b2bdbedc72e31b365625b2f06920238da2da0416a3e3c12767a621ff11875",
|
|
73
|
+
"benchmarks/overlay.ts": "9eced23935c5428d657df747d0f239c6fc8a61ef2f1582d88a86e08f71ddfbf5",
|
|
74
|
+
"benchmarks/recording.ts": "142f271e6fce60580ff273aa3b0e64de52bd6b74e838bb75c925ac90099c8159",
|
|
75
|
+
"benchmarks/run.ts": "5ff28c02fd9bc6dc03aaf5934e7e28300f958696a2fb1fffca44edabf748ebfa",
|
|
76
|
+
"benchmarks/runner.ts": "fd95d5b720e11f4f22bd39e5044b456fcd3c42fffd6b4f308ee98bd99695558f"
|
|
77
|
+
},
|
|
78
|
+
"corpus": {
|
|
79
|
+
"corpus-manifest.json": "00f4459996db0f61a38235adaf512e1bfc52e02e1d599bb590b62a98217e4cdb",
|
|
80
|
+
"photo-manifest.json": "a99f47d4e1c064581c6cc9123b58a5c81b196d15227963bf015041544f713aa3",
|
|
81
|
+
"text/text-manifest.json": "5e74c8c13cd1f1f8a2e2ecc22e90e6c0c83ebf5f9407dfed5b87204d3b8831ac",
|
|
82
|
+
"text/inputs/policy.json": "9db3010c97fa056fbb4a43ea51a5c973bd75b9d21f8d07f2c15d6d176e712b8a",
|
|
83
|
+
"inputs/dev.json": "0c443231956e9578afa43d4bf884cc9741594ae69ac5f957657b9c5a24422bb6",
|
|
84
|
+
"ground-truth/dev.json": "c618e1a950ae89e0c9ced0c2b1ca249ea7adc891f61c0579468c479613ea4b26",
|
|
85
|
+
"text/inputs/dev.json": "e7d2bc07559cba70494d6e4672cc3e1113c070aba8b2694ee45e76ebcbc04fcc",
|
|
86
|
+
"text/ground-truth/dev.json": "4827650b560f20b32b0366dea14947681d29ef9fefb5a059301ba5abf5c031f0",
|
|
87
|
+
"inputs/heldout.json": "2ce42723824900de9a8eca60b58220567b938a88855ec41a5a882bb410d3b1e4",
|
|
88
|
+
"ground-truth/heldout.json": "737ee884b8bbf557191d5848b56f17ac056fd01793cd8fb738c5624ea119c7d2",
|
|
89
|
+
"text/inputs/heldout.json": "e33beeeb011c55992d955ce66397717875c0084bcc21be29ecd123bd29ccce4a",
|
|
90
|
+
"text/ground-truth/heldout.json": "d29cf2fb7b980d138fd7c8e712708fa9730aa6637f350468ae15651bf9f5c4b0"
|
|
91
|
+
},
|
|
92
|
+
"perCallReservationUsd": 0.05,
|
|
93
|
+
"reasoningEffort": null,
|
|
94
|
+
"responseFormat": null,
|
|
95
|
+
"timeoutMs": 45000
|
|
96
|
+
}
|