orizu 0.3.0 → 0.3.2

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/dist/index.js CHANGED
@@ -1485,8 +1485,11 @@ function removeFlagWithValue(args, flag) {
1485
1485
  return filtered;
1486
1486
  }
1487
1487
  function bundledOrizuGepaPythonPath() {
1488
- const candidate = fileURLToPath(new URL('../../orizu-gepa-python/src', import.meta.url));
1489
- return existsSync(candidate) ? candidate : null;
1488
+ const candidates = [
1489
+ fileURLToPath(new URL('../vendor/orizu-gepa-python/src', import.meta.url)),
1490
+ fileURLToPath(new URL('../../orizu-gepa-python/src', import.meta.url)),
1491
+ ];
1492
+ return candidates.find(candidate => existsSync(candidate)) ?? null;
1490
1493
  }
1491
1494
  async function runGepaOptimization() {
1492
1495
  const project = getArg('--project') || await resolveProjectSlug(null);
@@ -2664,6 +2667,47 @@ function readRunnerManifest(runnerDir) {
2664
2667
  supports_body_kind: supportedBodyKinds,
2665
2668
  };
2666
2669
  }
2670
+ const RUNNER_TIMEOUT_MS = 120_000;
2671
+ const RUNNER_OUTPUT_MAX_BYTES = 2 * 1024 * 1024;
2672
+ const RUNNER_ARTIFACT_MAX_BYTES = 25 * 1024 * 1024;
2673
+ const RUNNER_ENV_ALLOWLIST = new Set([
2674
+ 'PATH',
2675
+ 'SystemRoot',
2676
+ 'WINDIR',
2677
+ 'HOME',
2678
+ 'TMPDIR',
2679
+ 'TEMP',
2680
+ 'TMP',
2681
+ 'LANG',
2682
+ 'LC_ALL',
2683
+ 'PYTHONPATH',
2684
+ 'NODE_PATH',
2685
+ 'ANTHROPIC_API_KEY',
2686
+ 'OPENAI_API_KEY',
2687
+ 'GEMINI_API_KEY',
2688
+ 'GOOGLE_API_KEY',
2689
+ ]);
2690
+ function runnerSubprocessEnv(inputPath, outputPath) {
2691
+ const env = {};
2692
+ for (const key of RUNNER_ENV_ALLOWLIST) {
2693
+ const value = process.env[key];
2694
+ if (value !== undefined) {
2695
+ env[key] = value;
2696
+ }
2697
+ }
2698
+ env.ORIZU_RUNNER_INPUT_PATH = inputPath;
2699
+ env.ORIZU_RUNNER_OUTPUT_PATH = outputPath;
2700
+ return env;
2701
+ }
2702
+ function boundedRunnerOutput(value) {
2703
+ if (!value)
2704
+ return '';
2705
+ const buffer = Buffer.isBuffer(value) ? value : Buffer.from(value);
2706
+ if (buffer.byteLength <= RUNNER_OUTPUT_MAX_BYTES) {
2707
+ return buffer.toString('utf8');
2708
+ }
2709
+ return `${buffer.subarray(0, RUNNER_OUTPUT_MAX_BYTES).toString('utf8')}\n[truncated]`;
2710
+ }
2667
2711
  async function materializeRunnerVersion(runnerVersionId) {
2668
2712
  const response = await authedFetch(`/api/cli/runner-versions/${encodeURIComponent(runnerVersionId)}/download`);
2669
2713
  if (!response.ok) {
@@ -2672,7 +2716,12 @@ async function materializeRunnerVersion(runnerVersionId) {
2672
2716
  const tempDir = mkdtempSync(join(tmpdir(), 'orizu-runner-version-'));
2673
2717
  const zipPath = join(tempDir, 'runner.zip');
2674
2718
  const runnerDir = join(tempDir, 'runner');
2675
- writeFileSync(zipPath, new Uint8Array(await response.arrayBuffer()));
2719
+ const zipBytes = new Uint8Array(await response.arrayBuffer());
2720
+ if (zipBytes.byteLength > RUNNER_ARTIFACT_MAX_BYTES) {
2721
+ rmSync(tempDir, { recursive: true, force: true });
2722
+ throw new Error(`Runner artifact exceeds ${RUNNER_ARTIFACT_MAX_BYTES} bytes`);
2723
+ }
2724
+ writeFileSync(zipPath, zipBytes);
2676
2725
  const result = spawnSync('unzip', ['-q', zipPath, '-d', runnerDir], {
2677
2726
  encoding: 'utf8',
2678
2727
  });
@@ -2778,18 +2827,16 @@ async function runnersExec() {
2778
2827
  }));
2779
2828
  const result = spawnSync(manifest.command[0], manifest.command.slice(1), {
2780
2829
  cwd: runnerDir,
2781
- env: {
2782
- ...process.env,
2783
- ORIZU_RUNNER_INPUT_PATH: inputPath,
2784
- ORIZU_RUNNER_OUTPUT_PATH: outputPath,
2785
- },
2830
+ env: runnerSubprocessEnv(inputPath, outputPath),
2786
2831
  encoding: 'utf8',
2832
+ maxBuffer: RUNNER_OUTPUT_MAX_BYTES,
2833
+ timeout: RUNNER_TIMEOUT_MS,
2787
2834
  });
2788
2835
  if (result.error) {
2789
2836
  throw result.error;
2790
2837
  }
2791
2838
  if (result.status !== 0) {
2792
- throw new Error(`Runner failed for row ${row.id} with exit code ${result.status}: ${sanitizeTerminalText(result.stderr || result.stdout || '')}`);
2839
+ throw new Error(`Runner failed for row ${row.id} with exit code ${result.status}: ${sanitizeTerminalText(boundedRunnerOutput(result.stderr || result.stdout))}`);
2793
2840
  }
2794
2841
  const runnerOutput = JSON.parse(readFileSync(outputPath, 'utf8'));
2795
2842
  resultLines.push(JSON.stringify({
package/package.json CHANGED
@@ -1,12 +1,22 @@
1
1
  {
2
2
  "name": "orizu",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "orizu": "dist/index.js"
8
8
  },
9
+ "files": [
10
+ "README.md",
11
+ "dist",
12
+ "scripts",
13
+ "src",
14
+ "tsconfig.json",
15
+ "vendor"
16
+ ],
9
17
  "scripts": {
10
- "build": "tsc -p tsconfig.json"
18
+ "build": "tsc -p tsconfig.json",
19
+ "prepack": "node scripts/vendor-gepa-python.mjs",
20
+ "postpack": "node scripts/vendor-gepa-python.mjs --clean"
11
21
  }
12
22
  }
@@ -0,0 +1,54 @@
1
+ import { cpSync, existsSync, mkdirSync, rmSync } from 'node:fs'
2
+ import { dirname, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const scriptDir = dirname(fileURLToPath(import.meta.url))
6
+ const cliRoot = resolve(scriptDir, '..')
7
+ const repoRoot = resolve(cliRoot, '..', '..')
8
+ const sourceRoot = resolve(repoRoot, 'packages', 'orizu-gepa-python')
9
+ const vendorRoot = resolve(cliRoot, 'vendor', 'orizu-gepa-python')
10
+ const shouldClean = process.argv.includes('--clean')
11
+ const sourceSrc = resolve(sourceRoot, 'src')
12
+ const sourcePyproject = resolve(sourceRoot, 'pyproject.toml')
13
+ const sourceManifest = resolve(sourceRoot, 'manifest.json')
14
+ const vendoredSrc = resolve(vendorRoot, 'src')
15
+ const vendoredPyproject = resolve(vendorRoot, 'pyproject.toml')
16
+ const vendoredManifest = resolve(vendorRoot, 'manifest.json')
17
+ const hasSourcePackage = existsSync(sourceSrc)
18
+ && existsSync(sourcePyproject)
19
+ && existsSync(sourceManifest)
20
+ const hasVendoredPackage = existsSync(vendoredSrc)
21
+ && existsSync(vendoredPyproject)
22
+ && existsSync(vendoredManifest)
23
+
24
+ if (shouldClean) {
25
+ if (hasSourcePackage) {
26
+ rmSync(vendorRoot, { recursive: true, force: true })
27
+ }
28
+ process.exit(0)
29
+ }
30
+
31
+ if (!hasSourcePackage) {
32
+ if (hasVendoredPackage) {
33
+ process.exit(0)
34
+ }
35
+ throw new Error(`Unable to vendor orizu-gepa-python: source package not found at ${sourceRoot}`)
36
+ }
37
+
38
+ rmSync(vendorRoot, { recursive: true, force: true })
39
+ mkdirSync(vendorRoot, { recursive: true })
40
+ cpSync(sourcePyproject, resolve(vendorRoot, 'pyproject.toml'))
41
+ cpSync(sourceManifest, resolve(vendorRoot, 'manifest.json'))
42
+ cpSync(sourceSrc, resolve(vendorRoot, 'src'), {
43
+ recursive: true,
44
+ filter: sourcePath => {
45
+ const normalizedPath = sourcePath.replace(/\\/g, '/')
46
+ return !normalizedPath.endsWith('/__pycache__')
47
+ && !normalizedPath.includes('/__pycache__/')
48
+ && !normalizedPath.endsWith('.pyc')
49
+ && !normalizedPath.endsWith('.pyo')
50
+ && !normalizedPath.endsWith('/.pytest_cache')
51
+ && !normalizedPath.includes('/.pytest_cache/')
52
+ && !normalizedPath.endsWith('/.DS_Store')
53
+ },
54
+ })
package/src/index.ts CHANGED
@@ -2083,8 +2083,12 @@ function removeFlagWithValue(args: string[], flag: string): string[] {
2083
2083
  }
2084
2084
 
2085
2085
  function bundledOrizuGepaPythonPath(): string | null {
2086
- const candidate = fileURLToPath(new URL('../../orizu-gepa-python/src', import.meta.url))
2087
- return existsSync(candidate) ? candidate : null
2086
+ const candidates = [
2087
+ fileURLToPath(new URL('../vendor/orizu-gepa-python/src', import.meta.url)),
2088
+ fileURLToPath(new URL('../../orizu-gepa-python/src', import.meta.url)),
2089
+ ]
2090
+
2091
+ return candidates.find(candidate => existsSync(candidate)) ?? null
2088
2092
  }
2089
2093
 
2090
2094
  async function runGepaOptimization() {
@@ -0,0 +1,9 @@
1
+ {
2
+ "schema_version": "orizu.optimizer.v1",
3
+ "name": "orizu-gepa-text",
4
+ "description": "Orizu-owned text candidate optimizer with GEPA-style reflection and mandatory live event logging.",
5
+ "language": "python",
6
+ "command": ["python3", "-m", "orizu_gepa.cli"],
7
+ "optimizer_family": "gepa",
8
+ "candidate_kind": "text"
9
+ }
@@ -0,0 +1,16 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "orizu-gepa"
7
+ version = "0.1.0"
8
+ description = "Orizu-owned text prompt optimizer with GEPA-style reflection and mandatory live logging."
9
+ requires-python = ">=3.10"
10
+ dependencies = []
11
+
12
+ [project.scripts]
13
+ orizu-gepa = "orizu_gepa.cli:main"
14
+
15
+ [tool.setuptools.packages.find]
16
+ where = ["src"]
@@ -0,0 +1,19 @@
1
+ from .optimizer import (
2
+ Budget,
3
+ DatasetRow,
4
+ PromptContext,
5
+ RowEvaluation,
6
+ TextGepaConfig,
7
+ TextGepaResult,
8
+ optimize_text_candidate,
9
+ )
10
+
11
+ __all__ = [
12
+ "Budget",
13
+ "DatasetRow",
14
+ "PromptContext",
15
+ "RowEvaluation",
16
+ "TextGepaConfig",
17
+ "TextGepaResult",
18
+ "optimize_text_candidate",
19
+ ]
@@ -0,0 +1,173 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from .client import OrizuClient, OrizuEventSink
9
+ from .optimizer import TextGepaConfig, optimize_loaded_text_candidate
10
+ from .reflection import reflect_with_provider
11
+ from .runner import make_candidate_runner, make_scorer_runner
12
+
13
+
14
+ def positive_int(value: str) -> int:
15
+ parsed = int(value)
16
+ if parsed <= 0:
17
+ raise argparse.ArgumentTypeError("must be positive")
18
+ return parsed
19
+
20
+
21
+ def read_text_arg(value: str | None) -> str | None:
22
+ if value is None:
23
+ return None
24
+ if value.startswith("@"):
25
+ return Path(value[1:]).read_text()
26
+ path = Path(value)
27
+ if path.exists():
28
+ return path.read_text()
29
+ return value
30
+
31
+
32
+ def read_json_object_arg(value: str | None, flag_name: str) -> dict[str, Any] | None:
33
+ text = read_text_arg(value)
34
+ if text is None:
35
+ return None
36
+ parsed = json.loads(text)
37
+ if not isinstance(parsed, dict):
38
+ raise RuntimeError(f"{flag_name} must be a JSON object")
39
+ return parsed
40
+
41
+
42
+ def main() -> None:
43
+ parser = argparse.ArgumentParser(prog="orizu-gepa")
44
+ parser.add_argument("--project", required=True)
45
+ parser.add_argument("--optimizer-version-id", required=True)
46
+ parser.add_argument("--candidate-version-id", required=True)
47
+ parser.add_argument("--runner-version-id", required=True)
48
+ parser.add_argument("--candidate-runner-dir", required=True)
49
+ parser.add_argument("--scorer-version-id", required=True)
50
+ parser.add_argument("--scorer-runner-version-id", required=True)
51
+ parser.add_argument("--scorer-runner-dir", required=True)
52
+ parser.add_argument("--dataset-version-id", required=True)
53
+ parser.add_argument("--split-set-id", required=True)
54
+ parser.add_argument("--train-split", default="train")
55
+ parser.add_argument("--val-split", default="validation")
56
+ parser.add_argument("--budget", default="light", choices=["auto", "light", "medium", "high"])
57
+ parser.add_argument("--max-iterations", type=positive_int, default=3)
58
+ parser.add_argument("--minibatch-size", type=positive_int, default=3)
59
+ parser.add_argument(
60
+ "--candidate-selection-strategy",
61
+ default=TextGepaConfig.candidate_selection_strategy,
62
+ choices=["pareto", "current_best", "epsilon_greedy"],
63
+ )
64
+ parser.add_argument("--epsilon", type=float, default=TextGepaConfig.epsilon)
65
+ parser.add_argument("--disable-evaluation-cache", action="store_true")
66
+ parser.add_argument("--max-metric-calls", type=positive_int)
67
+ parser.add_argument("--max-full-evals", type=positive_int)
68
+ parser.add_argument("--reflection-model", default=TextGepaConfig.reflection_model)
69
+ parser.add_argument("--reflection-temperature", type=float)
70
+ parser.add_argument("--reflection-prompt-template")
71
+ parser.add_argument("--reflection-provider-settings")
72
+ parser.add_argument("--objective", default=TextGepaConfig.objective)
73
+ parser.add_argument("--seed", type=int, default=0)
74
+ parser.add_argument("--auto-promote", action="store_true")
75
+ parser.add_argument("--promotion-label")
76
+ parser.add_argument("--log-row-snapshots", action="store_true")
77
+ parser.add_argument("--metadata", default="{}")
78
+ args = parser.parse_args()
79
+
80
+ metadata = json.loads(args.metadata)
81
+ if not isinstance(metadata, dict):
82
+ raise RuntimeError("--metadata must be a JSON object")
83
+
84
+ config = TextGepaConfig(
85
+ budget=args.budget,
86
+ max_iterations=args.max_iterations,
87
+ minibatch_size=args.minibatch_size,
88
+ candidate_selection_strategy=args.candidate_selection_strategy,
89
+ epsilon=args.epsilon,
90
+ max_metric_calls=args.max_metric_calls,
91
+ max_full_evals=args.max_full_evals,
92
+ reflection_model=args.reflection_model,
93
+ reflection_temperature=args.reflection_temperature,
94
+ reflection_prompt_template=read_text_arg(args.reflection_prompt_template),
95
+ reflection_provider_settings=read_json_object_arg(args.reflection_provider_settings, "--reflection-provider-settings") or {},
96
+ objective=args.objective,
97
+ seed=args.seed,
98
+ auto_promote=args.auto_promote,
99
+ promotion_label=args.promotion_label,
100
+ cache_evaluations=not args.disable_evaluation_cache,
101
+ log_row_snapshots=args.log_row_snapshots,
102
+ )
103
+ client = OrizuClient.from_env()
104
+ prompt_context, trainset = client.fetch_exec_context(
105
+ prompt_version_id=args.candidate_version_id,
106
+ runner_version_id=args.runner_version_id,
107
+ dataset_version_id=args.dataset_version_id,
108
+ split_set_id=args.split_set_id,
109
+ split=args.train_split,
110
+ )
111
+ _, valset = client.fetch_exec_context(
112
+ prompt_version_id=args.candidate_version_id,
113
+ runner_version_id=args.runner_version_id,
114
+ dataset_version_id=args.dataset_version_id,
115
+ split_set_id=args.split_set_id,
116
+ split=args.val_split,
117
+ )
118
+ scorer_context, _ = client.fetch_scorer_exec_context(
119
+ scorer_version_id=args.scorer_version_id,
120
+ runner_version_id=args.scorer_runner_version_id,
121
+ dataset_version_id=args.dataset_version_id,
122
+ split_set_id=args.split_set_id,
123
+ split=args.val_split,
124
+ )
125
+
126
+ run_id = client.start_run(
127
+ project=args.project,
128
+ optimizer_version_id=args.optimizer_version_id,
129
+ prompt_version_id=args.candidate_version_id,
130
+ scorer_version_id=args.scorer_version_id,
131
+ dataset_version_id=args.dataset_version_id,
132
+ split_set_id=args.split_set_id,
133
+ train_split=args.train_split,
134
+ validation_split=args.val_split,
135
+ metadata={
136
+ **metadata,
137
+ "optimizer_package": "orizu-gepa-python",
138
+ "optimizer_family": "gepa",
139
+ "mode": "text-candidate",
140
+ "inference_lm": prompt_context.provider_settings.get("model"),
141
+ "reflection_lm": config.reflection_model,
142
+ "scorer_lm": scorer_context.provider_settings.get("model"),
143
+ "dataset_size": len(trainset) + len(valset),
144
+ "train_count": len(trainset),
145
+ "validation_count": len(valset),
146
+ },
147
+ )
148
+ print(f"[orizu-gepa] started optimization run {run_id}", flush=True)
149
+
150
+ result = optimize_loaded_text_candidate(
151
+ run_id=run_id,
152
+ prompt_context=prompt_context,
153
+ scorer_context=scorer_context,
154
+ trainset=trainset,
155
+ valset=valset,
156
+ candidate_runner=make_candidate_runner(Path(args.candidate_runner_dir), run_id),
157
+ scorer_runner=make_scorer_runner(Path(args.scorer_runner_dir), run_id),
158
+ reflector=reflect_with_provider,
159
+ event_sink=OrizuEventSink(client, run_id, fail_on_log_error=config.fail_on_log_error),
160
+ config=config,
161
+ )
162
+ print(json.dumps({
163
+ "optimization_run_id": result.run_id,
164
+ "best_candidate_id": result.best_candidate_id,
165
+ "best_score": result.best_score,
166
+ "seed_score": result.seed_score,
167
+ "promoted_prompt_version_id": result.promoted_prompt_version_id,
168
+ "budget": result.budget.to_payload(),
169
+ }))
170
+
171
+
172
+ if __name__ == "__main__":
173
+ main()