Skip to main content

Output Controls

Quality Scorer

quality-scorer checks a finished answer before your application receives it. It can require a minimum length, score the answer with local benchmarks, and run your assertions. A failed answer is blocked or replaced with a fixed message.

It runs on Chat Completions and Responses with stream: false. A good score does not prove that the facts in an answer are correct.

Configuration​

Start with one measurable rule. Add it to a test gateway with the config-first workflow:

policy-config.yaml
pack:
  name: minimum-answer-length
  version: 1.0.0
  enabled: true
policies:
  chain:
    - quality-scorer
policy:
  quality-scorer:
    min_output_chars: 20
    failure_action:
      action: block

Compare a short and a long answer​

Test the rule offline with two JSON test files that give the provider answer in upstream_response:

tests/short.json
{"name": "short answer blocks",
 "input": {"messages": [{"role": "user", "content": "Is the report ready?"}],
           "upstream_response": {"choices": [{"message": {"role": "assistant", "content": "OK"}}]}},
 "expected": {"verdict": "block", "reason_code": "quality.too_short"}}
tests/long.json
{"name": "long answer passes",
 "input": {"messages": [{"role": "user", "content": "Is the report ready?"}],
           "upstream_response": {"choices": [{"message": {"role": "assistant", "content": "The requested report is ready for review."}}]}},
 "expected": {"verdict": "allow", "reason_code": "ok"}}

Run verdictan policy test --json. Both pass. Through a gateway, the short answer returns HTTP 400 with quality.too_short in the decision. Another control can still block an answer that passes this rule, so read the nested policy results to find the failing check.

To return a fixed message instead of an error, use:

Inside policy.quality-scorer
failure_action:
  action: fallback
  fallback_message: "The answer did not meet the minimum length."

The gateway then returns HTTP 200 with that message and reason code quality.fallback. It does not call the model again.

When the basic rule works, add quality assertions for JSON, required phrases, or a rubric.

Fields​

FieldTypeDefaultDescription
modestringbufferbuffer scores the complete response. stream_through returns allow with quality.deferred and does not score.
min_output_charsinteger0Fails an answer with fewer characters. 0 turns it off.
min_sentencesinteger0Fails an answer with fewer sentences. 0 turns it off.
benchmarksobject{}Switches: ragas_faithfulness, ragas_relevancy, bleu_score, nli_entailment, coherence, completeness
bleu_referencestringReference text for bleu_score when the request has no context documents
assertionsarray[]Assertions. See the fields below.
thresholdsobject{}Minimum scores: min_aggregate, min_faithfulness, min_relevancy, min_bleu, min_accuracy, min_coherence, min_completeness. A threshold turns on its benchmark if the benchmark is not set.
weightsobject{}Weights in the aggregate: faithfulness, relevancy, bleu, accuracy, coherence, completeness. An enabled benchmark without a weight uses 1.0.
failure_action.actionstringblockblock, fallback, or retry. Only fallback changes the response.
failure_action.fallback_messagestringBuilt-in textReplacement text with action: fallback. The default is I apologize, but I cannot provide a sufficiently accurate response to this query.
pass_policy.strategystringallall, quorum, or weighted_average, for assertions with mode: enforce and severity: critical
pass_policy.quorumnumber0.5Share of those assertions that must pass with quorum
pass_policy.thresholdnumber0.5Minimum weighted score with weighted_average
industry_profiles.<industry>object{}Threshold overrides for requests with verdictan.industry set to this name
judgeobjectOptional judge model call. Its result appears in the details and does not change the verdict.

Assertion fields​

FieldTypeDefaultDescription
typestringAssertion type. Required. An unsupported type shows as an assertion error.
namestringThe typeName in the details
enabledbooleantruefalse skips the assertion
thresholdnumberDepends on typeMinimum score that passes
weightnumber1.0Weight with weighted_average
modestringenforceenforce, audit, or shadow. Only enforce can fail an answer.
severitystringcriticalcritical, warning, or info. Only critical can fail an answer.
configobject{}Options of the type, such as value for contains or rubric for llm-rubric

Quality Assertion Configuration lists the types and their options.

How it works​

  1. The control reads the answer from choices[0].message.content, and the query and context documents from the request.
  2. An answer shorter than min_output_chars records quality.too_short. Fewer sentences than min_sentences records quality.too_few_sentences.
  3. If the request has verdictan.industry and industry_profiles has that name, the profile thresholds replace the top-level ones.
  4. Each enabled benchmark is scored with local heuristics. If the process environment has VERDICTAN_NLI_ENDPOINT, nli_entailment calls that endpoint with a 5-second timeout.
  5. Each enabled assertion is evaluated and scored.
  6. The aggregate combines the weighted benchmark and assertion scores.
  7. Each score below its threshold records a failure, such as quality.faithfulness_below_threshold or quality.aggregate_below_threshold.
  8. pass_policy is applied to the enforced, critical assertions. A failure records the names of the failed assertions.
  9. No failure: allow with ok.
  10. A failure with fallback: the gateway replaces the answer with fallback_message, and the verdict is allow with quality.fallback.
  11. A failure with any other action: block, with the comma-separated failures as the reason code, such as quality.too_short,quality.aggregate_below_threshold.

The details contain output_chars, sentence_count, metrics, thresholds, weights, has_context, assertions, failures, pass_policy_result, and judge. Scores in the details are percentages.

Phase and verdicts​

ItemValue
Phaseoutput
Enforced pathChat Completions and Responses with stream: false
Verdictsallow, block
Reason codesok, quality.fallback, quality.deferred, and one or more of quality.too_short, quality.too_few_sentences, quality.<metric>_below_threshold, quality.aggregate_below_threshold, or an assertion name

A block returns HTTP 400 with error code content_policy_violation and a verdictan.decision object with verdict BLOCK. A fallback returns HTTP 200 with the replacement text and quality.fallback in verdictan.decision.

Limits and failure behavior​

  • With stream: true, the gateway buffers the response but does not score it.
  • failure_action.action: retry does not retry. It blocks. Lint accepts max_retries, but the runtime ignores it.
  • Benchmarks are local heuristics, not model evaluations. Measure the thresholds on your own traffic before you block on them.
  • thresholds.min_aggregate without benchmarks or assertions turns on ragas_relevancy and completeness to calculate an aggregate.
  • ragas_faithfulness needs context documents in the request. Without them, there is no faithfulness score, and min_faithfulness cannot fail. Without context, bleu_score uses bleu_reference.
  • Audit and shadow assertion results stay available in the details but never block.
  • The judge adds latency and provider cost to every response it evaluates.
  • Lint also accepts industry, mock_scoring, providers, targets, and regression_monitoring. This page does not cover them.

Next steps​