Files
promptfoo--promptfoo/docs/plans/eng-1770.md
T
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
CI / Shell Format Check (push) Waiting to run
CI / Check Ruby (3.4) (push) Waiting to run
CI / CI Config (push) Waiting to run
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Blocked by required conditions
CI / Build on Node ${{ matrix.node }} (push) Blocked by required conditions
CI / Style Check (push) Waiting to run
CI / Generate Assets (push) Waiting to run
CI / Check Python (3.14) (push) Waiting to run
CI / Check Python (3.9) (push) Waiting to run
CI / Build Docs (push) Waiting to run
CI / Code Scan Action (push) Waiting to run
CI / Site tests (push) Waiting to run
CI / webui tests (push) Waiting to run
CI / Run Integration Tests (push) Waiting to run
CI / Run Smoke Tests (push) Waiting to run
CI / Go Tests (push) Waiting to run
CI / Share Test (push) Waiting to run
CI / Redteam (Production API) (push) Waiting to run
CI / Redteam (Staging API) (push) Waiting to run
CI / GitHub Actions Lint (push) Waiting to run
CI / Check Ruby (3.0) (push) Waiting to run
release-please / release-please (push) Waiting to run
release-please / build (push) Blocked by required conditions
release-please / publish-npm (push) Blocked by required conditions
release-please / publish-npm-backfill (push) Waiting to run
release-please / docker (push) Blocked by required conditions
release-please / publish-code-scan-action (push) Blocked by required conditions
release-please / attest-code-scan-action (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

6.1 KiB

Plan: promptfoo eval -c <uuid> Cloud Config Support

Context

Currently, promptfoo redteam run -c <uuid> supports loading configs from Promptfoo Cloud by UUID, but promptfoo eval -c only accepts local file paths. This feature extends the same cloud config loading pattern to the eval command, allowing users to run promptfoo eval -c <cloud-uuid> to fetch and execute a config stored in Promptfoo Cloud.

The ticket (ENG-1770) has two parts:

  1. Open Source (this PR): Make the CLI accept a cloud UUID for eval -c
  2. Cloud: Show the promptfoo eval -c <uuid> command in the Cloud run modal (separate repo)

Changes

1. Add getEvalConfigFromCloud() to src/util/cloud.ts

Create a new function modeled after the existing getConfigFromCloud() (line 88-114) but hitting a different endpoint for eval configs:

export async function getEvalConfigFromCloud(id: string): Promise<UnifiedConfig> {
  // Same pattern as getConfigFromCloud but using `configs/${id}` endpoint
}
  • Endpoint: GET /api/v1/configs/${id} (eval configs, not redteam-specific)
  • Reuse existing makeRequest() helper (line 25) and cloudConfig.isEnabled() check pattern
  • Same error handling pattern as getConfigFromCloud

2. Add UUID detection to src/commands/eval.ts

In doEval() (line 107), add UUID detection before the existing config path processing at line 142. This mirrors the pattern in src/redteam/commands/run.ts:62-79.

// Before the existing config path processing (line 142)
const UUID_REGEX = /^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$/;

if (cmdObj.config?.length === 1 && UUID_REGEX.test(cmdObj.config[0])) {
  const cloudConfigObj = await getEvalConfigFromCloud(cmdObj.config[0]);
  defaultConfig = cloudConfigObj;
  cmdObj.config = undefined;
}

Key behaviors:

  • Only trigger when exactly one config path is provided and it's a UUID
  • Fetch the config from cloud and use it as defaultConfig
  • Clear cmdObj.config so resolveConfigs uses defaultConfig instead of trying to read a file
  • Import getEvalConfigFromCloud from ../../util/cloud

3. Update eval command description

Update the -c option description at line 903-906 to mention cloud UUID support:

.option(
  '-c, --config <paths...>',
  'Path to configuration file or cloud config UUID. Automatically loads promptfooconfig.yaml',
)

4. Add tests

Create test cases in a new test file or add to existing eval command tests, following the pattern from test/redteam/commands/run.test.ts:

  • Test UUID detection triggers cloud fetch
  • Test local file paths bypass UUID detection
  • Test error when cloud is not enabled
  • Test multiple config paths with UUID (should not trigger UUID detection)

Files to Modify

File Change
src/util/cloud.ts Add getEvalConfigFromCloud() function
src/commands/eval.ts Add UUID detection + cloud fetch in doEval(), update -c description
test/commands/eval.test.ts or new test file Add tests for UUID cloud config

Existing Code to Reuse

  • makeRequest() from src/util/cloud.ts:25 - authenticated HTTP helper
  • cloudConfig.isEnabled() from src/globalConfig/cloud.ts - auth check
  • UUID regex pattern from src/redteam/commands/run.ts:17
  • resolveConfigs() from src/util/config/load.ts:481 - existing config resolution (no changes needed)

Verification

  1. Build: npm run build should succeed
  2. Lint: npm run l && npm run f
  3. Unit tests: Run existing + new tests with npx vitest src/commands/eval and npx vitest src/util/cloud
  4. Manual test (if cloud access available):
    • npm run local -- eval -c <valid-uuid> --env-file .env should fetch config from cloud
    • npm run local -- eval -c path/to/config.yaml should still work as before
    • npm run local -- eval -c <invalid-uuid-format> should fall through to file resolution

Decisions (Reconciled)

  1. Use the OSS endpoint contract: GET /api/v1/configs/:id returning an envelope ({ config: ... }), not a raw payload.
  2. Support persisted config shape with providers and tests; loading must not require additional requests.
  3. Provider references in the config should use promptfoo://provider/<uuid>.
  4. Prompts should be emitted as plain strings.
  5. tests defaults to [] when missing.
  6. description falls back to config.name when missing.
  7. --watch is not supported when -c <uuid> is used. CLI should fail fast with a clear error.
  8. If -c <value> matches UUID format but cloud fetch fails (404/auth disabled), hard-fail.
  9. If multiple -c values are supplied and any value is a UUID, fail with an explicit error stating only one -c value is allowed for cloud UUID mode.
  10. Add tests in both places:
    • test/commands/eval.test.ts for UUID detection/CLI behavior
    • test/util/cloud.test.ts for getEvalConfigFromCloud() contract/error handling
  11. Scope is eval only (not redteam eval).
  12. No temporary UI note or minimum-version gating is required.
  13. In UUID mode, clear/ignore defaultConfigPath for the run to prevent accidental local reload/fallback behavior.
  14. Read-time schema normalization should normalize legacy fields (providerIds/testCases) into canonical fields (providers/tests).

CLI Error Messages (exact draft text)

  1. Multiple -c values with UUID mode:
    • Cloud config UUID mode supports exactly one -c value. Use: promptfoo eval -c <cloud-config-uuid>
  2. UUID mode with --watch:
    • --watch is not supported when using a cloud config UUID with -c. Use a local config file path for watch mode.
  3. UUID-shaped value with failed cloud fetch:
    • Failed to load cloud eval config "<uuid>". <reason>. Cloud UUID inputs do not fall back to local file paths. Check authentication and that the UUID exists.

Remaining Follow-ups

None.