The no_converters bucket of the converter_type analytics dimension never matches a real attack.
The bucket is only set when the stored converter list is an empty JSON array (attack_analytics_query.py, the JsonArrayEmpty case). A real attack with no converters doesn't store an empty list, it leaves the key out, so it lands in missing instead.
Repro: run one PromptSendingAttack with no converters against a mock target, then query with AttackAnalyticsReader:
request groups: [('missing', {'undetermined': 1})]
request filter no_converters -> {}
response groups: [('missing', {'undetermined': 1})]
response filter no_converters -> {}
memory.get_attack_results_async(has_converters=False) -> 1
So filtering on "no converters" in analytics returns nothing, while the history API's has_converters=False finds the attack. Treating an absent list as no_converters for converter_type (when the attack identifier itself was recorded) would line the two up.
The
no_convertersbucket of theconverter_typeanalytics dimension never matches a real attack.The bucket is only set when the stored converter list is an empty JSON array (
attack_analytics_query.py, theJsonArrayEmptycase). A real attack with no converters doesn't store an empty list, it leaves the key out, so it lands inmissinginstead.Repro: run one
PromptSendingAttackwith no converters against a mock target, then query withAttackAnalyticsReader:So filtering on "no converters" in analytics returns nothing, while the history API's
has_converters=Falsefinds the attack. Treating an absent list asno_convertersfor converter_type (when the attack identifier itself was recorded) would line the two up.