Skip to main content

Kintsugi

Red-teaming for machine translation. Kintsugi finds where a translation model breaks: inputs that make it hallucinate drug names, erase a negation, leak its prompt, refuse, soften violent text, or silently switch language. It is named for the Japanese art of repairing breakage with gold, so the cracks can be seen and mended.

It packages a battery of translation attacks, a failure detector, and judges:

  • 201 attack categories / 2822 seeds across 9 target languages, each tagged with the catastrophic/bad rates actually observed in the runs.
  • A 123-check local detector that catches obvious failures (empty, copy through, refusal, prompt leak, repetition, script mismatch, number drop, negation erasure, plus per-language traps) with no model call.
  • 11 adversarial source transforms (homoglyphs, zero-width and RTL controls, fullwidth, injection, negation dropping, repetition, mixed scripts).
  • 115 translator framings (personas, task reframings, multi-hop pivots, context dilution) that change failure rates.
  • A two-layer judge (local prefilter then an LLM judge) that cut judge error from 53% to 0.2% in the project.
  • Frequent-vs-rare word attacks for 20 bundled languages: the same probe on common vs rare words, to measure the resource-tier effect directly.

It is provider-agnostic. Point it at a local Ollama model, any Hugging Face model, your own model via a plain function, or a frontier API. Every finding records the model's provenance (provider, model, version).

All bundled data is shipped as Python literals, not data files: importing kintsugi opens no files and parses no JSON, CSV or pickles.

Install

pip install kintsugi                 # core: zero dependencies
pip install "kintsugi[huggingface]"  # to target Hugging Face models
pip install "kintsugi[frequency]"    # frequency probes beyond the 20 bundled languages

Python 3.9+.

Quick start

Try your own model in the simplest possible way: give a function.

from kintsugi import RedTeam, CallableProvider, load_attacks

target = CallableProvider(lambda text, src, tgt: my_model.translate(text, tgt))
rt = RedTeam(target, lang_pair="en-de")                 # detector-only, no judge
findings = rt.run(load_attacks(category="prompt_injection"))
print(rt.summary(findings))

A local open-source model via Ollama, judged by a frontier model:

from kintsugi import RedTeam, OllamaProvider, AnthropicProvider, load_attacks

rt = RedTeam(
    target=OllamaProvider("qwen3.5:0.8b"),
    judge=AnthropicProvider("claude-opus-5-5"),   # set ANTHROPIC_API_KEY
    target_lang="Japanese", lang_pair="en-ja",
)
findings = rt.run(load_attacks(category="japanese_pharma_traps"), progress=True)
print(rt.summary(findings))

Any Hugging Face model, the simplest way to try an open-weights model:

from kintsugi import RedTeam, HuggingFaceProvider

rt = RedTeam(HuggingFaceProvider("Qwen/Qwen2.5-0.5B-Instruct"), lang_pair="en-de")
print(rt.summary(rt.run(load_attacks(limit_per_category=2))))

Frequent vs non-frequent words

Catastrophic failure scales inversely with how well-represented something is in training data, and the same attack is more effective on rarer material. Kintsugi makes that a controlled probe, with frequency bands bundled for 20 languages. Japanese is the reference language (the probes carry a gold expected answer from a rank-ordered frequency dictionary); Kannada and Cantonese come from the FLORES+ low-resource benchmark.

from kintsugi import RedTeam, OllamaProvider, frequency

rt = RedTeam(OllamaProvider("qwen3.5:0.8b"), lang_pair="en-ja")
bands = frequency.contrast("ja", n=50)                  # frequent vs rare, held constant
common = rt.summary(rt.run(bands["frequent"]))
rare   = rt.summary(rt.run(bands["rare"]))               # expect a higher catastrophic rate

Bundled languages: ar de en es fr hi id it ja kn ko nl pl pt ru sv tr vi yue zh zh-Hant.

Locale variants: region, script, register

Models often collapse a variant to its base language, and that collapse is a failure worth catching. kintsugi.locales registers region, script, and register (diglossia: standard vs colloquial/creole) variants as red-team targets, each resolving to a frequency band:

from kintsugi import locales, RedTeam, OllamaProvider

locales.diglossia_pairs()            # e.g. ('en-SG','en-SG-x-singlish'), ('de-CH','gsw'), ('ar','ar-EG')

