Class: Vangrail::Rails::ManyShot

Inherits:
Vangrail::Rail show all
Defined in:
lib/vangrail/rails/many_shot.rb

Overview

Catches a fake conversation pasted into a real one.

Two attacks share this shape. The first writes the model's own chat template into the text: the control tokens a server uses to separate system from user from assistant are ordinary characters by the time they reach a prompt, so a message containing them can close the user turn and open a system one. The second needs no special tokens at all and works by volume, filling the context with dozens of invented exchanges in which an assistant answers everything it is asked, until the pattern of the conversation outweighs the instructions at the top. The published measurement of that one is a success rate rising with the number of examples, which is why counting is a reasonable defence.

Two responses, because the right one differs:

template tokens   stripped, and reported as a rewrite
many-shot volume  blocked

Stripping rather than blocking the tokens is deliberate. On a desk that documents machine-learning software, "how do I use <|im_start|> in my template?" is a real question, and refusing it teaches the reader that the guardrail is the obstacle. Removed from the text, the token cannot restructure a prompt, and the question survives.

The volume threshold is on invented turns rather than on length. A long question is not an attack, and four alternations of a dialogue that never happened is not a long question.

Constant Summary collapse

TEMPLATE_TOKENS =

Chat template control tokens across the common families. These are not patterns that need judgement: text arriving from a reader has no honest reason to carry a delimiter the serving layer inserts.

/
  <\|(?:im_start|im_end|start_header_id|end_header_id|eot_id|begin_of_text|
       system|user|assistant|endoftext|end_of_turn|start_of_turn)\|>
  |\[\/?INST\]|<<\/?SYS>>|<\|channel\|>|<\|message\|>
/xi
TURN =

A role header at the start of a line, which is how a pasted transcript is written when it is not using template tokens.

/^\s{0,3}(?:###\s*)?(?:system|user|human|assistant|ai|bot|q|a|systeem|gebruiker|assistent|mens)\s*:\s*\S/i
CHATML_TURN =

After template tokens are stripped, ChatML leftover is a bare role on its own line. JSON and Alpaca never used a colon header either.

/^\s{0,3}(?:system|user|human|assistant|ai|bot)\s*$/i
JSON_TURN =
/["']role["']\s*:\s*["'](?:system|user|human|assistant)["']/i
ALPACA_TURN =
/^\#{1,3}\s*(?:instruction|response|input|system)\s*:/i

Constants inherited from Vangrail::Rail

Vangrail::Rail::DEFAULT_SIDES, Vangrail::Rail::SIDES

Instance Attribute Summary collapse

Attributes inherited from Vangrail::Rail

#name, #sides

Instance Method Summary collapse

Methods inherited from Vangrail::Rail

#applies_to?, #call, #language_agnostic?, #offline?, #placeholder?, #posture?, #quantifies?, #to_s, usable

Constructor Details

#initialize(max_turns: 4, placeholder: '', name: 'many_shot', sides: %i[input context])) ⇒ ManyShot

Returns a new instance of ManyShot.



56
57
58
59
60
# File 'lib/vangrail/rails/many_shot.rb', line 56

def initialize(max_turns: 4, placeholder: '', name: 'many_shot', sides: %i[input context])
  super(name: name, sides: sides)
  @max_turns = max_turns
  @placeholder = placeholder
end

Instance Attribute Details

#max_turnsObject (readonly)

Returns the value of attribute max_turns.



54
55
56
# File 'lib/vangrail/rails/many_shot.rb', line 54

def max_turns
  @max_turns
end

#placeholderObject (readonly)

Returns the value of attribute placeholder.



54
55
56
# File 'lib/vangrail/rails/many_shot.rb', line 54

def placeholder
  @placeholder
end

Instance Method Details

#cache_key(text, _context) ⇒ Object



62
63
64
# File 'lib/vangrail/rails/many_shot.rb', line 62

def cache_key(text, _context)
  text
end

#decide(text, _context) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/vangrail/rails/many_shot.rb', line 66

def decide(text, _context)
  body = text.to_s
  stripped = body.gsub(TEMPLATE_TOKENS, placeholder)
  turns = turn_count(stripped)
  if turns > max_turns
    return block(categories: ['many_shot'],
                 reason: "#{turns} conversation turns in one message")
  end

  return pass if stripped == body

  modify(stripped, categories: ['template_tokens'],
                   reason: 'removed chat template control tokens')
end