Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

501 lines
17 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
CLI entry point for HyperliquidBench.
Two modes are supported via ``--mode``:
* ``eliza`` (default) — routes plan generation through the eliza TypeScript benchmark
server via ``eliza_adapter.hyperliquid.ElizaHyperliquidAgent``. The Rust
execution path (``hl-runner`` + ``hl-evaluator``) is reused unchanged.
* ``deterministic`` / ``python`` — local deterministic demo plan generation,
retained for smoke tests and offline harness validation.
Examples:
# Eliza TS bridge, demo (starts the benchmark server automatically)
python -m benchmarks.HyperliquidBench --demo
# Free-form coverage scenario with specific coins
python -m benchmarks.HyperliquidBench --coins ETH,BTC,SOL --max-steps 7
# Run scenarios from task files
python -m benchmarks.HyperliquidBench --tasks hl_perp_basic_01.jsonl
# Live testnet (requires HL_PRIVATE_KEY)
python -m benchmarks.HyperliquidBench --network testnet --no-demo
"""
from __future__ import annotations
import argparse
import asyncio
import json
import logging
import os
import sys
from dataclasses import replace
from datetime import datetime
from pathlib import Path
# Ensure the eliza-adapter package is importable for the eliza TS bridge mode.
_ELIZA_ADAPTER_PKG = Path(__file__).resolve().parents[1] / "eliza-adapter"
if _ELIZA_ADAPTER_PKG.exists() and str(_ELIZA_ADAPTER_PKG) not in sys.path:
sys.path.insert(0, str(_ELIZA_ADAPTER_PKG))
EDGE_VARIANTS = (
"Use a conservative order size and avoid unnecessary leverage changes.",
"Prefer actions that are reversible in demo mode and cancel residual orders.",
"Exercise at least one cancellation path when the requested plan allows it.",
"Preserve the requested coin universe and do not introduce unrelated markets.",
"Route through builder-code handling if configured by the scenario.",
"Confirm transfer direction semantics before adding any USD class transfer step.",
"Use reduce-only only when the plan has an offsetting or risk-reducing intent.",
"Avoid market-impacting assumptions; use bounded prices or demo-safe placeholders.",
"Keep the plan under the scenario step budget even when adding validation actions.",
"Favor explicit time-in-force choices so evaluator coverage can attribute intent.",
)
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog="benchmarks.HyperliquidBench",
description="Run HyperliquidBench scenarios through an Eliza agent",
)
# Mode selection: bridge-backed Eliza by default; deterministic local path
# remains for offline smoke tests.
parser.add_argument(
"--mode",
type=str,
default="eliza",
choices=["eliza", "deterministic", "python"],
help=(
"Agent backend. 'eliza' routes plan generation through the eliza "
"TypeScript benchmark server via eliza_adapter.hyperliquid (default). "
"'deterministic'/'python' use the local deterministic smoke agent."
),
)
# Scenario selection
parser.add_argument(
"--tasks",
nargs="*",
default=None,
help=(
"Task JSONL filenames (relative to dataset/tasks/). "
"If omitted, loads all task files or uses a free-form coverage scenario."
),
)
parser.add_argument(
"--coverage",
action="store_true",
default=False,
help="Run a single free-form coverage scenario (agent decides the plan)",
)
parser.add_argument(
"--coins",
type=str,
default="ETH,BTC",
help="Comma-separated allowed coins (default: ETH,BTC)",
)
parser.add_argument(
"--max-steps",
type=int,
default=5,
help="Maximum steps the agent can include in a plan (default: 5)",
)
parser.add_argument(
"--builder-code",
type=str,
default=None,
help="Builder code to attach to orders",
)
# Execution settings
parser.add_argument(
"--demo",
action="store_true",
default=True,
help="Run in demo mode no real trading (default)",
)
parser.add_argument(
"--no-demo",
action="store_true",
default=False,
help="Disable demo mode execute on real network",
)
parser.add_argument(
"--network",
type=str,
default="testnet",
choices=["testnet", "mainnet", "local"],
help="Network to target (default: testnet)",
)
# Model settings
parser.add_argument(
"--model",
type=str,
default=None,
help=(
"Model name for plan generation. Defaults to gemma-4-31b for "
"Cerebras and openai/gpt-oss-120b otherwise."
),
)
parser.add_argument(
"--temperature",
type=float,
default=0.2,
help="Sampling temperature (default: 0.2)",
)
parser.add_argument(
"--max-iterations",
type=int,
default=3,
help="Max iterations per scenario (default: 3)",
)
# Output
parser.add_argument(
"--output",
"--output-dir",
dest="output",
type=str,
default=None,
help=(
"Directory to write the aggregated result JSON file. "
"Defaults to <bench_root>/runs."
),
)
parser.add_argument(
"--verbose", "-v",
action="store_true",
default=False,
help="Enable verbose/debug logging",
)
parser.add_argument(
"--expand-scenarios",
action="store_true",
help="Run ten deterministic trading edge variants per selected scenario.",
)
parser.add_argument(
"--count-scenarios",
action="store_true",
help="Print base/edge/total scenario counts before running.",
)
parser.add_argument(
"--validate-scenarios",
action="store_true",
help="Validate selected scenarios and optional expansion before running.",
)
return parser.parse_args()
def expand_scenarios(scenarios: list[object]) -> list[object]:
"""Return base scenarios plus ten deterministic edge variants each."""
expanded = list(scenarios)
for scenario in scenarios:
scenario_id = str(getattr(scenario, "scenario_id", "scenario"))
description = str(getattr(scenario, "description", ""))
allowed_coins = list(getattr(scenario, "allowed_coins", []) or [])
for index, variant in enumerate(EDGE_VARIANTS, start=1):
coins = list(allowed_coins)
if coins and index % 2 == 0:
coins = [*coins[1:], coins[0]]
expanded.append(
replace(
scenario,
scenario_id=f"{scenario_id}__edge_{index:02d}",
description=f"{description}\n\nEdge condition: {variant}",
allowed_coins=coins,
)
)
return expanded
def count_scenarios(scenarios: list[object], include_edge_scenarios: bool = False) -> dict[str, int]:
base = len(scenarios)
edge = base * len(EDGE_VARIANTS) if include_edge_scenarios else 0
return {
"base": base,
"edge": edge,
"edge_multiplier": len(EDGE_VARIANTS) if include_edge_scenarios else 0,
"total": base + edge,
}
def validate_scenarios(scenarios: list[object], include_edge_scenarios: bool = False) -> None:
if not scenarios:
raise ValueError("HyperliquidBench selected scenario set is empty")
for index, scenario in enumerate(scenarios):
if not str(getattr(scenario, "scenario_id", "")).strip():
raise ValueError(f"scenario {index} missing scenario_id")
if not str(getattr(scenario, "description", "")).strip():
raise ValueError(f"scenario {index} missing description")
if int(getattr(scenario, "max_steps", 0)) <= 0:
raise ValueError(f"scenario {index} has non-positive max_steps")
if include_edge_scenarios:
expanded = expand_scenarios(scenarios)
expected = len(scenarios) * (len(EDGE_VARIANTS) + 1)
if len(expanded) != expected:
raise ValueError(f"expanded scenario count {len(expanded)} != {expected}")
ids = [str(getattr(scenario, "scenario_id", "")) for scenario in expanded]
if len(ids) != len(set(ids)):
raise ValueError("expanded HyperliquidBench scenarios have duplicate ids")
def _build_results_summary(results: list[object]) -> dict[str, object]:
"""Aggregate per-scenario results into the JSON the registry will read."""
scenarios_out: list[dict[str, object]] = []
total_score = 0.0
total_base = 0.0
total_bonus = 0.0
total_penalty = 0.0
passed = 0
for result in results:
evaluator = getattr(result, "evaluator", None)
runner = getattr(result, "runner", None)
scenario_id = getattr(result, "scenario_id", "")
error_message = getattr(result, "error_message", None)
success = bool(evaluator and getattr(evaluator, "success", False))
score = float(getattr(evaluator, "final_score", 0.0)) if evaluator else 0.0
base = float(getattr(evaluator, "base", 0.0)) if evaluator else 0.0
bonus = float(getattr(evaluator, "bonus", 0.0)) if evaluator else 0.0
penalty = float(getattr(evaluator, "penalty", 0.0)) if evaluator else 0.0
sigs = list(getattr(evaluator, "unique_signatures", [])) if evaluator else []
out_dir = getattr(runner, "out_dir", "") if runner else ""
if success:
passed += 1
total_score += score
total_base += base
total_bonus += bonus
total_penalty += penalty
scenarios_out.append({
"scenario_id": scenario_id,
"success": success,
"final_score": score,
"base": base,
"bonus": bonus,
"penalty": penalty,
"unique_signatures": sigs,
"out_dir": out_dir,
"error": error_message,
})
n = max(len(results), 1)
return {
"benchmark": "hyperliquid_bench",
"scenarios": scenarios_out,
"total_scenarios": len(results),
"passed_scenarios": passed,
"final_score": total_score / n, # average per scenario
"total_score": total_score,
"base": total_base,
"bonus": total_bonus,
"penalty": total_penalty,
}
async def _main() -> int:
args = _parse_args()
log_level = logging.DEBUG if args.verbose else logging.INFO
logging.basicConfig(
level=log_level,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%H:%M:%S",
)
# Lazy imports so --help is fast
from .eliza_agent import (
load_scenarios_from_tasks,
make_coverage_scenario,
)
from .types import HLBenchConfig, TradingScenario
bench_root = Path(__file__).resolve().parent
demo_mode = args.demo and not args.no_demo
provider = _detect_model_provider()
model_name = (args.model or os.environ.get("BENCHMARK_MODEL_NAME", "")).strip()
if not model_name:
model_name = _default_model_for_provider(provider)
if args.mode == "eliza":
_apply_model_environment(provider, model_name)
config = HLBenchConfig(
bench_root=bench_root,
demo_mode=demo_mode,
network=args.network,
builder_code=args.builder_code,
model_name=model_name,
temperature=args.temperature,
max_iterations=args.max_iterations,
verbose=args.verbose,
)
coins = [c.strip().upper() for c in args.coins.split(",") if c.strip()]
scenarios: list[TradingScenario] = []
if args.coverage:
scenarios.append(
make_coverage_scenario(
allowed_coins=coins,
max_steps=args.max_steps,
builder_code=args.builder_code,
)
)
elif args.tasks:
scenarios = load_scenarios_from_tasks(bench_root, task_files=args.tasks)
else:
scenarios.append(
make_coverage_scenario(
allowed_coins=coins,
max_steps=args.max_steps,
builder_code=args.builder_code,
)
)
if not scenarios:
logging.error("No scenarios to run")
return 1
if args.validate_scenarios:
validate_scenarios(scenarios, include_edge_scenarios=args.expand_scenarios)
scenario_counts = count_scenarios(scenarios, include_edge_scenarios=args.expand_scenarios)
if args.count_scenarios:
print(json.dumps(scenario_counts, sort_keys=True))
if args.expand_scenarios:
scenarios = expand_scenarios(scenarios)
# Pick the agent backend.
bridge_manager = None
if args.mode == "eliza":
from eliza_adapter.hyperliquid import ElizaHyperliquidAgent as _BridgeAgent
from eliza_adapter.server_manager import ElizaServerManager
bridge_manager = ElizaServerManager()
bridge_manager.start()
agent: object = _BridgeAgent(
config=config,
client=bridge_manager.client,
verbose=args.verbose,
)
logging.info("Using eliza TS bridge agent (eliza_adapter.hyperliquid)")
else:
from .eliza_agent import ElizaHyperliquidAgent as _PythonAgent
agent = _PythonAgent(config=config, verbose=args.verbose)
logging.info("Using local deterministic HyperliquidBench smoke agent")
try:
results = await agent.run_benchmark(scenarios=scenarios) # type: ignore[attr-defined]
finally:
try:
await agent.cleanup() # type: ignore[attr-defined]
finally:
if bridge_manager is not None:
bridge_manager.stop()
summary = _build_results_summary(results)
summary["mode"] = args.mode
summary["model"] = model_name
summary["network"] = args.network
summary["demo_mode"] = demo_mode
summary["include_edge_scenarios"] = bool(args.expand_scenarios)
summary["scenario_counts"] = scenario_counts
# Write the aggregated result JSON in a location the registry can locate.
output_dir = Path(args.output).resolve() if args.output else (bench_root / config.runs_dir)
output_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
out_file = output_dir / f"hyperliquid_bench-{args.mode}-{timestamp}.json"
out_file.write_text(json.dumps(summary, indent=2))
logging.info("Wrote aggregated results to %s", out_file)
# Print the human summary on stdout.
print("\n" + "=" * 60)
print(f"HyperliquidBench — {args.mode} mode results")
print("=" * 60)
for scenario in summary["scenarios"]: # type: ignore[union-attr]
status = "PASS" if scenario["success"] else "FAIL" # type: ignore[index]
print(f"\n [{status}] {scenario['scenario_id']}") # type: ignore[index]
print(
f" Score: {scenario['final_score']:.3f} " # type: ignore[index]
f"(base={scenario['base']:.1f}, bonus={scenario['bonus']:.1f}, "
f"penalty={scenario['penalty']:.1f})"
)
if scenario["unique_signatures"]: # type: ignore[index]
print(f" Signatures: {', '.join(scenario['unique_signatures'])}") # type: ignore[index]
if scenario["error"]: # type: ignore[index]
print(f" Error: {scenario['error']}") # type: ignore[index]
print(f"\n Average final_score: {summary['final_score']:.3f}")
print(f" Scenarios: {summary['total_scenarios']}, Passed: {summary['passed_scenarios']}")
print(f" Result file: {out_file}")
print("=" * 60)
if summary["passed_scenarios"] != summary["total_scenarios"]:
return 1
if not demo_mode:
live_signatures = [
sig
for scenario in summary["scenarios"] # type: ignore[union-attr]
for sig in scenario.get("unique_signatures", []) # type: ignore[union-attr]
]
if not live_signatures:
logging.error("No confirmed live action signatures were recorded")
return 1
return 0
def _detect_model_provider() -> str:
provider = os.environ.get("BENCHMARK_MODEL_PROVIDER", "").strip().lower()
if provider:
return provider
if os.environ.get("CEREBRAS_API_KEY"):
return "cerebras"
if os.environ.get("GROQ_API_KEY"):
return "groq"
if os.environ.get("OPENROUTER_API_KEY"):
return "openrouter"
if os.environ.get("OPENAI_API_KEY"):
return "openai"
return ""
def _default_model_for_provider(provider: str) -> str:
if provider.strip().lower() == "cerebras":
return "gemma-4-31b"
return "openai/gpt-oss-120b"
def _apply_model_environment(provider: str, model_name: str) -> None:
if provider:
os.environ["BENCHMARK_MODEL_PROVIDER"] = provider
os.environ["BENCHMARK_MODEL_NAME"] = model_name
os.environ["OPENAI_LARGE_MODEL"] = model_name
os.environ["OPENAI_SMALL_MODEL"] = model_name
os.environ["GROQ_LARGE_MODEL"] = model_name
os.environ["GROQ_SMALL_MODEL"] = model_name
os.environ["OPENROUTER_LARGE_MODEL"] = model_name
os.environ["OPENROUTER_SMALL_MODEL"] = model_name
os.environ["CEREBRAS_LARGE_MODEL"] = model_name
os.environ["CEREBRAS_SMALL_MODEL"] = model_name
def main() -> None:
"""Synchronous entry point."""
sys.exit(asyncio.run(_main()))
if __name__ == "__main__":
main()