Configuration Parsing Warning:In adapter_config.json: "peft.task_type" must be a string

jeff-adapter-sanctions

Sanctions and watch-list screening. Compares a customer or payment party with one sanctions or watch-list entry - match, possible match (check it) or no match.

A LoRA adapter for jeff-base v1.3, a small open decision model (a fine-tune of Qwen3.5-0.8B). You send a situation (the state) and questions with named options; Jeff returns a calibrated probability for every option from one forward pass, with no generated text to parse. One Jeff server loads the base once and any number of adapters beside it; each request picks an adapter by name ("model": "sanctions").

Adapter page, with the full data card: jeffhub.ai/adapters/sanctions.

Results

On this adapter's held-out test set, never trained on, scored three ways on the same rows: the untrained model Jeff is built from, the Jeff v1.3 base alone, and the base with this adapter. Every question has 3 options. As of 2026-10-05. All adapters

Test set Test rows Qwen3.5-0.8B untrained Jeff base v1.3 alone Jeff base v1.3 + adapter
test 4,909 34.4% · 0.010 68.3% · 0.080 100.0% · 0.001

Each cell: accuracy · calibration error (ECE; lower is better, 0 is perfect).

With llama.cpp (GGUF)

The same test, through llama.cpp: the base GGUF (mstrasser/jeff-base-gguf) plus this adapter's LoRA GGUF (mstrasser/jeff-adapter-sanctions-gguf), with the temperature refitted for each format. Running Jeff with llama.cpp

Test set Full precision Q8_0 Q4_K_M
test 100.0% · 0.001 100.0% · 0.001 99.9% · 0.001

Not measured yet for v1.3: calibration charts, the commonest confusions and accuracy per answer.

Source of these numbers: results/sources/v1.3/new-adapters.table.json in the JeffHub repository, also collected in jeffhub-v1.3.json.

When to use it

  • You screen new customers or payment parties against watch-list entries and want a fast first decision for each pair.
  • You want "possible match" kept apart from "match", so a person checks only the pairs where the deciding detail is missing.
  • Your rule is the one the adapter was trained on - date of birth decides for people, country of registration for companies, IMO number for ships.

When not to use it

  • You need to search a whole list. The adapter compares one customer with one entry; find candidate entries first (for example by name search), then ask it about each pair.
  • Your matching rule differs (for example nationality or address must decide). It was trained on one rule, stated in the instructions.
  • You need a final decision without a person. Screening decisions have legal consequences; use the adapter to sort, not to clear on its own.
  • You plan commercial use. The training data is licensed for non-commercial use only (see Data and licence).

How to use it

The adapter runs with Jeff's server on the jeff-base v1.3 base. Adapter serving arrives with the next Jeff release; until then, these commands need the feat/lora branch of firelex/jeff.

git clone https://github.com/firelex/jeff && cd jeff
uv sync --no-default-groups --extra lora          # add --extra cuda on NVIDIA GPUs, --extra mac on Apple silicon
uv run --no-default-groups hf download mstrasser/jeff-base --revision v1.3 --local-dir checkpoints/jeff-base
uv run --no-default-groups hf download mstrasser/jeff-adapter-sanctions --revision v1.3 --local-dir adapters/sanctions
JEFF_CHECKPOINT=checkpoints/jeff-base JEFF_ADAPTERS=adapters/ PORT=8765 \
  uv run --no-default-groups jeff-serve          # on a Mac, add JEFF_BACKEND=mlx

Every folder in adapters/ is served under its folder name; add or replace adapters while the server runs with curl -X POST http://localhost:8765/v1/adapters/reload. Each adapter records the exact base it was trained on, and the server refuses an adapter trained on a different one, so this adapter loads only on jeff-base v1.3 (a v1.2 adapter does not load on v1.3). For llama.cpp, use mstrasser/jeff-adapter-sanctions-gguf.

Request format

State (the situation), in this order:

Key Changes per request What it holds
watch_list_entry no One entry from the list: name, aliases, type (person, company or organisation, ship), dates of birth, countries, IMO number and the lists it is on.
customer_record yes The customer or payment party as your records show it, as fields or as a short note.

Questions:

  • screening (choice): Whether the customer is the listed party. Options: match, possible_match, no_match, in that order

Rules:

  • Use the instructions below word for word; every training row used them.
  • Keep the entry the same across the customers you compare with it, and put the customer record last, so the entry can be prepared in advance.

General rules for every request: the request format guide.

Example

The request below is also in this repository as example.json.

{
  "model": "sanctions",
  "state": {
    "watch_list_entry": "Name: HH WOODCHIP\nAliases: PRIMROSE 6969\nType: ship\nCountries: Belize, Panama, Tanzania\nIMO number: 9118410\nLists: Tokyo MoU Detention List",
    "customer_record": "The ship Hh Woodchip, a dredger with IMO 9381469, is flagged in Belize and appears under reference 87965. The payment is in yuan with a value date of February 10, 2010."
  },
  "questions": {
    "screening": {
      "type": "choice",
      "instructions": "A bank checks each customer, or each party named in a payment, against one entry from a sanctions list or another watch list. Is the customer the party on the list? Names agree when the customer's name is the listed name or one of its aliases, allowing for other spellings or transliterations, a different order of the names, initials, a missing middle name and small typing mistakes. The deciding detail is the date of birth for a person, the country of registration for a company or organisation, and the IMO number for a ship. A year of birth alone cannot confirm a person, but a different year rules them out. A different kind of party, such as a ship instead of a person, is never the listed party. Nationality, occupation and the other details do not decide.",
      "criteria": {
        "match": "Match: the customer is the listed party. The names agree and the deciding detail agrees with the entry.",
        "possible_match": "Possible match: the names agree and nothing contradicts the entry, but the deciding detail is missing from the customer record or from the entry, so a person must check it.",
        "no_match": "No match: the customer is a different party. The names clearly differ, the customer is a different kind of party, or the deciding detail contradicts the entry."
      }
    }
  }
}
curl -s localhost:8765/v1/systemone -H 'content-type: application/json' -d @adapters/sanctions/example.json

