366 lines
14 KiB
Python
366 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import threading
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SCRIPT = ROOT / "scripts" / "run_output_execution.py"
|
|
LOCAL_RUNNER = ROOT / "scripts" / "local_output_eval_runner.py"
|
|
PROVIDER_RUNNER = ROOT / "scripts" / "provider_output_eval_runner.py"
|
|
|
|
|
|
def main() -> None:
|
|
tmp_root = ROOT / "tests" / "tmp_output_execution"
|
|
if tmp_root.exists():
|
|
shutil.rmtree(tmp_root)
|
|
tmp_root.mkdir(parents=True, exist_ok=True)
|
|
|
|
recorded_json = tmp_root / "recorded.json"
|
|
recorded_md = tmp_root / "recorded.md"
|
|
recorded_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--cases",
|
|
str(ROOT / "evals" / "output" / "cases.jsonl"),
|
|
"--output-json",
|
|
str(recorded_json),
|
|
"--output-md",
|
|
str(recorded_md),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
recorded = json.loads(recorded_proc.stdout)
|
|
assert recorded["ok"], recorded
|
|
assert recorded["summary"]["case_count"] == 5, recorded
|
|
assert recorded["summary"]["variant_run_count"] == 10, recorded
|
|
assert recorded["summary"]["recorded_fixture_count"] == 10, recorded
|
|
assert recorded["summary"]["command_executed_count"] == 0, recorded
|
|
assert recorded["summary"]["model_executed_count"] == 0, recorded
|
|
assert recorded["summary"]["token_estimated_count"] == 10, recorded
|
|
assert recorded["summary"]["with_skill_pass_rate"] > recorded["summary"]["baseline_pass_rate"], recorded
|
|
assert "No model-executed runs are recorded yet" in recorded_md.read_text(encoding="utf-8"), recorded_md
|
|
|
|
local_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--cases",
|
|
str(ROOT / "evals" / "output" / "cases.jsonl"),
|
|
"--output-json",
|
|
str(tmp_root / "local.json"),
|
|
"--output-md",
|
|
str(tmp_root / "local.md"),
|
|
"--runner-command",
|
|
json.dumps([sys.executable, str(LOCAL_RUNNER)]),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
local = json.loads(local_proc.stdout)
|
|
assert local["ok"], local
|
|
assert local["summary"]["variant_run_count"] == 10, local
|
|
assert local["summary"]["command_executed_count"] == 10, local
|
|
assert local["summary"]["recorded_fixture_count"] == 0, local
|
|
assert local["summary"]["model_executed_count"] == 0, local
|
|
assert local["summary"]["timing_observed_count"] == 10, local
|
|
assert local["summary"]["token_estimated_count"] == 10, local
|
|
assert all(item["provider"] == "local-output-eval-runner" for item in local["runs"]), local
|
|
local_md = (tmp_root / "local.md").read_text(encoding="utf-8")
|
|
assert "Command runner evidence is present" in local_md, local_md
|
|
assert "not provider-backed model evidence" in local_md, local_md
|
|
|
|
runner = tmp_root / "runner.py"
|
|
runner.write_text(
|
|
"\n".join(
|
|
[
|
|
"import json, sys, time",
|
|
"request = json.loads(sys.stdin.read())",
|
|
"time.sleep(0.001)",
|
|
"print(json.dumps({",
|
|
" 'output': request['fixture_output'],",
|
|
" 'execution_kind': 'model',",
|
|
" 'provider': 'local-fixture',",
|
|
" 'model': 'fixture-model',",
|
|
" 'usage': {'input_tokens': 11, 'output_tokens': 17, 'total_tokens': 28}",
|
|
"}))",
|
|
]
|
|
)
|
|
+ "\n",
|
|
encoding="utf-8",
|
|
)
|
|
command_json = json.dumps([sys.executable, str(runner)])
|
|
command_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--cases",
|
|
str(ROOT / "evals" / "output" / "cases.jsonl"),
|
|
"--output-json",
|
|
str(tmp_root / "command.json"),
|
|
"--output-md",
|
|
str(tmp_root / "command.md"),
|
|
"--runner-command",
|
|
command_json,
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
command = json.loads(command_proc.stdout)
|
|
assert command["ok"], command
|
|
assert command["summary"]["command_executed_count"] == 10, command
|
|
assert command["summary"]["model_executed_count"] == 10, command
|
|
assert command["summary"]["timing_observed_count"] == 10, command
|
|
assert command["summary"]["token_observed_count"] == 10, command
|
|
assert command["summary"]["token_estimated_count"] == 0, command
|
|
assert all(item["duration_ms"] is not None for item in command["runs"]), command
|
|
assert all(item["model"] == "fixture-model" for item in command["runs"]), command
|
|
|
|
provider_request = {
|
|
"case_id": "provider-contract",
|
|
"variant": "with_skill",
|
|
"prompt": "Create a reusable skill package.",
|
|
"input_files": [],
|
|
"metadata": {},
|
|
}
|
|
missing_env = os.environ.copy()
|
|
missing_env.pop("OPENAI_API_KEY", None)
|
|
missing_env.pop("YAO_OUTPUT_EVAL_MODEL", None)
|
|
missing_provider_proc = subprocess.run(
|
|
[sys.executable, str(PROVIDER_RUNNER), "--model", "fixture-model"],
|
|
cwd=ROOT,
|
|
input=json.dumps(provider_request),
|
|
capture_output=True,
|
|
text=True,
|
|
env=missing_env,
|
|
)
|
|
assert missing_provider_proc.returncode == 2, missing_provider_proc.stdout
|
|
assert "missing API key env" in missing_provider_proc.stderr, missing_provider_proc.stderr
|
|
|
|
custom_env = os.environ.copy()
|
|
custom_env["OPENAI_API_KEY"] = "test-key"
|
|
custom_env["YAO_OUTPUT_EVAL_MODEL"] = "fixture-model"
|
|
custom_host_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(PROVIDER_RUNNER),
|
|
"--base-url",
|
|
"https://example.com/v1/responses",
|
|
],
|
|
cwd=ROOT,
|
|
input=json.dumps(provider_request),
|
|
capture_output=True,
|
|
text=True,
|
|
env=custom_env,
|
|
)
|
|
assert custom_host_proc.returncode == 2, custom_host_proc.stdout
|
|
assert "custom provider host requires --allow-custom-base-url" in custom_host_proc.stderr, custom_host_proc.stderr
|
|
|
|
wrong_path_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(PROVIDER_RUNNER),
|
|
"--base-url",
|
|
"https://api.openai.com/v1/chat/completions",
|
|
],
|
|
cwd=ROOT,
|
|
input=json.dumps(provider_request),
|
|
capture_output=True,
|
|
text=True,
|
|
env=custom_env,
|
|
)
|
|
assert wrong_path_proc.returncode == 2, wrong_path_proc.stdout
|
|
assert "provider endpoint path must start with /v1/responses" in wrong_path_proc.stderr, wrong_path_proc.stderr
|
|
empty_path_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(PROVIDER_RUNNER),
|
|
"--base-url",
|
|
"https://api.openai.com",
|
|
],
|
|
cwd=ROOT,
|
|
input=json.dumps(provider_request),
|
|
capture_output=True,
|
|
text=True,
|
|
env=custom_env,
|
|
)
|
|
assert empty_path_proc.returncode == 2, empty_path_proc.stdout
|
|
assert "provider endpoint path must start with /v1/responses" in empty_path_proc.stderr, empty_path_proc.stderr
|
|
|
|
class ProviderHandler(BaseHTTPRequestHandler):
|
|
requests: list[dict] = []
|
|
|
|
def do_POST(self) -> None: # noqa: N802
|
|
length = int(self.headers.get("Content-Length", "0"))
|
|
body = self.rfile.read(length).decode("utf-8")
|
|
ProviderHandler.requests.append(
|
|
{
|
|
"path": self.path,
|
|
"authorization": self.headers.get("Authorization", ""),
|
|
"body": json.loads(body),
|
|
}
|
|
)
|
|
payload = {
|
|
"id": "resp_fixture",
|
|
"output": [
|
|
{
|
|
"content": [
|
|
{
|
|
"type": "output_text",
|
|
"text": "Create SKILL.md and reports/skill-overview.html for this reusable package.",
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"usage": {"input_tokens": 21, "output_tokens": 9, "total_tokens": 30},
|
|
}
|
|
encoded = json.dumps(payload).encode("utf-8")
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(encoded)))
|
|
self.end_headers()
|
|
self.wfile.write(encoded)
|
|
|
|
def log_message(self, format: str, *args: object) -> None:
|
|
return
|
|
|
|
server = HTTPServer(("127.0.0.1", 0), ProviderHandler)
|
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
thread.start()
|
|
provider_url = f"http://127.0.0.1:{server.server_port}/v1/responses"
|
|
provider_env = os.environ.copy()
|
|
provider_env["OPENAI_API_KEY"] = "test-key"
|
|
provider_env["YAO_OUTPUT_EVAL_MODEL"] = "fixture-model"
|
|
try:
|
|
direct_provider_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(PROVIDER_RUNNER),
|
|
"--base-url",
|
|
provider_url,
|
|
"--allow-insecure-localhost",
|
|
],
|
|
cwd=ROOT,
|
|
input=json.dumps(provider_request),
|
|
capture_output=True,
|
|
text=True,
|
|
env=provider_env,
|
|
check=True,
|
|
)
|
|
direct_provider = json.loads(direct_provider_proc.stdout)
|
|
assert direct_provider["execution_kind"] == "model", direct_provider
|
|
assert direct_provider["provider"] == "openai", direct_provider
|
|
assert direct_provider["model"] == "fixture-model", direct_provider
|
|
assert direct_provider["usage"]["estimated"] is False, direct_provider
|
|
assert ProviderHandler.requests[-1]["path"] == "/v1/responses", ProviderHandler.requests[-1]
|
|
assert ProviderHandler.requests[-1]["authorization"] == "Bearer test-key", ProviderHandler.requests[-1]
|
|
assert ProviderHandler.requests[-1]["body"]["model"] == "fixture-model", ProviderHandler.requests[-1]
|
|
assert "Create a reusable skill package." in ProviderHandler.requests[-1]["body"]["input"], ProviderHandler.requests[-1]
|
|
assert "fixture_output" not in ProviderHandler.requests[-1]["body"]["input"], ProviderHandler.requests[-1]
|
|
|
|
provider_cases = tmp_root / "provider_cases.jsonl"
|
|
provider_cases.write_text(
|
|
json.dumps(
|
|
{
|
|
"id": "provider-contract",
|
|
"prompt": "Create a reusable skill package.",
|
|
"baseline_output": "",
|
|
"with_skill_output": "",
|
|
"assertions": [
|
|
{
|
|
"id": "has-skill-entry",
|
|
"description": "Provider output includes the skill entrypoint.",
|
|
"required": ["SKILL.md"],
|
|
}
|
|
],
|
|
},
|
|
ensure_ascii=False,
|
|
)
|
|
+ "\n",
|
|
encoding="utf-8",
|
|
)
|
|
provider_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--cases",
|
|
str(provider_cases),
|
|
"--output-json",
|
|
str(tmp_root / "provider.json"),
|
|
"--output-md",
|
|
str(tmp_root / "provider.md"),
|
|
"--runner-command",
|
|
json.dumps(
|
|
[
|
|
sys.executable,
|
|
str(PROVIDER_RUNNER),
|
|
"--base-url",
|
|
provider_url,
|
|
"--allow-insecure-localhost",
|
|
"--model",
|
|
"fixture-model",
|
|
]
|
|
),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
env=provider_env,
|
|
check=True,
|
|
)
|
|
provider = json.loads(provider_proc.stdout)
|
|
assert provider["ok"], provider
|
|
assert provider["summary"]["variant_run_count"] == 2, provider
|
|
assert provider["summary"]["model_executed_count"] == 2, provider
|
|
assert provider["summary"]["token_observed_count"] == 2, provider
|
|
assert provider["summary"]["token_estimated_count"] == 0, provider
|
|
assert all(item["provider"] == "openai" for item in provider["runs"]), provider
|
|
assert all(item["model"] == "fixture-model" for item in provider["runs"]), provider
|
|
finally:
|
|
server.shutdown()
|
|
thread.join(timeout=2)
|
|
|
|
bad_runner = tmp_root / "bad_runner.py"
|
|
bad_runner.write_text("import sys\nsys.exit(3)\n", encoding="utf-8")
|
|
bad_proc = subprocess.run(
|
|
[
|
|
sys.executable,
|
|
str(SCRIPT),
|
|
"--cases",
|
|
str(ROOT / "evals" / "output" / "cases.jsonl"),
|
|
"--output-json",
|
|
str(tmp_root / "bad.json"),
|
|
"--output-md",
|
|
str(tmp_root / "bad.md"),
|
|
"--runner-command",
|
|
json.dumps([sys.executable, str(bad_runner)]),
|
|
],
|
|
cwd=ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
assert bad_proc.returncode == 2, bad_proc.stdout
|
|
bad = json.loads(bad_proc.stdout)
|
|
assert not bad["ok"], bad
|
|
assert bad["summary"]["failure_count"] == 10, bad
|
|
assert bad["failures"], bad
|
|
|
|
print(json.dumps({"ok": True}, ensure_ascii=False, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|