A fast, offline hard-or-easy call on every prompt — scored deterministically, with no model call. Route the easy ones to your small/local model and the hard ones to your big one, or compose any model-router behind it.每次提示都进行快速、离线、简单或复杂的调用——确定性评分,无需模型调用。将简单的提示路由到小型/本地模型,将复杂的提示路由到大型模型,或者在其后组合任何模型路由器。
Quickstart · Benchmark · How it compares · Explainer · Changelog 快速入门 · 性能基准 · 对比 · 说明 · 更新日志
| No model call to decide the route |
Deterministic and fully offline |
| Calibrate on your own data |
Bring your own key self-hosted |
Wayfinder scores a prompt's structure (length, headings, lists, code) and wording
(proofs, math, hard constraints) into a 0.0–1.0 complexity score, then routes the easy
ones to your small/local model and the hard ones to your big one. The decision is the
product: deterministic, sub-millisecond, and entirely offline — no API key, no network, no
model call to make it. What you route to is yours: two tiers, an N-tier ladder, or a
model-router composed behind it.Wayfinder 会根据提示的结构(长度、标题、列表、代码)和措辞(证明、数学、硬性约束)进行评分,得出一个 0.0-1.0 的复杂度分数,然后将简单的提示路由到小型/本地模型,将复杂的提示路由到大型模型。这个决定是结果:确定性的、亚毫秒级的、完全离线的——无需 API 密钥、无需网络、无需模型调用即可完成。您路由到的目标是您自己的:双层、多层阶梯,或在其后组合的模型路由器。
Cheap prompts stay local and hard ones go to the expensive model, so you stop paying top-tier prices for "summarize this" and "fix my typo."简单的提示保留在本地,复杂的提示发送到昂贵的模型,这样您就不必为“总结这个”和“修复我的错别字”支付顶级价格。
Most routers decide by calling a model: a trained classifier, an LLM judge, or a hosted API. That adds latency, cost, and randomness to the exact step meant to save you money. Wayfinder reads structure and wording instead, so the decision is free and the same every time.大多数路由器通过调用模型来决定:训练好的分类器、LLM 裁判或托管 API。这会增加延迟、成本和随机性,而这些正是旨在为您省钱的步骤。Wayfinder 则读取结构和措辞,因此决定是免费的,并且每次都相同。
| router | decides by | model call? | self-host | calibrate |
|---|---|---|---|---|
| Wayfinder | deterministic structural score | no | yes | yes |
| RouteLLM | trained classifier (preference data) | yes | yes | retrain |
| NotDiamond / Martian | learned, hosted | yes | no | via platform |
| OpenRouter (Auto) | hosted auto-router | yes | no | — |
| Bifrost / LiteLLM | provider gateway (not complexity-routed) | no | yes | n/a |
The gateways in the last two rows (OpenRouter, Bifrost, LiteLLM) answer a different question: which provider serves a call, by price, availability, and failover. Wayfinder answers which tier a prompt deserves: cheap vs expensive, by difficulty, decided offline. The two compose. Run Wayfinder to make the cheap-vs-expensive call, and a gateway underneath to reach the providers.最后两行的网关(OpenRouter、Bifrost、LiteLLM)回答的是另一个问题:哪个提供商根据价格、可用性和故障转移来提供调用。Wayfinder 回答的是提示应该属于哪个层级:便宜的 vs 昂贵的,根据难度,离线决定。两者可以组合。运行 Wayfinder 来做出便宜与昂贵的决定,然后在下方使用网关来连接提供商。
Wayfinder isn't chasing a top accuracy number — it gives you a routing decision you
can run offline and tune on your own traffic. By default it scores prompt
structure only; it can also read lexical cues (proofs, math, constraints), but
those ship off by default because a
double-blind test showed the lift doesn't generalize
(it caught ~20% of unseen hard prompts and lost to a plain word-count baseline). A
prompt whose difficulty is purely semantic (a subtle code snippet, "what is the
100th prime number?") has no structural tell, and a semantic router will beat it
there. The benchmark (make benchmark) shows where it wins
and loses against honest baselines and a perfect oracle; the FAQ
gives the straight version — including that it's no better than random on
RouterBench's short-but-hard items, and why you'd still run it.Wayfinder 不追求顶级的准确率数字——它为您提供一个可以在离线运行并根据您的流量进行调整的路由决策。默认情况下,它仅对提示结构进行评分;它还可以读取词汇线索(证明、数学、约束),但这些默认关闭,因为双盲测试表明其提升不具有通用性(它捕获了约 20% 的未见过的高难度提示,并且在纯词数基线测试中表现不佳)。一个难度纯粹是语义性的提示(一个微妙的代码片段,“第 100 个素数是什么?”)没有结构上的提示,而语义路由器在这种情况下会胜过它。性能基准(make benchmark)显示了它在与诚实的基线和完美的预言机相比时的胜负;FAQ 提供了直接的版本——包括它在 RouterBench 的简短但困难的项目上与随机无异,以及为什么您仍然会运行它。
Two ways to see the routing decision for yourself — no API keys, no models, nothing on the network.两种方式让您亲身体验路由决策——无需 API 密钥,无需模型,无需任何网络连接。
In your terminal — a decision-first chat in the Wayfinder palette. The terminal
chat ships in the default install, so there's nothing extra to add — or run it with
no install at all via uvx:在您的终端中——Wayfinder 调色板中的一个先决策的聊天。终端聊天包含在默认安装中,因此无需额外添加任何内容——或者通过 uvx 完全无需安装即可运行它:
uvx wayfinder-router chat --dry-run # zero install, zero keys
# or: pip install wayfinder-router && wayfinder-router chatEvery turn shows where it routed (● LOCAL / ◆ CLOUD), the structural score and why
(/why), and the running savings vs always-cloud. /init sets up models without leaving
the chat, /route · /local · /cloud force a turn, and conversations persist across
sessions (/threads).每一轮都显示了路由到的位置(● 本地 / ◆ 云端)、结构分数和原因(/why),以及相对于始终云端的运行节省。/init 设置模型而无需离开聊天,/route · /local · /cloud 强制进行一轮,并且对话在会话之间持久存在(/threads)。
In your browser — the web chat UI with a live threshold slider:在您的浏览器中——带有实时阈值滑块的 Web 聊天 UI:
pip install "wayfinder-router[gateway]"
wayfinder-router webchat --dry-run
# opens http://127.0.0.1:8088/demowebchat is a thin launcher over serve (the gateway and its /demo page; --no-open,
--port, --host 0.0.0.0, --dry-run); serve is the headless command. With no config
it's decision-only (--dry-run), so you can poke at it with zero setup; to get real
replies, run wayfinder-router init to scaffold [gateway.models] (then
wayfinder-router doctor to confirm your keys resolve) — see Quickstart.webchat 是 serve 的一个轻量级启动器(网关及其 /demo 页面;--no-open、--port、--host 0.0.0.0、--dry-run);serve 是无头命令。在没有配置的情况下,它仅进行决策(--dry-run),因此您可以零设置进行测试;要获得实际回复,请运行 wayfinder-router init 来生成 [gateway.models](然后运行 wayfinder-router doctor 来确认您的密钥已解析)——请参阅快速入门。
Wayfinder forwards each call to an OpenAI-style /chat/completions endpoint — so if
your provider speaks that (and most do), it just works. A tier is one base_url,
a model name, and a key read from the environment at request time; no SDK, no
per-provider code. Pair a free local model with a hosted one, or run two cloud tiers.Wayfinder 将每个调用转发到 OpenAI 风格的 /chat/completions 端点——因此,如果您的提供商支持该风格(大多数都支持),它就能正常工作。一个层级是一个 base_url、一个模型名称以及在请求时从环境中读取的密钥;无需 SDK,无需特定提供商的代码。将免费的本地模型与托管模型配对,或运行两个云层。
…plus Groq, Together, OpenRouter, Fireworks, DeepSeek, and local servers (vLLM, LM Studio, llama.cpp) — + any OpenAI-compatible endpoint that takes a Bearer key.…以及 Groq、Together、OpenRouter、Fireworks、DeepSeek 和本地服务器(vLLM、LM Studio、llama.cpp)——+ 任何接受 Bearer 密钥的 OpenAI 兼容端点。
Put Wayfinder in front of your models. Your app keeps speaking the OpenAI API; you
just change one base_url.将 Wayfinder 放在您的模型前面。您的应用程序继续使用 OpenAI API;您只需更改一个 base_url。
-
Scaffold a config —
initwrites a starterwayfinder-router.toml(keyless local Ollama → Anthropic cloud) plus a.env.example, then checks your keys:生成配置——init 会写入一个入门级的 wayfinder-router.toml(无密钥的本地 Ollama → Anthropic 云端)以及一个 .env.example,然后检查您的密钥:pip install "wayfinder-router[gateway]" wayfinder-router init # starter config (hybrid preset) wayfinder-router init --preset openai # two OpenAI tiers (gpt-4o-mini → gpt-4o) wayfinder-router init --preset gemini # two Gemini tiers (gemini-2.5-flash → gemini-2.5-pro) wayfinder-router init --interactive # pick providers/models step by step
Or describe your two models in
wayfinder-router.tomlby hand:或者手动在 wayfinder-router.toml 中描述您的两个模型:[routing] threshold = 0.5 # below -> local, at/above -> cloud [gateway.models.local] base_url = "http://localhost:11434/v1" model = "llama3.2" [gateway.models.cloud] base_url = "https://api.openai.com/v1" model = "gpt-4o" api_key_env = "OPENAI_API_KEY" # read from this env var, never stored # api_key_cmd = "op read op://Private/OpenAI/credential" # optional: fill it from a vault
Wayfinder never stores secrets: a model names an env var (
api_key_env) and the key is read from your environment at request time. There is nothing to "install" — just export the variable. Prefer not to paste a raw key into your shell? Add an optionalapi_key_cmdand Wayfinder fills that variable from your secret store at startup —op read …(1Password),security …(macOS Keychain),secret-tool …(Linux),pass/gopass,vault kv get …,aws secretsmanager get-secret-value …,bw,doppler,gcloud secrets …, or any command that prints the secret. The key is held in memory only, still never written to disk.wayfinder-router doctordetects which of these tools you have installed and suggests the exact line.Wayfinder 永远不会存储秘密:模型会指定一个环境变量(api_key_env),密钥在请求时从您的环境中读取。无需“安装”任何东西——只需导出变量。不想将原始密钥粘贴到您的 shell 中?添加一个可选的 api_key_cmd,Wayfinder 会在启动时从您的秘密存储中填充该变量——op read …(1Password)、security …(macOS Keychain)、secret-tool …(Linux)、pass/gopass、vault kv get …、aws secretsmanager get-secret-value …、bw、doppler、gcloud secrets …,或任何打印秘密的命令。密钥仅保存在内存中,绝不会写入磁盘。wayfinder-router doctor 会检测您安装了哪些工具,并建议确切的行。 -
Set your key(s), then run the gateway.
doctorre-checks the config and whether each model's key resolves (✓ set/✗ not set) before you start:设置您的密钥,然后运行网关。doctor 会在您开始之前重新检查配置以及每个模型的密钥是否已解析(✓ 已设置 / ✗ 未设置):export ANTHROPIC_API_KEY=sk-... # or OPENAI_API_KEY, per your config wayfinder-router doctor # ✓/✗ per model — is each key set? wayfinder-router serve --port 8088
-
Point your existing client at it. No code change:将您现有的客户端指向它。无需代码更改:
client = openai.OpenAI(base_url="http://localhost:8088/v1", api_key="unused") client.chat.completions.create(model="auto", messages=[{"role": "user", "content": "..."}])
Easy prompts go local, hard ones go cloud, and every response carries
x-wayfinder-router-model and x-wayfinder-router-score so you can see where it
went. Want to force a tier for one request? Set model="local" or "cloud" (or
prefer-local / prefer-hosted), move the cut for a single call with an
X-Wayfinder-Threshold header, or start a chat message with /local or /cloud
(see Steer a single request).简单的提示转到本地,复杂的提示转到云端,每个响应都带有 x-wayfinder-router-model 和 x-wayfinder-router-score,因此您可以查看它去了哪里。想为单个请求强制使用某个层级?设置 model="local" 或 "cloud"(或 prefer-local / prefer-hosted),使用 X-Wayfinder-Threshold 标头为单个调用更改阈值,或以 /local 或 /cloud 开头的聊天消息开始(请参阅控制单个请求)。
Check it's working:检查是否正常工作:
curl -s localhost:8088/healthz
# {"status":"ok","models":["cloud","local"]}
curl -s -D - -o /dev/null http://localhost:8088/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"auto","messages":[{"role":"user","content":"hi"}]}' \
| grep -i x-wayfinder-router
# x-wayfinder-router-model: local
# x-wayfinder-router-score: 0.00No backends yet? wayfinder-router serve --dry-run answers with the routing
decision instead of calling an upstream, so you can feel the routing in 30 seconds
before wiring up real models.还没有后端?wayfinder-router serve --dry-run 会返回路由决策而不是调用上游,因此您可以在 30 秒内体验路由,然后再连接实际模型。
| command | what you get |
|---|---|
pip install wayfinder-router |
scorer, CLI, Python API, and the terminal chat (chat); the scorer/library imports stay dependency-light |
pip install "wayfinder-router[gateway]" |
adds the OpenAI-compatible routing gateway, the common case for serving |
pip install "wayfinder-router[ui]" |
adds the local calibrate / explain / configure UI |
pip install "wayfinder-router[all]" |
gateway and UI on top of the default install |
Make Wayfinder your machine's always-on LLM endpoint, so every OpenAI-compatible app
can share one local base_url and you set your keys up once. service install
registers it with the OS service manager to start at login and restart if it exits:将 Wayfinder 设置为您的机器上始终运行的 LLM 端点,以便所有 OpenAI 兼容的应用程序都可以共享一个本地 base_url,并且您只需设置一次密钥。service install 将其注册到操作系统服务管理器,以便在登录时启动并在退出时重启:
wayfinder-router service install # macOS (launchd) or Linux (systemd user unit)
wayfinder-router service status # is it running? endpoint + /healthz
wayfinder-router service uninstallThen point your apps at it once — most OpenAI-compatible tools read OPENAI_BASE_URL:然后一次性将您的应用程序指向它——大多数 OpenAI 兼容的工具会读取 OPENAI_BASE_URL:
export OPENAI_BASE_URL=http://127.0.0.1:8088/v1macOS is the primary target; Linux works too. --print emits the unit file without
installing, and if no service manager is present it writes the unit and prints the one
command to start it. It's the same gateway, just kept running — the routing decision is
unchanged.macOS 是主要目标;Linux 也可用。--print 会发出单元文件而不安装,并且如果没有服务管理器,它会写入单元文件并打印启动它的命令。它与网关相同,只是保持运行——路由决策不变。
Wayfinder sits behind whatever OpenAI-compatible client you already use. You point
that client's base_url at the gateway once, and from then on it is invisible. The
same client serves a request whether it routes local or hosted.Wayfinder 位于您已使用的任何 OpenAI 兼容客户端之后。您只需将该客户端的 base_url 指向网关一次,之后它就变得不可见。无论请求路由到本地还是托管,相同的客户端都会处理该请求。
your client (chat app, IDE, agent, or code)
|
v
Wayfinder gateway scores, picks a model
|
|-- low --> local (Ollama, vLLM)
|-- high --> hosted (OpenAI, any /v1)
|
v
response returns via the same client,
with x-wayfinder-router-* headers
A few things follow from this:由此可以得出几点:
- The interface in front is yours. A chat GUI (Open WebUI, LibreChat), an IDE assistant with a custom endpoint (Cursor, Continue), an agent framework, or your own code on the OpenAI SDK. Want a chat window today? Put Open WebUI in front and point it at the gateway.前面的接口是您的。聊天 GUI(Open WebUI、LibreChat)、带有自定义端点的 IDE 助手(Cursor、Continue)、代理框架,或您自己的代码在 OpenAI SDK 上。今天想要一个聊天窗口?将 Open WebUI 放在前面并指向网关。
- Local and hosted are backends, not apps. The local model is just a server
(Ollama, LM Studio, vLLM, llama.cpp) speaking OpenAI's
/v1; the hosted one is the same shape. The user never switches UIs and usually never knows which model answered.本地和托管是后端,而不是应用程序。本地模型只是一个运行 OpenAI /v1 的服务器(Ollama、LM Studio、vLLM、llama.cpp);托管模型也是如此。用户永远不会切换 UI,通常也不知道哪个模型回答了问题。
Keys are read from the environment at request time and never touch the config file or the scored path.密钥在请求时从环境中读取,并且永远不会接触配置文件或评分路径。
echo "Summarise this paragraph in one sentence." | wayfinder-router route -Recommended Model: local
Complexity Score: 0.00 (mode: tiered)
Tiers:
>= 0.00 local <-
>= 0.50 cloud
Contributing Features:
Word Count: 6
...
Add --json for machine consumers (an agent reads this and routes to its own
model):添加 --json 以供机器消费者使用(代理会读取此内容并路由到自己的模型):
{
"schema_version": "3",
"score": 0.66,
"recommendation": "cloud",
"mode": "tiered",
"features": { "word_count": 545, "heading_count": 12, "reasoning_term_count": 3, "...": 0 },
"tiers": [{ "min_score": 0.0, "model": "local" }, { "min_score": 0.5, "model": "cloud" }]
}Wayfinder reads its own wayfinder-router.toml, found by walking up from where you
run it. There are three modes, in precedence order (classifier > tiers >
threshold); the scalar-score weights apply to any of them.Wayfinder 读取其自身的 wayfinder-router.toml 文件,该文件通过从运行它的位置向上查找而找到。有三种模式,按优先级顺序(分类器 > 层级 > 阈值);标量分数权重适用于其中任何一种。
Binary (the default) is a single cut:二进制(默认)是一个单一的阈值:
[routing]
threshold = 0.6
weights = { word_count = 4.0, list_item_count = 2.5 }--threshold N overrides it for one run; WAYFINDER_ROUTER_THRESHOLD overrides it
from the environment.--threshold N 会覆盖单次运行的设置;WAYFINDER_ROUTER_THRESHOLD 会从环境变量覆盖它。
To switch the lexical cues on, raise their weights and cut at the knee — the one
held-out improvement over the structural default on real frontier traffic (skill
−0.038 → +0.057, 61% cost saved on RouterBench). See
docs/lexical-routing.md and the ready-to-edit
examples/wayfinder-router.lexical.toml;
recalibrate the threshold to your own traffic (a ~20-prompt bootstrap is only a smoke
test — see benchmarks/calibration-eval.md).要打开词汇线索,请提高它们的权重并在拐点处进行切割——在真实前沿流量上,这是结构化默认值的一个独立改进(skill -0.038 → +0.057,RouterBench 上节省了 61% 的成本)。请参阅 docs/lexical-routing.md 和可编辑的 examples/wayfinder-router.lexical.toml;根据您的流量重新校准阈值(约 20 个提示的引导仅是初步测试——请参阅 benchmarks/calibration-eval.md)。
Tiered routes ordered score bands to any number of models:分层路由将评分段按顺序映射到任意数量的模型:
[[routing.tiers]]
min_score = 0.0
model = "llama-3b"
[[routing.tiers]]
min_score = 0.3
model = "llama-70b"
[[routing.tiers]]
min_score = 0.6
model = "claude-cloud"Classifier is a fitted multinomial-logistic model, argmax over per-model
linear scores. You usually generate it with calibrate rather than write it by
hand.分类器是拟合的多项逻辑模型,对每个模型的线性分数进行 argmax。您通常使用 calibrate 生成它,而不是手动编写。
Each [gateway.models.<name>] block maps a routed name to an upstream base_url, a
model, and an optional api_key_env (the name of an environment variable, never
the secret itself). The gateway is the only part that touches keys or the network;
the scorer, config, and calibrator stay pure and offline.每个 [gateway.models.<name>] 块将路由的名称映射到上游 base_url、模型和可选的 api_key_env(环境变量的名称,绝不是秘密本身)。网关是唯一接触密钥或网络的组件;评分器、配置器和校准器保持纯净且离线。
The cut is a proxy, so tune it against your own traffic. wayfinder-router calibrate reads a labeled JSONL dataset ({"text": ..., "label": ...}) and prints
a config fragment. It runs offline and never calls a model; the labels are your
ground truth.阈值是一个代理,因此请根据您自己的流量进行调整。wayfinder-router calibrate 读取一个带标签的 JSONL 数据集({"text": ..., "label": ...})并打印一个配置片段。它离线运行,从不调用模型;标签是您的地面真实情况。
wayfinder-router calibrate data.jsonl --mode threshold # sweep the binary cut
wayfinder-router calibrate data.jsonl --mode tiers # ordinal multi-model
wayfinder-router calibrate data.jsonl --mode classifier --out wayfinder-router.tomlThe fragment drops straight into wayfinder-router.toml; the accuracy and chosen
breakpoints print to stderr. The classifier is fit by deterministic L2-regularized
Newton/IRLS, pure Python, converging in a handful of iterations.片段直接放入 wayfinder-router.toml;准确率和选定的断点会打印到 stderr。分类器由确定性的 L2 正则化牛顿/IRLS 拟合,纯 Python,在几次迭代中收敛。
To pick a cut in cost terms instead of bare accuracy, use a cost-aware objective.
--objective knee chooses the cost-aware knee automatically (it maximizes
quality-recovered × cost-saved — no target to guess, and it can't collapse to
always-routing-to-the-expensive-model the way pure accuracy does on skewed labels);
--objective cost-quality --target-savings X instead holds a specific savings floor.
Add --weights to score with — and emit — custom feature weights, e.g. the lexical
opt-in, so the output is a complete, deployable config (see
docs/lexical-routing.md):要选择一个以成本而非纯粹准确率衡量的阈值,请使用成本感知目标。--objective knee 自动选择成本感知拐点(它最大化恢复的质量 × 节省的成本——无需猜测目标,并且它不会像纯粹准确率在偏斜标签上那样崩溃为始终路由到昂贵模型);--objective cost-quality --target-savings X 则保持特定的节省下限。添加 --weights 来评分——并输出——自定义特征权重,例如词汇选择加入,因此输出是一个完整、可部署的配置(请参阅 docs/lexical-routing.md):
wayfinder-router calibrate data.jsonl --mode threshold --objective knee \
--costs local=0.2,cloud=1.0 \
--weights reasoning_term_count=5,math_symbol_count=3,constraint_term_count=1.5Cost is metadata only — it shapes the calibrated cut and is reported on the
/metrics endpoint, but never enters a per-request decision, which stays
deterministic and free.成本仅为元数据——它决定了校准的阈值并在 /metrics 端点上报告,但从不进入每个请求的决策,该决策保持确定性和免费。
The deployment's config sets the default boundary, but a client can override the decision for one request over plain OpenAI transport. An override only changes where the request goes; the prompt is still scored, and nothing adds a model call.部署的配置设置了默认边界,但客户端可以通过纯 OpenAI 传输覆盖单个请求的决策。覆盖仅更改请求的去向;提示仍然被评分,并且没有任何东西会增加模型调用。
- The
modelfield is a routing directive.auto(or any normal model id) lets Wayfinder decide; a configured endpoint name (local,cloud) pins the request there;prefer-local/prefer-hostedpin to the low / high end of your router (prefer-cloudstill works as an alias ofprefer-hosted).model 字段是一个路由指令。auto(或任何普通模型 ID)让 Wayfinder 决定;已配置的端点名称(local、cloud)将请求固定在那里;prefer-local / prefer-hosted 将请求固定在路由器的低端/高端(prefer-cloud 仍然是 prefer-hosted 的别名)。 - An
X-Wayfinder-Thresholdheader re-cuts the decision for that request, a number in0.0-1.0reusing your weights (binary routers only).X-Wayfinder-Threshold 标头会为该请求重新计算决策,一个 0.0-1.0 的数字会重用您的权重(仅限二进制路由器)。 - An in-message
/directive(opt-in:[gateway] slash_directives = true) lets a plain chat box steer routing — start a message with/local,/cloud,/prefer-hosted, or/autoand it pins that turn (stripped before the model sees it). Only known directives are acted on; anything else starting with/is left as ordinary text (WF-ADR-0036).消息中的 /directive(选择加入:[gateway] slash_directives = true)允许一个普通的聊天框控制路由——用 /local、/cloud、/prefer-hosted 或 /auto 开始一条消息,它就会固定该轮(在模型看到之前被剥离)。只有已知的指令才会被执行;任何其他以 / 开头的都会被保留为普通文本(WF-ADR-0036)。 - Offline mode keeps you working with no network. Set
[gateway] offline = true(or sendX-Wayfinder-Offline: truefor one request) and Wayfinder serves the cheapest/local tier and never calls the cloud tier — so a request can't hang on a timeout on a plane. The prompt is still scored and reported; only delivery changes (WF-ADR-0039).离线模式让您在没有网络的情况下也能工作。设置 [gateway] offline = true(或为单个请求发送 X-Wayfinder-Offline: true),Wayfinder 将提供最便宜/本地的层级,并且永远不会调用云层——因此请求不会在飞机上因超时而挂起。提示仍然会被评分和报告;只有交付方式会改变(WF-ADR-0039)。
# Pin one call to cloud regardless of score:
client.chat.completions.create(model="cloud", messages=[...])
# Or move the cut for one call (keep model="auto"):
client.chat.completions.create(
model="auto", messages=[...], extra_headers={"X-Wayfinder-Threshold": "0.8"}
)Each response adds x-wayfinder-router-mode (scored / pinned /
threshold-override) next to the -model and -score headers, so you can see
which channel decided the route.每个响应都会在 -model 和 -score 标头旁边添加 x-wayfinder-router-mode(scored / pinned / threshold-override),因此您可以查看是哪个通道决定了路由。
Because the model field is a routing directive, any OpenAI-compatible chat UI can
drive routing with no code change: the app's normal model dropdown becomes a
per-conversation routing picker (auto / prefer-local / prefer-hosted / a
pinned endpoint). The gateway lists these at GET /v1/models, so a UI discovers
them on its own.由于 model 字段是一个路由指令,任何 OpenAI 兼容的聊天 UI 都可以驱动路由而无需更改代码:应用程序的普通模型下拉菜单变成了一个每个对话的路由选择器(auto / prefer-local / prefer-hosted / 一个固定的端点)。网关在 GET /v1/models 上列出这些,因此 UI 会自行发现它们。
- LibreChat — copy
examples/librechat.yamlandexamples/docker-compose.override.ymlinto your checkout, rundocker compose up, and pick the "Wayfinder" endpoint.LibreChat — 将 examples/librechat.yaml 和 examples/docker-compose.override.yml 复制到您的检出目录中,运行 docker compose up,然后选择“Wayfinder”端点。 - Open WebUI — add an OpenAI connection pointing at the gateway; it auto-discovers the routing options.Open WebUI — 添加一个指向网关的 OpenAI 连接;它会自动发现路由选项。
See examples/ for both. The one thing a stock UI can't express is a
live per-conversation threshold slider; that's what the wayfinder-chat fork adds,
and this no-fork path proves it out first.在 examples/ 中可以找到这两种方式。唯一一个标准 UI 无法表达的是每个对话的实时阈值滑块;这就是 wayfinder-chat 分叉所添加的功能,而这个无分叉路径首先证明了这一点。
Wayfinder's controls are spread across the tools you already run, so it's easy not to notice it working. Four surfaces show or steer routing:Wayfinder 的控件分布在您已使用的工具中,因此很容易忽略它的工作。四个界面显示或控制路由:
| surface | what it shows | where |
|---|---|---|
| Model dropdown | the routing picker (auto / prefer-local / prefer-hosted / a pinned endpoint) |
your client, from GET /v1/models |
| Response headers | where each request went and why (-model / -score / -mode / -request-id) |
every response |
| Debug body field | the decision inside the response body, opt-in | request header X-Wayfinder-Debug: true |
| Dashboard | recent decisions, per-model counts, scores — metadata only, never prompt text | GET /router (JSON at /router/recent) |
The dashboard is separate from the off-path wayfinder-router ui console, which is
for tuning, not production traffic.仪表板与非路径的 wayfinder-router ui 控制台分开,后者用于调优,而非生产流量。
Don't guess the cut, learn it from your own judgment of local versus hosted output. The loop is: collect judgments, calibrate, route automatically.不要猜测阈值,而是根据您自己对本地与托管输出的判断来学习它。循环是:收集判断,校准,自动路由。
Bootstrap it with A/B onboarding. For each sample prompt, wayfinder-router onboard runs both arms and asks which was good enough; the answer is a label:通过 A/B 测试进行引导。对于每个样本提示,wayfinder-router onboard 会运行两个分支并询问哪个更好;答案是一个标签:
wayfinder-router onboard prompts.jsonl --arms local,cloud --calibrate > wayfinder-router.tomlThe comparison goes to stderr; --calibrate prints the resulting config to stdout.
Each judgment appends a {"text", "label"} line to a feedback log, which is itself
the calibrate dataset, so the log turns straight into a config.比较结果会输出到 stderr;--calibrate 会将生成的配置打印到 stdout。每个判断都会将一个 {"text", "label"} 行附加到反馈日志中,该日志本身就是校准数据集,因此日志会直接转换为配置。
To skip the manual grading, let wayfinder-router judge label automatically. It runs
both tiers and asks an automated judge "was the cheaper tier good enough?" — the same
sufficiency question, no person in the loop:要跳过手动评分,请让 wayfinder-router 自动评分。它会运行两个层级并询问一个自动评分器“较便宜的层级是否足够好?”——这是同一个充分性问题,没有人参与:
wayfinder-router judge prompts.jsonl --arms local,cloud --gold gold.jsonl > wayfinder-router.tomlThe built-in judge is a deterministic text comparator that abstains rather than guess
when it can't tell. Because a bad label would silently degrade live routing, judge will
only emit a config once it passes trust gates — agreement with your human-labeled
--gold set (Cohen's κ ≥ 0.6), out-of-fold lift over the majority baseline, and both arms
represented. If the gates fail it prints the confusion matrix and refuses (the labels are
still recorded). Pass --save-comparisons out.jsonl to also keep the raw responses (off by
default — it's a body store).内置评分器是一个确定性的文本比较器,当它无法判断时会弃权而不是猜测。由于错误的标签会悄无声息地降低实时路由的质量,因此评分器只有在通过信任门槛后才会发出配置——与您手动标记的 --gold 集(Cohen's κ ≥ 0.6)一致,在样本外比多数基线有提升,并且两个分支都有代表。如果门槛失败,它会打印混淆矩阵并拒绝(标签仍然被记录)。传递 --save-comparisons out.jsonl 还可以保留原始响应(默认关闭——这是一个主体存储)。
Once you're routing automatically, keep it honest by recording which model was actually good enough:一旦您开始自动路由,请通过记录哪个模型实际上足够好来保持其准确性:
curl localhost:8088/v1/feedback -d '{"text": "...", "label": "cloud"}'Then re-fit on a schedule from cron, a k8s CronJob, or a click in the UI.
Recalibration rewrites only the [routing] section and preserves your [gateway]
endpoints, and a running gateway hot-reloads the result with no restart:然后按计划从 cron、k8s CronJob 或 UI 中的点击进行重新拟合。重新校准仅重写 [routing] 部分并保留您的 [gateway] 端点,并且正在运行的网关会热加载结果而无需重启:
wayfinder-router recalibrate # log -> calibrate -> write config
wayfinder-router recalibrate --min-labels 50 # no-op until you have enough signalThe judging runs models, so it lives in the gateway layer (with your key); the scoring core stays untouched and the log carries no secrets.评分会运行模型,因此它位于网关层(与您的密钥一起);评分核心保持不变,日志不包含任何秘密。
The CLI, onboarding, and UI are for operators and bootstrapping. In production, prompts flow through the gateway (transparent) or the library (in-process), so routing happens where prompts already are.CLI、引导和 UI 面向操作员和引导。在生产环境中,提示通过网关(透明)或库(进程内)流动,因此路由发生在提示已存在的地方。
Run the gateway as a service, sidecar or standalone:将网关作为服务、sidecar 或独立运行:
docker build -t wayfinder-router . && docker run -p 8088:8088 -v "$PWD/data:/data" wayfinder-router
# or: docker compose up gateway (see docker-compose.example.yml)Point your existing client at it with no app change. Anything that speaks the
OpenAI API takes a base_url, including agent frameworks (LangChain, LlamaIndex),
IDE assistants with a custom endpoint (Cursor, Continue), and gateways like LiteLLM:将您现有的客户端指向它,无需更改应用程序。任何支持 OpenAI API 的工具都可以接受 base_url,包括代理框架(LangChain、LlamaIndex)、带有自定义端点的 IDE 助手(Cursor、Continue)以及 LiteLLM 等网关:
client = openai.OpenAI(base_url="http://localhost:8088/v1", api_key="unused")See Integration recipes for copy-paste setup across chat UIs
(Open WebUI, LibreChat, Jan), editors (Continue, Cline, Zed, JetBrains), agent frameworks
(LangChain, LlamaIndex, CrewAI, AutoGen, the OpenAI Agents SDK, the Vercel AI SDK), and
CLIs (aider, Copilot CLI) — plus the canonical OPENAI_BASE_URL / OPENAI_API_KEY pair.请参阅集成配方,获取跨聊天 UI(Open WebUI、LibreChat、Jan)、编辑器(Continue、Cline、Zed、JetBrains)、代理框架(LangChain、LlamaIndex、CrewAI、AutoGen、OpenAI Agents SDK、Vercel AI SDK)和 CLI(aider、Copilot CLI)的复制粘贴设置——以及标准的 OPENAI_BASE_URL / OPENAI_API_KEY 对。
Claude Code speaks Anthropic's Messages API rather than OpenAI's, so the gateway exposes a
POST /v1/messages adapter (WF-DESIGN-0011) that translates Anthropic ⇄ OpenAI in both
directions — streaming and tool use included. Point it at the gateway root and Claude Code
routes through Wayfinder like any other client:Claude Code 使用 Anthropic 的 Messages API 而不是 OpenAI 的 API,因此网关公开了一个 POST /v1/messages 适配器(WF-DESIGN-0011),该适配器在两个方向上翻译 Anthropic ⇄ OpenAI——包括流式传输和工具使用。将其指向网关根目录,Claude Code 就像任何其他客户端一样通过 Wayfinder 进行路由:
export ANTHROPIC_BASE_URL="http://localhost:8088" # client appends /v1/messages
export ANTHROPIC_API_KEY="unused" # the gateway uses each upstream's own key
claudeWire feedback from wherever your users are. Your app, IDE, or chat shows a thumbs-up or thumbs-down and posts the judgment; the next recalibration learns from it:从您的用户所在的地方连接反馈。您的应用程序、IDE 或聊天会显示一个赞或踩,并发布判断;下一次重新校准将从中学习:
fetch("http://localhost:8088/v1/feedback", {
method: "POST",
body: JSON.stringify({ text: prompt, label: wasGoodEnough ? "local" : "cloud" }),
});The gateway forwards asynchronously and streams: a request with stream: true
comes back as Server-Sent-Events, so chat clients render tokens as they arrive. An
upstream timeout or connection failure returns an OpenAI-shaped error instead of a
bare 500, every response carries a request id for tracing, and routing decisions
and reload failures are logged.网关异步转发并流式传输:带有 stream: true 的请求会以 Server-Sent-Events 的形式返回,因此聊天客户端会在令牌到达时渲染它们。上游超时或连接失败会返回一个类似 OpenAI 的错误而不是纯粹的 500,每个响应都带有一个用于跟踪的请求 ID,并且路由决策和重新加载失败都会被记录。
Beyond that it has the production knobs you'd expect — per-request timeouts, bounded retries with a per-target circuit breaker and failover, a spend budget cap, an exact-match response cache, rate limiting, and virtual API keys with per-key budgets and allowlists. They're all off or generous by default; see Gateway configuration reference for every setting and the headers each one surfaces.除此之外,它还具有您期望的生产级功能——每个请求的超时、带有目标断路器和故障转移的有界重试、支出预算上限、精确匹配响应缓存、速率限制以及具有每个密钥预算和允许列表的虚拟 API 密钥。它们默认都关闭或非常宽松;请参阅网关配置参考以了解所有设置以及每个设置暴露的标头。
To see why a prompt routed where it did, ask for the per-feature breakdown: each feature's value, its normalized level, its weight, and its share of the score.要了解提示为何路由到此处,请请求每个特征的细分:每个特征的值、其标准化级别、其权重以及其在分数中的份额。
wayfinder-router route prompt.md --explainFor interactive tuning there's a local web UI:对于交互式调优,有一个本地 Web UI:
- Explain — paste a prompt; see the score, the tier ladder, and contribution bars, and drag a threshold slider to watch routing change live.解释——粘贴一个提示;查看分数、层级阶梯和贡献条形图,并拖动阈值滑块以实时观察路由变化。
- Calibrate — paste a labeled dataset, run a mode, and see accuracy, the sweep curve, and the resulting config fragment.校准——粘贴一个带标签的数据集,运行一个模式,然后查看准确率、扫描曲线和生成的配置片段。
- Configure — edit
wayfinder-router.tomlwith live validation and save.配置——使用实时验证编辑 wayfinder-router.toml 并保存。 - Onboard — A/B a local and a hosted model in the browser, judge each, and
calibrate from the log (needs
[gateway]for the model calls).引导——在浏览器中对本地和托管模型进行 A/B 测试,对每个模型进行评分,并从日志中校准(需要 [gateway] 进行模型调用)。
pip install "wayfinder-router[ui]"
wayfinder-router ui --port 8099 # then open http://localhost:8099The UI is a thin wrapper over the same pure functions; it never calls a model, and no secret appears in it.UI 是相同纯函数的薄包装器;它从不调用模型,并且没有任何秘密出现在其中。
from wayfinder_router import score_complexity, RoutingConfig, explain_score
result = score_complexity(prompt_text, config=RoutingConfig.binary(threshold=0.7))
print(result.recommendation, result.score, result.features)
for fc in explain_score(result.features, RoutingConfig().weights):
print(fc.name, fc.contribution)Wayfinder started as a route experiment inside a larger requirements tool and was
split out because routing is a runtime concern, not a knowledge one: a prompt router
shouldn't make you install an engine you don't need. The result is a small, focused
tool whose scoring core stays dependency-free — you can import wayfinder_router and
score prompts with nothing but the standard library (WF-ADR-0001, WF-ADR-0029).Wayfinder 最初是一个大型需求工具中的路由实验,后来被分离出来,因为路由是一个运行时问题,而不是知识问题:提示路由器不应该强制您安装不需要的引擎。结果是一个小巧、专注的工具,其评分核心保持无依赖性——您可以导入 wayfinder_router 并仅使用标准库对提示进行评分(WF-ADR-0001、WF-ADR-0029)。
wayfinder-router/
wayfinder_router/ the package: scorer, tiers + classifier, config loader/writer,
offline calibration (Newton/IRLS), explain, the feedback log and
onboarding harness, recalibration, CLI, and the optional gateway
and local UI (the impure layers, behind their extras)
tests/ scorer, config, calibration, explain, feedback, onboard,
recalibrate, CLI, gateway, and UI coverage
decisions/ design notes behind the tool's own choices
docs/ the FAQ and the lexical-routing guide
Dockerfile, docker-compose.example.yml deploy the gateway as a service
pip install -e .[dev] # or: pip install pytest
make test