omnilane 0.12.0 → 0.14.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/CHANGELOG.md +69 -1
- package/README.ja.md +26 -1
- package/README.ko.md +26 -1
- package/README.md +58 -3
- package/README.zh-CN.md +23 -1
- package/README.zh-TW.md +44 -3
- package/VERSION +1 -1
- package/benchmarks/workloads.tsv +4 -0
- package/bin/omnilane +10 -2
- package/bin/omnilane-mcp +126 -0
- package/completions/_omnilane +21 -4
- package/completions/omnilane.bash +19 -4
- package/completions/omnilane.fish +15 -3
- package/package.json +2 -1
- package/routing.yaml +1 -1
- package/scripts/benchmark.py +446 -0
- package/scripts/check.sh +1 -1
- package/scripts/doctor.sh +53 -10
- package/scripts/jobs.sh +162 -2
- package/scripts/provider-probe.sh +180 -0
- package/scripts/release-audit.sh +37 -0
- package/ui/app.js +56 -0
- package/ui/index.html +1 -0
- package/ui/styles.css +22 -0
package/completions/_omnilane
CHANGED
|
@@ -53,7 +53,7 @@ _omnilane() {
|
|
|
53
53
|
local command="${words[2]:-}" sub sub_index=3
|
|
54
54
|
local -a lanes ids
|
|
55
55
|
if (( CURRENT == 2 )); then
|
|
56
|
-
_values 'command' version list route dispatch jobs doctor release-audit ui configure completion help
|
|
56
|
+
_values 'command' version list route dispatch jobs doctor benchmark release-audit ui configure completion help
|
|
57
57
|
return
|
|
58
58
|
fi
|
|
59
59
|
case "$command" in
|
|
@@ -78,7 +78,7 @@ _omnilane() {
|
|
|
78
78
|
sub="${words[4]:-}"
|
|
79
79
|
fi
|
|
80
80
|
if (( CURRENT == sub_index )); then
|
|
81
|
-
_values 'job command' list status result tail retry stats wait audit prune help
|
|
81
|
+
_values 'job command' list status result tail retry stats recommend wait audit prune help
|
|
82
82
|
elif (( CURRENT == sub_index + 1 )) &&
|
|
83
83
|
[[ "$sub" == status || "$sub" == result || "$sub" == wait ||
|
|
84
84
|
"$sub" == tail || "$sub" == retry ]]; then
|
|
@@ -92,7 +92,13 @@ _omnilane() {
|
|
|
92
92
|
_arguments '--background[re-dispatch in the background]'
|
|
93
93
|
elif [[ "$sub" == wait ]]; then
|
|
94
94
|
_arguments '--timeout[maximum wait seconds]:seconds:'
|
|
95
|
-
elif [[ "$sub" == stats
|
|
95
|
+
elif [[ "$sub" == stats ]]; then
|
|
96
|
+
_arguments '--last[maximum jobs]:count:' '--lane[filter by lane]:lane:' \
|
|
97
|
+
'--vendor[filter by vendor]:vendor:' '--json[versioned metadata output]'
|
|
98
|
+
elif [[ "$sub" == recommend ]]; then
|
|
99
|
+
_arguments '--last[maximum jobs]:count:' '--lane[filter by lane]:lane:' \
|
|
100
|
+
'--min-samples[minimum completed samples]:count:' '--json[versioned metadata output]'
|
|
101
|
+
elif [[ "$sub" == audit ]]; then
|
|
96
102
|
_arguments '--last[maximum jobs]:count:' '--json[versioned metadata output]'
|
|
97
103
|
elif [[ "$sub" == prune ]]; then
|
|
98
104
|
_arguments '--keep[completed jobs to retain]:count:' \
|
|
@@ -100,7 +106,18 @@ _omnilane() {
|
|
|
100
106
|
'--apply[delete eligible jobs]'
|
|
101
107
|
fi
|
|
102
108
|
;;
|
|
103
|
-
doctor)
|
|
109
|
+
doctor)
|
|
110
|
+
_arguments '--json[versioned JSON output]' '--strict[fail on warnings]' \
|
|
111
|
+
'--probe[run one opt-in live provider probe]:vendor:' \
|
|
112
|
+
'--probe-timeout[probe timeout in seconds]:seconds:'
|
|
113
|
+
;;
|
|
114
|
+
benchmark)
|
|
115
|
+
_arguments '--json[versioned JSON output]' '--run[invoke providers in advise mode]' \
|
|
116
|
+
'*--vendor[vendor to compare]:vendor:(codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras)' \
|
|
117
|
+
'--timeout[per-workload timeout]:seconds:' \
|
|
118
|
+
'--workloads[TSV workload file]:file:_files' \
|
|
119
|
+
'*--cost-per-call[user-supplied vendor USD estimate]:vendor=USD:'
|
|
120
|
+
;;
|
|
104
121
|
release-audit)
|
|
105
122
|
_arguments '--target[release version]:version:' '--allow-dirty[permit a dirty worktree]' \
|
|
106
123
|
'--require-tag[require an annotated tag at HEAD]' '--manifest[include tracked inventory]' \
|
|
@@ -58,7 +58,7 @@ _omnilane() {
|
|
|
58
58
|
prev="${COMP_WORDS[COMP_CWORD-1]:-}"
|
|
59
59
|
command="${COMP_WORDS[1]:-}"
|
|
60
60
|
if [[ "$COMP_CWORD" -eq 1 ]]; then
|
|
61
|
-
|
|
61
|
+
words="version list route dispatch jobs doctor benchmark release-audit ui configure completion help"
|
|
62
62
|
else
|
|
63
63
|
case "$command" in
|
|
64
64
|
route|dispatch)
|
|
@@ -84,7 +84,7 @@ _omnilane() {
|
|
|
84
84
|
sub="${COMP_WORDS[3]:-}"
|
|
85
85
|
fi
|
|
86
86
|
if [[ "$COMP_CWORD" -eq "$sub_index" ]]; then
|
|
87
|
-
words="list status result tail retry stats wait audit prune help"
|
|
87
|
+
words="list status result tail retry stats recommend wait audit prune help"
|
|
88
88
|
elif [[ "$COMP_CWORD" -eq $((sub_index + 1)) &&
|
|
89
89
|
( "$sub" == status || "$sub" == result || "$sub" == wait ||
|
|
90
90
|
"$sub" == tail || "$sub" == retry ) ]]; then
|
|
@@ -99,7 +99,11 @@ _omnilane() {
|
|
|
99
99
|
words="--background"
|
|
100
100
|
elif [[ "$sub" == wait ]]; then
|
|
101
101
|
words="--timeout"
|
|
102
|
-
elif [[ "$sub" == stats
|
|
102
|
+
elif [[ "$sub" == stats ]]; then
|
|
103
|
+
words="--last --lane --vendor --json"
|
|
104
|
+
elif [[ "$sub" == recommend ]]; then
|
|
105
|
+
words="--last --lane --min-samples --json"
|
|
106
|
+
elif [[ "$sub" == audit ]]; then
|
|
103
107
|
words="--last --json"
|
|
104
108
|
elif [[ "$sub" == prune ]]; then
|
|
105
109
|
words="--keep --older-than --apply"
|
|
@@ -107,7 +111,18 @@ _omnilane() {
|
|
|
107
111
|
return
|
|
108
112
|
fi
|
|
109
113
|
;;
|
|
110
|
-
doctor) words="--json" ;;
|
|
114
|
+
doctor) words="--json --strict --probe --probe-timeout" ;;
|
|
115
|
+
benchmark)
|
|
116
|
+
case "$prev" in
|
|
117
|
+
--vendor) words="codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras" ;;
|
|
118
|
+
--workloads)
|
|
119
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
120
|
+
return
|
|
121
|
+
;;
|
|
122
|
+
--timeout|--cost-per-call) return ;;
|
|
123
|
+
*) words="--json --run --vendor --timeout --workloads --cost-per-call" ;;
|
|
124
|
+
esac
|
|
125
|
+
;;
|
|
111
126
|
release-audit) words="--target --allow-dirty --require-tag --manifest --json" ;;
|
|
112
127
|
ui) words="start status url stop" ;;
|
|
113
128
|
completion) words="bash zsh" ;;
|
|
@@ -10,6 +10,7 @@ complete -c omnilane -f -n __fish_use_subcommand -a dispatch -d 'dispatch o
|
|
|
10
10
|
complete -c omnilane -f -n __fish_use_subcommand -a jobs -d 'inspect background jobs'
|
|
11
11
|
complete -c omnilane -f -n __fish_use_subcommand -a mcp -d 'MCP stdio server'
|
|
12
12
|
complete -c omnilane -f -n __fish_use_subcommand -a doctor -d 'read-only health report'
|
|
13
|
+
complete -c omnilane -f -n __fish_use_subcommand -a benchmark -d 'fixed quality/cost comparison'
|
|
13
14
|
complete -c omnilane -f -n __fish_use_subcommand -a release-audit -d 'offline release gate'
|
|
14
15
|
complete -c omnilane -f -n __fish_use_subcommand -a ui -d 'Live Board server'
|
|
15
16
|
complete -c omnilane -f -n __fish_use_subcommand -a configure -d 'lane routing (menu or set/get/unset/list/diff)'
|
|
@@ -29,19 +30,30 @@ complete -c omnilane -n '__fish_seen_subcommand_from route dispatch' -l dry-run
|
|
|
29
30
|
|
|
30
31
|
# jobs subcommands (only before a jobs subcommand is chosen).
|
|
31
32
|
complete -c omnilane -f \
|
|
32
|
-
-n '__fish_seen_subcommand_from jobs; and not __fish_seen_subcommand_from list status result tail retry stats wait audit prune cancel rm help' \
|
|
33
|
-
-a 'list status result tail retry stats wait audit prune cancel rm help' -d 'jobs subcommand'
|
|
33
|
+
-n '__fish_seen_subcommand_from jobs; and not __fish_seen_subcommand_from list status result tail retry stats recommend wait audit prune cancel rm help' \
|
|
34
|
+
-a 'list status result tail retry stats recommend wait audit prune cancel rm help' -d 'jobs subcommand'
|
|
34
35
|
complete -c omnilane -n '__fish_seen_subcommand_from jobs' -l json -d 'JSON output'
|
|
35
36
|
# jobs list / stats filters.
|
|
36
|
-
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats' -l lane
|
|
37
|
+
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats recommend' -l lane -x -d 'filter by lane'
|
|
37
38
|
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list stats' -l vendor -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras exec' -d 'filter by vendor'
|
|
38
39
|
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from list' -l status -x -a 'running done' -d 'filter by status'
|
|
40
|
+
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from stats recommend audit' -l last -x -d 'maximum recent jobs'
|
|
41
|
+
complete -c omnilane -n '__fish_seen_subcommand_from jobs; and __fish_seen_subcommand_from recommend' -l min-samples -x -d 'minimum completed samples'
|
|
39
42
|
|
|
40
43
|
# configure subcommands (non-interactive).
|
|
41
44
|
complete -c omnilane -f -n '__fish_seen_subcommand_from configure; and not __fish_seen_subcommand_from set get unset list diff' -a 'set get unset list diff' -d 'configure action'
|
|
42
45
|
|
|
43
46
|
# doctor / release-audit / ui / completion.
|
|
44
47
|
complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l json -d 'JSON output'
|
|
48
|
+
complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l strict -d 'fail when any warning is reported'
|
|
49
|
+
complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l probe -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras' -d 'run one opt-in live probe'
|
|
50
|
+
complete -c omnilane -n '__fish_seen_subcommand_from doctor' -l probe-timeout -x -d 'probe timeout in seconds'
|
|
51
|
+
complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l json -d 'JSON output'
|
|
52
|
+
complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l run -d 'invoke providers in advise mode'
|
|
53
|
+
complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l vendor -x -a 'codex claude grok gemini kimi qwen opencode openrouter deepseek zai mistral groq cerebras' -d 'vendor to compare; repeatable'
|
|
54
|
+
complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l timeout -x -d 'per-workload timeout'
|
|
55
|
+
complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l workloads -r -d 'TSV workload file'
|
|
56
|
+
complete -c omnilane -n '__fish_seen_subcommand_from benchmark' -l cost-per-call -x -d 'user-supplied VENDOR=USD estimate'
|
|
45
57
|
complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l target -x -d 'target version'
|
|
46
58
|
complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l allow-dirty -d 'permit a dirty tree'
|
|
47
59
|
complete -c omnilane -n '__fish_seen_subcommand_from release-audit' -l require-tag -d 'require an annotated tag'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omnilane",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "One routing table, every harness — classify subtasks into lanes and dispatch each lane to the best vendor's agentic CLI (Codex, Claude, Gemini, Grok) using your existing subscription logins.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"omnilane": "bin/omnilane"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"!scripts/__pycache__",
|
|
12
12
|
"!**/*.pyc",
|
|
13
13
|
"completions/",
|
|
14
|
+
"benchmarks/",
|
|
14
15
|
"ui/",
|
|
15
16
|
"routing.yaml",
|
|
16
17
|
"routing.local.yaml.example",
|
package/routing.yaml
CHANGED
|
@@ -27,7 +27,7 @@ hard-judgment: claude claude-opus-5 xhigh | codex gpt-5.6-sol max # ordered
|
|
|
27
27
|
taste-final: claude claude-opus-5 high | codex gpt-5.6-sol max # user-facing prose, prompt/doc polish, Chinese phrasing, style arbitration
|
|
28
28
|
consult: codex gpt-5.6-sol max | claude claude-opus-5 high | grok grok-4.5 - | gemini "Gemini 3.1 Pro (High)" - # direct named-model consultation; use --vendor to prevent fallback
|
|
29
29
|
ui-draft: codex gpt-5.6-sol xhigh | claude claude-opus-5 high # only with a design system / reference images; open-ended visual taste -> taste-final
|
|
30
|
-
long-context: gemini "Gemini 3.1 Pro (High)" - |
|
|
30
|
+
long-context: gemini "Gemini 3.1 Pro (High)" - | codex gpt-5.6-sol high | claude claude-opus-5 high # all have 1M context; ordered on AA-LCR, which scores exactly this lane's work — extracting and synthesising across long documents — and where Gemini leads both fallbacks. Corrected 2026-08-03: this comment used to send multi-hop synthesis to the Claude candidate on second-hand prior-generation figures, and current first-party per-effort data reverses that, so the two fallbacks swapped. Caveat in docs: AA-LCR runs at 10k-100k tokens, so nothing here settles behaviour at a full 1M
|
|
31
31
|
fast-agentic: codex gpt-5.6-luna max | gemini "Gemini 3.6 Flash (High)" - # fast multi-step tool loops. Reordered 2026-08-03: Luna leads Flash on agentic benchmarks AND costs a fraction as much per task, so Flash's remaining edge is raw throughput alone. Keep Flash first only if your loops are latency-bound. Both take image input, so the lane's multimodal checks are unaffected
|
|
32
32
|
live-search: grok grok-4.5 - | off # native X/web search lane; no real substitute
|
|
33
33
|
coding-overflow: grok grok-4.5 - | kimi kimi-k3 - | qwen qwen3-coder-plus - | opencode - - | off # codex-quota relief valve: mid-tier coding; Grok 4.5 is a capable mid-tier coder but AA measures a high hallucination rate — verify every factual claim it ships. qwen3-coder-plus = 2025-09-23 snapshot alias (Qwen 3.6 Plus exists; re-evaluate before swapping). kimi/qwen model fields are CLI aliases — adjust to your login. opencode "-" model = its own configured default.
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Fixed, body-free routing quality/cost benchmark."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
from decimal import Decimal, InvalidOperation
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import re
|
|
10
|
+
import shlex
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
import tempfile
|
|
14
|
+
import time
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
18
|
+
DEFAULT_WORKLOADS = ROOT / "benchmarks" / "workloads.tsv"
|
|
19
|
+
NAME_RE = re.compile(r"^[a-z][a-z0-9-]*$")
|
|
20
|
+
ID_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BenchmarkError(Exception):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_args(argv=None):
|
|
28
|
+
parser = argparse.ArgumentParser(
|
|
29
|
+
prog="omnilane benchmark",
|
|
30
|
+
description=(
|
|
31
|
+
"Compare fixed-workload routing quality and user-supplied per-call "
|
|
32
|
+
"costs. Provider calls require --run."
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument("--json", action="store_true", help="emit JSON")
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--run",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="invoke providers in advise mode; default is routing-only dry-run",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--vendor",
|
|
43
|
+
action="append",
|
|
44
|
+
default=[],
|
|
45
|
+
metavar="VENDOR",
|
|
46
|
+
help="vendor to compare; repeatable (default: configured vendors)",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--timeout",
|
|
50
|
+
type=int,
|
|
51
|
+
default=60,
|
|
52
|
+
metavar="SECONDS",
|
|
53
|
+
help="per-workload timeout, 1..600 (default: 60)",
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--workloads",
|
|
57
|
+
type=Path,
|
|
58
|
+
default=DEFAULT_WORKLOADS,
|
|
59
|
+
metavar="FILE",
|
|
60
|
+
help="fixed TSV workload file",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--cost-per-call",
|
|
64
|
+
action="append",
|
|
65
|
+
default=[],
|
|
66
|
+
metavar="VENDOR=USD",
|
|
67
|
+
help="optional user-supplied estimated USD per call; repeatable",
|
|
68
|
+
)
|
|
69
|
+
args = parser.parse_args(argv)
|
|
70
|
+
if not 1 <= args.timeout <= 600:
|
|
71
|
+
parser.error("--timeout must be between 1 and 600 seconds")
|
|
72
|
+
return args
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def unique(values):
|
|
76
|
+
return list(dict.fromkeys(values))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def decode_plan_value(value):
|
|
80
|
+
try:
|
|
81
|
+
parsed = shlex.split(value, posix=True)
|
|
82
|
+
except ValueError:
|
|
83
|
+
return value
|
|
84
|
+
return parsed[0] if len(parsed) == 1 else value
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_plan(text):
|
|
88
|
+
plan = {}
|
|
89
|
+
for line in text.splitlines():
|
|
90
|
+
if "=" not in line:
|
|
91
|
+
continue
|
|
92
|
+
key, value = line.split("=", 1)
|
|
93
|
+
if re.fullmatch(r"[a-z_]+", key):
|
|
94
|
+
plan[key] = decode_plan_value(value)
|
|
95
|
+
return plan
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def load_workloads(path):
|
|
99
|
+
try:
|
|
100
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
101
|
+
except OSError as exc:
|
|
102
|
+
raise BenchmarkError(f"cannot read workloads: {path}: {exc}") from exc
|
|
103
|
+
|
|
104
|
+
workloads = []
|
|
105
|
+
seen = set()
|
|
106
|
+
for line_number, line in enumerate(lines, 1):
|
|
107
|
+
if not line.strip() or line.lstrip().startswith("#"):
|
|
108
|
+
continue
|
|
109
|
+
fields = line.split("\t", 4)
|
|
110
|
+
if len(fields) != 5:
|
|
111
|
+
raise BenchmarkError(
|
|
112
|
+
f"{path}:{line_number}: expected 5 tab-separated fields"
|
|
113
|
+
)
|
|
114
|
+
workload_id, lane, weight_text, pattern, prompt = fields
|
|
115
|
+
if not ID_RE.fullmatch(workload_id) or workload_id in seen:
|
|
116
|
+
raise BenchmarkError(f"{path}:{line_number}: invalid or duplicate id")
|
|
117
|
+
if not NAME_RE.fullmatch(lane):
|
|
118
|
+
raise BenchmarkError(f"{path}:{line_number}: invalid lane")
|
|
119
|
+
try:
|
|
120
|
+
weight = int(weight_text)
|
|
121
|
+
except ValueError as exc:
|
|
122
|
+
raise BenchmarkError(f"{path}:{line_number}: weight must be an integer") from exc
|
|
123
|
+
if not 1 <= weight <= 100:
|
|
124
|
+
raise BenchmarkError(f"{path}:{line_number}: weight must be 1..100")
|
|
125
|
+
try:
|
|
126
|
+
re.compile(pattern)
|
|
127
|
+
except re.error as exc:
|
|
128
|
+
raise BenchmarkError(f"{path}:{line_number}: invalid regex: {exc}") from exc
|
|
129
|
+
if not prompt.strip():
|
|
130
|
+
raise BenchmarkError(f"{path}:{line_number}: prompt must not be empty")
|
|
131
|
+
seen.add(workload_id)
|
|
132
|
+
workloads.append(
|
|
133
|
+
{
|
|
134
|
+
"id": workload_id,
|
|
135
|
+
"lane": lane,
|
|
136
|
+
"weight": weight,
|
|
137
|
+
"pattern": pattern,
|
|
138
|
+
"prompt": prompt,
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
if not workloads:
|
|
142
|
+
raise BenchmarkError(f"no workloads found: {path}")
|
|
143
|
+
return workloads
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def parse_costs(items):
|
|
147
|
+
costs = {}
|
|
148
|
+
for item in items:
|
|
149
|
+
if "=" not in item:
|
|
150
|
+
raise BenchmarkError("--cost-per-call must use VENDOR=USD")
|
|
151
|
+
vendor, raw = item.split("=", 1)
|
|
152
|
+
if not NAME_RE.fullmatch(vendor):
|
|
153
|
+
raise BenchmarkError(f"invalid cost vendor: {vendor}")
|
|
154
|
+
try:
|
|
155
|
+
value = Decimal(raw)
|
|
156
|
+
except InvalidOperation as exc:
|
|
157
|
+
raise BenchmarkError(f"invalid cost for {vendor}: {raw}") from exc
|
|
158
|
+
if not value.is_finite() or value < 0:
|
|
159
|
+
raise BenchmarkError(f"cost for {vendor} must be non-negative")
|
|
160
|
+
costs[vendor] = value
|
|
161
|
+
return costs
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def list_routes(dispatch, env):
|
|
165
|
+
result = subprocess.run(
|
|
166
|
+
[str(dispatch), "--list"],
|
|
167
|
+
text=True,
|
|
168
|
+
stdout=subprocess.PIPE,
|
|
169
|
+
stderr=subprocess.PIPE,
|
|
170
|
+
env=env,
|
|
171
|
+
timeout=15,
|
|
172
|
+
check=False,
|
|
173
|
+
)
|
|
174
|
+
if result.returncode != 0:
|
|
175
|
+
detail = result.stderr.strip().splitlines()[-1:] or ["unknown error"]
|
|
176
|
+
raise BenchmarkError(f"cannot list routing: {detail[0]}")
|
|
177
|
+
lanes = []
|
|
178
|
+
vendors = []
|
|
179
|
+
for line in result.stdout.splitlines():
|
|
180
|
+
if ":" not in line:
|
|
181
|
+
continue
|
|
182
|
+
lane, raw = line.split(":", 1)
|
|
183
|
+
lane = lane.strip()
|
|
184
|
+
if not NAME_RE.fullmatch(lane):
|
|
185
|
+
continue
|
|
186
|
+
lanes.append(lane)
|
|
187
|
+
try:
|
|
188
|
+
fields = shlex.split(raw.strip())
|
|
189
|
+
except ValueError:
|
|
190
|
+
fields = raw.split()
|
|
191
|
+
if fields and NAME_RE.fullmatch(fields[0]) and fields[0] not in {
|
|
192
|
+
"off",
|
|
193
|
+
"exec",
|
|
194
|
+
"vote",
|
|
195
|
+
}:
|
|
196
|
+
vendors.append(fields[0])
|
|
197
|
+
return unique(lanes), unique(vendors)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def resolve_route(dispatch, vendor, preferred_lane, lanes, prompt, env):
|
|
201
|
+
last_error = "vendor is not configured in an available lane"
|
|
202
|
+
for lane in unique([preferred_lane] + list(lanes)):
|
|
203
|
+
result = subprocess.run(
|
|
204
|
+
[
|
|
205
|
+
str(dispatch),
|
|
206
|
+
"--dry-run",
|
|
207
|
+
"--mode",
|
|
208
|
+
"advise",
|
|
209
|
+
"--vendor",
|
|
210
|
+
vendor,
|
|
211
|
+
lane,
|
|
212
|
+
prompt,
|
|
213
|
+
],
|
|
214
|
+
text=True,
|
|
215
|
+
stdout=subprocess.PIPE,
|
|
216
|
+
stderr=subprocess.PIPE,
|
|
217
|
+
env=env,
|
|
218
|
+
timeout=15,
|
|
219
|
+
check=False,
|
|
220
|
+
)
|
|
221
|
+
if result.returncode == 0:
|
|
222
|
+
plan = parse_plan(result.stdout)
|
|
223
|
+
if plan.get("provider_invoked") != "no":
|
|
224
|
+
return None, "dry-run contract did not confirm provider_invoked=no"
|
|
225
|
+
if plan.get("vendor") != vendor:
|
|
226
|
+
return None, "dry-run resolved a different vendor"
|
|
227
|
+
plan.setdefault("lane", lane)
|
|
228
|
+
return plan, ""
|
|
229
|
+
detail = result.stderr.strip().splitlines()
|
|
230
|
+
if detail:
|
|
231
|
+
last_error = detail[-1]
|
|
232
|
+
return None, last_error
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def money(value):
|
|
236
|
+
return format(value.quantize(Decimal("0.01")), "f")
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def cost_report(vendor, calls, costs):
|
|
240
|
+
if vendor not in costs:
|
|
241
|
+
return None
|
|
242
|
+
per_call = costs[vendor]
|
|
243
|
+
return {
|
|
244
|
+
"basis": "user-supplied-per-call",
|
|
245
|
+
"currency": "USD",
|
|
246
|
+
"per_call_usd": money(per_call),
|
|
247
|
+
"estimated_total_usd": money(per_call * calls),
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def run_one(repo, vendor, plan, workload, timeout, env):
|
|
252
|
+
runner = repo / "scripts" / "runners" / f"run-{vendor}.sh"
|
|
253
|
+
base = {
|
|
254
|
+
"id": workload["id"],
|
|
255
|
+
"requested_lane": workload["lane"],
|
|
256
|
+
"resolved_lane": plan.get("lane", workload["lane"]),
|
|
257
|
+
"model": plan.get("model", "-"),
|
|
258
|
+
"effort": plan.get("effort", "-"),
|
|
259
|
+
"weight": workload["weight"],
|
|
260
|
+
}
|
|
261
|
+
if not runner.is_file() or not os.access(runner, os.X_OK):
|
|
262
|
+
return dict(base, status="runner_error", duration_seconds=0.0, response_bytes=0), True
|
|
263
|
+
|
|
264
|
+
started = time.monotonic()
|
|
265
|
+
with tempfile.TemporaryDirectory(prefix="omnilane-benchmark-") as temporary:
|
|
266
|
+
temporary_path = Path(temporary)
|
|
267
|
+
prompt_file = temporary_path / "prompt.txt"
|
|
268
|
+
output_file = temporary_path / "response.txt"
|
|
269
|
+
prompt_file.write_text(str(workload["prompt"]) + "\n", encoding="utf-8")
|
|
270
|
+
run_env = env.copy()
|
|
271
|
+
run_env["OMNILANE_TIMEOUT"] = str(timeout)
|
|
272
|
+
try:
|
|
273
|
+
result = subprocess.run(
|
|
274
|
+
[
|
|
275
|
+
str(runner),
|
|
276
|
+
"advise",
|
|
277
|
+
str(repo),
|
|
278
|
+
plan.get("model", "-"),
|
|
279
|
+
plan.get("effort", "-"),
|
|
280
|
+
str(prompt_file),
|
|
281
|
+
str(output_file),
|
|
282
|
+
],
|
|
283
|
+
stdout=subprocess.DEVNULL,
|
|
284
|
+
stderr=subprocess.DEVNULL,
|
|
285
|
+
env=run_env,
|
|
286
|
+
timeout=timeout + 5,
|
|
287
|
+
check=False,
|
|
288
|
+
)
|
|
289
|
+
except subprocess.TimeoutExpired:
|
|
290
|
+
elapsed = round(time.monotonic() - started, 3)
|
|
291
|
+
return dict(base, status="runner_error", duration_seconds=elapsed, response_bytes=0), True
|
|
292
|
+
elapsed = round(time.monotonic() - started, 3)
|
|
293
|
+
try:
|
|
294
|
+
body = output_file.read_text(encoding="utf-8")
|
|
295
|
+
response_bytes = output_file.stat().st_size
|
|
296
|
+
except OSError:
|
|
297
|
+
body = ""
|
|
298
|
+
response_bytes = 0
|
|
299
|
+
if result.returncode != 0:
|
|
300
|
+
status = "runner_error"
|
|
301
|
+
runner_error = True
|
|
302
|
+
else:
|
|
303
|
+
status = "passed" if re.search(str(workload["pattern"]), body) else "failed"
|
|
304
|
+
runner_error = False
|
|
305
|
+
return dict(
|
|
306
|
+
base,
|
|
307
|
+
status=status,
|
|
308
|
+
duration_seconds=elapsed,
|
|
309
|
+
response_bytes=response_bytes,
|
|
310
|
+
), runner_error
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def execute(args):
|
|
314
|
+
repo = Path(os.environ.get("OMNILANE_BENCHMARK_REPO", str(ROOT))).resolve()
|
|
315
|
+
dispatch = repo / "scripts" / "dispatch.sh"
|
|
316
|
+
if not dispatch.is_file() or not os.access(dispatch, os.X_OK):
|
|
317
|
+
raise BenchmarkError(f"dispatch is not executable: {dispatch}")
|
|
318
|
+
env = os.environ.copy()
|
|
319
|
+
workloads = load_workloads(args.workloads)
|
|
320
|
+
costs = parse_costs(args.cost_per_call)
|
|
321
|
+
lanes, configured_vendors = list_routes(dispatch, env)
|
|
322
|
+
vendors = unique(args.vendor or configured_vendors)
|
|
323
|
+
if not vendors:
|
|
324
|
+
raise BenchmarkError("no configured benchmark vendors found")
|
|
325
|
+
for vendor in vendors:
|
|
326
|
+
if not NAME_RE.fullmatch(vendor) or vendor in {"off", "exec", "vote"}:
|
|
327
|
+
raise BenchmarkError(f"unsupported benchmark vendor: {vendor}")
|
|
328
|
+
|
|
329
|
+
report = {
|
|
330
|
+
"schema_version": 1,
|
|
331
|
+
"command": "benchmark",
|
|
332
|
+
"mode": "run" if args.run else "dry-run",
|
|
333
|
+
"provider_invoked": False,
|
|
334
|
+
"workload_count": len(workloads),
|
|
335
|
+
"workloads_file": str(args.workloads.resolve()),
|
|
336
|
+
"vendors": [],
|
|
337
|
+
}
|
|
338
|
+
had_error = False
|
|
339
|
+
provider_invoked = False
|
|
340
|
+
for vendor in vendors:
|
|
341
|
+
vendor_started = time.monotonic()
|
|
342
|
+
items = []
|
|
343
|
+
passed = 0
|
|
344
|
+
score_possible = 0
|
|
345
|
+
score_earned = 0
|
|
346
|
+
for workload in workloads:
|
|
347
|
+
score_possible += int(workload["weight"])
|
|
348
|
+
plan, error = resolve_route(
|
|
349
|
+
dispatch,
|
|
350
|
+
vendor,
|
|
351
|
+
workload["lane"],
|
|
352
|
+
lanes,
|
|
353
|
+
workload["prompt"],
|
|
354
|
+
env,
|
|
355
|
+
)
|
|
356
|
+
if plan is None:
|
|
357
|
+
items.append(
|
|
358
|
+
{
|
|
359
|
+
"id": workload["id"],
|
|
360
|
+
"requested_lane": workload["lane"],
|
|
361
|
+
"weight": workload["weight"],
|
|
362
|
+
"status": "unavailable",
|
|
363
|
+
"duration_seconds": 0.0,
|
|
364
|
+
"response_bytes": 0,
|
|
365
|
+
"error": error,
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
had_error = True
|
|
369
|
+
continue
|
|
370
|
+
if not args.run:
|
|
371
|
+
items.append(
|
|
372
|
+
{
|
|
373
|
+
"id": workload["id"],
|
|
374
|
+
"requested_lane": workload["lane"],
|
|
375
|
+
"resolved_lane": plan.get("lane", workload["lane"]),
|
|
376
|
+
"model": plan.get("model", "-"),
|
|
377
|
+
"effort": plan.get("effort", "-"),
|
|
378
|
+
"weight": workload["weight"],
|
|
379
|
+
"status": "planned",
|
|
380
|
+
"duration_seconds": 0.0,
|
|
381
|
+
"response_bytes": 0,
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
continue
|
|
385
|
+
provider_invoked = True
|
|
386
|
+
item, runner_error = run_one(
|
|
387
|
+
repo, vendor, plan, workload, args.timeout, env
|
|
388
|
+
)
|
|
389
|
+
items.append(item)
|
|
390
|
+
had_error = had_error or runner_error
|
|
391
|
+
if item["status"] == "passed":
|
|
392
|
+
passed += 1
|
|
393
|
+
score_earned += int(workload["weight"])
|
|
394
|
+
|
|
395
|
+
quality = None
|
|
396
|
+
if args.run and score_possible:
|
|
397
|
+
quality = round((score_earned / score_possible) * 100, 2)
|
|
398
|
+
if quality.is_integer():
|
|
399
|
+
quality = int(quality)
|
|
400
|
+
report["vendors"].append(
|
|
401
|
+
{
|
|
402
|
+
"vendor": vendor,
|
|
403
|
+
"planned_calls": len(workloads),
|
|
404
|
+
"passed": passed,
|
|
405
|
+
"score_earned": score_earned,
|
|
406
|
+
"score_possible": score_possible,
|
|
407
|
+
"quality_percent": quality,
|
|
408
|
+
"duration_seconds": round(time.monotonic() - vendor_started, 3),
|
|
409
|
+
"cost": cost_report(vendor, len(workloads), costs),
|
|
410
|
+
"workloads": items,
|
|
411
|
+
}
|
|
412
|
+
)
|
|
413
|
+
report["provider_invoked"] = provider_invoked
|
|
414
|
+
return report, 1 if had_error else 0
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def print_human(report):
|
|
418
|
+
invoked = "yes" if report["provider_invoked"] else "no"
|
|
419
|
+
print(f"mode={report['mode']} provider_invoked={invoked}")
|
|
420
|
+
for vendor in report["vendors"]:
|
|
421
|
+
quality = vendor["quality_percent"]
|
|
422
|
+
quality_text = "planned" if quality is None else f"{quality}%"
|
|
423
|
+
cost = vendor["cost"]
|
|
424
|
+
cost_text = "not supplied" if cost is None else f"USD {cost['estimated_total_usd']}"
|
|
425
|
+
print(
|
|
426
|
+
f"{vendor['vendor']}: calls={vendor['planned_calls']} "
|
|
427
|
+
f"quality={quality_text} estimated_cost={cost_text}"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def main(argv=None):
|
|
432
|
+
args = parse_args(argv)
|
|
433
|
+
try:
|
|
434
|
+
report, return_code = execute(args)
|
|
435
|
+
except BenchmarkError as exc:
|
|
436
|
+
print(f"omnilane benchmark: {exc}", file=sys.stderr)
|
|
437
|
+
return 2
|
|
438
|
+
if args.json:
|
|
439
|
+
print(json.dumps(report, ensure_ascii=False, separators=(",", ":")))
|
|
440
|
+
else:
|
|
441
|
+
print_human(report)
|
|
442
|
+
return return_code
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
if __name__ == "__main__":
|
|
446
|
+
raise SystemExit(main())
|
package/scripts/check.sh
CHANGED
|
@@ -75,7 +75,7 @@ fi
|
|
|
75
75
|
|
|
76
76
|
# 4) python compile of the UI and test modules
|
|
77
77
|
py_files=()
|
|
78
|
-
for rel in scripts/ui.py tests/test_ui.py tests/test_ci_policy.py tests/ui_browser_harness.py; do
|
|
78
|
+
for rel in scripts/ui.py scripts/benchmark.py tests/test_ui.py tests/test_benchmark.py tests/test_ci_policy.py tests/ui_browser_harness.py; do
|
|
79
79
|
[[ -f "$REPO/$rel" ]] && py_files+=("$REPO/$rel")
|
|
80
80
|
done
|
|
81
81
|
if ! command -v python3 >/dev/null 2>&1; then
|