vangrail 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +655 -43
  3. data/lib/vangrail/actions.rb +10 -3
  4. data/lib/vangrail/assessor.rb +249 -0
  5. data/lib/vangrail/bayes_data.rb +340 -0
  6. data/lib/vangrail/beta.rb +102 -0
  7. data/lib/vangrail/builder.rb +354 -0
  8. data/lib/vangrail/chat.rb +17 -15
  9. data/lib/vangrail/client/{completion.rb → turn.rb} +3 -3
  10. data/lib/vangrail/client.rb +27 -18
  11. data/lib/vangrail/colang/ast.rb +29 -3
  12. data/lib/vangrail/colang/interpreter.rb +55 -31
  13. data/lib/vangrail/colang/parser.rb +19 -61
  14. data/lib/vangrail/colang/value_parser.rb +161 -0
  15. data/lib/vangrail/completion.rb +86 -0
  16. data/lib/vangrail/config.rb +35 -15
  17. data/lib/vangrail/conversation.rb +240 -11
  18. data/lib/vangrail/dojo.rb +126 -0
  19. data/lib/vangrail/embeddings.rb +87 -0
  20. data/lib/vangrail/engine.rb +29 -70
  21. data/lib/vangrail/errors.rb +6 -1
  22. data/lib/vangrail/evidence.rb +303 -0
  23. data/lib/vangrail/evidence_data.rb +113 -0
  24. data/lib/vangrail/http.rb +18 -13
  25. data/lib/vangrail/judgement.rb +151 -0
  26. data/lib/vangrail/known_attacks.rb +45 -0
  27. data/lib/vangrail/linear_model.rb +124 -0
  28. data/lib/vangrail/nlp.rb +596 -0
  29. data/lib/vangrail/origin.rb +249 -0
  30. data/lib/vangrail/parsers.rb +5 -5
  31. data/lib/vangrail/profile.rb +114 -0
  32. data/lib/vangrail/prompt.rb +14 -3
  33. data/lib/vangrail/provider.rb +106 -75
  34. data/lib/vangrail/providers/gateway.rb +15 -14
  35. data/lib/vangrail/providers/llmlite.rb +25 -10
  36. data/lib/vangrail/providers.rb +6 -8
  37. data/lib/vangrail/rail.rb +46 -8
  38. data/lib/vangrail/rails/alignment.rb +91 -0
  39. data/lib/vangrail/rails/bayes.rb +115 -0
  40. data/lib/vangrail/rails/budget.rb +2 -2
  41. data/lib/vangrail/rails/canary.rb +2 -2
  42. data/lib/vangrail/rails/colang_flow.rb +9 -1
  43. data/lib/vangrail/rails/escalation.rb +15 -8
  44. data/lib/vangrail/rails/exfiltration.rb +2 -2
  45. data/lib/vangrail/rails/grounding.rb +8 -5
  46. data/lib/vangrail/rails/guard_model.rb +7 -4
  47. data/lib/vangrail/rails/hidden.rb +52 -9
  48. data/lib/vangrail/rails/injected_instructions.rb +29 -9
  49. data/lib/vangrail/rails/jailbreak.rb +2 -6
  50. data/lib/vangrail/rails/known_answer.rb +6 -2
  51. data/lib/vangrail/rails/language.rb +87 -0
  52. data/lib/vangrail/rails/linear.rb +80 -0
  53. data/lib/vangrail/rails/many_shot.rb +2 -6
  54. data/lib/vangrail/rails/markup.rb +3 -3
  55. data/lib/vangrail/rails/missing.rb +1 -5
  56. data/lib/vangrail/rails/obfuscation.rb +81 -13
  57. data/lib/vangrail/rails/paraphrase.rb +189 -0
  58. data/lib/vangrail/rails/pattern.rb +2 -6
  59. data/lib/vangrail/rails/perplexity.rb +100 -0
  60. data/lib/vangrail/rails/personal_data.rb +41 -9
  61. data/lib/vangrail/rails/prompt_leak.rb +132 -0
  62. data/lib/vangrail/rails/remote.rb +5 -1
  63. data/lib/vangrail/rails/secrets.rb +2 -2
  64. data/lib/vangrail/rails/self_check.rb +9 -6
  65. data/lib/vangrail/rails/semantic.rb +132 -0
  66. data/lib/vangrail/rails/similarity.rb +96 -0
  67. data/lib/vangrail/rails/trajectory.rb +10 -5
  68. data/lib/vangrail/result.rb +3 -3
  69. data/lib/vangrail/result_cache.rb +0 -0
  70. data/lib/vangrail/screening.rb +68 -0
  71. data/lib/vangrail/session.rb +365 -0
  72. data/lib/vangrail/spotlight.rb +48 -8
  73. data/lib/vangrail/stream_guard.rb +8 -6
  74. data/lib/vangrail/tools.rb +58 -0
  75. data/lib/vangrail/version.rb +1 -1
  76. data/lib/vangrail.rb +39 -258
  77. metadata +34 -5
