Integrations
A8 in promptfoo
Run A8 as a promptfoo provider, score each abstention as unanswered, and move an llm-rubric grader to A8.
This page as Markdown: https://u22a8.ai/docs/promptfoo.md. The index of every docs page: https://u22a8.ai/llms.txt.
promptfoo runs a model against a set of test cases and grades each output. A8 answers the Chat Completions API, so promptfoo’s openai:chat provider calls A8 once you point it at A8’s base URL.
§1Before you start
Create an API key with the models:infer permission, as API keys shows, and set it as U22A8_API_KEY.
npm install -g [email protected]
export U22A8_API_KEY="<your key>"
§2Configure the provider
Point the openai:chat provider at A8, and pin the version you test, with the minimum accuracy as its tag:
providers:
- id: openai:chat:a8-1-1:95@20260715t140322z # minimum accuracy 95
config:
apiBaseUrl: https://api.u22a8.ai/eval/v1
apiKeyEnvar: U22A8_API_KEY
response_format:
type: json_schema
json_schema:
name: verdict
schema:
type: object
properties:
refund: {type: boolean}
| Option | What it does for A8 |
|---|---|
id | openai:chat: followed by the model. A pinned identifier keeps a re-run on the same version, and its tag sets the minimum accuracy: :95 is 95 |
apiBaseUrl | A8’s base URL |
apiKeyEnvar | The environment variable that holds the key |
response_format | The verdict’s shape |
promptfoo also sends temperature and max_tokens by default. A8 accepts both and ignores them.
§3Judge one subject
The prompt is a chat in JSON: the criterion as the system message, and the subject as the user message, filled from each test’s variables:
prompts:
- label: refund
raw: |
[
{"role": "system", "content": "Does the reply promise a refund?"},
{"role": "user", "content": "{{reply}}"}
]
tests:
- vars:
reply: We will refund you in full by Friday.
refund: true
Run it:
promptfoo eval
Each result holds the verdict as its output: {"refund": true}. promptfoo parses a json_schema response, so output is that object.
§4Score an abstention as unanswered
An abstention reaches promptfoo as a refusal: the output is the refusal text, and the provider response carries isRefusal: true. A request A8 filtered carries finishReason: "content_filter" as well. Score both as unanswered, each with its own reason, so they lower coverage and leave accuracy as it is:
defaultTest:
assert:
- type: javascript
value: |
if (context.providerResponse?.finishReason === "content_filter") {
return { pass: true, score: 0, reason: "filtered" };
}
if (context.providerResponse?.isRefusal) {
return { pass: true, score: 0, reason: "abstained" };
}
return output.refund === context.vars.refund;
Two numbers then describe the run:
- Coverage: the share of results A8 answered, those whose
reasonis something other thanabstainedandfiltered. - Accuracy: of those results, the share that pass.
Write the results to a file and count them with jq:
promptfoo eval -o results.json
jq '.results.results
| map(.gradingResult.componentResults[0])
| (map(select(.reason != "abstained" and .reason != "filtered")))
as $answered
| {total: length,
answered: ($answered | length),
right: ($answered | map(select(.pass)) | length)}' results.json
Accuracy is right / answered, and coverage is answered / total. See Testing for why they stay apart.
§5Move an llm-rubric grader
An llm-rubric assertion grades each output with a grading provider. Make A8 the grading provider, and keep the rubric:
defaultTest:
options:
provider:
id: openai:chat:a8-1-1:95@20260715t140322z # minimum accuracy 95
config:
apiBaseUrl: https://api.u22a8.ai/eval/v1
apiKeyEnvar: U22A8_API_KEY
assert:
- type: llm-rubric
value: The reply promises the customer a refund.
promptfoo asks the grader for JSON with pass, score and reason, and A8 answers in that shape.
Inside llm-rubric, an abstention reaches the rubric as refusal text, and the assertion fails. To score abstentions as unanswered, judge the output with A8 as the provider and a javascript assertion, as Score an abstention as unanswered shows.
§6A complete example
# promptfooconfig.yaml
description: Refund promises, judged by A8 at a minimum accuracy of 95
providers:
- id: openai:chat:a8-1-1:95@20260715t140322z
config:
apiBaseUrl: https://api.u22a8.ai/eval/v1
apiKeyEnvar: U22A8_API_KEY
response_format:
type: json_schema
json_schema:
name: verdict
schema:
type: object
properties:
refund: {type: boolean}
prompts:
- label: refund
raw: |
[
{"role": "system", "content": "Does the reply promise a refund?"},
{"role": "user", "content": "{{reply}}"}
]
defaultTest:
assert:
- type: javascript
value: |
if (context.providerResponse?.finishReason === "content_filter") {
return { pass: true, score: 0, reason: "filtered" };
}
if (context.providerResponse?.isRefusal) {
return { pass: true, score: 0, reason: "abstained" };
}
return output.refund === context.vars.refund;
tests:
- vars:
reply: We will refund you in full by Friday.
refund: true
- vars:
reply: We will sort it out on Monday.
refund: false
Keep the test cases apart from what you teach A8, so a passing run measures what it learned. See Testing.