std      = locales.get("en-SG")              # Singapore Standard English
singlish = locales.get("en-SG-x-singlish")   # Singlish (creole)
rt = RedTeam(OllamaProvider("qwen3.5:0.8b"),
             source_lang="English", target_lang=singlish["name"], lang_pair="en-en")

Covered: region (pt-BR/pt-PT, fr-CA/fr-CH, de-AT/de-CH, zh-CN/ zh-TW/zh-HK/zh-MO/zh-SG, ...), script (zh-Hans/zh-Hant, sr-Cyrl/ sr-Latn, ms-Latn/ms-Arab, hi-Deva/hi-Latn, ...), and register (Singapore Standard English vs Singlish, Indian English vs Hinglish, Jamaican Standard English vs Patois, Swiss Standard German vs Schweizerdeutsch, MSA vs Egyptian/Moroccan Arabic, HK written Chinese vs spoken Cantonese).

kintsugi list-locales --register creole
kintsugi list-locales --script Hant
kintsugi frequency --target ollama:qwen3.5:0.8b --lang-of zh-Hant --band rare --n 30

The full Google Translate / Translated / Apple coverage catalogues are reference material in docs/LANGUAGE_COVERAGE.md, kept out of the code on purpose (that list is never-ending).

Command line

kintsugi list                                            # all attack categories
kintsugi list-framings                                   # all translator framings
kintsugi transform zero_width "Do not take with alcohol"
kintsugi attack  --target ollama:qwen3.5:0.8b --judge anthropic:claude-opus-5-5 \
                 --lang en-ja --category japanese_pharma_traps --out findings.jsonl
kintsugi frequency --target ollama:qwen3.5:0.8b --direction en-ja --band rare --n 30

What the attacks find

A few headline numbers, drawn from the bundled observed rates:

Attack Pair Catastrophic rate
japanese_pharma_traps en-ja ~51%
anatomical_near_miss en-ja 50%
prompt_injection en-de ~30%
safety_critical_substance en-de ~25%

Vulnerability tracks resource tier almost perfectly: French is the hardest to break, low-resource Indic and Cantonese targets the easiest. Models at or above ~4B parameters show near-zero catastrophic rates; models at or below ~2B should not be used for safety-critical translation.

import kintsugi
kintsugi.observed_rates()["japanese_pharma_traps"]["en-ja"]   # {'n': 79, 'catastrophic': 40, 'bad': ...}

How judging works

  1. Local prefilter (kintsugi.prefilter) classifies obvious failures for free and returns None for anything ambiguous.
  2. LLM judge (kintsugi.Judge) scores the rest on adequacy, fluency, faithfulness, format and safety, and never returns an error verdict: on a parse failure it defaults to ok so statistics stay clean, and always-severe failure types (hallucination, refusal, prompt leak) are promoted.

A judge is optional. With no judge, kintsugi still runs the full prefilter and profanity check, which already catch the most dangerous failure modes.

License

Apache 2.0.

Metadata

Release files for kintsugi 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kintsugi 0.1.0
File Size Uploaded Provenance Actions
kintsugi-0.1.0.tar.gz 590.8 kB Without provenance Details for kintsugi-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for kintsugi 0.1.0
File Interpreter ABI Platform Actions
kintsugi-0.1.0-py3-none-any.whl Python 3 none any Details for kintsugi-0.1.0-py3-none-any.whl

Total release size: 1.2 MB

Release files / kintsugi-0.1.0.tar.gz

Download URL kintsugi-0.1.0.tar.gz
Size 590.8 kB
Tags Source
SHA-256 checksum
How to use checksums
2d3866c050505fdc93a6a768f9ad354e956f87a577681c1f2c572fc776ee87ec
BLAKE2b-256 checksum
How to use checksums
0e208d51546c10c593e6d0d2b09483488dc041408d7060a4d02484f80857d886
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Release files / kintsugi-0.1.0-py3-none-any.whl

Download URL kintsugi-0.1.0-py3-none-any.whl
Size 601.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4a28f032e54e7e85d13124a19092ff51a86e0f381a5e3baf527ad152d6c6353b
BLAKE2b-256 checksum
How to use checksums
23d16ee5868ac42d3807757e79fcc399f6cef53668ffcaf5b2b8ba88f0b9d2b5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.15

Security for kintsugi 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page