Skip to content

In production

The common settings are keyword shortcuts on guard (and watch), so most setups need no Config object:

client = counterfix.guard(
    OpenAI(),
    log="calls.jsonl",  # every call and its diagnosis
    sample_rate=0.05,  # background judge checks (see below)
    judge="groq:qwen/qwen3.8-27b",  # optional; otherwise picked from your keys
    mode="retry",  # re-run a flagged call once with a known fix
)

For the other settings (hourly cap, hooks, rules) pass a counterfix.Config; shortcuts given next to it override the matching fields.

Log every call

client = counterfix.guard(OpenAI(), log="calls.jsonl")
# same as: guard(OpenAI(), config=counterfix.Config(on_diagnosis=counterfix.log_to("calls.jsonl")))

Each line is {"record": ..., "diagnosis": ...}: the call and what the live checks found.

Reports

counterfix diagnose calls.jsonl                      # terminal summary (free)
counterfix diagnose calls.jsonl --judge --html report.html --json results.jsonl
counterfix diagnose calls.jsonl --fail-on-failure    # exit code 1 if any call failed

The HTML report is one self-contained file to send to a teammate: totals per failure type, then each call with its prompt, answer, why it was flagged and what to try.

Judge checks on live traffic, with a cost cap

config = counterfix.Config(
    sample_rate=0.05,  # every flagged call + 5% of the rest
    max_judged_per_hour=60,  # calls over the cap are skipped, never delayed
    judge="groq:qwen/qwen3.8-27b",
    on_judged=counterfix.log_to("judged.jsonl"),
)

Judge checks run on one background thread: your call returns as fast as before. A full queue drops new calls rather than waiting; counterfix.background.stats counts what was judged and skipped. Call counterfix.background.flush() before a short script exits.

Automatic retry

Config(mode="retry") re-runs a failed call once with a known parameter fix (for example max_tokens raised 4x and to at least 1,024: a limit is a ceiling, so a short answer costs no more), keeps whichever response is better, and records the attempt in diagnosis.retries. It never raises if the retry fails. Add your own per-model fixes with RuleStore and FixRule.

OpenTelemetry

config = counterfix.Config(on_diagnosis=counterfix.otel.export())

Each diagnosis becomes a short counterfix.diagnosis span under your request span, with gen_ai.* attributes and counterfix.failures, counterfix.top, counterfix.top.confidence and counterfix.top.fix. Needs pip install "counterfix[otel]".

Privacy

No telemetry. Logs stay where you write them. Judge checks send the prompt and answer to the judge you chose; use a local Ollama judge to keep them on your machine.