Class: Vangrail::Rails::Trajectory

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

Overview

Judges where a conversation is going, not what its newest message says.

Rails::Escalation is the deterministic half of the multi-turn problem and is honest about its limit: it sees nothing until a refusal happens, and the published multi-turn methods are built precisely so that no turn ever triggers one. Each message is a reasonable follow-up to the answer before it, the assistant's own output is the foothold for the next step, and the escalation exists only in the sequence.

Reading a sequence for intent is what a model can do and a regexp cannot, so this rail sends the transcript to the judge model and asks about the direction rather than the content.

It costs a round trip per turn, which is why it is opt-in and why every exists: judging one turn in three is a defensible trade for a documentation desk, since a staged escalation takes several turns by construction and cannot complete inside the gap.

Rails::Trajectory.new(provider: provider, min_turns: 4, every: 2)

Below min_turns it passes and says it is certain, which is a real judgement rather than a dodge: a two-message conversation has no trajectory, and the single-turn rails have already read both messages. On a skipped turn it passes with certain? false, because that turn was not judged and an application reading the flag deserves to know which kind of pass it has.

Constant Summary collapse

DEFAULT_MIN_TURNS =
4

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?, #offline?, #placeholder?, #to_s

Constructor Details

#initialize(provider: nil, model: nil, chat: nil, policy: nil, min_turns: DEFAULT_MIN_TURNS, every: 1, window: 12, name: 'trajectory', max_tokens: 256, **chat_options) ⇒ Trajectory

Returns a new instance of Trajectory.



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/vangrail/rails/trajectory.rb', line 41

def initialize(provider: nil, model: nil, chat: nil, policy: nil,
               min_turns: DEFAULT_MIN_TURNS, every: 1, window: 12,
               name: 'trajectory', max_tokens: 256, **chat_options)
  super(name: name, sides: [:input])
  @model = model || provider&.model(:judge)
  @policy = policy || Policies.trajectory_policy
  @min_turns = min_turns
  @every = [every.to_i, 1].max
  @window = window
  @chat = chat || begin
    raise ArgumentError, 'a trajectory rail needs a provider or a chat client' unless provider

    Chat.new(model: @model, base_url: provider.base_url, api_key: provider.api_key,
             max_tokens: max_tokens, **chat_options)
  end
end

Instance Attribute Details

#chatObject (readonly)

Returns the value of attribute chat.



39
40
41
# File 'lib/vangrail/rails/trajectory.rb', line 39

def chat
  @chat
end

#everyObject (readonly)

Returns the value of attribute every.



39
40
41
# File 'lib/vangrail/rails/trajectory.rb', line 39

def every
  @every
end

#min_turnsObject (readonly)

Returns the value of attribute min_turns.



39
40
41
# File 'lib/vangrail/rails/trajectory.rb', line 39

def min_turns
  @min_turns
end

#modelObject (readonly)

Returns the value of attribute model.



39
40
41
# File 'lib/vangrail/rails/trajectory.rb', line 39

def model
  @model
end

#policyObject (readonly)

Returns the value of attribute policy.



39
40
41
# File 'lib/vangrail/rails/trajectory.rb', line 39

def policy
  @policy
end

#windowObject (readonly)

Returns the value of attribute window.



39
40
41
# File 'lib/vangrail/rails/trajectory.rb', line 39

def window
  @window
end

Instance Method Details

#cache_key(_text, _context) ⇒ Object

Never memoizable: the same message means different things depending on what it follows.



60
61
62
# File 'lib/vangrail/rails/trajectory.rb', line 60

def cache_key(_text, _context)
  nil
end

#call(text, context) ⇒ Object



64
65
66
67
68
69
70
71
# File 'lib/vangrail/rails/trajectory.rb', line 64

def call(text, context)
  turns = Array(context[:history]).last(window)
  return pass if turns.size < min_turns

  return unchecked("not judged this turn (every #{every})") unless due?(turns)

  judge(text, turns)
end