2026-04-09 19:34:48 +08:00
/* *
* @ d e s c r i p t i o n S T D M query s e r v i c e for t h e a g e n t f o r c e - o p t i m i z e C l a u d e Code s k i l l .
* Q u e r i e s t h e Session T r a c e D a t a M o d e l ( S T D M ) in D a t a C l o u d to r e t r i e v e
* session t r a c e s , c o n v e r s a t i o n t u r n s , m e s s a g e s , and s t e p s for i s s u e a n a l y s i s .
*
* D e p l o y e d o n c e p e r o r g by t h e a g e n t f o r c e - o p t i m i z e s k i l l ( P h a s e 1 s e t u p ) .
* All public m e t h o d s a c c e p t d a t a S p a c e N a m e s o no D a t a Space is h a r d c o d e d .
*
* M e t h o d s :
* f i n d S e s s i o n s ( d a t a S p a c e N a m e , s t a r t I s o , e n d I s o , m a x R o w s ) → J S O N L i s t < S e s s i o n S u m m a r y >
* f i n d S e s s i o n s ( d a t a S p a c e N a m e , s t a r t I s o , e n d I s o , m a x R o w s , a g e n t N a m e ) → J S O N L i s t < S e s s i o n S u m m a r y >
* g e t C o n v e r s a t i o n D e t a i l s ( d a t a S p a c e N a m e , s e s s i o n I d ) → J S O N C o n v e r s a t i o n D a t a
* g e t M u l t i p l e C o n v e r s a t i o n D e t a i l s ( d a t a S p a c e N a m e , s e s s i o n I d s ) → J S O N L i s t < C o n v e r s a t i o n D a t a >
* g e t L l m S t e p D e t a i l s ( d a t a S p a c e N a m e , s t e p I d s ) → J S O N L i s t < L l m S t e p D e t a i l >
* g e t M o m e n t I n s i g h t s ( d a t a S p a c e N a m e , s e s s i o n I d s ) → J S O N L i s t < S e s s i o n I n s i g h t s >
* g e t A g g r e g a t e d M e t r i c s ( d a t a S p a c e N a m e , s t a r t I s o , e n d I s o , m a x R o w s , a g e n t N a m e ) → J S O N A g g r e g a t e d M e t r i c s
* r u n O b s e r v a b i l i t y Q u e r y ( L i s t < O b s e r v a b i l i t y I n p u t > ) → L i s t < O b s e r v a b i l i t y O u t p u t > ( @ I n v o c a b l e M e t h o d )
* /
public with s h a r i n g class A g e n t f o r c e O p t i m i z e S e r v i c e {
// =========================================================================
// Output wrappers
// =========================================================================
/* * L i g h t w e i g h t s e s s i o n r e c o r d r e t u r n e d b y f i n d S e s s i o n s ( ) . */
public class S e s s i o n S u m m a r y {
public String s e s s i o n _ i d ;
public String s t a r t _ t i m e ;
public String e n d _ t i m e ;
public String c h a n n e l ;
public L o n g d u r a t i o n _ m s ;
/* * H o w t h e s e s s i o n e n d e d : e . g . U S E R _ E N D E D , A G E N T _ E N D E D ( n u l l = i n p r o g r e s s o r n o t r e c o r d e d ) */
public String e n d _ t y p e ;
}
/* * A s i n g l e u s e r / a g e n t m e s s a g e w i t h i n a t u r n . */
public class M e s s a g e D a t a {
public String m e s s a g e _ i d ;
/* * ' I n p u t ' ( u s e r ) o r ' O u t p u t ' ( a g e n t ) — r a w S T D M v a l u e */
public String m e s s a g e _ t y p e ;
public String text ;
public String s e n t _ a t ;
}
/* *
* A single i n t e r n a l s t e p w i t h i n a t u r n .
* All i s s u e - d e t e c t i o n fields a r e i n c l u d e d :
* - error → n o n - n u l l m e a n s A C T I O N _ S T E P f a i l u r e ( P 1 )
* - p r e _ v a r s / p o s t _ v a r s → null d e l t a m e a n s variable not c a p t u r e d ( P 2 )
* - d u r a t i o n _ m s > 10 000 → s l o w a c t i o n ( P 3 )
* - g e n e r a t i o n _ i d → n o n - n u l l on L L M _ S T E P ; use g e t L l m S t e p D e t a i l s ( ) to get t h e prompt
* /
public class S t e p D a t a {
public String s t e p _ i d ;
/* * T O P I C _ S T E P | L L M _ S T E P | A C T I O N _ S T E P | S E S S I O N _ E N D | T R U S T _ G U A R D R A I L S _ S T E P */
public String s t e p _ t y p e ;
public String name ;
public String s t a r t _ t i m e ;
public String e n d _ t i m e ;
public L o n g d u r a t i o n _ m s ;
/* * R a w i n p u t t o t h e s t e p ( J S O N f o r A C T I O N _ S T E P ; P y t h o n d i c t s t r i n g f o r L L M _ S T E P ) */
public String input ;
/* * R a w o u t p u t f r o m t h e s t e p ( J S O N f o r A C T I O N _ S T E P ; P y t h o n d i c t s t r i n g f o r L L M _ S T E P ) */
public String output ;
/* * N o n - n u l l i n d i c a t e s t h e s t e p t h r e w a n e r r o r ( o n l y A C T I O N _ S T E P c o u n t s t o w a r d a c t i o n _ e r r o r _ c o u n t ) */
public String error ;
/* * V a r i a b l e s n a p s h o t b e f o r e t h i s s t e p ( n u l l w h e n N O T _ S E T ) */
public String p r e _ v a r s ;
/* * V a r i a b l e s n a p s h o t a f t e r t h i s s t e p ( n u l l w h e n N O T _ S E T ) */
public String p o s t _ v a r s ;
/* * G e n A i G e n e r a t i o n I D — n o n - n u l l o n L L M _ S T E P ; p a s s t o g e t L l m S t e p D e t a i l s ( ) f o r f u l l p r o m p t / r e s p o n s e */
public String g e n e r a t i o n _ i d ;
/* * G e n A i G a t e w a y R e q u e s t I D — n o n - n u l l o n L L M _ S T E P ; l i n k s t o r a w g a t e w a y r e q u e s t */
public String g a t e w a y _ r e q u e s t _ i d ;
}
/* *
* O n e c o n v e r s a t i o n a l t u r n ( A i A g e n t I n t e r a c t i o n of type T U R N ) .
* Contains all m e s s a g e s and s t e p s for t h a t t u r n .
* /
public class T u r n D a t a {
public String i n t e r a c t i o n _ i d ;
chore: rename topic to subagent for Agent Script v2 @W-21955450@ (#193)
* @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>
2026-04-28 02:42:18 +08:00
/* * S u b a g e n t A P I n a m e ( s t o r e d a s ` t o p i c ` i n S T D M ) — n u l l / m i s m a t c h s i g n a l s a m i s r o u t e ( P 1 ) */
2026-04-09 19:34:48 +08:00
public String topic ;
public String s t a r t _ t i m e ;
public String e n d _ t i m e ;
public L o n g d u r a t i o n _ m s ;
/* * T e l e m e t r y t r a c e I D f o r d i s t r i b u t e d t r a c i n g c o r r e l a t i o n */
public String t e l e m e t r y _ t r a c e _ i d ;
public L i s t < M e s s a g e D a t a > m e s s a g e s ;
public L i s t < S t e p D a t a > s t e p s ;
public T u r n D a t a ( ) {
m e s s a g e s = new L i s t < M e s s a g e D a t a > ( ) ;
s t e p s = new L i s t < S t e p D a t a > ( ) ;
}
}
/* *
* F u l l c o n v e r s a t i o n for o n e session : session header + o r d e r e d t u r n s .
* t u r n _ c o u n t and a c t i o n _ e r r o r _ c o u n t a r e p r e - c o m p u t e d for q u i c k t r i a g e .
* /
public class C o n v e r s a t i o n D a t a {
public String s e s s i o n _ i d ;
public String s t a r t _ t i m e ;
public String e n d _ t i m e ;
public String c h a n n e l ;
public L o n g d u r a t i o n _ m s ;
/* * H o w t h e s e s s i o n e n d e d ( n u l l = i n p r o g r e s s o r n o t r e c o r d e d b y D a t a C l o u d ) */
public String e n d _ t y p e ;
/* * S e s s i o n - l e v e l v a r i a b l e s n a p s h o t f r o m s s o t _ _ V a r i a b l e T e x t _ _ c ( n u l l w h e n a b s e n t ) */
public String s e s s i o n _ v a r i a b l e s ;
public Integer t u r n _ c o u n t = 0 ;
public Integer a c t i o n _ e r r o r _ c o u n t = 0 ;
public L i s t < T u r n D a t a > t u r n s ;
public C o n v e r s a t i o n D a t a ( ) {
t u r n s = new L i s t < T u r n D a t a > ( ) ;
}
}
/* *
* L L M s t e p d e t a i l r e t r i e v e d from E i n s t e i n A u d i t & Feedback DMOs.
* O b t a i n e d by j o i n i n g A i A g e n t I n t e r a c t i o n S t e p with G e n A I G e n e r a t i o n and G e n A I G a t e w a y R e q u e s t .
* /
public class L l m S t e p D e t a i l {
public String s t e p _ i d ;
public String i n t e r a c t i o n _ i d ;
public String s t e p _ n a m e ;
/* * F u l l p r o m p t t e x t f r o m G e n A I G a t e w a y R e q u e s t _ _ d l m . p r o m p t _ _ c */
public String prompt ;
/* * L L M r e s p o n s e t e x t f r o m G e n A I G e n e r a t i o n _ _ d l m . r e s p o n s e T e x t _ _ c */
public String l l m _ r e s p o n s e ;
public String g e n e r a t i o n _ i d ;
public String g a t e w a y _ r e q u e s t _ i d ;
}
/* * A s i n g l e i n t e n t m o m e n t w i t h i n a s e s s i o n ( f r o m A i A g e n t M o m e n t D M O ) . */
public class M o m e n t D a t a {
public String m o m e n t _ i d ;
public String s e s s i o n _ i d ;
public String s t a r t _ t i m e ;
public String e n d _ t i m e ;
public L o n g d u r a t i o n _ m s ;
public String r e q u e s t _ s u m m a r y ;
public String r e s p o n s e _ s u m m a r y ;
public String a g e n t _ a p i _ n a m e ;
public String a g e n t _ v e r s i o n ;
/* * Q u a l i t y s c o r e 1 - 5 f r o m A i A g e n t T a g A s s o c i a t i o n → A i A g e n t T a g . V a l u e */
public Integer q u a l i t y _ s c o r e ;
/* * L L M - g e n e r a t e d r e a s o n i n g f o r t h e q u a l i t y s c o r e */
public String q u a l i t y _ r e a s o n i n g ;
public M o m e n t D a t a ( ) { }
}
/* * R A G q u a l i t y m e t r i c s f r o m t h e A i R e t r i e v e r Q u a l i t y M e t r i c D M O . */
public class R e t r i e v e r M e t r i c D a t a {
public String m e t r i c _ i d ;
public String g a t e w a y _ r e q u e s t _ i d ;
public String r e t r i e v e r _ r e q u e s t _ i d ;
public String r e t r i e v e r _ a p i _ n a m e ;
public String u s e r _ u t t e r a n c e ;
public Decimal f a i t h f u l n e s s ;
public Decimal a n s w e r _ r e l e v a n c e ;
public Decimal c o n t e x t _ p r e c i s i o n ;
}
/* * P e r - s e s s i o n i n s i g h t s r o l l u p ( m o m e n t s + r e t r i e v e r m e t r i c s ) . */
public class S e s s i o n I n s i g h t s {
public String s e s s i o n _ i d ;
public String s t a r t _ t i m e ;
public String e n d _ t i m e ;
public String e n d _ t y p e ;
public L o n g d u r a t i o n _ m s ;
public Integer t u r n _ c o u n t ;
public Integer m o m e n t _ c o u n t ;
public Decimal a v g _ q u a l i t y _ s c o r e ;
public Integer a c t i o n _ e r r o r _ c o u n t ;
public L i s t < M o m e n t D a t a > m o m e n t s ;
public L i s t < R e t r i e v e r M e t r i c D a t a > r e t r i e v e r _ m e t r i c s ;
public String d e b u g _ m e s s a g e ;
public S e s s i o n I n s i g h t s ( ) {
m o m e n t s = new L i s t < M o m e n t D a t a > ( ) ;
r e t r i e v e r _ m e t r i c s = new L i s t < R e t r i e v e r M e t r i c D a t a > ( ) ;
}
}
/* * A g g r e g a t e d m e t r i c s a c r o s s m u l t i p l e s e s s i o n s . */
public class A g g r e g a t e d M e t r i c s {
public Integer t o t a l _ s e s s i o n s ;
public Integer t o t a l _ m o m e n t s ;
public Integer t o t a l _ t u r n s ;
public Decimal a v g _ q u a l i t y _ s c o r e ;
public Decimal a v g _ s e s s i o n _ d u r a t i o n _ s e c ;
public Map < String , Integer > e n d _ t y p e _ c o u n t s ;
public Map < String , Integer > t o p _ i n t e n t s ;
public Map < String , Integer > q u a l i t y _ d i s t r i b u t i o n ;
public Decimal a b a n d o n m e n t _ r a t e ;
public Decimal e s c a l a t i o n _ r a t e ;
public Decimal d e f l e c t i o n _ r a t e ;
public Decimal a v g _ f a i t h f u l n e s s ;
public Decimal a v g _ a n s w e r _ r e l e v a n c e ;
public Decimal a v g _ c o n t e x t _ p r e c i s i o n ;
public L i s t < String > u n a v a i l a b l e _ d m o s ;
}
// =========================================================================
// Public API
// =========================================================================
/* *
* Find r e c e n t s e s s i o n s w i t h i n a date r a n g e , o p t i o n a l l y f i l t e r e d to a s p e c i f i c a g e n t .
*
* A g e n t f i l t e r i n g t r i e s t w o s t r a t e g i e s in o r d e r :
* 1 . D i r e c t : s s o t _ _ A i A g e n t A p i N a m e _ _ c = a g e n t A p i N a m e on t h e p a r t i c i p a n t D M O ( no S O Q L n e e d e d )
* 2 . F a l l b a c k : G e n A i P l a n n e r D e f i n i t i o n ( S O Q L ) → s s o t _ _ P a r t i c i p a n t I d _ _ c IN p l a n n e r I d s
* B o t h 15 - c h a r and 18 - c h a r I D f o r m a t s a r e i n c l u d e d to handle D M O i n c o n s i s t e n c y .
*
* If b o t h s t r a t e g i e s return empty , t h e query f a l l s back to all s e s s i o n s .
*
* @ param d a t a S p a c e N a m e D a t a C l o u d D a t a Space A P I name ( d i s c o v e r e d in P h a s e 0 )
* @ param s t a r t I s o I S O 8601 U T C start , e . g . '2025-03-01T00:00:00.000Z'
* @ param e n d I s o I S O 8601 U T C end
* @ param m a x R o w s Maximum s e s s i o n s to return ( e . g . 20 )
* @ param a g e n t A p i N a m e A g e n t display name / M a s t e r L a b e l to f i l t e r by ( null = all a g e n t s )
* @ return J S O N - s e r i a l i z e d L i s t < S e s s i o n S u m m a r y >
* /
public static String f i n d S e s s i o n s ( String d a t a S p a c e N a m e , String s t a r t I s o , String e n d I s o , Integer m a x R o w s , String a g e n t A p i N a m e ) {
// Step 1: Find sessions that have actual TURN interactions (skip empty preview sessions).
// Empty sessions (sf agent preview, builder pings) create AiAgentSession + SESSION_END
// interaction records but never TURN records. Querying for TURN directly ensures we only
// return sessions with real conversation data.
String t u r n S e s s i o n S q l =
'SELECT DISTINCT ssot__AiAgentSessionId__c '
+ ' FROM "ssot__AiAgentInteraction__dlm" '
+ 'WHERE ssot__AiAgentInteractionType__c = \'TURN\' '
+ ' AND ssot__StartTimestamp__c >= \'' + startIso + '\' '
+ ' AND ssot__StartTimestamp__c <= \'' + endIso + '\'' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 t u r n R e s u l t = r u n Q u e r y ( t u r n S e s s i o n S q l , d a t a S p a c e N a m e ) ;
Set < String > s e s s i o n s W i t h T u r n s = new Set < String > ( ) ;
if ( t u r n R e s u l t ! = null && turnResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : t u r n R e s u l t . d a t a ) {
String s i d = col ( row . r o w D a t a , 0 ) ;
if ( s i d ! = null ) s e s s i o n s W i t h T u r n s . add ( s i d ) ;
}
}
S y s t e m . debug ( L o g g i n g L e v e l . DEBUG , 'Sessions with turns in date range: ' + s e s s i o n s W i t h T u r n s . size ( ) ) ;
// Step 2: Build agent filter (optional)
String s e s s i o n F i l t e r = '' ;
if ( String . i s N o t B l a n k ( a g e n t A p i N a m e ) ) {
// Strategy 1: filter directly by ssot__AiAgentApiName__c (simplest, no SOQL)
String p a r t S q l B y N a m e =
'SELECT ssot__AiAgentSessionId__c '
+ ' FROM "ssot__AiAgentSessionParticipant__dlm" '
+ 'WHERE ssot__AiAgentApiName__c = \'' + String.escapeSingleQuotes(agentApiName) + '\'' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 n a m e R e s u l t = r u n Q u e r y ( p a r t S q l B y N a m e , d a t a S p a c e N a m e ) ;
L i s t < String > s e s s i o n I d s = e x t r a c t S e s s i o n I d s ( n a m e R e s u l t ) ;
if ( ! s e s s i o n I d s . i s E m p t y ( ) ) {
s e s s i o n F i l t e r = ' AND ssot__Id__c IN (\'' + String.join(sessionIds, '\',\'') + '\') ' ;
S y s t e m . debug ( L o g g i n g L e v e l . DEBUG , 'Agent filter (AiAgentApiName): '
+ s e s s i o n I d s . size ( ) + ' session(s) for agent: ' + a g e n t A p i N a m e ) ;
} else {
// Strategy 2: GenAiPlannerDefinition SOQL → ssot__ParticipantId__c
S y s t e m . debug ( L o g g i n g L e v e l . DEBUG ,
'AiAgentApiName filter returned no sessions; trying GenAiPlannerDefinition fallback' ) ;
L i s t < String > p l a n n e r I d s = r e s o l v e P l a n n e r I d s ( a g e n t A p i N a m e ) ;
if ( ! p l a n n e r I d s . i s E m p t y ( ) ) {
String p I n C l a u s e = '(\'' + String.join(plannerIds, '\',\'') + '\')' ;
String p a r t S q l B y I d =
'SELECT ssot__AiAgentSessionId__c '
+ ' FROM "ssot__AiAgentSessionParticipant__dlm" '
+ 'WHERE ssot__ParticipantId__c IN ' + p I n C l a u s e ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 i d R e s u l t = r u n Q u e r y ( p a r t S q l B y I d , d a t a S p a c e N a m e ) ;
L i s t < String > s e s s i o n I d s 2 = e x t r a c t S e s s i o n I d s ( i d R e s u l t ) ;
if ( ! s e s s i o n I d s 2 . i s E m p t y ( ) ) {
s e s s i o n F i l t e r = ' AND ssot__Id__c IN (\'' + String.join(sessionIds2, '\',\'') + '\') ' ;
S y s t e m . debug ( L o g g i n g L e v e l . DEBUG , 'Agent filter (PlannerIds): '
+ p l a n n e r I d s . size ( ) + ' planner(s), ' + sessionIds2.size() + ' session(s)' ) ;
} else {
S y s t e m . debug ( L o g g i n g L e v e l . W A R N ,
'No sessions found for agent: ' + agentApiName + ' — returning all sessions' ) ;
}
} else {
S y s t e m . debug ( L o g g i n g L e v e l . W A R N ,
'Agent not found: ' + agentApiName + ' — returning sessions for all agents' ) ;
}
}
}
// Step 3: Query sessions, preferring those with actual turns
String t u r n F i l t e r = '' ;
if ( ! s e s s i o n s W i t h T u r n s . i s E m p t y ( ) ) {
L i s t < String > t u r n L i s t = new L i s t < String > ( s e s s i o n s W i t h T u r n s ) ;
t u r n F i l t e r = ' AND ssot__Id__c IN (\'' + String.join(turnList, '\',\'') + '\') ' ;
}
String sql =
'SELECT ssot__Id__c, ssot__StartTimestamp__c, ssot__EndTimestamp__c, '
+ ' ssot__AiAgentChannelType__c, ssot__AiAgentSessionEndType__c '
+ ' FROM "ssot__AiAgentSession__dlm" '
+ 'WHERE ssot__StartTimestamp__c >= \'' + startIso + '\' '
+ ' AND ssot__StartTimestamp__c <= \'' + endIso + '\' '
+ s e s s i o n F i l t e r
+ t u r n F i l t e r
+ 'ORDER BY ssot__StartTimestamp__c DESC '
+ 'LIMIT ' + m a x R o w s ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 r e s u l t = r u n Q u e r y ( sql , d a t a S p a c e N a m e ) ;
L i s t < S e s s i o n S u m m a r y > s e s s i o n s = new L i s t < S e s s i o n S u m m a r y > ( ) ;
if ( r e s u l t ! = null && result.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : r e s u l t . d a t a ) {
S e s s i o n S u m m a r y s = new S e s s i o n S u m m a r y ( ) ;
s . s e s s i o n _ i d = col ( row . r o w D a t a , 0 ) ;
s . s t a r t _ t i m e = col ( row . r o w D a t a , 1 ) ;
s . e n d _ t i m e = col ( row . r o w D a t a , 2 ) ;
s . c h a n n e l = col ( row . r o w D a t a , 3 ) ;
s . e n d _ t y p e = n o t S e t ( col ( row . r o w D a t a , 4 ) ) ;
s . d u r a t i o n _ m s = d u r a t i o n M s ( s . s t a r t _ t i m e , s . e n d _ t i m e ) ;
s e s s i o n s . add ( s ) ;
}
}
return J S O N . s e r i a l i z e ( s e s s i o n s ) ;
}
/* *
* O v e r l o a d t h a t q u e r i e s s e s s i o n s for all a g e n t s ( no a g e n t f i l t e r ) .
* K e p t for backwards c o m p a t i b i l i t y ; p r e f e r t h e 5 - a r g u m e n t version .
* /
public static String f i n d S e s s i o n s ( String d a t a S p a c e N a m e , String s t a r t I s o , String e n d I s o , Integer m a x R o w s ) {
return f i n d S e s s i o n s ( d a t a S p a c e N a m e , s t a r t I s o , e n d I s o , m a x R o w s , null ) ;
}
/* *
* R e t r i e v e f u l l c o n v e r s a t i o n d e t a i l s for a single session .
* F e t c h e s i n t e r a c t i o n s , m e s s a g e s , and s t e p s ( with error / variable / g e n e r a t i o n fields ) .
*
* @ param d a t a S p a c e N a m e D a t a C l o u d D a t a Space A P I name
* @ param s e s s i o n I d s s o t _ _ I d _ _ c of t h e A i A g e n t S e s s i o n
* @ return J S O N - s e r i a l i z e d C o n v e r s a t i o n D a t a
* /
public static String g e t C o n v e r s a t i o n D e t a i l s ( String d a t a S p a c e N a m e , String s e s s i o n I d ) {
if ( String . i s B l a n k ( s e s s i o n I d ) ) return null ;
C o n v e r s a t i o n D a t a c o n v o = new C o n v e r s a t i o n D a t a ( ) ;
c o n v o . s e s s i o n _ i d = s e s s i o n I d ;
// --- Session header ---
String s e s s i o n S q l =
'SELECT ssot__StartTimestamp__c, ssot__EndTimestamp__c, ssot__AiAgentChannelType__c, '
+ ' ssot__AiAgentSessionEndType__c, ssot__VariableText__c '
+ ' FROM "ssot__AiAgentSession__dlm" '
+ 'WHERE ssot__Id__c = \'' + String.escapeSingleQuotes(sessionId) + '\'' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 s e s s i o n R e s u l t = r u n Q u e r y ( s e s s i o n S q l , d a t a S p a c e N a m e ) ;
if ( s e s s i o n R e s u l t ! = null && sessionResult.data != null && !sessionResult.data.isEmpty()) {
L i s t < O b j e c t > r = s e s s i o n R e s u l t . d a t a [ 0 ] . r o w D a t a ;
c o n v o . s t a r t _ t i m e = col ( r , 0 ) ;
c o n v o . e n d _ t i m e = col ( r , 1 ) ;
c o n v o . c h a n n e l = col ( r , 2 ) ;
c o n v o . e n d _ t y p e = n o t S e t ( col ( r , 3 ) ) ;
c o n v o . s e s s i o n _ v a r i a b l e s = n o t S e t ( col ( r , 4 ) ) ;
c o n v o . d u r a t i o n _ m s = d u r a t i o n M s ( c o n v o . s t a r t _ t i m e , c o n v o . e n d _ t i m e ) ;
}
// --- Interactions (turns) ---
// ssot__TopicApiName__c included for misroute detection
// ssot__TelemetryTraceId__c included for distributed tracing
String i n t e r S q l =
'SELECT ssot__Id__c, ssot__TopicApiName__c, ssot__AiAgentInteractionType__c, '
+ ' ssot__StartTimestamp__c, ssot__EndTimestamp__c, ssot__TelemetryTraceId__c '
+ ' FROM "ssot__AiAgentInteraction__dlm" '
+ 'WHERE ssot__AiAgentSessionId__c = \'' + String.escapeSingleQuotes(sessionId) + '\' '
+ 'ORDER BY ssot__StartTimestamp__c' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 i n t e r R e s u l t = r u n Q u e r y ( i n t e r S q l , d a t a S p a c e N a m e ) ;
if ( i n t e r R e s u l t = = null | | i n t e r R e s u l t . d a t a = = null | | i n t e r R e s u l t . d a t a . i s E m p t y ( ) ) {
return J S O N . s e r i a l i z e ( c o n v o ) ;
}
Map < String , T u r n D a t a > t u r n B y I d = new Map < String , T u r n D a t a > ( ) ;
L i s t < String > t u r n I d s = new L i s t < String > ( ) ;
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : i n t e r R e s u l t . d a t a ) {
String i n t e r T y p e = col ( row . r o w D a t a , 2 ) ;
// SESSION_END is a meta interaction, not a user turn
if ( 'SESSION_END' . e q u a l s I g n o r e C a s e ( i n t e r T y p e ) ) c o n t i n u e ;
T u r n D a t a t = new T u r n D a t a ( ) ;
t . i n t e r a c t i o n _ i d = col ( row . r o w D a t a , 0 ) ;
t . topic = col ( row . r o w D a t a , 1 ) ;
t . s t a r t _ t i m e = col ( row . r o w D a t a , 3 ) ;
t . e n d _ t i m e = col ( row . r o w D a t a , 4 ) ;
t . d u r a t i o n _ m s = d u r a t i o n M s ( t . s t a r t _ t i m e , t . e n d _ t i m e ) ;
t . t e l e m e t r y _ t r a c e _ i d = n o t S e t ( col ( row . r o w D a t a , 5 ) ) ;
t u r n B y I d . put ( t . i n t e r a c t i o n _ i d , t ) ;
t u r n I d s . add ( t . i n t e r a c t i o n _ i d ) ;
c o n v o . t u r n s . add ( t ) ;
}
c o n v o . t u r n _ c o u n t = c o n v o . t u r n s . size ( ) ;
if ( t u r n I d s . i s E m p t y ( ) ) return J S O N . s e r i a l i z e ( c o n v o ) ;
String i n C l a u s e = '(\'' + String.join(turnIds, '\',\'') + '\')' ;
// --- Messages ---
String m s g S q l =
'SELECT ssot__Id__c, ssot__AiAgentInteractionId__c, '
+ ' ssot__AiAgentInteractionMessageType__c, ssot__ContentText__c, '
+ ' ssot__MessageSentTimestamp__c '
+ ' FROM "ssot__AiAgentInteractionMessage__dlm" '
+ 'WHERE ssot__AiAgentInteractionId__c IN ' + inClause + ' '
+ 'ORDER BY ssot__MessageSentTimestamp__c' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 m s g R e s u l t = r u n Q u e r y ( m s g S q l , d a t a S p a c e N a m e ) ;
if ( m s g R e s u l t ! = null && msgResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : m s g R e s u l t . d a t a ) {
T u r n D a t a t = t u r n B y I d . get ( col ( row . r o w D a t a , 1 ) ) ;
if ( t = = null ) c o n t i n u e ;
M e s s a g e D a t a m = new M e s s a g e D a t a ( ) ;
m . m e s s a g e _ i d = col ( row . r o w D a t a , 0 ) ;
m . m e s s a g e _ t y p e = col ( row . r o w D a t a , 2 ) ; // 'Input' or 'Output'
m . text = col ( row . r o w D a t a , 3 ) ;
m . s e n t _ a t = col ( row . r o w D a t a , 4 ) ;
t . m e s s a g e s . add ( m ) ;
}
}
// Infer message types when ssot__AiAgentInteractionMessageType__c is null.
// In STDM, messages within a turn alternate: user Input first, then agent Output.
// If ALL messages in a turn have null type, assign by position (odd=Input, even=Output).
for ( T u r n D a t a t : c o n v o . t u r n s ) {
B o o l e a n a n y N u l l = false ;
B o o l e a n a l l N u l l = true ;
for ( M e s s a g e D a t a m : t . m e s s a g e s ) {
if ( m . m e s s a g e _ t y p e = = null ) { a n y N u l l = t r u e ; }
else { a l l N u l l = f a l s e ; }
}
if ( a n y N u l l && allNull) {
// All null — infer from position: 1st=Input, 2nd=Output, 3rd=Input, ...
for ( Integer i = 0 ; i < t . m e s s a g e s . size ( ) ; i + + ) {
t . m e s s a g e s [ i ] . m e s s a g e _ t y p e = ( M a t h . mod ( i , 2 ) = = 0 ) ? 'Input' : 'Output' ;
}
} else if ( a n y N u l l ) {
// Mixed — fill gaps by inferring the opposite of the nearest known neighbor
for ( Integer i = 0 ; i < t . m e s s a g e s . size ( ) ; i + + ) {
if ( t . m e s s a g e s [ i ] . m e s s a g e _ t y p e = = null ) {
// Look at previous message for context
if ( i > 0 && t.messages[i - 1].message_type != null) {
t . m e s s a g e s [ i ] . m e s s a g e _ t y p e = 'Input' . e q u a l s ( t . m e s s a g e s [ i - 1 ] . m e s s a g e _ t y p e )
? 'Output' : 'Input' ;
} else if ( i = = 0 ) {
t . m e s s a g e s [ i ] . m e s s a g e _ t y p e = 'Input' ; // first message is always user
}
}
}
}
}
// --- Steps ---
// All issue-detection fields are selected:
// ssot__ErrorMessageText__c → Action error (P1)
// ssot__InputValueText__c → Wrong action input (P2)
// ssot__OutputValueText__c → Action output / TRUST_GUARDRAILS adherence dict
// ssot__PreStepVariableText__c → Pre-step variable snapshot (P2)
// ssot__PostStepVariableText__c → Post-step variable snapshot (P2)
// ssot__EndTimestamp__c → Step duration for slow action (P3)
// ssot__GenerationId__c → Links to GenAIGeneration__dlm (LLM audit)
// ssot__GenAiGatewayRequestId__c → Links to GenAIGatewayRequest__dlm (prompt text)
String s t e p S q l =
'SELECT ssot__Id__c, ssot__AiAgentInteractionId__c, '
+ ' ssot__AiAgentInteractionStepType__c, ssot__Name__c, '
+ ' ssot__StartTimestamp__c, ssot__EndTimestamp__c, '
+ ' ssot__InputValueText__c, ssot__OutputValueText__c, '
+ ' ssot__ErrorMessageText__c, '
+ ' ssot__PreStepVariableText__c, ssot__PostStepVariableText__c, '
+ ' ssot__GenerationId__c, ssot__GenAiGatewayRequestId__c '
+ ' FROM "ssot__AiAgentInteractionStep__dlm" '
+ 'WHERE ssot__AiAgentInteractionId__c IN ' + inClause + ' '
+ 'ORDER BY ssot__StartTimestamp__c' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 s t e p R e s u l t = r u n Q u e r y ( s t e p S q l , d a t a S p a c e N a m e ) ;
if ( s t e p R e s u l t ! = null && stepResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : s t e p R e s u l t . d a t a ) {
T u r n D a t a t = t u r n B y I d . get ( col ( row . r o w D a t a , 1 ) ) ;
if ( t = = null ) c o n t i n u e ;
S t e p D a t a s = new S t e p D a t a ( ) ;
s . s t e p _ i d = col ( row . r o w D a t a , 0 ) ;
s . s t e p _ t y p e = col ( row . r o w D a t a , 2 ) ;
s . name = col ( row . r o w D a t a , 3 ) ;
s . s t a r t _ t i m e = col ( row . r o w D a t a , 4 ) ;
s . e n d _ t i m e = col ( row . r o w D a t a , 5 ) ;
s . d u r a t i o n _ m s = d u r a t i o n M s ( s . s t a r t _ t i m e , s . e n d _ t i m e ) ;
s . input = n o t S e t ( col ( row . r o w D a t a , 6 ) ) ;
s . output = n o t S e t ( col ( row . r o w D a t a , 7 ) ) ;
s . error = n o t S e t ( col ( row . r o w D a t a , 8 ) ) ;
s . p r e _ v a r s = n o t S e t ( col ( row . r o w D a t a , 9 ) ) ;
s . p o s t _ v a r s = n o t S e t ( col ( row . r o w D a t a , 10 ) ) ;
s . g e n e r a t i o n _ i d = n o t S e t ( col ( row . r o w D a t a , 11 ) ) ;
s . g a t e w a y _ r e q u e s t _ i d = n o t S e t ( col ( row . r o w D a t a , 12 ) ) ;
if ( s . error ! = null && 'ACTION_STEP'.equalsIgnoreCase(s.step_type)) convo.action_error_count++;
t . s t e p s . add ( s ) ;
}
}
return J S O N . s e r i a l i z e ( c o n v o ) ;
}
/* *
* R e t r i e v e f u l l c o n v e r s a t i o n d e t a i l s for multiple s e s s i o n s .
*
* @ param d a t a S p a c e N a m e D a t a C l o u d D a t a Space A P I name
* @ param s e s s i o n I d s L i s t of s s o t _ _ I d _ _ c values ( k e e p u n d e r 20 to a v o i d C P U l i m i t s )
* @ return J S O N - s e r i a l i z e d L i s t < C o n v e r s a t i o n D a t a >
* /
public static String g e t M u l t i p l e C o n v e r s a t i o n D e t a i l s ( String d a t a S p a c e N a m e , L i s t < String > s e s s i o n I d s ) {
L i s t < C o n v e r s a t i o n D a t a > r e s u l t s = new L i s t < C o n v e r s a t i o n D a t a > ( ) ;
if ( s e s s i o n I d s = = null | | s e s s i o n I d s . i s E m p t y ( ) ) return J S O N . s e r i a l i z e ( r e s u l t s ) ;
for ( String s i d : s e s s i o n I d s ) {
String d e t a i l = g e t C o n v e r s a t i o n D e t a i l s ( d a t a S p a c e N a m e , s i d ) ;
if ( String . i s N o t B l a n k ( d e t a i l ) ) {
C o n v e r s a t i o n D a t a c o n v o = ( C o n v e r s a t i o n D a t a ) J S O N . d e s e r i a l i z e ( d e t a i l , C o n v e r s a t i o n D a t a . class ) ;
r e s u l t s . add ( c o n v o ) ;
}
}
return J S O N . s e r i a l i z e ( r e s u l t s ) ;
}
/* *
* R e t r i e v e L L M prompt and r e s p o n s e for a set of L L M _ S T E P r e c o r d s by j o i n i n g t h e
* E i n s t e i n A u d i t & Feedback DMOs (GenAIGatewayRequest and GenAIGeneration).
*
* T y p i c a l use : a f t e r f i n d i n g L L M _ S T E P r e c o r d s with n o n - n u l l g e n e r a t i o n _ i d in a
* C o n v e r s a t i o n D a t a , p a s s t h o s e s t e p I D s h e r e to s e e w h a t prompt w a s s e n t and w h a t
* t h e m o d e l a c t u a l l y r e t u r n e d — u s e f u l for d i a g n o s i n g L O W i n s t r u c t i o n a d h e r e n c e .
*
* @ param d a t a S p a c e N a m e D a t a C l o u d D a t a Space A P I name
* @ param s t e p I d s L i s t of s s o t _ _ I d _ _ c values from L L M _ S T E P S t e p D a t a r e c o r d s
* @ return J S O N - s e r i a l i z e d L i s t < L l m S t e p D e t a i l >
* /
public static String g e t L l m S t e p D e t a i l s ( String d a t a S p a c e N a m e , L i s t < String > s t e p I d s ) {
if ( s t e p I d s = = null | | s t e p I d s . i s E m p t y ( ) ) return J S O N . s e r i a l i z e ( new L i s t < L l m S t e p D e t a i l > ( ) ) ;
String i n C l a u s e = '(\'' + String.join(stepIds, '\',\'') + '\')' ;
String sql =
'SELECT s.ssot__Id__c, s.ssot__AiAgentInteractionId__c, s.ssot__Name__c, '
+ ' r.prompt__c, g.responseText__c, '
+ ' s.ssot__GenerationId__c, s.ssot__GenAiGatewayRequestId__c '
+ ' FROM "ssot__AiAgentInteractionStep__dlm" s '
+ ' L E F T JOIN "GenAIGeneration__dlm" g '
+ ' ON s.ssot__GenerationId__c = g.generationId__c '
+ ' L E F T JOIN "GenAIGatewayRequest__dlm" r '
+ ' ON s.ssot__GenAiGatewayRequestId__c = r.gatewayRequestId__c '
+ 'WHERE s.ssot__Id__c IN ' + i n C l a u s e ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 r e s u l t = r u n Q u e r y ( sql , d a t a S p a c e N a m e ) ;
L i s t < L l m S t e p D e t a i l > d e t a i l s = new L i s t < L l m S t e p D e t a i l > ( ) ;
if ( r e s u l t ! = null && result.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : r e s u l t . d a t a ) {
L l m S t e p D e t a i l d = new L l m S t e p D e t a i l ( ) ;
d . s t e p _ i d = col ( row . r o w D a t a , 0 ) ;
d . i n t e r a c t i o n _ i d = col ( row . r o w D a t a , 1 ) ;
d . s t e p _ n a m e = col ( row . r o w D a t a , 2 ) ;
d . prompt = n o t S e t ( col ( row . r o w D a t a , 3 ) ) ;
d . l l m _ r e s p o n s e = n o t S e t ( col ( row . r o w D a t a , 4 ) ) ;
d . g e n e r a t i o n _ i d = n o t S e t ( col ( row . r o w D a t a , 5 ) ) ;
d . g a t e w a y _ r e q u e s t _ i d = n o t S e t ( col ( row . r o w D a t a , 6 ) ) ;
d e t a i l s . add ( d ) ;
}
}
return J S O N . s e r i a l i z e ( d e t a i l s ) ;
}
/* *
* R e t r i e v e m o m e n t i n s i g h t s ( i n t e n t s u m m a r i e s , d u r a t i o n s ) and r e t r i e v e r q u a l i t y m e t r i c s
* for a set of s e s s i o n s . G r a c e f u l l y d e g r a d e s if D M O s a r e u n a v a i l a b l e .
*
* @ param d a t a S p a c e N a m e D a t a C l o u d D a t a Space A P I name
* @ param s e s s i o n I d s L i s t of s s o t _ _ I d _ _ c values from A i A g e n t S e s s i o n
* @ return J S O N - s e r i a l i z e d L i s t < S e s s i o n I n s i g h t s >
* /
public static String g e t M o m e n t I n s i g h t s ( String d a t a S p a c e N a m e , L i s t < String > s e s s i o n I d s ) {
L i s t < S e s s i o n I n s i g h t s > r e s u l t s = new L i s t < S e s s i o n I n s i g h t s > ( ) ;
if ( s e s s i o n I d s = = null | | s e s s i o n I d s . i s E m p t y ( ) ) return J S O N . s e r i a l i z e ( r e s u l t s ) ;
String i n C l a u s e = '(\'' + String.join(sessionIds, '\',\'') + '\')' ;
// --- Session headers ---
String s e s s i o n S q l =
'SELECT ssot__Id__c, ssot__StartTimestamp__c, ssot__EndTimestamp__c, '
+ ' ssot__AiAgentSessionEndType__c '
+ ' FROM "ssot__AiAgentSession__dlm" '
+ 'WHERE ssot__Id__c IN ' + i n C l a u s e ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 s e s s i o n R e s u l t = r u n Q u e r y ( s e s s i o n S q l , d a t a S p a c e N a m e ) ;
Map < String , S e s s i o n I n s i g h t s > i n s i g h t s B y I d = new Map < String , S e s s i o n I n s i g h t s > ( ) ;
if ( s e s s i o n R e s u l t ! = null && sessionResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : s e s s i o n R e s u l t . d a t a ) {
S e s s i o n I n s i g h t s s i = new S e s s i o n I n s i g h t s ( ) ;
s i . s e s s i o n _ i d = col ( row . r o w D a t a , 0 ) ;
s i . s t a r t _ t i m e = col ( row . r o w D a t a , 1 ) ;
s i . e n d _ t i m e = col ( row . r o w D a t a , 2 ) ;
s i . e n d _ t y p e = n o t S e t ( col ( row . r o w D a t a , 3 ) ) ;
s i . d u r a t i o n _ m s = d u r a t i o n M s ( s i . s t a r t _ t i m e , s i . e n d _ t i m e ) ;
i n s i g h t s B y I d . put ( s i . s e s s i o n _ i d , s i ) ;
r e s u l t s . add ( s i ) ;
}
}
// --- Turn counts ---
String t u r n S q l =
'SELECT ssot__AiAgentSessionId__c, COUNT(*) '
+ ' FROM "ssot__AiAgentInteraction__dlm" '
+ 'WHERE ssot__AiAgentSessionId__c IN ' + inClause + ' '
+ ' AND ssot__AiAgentInteractionType__c = \'TURN\' '
+ 'GROUP BY ssot__AiAgentSessionId__c' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 t u r n R e s u l t = r u n Q u e r y ( t u r n S q l , d a t a S p a c e N a m e ) ;
if ( t u r n R e s u l t ! = null && turnResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : t u r n R e s u l t . d a t a ) {
S e s s i o n I n s i g h t s s i = i n s i g h t s B y I d . get ( col ( row . r o w D a t a , 0 ) ) ;
if ( s i ! = null ) {
s i . t u r n _ c o u n t = Integer . v a l u e O f ( col ( row . r o w D a t a , 1 ) ) ;
}
}
}
// --- Action error counts ---
String e r r S q l =
'SELECT ssot__AiAgentInteraction__dlm.ssot__AiAgentSessionId__c, COUNT(*) '
+ ' FROM "ssot__AiAgentInteractionStep__dlm" '
+ ' JOIN "ssot__AiAgentInteraction__dlm" '
+ ' ON ssot__AiAgentInteractionStep__dlm.ssot__AiAgentInteractionId__c = ssot__AiAgentInteraction__dlm.ssot__Id__c '
+ 'WHERE ssot__AiAgentInteraction__dlm.ssot__AiAgentSessionId__c IN ' + inClause + ' '
+ ' AND ssot__AiAgentInteractionStep__dlm.ssot__AiAgentInteractionStepType__c = \'ACTION_STEP\' '
+ ' AND ssot__AiAgentInteractionStep__dlm.ssot__ErrorMessageText__c IS NOT NULL '
+ ' AND ssot__AiAgentInteractionStep__dlm.ssot__ErrorMessageText__c != \'NOT_SET\' '
+ 'GROUP BY ssot__AiAgentInteraction__dlm.ssot__AiAgentSessionId__c' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 e r r R e s u l t = r u n Q u e r y ( e r r S q l , d a t a S p a c e N a m e ) ;
if ( e r r R e s u l t ! = null && errResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : e r r R e s u l t . d a t a ) {
S e s s i o n I n s i g h t s s i = i n s i g h t s B y I d . get ( col ( row . r o w D a t a , 0 ) ) ;
if ( s i ! = null ) {
s i . a c t i o n _ e r r o r _ c o u n t = Integer . v a l u e O f ( col ( row . r o w D a t a , 1 ) ) ;
}
}
}
// --- Moments (graceful degradation) ---
B o o l e a n m o m e n t A v a i l a b l e = i s D m o A v a i l a b l e ( 'ssot__AiAgentMoment__dlm' , d a t a S p a c e N a m e ) ;
if ( m o m e n t A v a i l a b l e ) {
String m o m e n t S q l =
'SELECT ssot__Id__c, ssot__AiAgentSessionId__c, '
+ ' ssot__StartTimestamp__c, ssot__EndTimestamp__c, '
+ ' ssot__RequestSummaryText__c, ssot__ResponseSummaryText__c, '
+ ' ssot__AiAgentApiName__c, ssot__AiAgentVersionApiName__c '
+ ' FROM "ssot__AiAgentMoment__dlm" '
+ 'WHERE ssot__AiAgentSessionId__c IN ' + inClause + ' '
+ 'ORDER BY ssot__StartTimestamp__c' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 m o m e n t R e s u l t = r u n Q u e r y ( m o m e n t S q l , d a t a S p a c e N a m e ) ;
if ( m o m e n t R e s u l t ! = null && momentResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : m o m e n t R e s u l t . d a t a ) {
M o m e n t D a t a m = new M o m e n t D a t a ( ) ;
m . m o m e n t _ i d = col ( row . r o w D a t a , 0 ) ;
m . s e s s i o n _ i d = col ( row . r o w D a t a , 1 ) ;
m . s t a r t _ t i m e = col ( row . r o w D a t a , 2 ) ;
m . e n d _ t i m e = col ( row . r o w D a t a , 3 ) ;
m . d u r a t i o n _ m s = d u r a t i o n M s ( m . s t a r t _ t i m e , m . e n d _ t i m e ) ;
m . r e q u e s t _ s u m m a r y = n o t S e t ( col ( row . r o w D a t a , 4 ) ) ;
m . r e s p o n s e _ s u m m a r y = n o t S e t ( col ( row . r o w D a t a , 5 ) ) ;
m . a g e n t _ a p i _ n a m e = n o t S e t ( col ( row . r o w D a t a , 6 ) ) ;
m . a g e n t _ v e r s i o n = n o t S e t ( col ( row . r o w D a t a , 7 ) ) ;
S e s s i o n I n s i g h t s s i = i n s i g h t s B y I d . get ( m . s e s s i o n _ i d ) ;
if ( s i ! = null ) s i . m o m e n t s . add ( m ) ;
}
}
// --- Quality scores via AiAgentTagAssociation → AiAgentTag ---
if ( i s D m o A v a i l a b l e ( 'ssot__AiAgentTagAssociation__dlm' , d a t a S p a c e N a m e ) ) {
// Collect all moment IDs for the quality score query
L i s t < String > m o m e n t I d s = new L i s t < String > ( ) ;
Map < String , M o m e n t D a t a > m o m e n t B y I d = new Map < String , M o m e n t D a t a > ( ) ;
for ( S e s s i o n I n s i g h t s s i : r e s u l t s ) {
for ( M o m e n t D a t a m : s i . m o m e n t s ) {
m o m e n t I d s . add ( m . m o m e n t _ i d ) ;
m o m e n t B y I d . put ( m . m o m e n t _ i d , m ) ;
}
}
if ( ! m o m e n t I d s . i s E m p t y ( ) ) {
String m o m e n t I n C l a u s e = '(\'' + String.join(momentIds, '\',\'') + '\')' ;
String q u a l i t y S q l =
'SELECT ta.ssot__AiAgentMomentId__c, t.ssot__Value__c, '
+ ' ta.ssot__AssociationReasonText__c '
+ ' FROM "ssot__AiAgentTagAssociation__dlm" t a '
+ ' JOIN "ssot__AiAgentTag__dlm" t '
+ ' ON ta.ssot__AiAgentTagId__c = t.ssot__Id__c '
+ 'WHERE ta.ssot__AiAgentMomentId__c IN ' + m o m e n t I n C l a u s e ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 q u a l i t y R e s u l t = r u n Q u e r y ( q u a l i t y S q l , d a t a S p a c e N a m e ) ;
if ( q u a l i t y R e s u l t ! = null && qualityResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : q u a l i t y R e s u l t . d a t a ) {
String m o m e n t I d = col ( row . r o w D a t a , 0 ) ;
M o m e n t D a t a m = m o m e n t B y I d . get ( m o m e n t I d ) ;
if ( m ! = null ) {
m . q u a l i t y _ s c o r e = t o I n t e g e r ( col ( row . r o w D a t a , 1 ) ) ;
m . q u a l i t y _ r e a s o n i n g = n o t S e t ( col ( row . r o w D a t a , 2 ) ) ;
}
}
}
}
}
} else {
for ( S e s s i o n I n s i g h t s s i : r e s u l t s ) {
s i . d e b u g _ m e s s a g e = 'AiAgentMoment DMO not available in this org' ;
}
}
// Set moment_count and avg_quality_score per session
for ( S e s s i o n I n s i g h t s s i : r e s u l t s ) {
s i . m o m e n t _ c o u n t = s i . m o m e n t s . size ( ) ;
Integer s c o r e S u m = 0 ;
Integer s c o r e C o u n t = 0 ;
for ( M o m e n t D a t a m : s i . m o m e n t s ) {
if ( m . q u a l i t y _ s c o r e ! = null ) {
s c o r e S u m + = m . q u a l i t y _ s c o r e ;
s c o r e C o u n t + + ;
}
}
if ( s c o r e C o u n t > 0 ) {
s i . a v g _ q u a l i t y _ s c o r e = Decimal . v a l u e O f ( s c o r e S u m ) / s c o r e C o u n t ;
}
}
// --- Retriever quality metrics (graceful degradation) ---
B o o l e a n r e t r i e v e r A v a i l a b l e = i s D m o A v a i l a b l e ( 'ssot__AiRetrieverQualityMetric__dlm' , d a t a S p a c e N a m e ) ;
if ( r e t r i e v e r A v a i l a b l e ) {
// Retriever metrics link to sessions via gateway request IDs on LLM steps.
// Query all retriever metrics for gateway requests that belong to these sessions.
String r e t r i e v e r S q l =
'SELECT r.ssot__Id__c, r.ssot__AiGatewayRequestId__c, '
+ ' r.ssot__AiRetrieverRequestId__c, r.ssot__RetrieverApiName__c, '
+ ' r.ssot__UserUtteranceText__c, '
+ ' r.ssot__FaithfulnessRelevancyScoreNumber__c, '
+ ' r.ssot__AnswerRelevancyScoreNumber__c, '
+ ' r.ssot__ContextPrecisionScoreNumber__c, '
+ ' i.ssot__AiAgentSessionId__c '
+ ' FROM "ssot__AiRetrieverQualityMetric__dlm" r '
+ ' JOIN "ssot__AiAgentInteractionStep__dlm" s '
+ ' ON r.ssot__AiGatewayRequestId__c = s.ssot__GenAiGatewayRequestId__c '
+ ' JOIN "ssot__AiAgentInteraction__dlm" i '
+ ' ON s.ssot__AiAgentInteractionId__c = i.ssot__Id__c '
+ 'WHERE i.ssot__AiAgentSessionId__c IN ' + i n C l a u s e ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 r e t R e s u l t = r u n Q u e r y ( r e t r i e v e r S q l , d a t a S p a c e N a m e ) ;
if ( r e t R e s u l t ! = null && retResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : r e t R e s u l t . d a t a ) {
R e t r i e v e r M e t r i c D a t a r m = new R e t r i e v e r M e t r i c D a t a ( ) ;
r m . m e t r i c _ i d = col ( row . r o w D a t a , 0 ) ;
r m . g a t e w a y _ r e q u e s t _ i d = col ( row . r o w D a t a , 1 ) ;
r m . r e t r i e v e r _ r e q u e s t _ i d = col ( row . r o w D a t a , 2 ) ;
r m . r e t r i e v e r _ a p i _ n a m e = n o t S e t ( col ( row . r o w D a t a , 3 ) ) ;
r m . u s e r _ u t t e r a n c e = n o t S e t ( col ( row . r o w D a t a , 4 ) ) ;
r m . f a i t h f u l n e s s = t o D e c i m a l ( col ( row . r o w D a t a , 5 ) ) ;
r m . a n s w e r _ r e l e v a n c e = t o D e c i m a l ( col ( row . r o w D a t a , 6 ) ) ;
r m . c o n t e x t _ p r e c i s i o n = t o D e c i m a l ( col ( row . r o w D a t a , 7 ) ) ;
String s e s s i o n I d = col ( row . r o w D a t a , 8 ) ;
S e s s i o n I n s i g h t s s i = i n s i g h t s B y I d . get ( s e s s i o n I d ) ;
if ( s i ! = null ) s i . r e t r i e v e r _ m e t r i c s . add ( r m ) ;
}
}
}
return J S O N . s e r i a l i z e ( r e s u l t s ) ;
}
/* *
* C o m p u t e a g g r e g a t e d m e t r i c s a c r o s s s e s s i o n s in a date r a n g e .
* I n c l u d e s session r a t e s ( a b a n d o n m e n t , e s c a l a t i o n , d e f l e c t i o n ) , t o p i n t e n t s from m o m e n t s ,
* and average R A G q u a l i t y s c o r e s . G r a c e f u l l y d e g r a d e s when D M O s a r e u n a v a i l a b l e .
*
* @ param d a t a S p a c e N a m e D a t a C l o u d D a t a Space A P I name
* @ param s t a r t I s o I S O 8601 U T C start t i m e s t a m p
* @ param e n d I s o I S O 8601 U T C end t i m e s t a m p
* @ param m a x R o w s Maximum s e s s i o n s to a g g r e g a t e o v e r
* @ param a g e n t A p i N a m e A g e n t M a s t e r L a b e l to f i l t e r by ( null = all a g e n t s )
* @ return J S O N - s e r i a l i z e d A g g r e g a t e d M e t r i c s
* /
public static String g e t A g g r e g a t e d M e t r i c s ( String d a t a S p a c e N a m e , String s t a r t I s o , String e n d I s o , Integer m a x R o w s , String a g e n t A p i N a m e ) {
A g g r e g a t e d M e t r i c s m e t r i c s = new A g g r e g a t e d M e t r i c s ( ) ;
m e t r i c s . e n d _ t y p e _ c o u n t s = new Map < String , Integer > ( ) ;
m e t r i c s . t o p _ i n t e n t s = new Map < String , Integer > ( ) ;
m e t r i c s . u n a v a i l a b l e _ d m o s = new L i s t < String > ( ) ;
// Step 1: Find sessions (reuse existing method)
String s e s s i o n s J s o n = f i n d S e s s i o n s ( d a t a S p a c e N a m e , s t a r t I s o , e n d I s o , m a x R o w s , a g e n t A p i N a m e ) ;
L i s t < S e s s i o n S u m m a r y > s e s s i o n s = ( L i s t < S e s s i o n S u m m a r y > ) J S O N . d e s e r i a l i z e ( s e s s i o n s J s o n , L i s t < S e s s i o n S u m m a r y > . class ) ;
m e t r i c s . t o t a l _ s e s s i o n s = s e s s i o n s . size ( ) ;
if ( s e s s i o n s . i s E m p t y ( ) ) return J S O N . s e r i a l i z e ( m e t r i c s ) ;
// Compute session-level aggregates
Decimal t o t a l D u r a t i o n S e c = 0 ;
Integer d u r a t i o n C o u n t = 0 ;
for ( S e s s i o n S u m m a r y s : s e s s i o n s ) {
// End type distribution
String e n d T y p e = s . e n d _ t y p e ! = null ? s . e n d _ t y p e : 'UNKNOWN' ;
Integer c n t = m e t r i c s . e n d _ t y p e _ c o u n t s . get ( e n d T y p e ) ;
m e t r i c s . e n d _ t y p e _ c o u n t s . put ( e n d T y p e , c n t ! = null ? c n t + 1 : 1 ) ;
// Duration
if ( s . d u r a t i o n _ m s ! = null ) {
t o t a l D u r a t i o n S e c + = Decimal . v a l u e O f ( s . d u r a t i o n _ m s ) / 1000 ;
d u r a t i o n C o u n t + + ;
}
}
if ( d u r a t i o n C o u n t > 0 ) {
m e t r i c s . a v g _ s e s s i o n _ d u r a t i o n _ s e c = t o t a l D u r a t i o n S e c / d u r a t i o n C o u n t ;
}
// Session rates
Integer u s e r E n d e d = m e t r i c s . e n d _ t y p e _ c o u n t s . get ( 'USER_ENDED' ) ;
Integer a g e n t E n d e d = m e t r i c s . e n d _ t y p e _ c o u n t s . get ( 'AGENT_ENDED' ) ;
Integer e s c a l a t e d = m e t r i c s . e n d _ t y p e _ c o u n t s . get ( 'ESCALATED' ) ;
Integer total = m e t r i c s . t o t a l _ s e s s i o n s ;
m e t r i c s . a b a n d o n m e n t _ r a t e = Decimal . v a l u e O f ( u s e r E n d e d ! = null ? u s e r E n d e d : 0 ) / total ;
m e t r i c s . d e f l e c t i o n _ r a t e = Decimal . v a l u e O f ( a g e n t E n d e d ! = null ? a g e n t E n d e d : 0 ) / total ;
m e t r i c s . e s c a l a t i o n _ r a t e = Decimal . v a l u e O f ( e s c a l a t e d ! = null ? e s c a l a t e d : 0 ) / total ;
// Collect session IDs for sub-queries
L i s t < String > s e s s i o n I d s = new L i s t < String > ( ) ;
for ( S e s s i o n S u m m a r y s : s e s s i o n s ) {
s e s s i o n I d s . add ( s . s e s s i o n _ i d ) ;
}
String i n C l a u s e = '(\'' + String.join(sessionIds, '\',\'') + '\')' ;
// Step 2: Total turns
String t u r n S q l =
'SELECT COUNT(*) '
+ ' FROM "ssot__AiAgentInteraction__dlm" '
+ 'WHERE ssot__AiAgentSessionId__c IN ' + inClause + ' '
+ ' AND ssot__AiAgentInteractionType__c = \'TURN\'' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 t u r n R e s u l t = r u n Q u e r y ( t u r n S q l , d a t a S p a c e N a m e ) ;
if ( t u r n R e s u l t ! = null && turnResult.data != null && !turnResult.data.isEmpty()) {
m e t r i c s . t o t a l _ t u r n s = Integer . v a l u e O f ( col ( t u r n R e s u l t . d a t a [ 0 ] . r o w D a t a , 0 ) ) ;
}
// Step 3: Moments — count + top intents
if ( i s D m o A v a i l a b l e ( 'ssot__AiAgentMoment__dlm' , d a t a S p a c e N a m e ) ) {
// Total moment count
String m o m e n t C o u n t S q l =
'SELECT COUNT(*) '
+ ' FROM "ssot__AiAgentMoment__dlm" '
+ 'WHERE ssot__AiAgentSessionId__c IN ' + i n C l a u s e ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 m c R e s u l t = r u n Q u e r y ( m o m e n t C o u n t S q l , d a t a S p a c e N a m e ) ;
if ( m c R e s u l t ! = null && mcResult.data != null && !mcResult.data.isEmpty()) {
m e t r i c s . t o t a l _ m o m e n t s = Integer . v a l u e O f ( col ( m c R e s u l t . d a t a [ 0 ] . r o w D a t a , 0 ) ) ;
}
// Top intents by request summary (GROUP BY truncated summary)
String i n t e n t S q l =
'SELECT ssot__RequestSummaryText__c, COUNT(*) AS cnt '
+ ' FROM "ssot__AiAgentMoment__dlm" '
+ 'WHERE ssot__AiAgentSessionId__c IN ' + inClause + ' '
+ ' AND ssot__RequestSummaryText__c IS NOT NULL '
+ ' AND ssot__RequestSummaryText__c != \'NOT_SET\' '
+ 'GROUP BY ssot__RequestSummaryText__c '
+ 'ORDER BY cnt DESC '
+ 'LIMIT 20' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 i n t e n t R e s u l t = r u n Q u e r y ( i n t e n t S q l , d a t a S p a c e N a m e ) ;
if ( i n t e n t R e s u l t ! = null && intentResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : i n t e n t R e s u l t . d a t a ) {
String i n t e n t = col ( row . r o w D a t a , 0 ) ;
Integer i n t e n t C n t = Integer . v a l u e O f ( col ( row . r o w D a t a , 1 ) ) ;
if ( i n t e n t ! = null ) {
// Truncate long summaries for the top_intents map key
if ( i n t e n t . length ( ) > 100 ) i n t e n t = i n t e n t . substring ( 0 , 100 ) + '...' ;
m e t r i c s . t o p _ i n t e n t s . put ( i n t e n t , i n t e n t C n t ) ;
}
}
}
} else {
m e t r i c s . u n a v a i l a b l e _ d m o s . add ( 'ssot__AiAgentMoment__dlm' ) ;
}
// Step 3b: Quality scores via AiAgentTagAssociation → AiAgentTag
m e t r i c s . q u a l i t y _ d i s t r i b u t i o n = new Map < String , Integer > ( ) ;
if ( i s D m o A v a i l a b l e ( 'ssot__AiAgentTagAssociation__dlm' , d a t a S p a c e N a m e ) ) {
// AVG quality score and distribution across all moments in these sessions
String q u a l i t y A v g S q l =
'SELECT t.ssot__Value__c, COUNT(*) AS cnt '
+ ' FROM "ssot__AiAgentTagAssociation__dlm" t a '
+ ' JOIN "ssot__AiAgentTag__dlm" t '
+ ' ON ta.ssot__AiAgentTagId__c = t.ssot__Id__c '
+ 'WHERE ta.ssot__AiAgentSessionId__c IN ' + inClause + ' '
+ 'GROUP BY t.ssot__Value__c '
+ 'ORDER BY t.ssot__Value__c' ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 q u a l i t y R e s u l t = r u n Q u e r y ( q u a l i t y A v g S q l , d a t a S p a c e N a m e ) ;
if ( q u a l i t y R e s u l t ! = null && qualityResult.data != null) {
Integer t o t a l S c o r e = 0 ;
Integer t o t a l C o u n t = 0 ;
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : q u a l i t y R e s u l t . d a t a ) {
String s c o r e S t r = col ( row . r o w D a t a , 0 ) ;
Integer c n t = Integer . v a l u e O f ( col ( row . r o w D a t a , 1 ) ) ;
if ( s c o r e S t r ! = null && cnt != null) {
Integer s c o r e = t o I n t e g e r ( s c o r e S t r ) ;
m e t r i c s . q u a l i t y _ d i s t r i b u t i o n . put ( String . v a l u e O f ( s c o r e ) , c n t ) ;
if ( s c o r e ! = null ) {
t o t a l S c o r e + = s c o r e * c n t ;
t o t a l C o u n t + = c n t ;
}
}
}
if ( t o t a l C o u n t > 0 ) {
m e t r i c s . a v g _ q u a l i t y _ s c o r e = Decimal . v a l u e O f ( t o t a l S c o r e ) / t o t a l C o u n t ;
}
}
} else {
m e t r i c s . u n a v a i l a b l e _ d m o s . add ( 'ssot__AiAgentTagAssociation__dlm' ) ;
}
// Step 4: Retriever quality averages
if ( i s D m o A v a i l a b l e ( 'ssot__AiRetrieverQualityMetric__dlm' , d a t a S p a c e N a m e ) ) {
String r e t S q l =
'SELECT AVG(r.ssot__FaithfulnessRelevancyScoreNumber__c), '
+ ' AVG(r.ssot__AnswerRelevancyScoreNumber__c), '
+ ' AVG(r.ssot__ContextPrecisionScoreNumber__c) '
+ ' FROM "ssot__AiRetrieverQualityMetric__dlm" r '
+ ' JOIN "ssot__AiAgentInteractionStep__dlm" s '
+ ' ON r.ssot__AiGatewayRequestId__c = s.ssot__GenAiGatewayRequestId__c '
+ ' JOIN "ssot__AiAgentInteraction__dlm" i '
+ ' ON s.ssot__AiAgentInteractionId__c = i.ssot__Id__c '
+ 'WHERE i.ssot__AiAgentSessionId__c IN ' + i n C l a u s e ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 r e t R e s u l t = r u n Q u e r y ( r e t S q l , d a t a S p a c e N a m e ) ;
if ( r e t R e s u l t ! = null && retResult.data != null && !retResult.data.isEmpty()) {
m e t r i c s . a v g _ f a i t h f u l n e s s = t o D e c i m a l ( col ( r e t R e s u l t . d a t a [ 0 ] . r o w D a t a , 0 ) ) ;
m e t r i c s . a v g _ a n s w e r _ r e l e v a n c e = t o D e c i m a l ( col ( r e t R e s u l t . d a t a [ 0 ] . r o w D a t a , 1 ) ) ;
m e t r i c s . a v g _ c o n t e x t _ p r e c i s i o n = t o D e c i m a l ( col ( r e t R e s u l t . d a t a [ 0 ] . r o w D a t a , 2 ) ) ;
}
} else {
m e t r i c s . u n a v a i l a b l e _ d m o s . add ( 'ssot__AiRetrieverQualityMetric__dlm' ) ;
}
return J S O N . s e r i a l i z e ( m e t r i c s ) ;
}
// =========================================================================
// Private helpers
// =========================================================================
/* * C a c h e f o r D M O a v a i l a b i l i t y p r o b e s t o a v o i d r e p e a t e d q u e r i e s . */
private static Map < String , B o o l e a n > d m o A v a i l a b i l i t y C a c h e = new Map < String , B o o l e a n > ( ) ;
/* *
* P r o b e w h e t h e r a D M O exists and is q u e r y a b l e in t h i s o r g ' s D a t a C l o u d .
* R e s u l t s a r e c a c h e d for t h e transaction to a v o i d r e p e a t e d p r o b e s .
* /
private static B o o l e a n i s D m o A v a i l a b l e ( String d m o N a m e , String d a t a S p a c e N a m e ) {
if ( d m o A v a i l a b i l i t y C a c h e . c o n t a i n s K e y ( d m o N a m e ) ) return d m o A v a i l a b i l i t y C a c h e . get ( d m o N a m e ) ;
C o n n e c t A p i . C d p Q u e r y I n p u t i n p = new C o n n e c t A p i . C d p Q u e r y I n p u t ( ) ;
i n p . sql = ' SELECT COUNT ( * ) FROM "' + dmoName + '" ' ;
t r y {
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 r e s u l t = C o n n e c t A p i . C d p Q u e r y . q u e r y A n s i S q l V 2 ( i n p , d a t a S p a c e N a m e ) ;
d m o A v a i l a b i l i t y C a c h e . put ( d m o N a m e , true ) ;
return true ;
} catch ( E x c e p t i o n e ) {
S y s t e m . debug ( L o g g i n g L e v e l . W A R N , 'DMO not available: ' + dmoName + ' — ' + e . g e t M e s s a g e ( ) ) ;
d m o A v a i l a b i l i t y C a c h e . put ( d m o N a m e , false ) ;
return false ;
}
}
/* * S a f e l y p a r s e a s t r i n g t o D e c i m a l ; r e t u r n s n u l l o n f a i l u r e . */
private static Decimal t o D e c i m a l ( String v a l ) {
if ( String . i s B l a n k ( v a l ) | | v a l = = 'NOT_SET' ) return null ;
t r y {
return Decimal . v a l u e O f ( v a l ) ;
} catch ( E x c e p t i o n e ) {
return null ;
}
}
/* * S a f e l y p a r s e a s t r i n g t o I n t e g e r ( t r u n c a t i n g d e c i m a l s ) ; r e t u r n s n u l l o n f a i l u r e . */
private static Integer t o I n t e g e r ( String v a l ) {
if ( String . i s B l a n k ( v a l ) | | v a l = = 'NOT_SET' ) return null ;
t r y {
return Decimal . v a l u e O f ( v a l ) . i n t V a l u e ( ) ;
} catch ( E x c e p t i o n e ) {
return null ;
}
}
/* *
* R e s o l v e a n A g e n t f o r c e a g e n t name to i t s G e n A i P l a n n e r D e f i n i t i o n I D s .
*
* Each d e p l o y e d a g e n t version c r e a t e s a G e n A i P l a n n e r D e f i n i t i o n w h o s e M a s t e r L a b e l
* matches t h e a g e n t 's display name (e.g. 'TeslaSupportAgent' ) . R e t u r n i n g all
* m a t c h i n g v e r s i o n s e n s u r e s h i s t o r i c a l s e s s i o n s from o l d e r d e p l o y m e n t s a r e i n c l u d e d .
*
* A l s o a d d s t h e 15 - c h a r version of each I D to handle S T D M D M O i n c o n s i s t e n c y :
* t h e p a r t i c i p a n t D M O s t o r e s s s o t _ _ P a r t i c i p a n t I d _ _ c as e i t h e r 15 - c h a r or 18 - c h a r .
*
* @ param a g e n t A p i N a m e M a s t e r L a b e l of t h e a g e n t ( s a m e as t h e a g e n t ' s display name )
* @ return L i s t of G e n A i P l a n n e r D e f i n i t i o n I d s in b o t h 15 - c h a r and 18 - c h a r f o r m a t s ; empty if not f o u n d
* /
private static L i s t < String > r e s o l v e P l a n n e r I d s ( String a g e n t A p i N a m e ) {
L i s t < String > i d s = new L i s t < String > ( ) ;
t r y {
// Search by MasterLabel (exact match) OR DeveloperName pattern.
// Agent Script agents create GenAiPlannerDefinition with DeveloperName
// like 'OrderService_v1' and MasterLabel like 'Order Service'.
// Accept the API name (no spaces) and match both patterns.
String d e v N a m e P a t t e r n = a g e n t A p i N a m e + '_%' ;
L i s t < G e n A i P l a n n e r D e f i n i t i o n > p l a n n e r s = [
SELECT I d FROM G e n A i P l a n n e r D e f i n i t i o n
WHERE M a s t e r L a b e l = : a g e n t A p i N a m e
OR D e v e l o p e r N a m e = : a g e n t A p i N a m e
OR D e v e l o p e r N a m e LIKE : d e v N a m e P a t t e r n
] ;
for ( G e n A i P l a n n e r D e f i n i t i o n p : p l a n n e r s ) {
String i d 18 = String . v a l u e O f ( p . I d ) ;
i d s . add ( i d 18 ) ;
// STDM DMO stores IDs inconsistently (15-char or 18-char) — include both
if ( i d 18 . length ( ) = = 18 ) i d s . add ( i d 18 . substring ( 0 , 15 ) ) ;
}
if ( i d s . i s E m p t y ( ) ) {
S y s t e m . debug ( L o g g i n g L e v e l . W A R N , 'No GenAiPlannerDefinition found for: ' + a g e n t A p i N a m e ) ;
} else {
S y s t e m . debug ( L o g g i n g L e v e l . DEBUG , 'Resolved ' + planners.size() + ' p l a n n e r version ( s ) for a g e n t " '
+ a g e n t A p i N a m e + ' " : ' + i d s ) ;
}
} catch ( E x c e p t i o n e ) {
S y s t e m . debug ( L o g g i n g L e v e l . W A R N , ' r e s o l v e P l a n n e r I d s f a i l e d for "' + agentApiName + '" : ' + e . g e t M e s s a g e ( ) ) ;
}
return i d s ;
}
/* * E x t r a c t s s o t _ _ A i A g e n t S e s s i o n I d _ _ c v a l u e s ( c o l 0 ) f r o m a p a r t i c i p a n t D M O q u e r y r e s u l t . */
private static L i s t < String > e x t r a c t S e s s i o n I d s ( C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 p a r t R e s u l t ) {
L i s t < String > s e s s i o n I d s = new L i s t < String > ( ) ;
if ( p a r t R e s u l t ! = null && partResult.data != null) {
for ( C o n n e c t A p i . C d p Q u e r y V 2 R o w row : p a r t R e s u l t . d a t a ) {
String s i d = col ( row . r o w D a t a , 0 ) ;
if ( s i d ! = null ) s e s s i o n I d s . add ( s i d ) ;
}
}
return s e s s i o n I d s ;
}
private static C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 r u n Q u e r y ( String sql , String d a t a S p a c e N a m e ) {
S y s t e m . debug ( L o g g i n g L e v e l . DEBUG , 'AgentforceOptimize CDP query (' + dataSpaceName + '): ' + sql ) ;
C o n n e c t A p i . C d p Q u e r y I n p u t i n p = new C o n n e c t A p i . C d p Q u e r y I n p u t ( ) ;
i n p . sql = sql ;
t r y {
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 r e s u l t = C o n n e c t A p i . C d p Q u e r y . q u e r y A n s i S q l V 2 ( i n p , d a t a S p a c e N a m e ) ;
Integer r o w C o u n t = ( r e s u l t ! = null && result.data != null) ? result.data.size() : 0;
S y s t e m . debug ( L o g g i n g L e v e l . DEBUG , 'Rows returned: ' + r o w C o u n t ) ;
return r e s u l t ;
} catch ( E x c e p t i o n e ) {
S y s t e m . debug ( L o g g i n g L e v e l . ERROR ,
'CDP query failed [' + dataSpaceName + ']: ' + e.getMessage() + ' | SQL: ' + sql ) ;
return null ;
}
}
/* * S a f e l y e x t r a c t a c o l u m n v a l u e a s S t r i n g ; r e t u r n s n u l l f o r e m p t y / n u l l c e l l s . */
private static String col ( L i s t < O b j e c t > row , Integer i ) {
if ( row = = null | | i > = row . size ( ) | | row [ i ] = = null ) return null ;
return String . v a l u e O f ( row [ i ] ) ;
}
/* * R e t u r n n u l l w h e n t h e v a l u e i s t h e S T D M " N O T _ S E T " s e n t i n e l o r b l a n k . */
private static String n o t S e t ( String v a l ) {
if ( String . i s B l a n k ( v a l ) | | v a l = = 'NOT_SET' ) return null ;
return v a l ;
}
/* * C o m p u t e m i l l i s e c o n d d u r a t i o n b e t w e e n t w o I S O t i m e s t a m p s t r i n g s ; n u l l o n a n y f a i l u r e . */
private static L o n g d u r a t i o n M s ( String s t a r t T s , String e n d T s ) {
if ( String . i s B l a n k ( s t a r t T s ) | | String . i s B l a n k ( e n d T s ) ) return null ;
t r y {
Datetime s t a r t D t = p a r s e T s ( s t a r t T s ) ;
Datetime e n d D t = p a r s e T s ( e n d T s ) ;
if ( s t a r t D t = = null | | e n d D t = = null ) return null ;
return e n d D t . g e t T i m e ( ) - s t a r t D t . g e t T i m e ( ) ;
} catch ( E x c e p t i o n e ) {
return null ;
}
}
/* * P a r s e a n I S O 8 6 0 1 t i m e s t a m p s t r i n g i n t o a D a t e t i m e , w i t h f a l l b a c k s t r a t e g i e s . */
private static Datetime p a r s e T s ( String t s ) {
if ( String . i s B l a n k ( t s ) ) return null ;
// Strategy 1: ISO 8601 via JSON deserialise
t r y {
return ( Datetime ) J S O N . d e s e r i a l i z e ( ' "' + ts + '" ' , Datetime . class ) ;
} catch ( E x c e p t i o n e 1 ) {
S y s t e m . debug ( L o g g i n g L e v e l . F I N E , ' p a r s e T s s t r a t e g y 1 f a i l e d for "' + ts + '" : ' + e 1 . g e t M e s s a g e ( ) ) ;
}
// Strategy 2: Datetime.valueOf (handles 'yyyy-MM-dd HH:mm:ss' format)
t r y {
return Datetime . v a l u e O f ( t s ) ;
} catch ( E x c e p t i o n e 2 ) {
S y s t e m . debug ( L o g g i n g L e v e l . F I N E , ' p a r s e T s s t r a t e g y 2 f a i l e d for "' + ts + '" : ' + e 2 . g e t M e s s a g e ( ) ) ;
}
// Strategy 3: epoch milliseconds
t r y {
return Datetime . n e w I n s t a n c e ( L o n g . v a l u e O f ( t s ) ) ;
} catch ( E x c e p t i o n e 3 ) {
S y s t e m . debug ( L o g g i n g L e v e l . F I N E , ' p a r s e T s s t r a t e g y 3 f a i l e d for "' + ts + '" : ' + e 3 . g e t M e s s a g e ( ) ) ;
}
return null ;
}
// =========================================================================
// Observability Query (@InvocableMethod for Flow / Agentforce actions)
// =========================================================================
public class O b s e r v a b i l i t y I n p u t {
@ I n v o c a b l e V a r i a b l e ( label = 'Query Type' r e q u i r e d = true )
public String q u e r y T y p e ;
@ I n v o c a b l e V a r i a b l e ( label = 'Agent API Name' r e q u i r e d = false )
public String a g e n t A p i N a m e ;
chore: rename topic to subagent for Agent Script v2 @W-21955450@ (#193)
* @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>
2026-04-28 02:42:18 +08:00
@ I n v o c a b l e V a r i a b l e ( label = 'Subagent API Name' r e q u i r e d = false )
2026-04-09 19:34:48 +08:00
public String t o p i c A p i N a m e ;
@ I n v o c a b l e V a r i a b l e ( label = 'Lookback Days' r e q u i r e d = false )
public Integer l o o k b a c k D a y s ;
}
public class O b s e r v a b i l i t y O u t p u t {
@ I n v o c a b l e V a r i a b l e ( label = 'Query Result JSON' )
public String r e s u l t J s o n ;
@ I n v o c a b l e V a r i a b l e ( label = 'Summary Text' )
public String s u m m a r y T e x t ;
}
@ I n v o c a b l e M e t h o d ( label = 'Run Observability Query' description='Executes a Data Cloud observability query based on query type and optional filters.' )
public static L i s t < O b s e r v a b i l i t y O u t p u t > r u n O b s e r v a b i l i t y Q u e r y ( L i s t < O b s e r v a b i l i t y I n p u t > i n p u t s ) {
L i s t < O b s e r v a b i l i t y O u t p u t > r e s u l t s = new L i s t < O b s e r v a b i l i t y O u t p u t > ( ) ;
for ( O b s e r v a b i l i t y I n p u t input : i n p u t s ) {
O b s e r v a b i l i t y O u t p u t o u t = new O b s e r v a b i l i t y O u t p u t ( ) ;
t r y {
String query = b u i l d O b s e r v a b i l i t y Q u e r y ( input ) ;
C o n n e c t A p i . C d p Q u e r y I n p u t c d p I n p u t = new C o n n e c t A p i . C d p Q u e r y I n p u t ( ) ;
c d p I n p u t . sql = query ;
C o n n e c t A p i . C d p Q u e r y O u t p u t V 2 c d p O u t p u t = C o n n e c t A p i . C d p Q u e r y . q u e r y A n s i S q l V 2 ( c d p I n p u t ) ;
o u t . r e s u l t J s o n = J S O N . s e r i a l i z e ( c d p O u t p u t ) ;
Integer r o w C o u n t = c d p O u t p u t . r o w C o u n t ! = null ? c d p O u t p u t . r o w C o u n t : 0 ;
if ( r o w C o u n t = = 0 ) {
o u t . s u m m a r y T e x t = 'Query executed for ' + input.queryType + '. No results found for the given filters and time range. SQL: ' + query ;
} else {
o u t . s u m m a r y T e x t = 'Query executed for ' + input.queryType + '. Found ' + rowCount + ' result(s). Use the metadata to map column positions to names in the data arrays.' ;
}
} catch ( E x c e p t i o n e ) {
o u t . s u m m a r y T e x t = 'Error executing ' + input.queryType + ' query: ' + e . g e t M e s s a g e ( ) ;
o u t . r e s u l t J s o n = ' { " e r r o r " : " ' + e . g e t M e s s a g e ( ) . r e p l a c e ( ' " ' , ' \ \ " ' ) + ' " } ' ;
}
r e s u l t s . add ( o u t ) ;
}
return r e s u l t s ;
}
private static String b u i l d O b s e r v a b i l i t y Q u e r y ( O b s e r v a b i l i t y I n p u t p ) {
Integer d a y s = p . l o o k b a c k D a y s ! = null ? p . l o o k b a c k D a y s : 90 ;
String c u t o f f = Datetime . now ( ) . a d d D a y s ( - d a y s ) . f o r m a t G m t ( 'yyyy-MM-dd HH:mm:ss.SSS' ) ;
String a g e n t F i l t e r = String . i s N o t B l a n k ( p . a g e n t A p i N a m e )
? ' AND sp.aiAgentApiName__c = \'' + String.escapeSingleQuotes(p.agentApiName) + '\'' : '' ;
String t o p i c F i l t e r = String . i s N o t B l a n k ( p . t o p i c A p i N a m e )
? ' AND i.topicApiName__c = \'' + String.escapeSingleQuotes(p.topicApiName) + '\'' : '' ;
if ( p . q u e r y T y p e = = 'KnowledgeGap' ) {
return 'SELECT i.topicApiName__c, sp.aiAgentApiName__c, ' +
'AVG(q.ContextPrecisionScoreNumber__c) AS avg_precision, ' +
'AVG(q.AnswerRelevancyScoreNumber__c) AS avg_relevancy, ' +
'COUNT(*) AS total_interactions ' +
'FROM AIRetrieverQualityMetric__dll q ' +
'JOIN AIAgentInteraction__dll i ON q.RetrieverTraceId__c = i.telemetryTraceId__c ' +
'JOIN AIAgentSessionParticipant__dll sp ON sp.aiAgentSessionId__c = i.aiAgentSessionId__c ' +
'WHERE i.startTimestamp__c > \'' + cutoff + '\'' +
a g e n t F i l t e r + t o p i c F i l t e r +
' GROUP BY i.topicApiName__c, sp.aiAgentApiName__c ' +
'ORDER BY avg_precision ASC LIMIT 25' ;
} else if ( p . q u e r y T y p e = = 'Hallucination' ) {
return 'SELECT sp.aiAgentApiName__c, i.topicApiName__c, ' +
'AVG(q.FaithfulnessRelevancyScoreNumber__c) AS avg_faithfulness, ' +
'COUNT(*) AS total_interactions ' +
'FROM AIRetrieverQualityMetric__dll q ' +
'JOIN AIAgentInteraction__dll i ON q.RetrieverTraceId__c = i.telemetryTraceId__c ' +
'JOIN AIAgentSessionParticipant__dll sp ON sp.aiAgentSessionId__c = i.aiAgentSessionId__c ' +
'WHERE i.startTimestamp__c > \'' + cutoff + '\'' +
' AND q.FaithfulnessRelevancyScoreNumber__c < 0.8' +
a g e n t F i l t e r + t o p i c F i l t e r +
' GROUP BY sp.aiAgentApiName__c, i.topicApiName__c ' +
'ORDER BY avg_faithfulness ASC LIMIT 25' ;
} else if ( p . q u e r y T y p e = = 'RetrievalQuality' ) {
return 'SELECT q.RetrieverApiName__c, i.topicApiName__c, sp.aiAgentApiName__c, ' +
'AVG(q.ContextPrecisionScoreNumber__c) AS avg_precision, COUNT(*) AS total ' +
'FROM AIRetrieverQualityMetric__dll q ' +
'JOIN AIAgentInteraction__dll i ON q.RetrieverTraceId__c = i.telemetryTraceId__c ' +
'JOIN AIAgentSessionParticipant__dll sp ON sp.aiAgentSessionId__c = i.aiAgentSessionId__c ' +
'WHERE i.startTimestamp__c > \'' + cutoff + '\'' +
a g e n t F i l t e r + t o p i c F i l t e r +
' GROUP BY q.RetrieverApiName__c, i.topicApiName__c, sp.aiAgentApiName__c ' +
'ORDER BY avg_precision ASC LIMIT 25' ;
} else if ( p . q u e r y T y p e = = 'AnswerRelevancy' ) {
return 'SELECT sp.aiAgentApiName__c, i.topicApiName__c, ' +
'AVG(q.AnswerRelevancyScoreNumber__c) AS avg_relevancy, COUNT(*) AS total ' +
'FROM AIRetrieverQualityMetric__dll q ' +
'JOIN AIAgentInteraction__dll i ON q.RetrieverTraceId__c = i.telemetryTraceId__c ' +
'JOIN AIAgentSessionParticipant__dll sp ON sp.aiAgentSessionId__c = i.aiAgentSessionId__c ' +
'WHERE i.startTimestamp__c > \'' + cutoff + '\'' +
' AND q.AnswerRelevancyScoreNumber__c < 0.7' +
a g e n t F i l t e r + t o p i c F i l t e r +
' GROUP BY sp.aiAgentApiName__c, i.topicApiName__c ' +
'ORDER BY avg_relevancy ASC LIMIT 25' ;
} else if ( p . q u e r y T y p e = = 'Leaderboard' ) {
return 'SELECT sp.aiAgentApiName__c, i.topicApiName__c, ' +
'AVG(q.ContextPrecisionScoreNumber__c) AS avg_precision, ' +
'AVG(q.AnswerRelevancyScoreNumber__c) AS avg_relevancy, ' +
'AVG(q.FaithfulnessRelevancyScoreNumber__c) AS avg_faithfulness, ' +
'COUNT(*) AS total_interactions ' +
'FROM AIRetrieverQualityMetric__dll q ' +
'JOIN AIAgentInteraction__dll i ON q.RetrieverTraceId__c = i.telemetryTraceId__c ' +
'JOIN AIAgentSessionParticipant__dll sp ON sp.aiAgentSessionId__c = i.aiAgentSessionId__c ' +
'WHERE i.startTimestamp__c > \'' + cutoff + '\'' +
a g e n t F i l t e r + t o p i c F i l t e r +
' GROUP BY sp.aiAgentApiName__c, i.topicApiName__c ' +
'ORDER BY avg_precision ASC LIMIT 25' ;
}
return 'SELECT COUNT(*) AS cnt FROM AIAgentSession__dll' ;
}
}