Class: Vangrail::Conversation
- Inherits:
-
Object
- Object
- Vangrail::Conversation
- 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
-
#engine ⇒ Object
readonly
Returns the value of attribute engine.
-
#turns ⇒ Object
readonly
Returns the value of attribute turns.
-
#window ⇒ Object
readonly
Returns the value of attribute window.
Instance Method Summary collapse
- #answer(text, **context) ⇒ Object
-
#ask(text, **context) ⇒ Object
Checks a question and records it, whatever the verdict.
- #blocked? ⇒ Boolean
- #blocked_turns ⇒ Object
-
#history ⇒ Object
The window the rails read: role and text, no Result objects, because a rail should not be reasoning about another rail's verdict text.
-
#initialize(engine, window: DEFAULT_WINDOW, **context) ⇒ Conversation
constructor
A new instance of Conversation.
- #last_user_turn ⇒ Object
-
#screen(documents, **context) ⇒ Object
Screens retrieved documents with the dialogue in view, so a context rail can see which question they were fetched for.
- #to_h ⇒ Object
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
#engine ⇒ Object (readonly)
Returns the value of attribute engine.
48 49 50 |
# File 'lib/vangrail/conversation.rb', line 48 def engine @engine end |
#turns ⇒ Object (readonly)
Returns the value of attribute turns.
48 49 50 |
# File 'lib/vangrail/conversation.rb', line 48 def turns @turns end |
#window ⇒ Object (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
87 88 89 |
# File 'lib/vangrail/conversation.rb', line 87 def blocked? !blocked_turns.empty? end |
#blocked_turns ⇒ Object
83 84 85 |
# File 'lib/vangrail/conversation.rb', line 83 def blocked_turns turns.select { |t| t.user? && t.blocked? } end |
#history ⇒ Object
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_turn ⇒ Object
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_h ⇒ Object
95 96 97 |
# File 'lib/vangrail/conversation.rb', line 95 def to_h { 'turns' => turns.map(&:to_h), 'blocked' => blocked_turns.size } end |