mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 19:50:42 +08:00
* @W-21955450@ Rename topic to subagent for Agent Script v2
Aligns with Agent Script v2 naming standards where `topic` is renamed
to `subagent` across all skill documentation and templates.
Changes:
- Agent Script templates: topic keyword → subagent keyword
- References: @topic.* → @subagent.*
- Documentation: Updated all skill references and guides
- Natural language references preserved in comments/descriptions
* Rename start_agent topic_selector to agent_router
Completes the topic → subagent terminology alignment by:
1. Renaming start_agent from topic_selector to agent_router (15 agent files)
2. Updating template topic declarations: topic {{placeholder}} → subagent {{placeholder}} (5 files)
3. Updating all @subagent.topic_selector references to @subagent.agent_router (35 occurrences)
4. Updating documentation: prose, examples, and diagrams (10 markdown files)
5. Updating comments to use agent_router terminology
Files affected:
- 22 agent template files
- 10 documentation/reference markdown files
- Template component files
The agent_router name is more descriptive of its actual function
(routing to different subagents) and completes the Agent Script v2
terminology standardization.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Rename files with "topic" to use "subagent" terminology
Completes the topic → subagent terminology alignment by renaming
files and updating all references:
**Files renamed (5):**
- multi-topic.agent → multi-subagent.agent
- template-single-topic.agent → template-single-subagent.agent
- template-multi-topic.agent → template-multi-subagent.agent
- topic-with-actions.agent → subagent-with-actions.agent
- agent-topic-map-diagrams.md → agent-subagent-map-diagrams.md
**References updated (6 docs):**
- Updated all filename references to point to new filenames
- Updated "Topic Map" → "Subagent Map" throughout documentation
- Updated "multi-topic"/"single-topic" → "multi-subagent"/"single-subagent"
Files modified:
- README.md, SKILL.md, agent-spec-template.md
- assets/agents/README.md, assets/README-legacy.md
- references/agent-design-and-spec-creation.md
This ensures consistent "subagent" terminology across filenames,
file content, and all documentation references.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Complete topic-to-subagent terminology update across skills
Comprehensive update replacing "topic" with "subagent" terminology throughout
the developing-agentforce and testing-agentforce skills to align with Agent
Script's `subagent` block naming.
Key changes:
- "Topic Selector" → "Subagent Router" in all agent templates and docs
- "Topic/action" → "Subagent/action" in documentation
- "Topic map" → "Subagent map" in diagram references
- Updated all architecture documentation to use "subagent" terminology
- Updated 19 .agent template files with new labels and comments
- Updated 8 reference documentation files with consistent terminology
API contract preservation:
- Test spec YAML files preserve "topic" terminology to match Testing Center API
- Added clarifying comments explaining topic/subagent equivalence in YAML files
- Field names like `expectedTopic` unchanged (Salesforce API requirement)
Preserved terms:
- "off-topic" (standard phrase for out-of-scope)
- "expectedTopic" field (Testing Center API)
- "platform topics" (Salesforce guardrail features)
32 files changed, 379 insertions(+), 366 deletions(-)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* Complete comprehensive topic-to-subagent terminology update
Thorough update replacing all remaining "topic" references with "subagent"
terminology across developing-agentforce, testing-agentforce, and
observing-agentforce skills to fully align with Agent Script's `subagent`
block naming.
Key changes:
- Agent Script syntax: @topic.<name> → @subagent.<name>
- Agent Script syntax: topic.actions → subagent.actions
- Shell script patterns: ^topic → ^subagent
- Documentation: "topic instructions" → "subagent instructions"
- observing-agentforce skill: Updated all agent architecture references
- Template files: Updated all inline comments and descriptions
- Variable names in scripts: TOPIC → SUBAGENT
Specific updates:
- 45 files changed, 294 insertions, 294 deletions
- Updated all Agent Script code examples to use @subagent syntax
- Updated observing-agentforce issue classification guide
- Updated shell script patterns in diagnostic tools
- Updated Apex comments to clarify topic field maps to subagents
Preserved (as required):
- "off-topic" and "off_topic" (standard out-of-scope phrase)
- Testing Center API fields: expectedTopic, topic: in YAML
- API response fields: .topic, generatedData.topic, topic_assertion
- STDM field names: ssot__TopicApiName__c (with clarifying docs)
- Template placeholders in test specs (API values)
- "Topic hash drift" (API field behavior)
- "Email topic/purpose" (means email subject)
- Explanatory comments about API field mapping
All Agent Script syntax and documentation now consistently uses "subagent"
while preserving backward compatibility with platform API field names.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* a few more topic -> subagent replacements
---------
Co-authored-by: Steve Hetzel <shetzel@salesforce.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
275 lines
12 KiB
Markdown
275 lines
12 KiB
Markdown
# Mode B: Testing Center Batch Testing — Full Reference
|
|
|
|
Testing Center is Salesforce's built-in test infrastructure for Agentforce agents. Tests are deployed as metadata to the org and can be run via CLI or Setup UI.
|
|
|
|
## Phase 1: Create Test Spec YAML
|
|
|
|
The Testing Center uses a specific YAML format. Create a temporary spec file:
|
|
|
|
```yaml
|
|
# /tmp/<AgentApiName>-test-spec.yaml
|
|
name: "OrderService Smoke Tests"
|
|
subjectType: AGENT
|
|
subjectName: OrderService # BotDefinition DeveloperName (API name)
|
|
|
|
testCases:
|
|
# Subagent routing test
|
|
- utterance: "Where is my order #12345?"
|
|
expectedTopic: order_status
|
|
|
|
# Action invocation test (FLAT string list -- NOT objects)
|
|
# CRITICAL: Use Level 2 INVOCATION names from reasoning: actions: (e.g. "lookup_order")
|
|
# NOT Level 1 DEFINITION names from subagent: actions: (e.g. "get_order_status")
|
|
- utterance: "I want to return my order from last week"
|
|
expectedTopic: returns
|
|
expectedActions:
|
|
- lookup_order
|
|
|
|
# Outcome validation (LLM-as-judge)
|
|
- utterance: "How do I track my shipment?"
|
|
expectedTopic: order_status
|
|
expectedOutcome: "Agent explains how to check shipment tracking status"
|
|
|
|
# Escalation test
|
|
- utterance: "I want to talk to a real person about a billing dispute"
|
|
expectedTopic: escalation
|
|
expectedActions:
|
|
- transfer_to_agent
|
|
|
|
# Guardrail test
|
|
- utterance: "What's the best recipe for chocolate cake?"
|
|
expectedOutcome: "Agent politely declines and redirects to order-related topics"
|
|
|
|
# Multi-turn test with conversation history
|
|
- utterance: "Yes, my email is john@example.com"
|
|
expectedTopic: identity_verification
|
|
expectedActions:
|
|
- verify_customer
|
|
conversationHistory:
|
|
- role: user
|
|
message: "I need to check my mortgage status"
|
|
- role: agent
|
|
topic: identity_verification
|
|
message: "I'd be happy to help with your mortgage status. First, I'll need to verify your identity. What is your email address on file?"
|
|
```
|
|
|
|
### Required Fields
|
|
|
|
| Field | Required | Description |
|
|
|-------|----------|-------------|
|
|
| `name` | Yes | Display name for the test suite (becomes MasterLabel) |
|
|
| `subjectType` | Yes | Always `AGENT` |
|
|
| `subjectName` | Yes | Agent BotDefinition DeveloperName (API name, e.g. `OrderService`) |
|
|
| `testCases` | Yes | Array of test case objects |
|
|
| `testCases[].utterance` | Yes | User input message to test |
|
|
| `testCases[].expectedTopic` | No | Expected subagent name |
|
|
| `testCases[].expectedActions` | No | Flat list of action name strings |
|
|
| `testCases[].expectedOutcome` | No | Natural language description (LLM-as-judge) |
|
|
| `testCases[].conversationHistory` | No | Prior conversation turns for multi-turn tests |
|
|
| `testCases[].contextVariables` | No | Session context variables |
|
|
|
|
### Key Rules
|
|
|
|
- `expectedActions` is a **flat string array**, NOT objects: `["action_a", "action_b"]`
|
|
- Action assertion uses **superset matching**: test PASSES if actual actions include all expected actions
|
|
- **Transition actions** (`go_home_search`, `go_escalation`) appear in `actionsSequence` alongside real actions. The superset matching handles this correctly -- you don't need to list transition actions.
|
|
- `expectedOutcome` uses LLM-as-judge evaluation -- describe the desired behavior in natural language
|
|
- Missing `expectedOutcome` causes a harmless ERROR in `output_validation` but topic/action assertions still pass
|
|
- **Always add `expectedOutcome`** -- it is the most reliable assertion type (LLM-as-judge scores 5/5 consistently for correct behavior) and works even when topic/action assertions can't capture nuanced behavior
|
|
|
|
### Single-Turn vs Multi-Turn Considerations
|
|
|
|
- Single-turn tests only capture the first response. If an action requires info collection first (e.g. identity verification asks for email before calling `verify_customer`), the action won't fire in one turn.
|
|
- For multi-turn workflows, either: (1) omit `expectedActions` and rely on `expectedOutcome`, or (2) use `conversationHistory` to simulate prior turns.
|
|
- For guardrail tests (off-topic), omit `expectedTopic` and use `expectedOutcome` only -- the agent correctly stays in `entry` which has no matching subagent assertion. NOTE: The generated XML still includes an empty `topic_assertion` expectation, which will return `FAILURE` with score=0. This is expected and harmless -- only check the `output_validation` result for guardrail tests.
|
|
|
|
### Parsing Results for Guardrail/Safety Tests
|
|
|
|
When summarizing results, filter out `topic_assertion` FAILURE for tests that have no
|
|
`expectedTopic` set. These are false negatives caused by the empty assertion XML. Count
|
|
only `output_validation` results for these tests. Example:
|
|
```python
|
|
# When parsing results, skip topic_assertion for guardrail tests
|
|
for tc in test_cases:
|
|
has_expected_topic = bool(tc.get('expectations', {}).get('expectedTopic'))
|
|
for r in tc.get('testResults', []):
|
|
if r['name'] == 'topic_assertion' and not has_expected_topic:
|
|
continue # Skip -- empty assertion always fails
|
|
# ... process other results
|
|
```
|
|
|
|
## Phase 2: Deploy and Run Tests
|
|
|
|
`sf agent test create` takes the YAML spec, converts it to `AiEvaluationDefinition` metadata XML, and deploys it to the org. The XML is written to `force-app/main/default/aiEvaluationDefinitions/` as part of the SFDX project.
|
|
|
|
```bash
|
|
# Step 1: Check if Testing Center is available
|
|
sf agent test list --json -o <org>
|
|
|
|
# Step 2: Deploy the test suite (writes XML to force-app/ and deploys to org)
|
|
sf agent test create --json \
|
|
--spec /tmp/<AgentApiName>-test-spec.yaml \
|
|
--api-name <TestSuiteName> \
|
|
-o <org>
|
|
|
|
# The deployed metadata is now at:
|
|
# force-app/main/default/aiEvaluationDefinitions/<TestSuiteName>.aiEvaluationDefinition-meta.xml
|
|
|
|
# Step 3: Run the tests (wait for results)
|
|
sf agent test run --json \
|
|
--api-name <TestSuiteName> \
|
|
--wait 10 \
|
|
--result-format json \
|
|
-o <org> | tee /tmp/test_run.json
|
|
|
|
# Step 4: Extract job ID from run output
|
|
JOB_ID=$(python3 -c "import json; print(json.load(open('/tmp/test_run.json'))['result']['runId'])")
|
|
|
|
# Step 5: Get detailed results (ALWAYS use --job-id, NOT --use-most-recent)
|
|
sf agent test results --json \
|
|
--job-id "$JOB_ID" \
|
|
--result-format json \
|
|
-o <org> | tee /tmp/test_results.json
|
|
```
|
|
|
|
### Updating an Existing Test Suite
|
|
|
|
```bash
|
|
sf agent test create --json \
|
|
--spec /tmp/<AgentApiName>-test-spec.yaml \
|
|
--api-name <TestSuiteName> \
|
|
--force-overwrite \
|
|
-o <org>
|
|
```
|
|
|
|
### Retrieving Existing Test Definitions
|
|
|
|
```bash
|
|
sf project retrieve start --json --metadata "AiEvaluationDefinition:<TestSuiteName>" -o <org>
|
|
# Retrieved to: force-app/main/default/aiEvaluationDefinitions/<TestSuiteName>.aiEvaluationDefinition-meta.xml
|
|
```
|
|
|
|
## Phase 3: Analyze Results
|
|
|
|
Parse the results JSON:
|
|
|
|
```bash
|
|
# Show pass/fail summary per test case
|
|
python3 -c "
|
|
import json
|
|
data = json.load(open('/tmp/test_results.json'))
|
|
for tc in data['result']['testCases']:
|
|
utterance = tc['inputs']['utterance'][:50]
|
|
results = {r['name']: r['result'] for r in tc.get('testResults', [])}
|
|
topic_pass = results.get('topic_assertion', 'N/A')
|
|
action_pass = results.get('action_assertion', 'N/A')
|
|
outcome_pass = results.get('output_validation', 'N/A')
|
|
print(f'{utterance:<50} topic={topic_pass:<6} action={action_pass:<6} outcome={outcome_pass}')
|
|
"
|
|
```
|
|
|
|
### Understanding Results Fields
|
|
|
|
| Result field | Description |
|
|
|---|---|
|
|
| `testResults[].name` | `topic_assertion`, `action_assertion`, `output_validation` |
|
|
| `testResults[].result` | `PASS`, `FAILURE`, or `ERROR` |
|
|
| `testResults[].score` | Numeric score (0-1) |
|
|
| `testResults[].expectedValue` | What you specified in the YAML |
|
|
| `testResults[].actualValue` | What the agent actually returned |
|
|
| `generatedData.topic` | Actual runtime topic name |
|
|
| `generatedData.actionsSequence` | Stringified list of actions invoked |
|
|
| `generatedData.outcome` | Agent's actual response text |
|
|
|
|
## Phase 4: Fix Loop
|
|
|
|
For each failed test case:
|
|
|
|
1. **Topic assertion failed** -- compare `expectedValue` vs `actualValue`
|
|
- If actual is a hash-suffixed name (e.g. `p_16j...`), see Topic Name Resolution below
|
|
- If actual is wrong subagent, fix the `.agent` file subagent description
|
|
|
|
2. **Action assertion failed** -- check `generatedData.actionsSequence`
|
|
- If action not invoked: fix subagent instructions or action `available when` guard
|
|
- If wrong action: fix action descriptions to disambiguate
|
|
|
|
3. **Outcome validation failed** -- check `generatedData.outcome`
|
|
- Review the agent's actual response against `expectedOutcome`
|
|
- Tighten subagent instructions to guide the response
|
|
|
|
After fixing the `.agent` file, redeploy and re-run:
|
|
|
|
```bash
|
|
# Redeploy agent
|
|
sf agent publish authoring-bundle --json --api-name <AgentApiName> -o <org>
|
|
|
|
# Re-run the same test suite
|
|
sf agent test run --json --api-name <TestSuiteName> --wait 10 --result-format json -o <org>
|
|
```
|
|
|
|
## Topic Name Resolution
|
|
|
|
Topic names in Testing Center may differ from what you see in the `.agent` file:
|
|
|
|
| Subagent type | Name to use in YAML | Example |
|
|
|---|---|---|
|
|
| Standard topics | `localDeveloperName` (short name) | `Escalation`, `Off_Topic` |
|
|
| Custom subagents | Short name from `.agent` file | `home_search`, `warranty_service` |
|
|
| Promoted topics | Full runtime `developerName` with hash suffix | `p_16jPl000000GwEX_Topic_16j8eeef13560aa` |
|
|
|
|
**Discovery workflow** (when subagent names don't match):
|
|
|
|
1. Run the test with best-guess subagent names
|
|
2. Check actual subagents in results: `jq '.result.testCases[].generatedData.topic' /tmp/test_results.json`
|
|
3. Update YAML with actual runtime names
|
|
4. Redeploy with `--force-overwrite` and re-run
|
|
|
|
**Topic hash drift**: Runtime topic `developerName` hash suffix changes after agent republish. Re-run discovery after each publish.
|
|
|
|
## Auto-Generation from .agent File
|
|
|
|
Derive a Testing Center spec from the `.agent` file:
|
|
|
|
1. **One test case per non-entry subagent** -- utterance from subagent description keywords
|
|
2. **One test case per key action** -- utterance that triggers the action's primary use case
|
|
3. **One guardrail test** -- off-topic utterance
|
|
4. **`expectedTopic`** from subagent name in `.agent` file
|
|
5. **`expectedActions`** from action names under `reasoning: actions:` (only `@actions.*`, not `@utils.transition`)
|
|
|
|
### Level 1 vs Level 2 Action Names (CRITICAL)
|
|
|
|
The `.agent` file has two levels of action definitions:
|
|
- **Level 1** (definition): under `subagent > actions:` — defines target, inputs, outputs (e.g. `get_order_status:`)
|
|
- **Level 2** (invocation): under `subagent > reasoning > actions:` — wires actions to the LLM (e.g. `check_order: @actions.get_order_status`)
|
|
|
|
Testing Center reports **Level 2 invocation names** (e.g. `check_order`), NOT Level 1 definition names (e.g. `get_order_status`). Using Level 1 names in `expectedActions` causes action assertions to FAIL even when the agent correctly invokes the action. Always use the Level 2 name from `reasoning: actions:`.
|
|
|
|
```
|
|
# .agent file
|
|
subagent order_support:
|
|
actions:
|
|
get_order_status: # <-- Level 1 (DON'T use this in expectedActions)
|
|
target: "flow://Get_Order_Status"
|
|
reasoning:
|
|
actions:
|
|
check_order: @actions.get_order_status # <-- Level 2 (USE this in expectedActions)
|
|
```
|
|
|
|
```yaml
|
|
# Test spec -- use Level 2 name
|
|
- utterance: "Where is my order?"
|
|
expectedActions: ["check_order"] # CORRECT (Level 2)
|
|
# expectedActions: ["get_order_status"] # WRONG (Level 1)
|
|
```
|
|
|
|
## Known Bugs and Workarounds
|
|
|
|
| Bug | Severity | Workaround |
|
|
|-----|----------|------------|
|
|
| `--use-most-recent` flag on `sf agent test results` is not implemented | Medium | Always use `--job-id` explicitly |
|
|
| Custom evaluations with `isReference: true` (JSONPath) crash results API | Critical | Skip custom evaluations; use `expectedOutcome` instead |
|
|
| `conciseness` metric returns score=0 | Medium | Skip `conciseness`; use `coherence` instead |
|
|
| `instruction_following` metric crashes Testing Center UI | High | Remove from metrics list; use CLI only |
|
|
| `instruction_following` shows FAILURE at score=1 | Low | Ignore PASS/FAILURE label; use numeric `score` |
|
|
| Topic hash drift on agent republish | Medium | Re-run discovery after each publish |
|