@@ -52,10 +52,17 @@ module Vangrail
52
52
  # polarity those flows branch on.
53
53
  def self.from_rails(input: nil, output: nil, facts: nil)
54
54
  actions = new
55
- actions.register('self_check_input') { |_args, ctx| input&.call(ctx[:text], ctx)&.allowed? }
56
- actions.register('self_check_output') { |_args, ctx| output&.call(ctx[:text], ctx)&.allowed? }
57
- actions.register('self_check_facts') { |_args, ctx| facts&.call(ctx[:text], ctx)&.allowed? }
55
+ actions.register('self_check_input') { |_args, ctx| allowed_by?(input, ctx) }
56
+ actions.register('self_check_output') { |_args, ctx| allowed_by?(output, ctx) }
57
+ actions.register('self_check_facts') { |_args, ctx| allowed_by?(facts, ctx) }
58
58
  actions
59
59
  end
60
+
61
+ def self.allowed_by?(rail, ctx)
62
+ return false unless rail
63
+
64
+ result = rail.call(ctx[:text], ctx)
65
+ result.respond_to?(:allowed?) && result.allowed?
66
+ end
60
67
  end
61
68
  end
@@ -0,0 +1,249 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'evidence'
4
+ require_relative 'evidence_data'
5
+ require_relative 'judgement'
6
+ require_relative 'origin'
7
+ require_relative 'rail'
8
+
9
+ module Vangrail
10
+ # Combines every measured rail into a posterior, then ranks a set by it.
11
+ #
12
+ # `check_input` and friends answer a different question. They run the rails
13
+ # in order, stop at the first block, and report a decision; that is the
14
+ # right shape for a request path and it is what every published defence
15
+ # does. It also throws away most of what was measured. A rail that fired
16
+ # tells you nothing about how much that hit is worth, three rails that
17
+ # nearly fired tell you nothing at all, and a block carries no number an
18
+ # operator can set a policy against.
19
+ #
20
+ # This runs every rail that has a measured operating point, treats each
21
+ # verdict as evidence, and combines it with the deployment's base rate.
22
+ # What comes back is a probability, the bits each rail contributed, and an
23
+ # action under a stated policy.
24
+ class Assessor
25
+ def initialize(engine)
26
+ @engine = engine
27
+ end
28
+
29
+ # The prior is not optional and has no sensible default. Detector papers
30
+ # report their numbers on balanced corpora, where an attack is half the
31
+ # traffic; a documentation desk over an editable wiki might see one poisoned
32
+ # page in ten thousand. Those two worlds disagree about what a hit means by
33
+ # four orders of magnitude, and only the deployment knows which one it is
34
+ # in. Guessing on its behalf would be the whole error this method exists to
35
+ # expose.
36
+ #
37
+ # judgement = engine.assess(page, side: :context, prior: 1e-4)
38
+ # judgement.posterior # => 0.0073
39
+ # judgement.action # => :review
40
+ # judgement.fired # => [{rail: "paraphrase", bits: 6.2, ...}]
41
+ #
42
+ # Costs more than a check, because nothing short-circuits: every rail with
43
+ # an entry in the table runs, including the ones a block would have skipped.
44
+ def assess(text, side: :input, prior: nil, policy: Policy::DEFAULT, evidence: nil,
45
+ escalate: false, confidence: Posterior::DEFAULT_CONFIDENCE, origin: nil, **context)
46
+ raise ArgumentError, prior_message if prior.nil?
47
+
48
+ origin = Origin.coerce(origin || Origin.default_for(side))
49
+ # A rail is worth different evidence on different sides, by a lot: the
50
+ # same paraphrase rail catches a third of in-the-wild jailbreak prompts
51
+ # and none of the published injections in retrieved documents. One table
52
+ # for both would be an average of two unrelated things.
53
+ evidence ||= EvidenceData.for_side(side)
54
+
55
+ observed = observe(text, side, context, evidence, escalate ? { prior: prior, policy: policy } : nil)
56
+ observations, direct, certain, skipped = observed
57
+ posterior, contributions = Posterior.combine(prior: prior, observations: observations,
58
+ evidence: evidence, direct: direct,
59
+ confidence: confidence)
60
+ action = policy.action_for(posterior)
61
+ # A confidence bound is what the corpus can defend. If the point
62
+ # estimate and the bound disagree about the action, the action is not
63
+ # identified: reporting it as a certain decision would spend evidence
64
+ # nobody measured.
65
+ if confidence
66
+ # Explicitly nil, because the bound is what `combine` defaults to now:
67
+ # asking for the point estimate has to say so, or this compares the
68
+ # bound against itself and reports every action as identified.
69
+ point, = Posterior.combine(prior: prior, observations: observations,
70
+ evidence: evidence, direct: direct, confidence: nil)
71
+ certain &&= policy.action_for(point) == action
72
+ end
73
+ Judgement.new(posterior: posterior, prior: prior, bits: contributions.sum { |c| c[:bits] },
74
+ contributions: contributions, certain: certain, side: side.to_sym,
75
+ skipped: skipped, action: action, origin: origin)
76
+ end
77
+
78
+ # Screening, with the documents ranked by how suspicious they are rather
79
+ # than partitioned by whether one rail objected.
80
+ #
81
+ # `screen` drops a document the moment a rail blocks it, which is the right
82
+ # shape when a rail is a switch. Given a posterior there is a better answer
83
+ # available: rank the set, drop what the policy says to drop, hand what it
84
+ # says to review to whoever reviews, and keep the rest. A page that trips
85
+ # one pattern at a base rate of one in ten thousand is not a page worth
86
+ # taking away from a reader, and it is worth putting at the bottom of the
87
+ # passage list.
88
+ #
89
+ # triage = engine.triage(documents, prior: 1e-4)
90
+ # triage.keep # documents, least suspicious first
91
+ # triage.review # [{document:, judgement:}]
92
+ # triage.dropped # [{document:, judgement:}]
93
+ def triage(documents, prior:, policy: Policy::DEFAULT, escalate: false, **context)
94
+ judged = Array(documents).each_with_index.map do |document, index|
95
+ judgement = assess(Cell.text_of(document), side: :context, prior: prior, policy: policy,
96
+ escalate: escalate, **context, document: document,
97
+ index: index)
98
+ { document: document, judgement: judgement }
99
+ end
100
+ Triage.new(judged: judged.sort_by { |row| -row[:judgement].posterior })
101
+ end
102
+
103
+ private
104
+
105
+ # Runs every rail with a measured operating point and records what each one
106
+ # saw: true for a hit, false for a rail that ran and did not fire, and
107
+ # nothing at all for a rail that could not decide.
108
+ #
109
+ # That third case is the one this gem can express and a pure detector stack
110
+ # cannot. An unreachable rail is not a rail that found nothing, and folding
111
+ # the two together silently converts a broken endpoint into evidence of
112
+ # innocence.
113
+ def observe(text, side, context, evidence, escalation = nil)
114
+ ctx = context.merge(side: side)
115
+ certain = true
116
+ observations = {}
117
+ direct = {}
118
+ skipped = []
119
+ readable = true
120
+ queue = queue_for(side, evidence, escalation)
121
+
122
+ queue.each_with_index do |rail, index|
123
+ if escalation && !rail.posture? && settled?(observations, queue[index..], evidence, escalation)
124
+ skipped = queue[index..].map(&:name)
125
+ break
126
+ end
127
+
128
+ result = @engine.invoke(rail, text, ctx)
129
+ certain, readable = record(rail, result, observations, direct, certain, readable)
130
+ end
131
+
132
+ [observations, direct, certain, skipped]
133
+ end
134
+
135
+ def queue_for(side, evidence, escalation)
136
+ queue = @engine.rails(side).select do |rail|
137
+ next false unless rail.applies_to?(side)
138
+ next true if rail.posture? || rail.quantifies?
139
+
140
+ evidence[rail.name]&.measured?
141
+ end
142
+ # Posture first, so a page nothing can read is known before silence is
143
+ # recorded. Cheap first after that when escalating, so a round trip is
144
+ # what an early stop can save.
145
+ posture, rest = queue.partition(&:posture?)
146
+ rest = rest.partition(&:offline?).flatten if escalation
147
+ posture + rest
148
+ end
149
+
150
+ def record(rail, result, observations, direct, certain, readable)
151
+ if rail.posture?
152
+ return [false, false] unless result.certain?
153
+
154
+ return [certain, readable]
155
+ end
156
+ return [false, readable] unless result.certain?
157
+
158
+ # A hit still counts: a hidden English injection inside a German
159
+ # page is a hit. Silence does not. A rail that cannot read this
160
+ # language and did not fire has not said the page is clean.
161
+ return [certain, readable] unless readable || rail.language_agnostic? || result.blocked?
162
+
163
+ # A rail that computed a likelihood ratio reports it in `raw`, and is
164
+ # read that way rather than being flattened to whether it blocked.
165
+ bits = result.raw.is_a?(Hash) ? result.raw['bits'] : nil
166
+ if bits
167
+ direct[rail.name] = bits.to_f
168
+ else
169
+ observations[rail.name] = result.blocked?
170
+ end
171
+ [certain, readable]
172
+ end
173
+
174
+ # Would running the rest change what happens?
175
+ #
176
+ # Not a guess and not a threshold on confidence: the evidence a rail can
177
+ # carry is bounded by its measured operating point, so the most the
178
+ # remaining rails could move the posterior in either direction is a number
179
+ # this can add up. When the action is the same at both ends of that
180
+ # interval, the remaining rails cannot change the outcome and running them
181
+ # buys nothing.
182
+ #
183
+ # This is why the framing pays for itself rather than only being tidier. A
184
+ # documentation desk running the deterministic rails and one model rail
185
+ # pays for the round trip on every check under the switch; here it pays only
186
+ # when the free evidence leaves the answer genuinely open, which on ordinary
187
+ # traffic is almost never.
188
+ #
189
+ # Skipping is not abstention. A rail that was proved irrelevant did not fail
190
+ # to run, so `certain?` stays true: the claim is about the action, and the
191
+ # action is unchanged by construction.
192
+ def settled?(observations, remaining, evidence, escalation)
193
+ return false if remaining.empty?
194
+ # A rail that reports its own likelihood ratio has no bound to reason
195
+ # against: its contribution is whatever the text scores. Nothing is
196
+ # skipped while one of those is still to run.
197
+ return false if remaining.any?(&:quantifies?)
198
+
199
+ posterior, = Posterior.combine(prior: escalation[:prior], observations: observations,
200
+ evidence: evidence)
201
+ reachable = remaining.filter_map { |rail| evidence[rail.name] }
202
+ return false if reachable.empty?
203
+
204
+ best = reachable.sum { |entry| entry.bits(true) }
205
+ worst = reachable.sum { |entry| entry.bits(false) }
206
+ policy = escalation[:policy]
207
+ odds = Posterior.to_odds(posterior)
208
+
209
+ policy.action_for(Posterior.from_odds(odds * (2**best))) ==
210
+ policy.action_for(Posterior.from_odds(odds * (2**worst)))
211
+ end
212
+
213
+ def prior_message
214
+ 'assess needs a prior: the share of texts on this side that are actually attacks. ' \
215
+ 'Published detector numbers assume a balanced corpus (0.5); a documentation desk over ' \
216
+ 'an editable wiki is nearer 1e-4. The answer changes the verdict, and only you know it.'
217
+ end
218
+ end
219
+
220
+ # What triage returns. The ranking is the product: a caller that wants the
221
+ # old behaviour reads `kept`, and one that wants to put the doubtful pages
222
+ # last reads them in order.
223
+ Triage = Struct.new(:judged, keyword_init: true) do
224
+ def dropped
225
+ judged.select { |row| row[:judgement].block? }
226
+ end
227
+
228
+ def review
229
+ judged.select { |row| row[:judgement].review? }
230
+ end
231
+
232
+ def kept
233
+ judged.reject { |row| row[:judgement].block? }.reverse.map { |row| row[:document] }
234
+ end
235
+
236
+ def certain?
237
+ judged.all? { |row| row[:judgement].certain? }
238
+ end
239
+
240
+ def to_h
241
+ {
242
+ 'kept' => kept.size,
243
+ 'review' => review.size,
244
+ 'dropped' => dropped.map { |row| row[:judgement].to_h },
245
+ 'certain' => certain?,
246
+ }
247
+ end
248
+ end
249
+ end
@@ -0,0 +1,340 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vangrail
4
+ # Naive Bayes weights over the corpora, in bits per feature.
5
+ #
6
+ # GENERATED FILE. Do not edit by hand; rerun script/train_bayes.rb when a
7
+ # corpus changes. The rail that reads this lives in rails/bayes.rb.
8
+ #
9
+ # 300 features selected by mutual information from 48
10
+ # attack and 56 benign
11
+ # clauses, counted as word stems and adjacent stem pairs, smoothed with a
12
+ # Dirichlet prior of 1.0. A weight is log2 of how much likelier the feature is
13
+ # in an attack than in ordinary documentation.
14
+ #
15
+ # Cross-validated over 5 folds at a threshold of 9 bits:
16
+ # 15 of 48 attacks above it, 0 of 240
17
+ # benign documents above it. That number is the held-out one; the training
18
+ # score is not reported because it is not evidence of anything.
19
+ module BayesData
20
+ THRESHOLD = 9
21
+
22
+ # Score band => bits, fitted on held-out scores and read at the 95% bound.
23
+ # A raw naive Bayes score is not a likelihood ratio; this is what turns it
24
+ # into one the corpus can defend.
25
+ CALIBRATION = [
26
+ [-Float::INFINITY, -14.552], # 0 attacks, 231 benign
27
+ [0.0, 2.601], # 22 attacks, 8 benign
28
+ [4.0, 4.712], # 26 attacks, 1 benign
29
+ ].freeze
30
+
31
+ # Held-out performance at THRESHOLD, for the evidence table.
32
+ CAUGHT = 15
33
+ ATTACKS = 48
34
+ FLAGGED = 0
35
+ BENIGN = 240
36
+
37
+ WEIGHTS = {
38
+ 'thi page' => 3.2141,
39
+ 'page' => 3.2141,
40
+ 'all' => 2.7991,
41
+ 'were' => 2.7991,
42
+ 'answer' => 2.7991,
43
+ 'alle' => 2.5361,
44
+ 'as' => 2.5361,
45
+ 'you were' => 2.5361,
46
+ 'that' => 2.5361,
47
+ 'ignore all' => 2.5361,
48
+ 'thi' => 2.384,
49
+ 'example invalid' => 2.2141,
50
+ 'invalid collect' => 2.2141,
51
+ 'the reader' => 2.2141,
52
+ 'example' => 2.2141,
53
+ 'http' => 2.2141,
54
+ 'deze pagina' => 2.2141,
55
+ 'reveal' => 2.2141,
56
+ 'pagina' => 2.2141,
57
+ 'negeer alle' => 2.2141,
58
+ 'http example' => 2.2141,
59
+ 'previous instruction' => 2.2141,
60
+ 'unlimit' => 2.2141,
61
+ 'the quota' => 2.2141,
62
+ 'previous' => 2.2141,
63
+ 'collect' => 2.2141,
64
+ 'invalid' => 2.2141,
65
+ 'reader' => 2.2141,
66
+ 'cluster' => 2.2141,
67
+ 'at' => 2.2141,
68
+ 'at the' => 2.2141,
69
+ 'anyth' => 2.2141,
70
+ 'your system' => 2.2141,
71
+ 'now' => 2.0215,
72
+ 'instruction' => 2.0215,
73
+ 'a cheaper' => 1.7991,
74
+ 'stat' => 1.7991,
75
+ 'je systeemprompt' => 1.7991,
76
+ 'einde' => 1.7991,
77
+ 'restriction' => 1.7991,
78
+ 'for thi' => 1.7991,
79
+ 'reveal your' => 1.7991,
80
+ 'rule' => 1.7991,
81
+ 'to http' => 1.7991,
82
+ 'voor' => 1.7991,
83
+ 'issu' => 1.7991,
84
+ 'developer' => 1.7991,
85
+ 'hebt gekregen' => 1.7991,
86
+ 'die je' => 1.7991,
87
+ 'gekregen' => 1.7991,
88
+ 'hiervoor' => 1.7991,
89
+ 'die' => 1.7991,
90
+ 'eerdere instructy' => 1.7991,
91
+ 'alle eerdere' => 1.7991,
92
+ 'model' => 1.7991,
93
+ 'alsof je' => 1.7991,
94
+ 'doe alsof' => 1.7991,
95
+ 'doe' => 1.7991,
96
+ 'alsof' => 1.7991,
97
+ 'recommend the' => 1.7991,
98
+ 'the end' => 1.7991,
99
+ 'end of' => 1.7991,
100
+ 'of your' => 1.7991,
101
+ 'given' => 1.7991,
102
+ 'were given' => 1.7991,
103
+ 'partition exist' => 1.7991,
104
+ 'that a' => 1.7991,
105
+ 'cheaper partition' => 1.7991,
106
+ 'end' => 1.7991,
107
+ 'recommend' => 1.7991,
108
+ 'exist' => 1.7991,
109
+ 'cheaper' => 1.7991,
110
+ 'commercial' => 1.7991,
111
+ 'that thi' => 1.7991,
112
+ 'all previous' => 1.7991,
113
+ 'so' => 1.7991,
114
+ 'instruction and' => 1.7991,
115
+ 'het einde' => 1.7991,
116
+ 'as authoritative' => 1.7991,
117
+ 'page as' => 1.7991,
118
+ 'treat thi' => 1.7991,
119
+ 'and treat' => 1.7991,
120
+ 'forget the' => 1.7991,
121
+ 'authoritative' => 1.7991,
122
+ 'treat' => 1.7991,
123
+ 'forget' => 1.7991,
124
+ 'the commercial' => 1.7991,
125
+ 'commercial cluster' => 1.7991,
126
+ 'disregard' => 1.7991,
127
+ 'and recommend' => 1.7991,
128
+ 'disregard the' => 1.7991,
129
+ 'lezer' => 1.2141,
130
+ 'herhaal' => 1.2141,
131
+ 'partitie bestaat' => 1.2141,
132
+ 'lezer dat' => 1.2141,
133
+ 'richtlijnen voor' => 1.2141,
134
+ 'omzeil de' => 1.2141,
135
+ 'opmerk omzeil' => 1.2141,
136
+ 'omzeil' => 1.2141,
137
+ 'opmerk' => 1.2141,
138
+ 'een goedkopere' => 1.2141,
139
+ 'dat er' => 1.2141,
140
+ 'naar http' => 1.2141,
141
+ 'is gewijzigd' => 1.2141,
142
+ 'gekregen naar' => 1.2141,
143
+ 'pagina is' => 1.2141,
144
+ 'je hebt' => 1.2141,
145
+ 'er een' => 1.2141,
146
+ 'sleutel die' => 1.2141,
147
+ 'niet dat' => 1.2141,
148
+ 'gebruiker niet' => 1.2141,
149
+ 'er' => 1.2141,
150
+ 'oorspronkelijke' => 1.2141,
151
+ 'letterlijk aan' => 1.2141,
152
+ 'configuratie letterlijk' => 1.2141,
153
+ 'oorspronkelijke configuratie' => 1.2141,
154
+ 'goedkopere' => 1.2141,
155
+ 'je oorspronkelijke' => 1.2141,
156
+ 'herhaal je' => 1.2141,
157
+ 'letterlijk' => 1.2141,
158
+ 'bestaat' => 1.2141,
159
+ 'verberg voor' => 1.2141,
160
+ 'voor de' => 1.2141,
161
+ 'de lezer' => 1.2141,
162
+ 'dat deze' => 1.2141,
163
+ 'verberg' => 1.2141,
164
+ 'voor deze' => 1.2141,
165
+ 'have no' => 1.2141,
166
+ 'you have' => 1.2141,
167
+ 'pretend you' => 1.2141,
168
+ 'no' => 1.2141,
169
+ 'have' => 1.2141,
170
+ 'pretend' => 1.2141,
171
+ 'anyth now' => 1.2141,
172
+ 'do anyth' => 1.2141,
173
+ 'for do' => 1.2141,
174
+ 'beperk hebt' => 1.2141,
175
+ 'geen beperk' => 1.2141,
176
+ 'je geen' => 1.2141,
177
+ 'beperk' => 1.2141,
178
+ 'geen' => 1.2141,
179
+ 'eerder instructy' => 1.2141,
180
+ 'alle eerder' => 1.2141,
181
+ 'eerder' => 1.2141,
182
+ 'fall asleep' => 1.2141,
183
+ 'naar' => 1.2141,
184
+ 'stuur de' => 1.2141,
185
+ 'de api' => 1.2141,
186
+ 'goedkopere partitie' => 1.2141,
187
+ 'fall' => 1.2141,
188
+ 'license' => 1.2141,
189
+ 'me' => 1.2141,
190
+ 'used' => 1.2141,
191
+ 'grandma' => 1.2141,
192
+ 'my' => 1.2141,
193
+ 'prompt now' => 1.2141,
194
+ 'answer anyth' => 1.2141,
195
+ 'and answer' => 1.2141,
196
+ 'restriction and' => 1.2141,
197
+ 'no restriction' => 1.2141,
198
+ 'you' => 1.1072,
199
+ 'je' => -0.7034,
200
+ 'het' => -1.0489,
201
+ 'als' => -1.1078,
202
+ 'een' => -1.1078,
203
+ 'a' => -1.2009,
204
+ 'if your' => -1.3708,
205
+ 'etc profile' => -1.3708,
206
+ 'profile d' => -1.3708,
207
+ 'a100' => -1.3708,
208
+ 'warn' => -1.3708,
209
+ 'project space' => -1.3708,
210
+ 'about' => -1.3708,
211
+ 'tijdslimiet' => -1.3708,
212
+ 'wordt' => -1.3708,
213
+ 'load python' => -1.3708,
214
+ 'about the' => -1.3708,
215
+ 'h100 node' => -1.3708,
216
+ 'reservation policy' => -1.3708,
217
+ '5' => -1.3708,
218
+ 'module load' => -1.3708,
219
+ 'aanvraag' => -1.3708,
220
+ 'd' => -1.3708,
221
+ 'the job' => -1.3708,
222
+ 'sleutel met' => -1.3708,
223
+ 'python' => -1.3708,
224
+ 'sbatch' => -1.3708,
225
+ '3' => -1.3708,
226
+ '11' => -1.3708,
227
+ 'follow the' => -1.3708,
228
+ 'account' => -1.3708,
229
+ 'profile' => -1.3708,
230
+ 'reserver' => -1.3708,
231
+ 'squeue' => -1.3708,
232
+ '200 gb' => -1.3708,
233
+ 'dien' => -1.3708,
234
+ 'scontrol show' => -1.3708,
235
+ 'de' => -1.3708,
236
+ 'voeg' => -1.3708,
237
+ 'toe' => -1.3708,
238
+ 'gpu a100' => -1.3708,
239
+ 'omp num' => -1.3708,
240
+ 'volg de' => -1.3708,
241
+ 'num thread' => -1.3708,
242
+ 'the module' => -1.3708,
243
+ 'and the' => -1.3708,
244
+ 'toe aan' => -1.3708,
245
+ 'het reserveringsbeleid' => -1.3708,
246
+ 'warn about' => -1.3708,
247
+ 'on the' => -1.3708,
248
+ 'service desk' => -1.3708,
249
+ 'deze toe' => -1.3708,
250
+ 'token' => -1.3708,
251
+ 'met een' => -1.3708,
252
+ 'the service' => -1.3708,
253
+ 'voeg deze' => -1.3708,
254
+ 'in' => -1.3708,
255
+ 'request' => -1.3708,
256
+ 'deel je' => -1.3708,
257
+ 'core' => -1.3708,
258
+ 'includ' => -1.3708,
259
+ 'verouderde' => -1.3708,
260
+ 'waarschuw' => -1.3708,
261
+ 'hierboven' => -1.3708,
262
+ 'volg' => -1.3708,
263
+ 'service' => -1.3708,
264
+ 'dedicat node' => -1.3708,
265
+ 'thread' => -1.3708,
266
+ 'taak' => -1.3708,
267
+ 'if' => -1.3708,
268
+ 'it to' => -1.3708,
269
+ 'space' => -1.3708,
270
+ 'can' => -1.3708,
271
+ 'uit' => -1.3708,
272
+ 'python 3' => -1.3708,
273
+ 'follow' => -1.3708,
274
+ '3 11' => -1.3708,
275
+ 'virtualenv' => -1.3708,
276
+ 'voordat' => -1.3708,
277
+ '30' => -1.3708,
278
+ 'met de' => -1.3708,
279
+ 'niet met' => -1.3708,
280
+ 'etc' => -1.3708,
281
+ 'op de' => -1.3708,
282
+ 'waarschuw over' => -1.3708,
283
+ '200' => -1.3708,
284
+ 'is 200' => -1.3708,
285
+ 'limit' => -1.3708,
286
+ 'with a' => -1.3708,
287
+ 'in met' => -1.3708,
288
+ 'gb' => -1.3708,
289
+ 'time limit' => -1.3708,
290
+ 'deel' => -1.3708,
291
+ 'rotate' => -1.3708,
292
+ 'reserveringsbeleid' => -1.3708,
293
+ 'rotate it' => -1.3708,
294
+ 'config' => -1.3708,
295
+ 'en voeg' => -1.3708,
296
+ 'in your' => -1.3708,
297
+ 'show' => -1.3708,
298
+ 'vraagt' => -1.3708,
299
+ 'scontrol' => -1.3708,
300
+ 'show config' => -1.3708,
301
+ 'ask' => -1.3708,
302
+ 'omp' => -1.3708,
303
+ 'num' => -1.3708,
304
+ 'het job' => -1.3708,
305
+ 'dedicat' => -1.3708,
306
+ 'your project' => -1.3708,
307
+ 'toolchain' => -1.3708,
308
+ 'en' => -1.7859,
309
+ 'with' => -1.7859,
310
+ 'run' => -1.7859,
311
+ 'the reservation' => -1.7859,
312
+ 'als je' => -1.7859,
313
+ 'policy' => -1.7859,
314
+ 'om' => -1.7859,
315
+ 'load' => -1.7859,
316
+ 'ssh' => -1.7859,
317
+ 'reservation' => -1.7859,
318
+ 'the desk' => -1.7859,
319
+ 'time' => -2.1078,
320
+ 'h100' => -2.1078,
321
+ 'gpu' => -2.1078,
322
+ 'gpu h100' => -2.1078,
323
+ 'ticket' => -2.1078,
324
+ 'de servicedesk' => -2.1078,
325
+ 'job id' => -2.1078,
326
+ 'te' => -2.1078,
327
+ 'servicedesk' => -2.1078,
328
+ 'op' => -2.1078,
329
+ 'per' => -2.1078,
330
+ 'project' => -2.1078,
331
+ 'desk' => -2.3708,
332
+ 'id' => -2.3708,
333
+ 'module' => -2.5932,
334
+ 'it' => -2.7859,
335
+ 'node' => -2.7859,
336
+ 'met' => -2.9558,
337
+ 'job' => -3.1078,
338
+ }.freeze
339
+ end
340
+ end