orizu 0.5.21 → 0.5.23
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/README.md +46 -15
- package/dist/cleanup.d.ts +6 -0
- package/dist/cleanup.d.ts.map +1 -0
- package/dist/cleanup.js +27 -0
- package/dist/gepa-engine-dispatch.d.ts.map +1 -1
- package/dist/gepa-engine-dispatch.js +20 -1
- package/dist/gepa-python-paths.d.ts.map +1 -1
- package/dist/gepa-python-paths.js +25 -6
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +37 -15
- package/dist/hosted-commands.d.ts +13 -0
- package/dist/hosted-commands.d.ts.map +1 -0
- package/dist/hosted-commands.js +10 -0
- package/dist/hosted-gepa-cli.d.ts +11 -0
- package/dist/hosted-gepa-cli.d.ts.map +1 -0
- package/dist/hosted-gepa-cli.js +110 -0
- package/dist/hosted-gepa-numeric-values.d.ts +3 -0
- package/dist/hosted-gepa-numeric-values.d.ts.map +1 -0
- package/dist/hosted-gepa-numeric-values.js +49 -0
- package/dist/hosted-provider-settings.d.ts +3 -0
- package/dist/hosted-provider-settings.d.ts.map +1 -0
- package/dist/hosted-provider-settings.js +69 -0
- package/dist/hosted-session-cli.d.ts.map +1 -1
- package/dist/hosted-session-cli.js +6 -1
- package/dist/http.d.ts +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +72 -74
- package/dist/instruction-set-loader.d.ts.map +1 -1
- package/dist/instruction-set-loader.js +57 -5
- package/dist/instruction-sets-cli.d.ts +5 -2
- package/dist/instruction-sets-cli.d.ts.map +1 -1
- package/dist/instruction-sets-cli.js +134 -24
- package/dist/optimizations-promote-cli.d.ts +3 -0
- package/dist/optimizations-promote-cli.d.ts.map +1 -1
- package/dist/optimizations-promote-cli.js +75 -7
- package/dist/runner-dir-verify.d.ts.map +1 -1
- package/dist/runner-dir-verify.js +4 -7
- package/dist/skilled-proposer-bake-report.d.ts +2 -0
- package/dist/skilled-proposer-bake-report.d.ts.map +1 -0
- package/dist/skilled-proposer-bake-report.js +1 -0
- package/dist/skilled-proposer-config.d.ts +2 -0
- package/dist/skilled-proposer-config.d.ts.map +1 -0
- package/dist/skilled-proposer-config.js +212 -0
- package/dist/skilled-proposer-launch.d.ts +26 -0
- package/dist/skilled-proposer-launch.d.ts.map +1 -1
- package/dist/skilled-proposer-launch.js +103 -6
- package/package.json +1 -1
- package/scripts/vendor-gepa-python.mjs +81 -12
- package/src/cleanup.ts +37 -0
- package/src/gepa-engine-dispatch.ts +21 -1
- package/src/gepa-python-paths.ts +24 -6
- package/src/help.ts +37 -15
- package/src/hosted-commands.ts +4 -0
- package/src/hosted-gepa-cli.ts +107 -0
- package/src/hosted-gepa-numeric-values.ts +60 -0
- package/src/hosted-provider-settings.ts +69 -0
- package/src/hosted-session-cli.ts +4 -1
- package/src/http.ts +8 -0
- package/src/index.ts +112 -89
- package/src/instruction-set-loader.ts +55 -5
- package/src/instruction-sets-cli.ts +173 -19
- package/src/optimizations-promote-cli.ts +85 -7
- package/src/runner-dir-verify.ts +11 -6
- package/src/skilled-proposer-bake-report.ts +1 -0
- package/src/skilled-proposer-config.ts +191 -0
- package/src/skilled-proposer-launch.ts +96 -7
- package/src/skilled-proposer-venv-manager.mjs +44 -27
- package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +36 -3
- package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +31 -3
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +7 -2
- package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +354 -5
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +1 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +15 -0
- package/vendor/skills/orizu-cli/SKILL.md +94 -168
- package/vendor/skills/orizu-cli/references/assess-and-plan.md +107 -0
- package/vendor/skills/orizu-cli/references/building-judges.md +442 -180
- package/vendor/skills/orizu-cli/references/cli-reference.md +69 -39
- package/vendor/skills/orizu-cli/references/dataset-design.md +100 -0
- package/vendor/skills/orizu-cli/references/eval-strategy.md +64 -0
- package/vendor/skills/orizu-cli/references/instructions-after-prompts.md +115 -0
- package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +150 -82
- package/vendor/skills/orizu-cli/references/optimization-reports.md +196 -104
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +57 -37
- package/vendor/skills/orizu-cli/references/primer.md +43 -18
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +199 -131
|
@@ -1,14 +1,78 @@
|
|
|
1
|
-
import { spawnSync } from 'child_process';
|
|
1
|
+
import { spawn, spawnSync } from 'child_process';
|
|
2
|
+
import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
2
4
|
import { join } from 'path';
|
|
3
5
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { bundledOfficialGepaPythonPath } from './gepa-python-paths.js';
|
|
6
|
+
import { bundledOfficialGepaPythonPath, getGepaPythonPathEntries } from './gepa-python-paths.js';
|
|
5
7
|
import { sanitizeTerminalText } from './json-response.js';
|
|
6
|
-
|
|
8
|
+
import { SKILLED_PROPOSER_BAKE_REPORT_FIELDS } from './skilled-proposer-bake-report.js';
|
|
9
|
+
function materializeConfigPayload(engine, environment) {
|
|
10
|
+
const payload = environment.ORIZU_SKILLED_PROPOSER_CONFIG;
|
|
11
|
+
if (engine !== 'official' || environment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer' || payload === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
const root = mkdtempSync(join(tmpdir(), 'orizu-skilled-proposer-payload-'));
|
|
14
|
+
try {
|
|
15
|
+
const path = join(root, 'config.json');
|
|
16
|
+
writeFileSync(path, payload, { encoding: 'utf8', mode: 0o600 });
|
|
17
|
+
environment.ORIZU_SKILLED_PROPOSER_CONFIG = `@${path}`;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
rmSync(root, { recursive: true, force: true });
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
return () => rmSync(root, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
export async function spawnSkilledProposerChild(command, args, engine, environment) {
|
|
26
|
+
const childEnvironment = { ...environment };
|
|
27
|
+
const shouldMaterialize = engine === 'official'
|
|
28
|
+
&& childEnvironment.ORIZU_CANDIDATE_PROPOSER === 'skilled-proposer'
|
|
29
|
+
&& childEnvironment.ORIZU_SKILLED_PROPOSER_CONFIG !== undefined;
|
|
30
|
+
if (!shouldMaterialize)
|
|
31
|
+
return spawnSync(command, args, { stdio: 'inherit', env: childEnvironment });
|
|
32
|
+
let child;
|
|
33
|
+
let cleanup = () => { };
|
|
34
|
+
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT'];
|
|
35
|
+
const handlers = signals.map(signal => ({
|
|
36
|
+
signal,
|
|
37
|
+
handle: () => {
|
|
38
|
+
try {
|
|
39
|
+
cleanup();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
try {
|
|
43
|
+
child?.kill(signal);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
for (const entry of handlers)
|
|
47
|
+
process.off(entry.signal, entry.handle);
|
|
48
|
+
process.kill(process.pid, signal);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
}));
|
|
53
|
+
for (const entry of handlers)
|
|
54
|
+
process.once(entry.signal, entry.handle);
|
|
55
|
+
try {
|
|
56
|
+
cleanup = materializeConfigPayload(engine, childEnvironment) ?? cleanup;
|
|
57
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
58
|
+
return await new Promise(resolve => {
|
|
59
|
+
let error;
|
|
60
|
+
child = spawn(command, args, { stdio: 'inherit', env: childEnvironment });
|
|
61
|
+
child.once('error', cause => { error = cause; });
|
|
62
|
+
child.once('close', status => resolve(error ? { status, error } : { status }));
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
for (const entry of handlers)
|
|
67
|
+
process.off(entry.signal, entry.handle);
|
|
68
|
+
cleanup();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function ensureSelectedSkilledProposerVenv(python, vendoredGepaPath, cacheRoot = existsSync('/opt/orizu/prebaked.json')
|
|
72
|
+
? '/opt/orizu/cache/skilled-proposer'
|
|
73
|
+
: join(process.cwd(), '.orizu', 'cache', 'skilled-proposer')) {
|
|
7
74
|
const manager = fileURLToPath(new URL('../scripts/ensure-skilled-proposer-venv.mjs', import.meta.url));
|
|
8
75
|
const lock = fileURLToPath(new URL('../requirements/skilled-proposer.lock', import.meta.url));
|
|
9
|
-
// This is a regenerable workspace cache. The manager's publish key further
|
|
10
|
-
// separates interpreters, platforms, and lock revisions inside it.
|
|
11
|
-
const cacheRoot = join(process.cwd(), '.orizu', 'cache', 'skilled-proposer');
|
|
12
76
|
const result = spawnSync(process.execPath, [manager, '--python', python, '--cache-root', cacheRoot, '--lock', lock,
|
|
13
77
|
'--vendored-gepa-path', vendoredGepaPath, '--json'], {
|
|
14
78
|
env: process.env,
|
|
@@ -34,6 +98,38 @@ function ensureSelectedSkilledProposerVenv(python, vendoredGepaPath) {
|
|
|
34
98
|
}
|
|
35
99
|
return report;
|
|
36
100
|
}
|
|
101
|
+
const packageRoot = join(fileURLToPath(new URL('.', import.meta.url)), '..');
|
|
102
|
+
const managerPath = join(packageRoot, 'scripts/ensure-skilled-proposer-venv.mjs');
|
|
103
|
+
const lockPath = join(packageRoot, 'requirements/skilled-proposer.lock');
|
|
104
|
+
const vendoredGepaPath = join(packageRoot, 'vendor/gepa-python/src');
|
|
105
|
+
const packagedPythonPathEntries = ['vendor/orizu-gepa/src', 'vendor/orizu-gepa-python/src', 'vendor/gepa-python/src'].map(path => join(packageRoot, path));
|
|
106
|
+
const bakedCacheRoot = '/opt/orizu/cache/skilled-proposer';
|
|
107
|
+
export function skilledProposerBakeCommand(io, verify = false) {
|
|
108
|
+
try {
|
|
109
|
+
const python = process.env.PYTHON || 'python3';
|
|
110
|
+
if (!verify) {
|
|
111
|
+
io.print(JSON.stringify(ensureSelectedSkilledProposerVenv(python, vendoredGepaPath, bakedCacheRoot)));
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
if (!readdirSync(join(bakedCacheRoot, 'venvs'), { withFileTypes: true }).some(entry => entry.isDirectory() && existsSync(join(bakedCacheRoot, 'venvs', entry.name, '.orizu-skilled-proposer-venv.json'))))
|
|
115
|
+
throw new Error('ALI_1588_VENV_MARKER_MISSING');
|
|
116
|
+
const environment = { ...process.env, ORIZU_CANDIDATE_PROPOSER: 'skilled-proposer' };
|
|
117
|
+
const launch = prepareSkilledProposerLaunch(python, 'official', environment);
|
|
118
|
+
const report = launch.report;
|
|
119
|
+
if (report.publishedVenv !== false || report.executedPipArgv !== null)
|
|
120
|
+
throw new Error('ALI_1588_DEPENDENCY_SETUP_NOT_REUSED');
|
|
121
|
+
const pythonPathEntries = getGepaPythonPathEntries(undefined);
|
|
122
|
+
if (JSON.stringify(pythonPathEntries) !== JSON.stringify(packagedPythonPathEntries))
|
|
123
|
+
throw new Error('ALI_1588_PYTHON_ROOT_MISSING');
|
|
124
|
+
const completeReport = { ...report, pythonPathEntries, marker: 'ALI_1588_DEPENDENCY_SETUP_REUSED', python: launch.python, packageRoot, manager: managerPath, lock: lockPath, vendoredGepaPath, launcherVenv: launch.environment.ORIZU_SKILLED_PROPOSER_VENV ?? null, launcherSslCertFile: launch.environment.SSL_CERT_FILE ?? null };
|
|
125
|
+
io.print(JSON.stringify(Object.fromEntries(SKILLED_PROPOSER_BAKE_REPORT_FIELDS.map(key => [key, completeReport[key]]))));
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
io.printErr?.(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
130
|
+
return 1;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
37
133
|
export function prepareSkilledProposerLaunch(python, engine, environment) {
|
|
38
134
|
if (engine !== 'official' || environment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer') {
|
|
39
135
|
return { python, environment };
|
|
@@ -56,5 +152,6 @@ export function prepareSkilledProposerLaunch(python, engine, environment) {
|
|
|
56
152
|
? join(report.venv, 'Scripts', 'python.exe')
|
|
57
153
|
: join(report.venv, 'bin', 'python'),
|
|
58
154
|
environment,
|
|
155
|
+
report,
|
|
59
156
|
};
|
|
60
157
|
}
|
package/package.json
CHANGED
|
@@ -19,6 +19,10 @@ const archiveArgumentIndex = process.argv.indexOf('--archive')
|
|
|
19
19
|
const suppliedArchive = archiveArgumentIndex === -1
|
|
20
20
|
? null
|
|
21
21
|
: process.argv[archiveArgumentIndex + 1]
|
|
22
|
+
const destinationArgumentIndex = process.argv.indexOf('--destination')
|
|
23
|
+
const suppliedDestination = destinationArgumentIndex === -1
|
|
24
|
+
? null
|
|
25
|
+
: process.argv[destinationArgumentIndex + 1]
|
|
22
26
|
const sourceSrc = resolve(sourceRoot, 'src')
|
|
23
27
|
const sourcePyproject = resolve(sourceRoot, 'pyproject.toml')
|
|
24
28
|
const sourceManifest = resolve(sourceRoot, 'manifest.json')
|
|
@@ -31,6 +35,7 @@ const GEPA_VERSION = '0.1.4'
|
|
|
31
35
|
const GEPA_RELEASE_COMMIT = '8b0ce6cd99a234f6b74daf37558a2ac0ce18f975'
|
|
32
36
|
const GEPA_WHEEL_FILENAME = 'gepa-0.1.4-py3-none-any.whl'
|
|
33
37
|
const GEPA_WHEEL_SHA256 = '12b971039599625c156d2231f6d72a29c31a22e9c237689459b5f1a3c353f532'
|
|
38
|
+
const GEPA_TREE_SHA256 = '8cb2ef66ca4212f416d1c4b23421f0de377b4997c1c50fd41ac16ca31015de99'
|
|
34
39
|
const hasSourcePackage = existsSync(sourceSrc)
|
|
35
40
|
&& existsSync(sourcePyproject)
|
|
36
41
|
&& existsSync(sourceManifest)
|
|
@@ -75,19 +80,55 @@ function collectFileHashes(root, prefix = '') {
|
|
|
75
80
|
return files
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
function
|
|
83
|
+
function fileInventoryDigest(files) {
|
|
84
|
+
const entries = Object.entries(files).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
85
|
+
return createHash('sha256').update(JSON.stringify(entries)).digest('hex')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function assertOfficialVendoredManifest(root = officialVendorRoot) {
|
|
89
|
+
const manifestPath = resolve(root, 'manifest.json')
|
|
79
90
|
let manifest
|
|
80
91
|
try {
|
|
81
|
-
manifest = JSON.parse(readFileSync(
|
|
92
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
82
93
|
} catch (error) {
|
|
83
94
|
throw new Error(`Official GEPA vendored manifest is unreadable: ${error.message}`)
|
|
84
95
|
}
|
|
85
96
|
|
|
97
|
+
if (manifest.schemaVersion !== 'gepa-vendor.v1') {
|
|
98
|
+
throw new Error('Official GEPA vendored manifest identity mismatch: schemaVersion')
|
|
99
|
+
}
|
|
100
|
+
if (!manifest.upstream || typeof manifest.upstream !== 'object' || Array.isArray(manifest.upstream)) {
|
|
101
|
+
throw new Error('Official GEPA vendored manifest identity mismatch: upstream')
|
|
102
|
+
}
|
|
103
|
+
for (const [field, expected] of [
|
|
104
|
+
['package', 'gepa'],
|
|
105
|
+
['version', GEPA_VERSION],
|
|
106
|
+
['repositoryCommit', GEPA_RELEASE_COMMIT],
|
|
107
|
+
]) {
|
|
108
|
+
if (manifest.upstream[field] !== expected) {
|
|
109
|
+
throw new Error(`Official GEPA vendored manifest identity mismatch: ${field}`)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (!manifest.upstream.wheel || typeof manifest.upstream.wheel !== 'object' || Array.isArray(manifest.upstream.wheel)) {
|
|
113
|
+
throw new Error('Official GEPA vendored manifest identity mismatch: wheel')
|
|
114
|
+
}
|
|
115
|
+
for (const [field, expected] of [['filename', GEPA_WHEEL_FILENAME], ['sha256', GEPA_WHEEL_SHA256]]) {
|
|
116
|
+
if (manifest.upstream.wheel[field] !== expected) {
|
|
117
|
+
throw new Error(`Official GEPA vendored manifest identity mismatch: wheel.${field}`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
86
121
|
if (!manifest.files || typeof manifest.files !== 'object' || Array.isArray(manifest.files)) {
|
|
87
122
|
throw new Error('Official GEPA vendored manifest is missing file hashes')
|
|
88
123
|
}
|
|
124
|
+
if (!Object.hasOwn(manifest.files, 'LICENSE')) {
|
|
125
|
+
throw new Error('Official GEPA vendored manifest is missing required LICENSE')
|
|
126
|
+
}
|
|
127
|
+
if (!Object.hasOwn(manifest.files, 'src/gepa/__init__.py')) {
|
|
128
|
+
throw new Error('Official GEPA vendored manifest is missing required GEPA package entry')
|
|
129
|
+
}
|
|
89
130
|
|
|
90
|
-
const actualFiles = collectFileHashes(
|
|
131
|
+
const actualFiles = collectFileHashes(root)
|
|
91
132
|
delete actualFiles['manifest.json']
|
|
92
133
|
const expectedEntries = Object.entries(manifest.files).sort(([left], [right]) => left.localeCompare(right))
|
|
93
134
|
const actualEntries = Object.entries(actualFiles).sort(([left], [right]) => left.localeCompare(right))
|
|
@@ -102,9 +143,28 @@ function assertOfficialVendoredManifest() {
|
|
|
102
143
|
throw new Error(`Official GEPA vendored manifest mismatch at ${expectedPath}`)
|
|
103
144
|
}
|
|
104
145
|
}
|
|
146
|
+
const treeSha256 = fileInventoryDigest(actualFiles)
|
|
147
|
+
if (treeSha256 !== GEPA_TREE_SHA256) {
|
|
148
|
+
throw new Error(`Official GEPA vendored tree digest mismatch: expected ${GEPA_TREE_SHA256}, got ${treeSha256}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
marker: 'ALI_1588_GEPA_VENDOR_VERIFIED',
|
|
153
|
+
schemaVersion: manifest.schemaVersion,
|
|
154
|
+
wheelSha256: manifest.upstream.wheel.sha256,
|
|
155
|
+
treeSha256,
|
|
156
|
+
fileCount: actualEntries.length,
|
|
157
|
+
}
|
|
105
158
|
}
|
|
106
159
|
|
|
107
|
-
function
|
|
160
|
+
function writeValidationReceipt(receipt) {
|
|
161
|
+
if (!receipt || receipt.marker !== 'ALI_1588_GEPA_VENDOR_VERIFIED') {
|
|
162
|
+
throw new Error('Official GEPA vendored manifest validation produced no receipt')
|
|
163
|
+
}
|
|
164
|
+
process.stdout.write(`${JSON.stringify(receipt)}\n`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function extractPinnedGepa(archivePath, destinationRoot) {
|
|
108
168
|
const extractor = String.raw`
|
|
109
169
|
from pathlib import Path, PurePosixPath
|
|
110
170
|
import shutil
|
|
@@ -132,7 +192,7 @@ with zipfile.ZipFile(archive_path) as archive:
|
|
|
132
192
|
with archive.open(member) as source, destination.open('wb') as output:
|
|
133
193
|
shutil.copyfileobj(source, output)
|
|
134
194
|
`
|
|
135
|
-
const result = spawnSync('python3', ['-c', extractor, archivePath,
|
|
195
|
+
const result = spawnSync('python3', ['-c', extractor, archivePath, destinationRoot], {
|
|
136
196
|
encoding: 'utf8',
|
|
137
197
|
})
|
|
138
198
|
if (result.status !== 0) {
|
|
@@ -141,14 +201,14 @@ with zipfile.ZipFile(archive_path) as archive:
|
|
|
141
201
|
}
|
|
142
202
|
}
|
|
143
203
|
|
|
144
|
-
function vendorOfficialGepa(archivePath) {
|
|
204
|
+
function vendorOfficialGepa(archivePath, destinationRoot = officialVendorRoot) {
|
|
145
205
|
assertPinnedGepaArchive(archivePath)
|
|
146
|
-
rmSync(
|
|
147
|
-
mkdirSync(
|
|
148
|
-
extractPinnedGepa(archivePath)
|
|
206
|
+
rmSync(destinationRoot, { recursive: true, force: true })
|
|
207
|
+
mkdirSync(destinationRoot, { recursive: true })
|
|
208
|
+
extractPinnedGepa(archivePath, destinationRoot)
|
|
149
209
|
|
|
150
|
-
const files = collectFileHashes(
|
|
151
|
-
writeFileSync(
|
|
210
|
+
const files = collectFileHashes(destinationRoot)
|
|
211
|
+
writeFileSync(resolve(destinationRoot, 'manifest.json'), `${JSON.stringify({
|
|
152
212
|
schemaVersion: 'gepa-vendor.v1',
|
|
153
213
|
upstream: {
|
|
154
214
|
package: 'gepa',
|
|
@@ -161,11 +221,20 @@ function vendorOfficialGepa(archivePath) {
|
|
|
161
221
|
},
|
|
162
222
|
files,
|
|
163
223
|
}, null, 2)}\n`)
|
|
224
|
+
return assertOfficialVendoredManifest(destinationRoot)
|
|
164
225
|
}
|
|
165
226
|
|
|
166
227
|
if (archiveArgumentIndex !== -1 && (!suppliedArchive || suppliedArchive.startsWith('--'))) {
|
|
167
228
|
throw new Error('Expected a source archive path after --archive')
|
|
168
229
|
}
|
|
230
|
+
if (destinationArgumentIndex !== -1 && (!suppliedDestination || suppliedDestination.startsWith('--'))) {
|
|
231
|
+
throw new Error('Expected a destination directory after --destination')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (suppliedArchive && suppliedDestination) {
|
|
235
|
+
writeValidationReceipt(vendorOfficialGepa(suppliedArchive, suppliedDestination))
|
|
236
|
+
process.exit(0)
|
|
237
|
+
}
|
|
169
238
|
|
|
170
239
|
if (shouldRefresh) {
|
|
171
240
|
if (!suppliedArchive) {
|
|
@@ -190,7 +259,7 @@ if (shouldClean) {
|
|
|
190
259
|
|
|
191
260
|
if (!hasSourcePackage || !hasConnectorSourcePackage) {
|
|
192
261
|
if (hasVendoredPackage && hasVendoredConnectorPackage && hasOfficialVendoredPackage) {
|
|
193
|
-
assertOfficialVendoredManifest()
|
|
262
|
+
writeValidationReceipt(assertOfficialVendoredManifest())
|
|
194
263
|
process.exit(0)
|
|
195
264
|
}
|
|
196
265
|
if (hasVendoredPackage || hasVendoredConnectorPackage || hasOfficialVendoredPackage) {
|
package/src/cleanup.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type Cleanup = () => void
|
|
2
|
+
|
|
3
|
+
function collectCleanupFailures(cleanups: readonly Cleanup[]): unknown[] {
|
|
4
|
+
const failures: unknown[] = []
|
|
5
|
+
for (const cleanup of cleanups) {
|
|
6
|
+
try {
|
|
7
|
+
cleanup()
|
|
8
|
+
} catch (error) {
|
|
9
|
+
failures.push(error)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return failures
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Attempt every disposer even when an earlier cleanup fails. */
|
|
16
|
+
export function cleanupAll(
|
|
17
|
+
cleanups: readonly Cleanup[],
|
|
18
|
+
message: string
|
|
19
|
+
): void {
|
|
20
|
+
const failures = collectCleanupFailures(cleanups)
|
|
21
|
+
if (failures.length > 0) {
|
|
22
|
+
throw new AggregateError(failures, message)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Preserve the operation failure together with every cleanup failure. */
|
|
27
|
+
export function throwWithCleanup(
|
|
28
|
+
operationError: unknown,
|
|
29
|
+
cleanups: readonly Cleanup[],
|
|
30
|
+
message: string
|
|
31
|
+
): never {
|
|
32
|
+
const cleanupFailures = collectCleanupFailures(cleanups)
|
|
33
|
+
if (cleanupFailures.length > 0) {
|
|
34
|
+
throw new AggregateError([operationError, ...cleanupFailures], message)
|
|
35
|
+
}
|
|
36
|
+
throw operationError
|
|
37
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'fs'
|
|
2
2
|
|
|
3
|
+
import { normalizedSkilledProposerConfig } from './skilled-proposer-config.js'
|
|
4
|
+
|
|
3
5
|
export type GepaEngine = 'official' | 'legacy'
|
|
4
6
|
|
|
5
7
|
export interface GepaEngineDispatch {
|
|
@@ -179,6 +181,7 @@ const CONTROLLED_ENVIRONMENT_NAMES = new Set([
|
|
|
179
181
|
// translation prevents an inherited environment from selecting a proposer
|
|
180
182
|
// on an otherwise byte-for-byte normal official-GEPA launch.
|
|
181
183
|
'ORIZU_CANDIDATE_PROPOSER',
|
|
184
|
+
'ORIZU_SKILLED_PROPOSER_CONFIG',
|
|
182
185
|
'ORIZU_COMPONENT_SELECTOR',
|
|
183
186
|
])
|
|
184
187
|
|
|
@@ -208,6 +211,7 @@ function translateOfficialOptions(args: string[], project: string, environment:
|
|
|
208
211
|
connectorEnvironment.ORIZU_PROJECT = project
|
|
209
212
|
|
|
210
213
|
let metadata: string | undefined
|
|
214
|
+
let candidateProposerConfig: string | undefined
|
|
211
215
|
let hasInstructionSet = false
|
|
212
216
|
let hasCandidateVersion = false
|
|
213
217
|
const budgetFlags: string[] = []
|
|
@@ -240,6 +244,12 @@ function translateOfficialOptions(args: string[], project: string, environment:
|
|
|
240
244
|
index = parsed.nextIndex
|
|
241
245
|
continue
|
|
242
246
|
}
|
|
247
|
+
if (flag === '--candidate-proposer-config') {
|
|
248
|
+
const parsed = optionValueOrThrow(args, index, flag)
|
|
249
|
+
candidateProposerConfig = parsed.value
|
|
250
|
+
index = parsed.nextIndex
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
243
253
|
const option = VALUE_OPTIONS_BY_FLAG.get(flag)
|
|
244
254
|
if (option) {
|
|
245
255
|
const parsed = optionValueOrThrow(args, index, flag, option.allowEmpty)
|
|
@@ -297,6 +307,16 @@ function translateOfficialOptions(args: string[], project: string, environment:
|
|
|
297
307
|
&& connectorEnvironment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer') {
|
|
298
308
|
throw new Error('--proposal-max-calls and --proposal-max-tokens require --candidate-proposer skilled-proposer')
|
|
299
309
|
}
|
|
310
|
+
if (candidateProposerConfig !== undefined) {
|
|
311
|
+
if (connectorEnvironment.ORIZU_CANDIDATE_PROPOSER !== 'skilled-proposer') {
|
|
312
|
+
throw new Error('--candidate-proposer-config requires --candidate-proposer skilled-proposer')
|
|
313
|
+
}
|
|
314
|
+
connectorEnvironment.ORIZU_SKILLED_PROPOSER_CONFIG = normalizedSkilledProposerConfig(candidateProposerConfig)
|
|
315
|
+
}
|
|
316
|
+
if (connectorEnvironment.ORIZU_CANDIDATE_PROPOSER === 'skilled-proposer'
|
|
317
|
+
&& connectorEnvironment.ORIZU_REFLECTION_PROMPT_TEMPLATE !== undefined) {
|
|
318
|
+
throw new Error('--reflection-prompt-template is incompatible with --candidate-proposer skilled-proposer')
|
|
319
|
+
}
|
|
300
320
|
|
|
301
321
|
const reflectionModel = connectorEnvironment.ORIZU_REFLECTION_MODEL ?? 'anthropic/claude-opus-4-7'
|
|
302
322
|
if (!reflectionModel.startsWith('openai/') && connectorEnvironment.ORIZU_REFLECTION_MAX_TOKENS === undefined) {
|
|
@@ -321,7 +341,7 @@ function validatedMetadata(raw: string | undefined): string {
|
|
|
321
341
|
export function dispatchGepaEngine(args: string[], project: string, environment: NodeJS.ProcessEnv): GepaEngineDispatch {
|
|
322
342
|
const selected = removeEngine(args)
|
|
323
343
|
if (selected.engine === 'legacy') {
|
|
324
|
-
for (const flag of ['--instruction-set', '--model-config', '--component-selector', '--max-candidate-proposals', '--candidate-proposer', '--proposal-max-calls', '--proposal-max-tokens']) {
|
|
344
|
+
for (const flag of ['--instruction-set', '--model-config', '--component-selector', '--max-candidate-proposals', '--candidate-proposer', '--candidate-proposer-config', '--proposal-max-calls', '--proposal-max-tokens']) {
|
|
325
345
|
if (selected.args.some(argument => argument === flag || argument.startsWith(`${flag}=`))) {
|
|
326
346
|
throw new Error(`${flag} is supported by the official GEPA engine only`)
|
|
327
347
|
}
|
package/src/gepa-python-paths.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'fs'
|
|
2
|
+
import { execFileSync } from 'child_process'
|
|
2
3
|
import { fileURLToPath } from 'url'
|
|
3
4
|
|
|
4
5
|
import { isOrizuSourceCheckout } from '../scripts/orizu-source-checkout.mjs'
|
|
@@ -34,14 +35,31 @@ export function bundledOrizuGepaConnectorPythonPath(): string | null {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export function bundledOfficialGepaPythonPath(): string | null {
|
|
37
|
-
const sourceArchive = fileURLToPath(new URL('../gepa-python-source.zip', import.meta.url))
|
|
38
38
|
const vendor = fileURLToPath(new URL('../vendor/gepa-python/src', import.meta.url))
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
if (prefersLiveSource()) {
|
|
40
|
+
const requiredVendorFiles = [
|
|
41
|
+
fileURLToPath(new URL('../vendor/gepa-python/manifest.json', import.meta.url)),
|
|
42
|
+
fileURLToPath(new URL('../vendor/gepa-python/src/gepa/__init__.py', import.meta.url)),
|
|
43
|
+
fileURLToPath(new URL('../vendor/gepa-python/src/gepa/adapters/optimize_anything_adapter/optimize_anything_adapter.py', import.meta.url)),
|
|
44
|
+
]
|
|
45
|
+
if (!requiredVendorFiles.every(existsSync)) {
|
|
46
|
+
const vendoringScript = fileURLToPath(new URL('../scripts/vendor-gepa-python.mjs', import.meta.url))
|
|
47
|
+
try {
|
|
48
|
+
execFileSync(process.execPath, [vendoringScript], { stdio: 'pipe' })
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const details = error instanceof Error ? error.message : String(error)
|
|
51
|
+
throw new Error(`Unable to materialize bundled official GEPA: ${details}`)
|
|
52
|
+
}
|
|
53
|
+
if (!requiredVendorFiles.every(existsSync)) {
|
|
54
|
+
throw new Error('Unable to materialize bundled official GEPA: extracted tree is incomplete')
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
43
58
|
|
|
44
|
-
|
|
59
|
+
// zipimport cannot resolve GEPA's namespace-package adapters on every
|
|
60
|
+
// supported Python minor. Both source and published CLIs execute only the
|
|
61
|
+
// verified tree materialized from the pinned wheel.
|
|
62
|
+
return resolveGepaPythonPath([vendor])
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
export function getGepaPythonPathEntries(existingPythonPath: string | undefined): string[] {
|
package/src/help.ts
CHANGED
|
@@ -89,6 +89,40 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
|
|
|
89
89
|
},
|
|
90
90
|
{ path: ['instructions', 'archive'], aliases: [['instruction-sets', 'archive']], usage: 'orizu instructions archive <slug-or-exact-name> --project <team/project> [--json]', summary: 'Archive an instruction set without changing resolution or sync.', group: 'Instruction sets', options: [{ name: '--project <team/project>', help: 'Project slug.', required: true }, { name: '--json', help: 'Print JSON.' }], examples: ['orizu instructions archive planner --project core/evals'] },
|
|
91
91
|
{ path: ['instructions', 'restore'], aliases: [['instruction-sets', 'restore']], usage: 'orizu instructions restore <slug-or-exact-name> --project <team/project> [--json]', summary: 'Restore an archived instruction set.', group: 'Instruction sets', options: [{ name: '--project <team/project>', help: 'Project slug.', required: true }, { name: '--json', help: 'Print JSON.' }], examples: ['orizu instructions restore planner --project core/evals'] },
|
|
92
|
+
{
|
|
93
|
+
path: ['instructions', 'scorers', 'set-headline'],
|
|
94
|
+
aliases: [['instruction-sets', 'scorers', 'set-headline']],
|
|
95
|
+
usage: 'orizu instructions scorers set-headline <set> --key <component-key> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]',
|
|
96
|
+
summary: 'Bind the headline scorer for one instruction-set component.',
|
|
97
|
+
group: 'Instruction sets',
|
|
98
|
+
options: [
|
|
99
|
+
{ name: '--key <component-key>', help: 'Instruction-set component key.', required: true },
|
|
100
|
+
{ name: '--scorer-version <id>', help: 'Scorer version to bind.', required: true },
|
|
101
|
+
{ name: '--dataset-version <id>', help: 'Default dataset version.' },
|
|
102
|
+
{ name: '--split-set <id>', help: 'Default dataset split set.' },
|
|
103
|
+
{ name: '--split <name>', help: 'Default split name.' },
|
|
104
|
+
{ name: '--project <team/project>', help: 'Project slug.' },
|
|
105
|
+
{ name: '--json', help: 'Print JSON.' },
|
|
106
|
+
],
|
|
107
|
+
examples: ['orizu instructions scorers set-headline planner --key system --scorer-version <id> --project core/evals'],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
path: ['instructions', 'scorers', 'add'],
|
|
111
|
+
aliases: [['instruction-sets', 'scorers', 'add']],
|
|
112
|
+
usage: 'orizu instructions scorers add <set> --key <component-key> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]',
|
|
113
|
+
summary: 'Track an additional scorer for one instruction-set component.',
|
|
114
|
+
group: 'Instruction sets',
|
|
115
|
+
options: [
|
|
116
|
+
{ name: '--key <component-key>', help: 'Instruction-set component key.', required: true },
|
|
117
|
+
{ name: '--scorer-version <id>', help: 'Scorer version to bind.', required: true },
|
|
118
|
+
{ name: '--dataset-version <id>', help: 'Default dataset version.' },
|
|
119
|
+
{ name: '--split-set <id>', help: 'Default dataset split set.' },
|
|
120
|
+
{ name: '--split <name>', help: 'Default split name.' },
|
|
121
|
+
{ name: '--project <team/project>', help: 'Project slug.' },
|
|
122
|
+
{ name: '--json', help: 'Print JSON.' },
|
|
123
|
+
],
|
|
124
|
+
examples: ['orizu instructions scorers add planner --key system --scorer-version <id> --project core/evals'],
|
|
125
|
+
},
|
|
92
126
|
{
|
|
93
127
|
path: ['login'],
|
|
94
128
|
usage: 'orizu login [--no-prompt-if-logged-in]',
|
|
@@ -119,7 +153,7 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
|
|
|
119
153
|
},
|
|
120
154
|
{
|
|
121
155
|
path: ['log'],
|
|
122
|
-
usage: 'orizu log <event_type> --run-id <id> --sequence <n> --payload @event.json',
|
|
156
|
+
usage: 'orizu log <event_type> --run-id <id> --sequence <n> --payload @event.json [--event-id <id>] [--event-layer <layer>] [--optimizer-family <family>] [--iteration <n>] [--candidate-id <id>] [--parent-candidate-id <id>] [--child-candidate-id <id>]',
|
|
123
157
|
summary: 'Submit an optimization event payload.',
|
|
124
158
|
group: 'Optimizations',
|
|
125
159
|
},
|
|
@@ -413,18 +447,6 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
|
|
|
413
447
|
summary: 'Move or set a non-production prompt label; use instructions profiles promote for production.',
|
|
414
448
|
group: 'Prompts and judges',
|
|
415
449
|
},
|
|
416
|
-
{
|
|
417
|
-
path: ['prompts', 'scorers', 'set-headline'],
|
|
418
|
-
usage: 'orizu prompts scorers set-headline <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]',
|
|
419
|
-
summary: 'Bind the headline scorer for a prompt.',
|
|
420
|
-
group: 'Scorers and runners',
|
|
421
|
-
},
|
|
422
|
-
{
|
|
423
|
-
path: ['prompts', 'scorers', 'add'],
|
|
424
|
-
usage: 'orizu prompts scorers add <prompt-id> --scorer-version <id> [--dataset-version <id> --split-set <id> --split <name>] [--project <team/project>] [--json]',
|
|
425
|
-
summary: 'Track an additional scorer for a prompt.',
|
|
426
|
-
group: 'Scorers and runners',
|
|
427
|
-
},
|
|
428
450
|
{
|
|
429
451
|
path: ['judges', 'list'],
|
|
430
452
|
usage: 'orizu judges list --project <team/project> [--status active|archived|all]',
|
|
@@ -578,8 +600,8 @@ export const COMMAND_DOCS: CliCommandDoc[] = [
|
|
|
578
600
|
},
|
|
579
601
|
{
|
|
580
602
|
path: ['optimizations', 'run-gepa'],
|
|
581
|
-
usage: 'orizu optimizations run-gepa --project <team/project> --optimizer-version-id <id> (--candidate-version-id <id> | --instruction-set <name> --model-config <identity> [--component-selector round-robin|all]) --runner-version-id <id> --candidate-runner-dir <dir> --scorer-version-id <id> --scorer-runner-version-id <id> --scorer-runner-dir <dir> [--engine official|legacy] [--scorer-input-contract gepa|flat_row] [--scorer-candidate-field <row-field>] [--allow-degenerate-seed] --dataset-version-id <id> --split-set-id <id> [--train-split train] [--val-split validation] [--budget auto|light|medium|heavy | --max-metric-calls <n> | --max-full-evals <n> | --max-iterations <n> | --max-candidate-proposals <n>] [--num-threads auto|N] [--reflection-retry-attempts N] [--reflection-http-timeout-seconds N] [--log-dir logs]',
|
|
582
|
-
summary: 'Run official GEPA locally and stream events
|
|
603
|
+
usage: 'orizu optimizations run-gepa [--hosted [--launch-intent-id <uuid>]] --project <team/project> --optimizer-version-id <id> (--candidate-version-id <id> | --instruction-set <name> --model-config <identity> [--component-selector round-robin|all]) --runner-version-id <id> [--candidate-runner-dir <dir>] --scorer-version-id <id> --scorer-runner-version-id <id> [--scorer-runner-dir <dir>] [--engine official|legacy] [--candidate-proposer skilled-proposer [--candidate-proposer-config <@file|path>] [--proposal-max-calls <n>] [--proposal-max-tokens <n>]] [--scorer-input-contract gepa|flat_row] [--scorer-candidate-field <row-field>] [--allow-degenerate-seed] [--seed <n>] --dataset-version-id <id> --split-set-id <id> [--train-split train] [--val-split validation] [--minibatch-size <n>] [--candidate-selection-strategy pareto|current_best|epsilon_greedy] [--epsilon <n>] [--objective <text>] [--budget auto|light|medium|heavy | --max-metric-calls <n> | --max-full-evals <n> | --max-iterations <n> | --max-candidate-proposals <n>] [--num-threads auto|N] [--reflection-model <provider/model>] [--reflection-temperature <n>] [--reflection-prompt-template <text|@file>] [--reflection-max-tokens N] [--reflection-provider-settings <json|@file>] [--reflection-retry-attempts N] [--reflection-http-timeout-seconds N] [--skip-perfect-parent-reflection|--no-skip-perfect-parent-reflection] [--disable-evaluation-cache] [--auto-promote] [--promotion-label <label>] [--log-row-snapshots] [--metadata <json>] [--python <command>] [--log-dir logs] [--no-local-log] [--json]',
|
|
604
|
+
summary: 'Run official GEPA locally and stream events, or use --hosted for a queued Orizu launch. Hosted mode does not require --candidate-runner-dir or --scorer-runner-dir, accepts only a named --budget preset, and supports response-loss recovery with --launch-intent-id. Local runner version ids materialize registered bytes, while optional runner dirs are additionally byte-verified before execution. Pass --engine legacy only for the frozen safety hatch. Skilled-proposer is official-only, its config requires that selection, and the selection cannot be combined with --reflection-prompt-template. --instruction-set requires --model-config and cannot be combined with --candidate-version-id; round-robin updates one component-map entry per round and all updates every component. --max-candidate-proposals is official-engine-only. Validates the scorer contract on the seed before iterating; judge runners built for flat-row score runs need --scorer-input-contract flat_row (ALI-1158).',
|
|
583
605
|
group: 'Optimizations',
|
|
584
606
|
},
|
|
585
607
|
{
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { runHostedGepaOptimization } from './hosted-gepa-cli.js'
|
|
2
|
+
import { hostedCommand } from './hosted-session-cli.js'
|
|
3
|
+
interface HostedCommandContext { environment: NodeJS.ProcessEnv; getArg: (name: string) => string | null; json: boolean; printJson: (value: Record<string, unknown>) => void; printLine: (value?: string) => void; printErr: (value: string) => void; resolveProjectSlug: (projectArg: string | null) => Promise<string>; setExitCode: (code: number) => void }
|
|
4
|
+
export async function dispatchHostedCommands(args: string[], context: HostedCommandContext): Promise<boolean> { const [command, subcommand] = args; if (command === 'optimizations' && subcommand === 'run-gepa' && args.includes('--hosted')) { const project = context.getArg('--project') || await context.resolveProjectSlug(null); await runHostedGepaOptimization({ args: args.slice(2), project, environment: context.environment, json: context.json, printJson: context.printJson, printLine: context.printLine }); return true } if (command === 'internal' || (command === 'session' && subcommand === 'start' && args.includes('--hosted'))) { context.setExitCode(await hostedCommand(args, { json: context.json, print: context.printLine, printErr: context.printErr })); return true } return false }
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import { dispatchGepaEngine } from './gepa-engine-dispatch.js'
|
|
4
|
+
import { parseHostedGepaNumericValue } from './hosted-gepa-numeric-values.js'
|
|
5
|
+
import { authedFetch } from './http.js'
|
|
6
|
+
import { hostedProviderSettingsError } from './hosted-provider-settings.js'
|
|
7
|
+
import { parseJsonResponse } from './json-response.js'
|
|
8
|
+
|
|
9
|
+
interface HostedGepaOptions {
|
|
10
|
+
args: string[]
|
|
11
|
+
project: string
|
|
12
|
+
environment: NodeJS.ProcessEnv
|
|
13
|
+
json: boolean
|
|
14
|
+
printJson: (value: Record<string, unknown>) => void
|
|
15
|
+
printLine: (value: string) => void
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function argumentValue(args: string[], name: string): string | null {
|
|
19
|
+
const index = args.indexOf(name)
|
|
20
|
+
if (index >= 0 && index + 1 < args.length) return args[index + 1]
|
|
21
|
+
return args.find(argument => argument.startsWith(`${name}=`))?.slice(name.length + 1) ?? null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function dispatchArgs(args: string[]): string[] {
|
|
25
|
+
const filtered: string[] = []
|
|
26
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
27
|
+
if (args[index] === '--hosted') continue
|
|
28
|
+
if (args[index] === '--launch-intent-id') { index += 1; continue }
|
|
29
|
+
if (args[index].startsWith('--launch-intent-id=')) continue
|
|
30
|
+
filtered.push(args[index])
|
|
31
|
+
}
|
|
32
|
+
return filtered
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function runHostedGepaOptimization(options: HostedGepaOptions): Promise<void> {
|
|
36
|
+
const hasLaunchIntent = options.args.some(argument =>
|
|
37
|
+
argument === '--launch-intent-id' || argument.startsWith('--launch-intent-id='))
|
|
38
|
+
const explicitLaunchIntent = argumentValue(options.args, '--launch-intent-id')
|
|
39
|
+
if (hasLaunchIntent && (!explicitLaunchIntent || explicitLaunchIntent.startsWith('-')
|
|
40
|
+
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(explicitLaunchIntent))) {
|
|
41
|
+
throw new Error('--launch-intent-id must be a UUIDv4 value when provided.')
|
|
42
|
+
}
|
|
43
|
+
const args = dispatchArgs(options.args)
|
|
44
|
+
if (['--max-iterations', '--max-full-evals', '--max-metric-calls', '--max-candidate-proposals']
|
|
45
|
+
.some(flag => args.some(argument => argument === flag || argument.startsWith(`${flag}=`)))) {
|
|
46
|
+
throw new Error('Hosted optimization requires a named --budget preset; numeric budget controls are not accepted.')
|
|
47
|
+
}
|
|
48
|
+
const dispatch = dispatchGepaEngine(args, options.project, options.environment)
|
|
49
|
+
if (dispatch.engine !== 'official') throw new Error('Hosted optimization requires the official GEPA engine.')
|
|
50
|
+
const env = dispatch.environment
|
|
51
|
+
const required = [
|
|
52
|
+
['ORIZU_OPTIMIZER_VERSION_ID', '--optimizer-version-id'], ['ORIZU_RUNNER_VERSION_ID', '--runner-version-id'],
|
|
53
|
+
['ORIZU_SCORER_VERSION_ID', '--scorer-version-id'], ['ORIZU_SCORER_RUNNER_VERSION_ID', '--scorer-runner-version-id'],
|
|
54
|
+
['ORIZU_DATASET_VERSION_ID', '--dataset-version-id'], ['ORIZU_SPLIT_SET_ID', '--split-set-id'],
|
|
55
|
+
] as const
|
|
56
|
+
for (const [name, flag] of required) if (!env[name]) throw new Error(`${flag} is required for hosted optimization.`)
|
|
57
|
+
const providerSettings = JSON.parse(env.ORIZU_REFLECTION_PROVIDER_SETTINGS ?? '{}') as Record<string, unknown>
|
|
58
|
+
const providerSettingsError = hostedProviderSettingsError(providerSettings,
|
|
59
|
+
env.ORIZU_REFLECTION_MODEL ?? 'anthropic/', env.ORIZU_REFLECTION_TEMPERATURE)
|
|
60
|
+
if (providerSettingsError) throw new Error(providerSettingsError)
|
|
61
|
+
const optional: Record<string, unknown> = {}
|
|
62
|
+
const fields: Array<[string, string, 'number' | 'json' | 'boolean' | 'string']> = [
|
|
63
|
+
['candidateProposer', 'ORIZU_CANDIDATE_PROPOSER', 'string'], ['candidateProposerConfig', 'ORIZU_SKILLED_PROPOSER_CONFIG', 'json'],
|
|
64
|
+
['proposalMaxCalls', 'ORIZU_PROPOSAL_MAX_CALLS', 'number'], ['proposalMaxTokens', 'ORIZU_PROPOSAL_MAX_TOKENS', 'number'],
|
|
65
|
+
['minibatchSize', 'ORIZU_MINIBATCH_SIZE', 'number'], ['numThreads', 'ORIZU_NUM_THREADS', 'number'],
|
|
66
|
+
['candidateSelectionStrategy', 'ORIZU_CANDIDATE_SELECTION_STRATEGY', 'string'], ['epsilon', 'ORIZU_EPSILON', 'number'],
|
|
67
|
+
['reflectionModel', 'ORIZU_REFLECTION_MODEL', 'string'], ['reflectionTemperature', 'ORIZU_REFLECTION_TEMPERATURE', 'number'],
|
|
68
|
+
['reflectionMaxTokens', 'ORIZU_REFLECTION_MAX_TOKENS', 'number'], ['reflectionRetryAttempts', 'ORIZU_REFLECTION_RETRY_ATTEMPTS', 'number'],
|
|
69
|
+
['reflectionHttpTimeoutSeconds', 'ORIZU_REFLECTION_HTTP_TIMEOUT_SECONDS', 'number'],
|
|
70
|
+
['reflectionPromptTemplate', 'ORIZU_REFLECTION_PROMPT_TEMPLATE', 'string'],
|
|
71
|
+
['reflectionProviderSettings', 'ORIZU_REFLECTION_PROVIDER_SETTINGS', 'json'], ['objective', 'ORIZU_OBJECTIVE', 'string'],
|
|
72
|
+
['seed', 'ORIZU_SEED', 'number'], ['disableEvaluationCache', 'ORIZU_DISABLE_EVALUATION_CACHE', 'boolean'],
|
|
73
|
+
['allowDegenerateSeed', 'ORIZU_ALLOW_DEGENERATE_SEED', 'boolean'],
|
|
74
|
+
['skipPerfectParentReflection', 'ORIZU_SKIP_PERFECT_PARENT_REFLECTION', 'boolean'],
|
|
75
|
+
]
|
|
76
|
+
for (const [field, name, kind] of fields) {
|
|
77
|
+
if (env[name] === undefined) continue
|
|
78
|
+
optional[field] = kind === 'number' ? parseHostedGepaNumericValue(field, env[name]!)
|
|
79
|
+
: kind === 'json' ? JSON.parse(env[name]!) : kind === 'boolean' ? env[name] === '1' : env[name]
|
|
80
|
+
}
|
|
81
|
+
const launchIntentId = explicitLaunchIntent || randomUUID()
|
|
82
|
+
const body = {
|
|
83
|
+
hosted: true, launchIntentId, optimizerVersionId: env.ORIZU_OPTIMIZER_VERSION_ID,
|
|
84
|
+
promptVersionIds: env.ORIZU_PROMPT_VERSION_ID ? [env.ORIZU_PROMPT_VERSION_ID] : [],
|
|
85
|
+
...(env.ORIZU_INSTRUCTION_SET_NAME ? { instructionSetName: env.ORIZU_INSTRUCTION_SET_NAME,
|
|
86
|
+
modelConfigIdentity: env.ORIZU_MODEL_CONFIG_IDENTITY, componentSelector: env.ORIZU_COMPONENT_SELECTOR } : {}),
|
|
87
|
+
runnerVersionId: env.ORIZU_RUNNER_VERSION_ID,
|
|
88
|
+
scorers: [{ scorerVersionId: env.ORIZU_SCORER_VERSION_ID,
|
|
89
|
+
scorerRunnerVersionId: env.ORIZU_SCORER_RUNNER_VERSION_ID, role: 'selection', scorerConfig: {} }],
|
|
90
|
+
datasetVersionId: env.ORIZU_DATASET_VERSION_ID, splitSetId: env.ORIZU_SPLIT_SET_ID,
|
|
91
|
+
trainSplitName: env.ORIZU_TRAIN_SPLIT ?? 'train', validationSplitName: env.ORIZU_VALIDATION_SPLIT ?? 'validation',
|
|
92
|
+
engine: 'official', scorerInputContract: env.ORIZU_SCORER_INPUT_CONTRACT ?? 'gepa',
|
|
93
|
+
scorerCandidateField: env.ORIZU_SCORER_CANDIDATE_FIELD ?? null, budget: env.ORIZU_BUDGET ?? 'auto', ...optional,
|
|
94
|
+
}
|
|
95
|
+
const response = await authedFetch(`/api/cli/optimization-runs?project=${encodeURIComponent(options.project)}`, {
|
|
96
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
97
|
+
})
|
|
98
|
+
const data = await parseJsonResponse<Record<string, unknown>>(response, 'Hosted optimization launch')
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
if (options.json) { options.printJson(data); process.exitCode = 1; return }
|
|
101
|
+
throw new Error(JSON.stringify(data))
|
|
102
|
+
}
|
|
103
|
+
if (options.json) { options.printJson(data); return }
|
|
104
|
+
options.printLine('Queued hosted optimization.')
|
|
105
|
+
options.printLine(`Launch intent: ${launchIntentId}`)
|
|
106
|
+
options.printLine(String(data.monitorUrl))
|
|
107
|
+
}
|