orizu 0.3.5 → 0.3.7
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 +25 -3
- package/dist/help.js +611 -0
- package/dist/index.js +268 -15
- package/dist/skill-installer.js +180 -0
- package/package.json +3 -3
- package/scripts/vendor-orizu-skill.mjs +54 -0
- package/src/help.ts +676 -0
- package/src/index.ts +353 -15
- package/src/skill-installer.ts +260 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/__init__.py +4 -0
- package/vendor/orizu-gepa-python/src/orizu_gepa/cli.py +24 -1
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +469 -21
- package/vendor/skills/orizu-cli/SKILL.md +186 -0
- package/vendor/skills/orizu-cli/agents/openai.yaml +4 -0
- package/vendor/skills/orizu-cli/references/building-apps.md +1461 -0
- package/vendor/skills/orizu-cli/references/building-judges.md +218 -0
- package/vendor/skills/orizu-cli/references/cli-reference.md +325 -0
- package/vendor/skills/orizu-cli/references/optimization-reports.md +174 -0
- package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +291 -0
- package/vendor/skills/orizu-cli/references/primer.md +245 -0
- package/vendor/skills/orizu-cli/references/prompt-control-plane.md +832 -0
- package/vendor/skills/orizu-cli/scripts/test-app.mjs +492 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cpSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from 'fs'
|
|
9
|
+
import { dirname, isAbsolute, relative, resolve } from 'path'
|
|
10
|
+
import { fileURLToPath } from 'url'
|
|
11
|
+
import { homedir } from 'os'
|
|
12
|
+
|
|
13
|
+
export const SKILL_NAME = 'orizu-cli'
|
|
14
|
+
export const AGENTS_START_MARKER = '<!-- orizu-cli:start -->'
|
|
15
|
+
export const AGENTS_END_MARKER = '<!-- orizu-cli:end -->'
|
|
16
|
+
|
|
17
|
+
export const SKILL_INSTALL_TARGETS = [
|
|
18
|
+
'agent-user',
|
|
19
|
+
'codex-project',
|
|
20
|
+
'claude-user',
|
|
21
|
+
'claude-project',
|
|
22
|
+
'agents-md',
|
|
23
|
+
] as const
|
|
24
|
+
|
|
25
|
+
export type SkillInstallTarget = typeof SKILL_INSTALL_TARGETS[number]
|
|
26
|
+
|
|
27
|
+
export interface SkillInstallOptions {
|
|
28
|
+
cwd?: string
|
|
29
|
+
homeDir?: string
|
|
30
|
+
overwrite?: boolean
|
|
31
|
+
dryRun?: boolean
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SkillInstallResult {
|
|
35
|
+
target: SkillInstallTarget
|
|
36
|
+
path: string
|
|
37
|
+
action: 'created' | 'updated' | 'would-create' | 'would-update'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getCliRoot(): string {
|
|
41
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isSkillDir(path: string): boolean {
|
|
45
|
+
return existsSync(resolve(path, 'SKILL.md'))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isSafeRelativeSkillPath(rel: string): boolean {
|
|
49
|
+
return rel !== '..' && !rel.startsWith('../') && !isAbsolute(rel)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function formatMissingSkillSourceError(paths: string[]): string {
|
|
53
|
+
return `Unable to find bundled ${SKILL_NAME} skill. Reinstall the orizu CLI or run from the Orizu repository.\n`
|
|
54
|
+
+ `Searched:\n- ${paths.join('\n- ')}`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function resolveSkillSourceDir(): string {
|
|
58
|
+
const override = process.env.ORIZU_SKILL_SOURCE_DIR
|
|
59
|
+
if (override) {
|
|
60
|
+
const resolvedOverride = resolve(override)
|
|
61
|
+
if (!isSkillDir(resolvedOverride)) {
|
|
62
|
+
throw new Error(`ORIZU_SKILL_SOURCE_DIR is not an Orizu skill directory: ${resolvedOverride}`)
|
|
63
|
+
}
|
|
64
|
+
return resolvedOverride
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const cliRoot = getCliRoot()
|
|
68
|
+
const packagedSkill = resolve(cliRoot, 'vendor', 'skills', SKILL_NAME)
|
|
69
|
+
if (isSkillDir(packagedSkill)) {
|
|
70
|
+
return packagedSkill
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const repoSkill = resolve(cliRoot, '..', '..', 'skills', SKILL_NAME)
|
|
74
|
+
if (isSkillDir(repoSkill)) {
|
|
75
|
+
return repoSkill
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const searchedPaths = [
|
|
79
|
+
`packaged skill: ${packagedSkill}`,
|
|
80
|
+
`repository fallback: ${repoSkill}`,
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
throw new Error(formatMissingSkillSourceError(searchedPaths))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveHome(options?: SkillInstallOptions): string {
|
|
87
|
+
return resolve(options?.homeDir || process.env.HOME || homedir())
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function resolveCwd(options?: SkillInstallOptions): string {
|
|
91
|
+
return resolve(options?.cwd || process.cwd())
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getSkillInstallPath(
|
|
95
|
+
target: SkillInstallTarget,
|
|
96
|
+
options?: SkillInstallOptions
|
|
97
|
+
): string {
|
|
98
|
+
const home = resolveHome(options)
|
|
99
|
+
const cwd = resolveCwd(options)
|
|
100
|
+
|
|
101
|
+
if (target === 'agent-user') {
|
|
102
|
+
return resolve(home, '.agents', 'skills', SKILL_NAME)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (target === 'codex-project') {
|
|
106
|
+
return resolve(cwd, '.codex', 'skills', SKILL_NAME)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (target === 'claude-user') {
|
|
110
|
+
return resolve(home, '.claude', 'skills', SKILL_NAME)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (target === 'claude-project') {
|
|
114
|
+
return resolve(cwd, '.claude', 'skills', SKILL_NAME)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return resolve(cwd, 'AGENTS.md')
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function shouldCopySkillPath(sourceRoot: string, sourcePath: string): boolean {
|
|
121
|
+
const rel = relative(sourceRoot, sourcePath).replace(/\\/g, '/')
|
|
122
|
+
if (!rel) {
|
|
123
|
+
return true
|
|
124
|
+
}
|
|
125
|
+
if (!isSafeRelativeSkillPath(rel)) {
|
|
126
|
+
return false
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const parts = rel.split('/')
|
|
130
|
+
return !parts.some(part =>
|
|
131
|
+
part === '.DS_Store' ||
|
|
132
|
+
part === '__pycache__' ||
|
|
133
|
+
part === '.pytest_cache'
|
|
134
|
+
) && !rel.endsWith('.pyc') && !rel.endsWith('.pyo')
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function copySkillTree(sourceDir: string, targetDir: string) {
|
|
138
|
+
rmSync(targetDir, { recursive: true, force: true })
|
|
139
|
+
mkdirSync(dirname(targetDir), { recursive: true })
|
|
140
|
+
cpSync(sourceDir, targetDir, {
|
|
141
|
+
recursive: true,
|
|
142
|
+
filter: sourcePath => shouldCopySkillPath(sourceDir, sourcePath),
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function stripFrontmatter(markdown: string): string {
|
|
147
|
+
const match = markdown.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n/)
|
|
148
|
+
if (!match) {
|
|
149
|
+
return markdown
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return markdown.slice(match[0].length).replace(/^[\r\n]+/, '')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function buildAgentsSection(sourceDir: string): string {
|
|
156
|
+
const skillMarkdown = readFileSync(resolve(sourceDir, 'SKILL.md'), 'utf8')
|
|
157
|
+
const body = stripFrontmatter(skillMarkdown).trimEnd()
|
|
158
|
+
return `${AGENTS_START_MARKER}\n${body}\n${AGENTS_END_MARKER}\n`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function installAgentsMd(
|
|
162
|
+
sourceDir: string,
|
|
163
|
+
targetPath: string,
|
|
164
|
+
options?: SkillInstallOptions
|
|
165
|
+
): SkillInstallResult {
|
|
166
|
+
const section = buildAgentsSection(sourceDir)
|
|
167
|
+
const exists = existsSync(targetPath)
|
|
168
|
+
const existing = exists ? readFileSync(targetPath, 'utf8') : ''
|
|
169
|
+
const hasManagedSection = existing.includes(AGENTS_START_MARKER)
|
|
170
|
+
&& existing.includes(AGENTS_END_MARKER)
|
|
171
|
+
const action = exists ? 'updated' : 'created'
|
|
172
|
+
|
|
173
|
+
if (options?.dryRun) {
|
|
174
|
+
return {
|
|
175
|
+
target: 'agents-md',
|
|
176
|
+
path: targetPath,
|
|
177
|
+
action: action === 'updated' ? 'would-update' : 'would-create',
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (hasManagedSection && !options?.overwrite) {
|
|
182
|
+
throw new Error(`${targetPath} already contains an Orizu CLI section. Pass --yes to replace it.`)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
mkdirSync(dirname(targetPath), { recursive: true })
|
|
186
|
+
if (!exists) {
|
|
187
|
+
writeFileSync(targetPath, section, 'utf8')
|
|
188
|
+
return { target: 'agents-md', path: targetPath, action: 'created' }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (hasManagedSection) {
|
|
192
|
+
const pattern = new RegExp(
|
|
193
|
+
`${escapeRegExp(AGENTS_START_MARKER)}[\\s\\S]*?${escapeRegExp(AGENTS_END_MARKER)}\\n?`
|
|
194
|
+
)
|
|
195
|
+
writeFileSync(targetPath, existing.replace(pattern, section), 'utf8')
|
|
196
|
+
return { target: 'agents-md', path: targetPath, action: 'updated' }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const existingPrefix = existing.trim().length === 0 ? '' : existing
|
|
200
|
+
const separator = existingPrefix.endsWith('\n') ? '\n' : '\n\n'
|
|
201
|
+
writeFileSync(targetPath, `${existingPrefix}${existingPrefix ? separator : ''}${section}`, 'utf8')
|
|
202
|
+
return { target: 'agents-md', path: targetPath, action: 'updated' }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function escapeRegExp(value: string): string {
|
|
206
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function targetNeedsOverwrite(
|
|
210
|
+
target: SkillInstallTarget,
|
|
211
|
+
options?: SkillInstallOptions
|
|
212
|
+
): boolean {
|
|
213
|
+
const targetPath = getSkillInstallPath(target, options)
|
|
214
|
+
if (!existsSync(targetPath)) {
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (target !== 'agents-md') {
|
|
219
|
+
return true
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const existing = readFileSync(targetPath, 'utf8')
|
|
223
|
+
return existing.includes(AGENTS_START_MARKER) && existing.includes(AGENTS_END_MARKER)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function installSkillTarget(
|
|
227
|
+
target: SkillInstallTarget,
|
|
228
|
+
options?: SkillInstallOptions
|
|
229
|
+
): SkillInstallResult {
|
|
230
|
+
const sourceDir = resolveSkillSourceDir()
|
|
231
|
+
const targetPath = getSkillInstallPath(target, options)
|
|
232
|
+
const exists = existsSync(targetPath)
|
|
233
|
+
|
|
234
|
+
if (target === 'agents-md') {
|
|
235
|
+
return installAgentsMd(sourceDir, targetPath, options)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (options?.dryRun) {
|
|
239
|
+
return {
|
|
240
|
+
target,
|
|
241
|
+
path: targetPath,
|
|
242
|
+
action: exists ? 'would-update' : 'would-create',
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (exists && !options?.overwrite) {
|
|
247
|
+
throw new Error(`${targetPath} already exists. Pass --yes to replace it.`)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
copySkillTree(sourceDir, targetPath)
|
|
251
|
+
return {
|
|
252
|
+
target,
|
|
253
|
+
path: targetPath,
|
|
254
|
+
action: exists ? 'updated' : 'created',
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function isSkillInstallTarget(value: string): value is SkillInstallTarget {
|
|
259
|
+
return (SKILL_INSTALL_TARGETS as readonly string[]).includes(value)
|
|
260
|
+
}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
from .optimizer import (
|
|
2
2
|
Budget,
|
|
3
3
|
DatasetRow,
|
|
4
|
+
NumThreadsPlan,
|
|
4
5
|
PromptContext,
|
|
5
6
|
RowEvaluation,
|
|
6
7
|
TextGepaConfig,
|
|
7
8
|
TextGepaResult,
|
|
8
9
|
optimize_text_candidate,
|
|
10
|
+
resolve_num_threads,
|
|
9
11
|
)
|
|
10
12
|
|
|
11
13
|
__all__ = [
|
|
12
14
|
"Budget",
|
|
13
15
|
"DatasetRow",
|
|
16
|
+
"NumThreadsPlan",
|
|
14
17
|
"PromptContext",
|
|
15
18
|
"RowEvaluation",
|
|
16
19
|
"TextGepaConfig",
|
|
17
20
|
"TextGepaResult",
|
|
18
21
|
"optimize_text_candidate",
|
|
22
|
+
"resolve_num_threads",
|
|
19
23
|
]
|
|
@@ -19,6 +19,12 @@ def positive_int(value: str) -> int:
|
|
|
19
19
|
return parsed
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
def num_threads_arg(value: str) -> int | str:
|
|
23
|
+
if value.strip().lower() == "auto":
|
|
24
|
+
return "auto"
|
|
25
|
+
return positive_int(value)
|
|
26
|
+
|
|
27
|
+
|
|
22
28
|
def read_text_arg(value: str | None) -> str | None:
|
|
23
29
|
if value is None:
|
|
24
30
|
return None
|
|
@@ -54,9 +60,10 @@ def main() -> None:
|
|
|
54
60
|
parser.add_argument("--split-set-id", required=True)
|
|
55
61
|
parser.add_argument("--train-split", default="train")
|
|
56
62
|
parser.add_argument("--val-split", default="validation")
|
|
57
|
-
parser.add_argument("--budget", default="light", choices=["auto", "light", "medium", "
|
|
63
|
+
parser.add_argument("--budget", default="light", choices=["auto", "light", "medium", "heavy"])
|
|
58
64
|
parser.add_argument("--max-iterations", type=positive_int, default=3)
|
|
59
65
|
parser.add_argument("--minibatch-size", type=positive_int, default=3)
|
|
66
|
+
parser.add_argument("--num-threads", type=num_threads_arg, default=TextGepaConfig.num_threads)
|
|
60
67
|
parser.add_argument(
|
|
61
68
|
"--candidate-selection-strategy",
|
|
62
69
|
default=TextGepaConfig.candidate_selection_strategy,
|
|
@@ -71,6 +78,19 @@ def main() -> None:
|
|
|
71
78
|
parser.add_argument("--reflection-max-tokens", type=positive_int)
|
|
72
79
|
parser.add_argument("--reflection-prompt-template")
|
|
73
80
|
parser.add_argument("--reflection-provider-settings")
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"--skip-perfect-parent-reflection",
|
|
83
|
+
dest="skip_perfect_parent_reflection",
|
|
84
|
+
action="store_true",
|
|
85
|
+
default=TextGepaConfig.skip_perfect_parent_reflection,
|
|
86
|
+
help="Skip reflection and child creation when the parent scores perfectly on the minibatch (default).",
|
|
87
|
+
)
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--no-skip-perfect-parent-reflection",
|
|
90
|
+
dest="skip_perfect_parent_reflection",
|
|
91
|
+
action="store_false",
|
|
92
|
+
help="Reflect and create a child even when the parent scores perfectly on the minibatch.",
|
|
93
|
+
)
|
|
74
94
|
parser.add_argument("--objective", default=TextGepaConfig.objective)
|
|
75
95
|
parser.add_argument("--seed", type=int, default=0)
|
|
76
96
|
parser.add_argument("--auto-promote", action="store_true")
|
|
@@ -89,6 +109,7 @@ def main() -> None:
|
|
|
89
109
|
budget=args.budget,
|
|
90
110
|
max_iterations=args.max_iterations,
|
|
91
111
|
minibatch_size=args.minibatch_size,
|
|
112
|
+
num_threads=args.num_threads,
|
|
92
113
|
candidate_selection_strategy=args.candidate_selection_strategy,
|
|
93
114
|
epsilon=args.epsilon,
|
|
94
115
|
max_metric_calls=args.max_metric_calls,
|
|
@@ -98,6 +119,7 @@ def main() -> None:
|
|
|
98
119
|
reflection_max_tokens=args.reflection_max_tokens,
|
|
99
120
|
reflection_prompt_template=read_text_arg(args.reflection_prompt_template),
|
|
100
121
|
reflection_provider_settings=read_json_object_arg(args.reflection_provider_settings, "--reflection-provider-settings") or {},
|
|
122
|
+
skip_perfect_parent_reflection=args.skip_perfect_parent_reflection,
|
|
101
123
|
objective=args.objective,
|
|
102
124
|
seed=args.seed,
|
|
103
125
|
auto_promote=args.auto_promote,
|
|
@@ -139,6 +161,7 @@ def main() -> None:
|
|
|
139
161
|
"dataset_size": len(trainset) + len(valset),
|
|
140
162
|
"train_count": len(trainset),
|
|
141
163
|
"validation_count": len(valset),
|
|
164
|
+
"num_threads": args.num_threads,
|
|
142
165
|
}
|
|
143
166
|
|
|
144
167
|
run_id = client.start_run(
|