Query Details

Custom Agents - High observed tool-call volume in a trace

Custom Excessive Tool Calls

Query

let Evidence=materialize(CustomAgentInlineContent(1h));
CustomAgentSpans(1h)
| where Classification !in ('Excluded managed platform', 'Unattributed GenAI') and HasLineage
| where IsTool
| summarize EventCount=count(), SuccessfulCalls=countif(Success == true),
    FailedCalls=countif(Success == false), arg_max(EventTime, *)
    by _ResourceId, OperationId, AgentKey
| where EventCount >= 50
| extend Signal='At least 50 observed tool spans in one resource/agent/trace'
| extend TimeGenerated=EventTime, ResourceId=_ResourceId, TraceId=OperationId,
    EventCount=tolong(column_ifexists('EventCount', 1)),
    SuccessfulCalls=tolong(column_ifexists('SuccessfulCalls', 0)),
    FailedCalls=tolong(column_ifexists('FailedCalls', 0))
| project TimeGenerated, ResourceId, Agent, AgentId, AgentKey, AppRoleName,
    TraceId, SpanId, Conversation, Model, Tool, TargetHost, ServerDisplay,
    Signal, EventCount, SuccessfulCalls, FailedCalls,
    TriggerSpanId=tostring(column_ifexists('TriggerSpanId', '')),
    TriggerTime=todatetime(column_ifexists('TriggerTime', datetime(null))),
    TriggerTool=tostring(column_ifexists('TriggerTool', '')),
    TriggerToolResult=tostring(column_ifexists('TriggerToolResult', '')),
    TriggerServerDisplay=tostring(column_ifexists('TriggerServerDisplay', '')),
    TriggerTargetHost=tostring(column_ifexists('TriggerTargetHost', '')),
    RiskyOutputKind=tostring(column_ifexists('RiskyOutputKind', '')),
    RelatedToolResults=tolong(column_ifexists('RelatedToolResults', 0)),
    MatchedIntentPrompt=tostring(column_ifexists('IntentPrompt', '')),
    MatchedIntentSpanId=tostring(column_ifexists('IntentSpanId', ''))
| join kind=leftouter (Evidence
    | project ResourceId=_ResourceId, TraceId=OperationId, SpanId, AgentKey, Prompt, Output, ToolArguments, ToolResult, InputMessages, OutputMessages,
    EvidenceSource, PromptEvidenceOrigin, OutputEvidenceOrigin, PromptEvidenceSpanId, OutputEvidenceSpanId,
    InputParseState, OutputParseState, ToolArgumentsState, ToolResultState)
    on ResourceId, TraceId, SpanId, AgentKey
| extend Prompt=coalesce(MatchedIntentPrompt, Prompt),
    PromptEvidenceOrigin=iff(isnotempty(MatchedIntentPrompt), 'Matched intent span: user role', PromptEvidenceOrigin),
    PromptEvidenceSpanId=coalesce(MatchedIntentSpanId, PromptEvidenceSpanId)
| extend PreviewCharacters=toint(min_of(160, max_of(0, (1500 - string_size(tostring(bag_pack(
    'AgentId', AgentId, 'AgentKey', AgentKey, 'TraceId', TraceId, 'SpanId', SpanId,
    'Conversation', Conversation, 'ResourceId', ResourceId, 'Signal', Signal)))) / 12)))
| extend PromptPreview=substring(Prompt, 0, PreviewCharacters),
    OutputPreview=substring(Output, 0, PreviewCharacters)
| extend EvidencePreviewStatus=case(PreviewCharacters == 0, 'Metadata exhausted preview budget; inspect full query results',
    strlen(Prompt) > PreviewCharacters or strlen(Output) > PreviewCharacters, 'Inline evidence; previews shortened; full text in query results',
    isempty(Prompt) and isempty(Output), 'No prompt/output captured or parsed; not a protection verdict',
    'Inline evidence; full recorded text in query results')
| project TimeGenerated, ResourceId, Agent, AgentId, AgentKey, AppRoleName,
    TraceId, SpanId, Conversation, Model, Tool, TargetHost, ServerDisplay,
    Signal, EventCount, SuccessfulCalls, FailedCalls, Prompt, Output, ToolArguments, ToolResult, InputMessages, OutputMessages,
    EvidenceSource, PromptEvidenceOrigin, OutputEvidenceOrigin, PromptEvidenceSpanId, OutputEvidenceSpanId,
    InputParseState, OutputParseState, ToolArgumentsState, ToolResultState,
    PromptPreview, OutputPreview, EvidencePreviewStatus, TriggerSpanId, TriggerTime, TriggerTool, TriggerToolResult,
    TriggerServerDisplay, TriggerTargetHost, RiskyOutputKind, RelatedToolResults
| order by TimeGenerated desc

Explanation

This query is designed to monitor and detect high volumes of tool calls made by custom agents within a specific environment. Here's a simplified breakdown of what it does:

  1. Purpose: The query identifies instances where a single resource, agent, or trace emits at least 50 unique tool spans within an hour. This is a heuristic to flag potentially unusual activity, but it doesn't necessarily indicate unauthorized actions.

  2. Data Sources: It uses data from Application Insights, specifically looking at application requests and dependencies.

  3. Frequency and Period: The query runs every hour and analyzes data from the past 14 days.

  4. Logic:

    • It first collects evidence of tool usage over the past hour.
    • It filters out certain classifications and focuses on tool-related spans.
    • It summarizes the data to count the number of events, successful calls, and failed calls for each resource, operation, and agent.
    • It flags cases where the event count is 50 or more.
    • It enriches the data with additional details like prompts and outputs, and provides a preview of these if they are too long.
  5. Output: The query projects various fields such as time generated, resource ID, agent details, trace ID, and more, and orders the results by the time they were generated.

  6. Entity Mapping: It maps the results to Azure resources and cloud applications for better context.

  7. Custom Details and Grouping: It includes custom details for agents and traces, and groups alerts by agent key and resource ID to manage incidents effectively.

  8. Incident Management: The query is configured to create incidents when the conditions are met, with specific settings for grouping and reopening incidents.

  9. Version and Tags: The query is versioned and tagged for categorization and management purposes.

Overall, this query is part of a monitoring system to detect potentially high volumes of tool usage by custom agents, which could indicate unusual or noteworthy activity.