plugins/promptfoo/skills/promptfoo-provider-setup/SKILL.md
Connect Promptfoo to the system under test with the smallest reliable provider or target configuration. Prefer a working smoke test over a clever abstraction.
Read references/provider-patterns.md when you need concrete YAML or provider
wrapper examples.
For OpenAPI specs, you can run
scripts/openapi-operation-to-config.mjs to draft a one-operation HTTP smoke
config, then inspect and edit the result before probing. With --token-env, it
infers Bearer/OAuth2/OpenID and header/query/cookie API-key auth; use
--auth-header/--auth-prefix to override.
Infer from the repo or user prompt when possible:
targets block, local provider
wrapper, or a minimal smoke-test suite.If the contract is unclear, create a conservative TODO-marked starter and state exactly what must be verified before using it against production.
Use one of these modes, or combine them:
provider.js or provider.py when built-in providers
cannot express auth, signing, streaming, multi-step calls, or custom parsing.Do not send secrets to unknown endpoints. Use {{env.VAR}} placeholders in
configs and local environment variables only in shell commands.
For live HTTP endpoints:
OPTIONS, or a safe GET.transformResponse, such as json.output.queryParams for query-string fields on any HTTP method, and use the
text variable in transformResponse for plain-text responses.stateful: false for stateless endpoints; otherwise validate target
will run a session-memory check. For stateful apps, include {{sessionId}}
in the request or configure server-side session parsing.For static code discovery:
rg.id: https for straightforward JSON HTTP APIs.file://provider.js or file://provider.py for custom auth, request
signing, streaming, retries, multi-step setup, local code, Python agent SDKs,
or complex parsing.targets with inputs for redteam multi-input systems. Do not invent a
single prompt field when the real app accepts named inputs.Add or update a config with:
# yaml-language-server: $schema=https://promptfoo.dev/config-schema.jsondescriptiontargets config with {{env.VAR}} for secrets--no-cache run commandsWhen writing a local wrapper, return { output } and include structured errors
when the target response is malformed. JavaScript providers receive config in
constructor options.config and expose callApi(prompt, context); read named
inputs from context.vars. Python providers use file://provider.py or
file://provider.py:function_name; the function takes (prompt, options, context) and reads named inputs from context.get("vars", {}). Set
config.workers: 1 for non-thread-safe SDKs, config.timeout for slow calls,
and config.pythonExecutable/PROMPTFOO_PYTHON for venvs. Add harmless
defaults because validate target may call providers without test-case vars.
From the promptfoo repo, use the local build:
npm run local -- validate config -c path/to/promptfooconfig.yaml
npm run local -- validate target -c path/to/promptfooconfig.yaml
npm run local -- eval -c path/to/promptfooconfig.yaml -o output.json --no-cache --no-share
Outside the promptfoo repo, use:
npx promptfoo@latest validate config -c path/to/promptfooconfig.yaml
npx promptfoo@latest validate target -c path/to/promptfooconfig.yaml
npx promptfoo@latest eval -c path/to/promptfooconfig.yaml -o output.json --no-cache --no-share
Inspect the output file for results.stats, response.output, score, and
error; do not rely only on the process exit code.
Use --no-share by default while probing live or internal systems. Remove it
only when the user explicitly wants a cloud share URL.
# WRONG: shell-style env vars are literal strings in YAML
apiKey: $API_KEY
# CORRECT: promptfoo renders Nunjucks env references
apiKey: '{{env.API_KEY}}'
# WRONG: flattening a multi-input target into prompt loses attack surface
body:
prompt: '{{prompt}}'
# BETTER: preserve the real app fields
body:
user_id: '{{user_id}}'
message: '{{message}}'
When done, state:
--no-cache --no-share