The answer holds a probability for each option of each question. A recorded response from the v1.3 adapter is not published yet.

Files

  • adapter_model.safetensors, adapter_config.json: the LoRA weights (PEFT format);
  • readout.safetensors: the adapter's own readout over the answer codes;
  • decision_config.json: answer codes, temperature, prompt layout and the checksum of the base it was trained on;
  • example.json: the example request above.

adapter_config.json and decision_config.json name the base as mstrasser/jeff-base, revision v1.3; the server checks the base by the checksum of its weights.

Training

Base mstrasser/jeff-base, revision v1.3 (a fine-tune of Qwen3.5-0.8B)
Prompt layout live-last: the fixed part of the request first, the changing state field last
Run 0.8b-sanctions-20261003-1217, final checkpoint
Adapter files 41.5 MB (adapter_model.safetensors and readout.safetensors)
LoRA GGUF for llama.cpp mstrasser/jeff-adapter-sanctions-gguf
  • 1.3.0 (2026-10-03): First release, trained on Jeff v1.3 with the live-last prompt layout (LoRA rank 16, one epoch, about 10% of the base model's own training data mixed in).

Data card

Self-reported. The numbers come from the adapter’s own maintainers and have not been re-run by anyone else. What the levels mean

  • Test set: not attached yet
  • QA report: not available here yet; it will be added once sanitised

How the test set was held out. Split by listed entity. About 9.5% of the listed persons, companies and ships (825 entries) are only in the test set; no entry, and no real entity used as a different-party customer, appears in both training and test.

Training data. Training data not published.

Which models made the data, counted on the 44,663 training rows:

What it did Model Where it ran Training rows
Wrote the customer's name variant (spelling, transliteration, order, initials, typo, format), checked by code name_source GLM 5.3 own hardware (DGX B200) 21,940
Rewrote the customer record as an onboarding note with the same facts, checked by code glm_note.model GLM 5.3 own hardware (DGX B200) 14,508

Counted from each row's own record of the models that made it (the field named under each job). A row counts once under every job that names a model, so the counts do not add up to the total. Every label is computed by code from the entry and the customer's facts (names, date of birth, country of registration, IMO number). GLM never sees or sets a label.

Training mixed in a replay sample of the Jeff base model's own training data: 4,466 rows, about 10% on top of the adapter's 44,663 (inherited from the v1.2 recipe as a precaution; its effect has not been measured).

The test may be too easy: the adapter scores 100.0% on it, and the blind check also agreed on 200 of 200 rows. We are checking whether the answer can be read off a few fields. Treat the number as an upper bound until that check is done.

Real homonyms (the same name, a different person) are rare in the source data (76 rows in all); most different-party rows differ in name or detail more clearly.

Training mixes in about 10% of the base model's own training data.

Data and licence

Adapter licence: CC BY-NC 4.0 (Creative Commons Attribution-NonCommercial 4.0: free to use and share with credit, but not for commercial purposes). The JeffHub page still lists the adapter's licence as to be decided; this release uses CC BY-NC 4.0.

Qwen3.5-0.8B notice: these weights were modified from Qwen3.5-0.8B by the Jeff project: jeff-base is a fine-tune of Qwen3.5-0.8B, and this adapter was trained on top of it. Qwen3.5-0.8B is Copyright 2026 Alibaba Cloud and licensed under the Apache License, Version 2.0; a copy of that licence is in LICENSE.

To confirm: the adapter's licence, given that OpenSanctions data is CC BY-NC 4.0 and commercial use needs a licence from OpenSanctions.

It was trained on:

  • OpenSanctions default collection (export 20261002125305-bic). Licence: Creative Commons Attribution-NonCommercial 4.0 (CC BY-NC 4.0); commercial use needs a separate licence from OpenSanctions (non-commercial use only) · Not made by a model

    Aggregates many official lists (OFAC, EU, UN, UK, INTERPOL, national exclusion and debarment lists and others), each with its own terms. Citation - OpenSanctions (2026), OpenSanctions Default dataset, version 20261002125305-bic.

  • Customer records, name variants and onboarding notes. Licence: Built for this adapter; terms follow the adapter's licence (made for this adapter) · Made by GLM 5.3 (own hardware), for name variants and notes

    Customer records built by code from the entries and from other real entities in the collection. Name variants and notes were written by GLM 5.3 and checked by code.

Limitations

  • Tied to jeff-base v1.3. It will not load on any other base or version; the server checks the base weights' checksum.
  • Jeff chooses between the options you give it. It does not write text or reason in several steps.
  • Calibration was fitted on this adapter's own calibration rows. On very different data, check it again.
  • Everything listed under When not to use it above.

Links

Jeff is an independent project. It uses the same request format as Jev but is not affiliated with or endorsed by TypeSafe, the makers of Jev.

Downloads last month
9
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for mstrasser/jeff-adapter-sanctions

Adapter
(30)
this model