response.modelRead the model that answered
You send enthalpy-1; the model field that comes back is the id of the model that actually generated the text. Billing, evaluation, and reproduction all follow that field.
4ROUTER / Integration
Update your base_url and key. Keep your existing OpenAI client. Model routing, usage billing and decision records come with the same call.
enthalpy-1/v1/chat/completionsmessages + enthalpyenthalpy-1choices + enthalpy_routeOpenAI-compatible. A client that ignores the enthalpy and enthalpy_route objects still receives a fully valid chat completion.
curl -sS http://127.0.0.1:8080/v1/chat/completions \
-H "Authorization: Bearer $ENTHALPY_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "enthalpy-1",
"messages": [{"role": "user", "content": "把这段 changelog 总结成三行"}],
"enthalpy": {"cost_weight": 1, "explain": true}
}'model carries the service model name (ENTHALPY_SERVICE_MODEL_NAME, default enthalpy-1), meaning: you choose. Put the real id of a model in the pool there instead and the request becomes PINNED — the router is skipped and that model answers directly, while auth, admission, billing, and the decision log all stay in place. It is the escape hatch that makes moving over safe to try.
An unknown field inside the enthalpy block is a 400 (extra="forbid") — a typo is a loud error rather than a setting that is silently ignored. cost_weight takes a value in [0, 8], in units of one logit per log1p(est_cost / $0.01), and defaults to 0.
{
"model": "kimi-k2.5",
"enthalpy_route": {
"selected_model": "kimi-k2.5",
"billed_usd": "0.00044520",
"router_latency_ms": 71.1,
…
}
}Money is always a string: billed_usd is exact to 1e-8 of a dollar, and a JSON float would round away the precision the ledger took care to keep. router_latency_ms is the time the routing step spent on its own, reported alongside total_latency_ms, so the overhead of this layer can be seen and questioned by itself.
{
"id": "chatcmpl-…",
"object": "chat.completion",
"model": "kimi-k2.5",
"choices": [{ "index": 0, "message": { "role": "assistant", "content": "…" },
"finish_reason": "stop" }],
"usage": { "prompt_tokens": 41, "completion_tokens": 260, "total_tokens": 301 },
"enthalpy_route": {
"request_id": "…",
"mode": "select",
"selected_model": "kimi-k2.5",
"attempts": [ { "step": 0, "model_id": "kimi-k2.5", "score": 14.678,
"accepted": true, "error": null,
"prompt_tokens": 41, "completion_tokens": 260,
"upstream_cost_usd": 0.000371, "latency_ms": 2841.0 } ],
"upstream_cost_usd": 0.000371,
"billed_usd": "0.00044520",
"router_latency_ms": 71.1,
"total_latency_ms": 2912.4,
"accept_prob": null,
"checkpoint_id": "…",
"pool_fingerprint": "…",
"notes": [],
"candidates": [ /* with explain: true, one row per candidate */ ],
"plan": null
}
}The base URL above is the loopback address the documentation uses. The hosted 4router.cn/v1 is still being connected to upstream credentials, and until it answers, no unreachable address is printed here.
The integration contract
A familiar interface, with explicit fields for the model, the charge, the stream and the failure.
response.modelYou send enthalpy-1; the model field that comes back is the id of the model that actually generated the text. Billing, evaluation, and reproduction all follow that field.
stream: trueThe enthalpy_route frame is emitted before data: [DONE] — a client stops as soon as it reads [DONE]. Read the frames in order; do not skip ahead.
max_cost_usdenthalpy.max_cost_usd is not advisory. A candidate over budget is marked ineligible during scoring, and exclusion_reason states why.
eligible: falseexclusion_reason: "budget"
error.typeThe error object includes a stable type, a readable message, optional structured context and a request_id. Handle failures by type and correlate the request with its logs.
402 · budget_exceeded429 · quota_exceeded
Run locally. Reproduce the result.
The examples target a local development endpoint. Start the service, run the evaluation and inspect the reports behind the result.
Not one number on this page is typed by hand — web/crates/ui/build.rs generates them at compile time, as typed constants, from the JSON under reports/. Change an evaluation result and the page follows; drop a field and the build fails naming the file and the JSON path, rather than quietly rendering a 0.
docker compose -f deploy/docker-compose.yml up -d
docker compose -f deploy/docker-compose.yml logs -f gateway# 1. Sample a matrix over your own pool (docs/TRAINING.md §1b quotes the cost)
enthalpy data harvest --dataset gsm8k --dataset math500 --split test \
--limit 1000 --out data/matrices/mine.jsonl.gz
# 2. Train a checkpoint
enthalpy train sft --matrix data/matrices/mine.jsonl.gz --name gen1
# 3. Evaluate -- free, and it calls no provider
enthalpy eval --matrix data/matrices/mine.jsonl.gz \
--checkpoint gen1 \
--test-fraction 0.3 --split-seed 0 \
--reference best_single \
--sweep-cost \
--out reports/gen1.json--out writes both reports/gen1.json — every number, for regression — and reports/gen1.md, which carries the headline, the provenance line, the results table, the † note, the bound note, and the comparison table.
Quoted from docs/BENCHMARKS.md §7:
The raw artefacts are in the repository: reports/crb-gen2.json, reports/crb-gen2.sweep.json, reports/crb-gen2-vs-knn.json, reports/router-latency.cpu.json, and reports/stage0-routereval.report.json. Every number on the page can be traced to its source in one of them.
4ROUTER / SYSTEMS RESEARCH
Understand how operators, KV state and scheduling shape an inference path.
Read the systems notes in Resources ↗One optimizes data movement; the other organizes persistent state.
↗PREFIX / REUSEReuse a prefix state with compatible computational history.
↗TIERS / TRANSFERAvoiding prefill requires paying for state access.
↗Build with 4Router