# STDM Query Reference Detailed procedures for querying Session Trace Data Model (STDM) via the `AgentforceOptimizeService` Apex helper class. --- ## Deploy Helper Class (Once Per Org) `AgentforceOptimizeService` is a bundled Apex class that queries STDM DMOs and returns clean JSON. Deploy it once; subsequent runs reuse the deployed class. Methods: - `findSessions(dataSpaceName, startIso, endIso, maxRows, agentName)` -> `List` - `getConversationDetails(dataSpaceName, sessionId)` -> `ConversationData` - `getMultipleConversationDetails(dataSpaceName, sessionIds)` -> `List` - `getLlmStepDetails(dataSpaceName, stepIds)` -> `List` - `getMomentInsights(dataSpaceName, sessionIds)` -> `List` (moments, turn counts, retriever metrics) - `getAggregatedMetrics(dataSpaceName, startIso, endIso, maxRows, agentName)` -> `AggregatedMetrics` (session rates, top intents, RAG quality) - `runObservabilityQuery(List)` -> `List` (@InvocableMethod -- RAG observability queries for Flow/Agentforce actions) **Step 1 -- copy the class into the project:** ```bash # Ensure the classes directory exists mkdir -p /force-app/main/default/classes # Copy from the skill's apex directory cp ../assets/apex/AgentforceOptimizeService.cls \ /force-app/main/default/classes/ cp ../assets/apex/AgentforceOptimizeService.cls-meta.xml \ /force-app/main/default/classes/ ``` **Step 2 -- ensure `sfdx-project.json` exists** (if absent, create a minimal one): ```json { "packageDirectories": [{ "path": "force-app", "default": true }], "sourceApiVersion": "63.0" } ``` **Step 3 -- deploy to the org:** ```bash sf project deploy start --json \ --metadata ApexClass:AgentforceOptimizeService \ -o ``` Confirm the deploy succeeds before proceeding. If it fails with a compile error, check that the org has Data Cloud enabled (the `ConnectApi.CdpQuery` namespace requires Data Cloud). **Skip this step if `AgentforceOptimizeService` is already deployed** -- check with: ```bash sf data query --json \ --query "SELECT Id, Name FROM ApexClass WHERE Name = 'AgentforceOptimizeService'" \ -o ``` --- ## Find Sessions If the user provided session IDs, skip to conversation details. Otherwise, write `/tmp/stdm_find.apex` and run it (substitute actual ISO 8601 UTC timestamps, DATA_SPACE, and AGENT_API_NAME): ```apex String result = AgentforceOptimizeService.findSessions( 'DATA_SPACE', 'START_ISO', 'END_ISO', 20, 'AGENT_MASTER_LABEL' ); System.debug('STDM_RESULT:' + result); ``` ```bash sf apex run --json --file /tmp/stdm_find.apex -o ``` Parse: search for `DEBUG|STDM_RESULT:` (not `STDM_RESULT:` -- the first occurrence of that string is in the source echo, not the debug output) and extract the JSON that follows on that line: ```bash python3 -c " import json, sys logs = json.load(sys.stdin)['result']['logs'] idx = logs.find('DEBUG|STDM_RESULT:') print(logs[idx + len('DEBUG|STDM_RESULT:'):].split('\n')[0].strip()) " < /tmp/apex_result.json ``` The result is a JSON array of `SessionSummary` objects: ```json [ { "session_id": "...", "start_time": "...", "end_time": "...", "channel": "...", "duration_ms": 12345, "end_type": "USER_ENDED" } ] ``` - `end_time` and `duration_ms` may be `null` when the session has no recorded end event -- this is a normal STDM data quality gap, not an error. - `end_type` values: `USER_ENDED`, `AGENT_ENDED`, or `null` (in-progress or not recorded). A `null` `end_type` may indicate an abandoned session. **Session quality filter:** `findSessions` automatically filters to sessions with actual conversation turns by querying `AiAgentInteraction` first. Sessions from `sf agent preview`, `sf agent test`, and Agent Builder that created `AiAgentSession` records but no `AiAgentInteraction` (TURN) records are excluded. If `findSessions` returns an empty list, there are no sessions with actionable data — go directly to Phase 1-ALT (local traces). **How agent filtering works** -- `findSessions` tries two strategies in order: 1. **Direct** (preferred): `ssot__AiAgentApiName__c = agentApiName` on `ssot__AiAgentSessionParticipant__dlm` -- no SOQL needed, uses a dedicated DMO field. Resolves in a single Data Cloud query. 2. **Planner fallback**: If strategy 1 returns no rows, SOQL: `SELECT Id FROM GenAiPlannerDefinition WHERE MasterLabel = :agentApiName` -> `ssot__ParticipantId__c IN (...)`. Both 15-char and 18-char ID formats are included (the DMO stores them inconsistently). If both strategies return empty, the query falls back to all sessions in the date range. **If the debug log shows `Agent not found: `**, no `GenAiPlannerDefinition` matched -- verify the agent name with: ```bash sf data query --json --query "SELECT Id, MasterLabel, DeveloperName FROM GenAiPlannerDefinition" -o ``` Use the exact `MasterLabel` value (not `DeveloperName`). `MasterLabel` matches the agent's display name; `DeveloperName` has a version suffix (e.g. `TeslaSupportAgent_v1`). **If the debug log shows a warning about no sessions for the agent**, both strategies returned empty -- the agent may have no sessions in this date range, or Data Cloud ingestion may be delayed. The query falls back to all sessions in the date range. --- ## Get Conversation Details For up to 5 sessions (most recent first), write `/tmp/stdm_details.apex` and run it (substitute session IDs and DATA_SPACE): ```apex String result = AgentforceOptimizeService.getMultipleConversationDetails( 'DATA_SPACE', new List{ 'SESSION_ID_1', 'SESSION_ID_2' } ); System.debug('STDM_RESULT:' + result); ``` ```bash sf apex run --json --file /tmp/stdm_details.apex -o ``` Parse using the same `DEBUG|STDM_RESULT:` pattern. Each element is a `ConversationData` object: ```json { "session_id": "...", "start_time": "...", "end_time": "...", "channel": "...", "duration_ms": 45000, "end_type": "USER_ENDED", "session_variables": "{...}", "turn_count": 3, "action_error_count": 1, "turns": [ { "interaction_id": "...", "topic": "CheckOrderStatus", "start_time": "...", "end_time": "...", "duration_ms": 8000, "telemetry_trace_id": "...", "messages": [ { "message_type": "Input", "text": "Where is my order?", "sent_at": "..." }, { "message_type": "Output", "text": "I found your order...", "sent_at": "..." } ], "steps": [ { "step_type": "TOPIC_STEP", "name": "CheckOrderStatus" }, { "step_type": "LLM_STEP", "name": "...", "duration_ms": 3200, "generation_id": "abc123", "gateway_request_id": "def456" }, { "step_type": "ACTION_STEP", "name": "GetOrderDetails", "input": "{...}", "output": "{...}", "error": null, "pre_vars": "{...}", "post_vars": "{...}", "duration_ms": 1500 } ] } ] } ``` Key fields: - `end_type` -- how the session ended (`USER_ENDED`, `AGENT_ENDED`, or null) - `session_variables` -- final variable snapshot for the session (null when absent) - `telemetry_trace_id` -- distributed tracing ID for this turn (null when absent) - `generation_id` / `gateway_request_id` on `LLM_STEP` -- pass these step IDs to `getLlmStepDetails()` to retrieve the actual LLM prompt and response (useful for diagnosing LOW instruction adherence) Treat any `null` field as absent/unknown. The `"NOT_SET"` sentinel is stripped by the service class before returning. --- ## Get LLM Prompt/Response (Optional, for LOW Adherence) When a session shows `TRUST_GUARDRAILS_STEP` with `'value': 'LOW'`, use `getLlmStepDetails()` to retrieve the actual LLM prompt and response for the associated `LLM_STEP` records. Pass the `step_id` values from steps where `step_type == "LLM_STEP"` and `generation_id != null`. ```apex String result = AgentforceOptimizeService.getLlmStepDetails( 'DATA_SPACE', new List{ 'STEP_ID_1', 'STEP_ID_2' } ); System.debug('STDM_RESULT:' + result); ``` ```bash sf apex run --json --file /tmp/stdm_llm.apex -o ``` Returns a JSON array of `LlmStepDetail` objects: ```json [ { "step_id": "...", "interaction_id": "...", "step_name": "...", "prompt": "System: You are a Tesla support agent...\nUser: I want to schedule a test drive", "llm_response": "I'd be happy to help you schedule a test drive...", "generation_id": "...", "gateway_request_id": "..." } ] ``` - `prompt` -- full prompt from `GenAIGatewayRequest__dlm.prompt__c` (null if Einstein Audit DMO not enabled) - `llm_response` -- model response from `GenAIGeneration__dlm.responseText__c` (null if not available) Use these to confirm whether the agent's instructions were included in the prompt and whether the response deviated from them. --- ## Get Aggregated Metrics (Recommended First Step) Before drilling into individual sessions, get a high-level health dashboard with `getAggregatedMetrics()`. This gives session rates, top intents, and RAG quality averages across the date range. ```apex String result = AgentforceOptimizeService.getAggregatedMetrics( 'DATA_SPACE', 'START_ISO', 'END_ISO', 50, 'AGENT_MASTER_LABEL' ); System.debug('STDM_RESULT:' + result); ``` Returns an `AggregatedMetrics` object: ```json { "total_sessions": 36, "total_moments": 32, "total_turns": 101, "avg_quality_score": 4.34, "avg_session_duration_sec": 45.2, "end_type_counts": { "USER_ENDED": 5, "AGENT_ENDED": 10, "UNKNOWN": 21 }, "quality_distribution": { "5": 20, "4": 6, "3": 4, "2": 1, "1": 1 }, "abandonment_rate": 0.14, "deflection_rate": 0.28, "escalation_rate": 0.0, "top_intents": { "I want help finding homes in San Jose.": 3, "Check order status": 2 }, "avg_faithfulness": 0.85, "avg_answer_relevance": 0.72, "avg_context_precision": 0.91, "unavailable_dmos": [] } ``` Key signals: - `avg_quality_score` < 4.0 -> agent has Medium/Low quality responses, investigate low-scoring moments. Score labels: 5=High, 3-4=Medium, 2=Low, 1=Very Low - `quality_distribution` skewed toward 1-3 -> systemic agent quality issue; focus on moments with score <= 3 - High `abandonment_rate` (> 0.3) -> users giving up, check for dead-ends or missing actions - Low `avg_faithfulness` / `avg_answer_relevance` -> RAG retrieval issues, check knowledge base content - `top_intents` shows what users ask about most -- verify the agent has topics/actions for each - `unavailable_dmos` lists any DMOs that couldn't be queried (graceful degradation) --- ## Get Moment Insights (Per-Session Detail) For deeper analysis of specific sessions, use `getMomentInsights()` to get intent summaries, moment durations, and retriever quality metrics per session. ```apex String result = AgentforceOptimizeService.getMomentInsights( 'DATA_SPACE', new List{ 'SESSION_ID_1', 'SESSION_ID_2' } ); System.debug('STDM_RESULT:' + result); ``` Returns a JSON array of `SessionInsights` objects: ```json [ { "session_id": "...", "start_time": "...", "end_time": "...", "end_type": null, "duration_ms": null, "turn_count": 3, "moment_count": 2, "avg_quality_score": 4.5, "action_error_count": 0, "moments": [ { "moment_id": "...", "session_id": "...", "start_time": "...", "end_time": "...", "duration_ms": 10000, "request_summary": "I want help finding homes in San Jose.", "response_summary": "The agent provided details on three homes...", "agent_api_name": "MyServiceAgent", "agent_version": null, "quality_score": 5, "quality_reasoning": "The agent provided a detailed and helpful response..." } ], "retriever_metrics": [], "debug_message": null } ] ``` Key fields: - `quality_score` (1-5) -- per-moment quality score from `AiAgentTagAssociation -> AiAgentTag.Value`. Maps to UI labels: 5=High, 3-4=Medium, 2=Low, 1=Very Low - `quality_reasoning` -- LLM-generated explanation for the score (from `AssociationReasonText`) - `avg_quality_score` -- session-level average across all scored moments - `request_summary` / `response_summary` -- LLM-generated intent and response summaries per moment - `moment_count` vs `turn_count` -- if `turn_count` >> `moment_count`, the agent needed many turns per intent (inefficient) - `retriever_metrics` -- RAG quality scores per retrieval (empty if agent doesn't use knowledge retrieval) - `debug_message` -- non-null if a DMO was unavailable (e.g. "AiAgentMoment DMO not available in this org") --- ## Run Observability Queries (RAG Deep-Dive) For targeted RAG/retriever quality analysis, use the `@InvocableMethod` entry point `runObservabilityQuery()`. This can be called from anonymous Apex, Flows, or Agentforce actions. It queries Data Lake objects (`*__dll`) directly without a Data Space parameter. **Query types:** | `queryType` | What it returns | |---|---| | `KnowledgeGap` | Avg context precision + answer relevancy by subagent/agent (lowest first) | | `Hallucination` | Subagents with avg faithfulness < 0.8 | | `RetrievalQuality` | Avg context precision by retriever/subagent/agent | | `AnswerRelevancy` | Subagents with avg answer relevancy < 0.7 | | `Leaderboard` | Combined precision, relevancy, and faithfulness by subagent/agent | **From anonymous Apex:** ```apex AgentforceOptimizeService.ObservabilityInput inp = new AgentforceOptimizeService.ObservabilityInput(); inp.queryType = 'KnowledgeGap'; inp.agentApiName = 'AGENT_API_NAME'; // optional inp.topicApiName = 'TOPIC_API_NAME'; // optional inp.lookbackDays = 90; // optional, default 90 List results = AgentforceOptimizeService.runObservabilityQuery( new List{ inp } ); System.debug('STDM_RESULT:' + results[0].summaryText); System.debug('STDM_RESULT:' + results[0].resultJson); ``` ```bash sf apex run --json --file /tmp/observability_query.apex -o ``` **When to use observability queries vs `getAggregatedMetrics()`:** - Use `getAggregatedMetrics()` for a broad health dashboard (session rates, top intents, overall RAG averages) - Use `runObservabilityQuery()` for targeted RAG deep-dives when knowledge gaps or hallucination issues are detected -- it provides per-subagent and per-retriever breakdowns --- ## Reconstruct Conversations For each session, render the turn-by-turn timeline from the `ConversationData` JSON: ``` Session [] ms total turns ------------------------------------------------------------ Turn 1 [Subagent: ] ms User: Agent: Steps: TOPIC_STEP: LLM_STEP: (ms) ACTION_STEP: in: out: [ERROR: ] ```