Skip to content

feat: add agent scorer authoring @W-23997055 - #317

Draft
nabilnaffar-sf wants to merge 23 commits into
forcedotcom:mainfrom
nabilnaffar-sf:nnaffar/scorers
Draft

feat: add agent scorer authoring @W-23997055#317
nabilnaffar-sf wants to merge 23 commits into
forcedotcom:mainfrom
nabilnaffar-sf:nnaffar/scorers

Conversation

@nabilnaffar-sf

@nabilnaffar-sf nabilnaffar-sf commented Jul 7, 2026

Copy link
Copy Markdown

What does this PR do?

Add Agent Scorer authoring & run support

What issues does this PR fix or reference?

Introduces a client-side API for authoring, versioning, and running Agent Scorers — the evaluators that grade Agentforce agent sessions. Consumers (plugin-agent CLI, vscode-agents) get a single, typed entry point that turns a ScorerSpec into deployable metadata and runs a scorer against an STDM session, without each repo re-implementing the XML/prompt plumbing.

What's included

  • AuthoringcreateScorerDefinition(spec, { outputDir, write }) generates aiAgentScorerDefinition and (when needed) genAiPromptTemplate metadata from a typed ScorerSpec. Default prompt content is derived from the scorer's output JSON Schema and aligned with the NGT templates.
  • Versioning & lifecycleaddScorerVersion, setScorerVersionStatus, and activation toggling (setVersionAssociationActiveInScorerXml) let callers add versions and edit status/activation on existing scorer XML.
  • RunningrunScorer(spec, session, connection) runs a scorer against a normalized STDM SessionView and returns a ScorerResult, dispatched through a pluggable engine registry (registerEngine / getEngine), with Generations and PromptTemplate engines provided.
  • Validation & schemasvalidateScorerSpec, API-name helpers (isValidScorerApiName, labelToApiName), plus exported JSON Schemas (SCORER_SPEC_JSON_SCHEMA, SESSION_VIEW_JSON_SCHEMA) and a scripts/gen-scorer-schema.mjs generator.
  • Public types — full scorer vocabulary (ScorerSpec, engine/status/outcome/input-scope enums, session view model) re-exported from the package root.

@nabilnaffar-sf
nabilnaffar-sf marked this pull request as ready for review July 7, 2026 06:02
@jeniok

jeniok commented Jul 12, 2026

Copy link
Copy Markdown

Nice work getting this out — the shape is right and the XML matches what the org expects. A few things I'd like to see addressed before we approve; the top three are library-level correctness issues that let
invalid specs slip through to on-disk metadata.

Blocking

  1. validateScorerSpec doesn't require specification for dataType: Number — src/agentScorer.ts:135-147

validateScorerSpec({ dataType: 'Number', ...no specification... }) → no throw

buildScorerXml then produces a Number scorer with zero entries and no block. Add:

if (spec.dataType === 'Number' && !spec.specification) {
throw new Error("specification is required when dataType is 'Number'.");
}

  1. validateScorerSpec doesn't validate lightningType against SUPPORTED_LIGHTNING_TYPES — src/agentScorer.ts:149-151

validateScorerSpec({ dataType: 'LightningType', lightningType: 'bogus__type', ... }) → no throw

The CLI's JSON schema catches this today, but library-only callers get no protection. Please cross-check against the const you already export.

  1. buildScorerXml silently drops user-supplied outputEnumValues when dataType === 'Number' — src/agentScorer.ts:219-243

The Number branch always regenerates enum values from specification and ignores anything the caller passed in outputEnumValues. Either reject the combination in validateScorerSpec (mutually exclusive) or honor
the caller's values.

Should fix

  1. No validation that Text scorers have outputEnumValues at all. validateScorerSpec only runs the fallback-count check when outputEnumValues is defined. A Text scorer with no output values passes validation and
    produces XML with no outcomes.

  2. No validation of agentAssociation.samplingRate ∈ [0, 1]. The CLI's YAML schema enforces this; library callers get no bounds check.

  3. buildPromptTemplateXml hard-codes primaryModel: 'sfdc_ai__DefaultOpenAIGPT4OmniMini' — src/agentScorer.ts:333. If intentional for v1, drop a short // WHY comment; otherwise expose as an optional spec field.

  4. Duplicated mkdir for promptDir — src/agentScorer.ts:377 and :385 both compute join(options.outputDir, 'genAiPromptTemplates'). Harmless (recursive: true) but the variable gets redeclared.

