Class: Vangrail::Conversation

Inherits:
Object
  • Object
show all
Defined in:
lib/vangrail/conversation.rb

Overview

A dialogue, so rails can see more than the turn in front of them.

Every rail so far reads one string. That is enough for the attacks that fit in one string, and it is exactly wrong for the ones built out of turns that are individually unremarkable: ask something harmless, ask for more detail about the part of the answer that helps, keep going until the thing you wanted is on screen. No single message in that sequence looks like an attack, because none of them is one.

So this holds the turns and threads them into the rail context as :history, which the rail protocol has always carried and nothing has ever filled in. A rail that ignores history behaves exactly as before.

convo = Vangrail::Conversation.new(engine)
verdict = convo.ask(question)
convo.answer(text) if verdict.allowed?

What it also does is remember the verdicts. A refusal is the most informative event in a dialogue: the next message is either an ordinary follow-up or the same request rewritten, and telling those apart is impossible without knowing a refusal happened.

Defined Under Namespace

Classes: Turn

Constant Summary collapse

DEFAULT_WINDOW =

How many turns of history the rails see. A dialogue that has been running for an hour is mostly irrelevant to whether this message is a retry, and an unbounded window makes the cost of a check grow with the session.

12

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(engine, window: DEFAULT_WINDOW, **context) ⇒ Conversation

Returns a new instance of Conversation.



50
51
52
53
54
55
# File 'lib/vangrail/conversation.rb', line 50

def initialize(engine, window: DEFAULT_WINDOW, **context)
  @engine = engine
  @window = window
  @base_context = context
  @turns = []
end

Instance Attribute Details

#engineObject (readonly)

Returns the value of attribute engine.



48
49
50
# File 'lib/vangrail/conversation.rb', line 48

def engine
  @engine
end

#turnsObject (readonly)

Returns the value of attribute turns.



48
49
50
# File 'lib/vangrail/conversation.rb', line 48

def turns
  @turns
end

#windowObject (readonly)

Returns the value of attribute window.



48
49
50
# File 'lib/vangrail/conversation.rb', line 48

def window
  @window
end

Instance Method Details

#answer(text, **context) ⇒ Object



65
66
67
68
69
# File 'lib/vangrail/conversation.rb', line 65

def answer(text, **context)
  result = engine.check_output(text, history: history, **@base_context, **context)
  @turns << Turn.new(role: :assistant, text: content_of(result, text), result: result)
  result
end

#ask(text, **context) ⇒ Object

Checks a question and records it, whatever the verdict. A blocked turn stays in the history: it is the part the next check needs most.



59
60
61
62
63
# File 'lib/vangrail/conversation.rb', line 59

def ask(text, **context)
  result = engine.check_input(text, history: history, **@base_context, **context)
  @turns << Turn.new(role: :user, text: text.to_s, result: result)
  result
end

#blocked?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/vangrail/conversation.rb', line 87

def blocked?
  !blocked_turns.empty?
end

#blocked_turnsObject



83
84
85
# File 'lib/vangrail/conversation.rb', line 83

def blocked_turns
  turns.select { |t| t.user? && t.blocked? }
end

#historyObject

The window the rails read: role and text, no Result objects, because a rail should not be reasoning about another rail's verdict text.



79
80
81
# File 'lib/vangrail/conversation.rb', line 79

def history
  turns.last(window).map { |t| { role: t.role, text: t.text, blocked: t.blocked? } }
end

#last_user_turnObject



91
92
93
# File 'lib/vangrail/conversation.rb', line 91

def last_user_turn
  turns.reverse.find(&:user?)
end

#screen(documents, **context) ⇒ Object

Screens retrieved documents with the dialogue in view, so a context rail can see which question they were fetched for.



73
74
75
# File 'lib/vangrail/conversation.rb', line 73

def screen(documents, **context)
  engine.screen(documents, history: history, **@base_context, **context)
end

#to_hObject



95
96
97
# File 'lib/vangrail/conversation.rb', line 95

def to_h
  { 'turns' => turns.map(&:to_h), 'blocked' => blocked_turns.size }
end