Docs · Features
Quality judges
A quality judge scores every translated segment for a workflow step's QE gate. Use hosted Jev with a key, or run a Jev-compatible server such as SemIf on your own GPU, on the same machine or over Tailscale.
What a quality judge is#
A translation engine writes translations. A quality judge never does: it reads a source segment and its translation, answers a fixed set of questions about the pair, and Localization OS turns the answers into a score from 0 to 100 and a verdict for a workflow step's QE gate. Because a judge never translates, it appears only in a step's QE judge engine picker. Every translation picker (project default, language pair, step provider and fallback, install default) hides it, and saving one there is refused.
Localization OS speaks the Jev System One protocol. Jev, by TypeSafe, is a typed judge: for each segment it answers five questions, accuracy, fluency, terminology and style, each as a level from none through minor and major to critical, plus a yes-or-no "did the meaning change", with a probability for every answer. A Jev-compatible server is any program that answers the same requests, so you can run the judge on your own hardware instead of calling the hosted service.
| Compared | Hosted Jev | A Jev-compatible server you run |
|---|---|---|
| Engine kind in Setup AI | Jev (TypeSafe, hosted) | Jev-compatible local server (SemIf, Kev) |
| Where your text goes | TypeSafe's API | Only the machine you run it on |
| Cost | Priced per call, tracked in spend and counted against the spend cap | Free, never counted against the spend cap |
| Setup | Paste a TypeSafe API key | A GPU machine running a server such as SemIf |
Hosted Jev#
- Open Setup AI and scroll to Engines. Judges are listed in their own group, Quality judges.
- Open + Add engine, give it a name, choose the kind Jev (TypeSafe, hosted), paste your TypeSafe API key and press Add engine. The address is fixed to
https://api.typesafe.aiand cannot be changed, so the key is only ever sent to TypeSafe. - On the new card, open Edit and press Test. It lists the models the service offers, says whether the configured model is among them, and asks one small question, reporting the latency of each call.
If the install already has a TYPESAFE_API_KEY environment variable and no hosted Jev engine exists yet, an administrator sees a Create a Jev engine from TYPESAFE_API_KEY button in the Quality judges group. Pressing it moves the key into an encrypted engine that never shows the key again, and pressing it a second time creates nothing new.
Every hosted call is priced. The spend cap is checked before each hosted request, and the reported usage is recorded after the response.
Run SemIf on a GPU machine#
SemIf reads typed answers directly out of an open-weight language model. semif-serve is a small HTTP server that puts SemIf behind the Jev protocol, so Localization OS talks to it exactly as it talks to hosted Jev, only at a different address and for free. Both are third-party projects (see Credits and licences): Localization OS does not ship, bundle or install them or any model code, and semif-serve's own README is the authority on running it. The steps below are the path we have used.
What you need
- An NVIDIA GPU with about 10 GB of free video memory.
gitand uv.- Disk space and bandwidth for the model: the first start downloads the model semif-serve is pinned to, several gigabytes.
Install
git -c url."https://github.com/".insteadOf="git@github.com:" clone --recurse-submodules https://github.com/dddanielliu/semif-serve.git
cd semif-serve
uv sync
uv run --no-sync python -c "import torch; print(torch.__version__, torch.cuda.is_available())"
semif-serve pulls SemIf in as a git submodule addressed over SSH; the -c url...insteadOf part fetches it over HTTPS instead, so no GitHub SSH key is needed.
The last command must print True. On Windows, uv sync installs the CPU-only build of torch, so swap in the CUDA build at the version semif-serve pins, then run the check again. It should print a version ending in +cu128 and True:
uv pip install torch==2.10.0 --index-url https://download.pytorch.org/whl/cu128
2.10.0 was the pinned version when this page was written; match whatever semif-serve's lock file pins. If the check still reports a CPU build, repeat the install with --reinstall.
SemIf can optionally use faster attention kernels (uv pip install flash-linear-attention causal-conv1d). Without them it logs a warning and uses a slower reference path. On Windows these packages often cannot be installed, and the slower path is normal there.
Start the server
# Linux or macOS shell
CUDA_VISIBLE_DEVICES=0 PYTORCH_ALLOC_CONF=expandable_segments:True SEMIF_PORT=8077 uv run --no-sync semif-serve
# PowerShell on Windows
$env:CUDA_VISIBLE_DEVICES = "0"
$env:PYTORCH_ALLOC_CONF = "expandable_segments:True"
$env:SEMIF_PORT = "8077"
uv run --no-sync semif-serve
PYTORCH_ALLOC_CONF=expandable_segments:True lets PyTorch use video memory more flexibly. semif-serve's README requires it on a 12 GB card, where the model can otherwise run out of memory partway through a batch, and it does no harm on larger cards.
Always start it with uv run --no-sync. A plain uv run re-syncs the environment, which can put the CPU build of torch back, and the server then runs very slowly on the CPU.
The settings it reads:
| Variable | Meaning |
|---|---|
SEMIF_HOST | The address it listens on. Leave it unset to listen on this machine only (loopback). |
SEMIF_PORT | The port. Its default, and the one this page uses, is 8077. |
SEMIF_API_KEY | A shared secret the server requires on every request. semif-serve refuses to listen on anything but loopback without one. Use a long random value, for example the output of openssl rand -hex 32, or [guid]::NewGuid().ToString("N") in PowerShell. |
CUDA_VISIBLE_DEVICES | Which GPU SemIf uses. It needs exactly one visible GPU, so on a machine with two, name the one with room for the model (nvidia-smi lists them). |
Check that it answers, from a second terminal on the same machine:
curl http://127.0.0.1:8077/v1/models
Connect it on the same machine#
When SemIf runs on the same machine as Localization OS:
- Open Setup AI, then + Add engine under Engines.
- Name it, for example "SemIf (this machine)", and choose the kind Jev-compatible local server (SemIf, Kev).
- Set the Base URL to
http://127.0.0.1:8077. Leave the API key blank unless you setSEMIF_API_KEY, and leave Model blank, which meansjev-latest. The confidence floor, meaning-changed floor and timeout are optional: blank floors use the install defaults, and a blank timeout uses the fixed default (30 seconds for a local server, 10 seconds for hosted Jev). - Press Add engine, then on its card open Edit and press Test. You want "Jev reachable", a model list, and "one-question check OK".
- Press Inspect on the card and use Try it with a translation you know is wrong, to watch the judge catch it (see Inspect a judge and try it).
- Pick the engine as a workflow step's QE judge engine (see Use a judge in a workflow).
From the command line:
loc-tms engine create --name "SemIf (this machine)" --kind jev_local --base-url http://127.0.0.1:8077
Connect it over Tailscale#
Use this when the GPU is in a different machine from Localization OS, for example a GPU workstation serving an install that runs elsewhere. Tailscale gives both machines private addresses on your own network, called a tailnet, and encrypts the traffic between them.
On the GPU machine
- Install Tailscale and sign in, so the machine joins your tailnet.
tailscale ip -4prints its tailnet address, a100.x.y.zaddress, and the Tailscale admin console shows its MagicDNS name. - Start semif-serve listening on that tailnet address only, with a key. Use the exact address
tailscale ip -4printed. Do not bind it to every interface, which would also expose it on your office or home network.
# Linux or macOS shell
CUDA_VISIBLE_DEVICES=0 PYTORCH_ALLOC_CONF=expandable_segments:True \
SEMIF_HOST=100.x.y.z SEMIF_PORT=8077 SEMIF_API_KEY=a-long-random-value uv run --no-sync semif-serve
# PowerShell on Windows
$env:CUDA_VISIBLE_DEVICES = "0"
$env:PYTORCH_ALLOC_CONF = "expandable_segments:True"
$env:SEMIF_HOST = "100.x.y.z"
$env:SEMIF_PORT = "8077"
$env:SEMIF_API_KEY = "a-long-random-value"
uv run --no-sync semif-serve
The key is a password, not encryption. semif-serve speaks plain HTTP, so your text and the key are protected in transit only because Tailscale encrypts everything on the tailnet. Keep the server on its tailnet address and never forward its port to the internet. A firewall rule on the GPU machine that allows the port only from the Tailscale interface is a sensible extra layer. Start semif-serve after Tailscale is connected: binding to the tailnet address fails while that address does not exist yet.
In Localization OS
- In Setup AI, open + Add engine and choose the kind Jev-compatible local server (SemIf, Kev).
- Set the Base URL to the machine's MagicDNS name or its tailnet IPv4 address, with the port, for example
http://100.x.y.z:8077. - Paste the same value you gave
SEMIF_API_KEYas the engine's API key. Whenever the server was started with a key, the matching key is required: without it every request fails authentication. semif-serve refuses to listen anywhere but loopback without a key, so only a server started without one, on the same machine as Localization OS, can be used keyless. - Press Test, then pick the engine as a step's QE judge engine.
The machine that runs Localization OS must itself be able to reach the GPU machine's tailnet address, which normally means Tailscale is installed and signed in there too. If Localization OS cannot reach the address at all, no URL will make it work.
Use a judge in a workflow#
- Open the workflow and edit the Translate step you want gated.
- Under Options, tick QE routing gate, set the QE threshold (0 to 100, default 70), and pick the judge as the QE judge engine. Save.
- Optionally tick Auto-fix failures, which appears only when the QE judge is a Jev judge, so failing segments get a bounded rewrite before they reach a person.
A segment that scores at or above the threshold with every dimension answered confidently passes. One below the threshold fails and goes to review, or first to the auto-fix post-editor when auto-fix is on and no dimension is unsure. One that scores high enough but where the judge was unsure about a dimension is undecided: it carries on unchanged, is counted as QE skipped, and shows a QE undecided badge in the editor rather than going to review. Add a Review step if those segments must be seen by a person. The Workflows page covers the gate and auto-fix in full.
Inspect a judge and try it#
Every judge card in Setup AI has an Inspect button, available to administrators. The page has four parts:
- What Jev is asked: the five questions, what every level means, and the exact request body. The API key travels only in an authorization header and is never shown.
- How it decides: the confidence floor, the meaning check, how an unsure answer is handled, the score formula and the pass threshold. It is rendered from the same numbers the QE gate uses and from this engine's own settings, so it always matches what the gate does.
- Try it: enter a source, a translation, both locales and a threshold, and one segment is scored exactly as a QE-gated step would score it. You see the request sent, the raw answer, each dimension's level, the verdict (pass, fail, undecided or not scored), the cost and latency, and the exact prompt the auto-fix post-editor would receive for its first round.
- Ledger: a link to the LLM Ledger filtered to this engine. Tries are recorded there while the Ledger is on.
A try on hosted Jev is billed like any other call and stops with a clear message once the spend cap is reached; a try on a server you run is free. Try it is rate limited, and hosted requests are spend-checked.
Evaluate any judge, hosted or one you run, on representative segments and language pairs of your own before you rely on it. Inspect, then Try it, is the way to do that: feed it real segments from your projects, including ones you know are wrong.
Command line, API and MCP#
Everything above is also available headlessly. Engine ids below are examples; loc-tms engine list shows yours.
loc-tms engine create --name "Jev" --kind jev --secret api_key=YOUR_TYPESAFE_KEY
loc-tms engine create --name "SemIf" --kind jev_local --base-url http://127.0.0.1:8077 \
--config confidence_floor=0.6 --config meaning_floor=0.8 --config timeout_s=30
loc-tms engine import-jev-env
loc-tms engine test 7
loc-tms engine inspect 7
loc-tms engine jev-try 7 --source "Pay ten euros." --target "Zahlen Sie neun Euro." \
--source-locale en --target-locale de
loc-tms workflow edit-step 12 3 --qe-gate --qe-provider engine:7 --qe-autofix
The --config values are optional: unset floors use the install defaults, and an unset timeout uses the fixed default (30 seconds local, 10 seconds hosted). loc-tms lqa jev-refine --judge engine:7 runs the same score-and-rewrite loop as an experiment over pairs you supply; it writes no production content.
Over the API, a judge is created like any engine with POST /api/admin/engines and changed with POST /api/admin/engines/{id}/update. Its key travels only over those admin API routes, never over MCP. GET /api/admin/engines/{id}/inspect and POST /api/admin/engines/{id}/jev-try are the Inspect page's two halves, and POST /api/admin/engines/import-jev-env is the one-click import. The MCP server's install-management tool offers inspect_engine and jev_try, and never carries a key.
Troubleshooting#
| What you see | What it means and what to do |
|---|---|
| Test says authentication failed (HTTP 401) | The key is wrong or missing. For SemIf, the engine's API key must equal the server's SEMIF_API_KEY; for hosted Jev, paste the TypeSafe key again. |
| Test or Try it times out | A local GPU can be slow on its first requests, or while another model is busy on the same card. For a Jev-compatible local server, edit the engine in Setup AI and raise Timeout, seconds, for example to 60. For hosted Jev, set the timeout from the command line with loc-tms engine update 7 --config timeout_s=60, or over the API with POST /api/admin/engines/{id}/update and a body of {"config": {"timeout_s": 60}}. |
| Test says the configured model is not in the list | Set the engine's Model to one of the names Test lists, or leave it blank for jev-latest. |
| Test or Try it says the spend cap is reached | Hosted Jev is priced and the monthly cap is used up, so nothing was sent. Raise or clear the cap in Setup AI, or use a judge you run, which is free and never capped. |
| The server answers on the GPU machine but not from Localization OS | It is listening on loopback only. Set SEMIF_HOST and SEMIF_API_KEY as in Connect it over Tailscale, and check that both machines are on the tailnet. |
| The server is very slow and the GPU is idle | It was started with a plain uv run, which put the CPU build of torch back. Reinstall the CUDA build and start it with uv run --no-sync. |
| Your local LLM server will not load the post-editor model | The judge and the LLM share one GPU and the LLM is being loaded with a context window too large for the remaining video memory. Load it with a smaller context length, for example 8192. |
Credits and licences#
Localization OS talks to these judges over the network. It does not ship, bundle, install or redistribute any of them, or any model weights. They are third-party projects with their own terms, outside the Localization OS licence and support, so evaluate each one yourself before you use it in production.
- Jev and the System One protocol are TypeSafe's. Hosted Jev is used with your own TypeSafe account and key, under TypeSafe's terms.
- SemIf (github.com/TheoLeeCJ/SemIf) is the library that reads typed answers from an open-weight model. It is published under the MIT licence.
- semif-serve (github.com/dddanielliu/semif-serve) is the HTTP server that puts SemIf behind the Jev protocol. At the time of writing its repository has no licence file, so its terms of use are unstated; decide for yourself whether that is acceptable for your use.
- Kev (github.com/jaredpalmer/kev) is a family of Jev-style models that speaks the same protocol, published under the Apache 2.0 licence. Add it exactly like SemIf, as a Jev-compatible local server at its own address and port. It needs more video memory than SemIf; check its README before sharing a card with another model.
SemIf, semif-serve and Kev are third-party projects; SemIf's own repository states that it is independent and not affiliated with TypeSafe. SemIf and Kev carry the licences named above; semif-serve has no licence file. Thanks to the authors of all three for making a local judge possible.
Current limits#
A judge can only be a step's QE judge. Saving one as a project default, a language pair's provider, a step provider or fallback, or the install default is refused, and a hand-written reference to one in a translation slot fails loudly at run time.
A hosted Jev engine's address cannot be changed. The reverse also holds: a Jev-compatible local server cannot point at TypeSafe's own API; add it as the hosted kind instead, so its calls are priced and counted against the spend cap.
A judge answers with a level per dimension, not with marked error spans, so a reviewer sees which dimension cost points rather than where in the sentence the problem is.
When a judge cannot score a segment, because it is unreachable, the spend cap is reached, its answer is unreadable, or it was unsure and found no error it was sure of (low confidence), the segment carries on without a score and the run counts it as QE skipped. It is not held.
In the Evaluations Lab, the Jev judge scorer uses the install's TYPESAFE_API_KEY setup; a judge engine cannot be picked there.