Nice-to-have

  1. No unit tests in this PR. All coverage currently lives downstream in plugin-agent PR #456, and that suite is broken. A small test/agentScorer.test.ts here would let the library stand on its own.

  2. String(rounded) for number enum values strips trailing zeros — src/agentScorer.ts:110-115. Step 0.1 produces '0', '0.1', '0.5', '1'. Please confirm the backend accepts trailing-zero-stripped forms; if it
    wants "0.10" or "1.0", this drifts.

@salesforce-cla

salesforce-cla Bot commented Sep 2, 2026

Copy link
Copy Markdown

Thanks for the contribution! It looks like @shanile50 is an internal user so signing the CLA is not required. However, we need to confirm this.

Comment thread src/agentScorer.ts Outdated
@nabilnaffar-sf nabilnaffar-sf changed the title feat: add agent scorer authoring feat: add agent scorer authoring @W-23997055 Sep 6, 2026
jfeingold35 and others added 14 commits September 9, 2026 11:47
Route OpenEnded scorers to the NGT (core) scorer-open-* prompt bodies so
the default prompt content matches what the NGT UI shows:

- text / number: labeled variant when the scorer has predefined values
  (outputEnumValues), plain variant otherwise
- boolean / url / date: their dedicated resource bodies

The transcript is referenced via {!$Input:Session} (core pipes the same
Session input through a getSession data action; same meaning, and it keeps
the prompt consistent with the inputs the template declares). Shared JSON
result block + transcript footer are factored into a helper to avoid
duplication across the seven templates. The legacy Predefined
measurement/multilabel branches are left untouched since the authoring
flow only ever creates OpenEnded scorers.

Update unit tests to cover the new per-type routing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the seven per-type OpenEnded prompt constants (and
resolveOpenEndedCategory) with a single generic SCORER_PROMPT template
whose output shape is driven by the type's JSON Schema, substituted into
{!$OutputSchema}, plus per-scorer guidance in {!$Instructions}.

schemaFor() is now the only place that knows about a given type, so
supporting new/custom Lightning Types is a follow-up that touches only
schemaFor() with no prompt changes. This intentionally diverges from the
NGT UI's per-type prose in favour of extensibility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
generateNumberEnumValues was exported and unit-tested but had no caller:
Number scorers emit a <specification> (min/max/step/threshold) that the
backend applies at runtime, and validateScorerSpec forbids outputEnumValues
for Number, so the helper was never invoked. Its threshold-agnostic
all-NotApplicable output was also misleading. Remove the function, its
export, and its tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keep the branch lint-clean ahead of review. No behavior change:

- split validateScorerSpec into validateNumberScorer /
  validateLightningTypeScorer to drop its cyclomatic complexity
  under the limit (was 30);
- add an explicit `case undefined` so the lightningType switch is
  exhaustive over `string | undefined`;
- demote two narrative block comments from /** to /* (they carry no
  JSDoc tags) so jsdoc/check-indentation stops flagging their aligned
  bullet lists.
@nabilnaffar-sf
nabilnaffar-sf marked this pull request as draft September 9, 2026 17:32
Remove the scorer mutation/update surface from the lib (version-append,
status, and agent-association mutators). Once an AiAgentScorerDefinition
XML is written it is the source of truth; any further change is authored
directly in the XML. Version parsing and read-time version selection are
unchanged.

Update doc comments and the not-found error to reference the renamed CLI
command 'sf agent scorer generate-metadata-file' (was 'create').
… validation

Documentation (single source of truth: ScorerSpec/AgentAssociation JSDoc → generated
spec schema):
- Explain what each status means and how to author it: Draft = still developing
  (inner loop), runnable ad-hoc but not activatable; Available = validated/ready,
  runnable ad-hoc and eligible for automatic production scoring.
- Clarify isActive (automatic production scoring, sampled by samplingRate; requires
  Available) vs ad-hoc runs, and that samplingRate governs automatic sampling only.
- STDM session view: require every timestamp to be UTC ending in a literal '+0000'
  offset (reject 'Z', colon offsets, and non-UTC offsets) via a shared IsoTimestamp
  type carrying the description and @pattern.

Behavior:
- Fail a run on a blank/score-less generation (empty completion, or a parseable
  envelope with empty/all-null outputs and no legacy output) instead of reporting a
  passing empty score.
- Validate apiName presence, engineType, and agentAssociation presence with
  actionable messages instead of raw TypeErrors.
- Ad-hoc run selection falls back to the highest non-Archived (Draft) version when no
  Available version exists, so a freshly-scaffolded scorer runs without --scorer-version;
  only throws when every version is Archived. Activation still requires Available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants