OpenAI-compatible LLM client, a harness that compares multiple models on the same case set, and a browser UI for the results — all in MoonBit.
Dependencies
| binary | what it does | source |
|---|---|---|
| faceoff | ask one prompt, once or streamed | cmd/faceoff |
| bench | run a suite against several models and compare them | cmd/bench |
| web server | interactive page: pick models/params, run, watch results | web/cmd/server |
| platform | command |
|---|---|
| macOS / Linux | curl -fsSL https://cli.moonbitlang.com/install/unix.sh \| bash |
| Windows (PowerShell) | Set-ExecutionPolicy RemoteSigned -Scope CurrentUser; irm https://cli.moonbitlang.com/install/powershell.ps1 \| iex |
| VS Code | command palette → MoonBit:install latest moonbit toolchain |
git clone <this repo> && cd moonbit-llm-faceoff
moon run --target native scripts/demo.mbtx # the same as make demo==> 1/3 one-shot
航空母舰是一种以舰载机为主要作战武器的大型水面舰艇。
==> 2/3 streaming (fragments arrive one by one)
侧风掠过甲板,把雨线吹成斜的。
==> 3/3 comparing two models
runs 1 failures 0 truncated 0 retried 0make serve # builds the page, then serves it from web/ → http://127.0.0.1:8137/
make dev # the same, but watching the sources: a page change rebuilds the page, a server change restarts it
make serve-demo # mock endpoint + demo data + page: the quickest look at the whole thingexport MOONLLM_BASE_URL="https://api.deepseek.com/v1" # or any compatible base URL
export MOONLLM_MODEL="deepseek-chat"
export MOONLLM_API_KEY="sk-..."
make deps && MOON_CC=gcc moon build --target native # build once
faceoff=./_build/native/debug/build/cmd/faceoff/faceoff.exe
bench=./_build/native/debug/build/cmd/bench/bench.exe
$faceoff "用一句话说明什么是航空母舰" # one-shot
$faceoff --stream "写一首关于侧风的短诗" # streamed, printed as fragments arrive
echo "总结一下这段日志" | $faceoff --stream # prompt from stdin
mkdir -p my-run && printf '%s\n' '{"id":"math-short","prompt":"计算 17 × 23。只输出数字。"}' > my-run/cases.jsonl
$bench --models <model-a>,<model-b> --cases my-run/cases.jsonl --repeats 3 --max-tokens 2048 \
--temperature 0.0 --pace-ms 1000 --retry 2 --json my-run/runs.jsonl
moon run --target native scripts/build-web.mbtx my-run/runs.jsonl # → web/out/report.html
moon run --target native scripts/real-gateway.mbtx # → docs/real-gateway-run.md| path | what |
|---|---|
| $XDG_DATA_HOME/faceoff/cases/default.jsonl | the generated case set: seeded from bench/cases.example.jsonl the first time the server starts with an empty cases directory. Your own sets live here too |
| $XDG_CONFIG_HOME/faceoff/presets.json | presets: models plus parameters |
| $XDG_DATA_HOME/faceoff/runs/ | run history, one directory per run |
| web/data.json | page data exported from a run; a build artifact inside the repository, gitignored |
| web/out/ | built page and static report, produced by scripts/build-web.mbtx; a build artifact inside the repository, gitignored |
| document | covers |
|---|---|
| docs/cli.md | faceoff and bench: installing them, every flag, the environment variables, the suite format, the metrics, rate limits and replay, the outputs, the loop end to end, and the probes against a real gateway |
| docs/web.md | the page and its server: what every panel does, the URL parameters, the HTTP API, the server environment, the static report, and the web/ layout |
| docs/library.md | using bench and the client as MoonBit packages: the import alias, one-shot, streaming, benchmarking, and the error type |
| docs/testing.md | what make ci runs, the five suites (85 unit tests plus the end-to-end ones) and what each covers, why four of them are shaped that way, and the two suites CI does not run |
| command | what it does | doc |
|---|---|---|
| make demo | zero-API-key demo of all three paths | docs/cli.md |
| make serve / make dev | build the page, then serve it from web/; dev rebuilds on change | docs/web.md |
| make serve-demo | mock endpoint + demo data + page | docs/web.md |
| make install / make uninstall | install both CLIs into ~/.moon/bin / remove them again | docs/cli.md |
| make ci / make e2e | the deterministic suite CI runs / that plus the browser test | docs/testing.md |
| make real-gateway | four probes against a real endpoint; needs a key, not in CI | docs/testing.md |
| make web | build the page into web/out/ and the static report | docs/web.md |
| package | license | used for |
|---|---|---|
| moonbitlang/async | Apache-2.0 | HTTP client, streaming reads, the local server behind the page, and the concurrency the harness runs on |
| moonbit-community/rabbita | Apache-2.0 | the page app and the static report generator |
| conglinyizhi/precss | Apache-2.0 | compiling web/styles/site.scss |
pub(all) suberror ClientError {
Transport(String)
Status(code~ : Int, message~ : String)
Decode(String)
}pub(all) suberror ConfigError {
MissingApiKey
MissingValue(flag~ : String)
BadNumber(flag~ : String, value~ : String)
UnknownFlag(flag~ : String)
}pub(all) struct AskOutcome {
content : String
reasoning : String
usage : TokenUsage?
finish_reason : String?
}pub(all) struct Cli {
settings : Settings
prompt : String?
stream : Bool
show_cot : Bool
quiet : Bool
show_help : Bool
}pub(all) struct Settings {
api_key : String
base_url : String
model : String
system : String
temperature : Double?
max_tokens : Int?
timeout_ms : Int
enable_thinking : Bool
reasoning_effort : String?
} derive(Eq)pub(all) enum SseEvent {
Content(String)
Reasoning(String)
Usage(TokenUsage)
Finish(String)
Done
Ignore
} derive(Eq, Debug)pub(all) struct StreamOutcome {
content : String
reasoning : String
usage : TokenUsage?
finish_reason : String?
}fn TokenUsage::new(prompt_tokens : Int, completion_tokens : Int, reasoning_tokens : Int) -> TokenUsageasync fn read_prompt_from_stdin() -> Stringasync fn stream_chat(settings : Settings, prompt : String, on_delta : async (String) -> Unit) -> String raise ClientErrorasync fn stream_parts(settings : Settings, prompt : String, on_part : async (StreamPart) -> Unit) -> StreamOutcome raise ClientErrorInstall
Download zipOpenAI-compatible LLM client, a harness that compares multiple models on the same case set, and a browser UI for the results — all in MoonBit.
Dependencies